dialog-lite
Version:
DialogLite is designed to control a dialog box (modal window) on a web page, providing the functionality to open, close and apply custom styles through a simple interface.
101 lines (86 loc) • 8.69 kB
JavaScript
;Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=`:root {
--z-index-dialog-lite: 992;
--z-index-dialog-lite-backdrop: 993;
--z-index-dialog-lite-container: 994;
}
.dialog-lite {
position: fixed;
inset: 0;
z-index: var(--z-index-dialog-lite, 992);
width: 100vw;
overflow: clip auto;
}
.dialog-lite--in {
-webkit-overflow-scrolling: touch;
}
.dialog-lite--out {
pointer-events: none;
}
.dialog-lite__backdrop {
position: fixed;
inset: 0;
margin: auto;
z-index: var(--z-index-dialog-lite-backdrop, 993);
}
.dialog-lite--in .dialog-lite__backdrop {
background-color: var(--c-dialog-lite-backdrop-in, hsla(240deg 22% 6% / 82%));
transition: background-color 400ms cubic-bezier(0.61, 1, 0.88, 1);
}
.dialog-lite--out .dialog-lite__backdrop {
pointer-events: none;
background-color: var(--c-dialog-lite-backdrop-out, hsla(200deg 2% 6% / 0%));
transition: background-color 500ms cubic-bezier(0, 0, 0.5, 1);
}
.dialog-lite__container {
pointer-events: none;
position: relative;
z-index: var(--z-index-dialog-lite-container, 994);
display: grid;
place-content: center;
width: 100vw;
}
@supports (min-height: 100dvh) {
.dialog-lite__container {
min-height: 100dvh;
}
}
@supports not (min-height: 100dvh) {
.dialog-lite__container {
min-height: 100vh;
}
}
.dialog-lite__container-inner {
position: relative;
margin: 20px;
}
.dialog-lite--in .dialog-lite__container-inner {
pointer-events: auto;
opacity: 1;
transform: translateY(0);
transition:
opacity 400ms cubic-bezier(0.61, 1, 0.88, 1),
transform 400ms cubic-bezier(0.61, 1, 0.88, 1);
}
.dialog-lite--out .dialog-lite__container-inner {
pointer-events: none;
opacity: 0;
transform: translateY(40px);
transition:
opacity 500ms cubic-bezier(0, 0, 0.5, 1),
transform 550ms cubic-bezier(0.22, 1, 0.5, 0.95);
}
.dialog-lite-close-button {
cursor: pointer;
position: absolute;
inset: 0 0 auto auto;
display: grid;
place-content: center;
width: 50px;
height: 50px;
}
.dialog-lite-close-button .svg-icon {
width: 24px;
height: 24px;
fill: black;
}
`,c=r;function u(l,t){const e=typeof CSS<"u"&&"escape"in CSS?CSS.escape(t):t.replace(/"/g,'\\"');return l.querySelector(`#${e}`)}function a(l={}){const{target:t=document,cssText:e=r,id:i="dialog-lite-styles"}=l,n=u(t,i);if(n&&n instanceof HTMLStyleElement)return n.textContent=e,n;const o=document.createElement("style");return o.id=i,o.textContent=e,t instanceof Document?t.head.append(o):t.append(o),o}class s{options;dialogEl=null;dialogCloseEl=null;dialogBackdropEl=null;mainContentEl=null;currentExtraClass="";previouslyFocusedElement=null;lastActionTime=0;isOpen=!1;abortController=null;hideTimeout=null;removeExtraClassTimeout=null;prevBodyOverflow=null;prevBodyPaddingRight=null;constructor(t={}){this.options={closingButton:t.closingButton??!1,closingBackdrop:t.closingBackdrop??!1,dialog:t.dialog??".dialog-lite",mainContent:t.mainContent??"#main-content",closeButtonSelector:t.closeButtonSelector??".dialog-lite-close-button",backdropSelector:t.backdropSelector??".dialog-lite__backdrop",debounceMs:t.debounceMs??500,hideDelayMs:t.hideDelayMs??500,focusOnOpenSelector:t.focusOnOpenSelector??'[tabindex="0"]',lockScroll:t.lockScroll??!0,trapFocus:t.trapFocus??!0,role:t.role??"dialog",ariaModal:t.ariaModal??!0,emitEvents:t.emitEvents??!0}}resolveHTMLElement(t){return t==null?null:typeof t=="string"?document.querySelector(t):t}resolveElementsOrThrow(){if(this.dialogEl=this.resolveHTMLElement(this.options.dialog),this.mainContentEl=this.resolveHTMLElement(this.options.mainContent??null),!this.dialogEl)throw new Error("Dialog element not found. Provide { dialog } option or ensure `.dialog-lite` exists.");this.dialogCloseEl=this.dialogEl.querySelector(this.options.closeButtonSelector),this.dialogBackdropEl=this.dialogEl.querySelector(this.options.backdropSelector),this.dialogEl.hasAttribute("tabindex")||this.dialogEl.setAttribute("tabindex","-1"),this.options.role&&this.dialogEl.setAttribute("role",this.options.role),this.options.ariaModal&&this.dialogEl.setAttribute("aria-modal","true")}emit(t,e){!this.options.emitEvents||!this.dialogEl||this.dialogEl.dispatchEvent(new CustomEvent(t,{detail:e}))}lockScroll(){if(!this.options.lockScroll||this.prevBodyOverflow!=null)return;const t=document.body;this.prevBodyOverflow=t.style.overflow,this.prevBodyPaddingRight=t.style.paddingRight;const e=window.innerWidth-document.documentElement.clientWidth;if(e>0){const i=Number.parseFloat(getComputedStyle(t).paddingRight||"0");t.style.paddingRight=`${i+e}px`}t.style.overflow="hidden"}unlockScroll(){if(this.prevBodyOverflow==null)return;const t=document.body;t.style.overflow=this.prevBodyOverflow,t.style.paddingRight=this.prevBodyPaddingRight??"",this.prevBodyOverflow=null,this.prevBodyPaddingRight=null}getFocusableElements(){return this.dialogEl?Array.from(this.dialogEl.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')).filter(i=>!i.hasAttribute("disabled")&&i.tabIndex>=0&&i.getClientRects().length>0):[]}handleFocusTrapKeydown(t){if(!this.options.trapFocus||!this.isOpen||t.key!=="Tab")return;const e=this.getFocusableElements();if(!e.length){this.dialogEl?.focus?.(),t.preventDefault();return}const i=document.activeElement,n=i instanceof HTMLElement?e.indexOf(i):-1,d=t.shiftKey?n<=0?e.length-1:n-1:n>=e.length-1?0:n+1;e[d]?.focus?.(),t.preventDefault()}clearTimers(){this.hideTimeout!=null&&(window.clearTimeout(this.hideTimeout),this.hideTimeout=null),this.removeExtraClassTimeout!=null&&(window.clearTimeout(this.removeExtraClassTimeout),this.removeExtraClassTimeout=null)}ensureInitialized(){this.abortController||this.init()}init(){this.destroy(),this.resolveElementsOrThrow(),this.abortController=new AbortController;const t=this.abortController.signal;this.options.closingButton&&this.dialogCloseEl&&this.dialogCloseEl.addEventListener("click",()=>this.close(),{signal:t}),this.options.closingBackdrop&&this.dialogBackdropEl&&this.dialogBackdropEl.addEventListener("click",()=>this.close(),{signal:t}),document.addEventListener("keydown",e=>{e.key==="Escape"&&this.isOpen&&this.close()},{signal:t}),this.dialogEl?.addEventListener("keydown",e=>this.handleFocusTrapKeydown(e),{signal:t})}destroy(){this.abortController?.abort(),this.abortController=null,this.clearTimers(),this.unlockScroll()}open({stylingClass:t=""}={}){if(this.isDebounced()||(this.ensureInitialized(),!this.dialogEl))return;this.clearTimers(),this.dialogEl.hidden=!1,this.dialogEl.offsetWidth,this.isOpen=!0,this.lockScroll(),this.mainContentEl&&this.mainContentEl.setAttribute("aria-hidden","true"),this.dialogEl.setAttribute("aria-hidden","false");const e=document.activeElement;this.previouslyFocusedElement=e instanceof HTMLElement?e:null,this.updateClassList({addClass:"dialog-lite--in",removeClass:"dialog-lite--out",newClass:t});const i=this.dialogEl.querySelector(this.options.focusOnOpenSelector);i?.focus?.(),i||this.dialogEl.focus(),this.emit("dialog-lite:open",{stylingClass:t})}close(){this.isDebounced()||(this.ensureInitialized(),this.dialogEl&&(this.isOpen=!1,this.unlockScroll(),this.mainContentEl&&this.mainContentEl.setAttribute("aria-hidden","false"),this.dialogEl.setAttribute("aria-hidden","true"),this.previouslyFocusedElement?.isConnected&&this.previouslyFocusedElement.focus(),this.updateClassList({addClass:"dialog-lite--out",removeClass:"dialog-lite--in",newClass:"",delayRemove:!0}),this.hideTimeout=window.setTimeout(()=>{this.dialogEl&&(this.dialogEl.hidden=!0),this.hideTimeout=null},this.options.hideDelayMs),this.emit("dialog-lite:close",{})))}updateClassList({addClass:t,removeClass:e,newClass:i,delayRemove:n=!1}){if(this.dialogEl){if(this.currentExtraClass)if(n){const o=this.currentExtraClass;this.removeExtraClassTimeout=window.setTimeout(()=>{this.dialogEl?.classList.remove(o),this.currentExtraClass="",this.removeExtraClassTimeout=null},this.options.hideDelayMs)}else this.dialogEl.classList.remove(this.currentExtraClass),this.currentExtraClass="";this.dialogEl.classList.remove(e),this.dialogEl.classList.add(t),i&&(this.dialogEl.classList.add(i),this.currentExtraClass=i)}}isDebounced(){const t=Date.now();return t-this.lastActionTime<this.options.debounceMs?!0:(this.lastActionTime=t,!1)}}function h(l={}){return new s(l)}function g(l={}){const{injectCss:t=!0,cssText:e,cssTarget:i,...n}=l;t&&a({cssText:e,target:i});const o=new s(n);return o.init(),o}exports.DialogLite=s;exports.createDialogLite=h;exports.dialogLiteCssText=c;exports.initDialogLite=g;exports.injectDialogLiteCss=a;