@substrate-system/dialog 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +10 -27
- package/dist/index.cjs.map +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -27
- package/dist/index.js.map +2 -2
- package/dist/index.min.js +1 -1
- package/dist/meta.json +4 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -84,9 +84,6 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
84
84
|
_closable = true;
|
|
85
85
|
_showIcon = true;
|
|
86
86
|
_noClick = false;
|
|
87
|
-
// =======================
|
|
88
|
-
// Lifecycle: constructor.
|
|
89
|
-
// =======================
|
|
90
87
|
constructor() {
|
|
91
88
|
super();
|
|
92
89
|
this._bind();
|
|
@@ -96,16 +93,8 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
96
93
|
const contentNodes = Array.from(this.childNodes);
|
|
97
94
|
return this._buildModal(contentNodes);
|
|
98
95
|
}
|
|
99
|
-
// ============================
|
|
100
|
-
// Helper: build modal structure.
|
|
101
|
-
// ============================
|
|
102
96
|
_buildModal(contentNodes) {
|
|
103
|
-
|
|
104
|
-
const trap = document.createElement("span");
|
|
105
|
-
trap.classList.add("modal-focus-trap");
|
|
106
|
-
trap.tabIndex = 0;
|
|
107
|
-
return trap;
|
|
108
|
-
}, "createFocusTrap");
|
|
97
|
+
createFocusTrap();
|
|
109
98
|
const scroll = document.createElement("div");
|
|
110
99
|
scroll.classList.add("modal-scroll");
|
|
111
100
|
this._modalScroll = scroll;
|
|
@@ -140,9 +129,6 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
140
129
|
scroll.appendChild(this._focusTrap2);
|
|
141
130
|
this.appendChild(scroll);
|
|
142
131
|
}
|
|
143
|
-
// ============================
|
|
144
|
-
// Lifecycle: watch attributes.
|
|
145
|
-
// ============================
|
|
146
132
|
static get observedAttributes() {
|
|
147
133
|
return [ACTIVE, ANIMATED, ARIA_DESCRIBEDBY, CLOSE];
|
|
148
134
|
}
|
|
@@ -284,21 +270,17 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
284
270
|
this._modal.removeAttribute(ARIA_DESCRIBEDBY);
|
|
285
271
|
}
|
|
286
272
|
}
|
|
287
|
-
// ========================
|
|
288
|
-
// Helper: set active flag.
|
|
289
|
-
// ========================
|
|
290
273
|
_setActiveFlag() {
|
|
291
274
|
const isActive = this.getAttribute(ACTIVE) === TRUE;
|
|
292
275
|
this._isActive = isActive;
|
|
293
276
|
this._toggleModalDisplay(() => {
|
|
294
277
|
if (this._isActive) {
|
|
295
278
|
this._focusModal();
|
|
279
|
+
} else {
|
|
280
|
+
(0, import_scroll_lock.unlockBodyScrolling)(document.body);
|
|
296
281
|
}
|
|
297
282
|
});
|
|
298
283
|
}
|
|
299
|
-
// ======================
|
|
300
|
-
// Helper: focus element.
|
|
301
|
-
// ======================
|
|
302
284
|
_focusElement(element) {
|
|
303
285
|
window.requestAnimationFrame(() => {
|
|
304
286
|
if (typeof element.focus === "function") {
|
|
@@ -319,9 +301,6 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
319
301
|
}
|
|
320
302
|
});
|
|
321
303
|
}
|
|
322
|
-
// =============================
|
|
323
|
-
// Helper: detect outside modal.
|
|
324
|
-
// =============================
|
|
325
304
|
_isOutsideModal(element) {
|
|
326
305
|
if (!this._isActive || !element || !this._modal) {
|
|
327
306
|
return false;
|
|
@@ -337,9 +316,6 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
337
316
|
const { matches } = window.matchMedia(PREFERS_REDUCED_MOTION);
|
|
338
317
|
return this._isAnimated && !matches;
|
|
339
318
|
}
|
|
340
|
-
// =====================
|
|
341
|
-
// Helper: toggle modal.
|
|
342
|
-
// =====================
|
|
343
319
|
_toggleModalDisplay(callback) {
|
|
344
320
|
if (!this._modalScroll) return;
|
|
345
321
|
this.setAttribute(ACTIVE, this._isActive);
|
|
@@ -452,4 +428,11 @@ class ModalWindow extends import_web_component.WebComponent.create("modal-window
|
|
|
452
428
|
}
|
|
453
429
|
}
|
|
454
430
|
(0, import_web_component.define)(ModalWindow.TAG, ModalWindow);
|
|
431
|
+
function createFocusTrap() {
|
|
432
|
+
const trap = document.createElement("span");
|
|
433
|
+
trap.classList.add("modal-focus-trap");
|
|
434
|
+
trap.tabIndex = 0;
|
|
435
|
+
return trap;
|
|
436
|
+
}
|
|
437
|
+
__name(createFocusTrap, "createFocusTrap");
|
|
455
438
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { WebComponent, define } from '@substrate-system/web-component'\nimport {\n lockBodyScrolling,\n unlockBodyScrolling\n} from '@substrate-system/scroll-lock'\n\n// for docuement.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'modal-window':ModalWindow\n }\n}\n\n/**\n * Modal window web component.\n *\n * Opens/closes via the `active` attribute:\n * modal.setAttribute('active', 'true') // open\n * modal.setAttribute('active', 'false') // close\n * modal.removeAttribute('active') // close\n *\n * Or via methods:\n * modal.open()\n * modal.close()\n */\n\n// ==========\n// Constants.\n// ==========\n\nconst ACTIVE = 'active'\nconst ANIMATED = 'animated'\nconst ANIMATION_DURATION = 250\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ARIA_LABEL = 'aria-label'\nconst CLOSE = 'close'\nconst CLOSE_TITLE = 'Close'\nconst NO_ICON = 'no-icon'\nconst CLASS_HIDE = 'modal-hide'\nconst CLASS_SHOW = 'modal-show'\nconst CLASS_VISIBLE = 'modal-visible'\nconst ESCAPE = 'escape'\nconst FALSE = 'false'\nconst FOCUSIN = 'focusin'\nconst KEYDOWN = 'keydown'\nconst MODAL_LABEL_FALLBACK = 'modal'\nconst NOCLICK = 'noclick'\nconst PREFERS_REDUCED_MOTION = '(prefers-reduced-motion: reduce)'\nconst SPACE = ' '\nconst SPACE_REGEX = /\\s+/g\nconst TAB = 'tab'\nconst TRUE = 'true'\n\nconst FOCUSABLE_SELECTORS = [\n '[contenteditable]',\n '[tabindex=\"0\"]:not([disabled])',\n 'a[href]',\n 'audio[controls]',\n 'button:not([disabled])',\n 'iframe',\n \"input:not([disabled]):not([type='hidden'])\",\n 'select:not([disabled])',\n 'summary',\n 'textarea:not([disabled])',\n 'video[controls]',\n].join(',')\n\n// ====================\n// The component\n// ====================\n\nexport class ModalWindow extends WebComponent.create('modal-window') {\n // Element references (set during build).\n _buttonClose:HTMLButtonElement|null = null\n _modal:HTMLDialogElement|null = null\n _modalOverlay:HTMLDivElement|null = null\n _modalScroll:HTMLDivElement|null = null\n _modalContent:HTMLDivElement|null = null\n _focusTrap1:HTMLSpanElement|null = null\n _focusTrap2:HTMLSpanElement|null = null\n _heading:HTMLElement|null = null\n\n static TAG:string = 'modal-window'\n\n // State.\n _activeElement:HTMLElement|null = null\n _isActive = false\n _isAnimated = true\n _isBuilt = false\n _isHideShow = false\n _timerForHide:number|undefined\n _timerForShow:number|undefined\n _closable:boolean = true\n _showIcon:boolean = true\n _noClick:boolean = false\n\n // =======================\n // Lifecycle: constructor.\n // =======================\n\n constructor () {\n super()\n this._bind()\n }\n\n // for TS, inheritance\n render () {\n const contentNodes = Array.from(this.childNodes)\n return this._buildModal(contentNodes)\n }\n\n // ============================\n // Helper: build modal structure.\n // ============================\n\n _buildModal (contentNodes:Node[]):void {\n // Create focus trap\n const createFocusTrap = () => {\n const trap = document.createElement('span')\n trap.classList.add('modal-focus-trap')\n trap.tabIndex = 0\n return trap\n }\n\n // Create scroll container\n const scroll = document.createElement('div')\n scroll.classList.add('modal-scroll')\n this._modalScroll = scroll\n\n // Create overlay\n const overlay = document.createElement('div')\n overlay.classList.add('modal-overlay')\n this._modalOverlay = overlay\n\n // Create dialog\n const dialog = document.createElement('dialog')\n dialog.setAttribute('aria-modal', 'true')\n dialog.classList.add('modal-dialog')\n dialog.tabIndex = -1\n this._modal = dialog\n\n // Create close button if closable and icon should be shown\n if (this._closable && this._showIcon) {\n const closeBtn = document.createElement('button')\n closeBtn.classList.add('modal-close')\n closeBtn.type = 'button'\n closeBtn.innerHTML = '×'\n dialog.appendChild(closeBtn)\n this._buttonClose = closeBtn\n }\n\n // Create content wrapper\n const content = document.createElement('div')\n content.classList.add('modal-content')\n this._modalContent = content\n\n // Move content nodes into the content wrapper\n contentNodes.forEach(node => {\n content.appendChild(node)\n })\n\n dialog.appendChild(content)\n\n // Create focus traps\n this._focusTrap1 = createFocusTrap()\n this._focusTrap2 = createFocusTrap()\n\n // Assemble structure\n overlay.appendChild(dialog)\n scroll.appendChild(this._focusTrap1)\n scroll.appendChild(overlay)\n scroll.appendChild(this._focusTrap2)\n\n // Add to component\n this.appendChild(scroll)\n }\n\n // ============================\n // Lifecycle: watch attributes.\n // ============================\n\n static get observedAttributes () {\n return [ACTIVE, ANIMATED, ARIA_DESCRIBEDBY, CLOSE]\n }\n\n // ==============================\n // Lifecycle: attributes changed.\n // ==============================\n\n async attributeChangedCallback (\n name:string,\n oldValue:string,\n newValue:string\n ) {\n // Different old/new values?\n if (oldValue !== newValue) {\n if (name === ACTIVE) {\n this._setActiveFlag()\n }\n\n if (name === ANIMATED) {\n this._setAnimationFlag()\n }\n\n if (name === ARIA_DESCRIBEDBY) {\n this._setModalDescription()\n }\n\n if (name === CLOSE) {\n this._setCloseTitle()\n }\n }\n }\n\n // ===========================\n // Lifecycle: component mount.\n // ===========================\n\n connectedCallback () {\n // Build modal structure once.\n if (!this._isBuilt) {\n this._closable = this.getAttribute('closable') !== 'false'\n this._showIcon = !this.hasAttribute(NO_ICON)\n this._noClick = this.hasAttribute(NOCLICK)\n\n // Get heading for aria-label.\n this._heading = this.querySelector('h1, h2, h3, h4, h5, h6')\n\n // Collect all child nodes.\n const contentNodes = Array.from(this.childNodes)\n\n // Build the modal structure.\n this._buildModal(contentNodes)\n\n // Set animation flag.\n this._setAnimationFlag()\n\n // Set close title.\n this._setCloseTitle()\n\n // Set modal label.\n this._setModalLabel()\n\n // Set modal description.\n this._setModalDescription()\n\n // Set active flag.\n this._setActiveFlag()\n\n this._isBuilt = true\n }\n\n this._addEvents()\n }\n\n // =============================\n // Lifecycle: component unmount.\n // =============================\n\n disconnectedCallback () {\n this._removeEvents()\n }\n\n // ============================\n // Helper: bind `this` context.\n // ============================\n\n _bind () {\n const propertyNames = Object.getOwnPropertyNames(\n Object.getPrototypeOf(this)\n ) as (keyof ModalWindow)[]\n\n propertyNames.forEach((name) => {\n // Bind functions.\n if (typeof this[name] === 'function') {\n // @ts-expect-error bind\n this[name] = this[name].bind(this)\n }\n })\n }\n\n // ===================\n // Helper: add events.\n // ===================\n\n _addEvents () {\n // Prevent doubles.\n this._removeEvents()\n\n document.addEventListener(FOCUSIN, this._handleFocusIn)\n document.addEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.addEventListener('click',\n this._handleClickClose)\n }\n if (this._modalOverlay) {\n this._modalOverlay.addEventListener('click',\n this._handleClickOverlay)\n }\n }\n\n // ======================\n // Helper: remove events.\n // ======================\n\n _removeEvents () {\n document.removeEventListener(FOCUSIN, this._handleFocusIn)\n document.removeEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.removeEventListener(\n 'click',\n this._handleClickClose\n )\n }\n if (this._modalOverlay) {\n this._modalOverlay.removeEventListener(\n 'click',\n this._handleClickOverlay\n )\n }\n }\n\n // ===========================\n // Helper: set animation flag.\n // ===========================\n\n _setAnimationFlag () {\n this._isAnimated = this.getAttribute(ANIMATED) !== FALSE\n }\n\n // ========================\n // Helper: add close title.\n // ========================\n\n _setCloseTitle () {\n // Get title.\n const title = this.getAttribute(CLOSE) || CLOSE_TITLE\n\n // Set title.\n if (this._buttonClose) {\n this._buttonClose.title = title\n this._buttonClose.setAttribute(ARIA_LABEL, title)\n }\n }\n\n // ========================\n // Helper: add modal label.\n // ========================\n\n _setModalLabel () {\n // Set later.\n let label = MODAL_LABEL_FALLBACK\n\n // Heading exists?\n if (this._heading) {\n // Get text.\n label = this._heading.textContent || label\n label = label.trim().replace(SPACE_REGEX, SPACE)\n }\n\n // Set label.\n if (this._modal) {\n this._modal.setAttribute(ARIA_LABEL, label)\n }\n }\n\n // ==============================\n // Helper: set modal description.\n // ==============================\n\n _setModalDescription () {\n if (!this._modal) return\n\n const describedBy = this.getAttribute(ARIA_DESCRIBEDBY)\n\n if (describedBy) {\n this._modal.setAttribute(ARIA_DESCRIBEDBY, describedBy)\n } else {\n this._modal.removeAttribute(ARIA_DESCRIBEDBY)\n }\n }\n\n // ========================\n // Helper: set active flag.\n // ========================\n\n _setActiveFlag () {\n // Get flag.\n const isActive = this.getAttribute(ACTIVE) === TRUE\n\n // Set flag.\n this._isActive = isActive\n\n // Set display.\n this._toggleModalDisplay(() => {\n // Focus modal?\n if (this._isActive) {\n this._focusModal()\n }\n })\n }\n\n // ======================\n // Helper: focus element.\n // ======================\n\n _focusElement (element: HTMLElement) {\n window.requestAnimationFrame(() => {\n if (typeof element.focus === 'function') {\n element.focus()\n }\n })\n }\n\n // ====================\n // Helper: focus modal.\n // ====================\n\n _focusModal () {\n window.requestAnimationFrame(() => {\n if (this._modal) {\n this._modal.focus()\n }\n if (this._modalScroll) {\n this._modalScroll.scrollTo(0, 0)\n }\n })\n }\n\n // =============================\n // Helper: detect outside modal.\n // =============================\n\n _isOutsideModal (element?: HTMLElement) {\n // Early exit.\n if (!this._isActive || !element || !this._modal) {\n return false\n }\n\n // Has element?\n const hasElement = this.contains(element) || this._modal.contains(element)\n\n // Get boolean.\n const bool = !hasElement\n\n // Expose boolean.\n return bool\n }\n\n // ===========================\n // Helper: detect motion pref.\n // ===========================\n\n _isMotionOkay () {\n // Get pref.\n const { matches } = window.matchMedia(PREFERS_REDUCED_MOTION)\n\n // Expose boolean.\n return this._isAnimated && !matches\n }\n\n // =====================\n // Helper: toggle modal.\n // =====================\n\n _toggleModalDisplay (callback: () => void) {\n if (!this._modalScroll) return\n\n // @ts-expect-error boolean\n this.setAttribute(ACTIVE, this._isActive)\n\n // Get booleans.\n const isModalVisible = this._modalScroll.classList.contains(CLASS_VISIBLE)\n const isMotionOkay = this._isMotionOkay()\n\n // Get delay.\n const delay = isMotionOkay ? ANIMATION_DURATION : 0\n\n // Get active element.\n const activeElement = document.activeElement as HTMLElement\n\n // Cache active element?\n if (this._isActive && activeElement) {\n this._activeElement = activeElement\n }\n\n // =============\n // Modal active?\n // =============\n\n if (this._isActive) {\n // Show modal.\n this._modalScroll.classList.add(CLASS_VISIBLE)\n\n // Lock body scrolling.\n lockBodyScrolling(document.body)\n\n // Set flag.\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_SHOW)\n }\n\n // Fire callback.\n callback()\n\n // Await CSS animation.\n this._timerForShow = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForShow)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_SHOW)\n }, delay)\n } else if (isModalVisible) {\n // Set flag.\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_HIDE)\n }\n\n // Fire callback?\n callback()\n\n // Await CSS animation.\n this._timerForHide = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForHide)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_HIDE)\n\n // Hide modal.\n this._modalScroll?.classList.remove(CLASS_VISIBLE)\n\n // Unlock body scrolling.\n unlockBodyScrolling(document.body)\n\n // Delay.\n }, delay)\n }\n }\n\n // =====================\n // Event: overlay click.\n // =====================\n\n _handleClickOverlay (event:MouseEvent) {\n if (this._isHideShow || this._noClick) return\n if (!this._closable) return\n\n // Get layer.\n const target = event.target as HTMLElement\n\n // Outside modal? (clicked directly on overlay, not dialog)\n if (target === this._modalOverlay) {\n this.close()\n }\n }\n\n // ====================\n // Event: close button click.\n // ====================\n\n _handleClickClose () {\n this.close()\n }\n\n // =========================\n // Event: focus in document.\n // =========================\n\n _handleFocusIn () {\n if (!this._isActive || !this._modal) return\n\n const activeElement = document.activeElement as HTMLElement\n\n // Get booleans.\n const isFocusTrap1 = activeElement === this._focusTrap1\n const isFocusTrap2 = activeElement === this._focusTrap2\n\n // Get focusable elements in modal.\n const focusList = Array.from(\n this._modal.querySelectorAll(FOCUSABLE_SELECTORS)\n ) as HTMLElement[]\n\n // Get first & last items.\n const focusItemFirst = focusList[0]\n const focusItemLast = focusList[focusList.length - 1]\n\n // Focus trap: above?\n if (isFocusTrap1 && focusItemLast) {\n this._focusElement(focusItemLast)\n\n // Focus trap: below?\n } else if (isFocusTrap2 && focusItemFirst) {\n this._focusElement(focusItemFirst)\n\n // Outside modal?\n } else if (this._isOutsideModal(activeElement)) {\n this._focusModal()\n }\n }\n\n // =================\n // Event: key press.\n // =================\n\n _handleKeyDown ({ key }:KeyboardEvent) {\n if (!this._isActive) return\n\n key = key.toLowerCase()\n\n // Escape key?\n if (\n key === ESCAPE &&\n !this._isHideShow &&\n this._closable\n ) {\n this.close()\n }\n\n // Tab key?\n if (key === TAB) {\n this._handleFocusIn()\n }\n }\n\n // =================\n // Public: open modal.\n // =================\n\n open () {\n this._isActive = true\n this._toggleModalDisplay(() => {\n this._focusModal()\n })\n }\n\n // =================\n // Public: close modal.\n // =================\n\n close () {\n this._isActive = false\n this._toggleModalDisplay(() => {\n if (this._activeElement) {\n this._focusElement(this._activeElement)\n }\n })\n this.emit('close')\n }\n}\n\ndefine(ModalWindow.TAG, ModalWindow)\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAAqC;AACrC,yBAGO;AA0BP,MAAM,SAAS;AACf,MAAM,WAAW;AACjB,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,QAAQ;AACd,MAAM,cAAc;AACpB,MAAM,UAAU;AAChB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,gBAAgB;AACtB,MAAM,SAAS;AACf,MAAM,QAAQ;AACd,MAAM,UAAU;AAChB,MAAM,UAAU;AAChB,MAAM,uBAAuB;AAC7B,MAAM,UAAU;AAChB,MAAM,yBAAyB;AAC/B,MAAM,QAAQ;AACd,MAAM,cAAc;AACpB,MAAM,MAAM;AACZ,MAAM,OAAO;AAEb,MAAM,sBAAsB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,EAAE,KAAK,GAAG;AAMH,MAAM,oBAAoB,kCAAa,OAAO,cAAc,EAAE;AAAA,EAvErE,OAuEqE;AAAA;AAAA;AAAA;AAAA,EAEjE,eAAsC;AAAA,EACtC,SAAgC;AAAA,EAChC,gBAAoC;AAAA,EACpC,eAAmC;AAAA,EACnC,gBAAoC;AAAA,EACpC,cAAmC;AAAA,EACnC,cAAmC;AAAA,EACnC,WAA4B;AAAA,EAE5B,OAAO,MAAa;AAAA;AAAA,EAGpB,iBAAkC;AAAA,EAClC,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,YAAoB;AAAA,EACpB,YAAoB;AAAA,EACpB,WAAmB;AAAA
|
|
4
|
+
"sourcesContent": ["import { WebComponent, define } from '@substrate-system/web-component'\nimport {\n lockBodyScrolling,\n unlockBodyScrolling\n} from '@substrate-system/scroll-lock'\n\n// for docuement.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'modal-window':ModalWindow\n }\n}\n\n/**\n * Modal window web component.\n *\n * Opens/closes via the `active` attribute:\n * modal.setAttribute('active', 'true') // open\n * modal.setAttribute('active', 'false') // close\n * modal.removeAttribute('active') // close\n *\n * Or via methods:\n * modal.open()\n * modal.close()\n */\n\n// ==========\n// Constants.\n// ==========\n\nconst ACTIVE = 'active'\nconst ANIMATED = 'animated'\nconst ANIMATION_DURATION = 250\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ARIA_LABEL = 'aria-label'\nconst CLOSE = 'close'\nconst CLOSE_TITLE = 'Close'\nconst NO_ICON = 'no-icon'\nconst CLASS_HIDE = 'modal-hide'\nconst CLASS_SHOW = 'modal-show'\nconst CLASS_VISIBLE = 'modal-visible'\nconst ESCAPE = 'escape'\nconst FALSE = 'false'\nconst FOCUSIN = 'focusin'\nconst KEYDOWN = 'keydown'\nconst MODAL_LABEL_FALLBACK = 'modal'\nconst NOCLICK = 'noclick'\nconst PREFERS_REDUCED_MOTION = '(prefers-reduced-motion: reduce)'\nconst SPACE = ' '\nconst SPACE_REGEX = /\\s+/g\nconst TAB = 'tab'\nconst TRUE = 'true'\n\nconst FOCUSABLE_SELECTORS = [\n '[contenteditable]',\n '[tabindex=\"0\"]:not([disabled])',\n 'a[href]',\n 'audio[controls]',\n 'button:not([disabled])',\n 'iframe',\n \"input:not([disabled]):not([type='hidden'])\",\n 'select:not([disabled])',\n 'summary',\n 'textarea:not([disabled])',\n 'video[controls]',\n].join(',')\n\n// ====================\n// The component\n// ====================\n\nexport class ModalWindow extends WebComponent.create('modal-window') {\n // Element references (set during build).\n _buttonClose:HTMLButtonElement|null = null\n _modal:HTMLDialogElement|null = null\n _modalOverlay:HTMLDivElement|null = null\n _modalScroll:HTMLDivElement|null = null\n _modalContent:HTMLDivElement|null = null\n _focusTrap1:HTMLSpanElement|null = null\n _focusTrap2:HTMLSpanElement|null = null\n _heading:HTMLElement|null = null\n\n static TAG:string = 'modal-window'\n\n // State.\n _activeElement:HTMLElement|null = null\n _isActive = false\n _isAnimated = true\n _isBuilt = false\n _isHideShow = false\n _timerForHide:number|undefined\n _timerForShow:number|undefined\n _closable:boolean = true\n _showIcon:boolean = true\n _noClick:boolean = false\n\n constructor () {\n super()\n this._bind()\n }\n\n // for TS, inheritance\n render () {\n const contentNodes = Array.from(this.childNodes)\n return this._buildModal(contentNodes)\n }\n\n _buildModal (contentNodes:Node[]):void {\n // Create focus trap\n createFocusTrap()\n\n // Create scroll container\n const scroll = document.createElement('div')\n scroll.classList.add('modal-scroll')\n this._modalScroll = scroll\n\n // Create overlay\n const overlay = document.createElement('div')\n overlay.classList.add('modal-overlay')\n this._modalOverlay = overlay\n\n // Create dialog\n const dialog = document.createElement('dialog')\n dialog.setAttribute('aria-modal', 'true')\n dialog.classList.add('modal-dialog')\n dialog.tabIndex = -1\n this._modal = dialog\n\n // Create close button if closable and icon should be shown\n if (this._closable && this._showIcon) {\n const closeBtn = document.createElement('button')\n closeBtn.classList.add('modal-close')\n closeBtn.type = 'button'\n closeBtn.innerHTML = '×'\n dialog.appendChild(closeBtn)\n this._buttonClose = closeBtn\n }\n\n // Create content wrapper\n const content = document.createElement('div')\n content.classList.add('modal-content')\n this._modalContent = content\n\n // Move content nodes into the content wrapper\n contentNodes.forEach(node => {\n content.appendChild(node)\n })\n\n dialog.appendChild(content)\n\n // Create focus traps\n this._focusTrap1 = createFocusTrap()\n this._focusTrap2 = createFocusTrap()\n\n // Assemble structure\n overlay.appendChild(dialog)\n scroll.appendChild(this._focusTrap1)\n scroll.appendChild(overlay)\n scroll.appendChild(this._focusTrap2)\n\n // Add to component\n this.appendChild(scroll)\n }\n\n static get observedAttributes () {\n return [ACTIVE, ANIMATED, ARIA_DESCRIBEDBY, CLOSE]\n }\n\n // ==============================\n // Lifecycle: attributes changed.\n // ==============================\n\n async attributeChangedCallback (\n name:string,\n oldValue:string,\n newValue:string\n ) {\n // Different old/new values?\n if (oldValue !== newValue) {\n if (name === ACTIVE) {\n this._setActiveFlag()\n }\n\n if (name === ANIMATED) {\n this._setAnimationFlag()\n }\n\n if (name === ARIA_DESCRIBEDBY) {\n this._setModalDescription()\n }\n\n if (name === CLOSE) {\n this._setCloseTitle()\n }\n }\n }\n\n // ===========================\n // Lifecycle: component mount.\n // ===========================\n\n connectedCallback () {\n // Build modal structure once.\n if (!this._isBuilt) {\n this._closable = this.getAttribute('closable') !== 'false'\n this._showIcon = !this.hasAttribute(NO_ICON)\n this._noClick = this.hasAttribute(NOCLICK)\n\n // Get heading for aria-label.\n this._heading = this.querySelector('h1, h2, h3, h4, h5, h6')\n\n // Collect all child nodes.\n const contentNodes = Array.from(this.childNodes)\n\n // Build the modal structure.\n this._buildModal(contentNodes)\n\n // Set animation flag.\n this._setAnimationFlag()\n\n // Set close title.\n this._setCloseTitle()\n\n // Set modal label.\n this._setModalLabel()\n\n // Set modal description.\n this._setModalDescription()\n\n // Set active flag.\n this._setActiveFlag()\n\n this._isBuilt = true\n }\n\n this._addEvents()\n }\n\n // =============================\n // Lifecycle: component unmount.\n // =============================\n\n disconnectedCallback () {\n this._removeEvents()\n }\n\n // ============================\n // Helper: bind `this` context.\n // ============================\n\n _bind () {\n const propertyNames = Object.getOwnPropertyNames(\n Object.getPrototypeOf(this)\n ) as (keyof ModalWindow)[]\n\n propertyNames.forEach((name) => {\n // Bind functions.\n if (typeof this[name] === 'function') {\n // @ts-expect-error bind\n this[name] = this[name].bind(this)\n }\n })\n }\n\n // ===================\n // Helper: add events.\n // ===================\n\n _addEvents () {\n // Prevent doubles.\n this._removeEvents()\n\n document.addEventListener(FOCUSIN, this._handleFocusIn)\n document.addEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.addEventListener('click',\n this._handleClickClose)\n }\n if (this._modalOverlay) {\n this._modalOverlay.addEventListener('click',\n this._handleClickOverlay)\n }\n }\n\n // ======================\n // Helper: remove events.\n // ======================\n\n _removeEvents () {\n document.removeEventListener(FOCUSIN, this._handleFocusIn)\n document.removeEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.removeEventListener(\n 'click',\n this._handleClickClose\n )\n }\n if (this._modalOverlay) {\n this._modalOverlay.removeEventListener(\n 'click',\n this._handleClickOverlay\n )\n }\n }\n\n // ===========================\n // Helper: set animation flag.\n // ===========================\n\n _setAnimationFlag () {\n this._isAnimated = this.getAttribute(ANIMATED) !== FALSE\n }\n\n // ========================\n // Helper: add close title.\n // ========================\n\n _setCloseTitle () {\n // Get title.\n const title = this.getAttribute(CLOSE) || CLOSE_TITLE\n\n // Set title.\n if (this._buttonClose) {\n this._buttonClose.title = title\n this._buttonClose.setAttribute(ARIA_LABEL, title)\n }\n }\n\n // ========================\n // Helper: add modal label.\n // ========================\n\n _setModalLabel () {\n // Set later.\n let label = MODAL_LABEL_FALLBACK\n\n // Heading exists?\n if (this._heading) {\n // Get text.\n label = this._heading.textContent || label\n label = label.trim().replace(SPACE_REGEX, SPACE)\n }\n\n // Set label.\n if (this._modal) {\n this._modal.setAttribute(ARIA_LABEL, label)\n }\n }\n\n // ==============================\n // Helper: set modal description.\n // ==============================\n\n _setModalDescription () {\n if (!this._modal) return\n\n const describedBy = this.getAttribute(ARIA_DESCRIBEDBY)\n\n if (describedBy) {\n this._modal.setAttribute(ARIA_DESCRIBEDBY, describedBy)\n } else {\n this._modal.removeAttribute(ARIA_DESCRIBEDBY)\n }\n }\n\n _setActiveFlag () {\n // Get flag.\n const isActive = this.getAttribute(ACTIVE) === TRUE\n\n // Set flag.\n this._isActive = isActive\n\n // Set display.\n this._toggleModalDisplay(() => {\n // Focus modal?\n if (this._isActive) {\n this._focusModal()\n } else {\n unlockBodyScrolling(document.body)\n }\n })\n }\n\n _focusElement (element: HTMLElement) {\n window.requestAnimationFrame(() => {\n if (typeof element.focus === 'function') {\n element.focus()\n }\n })\n }\n\n // ====================\n // Helper: focus modal.\n // ====================\n\n _focusModal () {\n window.requestAnimationFrame(() => {\n if (this._modal) {\n this._modal.focus()\n }\n if (this._modalScroll) {\n this._modalScroll.scrollTo(0, 0)\n }\n })\n }\n\n _isOutsideModal (element?:HTMLElement):boolean {\n if (!this._isActive || !element || !this._modal) {\n return false\n }\n\n // Has element?\n const hasElement = this.contains(element) || this._modal.contains(element)\n\n const bool = !hasElement\n return bool\n }\n\n // ===========================\n // Helper: detect motion pref.\n // ===========================\n _isMotionOkay ():boolean {\n // Get pref.\n const { matches } = window.matchMedia(PREFERS_REDUCED_MOTION)\n\n return this._isAnimated && !matches\n }\n\n _toggleModalDisplay (callback:()=>void) {\n if (!this._modalScroll) return\n\n // @ts-expect-error boolean\n this.setAttribute(ACTIVE, this._isActive)\n\n const isModalVisible = this._modalScroll.classList.contains(CLASS_VISIBLE)\n const isMotionOkay = this._isMotionOkay()\n\n // Get delay.\n const delay = isMotionOkay ? ANIMATION_DURATION : 0\n\n // Get active element.\n const activeElement = document.activeElement as HTMLElement\n\n // Cache active element?\n if (this._isActive && activeElement) {\n this._activeElement = activeElement\n }\n\n // =============\n // Modal active?\n // =============\n if (this._isActive) {\n // Show modal.\n this._modalScroll.classList.add(CLASS_VISIBLE)\n\n // Lock body scrolling.\n lockBodyScrolling(document.body)\n\n // Set flag.\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_SHOW)\n }\n\n // Fire callback.\n callback()\n\n // Await CSS animation.\n this._timerForShow = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForShow)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_SHOW)\n }, delay)\n } else if (isModalVisible) { // not active, is visible\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_HIDE)\n }\n\n // Fire callback?\n callback()\n\n // Await CSS animation.\n this._timerForHide = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForHide)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_HIDE)\n\n // Hide modal.\n this._modalScroll?.classList.remove(CLASS_VISIBLE)\n\n // Unlock body scrolling.\n unlockBodyScrolling(document.body)\n\n // Delay.\n }, delay)\n }\n }\n\n // =====================\n // Event: overlay click.\n // =====================\n\n _handleClickOverlay (event:MouseEvent) {\n if (this._isHideShow || this._noClick) return\n if (!this._closable) return\n\n // Get layer.\n const target = event.target as HTMLElement\n\n // Outside modal? (clicked directly on overlay, not dialog)\n if (target === this._modalOverlay) {\n this.close()\n }\n }\n\n // ====================\n // Event: close button click.\n // ====================\n\n _handleClickClose () {\n this.close()\n }\n\n // =========================\n // Event: focus in document.\n // =========================\n\n _handleFocusIn () {\n if (!this._isActive || !this._modal) return\n\n const activeElement = document.activeElement as HTMLElement\n\n // Get booleans.\n const isFocusTrap1 = activeElement === this._focusTrap1\n const isFocusTrap2 = activeElement === this._focusTrap2\n\n // Get focusable elements in modal.\n const focusList = Array.from(\n this._modal.querySelectorAll(FOCUSABLE_SELECTORS)\n ) as HTMLElement[]\n\n // Get first & last items.\n const focusItemFirst = focusList[0]\n const focusItemLast = focusList[focusList.length - 1]\n\n // Focus trap: above?\n if (isFocusTrap1 && focusItemLast) {\n this._focusElement(focusItemLast)\n\n // Focus trap: below?\n } else if (isFocusTrap2 && focusItemFirst) {\n this._focusElement(focusItemFirst)\n\n // Outside modal?\n } else if (this._isOutsideModal(activeElement)) {\n this._focusModal()\n }\n }\n\n // =================\n // Event: key press.\n // =================\n\n _handleKeyDown ({ key }:KeyboardEvent) {\n if (!this._isActive) return\n\n key = key.toLowerCase()\n\n // Escape key?\n if (\n key === ESCAPE &&\n !this._isHideShow &&\n this._closable\n ) {\n this.close()\n }\n\n // Tab key?\n if (key === TAB) {\n this._handleFocusIn()\n }\n }\n\n // =================\n // Public: open modal.\n // =================\n\n open () {\n this._isActive = true\n this._toggleModalDisplay(() => {\n this._focusModal()\n })\n }\n\n // =================\n // Public: close modal.\n // =================\n\n close () {\n this._isActive = false\n this._toggleModalDisplay(() => {\n if (this._activeElement) {\n this._focusElement(this._activeElement)\n }\n })\n this.emit('close')\n }\n}\n\ndefine(ModalWindow.TAG, ModalWindow)\n\nfunction createFocusTrap () {\n const trap = document.createElement('span')\n trap.classList.add('modal-focus-trap')\n trap.tabIndex = 0\n return trap\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAU,MAAM,iCAAiC,CAAA;AAOtE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,cAAc,EAAC,WAAW,CAAA;KAC7B;CACJ;;;;;;;AA4DD,qBAAa,WAAY,SAAQ,gBAAmC;IAEhE,YAAY,EAAC,iBAAiB,GAAC,IAAI,CAAO;IAC1C,MAAM,EAAC,iBAAiB,GAAC,IAAI,CAAO;IACpC,aAAa,EAAC,cAAc,GAAC,IAAI,CAAO;IACxC,YAAY,EAAC,cAAc,GAAC,IAAI,CAAO;IACvC,aAAa,EAAC,cAAc,GAAC,IAAI,CAAO;IACxC,WAAW,EAAC,eAAe,GAAC,IAAI,CAAO;IACvC,WAAW,EAAC,eAAe,GAAC,IAAI,CAAO;IACvC,QAAQ,EAAC,WAAW,GAAC,IAAI,CAAO;IAEhC,MAAM,CAAC,GAAG,EAAC,MAAM,CAAiB;IAGlC,cAAc,EAAC,WAAW,GAAC,IAAI,CAAO;IACtC,SAAS,UAAQ;IACjB,WAAW,UAAO;IAClB,QAAQ,UAAQ;IAChB,WAAW,UAAQ;IACnB,aAAa,EAAC,MAAM,GAAC,SAAS,CAAA;IAC9B,aAAa,EAAC,MAAM,GAAC,SAAS,CAAA;IAC9B,SAAS,EAAC,OAAO,CAAO;IACxB,SAAS,EAAC,OAAO,CAAO;IACxB,QAAQ,EAAC,OAAO,CAAQ;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAU,MAAM,iCAAiC,CAAA;AAOtE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,cAAc,EAAC,WAAW,CAAA;KAC7B;CACJ;;;;;;;AA4DD,qBAAa,WAAY,SAAQ,gBAAmC;IAEhE,YAAY,EAAC,iBAAiB,GAAC,IAAI,CAAO;IAC1C,MAAM,EAAC,iBAAiB,GAAC,IAAI,CAAO;IACpC,aAAa,EAAC,cAAc,GAAC,IAAI,CAAO;IACxC,YAAY,EAAC,cAAc,GAAC,IAAI,CAAO;IACvC,aAAa,EAAC,cAAc,GAAC,IAAI,CAAO;IACxC,WAAW,EAAC,eAAe,GAAC,IAAI,CAAO;IACvC,WAAW,EAAC,eAAe,GAAC,IAAI,CAAO;IACvC,QAAQ,EAAC,WAAW,GAAC,IAAI,CAAO;IAEhC,MAAM,CAAC,GAAG,EAAC,MAAM,CAAiB;IAGlC,cAAc,EAAC,WAAW,GAAC,IAAI,CAAO;IACtC,SAAS,UAAQ;IACjB,WAAW,UAAO;IAClB,QAAQ,UAAQ;IAChB,WAAW,UAAQ;IACnB,aAAa,EAAC,MAAM,GAAC,SAAS,CAAA;IAC9B,aAAa,EAAC,MAAM,GAAC,SAAS,CAAA;IAC9B,SAAS,EAAC,OAAO,CAAO;IACxB,SAAS,EAAC,OAAO,CAAO;IACxB,QAAQ,EAAC,OAAO,CAAQ;;IAQxB,MAAM;IAKN,WAAW,CAAE,YAAY,EAAC,IAAI,EAAE,GAAE,IAAI;IAyDtC,MAAM,KAAK,kBAAkB,aAE5B;IAMK,wBAAwB,CAC1B,IAAI,EAAC,MAAM,EACX,QAAQ,EAAC,MAAM,EACf,QAAQ,EAAC,MAAM;IA0BnB,iBAAiB;IAyCjB,oBAAoB;IAQpB,KAAK;IAkBL,UAAU;IAqBV,aAAa;IAsBb,iBAAiB;IAQjB,cAAc;IAed,cAAc;IAqBd,oBAAoB;IAYpB,cAAc;IAkBd,aAAa,CAAE,OAAO,EAAE,WAAW;IAYnC,WAAW;IAWX,eAAe,CAAE,OAAO,CAAC,EAAC,WAAW,GAAE,OAAO;IAe9C,aAAa,IAAI,OAAO;IAOxB,mBAAmB,CAAE,QAAQ,EAAC,MAAI,IAAI;IAiFtC,mBAAmB,CAAE,KAAK,EAAC,UAAU;IAiBrC,iBAAiB;IAQjB,cAAc;IAoCd,cAAc,CAAE,EAAE,GAAG,EAAE,EAAC,aAAa;IAwBrC,IAAI;IAWJ,KAAK;CASR"}
|
package/dist/index.js
CHANGED
|
@@ -65,9 +65,6 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
65
65
|
_closable = true;
|
|
66
66
|
_showIcon = true;
|
|
67
67
|
_noClick = false;
|
|
68
|
-
// =======================
|
|
69
|
-
// Lifecycle: constructor.
|
|
70
|
-
// =======================
|
|
71
68
|
constructor() {
|
|
72
69
|
super();
|
|
73
70
|
this._bind();
|
|
@@ -77,16 +74,8 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
77
74
|
const contentNodes = Array.from(this.childNodes);
|
|
78
75
|
return this._buildModal(contentNodes);
|
|
79
76
|
}
|
|
80
|
-
// ============================
|
|
81
|
-
// Helper: build modal structure.
|
|
82
|
-
// ============================
|
|
83
77
|
_buildModal(contentNodes) {
|
|
84
|
-
|
|
85
|
-
const trap = document.createElement("span");
|
|
86
|
-
trap.classList.add("modal-focus-trap");
|
|
87
|
-
trap.tabIndex = 0;
|
|
88
|
-
return trap;
|
|
89
|
-
}, "createFocusTrap");
|
|
78
|
+
createFocusTrap();
|
|
90
79
|
const scroll = document.createElement("div");
|
|
91
80
|
scroll.classList.add("modal-scroll");
|
|
92
81
|
this._modalScroll = scroll;
|
|
@@ -121,9 +110,6 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
121
110
|
scroll.appendChild(this._focusTrap2);
|
|
122
111
|
this.appendChild(scroll);
|
|
123
112
|
}
|
|
124
|
-
// ============================
|
|
125
|
-
// Lifecycle: watch attributes.
|
|
126
|
-
// ============================
|
|
127
113
|
static get observedAttributes() {
|
|
128
114
|
return [ACTIVE, ANIMATED, ARIA_DESCRIBEDBY, CLOSE];
|
|
129
115
|
}
|
|
@@ -265,21 +251,17 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
265
251
|
this._modal.removeAttribute(ARIA_DESCRIBEDBY);
|
|
266
252
|
}
|
|
267
253
|
}
|
|
268
|
-
// ========================
|
|
269
|
-
// Helper: set active flag.
|
|
270
|
-
// ========================
|
|
271
254
|
_setActiveFlag() {
|
|
272
255
|
const isActive = this.getAttribute(ACTIVE) === TRUE;
|
|
273
256
|
this._isActive = isActive;
|
|
274
257
|
this._toggleModalDisplay(() => {
|
|
275
258
|
if (this._isActive) {
|
|
276
259
|
this._focusModal();
|
|
260
|
+
} else {
|
|
261
|
+
unlockBodyScrolling(document.body);
|
|
277
262
|
}
|
|
278
263
|
});
|
|
279
264
|
}
|
|
280
|
-
// ======================
|
|
281
|
-
// Helper: focus element.
|
|
282
|
-
// ======================
|
|
283
265
|
_focusElement(element) {
|
|
284
266
|
window.requestAnimationFrame(() => {
|
|
285
267
|
if (typeof element.focus === "function") {
|
|
@@ -300,9 +282,6 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
300
282
|
}
|
|
301
283
|
});
|
|
302
284
|
}
|
|
303
|
-
// =============================
|
|
304
|
-
// Helper: detect outside modal.
|
|
305
|
-
// =============================
|
|
306
285
|
_isOutsideModal(element) {
|
|
307
286
|
if (!this._isActive || !element || !this._modal) {
|
|
308
287
|
return false;
|
|
@@ -318,9 +297,6 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
318
297
|
const { matches } = window.matchMedia(PREFERS_REDUCED_MOTION);
|
|
319
298
|
return this._isAnimated && !matches;
|
|
320
299
|
}
|
|
321
|
-
// =====================
|
|
322
|
-
// Helper: toggle modal.
|
|
323
|
-
// =====================
|
|
324
300
|
_toggleModalDisplay(callback) {
|
|
325
301
|
if (!this._modalScroll) return;
|
|
326
302
|
this.setAttribute(ACTIVE, this._isActive);
|
|
@@ -433,6 +409,13 @@ class ModalWindow extends WebComponent.create("modal-window") {
|
|
|
433
409
|
}
|
|
434
410
|
}
|
|
435
411
|
define(ModalWindow.TAG, ModalWindow);
|
|
412
|
+
function createFocusTrap() {
|
|
413
|
+
const trap = document.createElement("span");
|
|
414
|
+
trap.classList.add("modal-focus-trap");
|
|
415
|
+
trap.tabIndex = 0;
|
|
416
|
+
return trap;
|
|
417
|
+
}
|
|
418
|
+
__name(createFocusTrap, "createFocusTrap");
|
|
436
419
|
export {
|
|
437
420
|
ModalWindow
|
|
438
421
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { WebComponent, define } from '@substrate-system/web-component'\nimport {\n lockBodyScrolling,\n unlockBodyScrolling\n} from '@substrate-system/scroll-lock'\n\n// for docuement.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'modal-window':ModalWindow\n }\n}\n\n/**\n * Modal window web component.\n *\n * Opens/closes via the `active` attribute:\n * modal.setAttribute('active', 'true') // open\n * modal.setAttribute('active', 'false') // close\n * modal.removeAttribute('active') // close\n *\n * Or via methods:\n * modal.open()\n * modal.close()\n */\n\n// ==========\n// Constants.\n// ==========\n\nconst ACTIVE = 'active'\nconst ANIMATED = 'animated'\nconst ANIMATION_DURATION = 250\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ARIA_LABEL = 'aria-label'\nconst CLOSE = 'close'\nconst CLOSE_TITLE = 'Close'\nconst NO_ICON = 'no-icon'\nconst CLASS_HIDE = 'modal-hide'\nconst CLASS_SHOW = 'modal-show'\nconst CLASS_VISIBLE = 'modal-visible'\nconst ESCAPE = 'escape'\nconst FALSE = 'false'\nconst FOCUSIN = 'focusin'\nconst KEYDOWN = 'keydown'\nconst MODAL_LABEL_FALLBACK = 'modal'\nconst NOCLICK = 'noclick'\nconst PREFERS_REDUCED_MOTION = '(prefers-reduced-motion: reduce)'\nconst SPACE = ' '\nconst SPACE_REGEX = /\\s+/g\nconst TAB = 'tab'\nconst TRUE = 'true'\n\nconst FOCUSABLE_SELECTORS = [\n '[contenteditable]',\n '[tabindex=\"0\"]:not([disabled])',\n 'a[href]',\n 'audio[controls]',\n 'button:not([disabled])',\n 'iframe',\n \"input:not([disabled]):not([type='hidden'])\",\n 'select:not([disabled])',\n 'summary',\n 'textarea:not([disabled])',\n 'video[controls]',\n].join(',')\n\n// ====================\n// The component\n// ====================\n\nexport class ModalWindow extends WebComponent.create('modal-window') {\n // Element references (set during build).\n _buttonClose:HTMLButtonElement|null = null\n _modal:HTMLDialogElement|null = null\n _modalOverlay:HTMLDivElement|null = null\n _modalScroll:HTMLDivElement|null = null\n _modalContent:HTMLDivElement|null = null\n _focusTrap1:HTMLSpanElement|null = null\n _focusTrap2:HTMLSpanElement|null = null\n _heading:HTMLElement|null = null\n\n static TAG:string = 'modal-window'\n\n // State.\n _activeElement:HTMLElement|null = null\n _isActive = false\n _isAnimated = true\n _isBuilt = false\n _isHideShow = false\n _timerForHide:number|undefined\n _timerForShow:number|undefined\n _closable:boolean = true\n _showIcon:boolean = true\n _noClick:boolean = false\n\n // =======================\n // Lifecycle: constructor.\n // =======================\n\n constructor () {\n super()\n this._bind()\n }\n\n // for TS, inheritance\n render () {\n const contentNodes = Array.from(this.childNodes)\n return this._buildModal(contentNodes)\n }\n\n // ============================\n // Helper: build modal structure.\n // ============================\n\n _buildModal (contentNodes:Node[]):void {\n // Create focus trap\n const createFocusTrap = () => {\n const trap = document.createElement('span')\n trap.classList.add('modal-focus-trap')\n trap.tabIndex = 0\n return trap\n }\n\n // Create scroll container\n const scroll = document.createElement('div')\n scroll.classList.add('modal-scroll')\n this._modalScroll = scroll\n\n // Create overlay\n const overlay = document.createElement('div')\n overlay.classList.add('modal-overlay')\n this._modalOverlay = overlay\n\n // Create dialog\n const dialog = document.createElement('dialog')\n dialog.setAttribute('aria-modal', 'true')\n dialog.classList.add('modal-dialog')\n dialog.tabIndex = -1\n this._modal = dialog\n\n // Create close button if closable and icon should be shown\n if (this._closable && this._showIcon) {\n const closeBtn = document.createElement('button')\n closeBtn.classList.add('modal-close')\n closeBtn.type = 'button'\n closeBtn.innerHTML = '×'\n dialog.appendChild(closeBtn)\n this._buttonClose = closeBtn\n }\n\n // Create content wrapper\n const content = document.createElement('div')\n content.classList.add('modal-content')\n this._modalContent = content\n\n // Move content nodes into the content wrapper\n contentNodes.forEach(node => {\n content.appendChild(node)\n })\n\n dialog.appendChild(content)\n\n // Create focus traps\n this._focusTrap1 = createFocusTrap()\n this._focusTrap2 = createFocusTrap()\n\n // Assemble structure\n overlay.appendChild(dialog)\n scroll.appendChild(this._focusTrap1)\n scroll.appendChild(overlay)\n scroll.appendChild(this._focusTrap2)\n\n // Add to component\n this.appendChild(scroll)\n }\n\n // ============================\n // Lifecycle: watch attributes.\n // ============================\n\n static get observedAttributes () {\n return [ACTIVE, ANIMATED, ARIA_DESCRIBEDBY, CLOSE]\n }\n\n // ==============================\n // Lifecycle: attributes changed.\n // ==============================\n\n async attributeChangedCallback (\n name:string,\n oldValue:string,\n newValue:string\n ) {\n // Different old/new values?\n if (oldValue !== newValue) {\n if (name === ACTIVE) {\n this._setActiveFlag()\n }\n\n if (name === ANIMATED) {\n this._setAnimationFlag()\n }\n\n if (name === ARIA_DESCRIBEDBY) {\n this._setModalDescription()\n }\n\n if (name === CLOSE) {\n this._setCloseTitle()\n }\n }\n }\n\n // ===========================\n // Lifecycle: component mount.\n // ===========================\n\n connectedCallback () {\n // Build modal structure once.\n if (!this._isBuilt) {\n this._closable = this.getAttribute('closable') !== 'false'\n this._showIcon = !this.hasAttribute(NO_ICON)\n this._noClick = this.hasAttribute(NOCLICK)\n\n // Get heading for aria-label.\n this._heading = this.querySelector('h1, h2, h3, h4, h5, h6')\n\n // Collect all child nodes.\n const contentNodes = Array.from(this.childNodes)\n\n // Build the modal structure.\n this._buildModal(contentNodes)\n\n // Set animation flag.\n this._setAnimationFlag()\n\n // Set close title.\n this._setCloseTitle()\n\n // Set modal label.\n this._setModalLabel()\n\n // Set modal description.\n this._setModalDescription()\n\n // Set active flag.\n this._setActiveFlag()\n\n this._isBuilt = true\n }\n\n this._addEvents()\n }\n\n // =============================\n // Lifecycle: component unmount.\n // =============================\n\n disconnectedCallback () {\n this._removeEvents()\n }\n\n // ============================\n // Helper: bind `this` context.\n // ============================\n\n _bind () {\n const propertyNames = Object.getOwnPropertyNames(\n Object.getPrototypeOf(this)\n ) as (keyof ModalWindow)[]\n\n propertyNames.forEach((name) => {\n // Bind functions.\n if (typeof this[name] === 'function') {\n // @ts-expect-error bind\n this[name] = this[name].bind(this)\n }\n })\n }\n\n // ===================\n // Helper: add events.\n // ===================\n\n _addEvents () {\n // Prevent doubles.\n this._removeEvents()\n\n document.addEventListener(FOCUSIN, this._handleFocusIn)\n document.addEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.addEventListener('click',\n this._handleClickClose)\n }\n if (this._modalOverlay) {\n this._modalOverlay.addEventListener('click',\n this._handleClickOverlay)\n }\n }\n\n // ======================\n // Helper: remove events.\n // ======================\n\n _removeEvents () {\n document.removeEventListener(FOCUSIN, this._handleFocusIn)\n document.removeEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.removeEventListener(\n 'click',\n this._handleClickClose\n )\n }\n if (this._modalOverlay) {\n this._modalOverlay.removeEventListener(\n 'click',\n this._handleClickOverlay\n )\n }\n }\n\n // ===========================\n // Helper: set animation flag.\n // ===========================\n\n _setAnimationFlag () {\n this._isAnimated = this.getAttribute(ANIMATED) !== FALSE\n }\n\n // ========================\n // Helper: add close title.\n // ========================\n\n _setCloseTitle () {\n // Get title.\n const title = this.getAttribute(CLOSE) || CLOSE_TITLE\n\n // Set title.\n if (this._buttonClose) {\n this._buttonClose.title = title\n this._buttonClose.setAttribute(ARIA_LABEL, title)\n }\n }\n\n // ========================\n // Helper: add modal label.\n // ========================\n\n _setModalLabel () {\n // Set later.\n let label = MODAL_LABEL_FALLBACK\n\n // Heading exists?\n if (this._heading) {\n // Get text.\n label = this._heading.textContent || label\n label = label.trim().replace(SPACE_REGEX, SPACE)\n }\n\n // Set label.\n if (this._modal) {\n this._modal.setAttribute(ARIA_LABEL, label)\n }\n }\n\n // ==============================\n // Helper: set modal description.\n // ==============================\n\n _setModalDescription () {\n if (!this._modal) return\n\n const describedBy = this.getAttribute(ARIA_DESCRIBEDBY)\n\n if (describedBy) {\n this._modal.setAttribute(ARIA_DESCRIBEDBY, describedBy)\n } else {\n this._modal.removeAttribute(ARIA_DESCRIBEDBY)\n }\n }\n\n // ========================\n // Helper: set active flag.\n // ========================\n\n _setActiveFlag () {\n // Get flag.\n const isActive = this.getAttribute(ACTIVE) === TRUE\n\n // Set flag.\n this._isActive = isActive\n\n // Set display.\n this._toggleModalDisplay(() => {\n // Focus modal?\n if (this._isActive) {\n this._focusModal()\n }\n })\n }\n\n // ======================\n // Helper: focus element.\n // ======================\n\n _focusElement (element: HTMLElement) {\n window.requestAnimationFrame(() => {\n if (typeof element.focus === 'function') {\n element.focus()\n }\n })\n }\n\n // ====================\n // Helper: focus modal.\n // ====================\n\n _focusModal () {\n window.requestAnimationFrame(() => {\n if (this._modal) {\n this._modal.focus()\n }\n if (this._modalScroll) {\n this._modalScroll.scrollTo(0, 0)\n }\n })\n }\n\n // =============================\n // Helper: detect outside modal.\n // =============================\n\n _isOutsideModal (element?: HTMLElement) {\n // Early exit.\n if (!this._isActive || !element || !this._modal) {\n return false\n }\n\n // Has element?\n const hasElement = this.contains(element) || this._modal.contains(element)\n\n // Get boolean.\n const bool = !hasElement\n\n // Expose boolean.\n return bool\n }\n\n // ===========================\n // Helper: detect motion pref.\n // ===========================\n\n _isMotionOkay () {\n // Get pref.\n const { matches } = window.matchMedia(PREFERS_REDUCED_MOTION)\n\n // Expose boolean.\n return this._isAnimated && !matches\n }\n\n // =====================\n // Helper: toggle modal.\n // =====================\n\n _toggleModalDisplay (callback: () => void) {\n if (!this._modalScroll) return\n\n // @ts-expect-error boolean\n this.setAttribute(ACTIVE, this._isActive)\n\n // Get booleans.\n const isModalVisible = this._modalScroll.classList.contains(CLASS_VISIBLE)\n const isMotionOkay = this._isMotionOkay()\n\n // Get delay.\n const delay = isMotionOkay ? ANIMATION_DURATION : 0\n\n // Get active element.\n const activeElement = document.activeElement as HTMLElement\n\n // Cache active element?\n if (this._isActive && activeElement) {\n this._activeElement = activeElement\n }\n\n // =============\n // Modal active?\n // =============\n\n if (this._isActive) {\n // Show modal.\n this._modalScroll.classList.add(CLASS_VISIBLE)\n\n // Lock body scrolling.\n lockBodyScrolling(document.body)\n\n // Set flag.\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_SHOW)\n }\n\n // Fire callback.\n callback()\n\n // Await CSS animation.\n this._timerForShow = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForShow)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_SHOW)\n }, delay)\n } else if (isModalVisible) {\n // Set flag.\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_HIDE)\n }\n\n // Fire callback?\n callback()\n\n // Await CSS animation.\n this._timerForHide = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForHide)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_HIDE)\n\n // Hide modal.\n this._modalScroll?.classList.remove(CLASS_VISIBLE)\n\n // Unlock body scrolling.\n unlockBodyScrolling(document.body)\n\n // Delay.\n }, delay)\n }\n }\n\n // =====================\n // Event: overlay click.\n // =====================\n\n _handleClickOverlay (event:MouseEvent) {\n if (this._isHideShow || this._noClick) return\n if (!this._closable) return\n\n // Get layer.\n const target = event.target as HTMLElement\n\n // Outside modal? (clicked directly on overlay, not dialog)\n if (target === this._modalOverlay) {\n this.close()\n }\n }\n\n // ====================\n // Event: close button click.\n // ====================\n\n _handleClickClose () {\n this.close()\n }\n\n // =========================\n // Event: focus in document.\n // =========================\n\n _handleFocusIn () {\n if (!this._isActive || !this._modal) return\n\n const activeElement = document.activeElement as HTMLElement\n\n // Get booleans.\n const isFocusTrap1 = activeElement === this._focusTrap1\n const isFocusTrap2 = activeElement === this._focusTrap2\n\n // Get focusable elements in modal.\n const focusList = Array.from(\n this._modal.querySelectorAll(FOCUSABLE_SELECTORS)\n ) as HTMLElement[]\n\n // Get first & last items.\n const focusItemFirst = focusList[0]\n const focusItemLast = focusList[focusList.length - 1]\n\n // Focus trap: above?\n if (isFocusTrap1 && focusItemLast) {\n this._focusElement(focusItemLast)\n\n // Focus trap: below?\n } else if (isFocusTrap2 && focusItemFirst) {\n this._focusElement(focusItemFirst)\n\n // Outside modal?\n } else if (this._isOutsideModal(activeElement)) {\n this._focusModal()\n }\n }\n\n // =================\n // Event: key press.\n // =================\n\n _handleKeyDown ({ key }:KeyboardEvent) {\n if (!this._isActive) return\n\n key = key.toLowerCase()\n\n // Escape key?\n if (\n key === ESCAPE &&\n !this._isHideShow &&\n this._closable\n ) {\n this.close()\n }\n\n // Tab key?\n if (key === TAB) {\n this._handleFocusIn()\n }\n }\n\n // =================\n // Public: open modal.\n // =================\n\n open () {\n this._isActive = true\n this._toggleModalDisplay(() => {\n this._focusModal()\n })\n }\n\n // =================\n // Public: close modal.\n // =================\n\n close () {\n this._isActive = false\n this._toggleModalDisplay(() => {\n if (this._activeElement) {\n this._focusElement(this._activeElement)\n }\n })\n this.emit('close')\n }\n}\n\ndefine(ModalWindow.TAG, ModalWindow)\n"],
|
|
5
|
-
"mappings": ";;AAAA,SAAS,cAAc,cAAc;AACrC;AAAA,EACI;AAAA,EACA;AAAA,OACG;AA0BP,MAAM,SAAS;AACf,MAAM,WAAW;AACjB,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,QAAQ;AACd,MAAM,cAAc;AACpB,MAAM,UAAU;AAChB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,gBAAgB;AACtB,MAAM,SAAS;AACf,MAAM,QAAQ;AACd,MAAM,UAAU;AAChB,MAAM,UAAU;AAChB,MAAM,uBAAuB;AAC7B,MAAM,UAAU;AAChB,MAAM,yBAAyB;AAC/B,MAAM,QAAQ;AACd,MAAM,cAAc;AACpB,MAAM,MAAM;AACZ,MAAM,OAAO;AAEb,MAAM,sBAAsB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,EAAE,KAAK,GAAG;AAMH,MAAM,oBAAoB,aAAa,OAAO,cAAc,EAAE;AAAA,EAvErE,OAuEqE;AAAA;AAAA;AAAA;AAAA,EAEjE,eAAsC;AAAA,EACtC,SAAgC;AAAA,EAChC,gBAAoC;AAAA,EACpC,eAAmC;AAAA,EACnC,gBAAoC;AAAA,EACpC,cAAmC;AAAA,EACnC,cAAmC;AAAA,EACnC,WAA4B;AAAA,EAE5B,OAAO,MAAa;AAAA;AAAA,EAGpB,iBAAkC;AAAA,EAClC,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,YAAoB;AAAA,EACpB,YAAoB;AAAA,EACpB,WAAmB;AAAA
|
|
4
|
+
"sourcesContent": ["import { WebComponent, define } from '@substrate-system/web-component'\nimport {\n lockBodyScrolling,\n unlockBodyScrolling\n} from '@substrate-system/scroll-lock'\n\n// for docuement.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'modal-window':ModalWindow\n }\n}\n\n/**\n * Modal window web component.\n *\n * Opens/closes via the `active` attribute:\n * modal.setAttribute('active', 'true') // open\n * modal.setAttribute('active', 'false') // close\n * modal.removeAttribute('active') // close\n *\n * Or via methods:\n * modal.open()\n * modal.close()\n */\n\n// ==========\n// Constants.\n// ==========\n\nconst ACTIVE = 'active'\nconst ANIMATED = 'animated'\nconst ANIMATION_DURATION = 250\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ARIA_LABEL = 'aria-label'\nconst CLOSE = 'close'\nconst CLOSE_TITLE = 'Close'\nconst NO_ICON = 'no-icon'\nconst CLASS_HIDE = 'modal-hide'\nconst CLASS_SHOW = 'modal-show'\nconst CLASS_VISIBLE = 'modal-visible'\nconst ESCAPE = 'escape'\nconst FALSE = 'false'\nconst FOCUSIN = 'focusin'\nconst KEYDOWN = 'keydown'\nconst MODAL_LABEL_FALLBACK = 'modal'\nconst NOCLICK = 'noclick'\nconst PREFERS_REDUCED_MOTION = '(prefers-reduced-motion: reduce)'\nconst SPACE = ' '\nconst SPACE_REGEX = /\\s+/g\nconst TAB = 'tab'\nconst TRUE = 'true'\n\nconst FOCUSABLE_SELECTORS = [\n '[contenteditable]',\n '[tabindex=\"0\"]:not([disabled])',\n 'a[href]',\n 'audio[controls]',\n 'button:not([disabled])',\n 'iframe',\n \"input:not([disabled]):not([type='hidden'])\",\n 'select:not([disabled])',\n 'summary',\n 'textarea:not([disabled])',\n 'video[controls]',\n].join(',')\n\n// ====================\n// The component\n// ====================\n\nexport class ModalWindow extends WebComponent.create('modal-window') {\n // Element references (set during build).\n _buttonClose:HTMLButtonElement|null = null\n _modal:HTMLDialogElement|null = null\n _modalOverlay:HTMLDivElement|null = null\n _modalScroll:HTMLDivElement|null = null\n _modalContent:HTMLDivElement|null = null\n _focusTrap1:HTMLSpanElement|null = null\n _focusTrap2:HTMLSpanElement|null = null\n _heading:HTMLElement|null = null\n\n static TAG:string = 'modal-window'\n\n // State.\n _activeElement:HTMLElement|null = null\n _isActive = false\n _isAnimated = true\n _isBuilt = false\n _isHideShow = false\n _timerForHide:number|undefined\n _timerForShow:number|undefined\n _closable:boolean = true\n _showIcon:boolean = true\n _noClick:boolean = false\n\n constructor () {\n super()\n this._bind()\n }\n\n // for TS, inheritance\n render () {\n const contentNodes = Array.from(this.childNodes)\n return this._buildModal(contentNodes)\n }\n\n _buildModal (contentNodes:Node[]):void {\n // Create focus trap\n createFocusTrap()\n\n // Create scroll container\n const scroll = document.createElement('div')\n scroll.classList.add('modal-scroll')\n this._modalScroll = scroll\n\n // Create overlay\n const overlay = document.createElement('div')\n overlay.classList.add('modal-overlay')\n this._modalOverlay = overlay\n\n // Create dialog\n const dialog = document.createElement('dialog')\n dialog.setAttribute('aria-modal', 'true')\n dialog.classList.add('modal-dialog')\n dialog.tabIndex = -1\n this._modal = dialog\n\n // Create close button if closable and icon should be shown\n if (this._closable && this._showIcon) {\n const closeBtn = document.createElement('button')\n closeBtn.classList.add('modal-close')\n closeBtn.type = 'button'\n closeBtn.innerHTML = '×'\n dialog.appendChild(closeBtn)\n this._buttonClose = closeBtn\n }\n\n // Create content wrapper\n const content = document.createElement('div')\n content.classList.add('modal-content')\n this._modalContent = content\n\n // Move content nodes into the content wrapper\n contentNodes.forEach(node => {\n content.appendChild(node)\n })\n\n dialog.appendChild(content)\n\n // Create focus traps\n this._focusTrap1 = createFocusTrap()\n this._focusTrap2 = createFocusTrap()\n\n // Assemble structure\n overlay.appendChild(dialog)\n scroll.appendChild(this._focusTrap1)\n scroll.appendChild(overlay)\n scroll.appendChild(this._focusTrap2)\n\n // Add to component\n this.appendChild(scroll)\n }\n\n static get observedAttributes () {\n return [ACTIVE, ANIMATED, ARIA_DESCRIBEDBY, CLOSE]\n }\n\n // ==============================\n // Lifecycle: attributes changed.\n // ==============================\n\n async attributeChangedCallback (\n name:string,\n oldValue:string,\n newValue:string\n ) {\n // Different old/new values?\n if (oldValue !== newValue) {\n if (name === ACTIVE) {\n this._setActiveFlag()\n }\n\n if (name === ANIMATED) {\n this._setAnimationFlag()\n }\n\n if (name === ARIA_DESCRIBEDBY) {\n this._setModalDescription()\n }\n\n if (name === CLOSE) {\n this._setCloseTitle()\n }\n }\n }\n\n // ===========================\n // Lifecycle: component mount.\n // ===========================\n\n connectedCallback () {\n // Build modal structure once.\n if (!this._isBuilt) {\n this._closable = this.getAttribute('closable') !== 'false'\n this._showIcon = !this.hasAttribute(NO_ICON)\n this._noClick = this.hasAttribute(NOCLICK)\n\n // Get heading for aria-label.\n this._heading = this.querySelector('h1, h2, h3, h4, h5, h6')\n\n // Collect all child nodes.\n const contentNodes = Array.from(this.childNodes)\n\n // Build the modal structure.\n this._buildModal(contentNodes)\n\n // Set animation flag.\n this._setAnimationFlag()\n\n // Set close title.\n this._setCloseTitle()\n\n // Set modal label.\n this._setModalLabel()\n\n // Set modal description.\n this._setModalDescription()\n\n // Set active flag.\n this._setActiveFlag()\n\n this._isBuilt = true\n }\n\n this._addEvents()\n }\n\n // =============================\n // Lifecycle: component unmount.\n // =============================\n\n disconnectedCallback () {\n this._removeEvents()\n }\n\n // ============================\n // Helper: bind `this` context.\n // ============================\n\n _bind () {\n const propertyNames = Object.getOwnPropertyNames(\n Object.getPrototypeOf(this)\n ) as (keyof ModalWindow)[]\n\n propertyNames.forEach((name) => {\n // Bind functions.\n if (typeof this[name] === 'function') {\n // @ts-expect-error bind\n this[name] = this[name].bind(this)\n }\n })\n }\n\n // ===================\n // Helper: add events.\n // ===================\n\n _addEvents () {\n // Prevent doubles.\n this._removeEvents()\n\n document.addEventListener(FOCUSIN, this._handleFocusIn)\n document.addEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.addEventListener('click',\n this._handleClickClose)\n }\n if (this._modalOverlay) {\n this._modalOverlay.addEventListener('click',\n this._handleClickOverlay)\n }\n }\n\n // ======================\n // Helper: remove events.\n // ======================\n\n _removeEvents () {\n document.removeEventListener(FOCUSIN, this._handleFocusIn)\n document.removeEventListener(KEYDOWN, this._handleKeyDown)\n\n if (this._buttonClose) {\n this._buttonClose.removeEventListener(\n 'click',\n this._handleClickClose\n )\n }\n if (this._modalOverlay) {\n this._modalOverlay.removeEventListener(\n 'click',\n this._handleClickOverlay\n )\n }\n }\n\n // ===========================\n // Helper: set animation flag.\n // ===========================\n\n _setAnimationFlag () {\n this._isAnimated = this.getAttribute(ANIMATED) !== FALSE\n }\n\n // ========================\n // Helper: add close title.\n // ========================\n\n _setCloseTitle () {\n // Get title.\n const title = this.getAttribute(CLOSE) || CLOSE_TITLE\n\n // Set title.\n if (this._buttonClose) {\n this._buttonClose.title = title\n this._buttonClose.setAttribute(ARIA_LABEL, title)\n }\n }\n\n // ========================\n // Helper: add modal label.\n // ========================\n\n _setModalLabel () {\n // Set later.\n let label = MODAL_LABEL_FALLBACK\n\n // Heading exists?\n if (this._heading) {\n // Get text.\n label = this._heading.textContent || label\n label = label.trim().replace(SPACE_REGEX, SPACE)\n }\n\n // Set label.\n if (this._modal) {\n this._modal.setAttribute(ARIA_LABEL, label)\n }\n }\n\n // ==============================\n // Helper: set modal description.\n // ==============================\n\n _setModalDescription () {\n if (!this._modal) return\n\n const describedBy = this.getAttribute(ARIA_DESCRIBEDBY)\n\n if (describedBy) {\n this._modal.setAttribute(ARIA_DESCRIBEDBY, describedBy)\n } else {\n this._modal.removeAttribute(ARIA_DESCRIBEDBY)\n }\n }\n\n _setActiveFlag () {\n // Get flag.\n const isActive = this.getAttribute(ACTIVE) === TRUE\n\n // Set flag.\n this._isActive = isActive\n\n // Set display.\n this._toggleModalDisplay(() => {\n // Focus modal?\n if (this._isActive) {\n this._focusModal()\n } else {\n unlockBodyScrolling(document.body)\n }\n })\n }\n\n _focusElement (element: HTMLElement) {\n window.requestAnimationFrame(() => {\n if (typeof element.focus === 'function') {\n element.focus()\n }\n })\n }\n\n // ====================\n // Helper: focus modal.\n // ====================\n\n _focusModal () {\n window.requestAnimationFrame(() => {\n if (this._modal) {\n this._modal.focus()\n }\n if (this._modalScroll) {\n this._modalScroll.scrollTo(0, 0)\n }\n })\n }\n\n _isOutsideModal (element?:HTMLElement):boolean {\n if (!this._isActive || !element || !this._modal) {\n return false\n }\n\n // Has element?\n const hasElement = this.contains(element) || this._modal.contains(element)\n\n const bool = !hasElement\n return bool\n }\n\n // ===========================\n // Helper: detect motion pref.\n // ===========================\n _isMotionOkay ():boolean {\n // Get pref.\n const { matches } = window.matchMedia(PREFERS_REDUCED_MOTION)\n\n return this._isAnimated && !matches\n }\n\n _toggleModalDisplay (callback:()=>void) {\n if (!this._modalScroll) return\n\n // @ts-expect-error boolean\n this.setAttribute(ACTIVE, this._isActive)\n\n const isModalVisible = this._modalScroll.classList.contains(CLASS_VISIBLE)\n const isMotionOkay = this._isMotionOkay()\n\n // Get delay.\n const delay = isMotionOkay ? ANIMATION_DURATION : 0\n\n // Get active element.\n const activeElement = document.activeElement as HTMLElement\n\n // Cache active element?\n if (this._isActive && activeElement) {\n this._activeElement = activeElement\n }\n\n // =============\n // Modal active?\n // =============\n if (this._isActive) {\n // Show modal.\n this._modalScroll.classList.add(CLASS_VISIBLE)\n\n // Lock body scrolling.\n lockBodyScrolling(document.body)\n\n // Set flag.\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_SHOW)\n }\n\n // Fire callback.\n callback()\n\n // Await CSS animation.\n this._timerForShow = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForShow)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_SHOW)\n }, delay)\n } else if (isModalVisible) { // not active, is visible\n if (isMotionOkay) {\n this._isHideShow = true\n this._modalScroll.classList.add(CLASS_HIDE)\n }\n\n // Fire callback?\n callback()\n\n // Await CSS animation.\n this._timerForHide = window.setTimeout(() => {\n // Clear.\n clearTimeout(this._timerForHide)\n\n // Remove flag.\n this._isHideShow = false\n this._modalScroll?.classList.remove(CLASS_HIDE)\n\n // Hide modal.\n this._modalScroll?.classList.remove(CLASS_VISIBLE)\n\n // Unlock body scrolling.\n unlockBodyScrolling(document.body)\n\n // Delay.\n }, delay)\n }\n }\n\n // =====================\n // Event: overlay click.\n // =====================\n\n _handleClickOverlay (event:MouseEvent) {\n if (this._isHideShow || this._noClick) return\n if (!this._closable) return\n\n // Get layer.\n const target = event.target as HTMLElement\n\n // Outside modal? (clicked directly on overlay, not dialog)\n if (target === this._modalOverlay) {\n this.close()\n }\n }\n\n // ====================\n // Event: close button click.\n // ====================\n\n _handleClickClose () {\n this.close()\n }\n\n // =========================\n // Event: focus in document.\n // =========================\n\n _handleFocusIn () {\n if (!this._isActive || !this._modal) return\n\n const activeElement = document.activeElement as HTMLElement\n\n // Get booleans.\n const isFocusTrap1 = activeElement === this._focusTrap1\n const isFocusTrap2 = activeElement === this._focusTrap2\n\n // Get focusable elements in modal.\n const focusList = Array.from(\n this._modal.querySelectorAll(FOCUSABLE_SELECTORS)\n ) as HTMLElement[]\n\n // Get first & last items.\n const focusItemFirst = focusList[0]\n const focusItemLast = focusList[focusList.length - 1]\n\n // Focus trap: above?\n if (isFocusTrap1 && focusItemLast) {\n this._focusElement(focusItemLast)\n\n // Focus trap: below?\n } else if (isFocusTrap2 && focusItemFirst) {\n this._focusElement(focusItemFirst)\n\n // Outside modal?\n } else if (this._isOutsideModal(activeElement)) {\n this._focusModal()\n }\n }\n\n // =================\n // Event: key press.\n // =================\n\n _handleKeyDown ({ key }:KeyboardEvent) {\n if (!this._isActive) return\n\n key = key.toLowerCase()\n\n // Escape key?\n if (\n key === ESCAPE &&\n !this._isHideShow &&\n this._closable\n ) {\n this.close()\n }\n\n // Tab key?\n if (key === TAB) {\n this._handleFocusIn()\n }\n }\n\n // =================\n // Public: open modal.\n // =================\n\n open () {\n this._isActive = true\n this._toggleModalDisplay(() => {\n this._focusModal()\n })\n }\n\n // =================\n // Public: close modal.\n // =================\n\n close () {\n this._isActive = false\n this._toggleModalDisplay(() => {\n if (this._activeElement) {\n this._focusElement(this._activeElement)\n }\n })\n this.emit('close')\n }\n}\n\ndefine(ModalWindow.TAG, ModalWindow)\n\nfunction createFocusTrap () {\n const trap = document.createElement('span')\n trap.classList.add('modal-focus-trap')\n trap.tabIndex = 0\n return trap\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var W=Object.defineProperty;var n=(s,t)=>W(s,"name",{value:t,configurable:!0});var F=Object.defineProperty,r=n((s,t)=>F(s,"name",{value:t,configurable:!0}),"__name"),h=class s extends HTMLElement{static{n(this,"WebComponent")}static{r(this,"WebComponent")}static TAG="";TAG="";_globalWildcardListeners=new Set;_namespacedWildcardListeners=new Set;static create(t){let e=class extends s{static{n(this,"CreatedClass")}static{r(this,"CreatedClass")}static TAG=t;TAG=t;render(){throw new Error("`render`should be implemented by children")}};return e.define=function(){return s.define.call(this)},e.event=function(i){return s.event.call(this,i)},e}static define(){u(this.TAG,this)}async attributeChangedCallback(t,e,i){let o=this[`handleChange_${t}`];o&&await o.call(this,e,i)}addEventListener(t,e,i){t===s.event.call(this,"*")?this._namespacedWildcardListeners.add({listener:e,options:i}):t==="*"?e&&this._globalWildcardListeners.add({listener:e,options:i}):super.addEventListener(t,e,i)}_notifyNamespacedWildcardListeners(t){if(this._namespacedWildcardListeners.size===0)return;let e=this.TAG;!e||!t.type.startsWith(`${e}:`)||this._namespacedWildcardListeners.forEach(({listener:i})=>{try{typeof i=="function"?i.call(this,t):i&&typeof i.handleEvent=="function"&&i.handleEvent(t)}catch(o){console.error("Error in namespaced wildcard event listener:",o)}})}_notifyGlobalWildcardListeners(t){this._globalWildcardListeners.size!==0&&this._globalWildcardListeners.forEach(({listener:e})=>{try{typeof e=="function"?e.call(this,t):e&&typeof e.handleEvent=="function"&&e.handleEvent(t)}catch(i){console.error("Error in global wildcard event listener:",i)}})}connectedCallback(){this.render()}qs(t){return this.querySelector(t)}qsa(t){return this.querySelectorAll(t)}static event(t){return A(this.TAG,t)}emit(t,e={}){if(t==="*")throw new Error('Do not emit the literal "*"');let{bubbles:i=!0,cancelable:o=!0,detail:l}=e,c=`${this.TAG}:${t}`,a=new CustomEvent(c,{bubbles:i,cancelable:o,detail:l}),H=this.dispatchEvent(a);return this._notifyNamespacedWildcardListeners(a),H}dispatchEvent(t){let e=super.dispatchEvent(t);return this._notifyGlobalWildcardListeners(t),e}dispatch(t,e={}){let i=new CustomEvent(t,{bubbles:e.bubbles===void 0?!0:e.bubbles,cancelable:e.cancelable===void 0?!0:e.cancelable,detail:e.detail});return this.dispatchEvent(i)}removeEventListener(t,e,i){if(t===s.event.call(this,"*")){if(e&&this._namespacedWildcardListeners){for(let o of this._namespacedWildcardListeners)if(o.listener===e){this._namespacedWildcardListeners.delete(o);break}}}else if(t==="*"){if(e&&this._globalWildcardListeners){for(let o of this._globalWildcardListeners)if(o.listener===e){this._globalWildcardListeners.delete(o);break}}}else super.removeEventListener(t,e,i)}};function A(s,t){return`${s}:${t}`}n(A,"eventName");r(A,"eventName");function y(s){return document.createElement(s).constructor!==HTMLElement}n(y,"isRegistered");r(y,"isRegistered");function u(s,t){window&&"customElements"in window&&(y(s)||customElements.define(s,t))}n(u,"define");r(u,"define");var I=Object.defineProperty,m=n((s,t)=>I(s,"name",{value:t,configurable:!0}),"__name"),b=new Set;function C(){let s=document.documentElement.clientWidth;return Math.abs(innerWidth-s)}n(C,"getScrollbarWidth");m(C,"getScrollbarWidth");function w(){let s=Number(getComputedStyle(document.body).paddingRight.replace(/px/,""));return isNaN(s)||!s?0:s}n(w,"getExistingBodyPadding");m(w,"getExistingBodyPadding");function E(s){if(b.add(s),!document.documentElement.classList.contains("scroll-lock")){let t=C()+w(),e=getComputedStyle(document.documentElement).scrollbarGutter;(!e||e==="auto")&&(e="stable"),t<2&&(e=""),document.documentElement.style.setProperty("--scroll-lock-gutter",e),document.documentElement.classList.add("scroll-lock"),document.documentElement.style.setProperty("--scroll-lock-size",`${t}px`)}}n(E,"lockBodyScrolling");m(E,"lockBodyScrolling");function v(s){b.delete(s),b.size===0&&(document.documentElement.classList.remove("scroll-lock"),document.documentElement.style.removeProperty("--scroll-lock-size"))}n(v,"unlockBodyScrolling");m(v,"unlockBodyScrolling");var _="active",p="animated",N=250,d="aria-describedby",S="aria-label",L="close",D="Close",B="no-icon",T="modal-hide",M="modal-show",g="modal-visible",P="escape",x="false",O="focusin",k="keydown",G="modal",R="noclick",q="(prefers-reduced-motion:reduce)",K=" ",$=/\s+/g,j="tab",z="true",U=["[contenteditable]",'[tabindex="0"]:not([disabled])',"a[href]","audio[controls]","button:not([disabled])","iframe","input:not([disabled]):not([type=hidden])","select:not([disabled])","summary","textarea:not([disabled])","video[controls]"].join(","),f=class extends h.create("modal-window"){static{n(this,"ModalWindow")}_buttonClose=null;_modal=null;_modalOverlay=null;_modalScroll=null;_modalContent=null;_focusTrap1=null;_focusTrap2=null;_heading=null;static TAG="modal-window";_activeElement=null;_isActive=!1;_isAnimated=!0;_isBuilt=!1;_isHideShow=!1;_timerForHide;_timerForShow;_closable=!0;_showIcon=!0;_noClick=!1;constructor(){super(),this._bind()}render(){let t=Array.from(this.childNodes);return this._buildModal(t)}_buildModal(t){let e=n(()=>{let a=document.createElement("span");return a.classList.add("modal-focus-trap"),a.tabIndex=0,a},"createFocusTrap"),i=document.createElement("div");i.classList.add("modal-scroll"),this._modalScroll=i;let o=document.createElement("div");o.classList.add("modal-overlay"),this._modalOverlay=o;let l=document.createElement("dialog");if(l.setAttribute("aria-modal","true"),l.classList.add("modal-dialog"),l.tabIndex=-1,this._modal=l,this._closable&&this._showIcon){let a=document.createElement("button");a.classList.add("modal-close"),a.type="button",a.innerHTML="×",l.appendChild(a),this._buttonClose=a}let c=document.createElement("div");c.classList.add("modal-content"),this._modalContent=c,t.forEach(a=>{c.appendChild(a)}),l.appendChild(c),this._focusTrap1=e(),this._focusTrap2=e(),o.appendChild(l),i.appendChild(this._focusTrap1),i.appendChild(o),i.appendChild(this._focusTrap2),this.appendChild(i)}static get observedAttributes(){return[_,p,d,L]}async attributeChangedCallback(t,e,i){e!==i&&(t===_&&this._setActiveFlag(),t===p&&this._setAnimationFlag(),t===d&&this._setModalDescription(),t===L&&this._setCloseTitle())}connectedCallback(){if(!this._isBuilt){this._closable=this.getAttribute("closable")!=="false",this._showIcon=!this.hasAttribute(B),this._noClick=this.hasAttribute(R),this._heading=this.querySelector("h1,h2,h3,h4,h5,h6");let t=Array.from(this.childNodes);this._buildModal(t),this._setAnimationFlag(),this._setCloseTitle(),this._setModalLabel(),this._setModalDescription(),this._setActiveFlag(),this._isBuilt=!0}this._addEvents()}disconnectedCallback(){this._removeEvents()}_bind(){Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(e=>{typeof this[e]=="function"&&(this[e]=this[e].bind(this))})}_addEvents(){this._removeEvents(),document.addEventListener(O,this._handleFocusIn),document.addEventListener(k,this._handleKeyDown),this._buttonClose&&this._buttonClose.addEventListener("click",this._handleClickClose),this._modalOverlay&&this._modalOverlay.addEventListener("click",this._handleClickOverlay)}_removeEvents(){document.removeEventListener(O,this._handleFocusIn),document.removeEventListener(k,this._handleKeyDown),this._buttonClose&&this._buttonClose.removeEventListener("click",this._handleClickClose),this._modalOverlay&&this._modalOverlay.removeEventListener("click",this._handleClickOverlay)}_setAnimationFlag(){this._isAnimated=this.getAttribute(p)!==x}_setCloseTitle(){let t=this.getAttribute(L)||D;this._buttonClose&&(this._buttonClose.title=t,this._buttonClose.setAttribute(S,t))}_setModalLabel(){let t=G;this._heading&&(t=this._heading.textContent||t,t=t.trim().replace($,K)),this._modal&&this._modal.setAttribute(S,t)}_setModalDescription(){if(!this._modal)return;let t=this.getAttribute(d);t?this._modal.setAttribute(d,t):this._modal.removeAttribute(d)}_setActiveFlag(){let t=this.getAttribute(_)===z;this._isActive=t,this._toggleModalDisplay(()=>{this._isActive&&this._focusModal()})}_focusElement(t){requestAnimationFrame(()=>{typeof t.focus=="function"&&t.focus()})}_focusModal(){requestAnimationFrame(()=>{this._modal&&this._modal.focus(),this._modalScroll&&this._modalScroll.scrollTo(0,0)})}_isOutsideModal(t){return!this._isActive||!t||!this._modal?!1:!(this.contains(t)||this._modal.contains(t))}_isMotionOkay(){let{matches:t}=matchMedia(q);return this._isAnimated&&!t}_toggleModalDisplay(t){if(!this._modalScroll)return;this.setAttribute(_,this._isActive);let e=this._modalScroll.classList.contains(g),i=this._isMotionOkay(),o=i?N:0,l=document.activeElement;this._isActive&&l&&(this._activeElement=l),this._isActive?(this._modalScroll.classList.add(g),E(document.body),i&&(this._isHideShow=!0,this._modalScroll.classList.add(M)),t(),this._timerForShow=setTimeout(()=>{clearTimeout(this._timerForShow),this._isHideShow=!1,this._modalScroll?.classList.remove(M)},o)):e&&(i&&(this._isHideShow=!0,this._modalScroll.classList.add(T)),t(),this._timerForHide=setTimeout(()=>{clearTimeout(this._timerForHide),this._isHideShow=!1,this._modalScroll?.classList.remove(T),this._modalScroll?.classList.remove(g),v(document.body)},o))}_handleClickOverlay(t){if(this._isHideShow||this._noClick||!this._closable)return;t.target===this._modalOverlay&&this.close()}_handleClickClose(){this.close()}_handleFocusIn(){if(!this._isActive||!this._modal)return;let t=document.activeElement,e=t===this._focusTrap1,i=t===this._focusTrap2,o=Array.from(this._modal.querySelectorAll(U)),l=o[0],c=o[o.length-1];e&&c?this._focusElement(c):i&&l?this._focusElement(l):this._isOutsideModal(t)&&this._focusModal()}_handleKeyDown({key:t}){this._isActive&&(t=t.toLowerCase(),t===P&&!this._isHideShow&&this._closable&&this.close(),t===j&&this._handleFocusIn())}open(){this._isActive=!0,this._toggleModalDisplay(()=>{this._focusModal()})}close(){this._isActive=!1,this._toggleModalDisplay(()=>{this._activeElement&&this._focusElement(this._activeElement)}),this.emit("close")}};u(f.TAG,f);export{f as ModalWindow};
|
|
1
|
+
var F=Object.defineProperty;var n=(s,t)=>F(s,"name",{value:t,configurable:!0});var I=Object.defineProperty,c=n((s,t)=>I(s,"name",{value:t,configurable:!0}),"__name"),r=class s extends HTMLElement{static{n(this,"WebComponent")}static{c(this,"WebComponent")}static TAG="";TAG="";_globalWildcardListeners=new Set;_namespacedWildcardListeners=new Set;static create(t){let e=class extends s{static{n(this,"CreatedClass")}static{c(this,"CreatedClass")}static TAG=t;TAG=t;render(){throw new Error("`render`should be implemented by children")}};return e.define=function(){return s.define.call(this)},e.event=function(i){return s.event.call(this,i)},e}static define(){h(this.TAG,this)}async attributeChangedCallback(t,e,i){let o=this[`handleChange_${t}`];o&&await o.call(this,e,i)}addEventListener(t,e,i){t===s.event.call(this,"*")?this._namespacedWildcardListeners.add({listener:e,options:i}):t==="*"?e&&this._globalWildcardListeners.add({listener:e,options:i}):super.addEventListener(t,e,i)}_notifyNamespacedWildcardListeners(t){if(this._namespacedWildcardListeners.size===0)return;let e=this.TAG;!e||!t.type.startsWith(`${e}:`)||this._namespacedWildcardListeners.forEach(({listener:i})=>{try{typeof i=="function"?i.call(this,t):i&&typeof i.handleEvent=="function"&&i.handleEvent(t)}catch(o){console.error("Error in namespaced wildcard event listener:",o)}})}_notifyGlobalWildcardListeners(t){this._globalWildcardListeners.size!==0&&this._globalWildcardListeners.forEach(({listener:e})=>{try{typeof e=="function"?e.call(this,t):e&&typeof e.handleEvent=="function"&&e.handleEvent(t)}catch(i){console.error("Error in global wildcard event listener:",i)}})}connectedCallback(){this.render()}qs(t){return this.querySelector(t)}qsa(t){return this.querySelectorAll(t)}static event(t){return A(this.TAG,t)}emit(t,e={}){if(t==="*")throw new Error('Do not emit the literal "*"');let{bubbles:i=!0,cancelable:o=!0,detail:l}=e,a=`${this.TAG}:${t}`,y=new CustomEvent(a,{bubbles:i,cancelable:o,detail:l}),W=this.dispatchEvent(y);return this._notifyNamespacedWildcardListeners(y),W}dispatchEvent(t){let e=super.dispatchEvent(t);return this._notifyGlobalWildcardListeners(t),e}dispatch(t,e={}){let i=new CustomEvent(t,{bubbles:e.bubbles===void 0?!0:e.bubbles,cancelable:e.cancelable===void 0?!0:e.cancelable,detail:e.detail});return this.dispatchEvent(i)}removeEventListener(t,e,i){if(t===s.event.call(this,"*")){if(e&&this._namespacedWildcardListeners){for(let o of this._namespacedWildcardListeners)if(o.listener===e){this._namespacedWildcardListeners.delete(o);break}}}else if(t==="*"){if(e&&this._globalWildcardListeners){for(let o of this._globalWildcardListeners)if(o.listener===e){this._globalWildcardListeners.delete(o);break}}}else super.removeEventListener(t,e,i)}};function A(s,t){return`${s}:${t}`}n(A,"eventName");c(A,"eventName");function C(s){return document.createElement(s).constructor!==HTMLElement}n(C,"isRegistered");c(C,"isRegistered");function h(s,t){window&&"customElements"in window&&(C(s)||customElements.define(s,t))}n(h,"define");c(h,"define");var N=Object.defineProperty,u=n((s,t)=>N(s,"name",{value:t,configurable:!0}),"__name"),b=new Set;function w(){let s=document.documentElement.clientWidth;return Math.abs(innerWidth-s)}n(w,"getScrollbarWidth");u(w,"getScrollbarWidth");function S(){let s=Number(getComputedStyle(document.body).paddingRight.replace(/px/,""));return isNaN(s)||!s?0:s}n(S,"getExistingBodyPadding");u(S,"getExistingBodyPadding");function E(s){if(b.add(s),!document.documentElement.classList.contains("scroll-lock")){let t=w()+S(),e=getComputedStyle(document.documentElement).scrollbarGutter;(!e||e==="auto")&&(e="stable"),t<2&&(e=""),document.documentElement.style.setProperty("--scroll-lock-gutter",e),document.documentElement.classList.add("scroll-lock"),document.documentElement.style.setProperty("--scroll-lock-size",`${t}px`)}}n(E,"lockBodyScrolling");u(E,"lockBodyScrolling");function m(s){b.delete(s),b.size===0&&(document.documentElement.classList.remove("scroll-lock"),document.documentElement.style.removeProperty("--scroll-lock-size"))}n(m,"unlockBodyScrolling");u(m,"unlockBodyScrolling");var _="active",v="animated",D=250,d="aria-describedby",T="aria-label",p="close",B="Close",P="no-icon",M="modal-hide",O="modal-show",L="modal-visible",x="escape",G="false",k="focusin",H="keydown",R="modal",q="noclick",K="(prefers-reduced-motion:reduce)",$=" ",j=/\s+/g,z="tab",U="true",V=["[contenteditable]",'[tabindex="0"]:not([disabled])',"a[href]","audio[controls]","button:not([disabled])","iframe","input:not([disabled]):not([type=hidden])","select:not([disabled])","summary","textarea:not([disabled])","video[controls]"].join(","),f=class extends r.create("modal-window"){static{n(this,"ModalWindow")}_buttonClose=null;_modal=null;_modalOverlay=null;_modalScroll=null;_modalContent=null;_focusTrap1=null;_focusTrap2=null;_heading=null;static TAG="modal-window";_activeElement=null;_isActive=!1;_isAnimated=!0;_isBuilt=!1;_isHideShow=!1;_timerForHide;_timerForShow;_closable=!0;_showIcon=!0;_noClick=!1;constructor(){super(),this._bind()}render(){let t=Array.from(this.childNodes);return this._buildModal(t)}_buildModal(t){g();let e=document.createElement("div");e.classList.add("modal-scroll"),this._modalScroll=e;let i=document.createElement("div");i.classList.add("modal-overlay"),this._modalOverlay=i;let o=document.createElement("dialog");if(o.setAttribute("aria-modal","true"),o.classList.add("modal-dialog"),o.tabIndex=-1,this._modal=o,this._closable&&this._showIcon){let a=document.createElement("button");a.classList.add("modal-close"),a.type="button",a.innerHTML="×",o.appendChild(a),this._buttonClose=a}let l=document.createElement("div");l.classList.add("modal-content"),this._modalContent=l,t.forEach(a=>{l.appendChild(a)}),o.appendChild(l),this._focusTrap1=g(),this._focusTrap2=g(),i.appendChild(o),e.appendChild(this._focusTrap1),e.appendChild(i),e.appendChild(this._focusTrap2),this.appendChild(e)}static get observedAttributes(){return[_,v,d,p]}async attributeChangedCallback(t,e,i){e!==i&&(t===_&&this._setActiveFlag(),t===v&&this._setAnimationFlag(),t===d&&this._setModalDescription(),t===p&&this._setCloseTitle())}connectedCallback(){if(!this._isBuilt){this._closable=this.getAttribute("closable")!=="false",this._showIcon=!this.hasAttribute(P),this._noClick=this.hasAttribute(q),this._heading=this.querySelector("h1,h2,h3,h4,h5,h6");let t=Array.from(this.childNodes);this._buildModal(t),this._setAnimationFlag(),this._setCloseTitle(),this._setModalLabel(),this._setModalDescription(),this._setActiveFlag(),this._isBuilt=!0}this._addEvents()}disconnectedCallback(){this._removeEvents()}_bind(){Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(e=>{typeof this[e]=="function"&&(this[e]=this[e].bind(this))})}_addEvents(){this._removeEvents(),document.addEventListener(k,this._handleFocusIn),document.addEventListener(H,this._handleKeyDown),this._buttonClose&&this._buttonClose.addEventListener("click",this._handleClickClose),this._modalOverlay&&this._modalOverlay.addEventListener("click",this._handleClickOverlay)}_removeEvents(){document.removeEventListener(k,this._handleFocusIn),document.removeEventListener(H,this._handleKeyDown),this._buttonClose&&this._buttonClose.removeEventListener("click",this._handleClickClose),this._modalOverlay&&this._modalOverlay.removeEventListener("click",this._handleClickOverlay)}_setAnimationFlag(){this._isAnimated=this.getAttribute(v)!==G}_setCloseTitle(){let t=this.getAttribute(p)||B;this._buttonClose&&(this._buttonClose.title=t,this._buttonClose.setAttribute(T,t))}_setModalLabel(){let t=R;this._heading&&(t=this._heading.textContent||t,t=t.trim().replace(j,$)),this._modal&&this._modal.setAttribute(T,t)}_setModalDescription(){if(!this._modal)return;let t=this.getAttribute(d);t?this._modal.setAttribute(d,t):this._modal.removeAttribute(d)}_setActiveFlag(){let t=this.getAttribute(_)===U;this._isActive=t,this._toggleModalDisplay(()=>{this._isActive?this._focusModal():m(document.body)})}_focusElement(t){requestAnimationFrame(()=>{typeof t.focus=="function"&&t.focus()})}_focusModal(){requestAnimationFrame(()=>{this._modal&&this._modal.focus(),this._modalScroll&&this._modalScroll.scrollTo(0,0)})}_isOutsideModal(t){return!this._isActive||!t||!this._modal?!1:!(this.contains(t)||this._modal.contains(t))}_isMotionOkay(){let{matches:t}=matchMedia(K);return this._isAnimated&&!t}_toggleModalDisplay(t){if(!this._modalScroll)return;this.setAttribute(_,this._isActive);let e=this._modalScroll.classList.contains(L),i=this._isMotionOkay(),o=i?D:0,l=document.activeElement;this._isActive&&l&&(this._activeElement=l),this._isActive?(this._modalScroll.classList.add(L),E(document.body),i&&(this._isHideShow=!0,this._modalScroll.classList.add(O)),t(),this._timerForShow=setTimeout(()=>{clearTimeout(this._timerForShow),this._isHideShow=!1,this._modalScroll?.classList.remove(O)},o)):e&&(i&&(this._isHideShow=!0,this._modalScroll.classList.add(M)),t(),this._timerForHide=setTimeout(()=>{clearTimeout(this._timerForHide),this._isHideShow=!1,this._modalScroll?.classList.remove(M),this._modalScroll?.classList.remove(L),m(document.body)},o))}_handleClickOverlay(t){if(this._isHideShow||this._noClick||!this._closable)return;t.target===this._modalOverlay&&this.close()}_handleClickClose(){this.close()}_handleFocusIn(){if(!this._isActive||!this._modal)return;let t=document.activeElement,e=t===this._focusTrap1,i=t===this._focusTrap2,o=Array.from(this._modal.querySelectorAll(V)),l=o[0],a=o[o.length-1];e&&a?this._focusElement(a):i&&l?this._focusElement(l):this._isOutsideModal(t)&&this._focusModal()}_handleKeyDown({key:t}){this._isActive&&(t=t.toLowerCase(),t===x&&!this._isHideShow&&this._closable&&this.close(),t===z&&this._handleFocusIn())}open(){this._isActive=!0,this._toggleModalDisplay(()=>{this._focusModal()})}close(){this._isActive=!1,this._toggleModalDisplay(()=>{this._activeElement&&this._focusElement(this._activeElement)}),this.emit("close")}};h(f.TAG,f);function g(){let s=document.createElement("span");return s.classList.add("modal-focus-trap"),s.tabIndex=0,s}n(g,"createFocusTrap");export{f as ModalWindow};
|
package/dist/meta.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"inputs": {
|
|
3
3
|
"src/index.ts": {
|
|
4
|
-
"bytes":
|
|
4
|
+
"bytes": 16613,
|
|
5
5
|
"imports": [],
|
|
6
6
|
"format": "esm"
|
|
7
7
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"imports": [],
|
|
12
12
|
"exports": [],
|
|
13
13
|
"inputs": {},
|
|
14
|
-
"bytes":
|
|
14
|
+
"bytes": 24490
|
|
15
15
|
},
|
|
16
16
|
"dist/index.js": {
|
|
17
17
|
"imports": [
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"entryPoint": "src/index.ts",
|
|
33
33
|
"inputs": {
|
|
34
34
|
"src/index.ts": {
|
|
35
|
-
"bytesInOutput":
|
|
35
|
+
"bytesInOutput": 12150
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
"bytes":
|
|
38
|
+
"bytes": 12339
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|