//notifications
const smNotifications = document.createElement('template')
smNotifications.innerHTML = `
`;
customElements.define('sm-notifications', class extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: 'open'
}).append(smNotifications.content.cloneNode(true))
this.notificationPanel = this.shadowRoot.querySelector('.notification-panel')
this.animationOptions = {
duration: 300,
fill: "forwards",
easing: "cubic-bezier(0.175, 0.885, 0.32, 1.275)"
}
this.push = this.push.bind(this)
this.createNotification = this.createNotification.bind(this)
this.removeNotification = this.removeNotification.bind(this)
this.clearAll = this.clearAll.bind(this)
this.remove = this.remove.bind(this)
this.handlePointerMove = this.handlePointerMove.bind(this)
this.startX = 0;
this.currentX = 0;
this.endX = 0;
this.swipeDistance = 0;
this.swipeDirection = '';
this.swipeThreshold = 0;
this.startTime = 0;
this.swipeTime = 0;
this.swipeTimeThreshold = 200;
this.currentTarget = null;
this.mediaQuery = window.matchMedia('(min-width: 640px)')
this.handleOrientationChange = this.handleOrientationChange.bind(this)
this.isLandscape = false
}
randString(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
for (let i = 0; i < length; i++)
result += characters.charAt(Math.floor(Math.random() * characters.length));
return result;
}
createNotification(message, options = {}) {
const { pinned = false, icon = '', action } = options;
const notification = document.createElement('div')
notification.id = this.randString(8)
notification.classList.add('notification');
let composition = ``;
composition += `
${icon}
`;
if (action) {
composition += `
`
}
if (pinned) {
notification.classList.add('pinned');
composition += `
`;
}
notification.innerHTML = composition;
return notification;
}
push(message, options = {}) {
const notification = this.createNotification(message, options);
if (this.isLandscape)
this.notificationPanel.append(notification);
else
this.notificationPanel.prepend(notification);
this.notificationPanel.animate(
[
{
transform: `translateY(${this.isLandscape ? '' : '-'}${notification.clientHeight}px)`,
},
{
transform: `none`,
},
], this.animationOptions
)
notification.animate([
{
transform: `translateY(-1rem)`,
opacity: '0'
},
{
transform: `none`,
opacity: '1'
},
], this.animationOptions).onfinish = (e) => {
e.target.commitStyles()
e.target.cancel()
}
if (notification.querySelector('.action'))
notification.querySelector('.action').addEventListener('click', options.action.callback)
return notification.id;
}
removeNotification(notification, direction = 'left') {
if (!notification) return;
const sign = direction === 'left' ? '-' : '+';
notification.animate([
{
transform: this.currentX ? `translateX(${this.currentX}px)` : `none`,
opacity: '1'
},
{
transform: `translateX(calc(${sign}${Math.abs(this.currentX)}px ${sign} 1rem))`,
opacity: '0'
}
], this.animationOptions).onfinish = () => {
notification.remove();
};
}
remove(id) {
const notification = this.notificationPanel.querySelector(`#${id}`);
if (!notification) return;
this.removeNotification(notification);
}
clearAll() {
Array.from(this.notificationPanel.children).forEach(child => {
this.removeNotification(child);
});
}
handlePointerMove(e) {
this.currentX = e.clientX - this.startX;
this.currentTarget.style.transform = `translateX(${this.currentX}px)`;
}
handleOrientationChange(e) {
this.isLandscape = e.matches
if (e.matches) {
// landscape
} else {
// portrait
}
}
connectedCallback() {
this.handleOrientationChange(this.mediaQuery);
this.mediaQuery.addEventListener('change', this.handleOrientationChange);
this.notificationPanel.addEventListener('pointerdown', e => {
if (e.target.closest('.close')) {
this.removeNotification(e.target.closest('.notification'));
} else if (e.target.closest('.notification')) {
this.swipeThreshold = e.target.closest('.notification').getBoundingClientRect().width / 2;
this.currentTarget = e.target.closest('.notification');
this.currentTarget.setPointerCapture(e.pointerId);
this.startTime = Date.now();
this.startX = e.clientX;
this.startY = e.clientY;
this.notificationPanel.addEventListener('pointermove', this.handlePointerMove);
}
});
this.notificationPanel.addEventListener('pointerup', e => {
this.endX = e.clientX;
this.endY = e.clientY;
this.swipeDistance = Math.abs(this.endX - this.startX);
this.swipeTime = Date.now() - this.startTime;
if (this.endX > this.startX) {
this.swipeDirection = 'right';
} else {
this.swipeDirection = 'left';
}
if (this.swipeTime < this.swipeTimeThreshold) {
if (this.swipeDistance > 50)
this.removeNotification(this.currentTarget, this.swipeDirection);
} else {
if (this.swipeDistance > this.swipeThreshold) {
this.removeNotification(this.currentTarget, this.swipeDirection);
} else {
this.currentTarget.animate([
{
transform: `translateX(${this.currentX}px)`,
},
{
transform: `none`,
},
], this.animationOptions).onfinish = (e) => {
e.target.commitStyles()
e.target.cancel()
}
}
}
this.notificationPanel.removeEventListener('pointermove', this.handlePointerMove)
this.notificationPanel.releasePointerCapture(e.pointerId);
this.currentX = 0;
});
const observer = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length && !mutation.addedNodes[0].classList.contains('pinned')) {
setTimeout(() => {
this.removeNotification(mutation.addedNodes[0]);
}, 5000);
}
}
});
});
observer.observe(this.notificationPanel, {
childList: true,
});
}
disconnectedCallback() {
mediaQueryList.removeEventListener('change', handleOrientationChange);
}
});