diff --git a/components/dist/notifications.js b/components/dist/notifications.js
index 33ba56d..1f71100 100644
--- a/components/dist/notifications.js
+++ b/components/dist/notifications.js
@@ -20,7 +20,7 @@ smNotifications.innerHTML = `
}
.notification-panel{
display: grid;
- width: 100%;
+ width: min(26rem, 100%);
gap: 0.5rem;
position: fixed;
left: 0;
@@ -29,65 +29,55 @@ smNotifications.innerHTML = `
max-height: 100%;
padding: 1rem;
overflow: hidden auto;
- -ms-scroll-chaining: none;
- overscroll-behavior: contain;
+ overscroll-behavior: contain;
touch-action: none;
}
.notification-panel:empty{
display:none;
}
.notification{
- display: -webkit-box;
- display: -ms-flexbox;
display: flex;
position: relative;
- border-radius: 0.3rem;
+ border-radius: 0.5rem;
background: rgba(var(--foreground-color, (255,255,255)), 1);
overflow: hidden;
overflow-wrap: break-word;
word-wrap: break-word;
- -ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
- -ms-hyphens: auto;
- -webkit-hyphens: auto;
hyphens: auto;
max-width: 100%;
- padding: 1rem;
+ padding: max(1rem,1.5vw);
align-items: center;
box-shadow: 0 0.5rem 1rem 0 rgba(0,0,0,0.14);
touch-action: none;
}
+ .notification:not(.pinned)::before{
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ height: 0.2rem;
+ width: 100%;
+ background-color: var(--accent-color, teal);
+ transform: scaleX(0);
+ animation: loading var(--timeout, 5000ms) linear forwards;
+ transform-origin: left;
+ }
+ @keyframes loading{
+ 0%{
+ transform: scaleX(0);
+ }
+ 100%{
+ transform: scaleX(1);
+ }
+ }
.icon-container:not(:empty){
margin-right: 0.5rem;
height: var(--icon-height);
width: var(--icon-width);
flex-shrink: 0;
}
- h4:first-letter,
- p:first-letter{
- text-transform: uppercase;
- }
- h4{
- font-weight: 400;
- }
- p{
- line-height: 1.6;
- -webkit-box-flex: 1;
- -ms-flex: 1;
- flex: 1;
- color: rgba(var(--text-color, (17,17,17)), 0.9);
- overflow-wrap: break-word;
- overflow-wrap: break-word;
- word-wrap: break-word;
- -ms-word-break: break-all;
- word-break: break-all;
- word-break: break-word;
- -ms-hyphens: auto;
- -webkit-hyphens: auto;
- hyphens: auto;
- max-width: 100%;
- }
.notification:last-of-type{
margin-bottom: 0;
}
@@ -98,11 +88,14 @@ smNotifications.innerHTML = `
}
.icon--success {
fill: var(--green);
- }
- .icon--failure,
- .icon--error {
- fill: var(--danger-color);
- }
+ }
+ .icon--failure,
+ .icon--error {
+ fill: var(--danger-color);
+ }
+ output{
+ width: 100%;
+ }
.close{
height: 2rem;
width: 2rem;
@@ -139,8 +132,6 @@ smNotifications.innerHTML = `
}
@media screen and (min-width: 640px){
.notification-panel{
- max-width: 28rem;
- width: max-content;
top: auto;
bottom: 0;
}
@@ -187,7 +178,7 @@ customElements.define('sm-notifications', class extends HTMLElement {
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.handleTouchMove = this.handleTouchMove.bind(this)
this.startX = 0;
@@ -200,12 +191,16 @@ customElements.define('sm-notifications', class extends HTMLElement {
this.swipeTime = 0;
this.swipeTimeThreshold = 200;
this.currentTarget = null;
+ this.notificationTimeout = 5000;
this.mediaQuery = window.matchMedia('(min-width: 640px)')
this.handleOrientationChange = this.handleOrientationChange.bind(this)
- this.isLandscape = false
+ this.isBigViewport = false
+ }
+ set timeout(value) {
+ if (isNaN(value)) return;
+ this.notificationTimeout = value;
}
-
randString(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
@@ -215,46 +210,44 @@ customElements.define('sm-notifications', class extends HTMLElement {
}
createNotification(message, options = {}) {
- const { pinned = false, icon = '', action } = options;
+ const { pinned = false, icon, action, timeout = this.notificationTimeout } = options;
const notification = document.createElement('div')
notification.id = this.randString(8)
notification.className = `notification ${pinned ? 'pinned' : ''}`
- const iconContainer = document.createElement('div')
- iconContainer.className = 'icon-container'
- iconContainer.innerHTML = icon
- const output = document.createElement('output')
- output.textContent = message
- notification.append(iconContainer, output)
+ notification.style.setProperty('--timeout', `${timeout}ms`);
+ notification.innerHTML = `
+ ${icon ? `
${icon}
` : ''}
+
+ ${action ? `` : ''}
+ ${pinned ? `` : ''}
+ `;
if (action) {
- const button = document.createElement('button')
- button.className = 'action'
- button.innerText = action.label
- button.addEventListener('click', action.callback)
+ notification.querySelector('.action').addEventListener('click', action.callback)
}
if (pinned) {
- const button = document.createElement('button')
- button.className = 'close'
- button.innerHTML = `
-
- `
- button.addEventListener('click', () => {
- this.remove(notification.id)
- })
- notification.append(button)
+ notification.querySelector('.close').addEventListener('click', () => {
+ this.removeNotification(notification);
+ });
+ } else {
+ setTimeout(() => {
+ this.removeNotification(notification, this.isBigViewport ? 'left' : 'top');
+ }, timeout);
}
return notification;
}
push(message, options = {}) {
const notification = this.createNotification(message, options);
- if (this.isLandscape)
+ if (this.isBigViewport)
this.notificationPanel.append(notification);
else
this.notificationPanel.prepend(notification);
this.notificationPanel.animate(
[
{
- transform: `translateY(${this.isLandscape ? '' : '-'}${notification.clientHeight}px)`,
+ transform: `translateY(${this.isBigViewport ? '' : '-'}${notification.clientHeight}px)`,
},
{
transform: `none`,
@@ -274,26 +267,40 @@ customElements.define('sm-notifications', class extends HTMLElement {
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();
- };
+ const sign = direction === 'left' || direction === 'top' ? '-' : '+';
+
+ if (!this.isBigViewport && direction === 'top') {
+ notification.animate([
+ {
+ transform: this.currentX ? `translateY(${this.currentX}px)` : `none`,
+ opacity: '1'
+ },
+ {
+ transform: `translateY(calc(${sign}${Math.abs(this.currentX)}px ${sign} 1rem))`,
+ opacity: '0'
+ }
+ ], this.animationOptions).onfinish = () => {
+ notification.remove();
+ };
+ } else {
+ 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}`);
@@ -307,13 +314,13 @@ customElements.define('sm-notifications', class extends HTMLElement {
});
}
- handlePointerMove(e) {
- this.currentX = e.clientX - this.startX;
+ handleTouchMove(e) {
+ this.currentX = e.touches[0].clientX - this.startX;
this.currentTarget.style.transform = `translateX(${this.currentX}px)`;
}
handleOrientationChange(e) {
- this.isLandscape = e.matches
+ this.isBigViewport = e.matches
if (e.matches) {
// landscape
@@ -326,22 +333,21 @@ customElements.define('sm-notifications', class extends HTMLElement {
this.handleOrientationChange(this.mediaQuery);
this.mediaQuery.addEventListener('change', this.handleOrientationChange);
- this.notificationPanel.addEventListener('pointerdown', e => {
+ this.notificationPanel.addEventListener('touchstart', 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.startX = e.touches[0].clientX;
+ this.startY = e.touches[0].clientY;
+ this.notificationPanel.addEventListener('touchmove', this.handleTouchMove, { passive: true });
}
- });
- this.notificationPanel.addEventListener('pointerup', e => {
- this.endX = e.clientX;
- this.endY = e.clientY;
+ }, { passive: true });
+ this.notificationPanel.addEventListener('touchend', e => {
+ this.endX = e.changedTouches[0].clientX;
+ this.endY = e.changedTouches[0].clientY;
this.swipeDistance = Math.abs(this.endX - this.startX);
this.swipeTime = Date.now() - this.startTime;
if (this.endX > this.startX) {
@@ -369,24 +375,9 @@ customElements.define('sm-notifications', class extends HTMLElement {
}
}
}
- this.notificationPanel.removeEventListener('pointermove', this.handlePointerMove)
- this.notificationPanel.releasePointerCapture(e.pointerId);
+ this.notificationPanel.removeEventListener('touchmove', this.handleTouchMove)
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);
diff --git a/components/dist/notifications.min.js b/components/dist/notifications.min.js
index 0d5fd6b..c478141 100644
--- a/components/dist/notifications.min.js
+++ b/components/dist/notifications.min.js
@@ -1 +1 @@
-const smNotifications=document.createElement("template");smNotifications.innerHTML='\n \n \n ',customElements.define("sm-notifications",class extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}).append(smNotifications.content.cloneNode(!0)),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=!1}randString(n){let t="";const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";for(let i=0;i\n ',n.addEventListener("click",()=>{this.remove(r.id)}),r.append(n)}return r}push(n,t={}){const e=this.createNotification(n,t);return this.isLandscape?this.notificationPanel.append(e):this.notificationPanel.prepend(e),this.notificationPanel.animate([{transform:`translateY(${this.isLandscape?"":"-"}${e.clientHeight}px)`},{transform:"none"}],this.animationOptions),e.animate([{transform:"translateY(-1rem)",opacity:"0"},{transform:"none",opacity:"1"}],this.animationOptions).onfinish=(n=>{n.target.commitStyles(),n.target.cancel()}),e.querySelector(".action")&&e.querySelector(".action").addEventListener("click",t.action.callback),e.id}removeNotification(n,t="left"){if(!n)return;const e="left"===t?"-":"+";n.animate([{transform:this.currentX?`translateX(${this.currentX}px)`:"none",opacity:"1"},{transform:`translateX(calc(${e}${Math.abs(this.currentX)}px ${e} 1rem))`,opacity:"0"}],this.animationOptions).onfinish=(()=>{n.remove()})}remove(n){const t=this.notificationPanel.querySelector(`#${n}`);t&&this.removeNotification(t)}clearAll(){Array.from(this.notificationPanel.children).forEach(n=>{this.removeNotification(n)})}handlePointerMove(n){this.currentX=n.clientX-this.startX,this.currentTarget.style.transform=`translateX(${this.currentX}px)`}handleOrientationChange(n){this.isLandscape=n.matches,n.matches}connectedCallback(){this.handleOrientationChange(this.mediaQuery),this.mediaQuery.addEventListener("change",this.handleOrientationChange),this.notificationPanel.addEventListener("pointerdown",n=>{n.target.closest(".close")?this.removeNotification(n.target.closest(".notification")):n.target.closest(".notification")&&(this.swipeThreshold=n.target.closest(".notification").getBoundingClientRect().width/2,this.currentTarget=n.target.closest(".notification"),this.currentTarget.setPointerCapture(n.pointerId),this.startTime=Date.now(),this.startX=n.clientX,this.startY=n.clientY,this.notificationPanel.addEventListener("pointermove",this.handlePointerMove))}),this.notificationPanel.addEventListener("pointerup",n=>{this.endX=n.clientX,this.endY=n.clientY,this.swipeDistance=Math.abs(this.endX-this.startX),this.swipeTime=Date.now()-this.startTime,this.endX>this.startX?this.swipeDirection="right":this.swipeDirection="left",this.swipeTime50&&this.removeNotification(this.currentTarget,this.swipeDirection):this.swipeDistance>this.swipeThreshold?this.removeNotification(this.currentTarget,this.swipeDirection):this.currentTarget.animate([{transform:`translateX(${this.currentX}px)`},{transform:"none"}],this.animationOptions).onfinish=(n=>{n.target.commitStyles(),n.target.cancel()}),this.notificationPanel.removeEventListener("pointermove",this.handlePointerMove),this.notificationPanel.releasePointerCapture(n.pointerId),this.currentX=0});const n=new MutationObserver(n=>{n.forEach(n=>{"childList"===n.type&&n.addedNodes.length&&!n.addedNodes[0].classList.contains("pinned")&&setTimeout(()=>{this.removeNotification(n.addedNodes[0])},5e3)})});n.observe(this.notificationPanel,{childList:!0})}disconnectedCallback(){mediaQueryList.removeEventListener("change",handleOrientationChange)}});
\ No newline at end of file
+const smNotifications=document.createElement("template");smNotifications.innerHTML="\n \n \n ",customElements.define("sm-notifications",class extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}).append(smNotifications.content.cloneNode(!0)),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.handleTouchMove=this.handleTouchMove.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.notificationTimeout=5e3,this.mediaQuery=window.matchMedia("(min-width: 640px)"),this.handleOrientationChange=this.handleOrientationChange.bind(this),this.isBigViewport=!1}set timeout(t){isNaN(t)||(this.notificationTimeout=t)}randString(t){let n="";const i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";for(let e=0;e${e}`:""}\n \n ${o?``:""}\n ${i?'':""}\n `,o&&r.querySelector(".action").addEventListener("click",o.callback),i?r.querySelector(".close").addEventListener("click",()=>{this.removeNotification(r)}):setTimeout(()=>{this.removeNotification(r,this.isBigViewport?"left":"top")},a),r}push(t,n={}){const i=this.createNotification(t,n);return this.isBigViewport?this.notificationPanel.append(i):this.notificationPanel.prepend(i),this.notificationPanel.animate([{transform:`translateY(${this.isBigViewport?"":"-"}${i.clientHeight}px)`},{transform:"none"}],this.animationOptions),i.animate([{transform:"translateY(-1rem)",opacity:"0"},{transform:"none",opacity:"1"}],this.animationOptions).onfinish=(t=>{t.target.commitStyles(),t.target.cancel()}),i.id}removeNotification(t,n="left"){if(!t)return;const i="left"===n||"top"===n?"-":"+";this.isBigViewport||"top"!==n?t.animate([{transform:this.currentX?`translateX(${this.currentX}px)`:"none",opacity:"1"},{transform:`translateX(calc(${i}${Math.abs(this.currentX)}px ${i} 1rem))`,opacity:"0"}],this.animationOptions).onfinish=(()=>{t.remove()}):t.animate([{transform:this.currentX?`translateY(${this.currentX}px)`:"none",opacity:"1"},{transform:`translateY(calc(${i}${Math.abs(this.currentX)}px ${i} 1rem))`,opacity:"0"}],this.animationOptions).onfinish=(()=>{t.remove()})}remove(t){const n=this.notificationPanel.querySelector(`#${t}`);n&&this.removeNotification(n)}clearAll(){Array.from(this.notificationPanel.children).forEach(t=>{this.removeNotification(t)})}handleTouchMove(t){this.currentX=t.touches[0].clientX-this.startX,this.currentTarget.style.transform=`translateX(${this.currentX}px)`}handleOrientationChange(t){this.isBigViewport=t.matches,t.matches}connectedCallback(){this.handleOrientationChange(this.mediaQuery),this.mediaQuery.addEventListener("change",this.handleOrientationChange),this.notificationPanel.addEventListener("touchstart",t=>{t.target.closest(".close")?this.removeNotification(t.target.closest(".notification")):t.target.closest(".notification")&&(this.swipeThreshold=t.target.closest(".notification").getBoundingClientRect().width/2,this.currentTarget=t.target.closest(".notification"),this.startTime=Date.now(),this.startX=t.touches[0].clientX,this.startY=t.touches[0].clientY,this.notificationPanel.addEventListener("touchmove",this.handleTouchMove,{passive:!0}))},{passive:!0}),this.notificationPanel.addEventListener("touchend",t=>{this.endX=t.changedTouches[0].clientX,this.endY=t.changedTouches[0].clientY,this.swipeDistance=Math.abs(this.endX-this.startX),this.swipeTime=Date.now()-this.startTime,this.endX>this.startX?this.swipeDirection="right":this.swipeDirection="left",this.swipeTime50&&this.removeNotification(this.currentTarget,this.swipeDirection):this.swipeDistance>this.swipeThreshold?this.removeNotification(this.currentTarget,this.swipeDirection):this.currentTarget.animate([{transform:`translateX(${this.currentX}px)`},{transform:"none"}],this.animationOptions).onfinish=(t=>{t.target.commitStyles(),t.target.cancel()}),this.notificationPanel.removeEventListener("touchmove",this.handleTouchMove),this.currentX=0})}disconnectedCallback(){mediaQueryList.removeEventListener("change",handleOrientationChange)}});
\ No newline at end of file
diff --git a/components/test.html b/components/test.html
index 0a9600c..b2b3d47 100644
--- a/components/test.html
+++ b/components/test.html
@@ -21,6 +21,7 @@
+