1 line
10 KiB
JavaScript
1 line
10 KiB
JavaScript
class Stack{constructor(){this.items=[]}push(t){this.items.push(t)}pop(){return 0==this.items.length?"Underflow":this.items.pop()}peek(){return this.items[this.items.length-1]}}const popupStack=new Stack,smPopup=document.createElement("template");smPopup.innerHTML='\n<style>\n*{\n padding: 0;\n margin: 0;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n} \n:host{\n position: fixed;\n display: -ms-grid;\n display: grid;\n z-index: 10;\n --accent-color: #4d2588;\n --text-color: 17, 17, 17;\n --background-color: 255, 255, 255;\n --width: 100%;\n --height: auto;\n --min-width: auto;\n --min-height: auto;\n --backdrop-background: rgba(0, 0, 0, 0.6);\n --border-radius: 0.8rem 0.8rem 0 0;\n}\n.popup-container{\n display: -ms-grid;\n display: grid;\n position: fixed;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n place-items: center;\n z-index: 10;\n touch-action: none;\n}\n:host(.stacked) .popup{\n -webkit-transform: scale(0.9) translateY(-2rem) !important;\n transform: scale(0.9) translateY(-2rem) !important;\n}\n.background{\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n pointer-events: none;\n background: var(--backdrop-background);\n -webkit-transition: opacity 0.3s;\n -o-transition: opacity 0.3s;\n transition: opacity 0.3s;\n}\n.popup{\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n flex-direction: column;\n position: relative;\n -ms-flex-item-align: end;\n align-self: flex-end;\n -webkit-box-align: start;\n -ms-flex-align: start;\n align-items: flex-start;\n width: var(--width);\n min-width: var(--min-width);\n height: var(--height);\n min-height: var(--min-height);\n max-height: 90vh;\n border-radius: var(--border-radius);\n background: rgba(var(--background-color), 1);\n -webkit-box-shadow: 0 -1rem 2rem #00000020;\n box-shadow: 0 -1rem 2rem #00000020;\n}\n.container-header{\n display: -webkit-box;\n display: flex;\n width: 100%;\n touch-action: none;\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n}\n.popup-top{\n display: -webkit-box;\n display: flex;\n width: 100%;\n}\n.popup-body{\n display: -webkit-box;\n display: flex;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n -ms-flex-direction: column;\n flex-direction: column;\n -webkit-box-flex: 1;\n -ms-flex: 1;\n flex: 1;\n width: 100%;\n padding: var(--body-padding, 1.5rem);\n overflow-y: auto;\n}\n.hide{\n display:none;\n}\n@media screen and (min-width: 640px){\n :host{\n --border-radius: 0.5rem;\n }\n .popup{\n -ms-flex-item-align: center;\n -ms-grid-row-align: center;\n align-self: center;\n border-radius: var(--border-radius);\n height: var(--height);\n -webkit-box-shadow: 0 3rem 2rem -0.5rem #00000040;\n box-shadow: 0 3rem 2rem -0.5rem #00000040;\n }\n}\n@media screen and (max-width: 640px){\n .popup-top{\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n flex-direction: column;\n -webkit-box-align: center;\n align-items: center;\n }\n .handle{\n height: 0.3rem;\n width: 2rem;\n background: rgba(var(--text-color), .4);\n border-radius: 1rem;\n margin: 0.5rem 0;\n }\n}\n@media (any-hover: hover){\n ::-webkit-scrollbar{\n width: 0.5rem;\n }\n \n ::-webkit-scrollbar-thumb{\n background: rgba(var(--text-color), 0.3);\n border-radius: 1rem;\n &:hover{\n background: rgba(var(--text-color), 0.5);\n }\n }\n}\n</style>\n<div class="popup-container hide" role="dialog">\n <div part="background" class="background"></div>\n <div part="popup" class="popup">\n <div part="popup-header" class="popup-top">\n <div class="handle"></div>\n <slot name="header"></slot>\n </div>\n <div part="popup-body" class="popup-body">\n <slot></slot>\n </div>\n </div>\n</div>\n',customElements.define("sm-popup",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smPopup.content.cloneNode(!0)),this.allowClosing=!1,this.isOpen=!1,this.pinned=!1,this.offset=0,this.touchStartY=0,this.touchEndY=0,this.touchStartTime=0,this.touchEndTime=0,this.touchEndAnimation=void 0,this.focusable,this.autoFocus,this.mutationObserver,this.popupContainer=this.shadowRoot.querySelector(".popup-container"),this.backdrop=this.shadowRoot.querySelector(".background"),this.popup=this.shadowRoot.querySelector(".popup"),this.popupBodySlot=this.shadowRoot.querySelector(".popup-body slot"),this.popupHeader=this.shadowRoot.querySelector(".popup-top"),this.resumeScrolling=this.resumeScrolling.bind(this),this.setStateOpen=this.setStateOpen.bind(this),this.show=this.show.bind(this),this.hide=this.hide.bind(this),this.handleTouchStart=this.handleTouchStart.bind(this),this.handleTouchMove=this.handleTouchMove.bind(this),this.handleTouchEnd=this.handleTouchEnd.bind(this),this.detectFocus=this.detectFocus.bind(this)}static get observedAttributes(){return["open"]}get open(){return this.isOpen}animateTo(t,e,n){const i=t.animate(e,{...n,fill:"both"});return i.finished.then(()=>{i.commitStyles(),i.cancel()}),i}resumeScrolling(){const t=document.body.style.top;window.scrollTo(0,-1*parseInt(t||"0")),document.body.style.overflow="auto",document.body.style.top="initial"}setStateOpen(){if(!this.isOpen||this.offset){const t={duration:300,easing:"ease"},e=window.innerWidth>640?"scale(1.1)":`translateY(${this.offset?`${this.offset}px`:"100%"})`;this.animateTo(this.popup,[{opacity:this.offset?1:0,transform:e},{opacity:1,transform:"none"}],t)}}show(t={}){const{pinned:e=!1}=t;if(!this.isOpen){const t={duration:300,easing:"ease"};popupStack&&(popupStack.push({popup:this,permission:e}),popupStack.items.length>1&&this.animateTo(popupStack.items[popupStack.items.length-2].popup.shadowRoot.querySelector(".popup"),[{transform:"none"},{transform:"translateY(-1.5rem) scale(0.9)"}],t)),this.popupContainer.classList.remove("hide"),this.offset||this.backdrop.animate([{opacity:0},{opacity:1}],t),this.setStateOpen(),this.dispatchEvent(new CustomEvent("popupopened",{bubbles:!0,detail:{popup:this}})),this.pinned=e,this.isOpen=!0,document.body.style.overflow="hidden",document.body.style.top=`-${window.scrollY}px`;const n=this.autoFocus||this.focusable[0];n.tagName.includes("SM-")?n.focusIn():n.focus(),this.hasAttribute("open")||this.setAttribute("open","")}}hide(){const t={duration:150,easing:"ease"};this.backdrop.animate([{opacity:1},{opacity:0}],t),this.animateTo(this.popup,[{opacity:1,transform:window.innerWidth>640?"none":`translateY(${this.offset?`${this.offset}px`:"0"})`},{opacity:0,transform:window.innerWidth>640?"scale(1.1)":"translateY(100%)"}],t).finished.finally(()=>{this.popupContainer.classList.add("hide"),this.popup.style="",this.removeAttribute("open"),void 0!==popupStack?(popupStack.pop(),popupStack.items.length?this.animateTo(popupStack.items[popupStack.items.length-1].popup.shadowRoot.querySelector(".popup"),[{transform:"translateY(-1.5rem) scale(0.9)"},{transform:"none"}],t):this.resumeScrolling()):this.resumeScrolling(),this.forms.length&&this.forms.forEach(t=>t.reset()),this.dispatchEvent(new CustomEvent("popupclosed",{bubbles:!0,detail:{popup:this}})),this.isOpen=!1})}handleTouchStart(t){this.offset=0,this.popupHeader.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.popupHeader.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.touchStartY=t.changedTouches[0].clientY,this.touchStartTime=t.timeStamp}handleTouchMove(t){this.touchStartY<t.changedTouches[0].clientY&&(this.offset=t.changedTouches[0].clientY-this.touchStartY,this.touchEndAnimation=window.requestAnimationFrame(()=>{this.popup.style.transform=`translateY(${this.offset}px)`}))}handleTouchEnd(t){if(this.touchEndTime=t.timeStamp,cancelAnimationFrame(this.touchEndAnimation),this.touchEndY=t.changedTouches[0].clientY,this.threshold=.3*this.popup.getBoundingClientRect().height,this.touchEndTime-this.touchStartTime>200)if(this.touchEndY-this.touchStartY>this.threshold){if(this.pinned)return void this.setStateOpen();this.hide()}else this.setStateOpen();else if(this.touchEndY>this.touchStartY){if(this.pinned)return void this.setStateOpen();this.hide()}this.popupHeader.removeEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.popupHeader.removeEventListener("touchend",this.handleTouchEnd,{passive:!0})}detectFocus(t){if("Tab"===t.code){const e=this.focusable[this.focusable.length-1],n=this.focusable[0];t.shiftKey&&document.activeElement===n?(t.preventDefault(),e.tagName.includes("SM-")?e.focusIn():e.focus()):t.shiftKey||document.activeElement!==e||(t.preventDefault(),n.tagName.includes("SM-")?n.focusIn():n.focus())}}updateFocusableList(){this.focusable=this.querySelectorAll('sm-button:not([disabled]), button:not([disabled]), [href], sm-input, input, sm-select, select, sm-checkbox, sm-textarea, textarea, [tabindex]:not([tabindex="-1"])'),this.autoFocus=this.querySelector("[autofocus]")}connectedCallback(){this.popupBodySlot.addEventListener("slotchange",()=>{this.forms=this.querySelectorAll("sm-form"),this.updateFocusableList()}),this.popupContainer.addEventListener("mousedown",t=>{t.target!==this.popupContainer||this.pinned||(this.pinned?this.setStateOpen():this.hide())});const t=new ResizeObserver(t=>{for(let e of t)if(e.contentBoxSize){const t=Array.isArray(e.contentBoxSize)?e.contentBoxSize[0]:e.contentBoxSize;this.threshold=.3*t.blockSize.height}else this.threshold=.3*e.contentRect.height});t.observe(this),this.mutationObserver=new MutationObserver(t=>{this.updateFocusableList()}),this.mutationObserver.observe(this,{attributes:!0,childList:!0,subtree:!0}),this.addEventListener("keydown",this.detectFocus),this.popupHeader.addEventListener("touchstart",this.handleTouchStart,{passive:!0})}disconnectedCallback(){this.removeEventListener("keydown",this.detectFocus),resizeObserver.unobserve(),this.mutationObserver.disconnect(),this.popupHeader.removeEventListener("touchstart",this.handleTouchStart,{passive:!0})}attributeChangedCallback(t){"open"===t&&this.hasAttribute("open")&&this.show()}}); |