@magic-spells/dialog-panel 0.1.0 → 0.2.2

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.
@@ -0,0 +1,71 @@
1
+ :root {
2
+ --dp-panel-top: 0;
3
+ --dp-panel-left: 0;
4
+ --dp-panel-width: 100vw;
5
+ --dp-panel-height: 100vh;
6
+ --dp-panel-z-index: 10;
7
+ --dp-overlay-z-index: 1000;
8
+ --dp-overlay-background: rgba(20, 23, 26, 0.4);
9
+ --dp-overlay-backdrop-filter: blur(2px) saturate(120%);
10
+ --dp-overlay-transition: all 400ms ease-out;
11
+ --dp-content-display: block;
12
+ --dp-content-background: white;
13
+ --dp-content-z-index: 1001;
14
+ --dp-content-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
15
+ --dp-content-border-radius: 8px;
16
+ --dp-transition-duration: 400ms;
17
+ --dp-transition-timing: ease-out;
18
+ }
19
+
20
+ dialog-panel {
21
+ /* Make it take no space and be invisible in the document flow */
22
+ display: contents;
23
+ }
24
+ dialog-panel[aria-hidden=false] dialog-overlay,
25
+ dialog-panel[aria-hidden=false] dialog-content {
26
+ pointer-events: auto;
27
+ opacity: 1;
28
+ transform: scale(1);
29
+ filter: blur(0px);
30
+ }
31
+
32
+ /* Overlay background */
33
+ dialog-overlay {
34
+ position: fixed;
35
+ top: 0;
36
+ left: 0;
37
+ width: 100vw;
38
+ height: 100vh;
39
+ opacity: 0;
40
+ pointer-events: none;
41
+ z-index: var(--dp-overlay-z-index, 1000);
42
+ transition: var(--dp-overlay-transition, all 300ms ease-out);
43
+ background-color: var(--dp-overlay-background, rgba(20, 23, 26, 0.4));
44
+ backdrop-filter: var(--dp-overlay-backdrop-filter, blur(2px) saturate(120%));
45
+ }
46
+
47
+ dialog-content {
48
+ position: fixed;
49
+ top: 50%;
50
+ left: 50%;
51
+ transform: translate(-50%, -50%) scale(0.95);
52
+ max-width: 90vw;
53
+ max-height: 85vh;
54
+ display: var(--dp-content-display, block);
55
+ opacity: 0;
56
+ background: var(--dp-content-background, white);
57
+ pointer-events: none;
58
+ z-index: var(--dp-content-z-index, 1001);
59
+ box-shadow: var(--dp-content-shadow, 0 10px 25px rgba(0, 0, 0, 0.15));
60
+ border-radius: var(--dp-content-border-radius, 8px);
61
+ overflow: auto;
62
+ transition: opacity var(--dp-transition-duration, 300ms) var(--dp-transition-timing, ease-out), transform var(--dp-transition-duration, 300ms) var(--dp-transition-timing, ease-out);
63
+ /* When shown, reset transform to center */
64
+ /* When explicitly hidden, remove from layout */
65
+ }
66
+ dialog-panel[aria-hidden=false] dialog-content {
67
+ transform: translate(-50%, -50%) scale(1);
68
+ }
69
+ dialog-content.hidden {
70
+ display: none;
71
+ }
@@ -1,201 +1,57 @@
1
+ import '@magic-spells/focus-trap';
2
+
1
3
  /**
2
- * Retrieves all focusable elements within a given container.
3
- *
4
- * @param {HTMLElement} container - The container element to search for focusable elements.
5
- * @returns {HTMLElement[]} An array of focusable elements found within the container.
4
+ * Custom element that creates an accessible modal dialog panel with focus management
5
+ * @extends HTMLElement
6
6
  */
7
- const getFocusableElements = (container) => {
8
- const focusableSelectors =
9
- 'summary, a[href], button:not(:disabled), [tabindex]:not([tabindex^="-"]):not(focus-trap-start):not(focus-trap-end), [draggable], area, input:not([type=hidden]):not(:disabled), select:not(:disabled), textarea:not(:disabled), object, iframe';
10
- return Array.from(container.querySelectorAll(focusableSelectors));
11
- };
12
-
13
- class FocusTrap extends HTMLElement {
14
- /** @type {boolean} Indicates whether the styles have been injected into the DOM. */
15
- static styleInjected = false;
16
-
17
- constructor() {
18
- super();
19
- this.trapStart = null;
20
- this.trapEnd = null;
21
-
22
- // Inject styles only once, when the first FocusTrap instance is created.
23
- if (!FocusTrap.styleInjected) {
24
- this.injectStyles();
25
- FocusTrap.styleInjected = true;
26
- }
27
- }
28
-
29
- /**
30
- * Injects necessary styles for the focus trap into the document's head.
31
- * This ensures that focus-trap-start and focus-trap-end elements are hidden.
32
- */
33
- injectStyles() {
34
- const style = document.createElement('style');
35
- style.textContent = `
36
- focus-trap-start,
37
- focus-trap-end {
38
- position: absolute;
39
- width: 1px;
40
- height: 1px;
41
- margin: -1px;
42
- padding: 0;
43
- border: 0;
44
- clip: rect(0, 0, 0, 0);
45
- overflow: hidden;
46
- white-space: nowrap;
47
- }
48
- `;
49
- document.head.appendChild(style);
50
- }
51
-
52
- /**
53
- * Called when the element is connected to the DOM.
54
- * Sets up the focus trap and adds the keydown event listener.
55
- */
56
- connectedCallback() {
57
- this.setupTrap();
58
- this.addEventListener('keydown', this.handleKeyDown);
59
- }
7
+ class DialogPanel extends HTMLElement {
8
+ #handleTransitionEnd;
9
+ #scrollPosition = 0;
60
10
 
61
11
  /**
62
- * Called when the element is disconnected from the DOM.
63
- * Removes the keydown event listener.
12
+ * Clean up event listeners when component is removed from DOM
64
13
  */
65
14
  disconnectedCallback() {
66
- this.removeEventListener('keydown', this.handleKeyDown);
67
- }
68
-
69
- /**
70
- * Sets up the focus trap by adding trap start and trap end elements.
71
- * Focuses the trap start element to initiate the focus trap.
72
- */
73
- setupTrap() {
74
- // check to see it there are any focusable children
75
- const focusableElements = getFocusableElements(this);
76
- // exit if there aren't any
77
- if (focusableElements.length === 0) return;
78
-
79
- // create trap start and end elements
80
- this.trapStart = document.createElement('focus-trap-start');
81
- this.trapEnd = document.createElement('focus-trap-end');
82
-
83
- // add to DOM
84
- this.prepend(this.trapStart);
85
- this.append(this.trapEnd);
86
- }
87
-
88
- /**
89
- * Handles the keydown event. If the Escape key is pressed, the focus trap is exited.
90
- *
91
- * @param {KeyboardEvent} e - The keyboard event object.
92
- */
93
- handleKeyDown = (e) => {
94
- if (e.key === 'Escape') {
95
- e.preventDefault();
96
- this.exitTrap();
15
+ const _ = this;
16
+ if (_.contentPanel) {
17
+ _.contentPanel.removeEventListener(
18
+ 'transitionend',
19
+ _.#handleTransitionEnd
20
+ );
97
21
  }
98
- };
99
-
100
- /**
101
- * Exits the focus trap by hiding the current container and shifting focus
102
- * back to the trigger element that opened the trap.
103
- */
104
- exitTrap() {
105
- const container = this.closest('[aria-hidden="false"]');
106
- if (!container) return;
107
22
 
108
- container.setAttribute('aria-hidden', 'true');
109
-
110
- const trigger = document.querySelector(
111
- `[aria-expanded="true"][aria-controls="${container.id}"]`
112
- );
113
- if (trigger) {
114
- trigger.setAttribute('aria-expanded', 'false');
115
- trigger.focus();
116
- }
23
+ // Ensure body scroll is restored if component is removed while open
24
+ document.body.classList.remove('overflow-hidden');
25
+ this.#restoreScroll();
117
26
  }
118
- }
119
27
 
120
- class FocusTrapStart extends HTMLElement {
121
28
  /**
122
- * Called when the element is connected to the DOM.
123
- * Sets the tabindex and adds the focus event listener.
29
+ * Saves current scroll position and locks body scrolling
30
+ * @private
124
31
  */
125
- connectedCallback() {
126
- this.setAttribute('tabindex', '0');
127
- this.addEventListener('focus', this.handleFocus);
128
- }
32
+ #lockScroll() {
33
+ const _ = this;
34
+ // Save current scroll position
35
+ _.#scrollPosition = window.pageYOffset;
129
36
 
130
- /**
131
- * Called when the element is disconnected from the DOM.
132
- * Removes the focus event listener.
133
- */
134
- disconnectedCallback() {
135
- this.removeEventListener('focus', this.handleFocus);
37
+ // Apply fixed position to body
38
+ document.body.classList.add('overflow-hidden');
39
+ document.body.style.top = `-${_.#scrollPosition}px`;
136
40
  }
137
41
 
138
42
  /**
139
- * Handles the focus event. If focus moves backwards from the first focusable element,
140
- * it is cycled to the last focusable element, and vice versa.
141
- *
142
- * @param {FocusEvent} e - The focus event object.
143
- */
144
- handleFocus = (e) => {
145
- const trap = this.closest('focus-trap');
146
- const focusableElements = getFocusableElements(trap);
147
-
148
- if (focusableElements.length === 0) return;
149
-
150
- const firstElement = focusableElements[0];
151
- const lastElement =
152
- focusableElements[focusableElements.length - 1];
153
-
154
- if (e.relatedTarget === firstElement) {
155
- lastElement.focus();
156
- } else {
157
- firstElement.focus();
158
- }
159
- };
160
- }
161
-
162
- class FocusTrapEnd extends HTMLElement {
163
- /**
164
- * Called when the element is connected to the DOM.
165
- * Sets the tabindex and adds the focus event listener.
43
+ * Restores scroll position when dialog is closed
44
+ * @private
166
45
  */
167
- connectedCallback() {
168
- this.setAttribute('tabindex', '0');
169
- this.addEventListener('focus', this.handleFocus);
170
- }
46
+ #restoreScroll() {
47
+ const _ = this;
48
+ // Remove fixed positioning
49
+ document.body.classList.remove('overflow-hidden');
50
+ document.body.style.removeProperty('top');
171
51
 
172
- /**
173
- * Called when the element is disconnected from the DOM.
174
- * Removes the focus event listener.
175
- */
176
- disconnectedCallback() {
177
- this.removeEventListener('focus', this.handleFocus);
52
+ // Restore scroll position
53
+ window.scrollTo(0, _.#scrollPosition);
178
54
  }
179
-
180
- /**
181
- * Handles the focus event. When the trap end is focused, focus is shifted back to the trap start.
182
- */
183
- handleFocus = () => {
184
- const trap = this.closest('focus-trap');
185
- const trapStart = trap.querySelector('focus-trap-start');
186
- trapStart.focus();
187
- };
188
- }
189
-
190
- customElements.define('focus-trap', FocusTrap);
191
- customElements.define('focus-trap-start', FocusTrapStart);
192
- customElements.define('focus-trap-end', FocusTrapEnd);
193
-
194
- /**
195
- * Custom element that creates an accessible modal dialog panel with focus management
196
- * @extends HTMLElement
197
- */
198
- class DialogPanel extends HTMLElement {
199
55
  /**
200
56
  * Initializes the dialog panel, sets up focus trap and overlay
201
57
  */
@@ -211,6 +67,24 @@ class DialogPanel extends HTMLElement {
211
67
  _.focusTrap = document.createElement('focus-trap');
212
68
  _.triggerEl = null;
213
69
 
70
+ // Create a handler for transition end events
71
+ _.#handleTransitionEnd = (e) => {
72
+ if (
73
+ e.propertyName === 'opacity' &&
74
+ _.getAttribute('aria-hidden') === 'true'
75
+ ) {
76
+ _.contentPanel.classList.add('hidden');
77
+
78
+ // Dispatch afterHide event - dialog has completed its transition
79
+ _.dispatchEvent(
80
+ new CustomEvent('afterHide', {
81
+ bubbles: true,
82
+ detail: { triggerElement: _.triggerEl },
83
+ })
84
+ );
85
+ }
86
+ };
87
+
214
88
  // Ensure we have labelledby and describedby references
215
89
  if (!_.getAttribute('aria-labelledby')) {
216
90
  const heading = _.querySelector('h1, h2, h3');
@@ -241,26 +115,31 @@ class DialogPanel extends HTMLElement {
241
115
  * @private
242
116
  */
243
117
  #bindUI() {
118
+ const _ = this;
119
+
244
120
  // Handle trigger buttons
245
121
  document.addEventListener('click', (e) => {
246
- const trigger = e.target.closest(
247
- `[aria-controls="${this.id}"]`
248
- );
122
+ const trigger = e.target.closest(`[aria-controls="${_.id}"]`);
249
123
  if (!trigger) return;
250
124
 
251
125
  if (trigger.getAttribute('data-prevent-default') === 'true') {
252
126
  e.preventDefault();
253
127
  }
254
128
 
255
- // this.triggerEl = trigger;
256
- this.show(trigger);
129
+ _.show(trigger);
257
130
  });
258
131
 
259
132
  // Handle close buttons
260
- this.addEventListener('click', (e) => {
133
+ _.addEventListener('click', (e) => {
261
134
  if (!e.target.closest('[data-action="hide-dialog"]')) return;
262
- this.hide();
135
+ _.hide();
263
136
  });
137
+
138
+ // Add transition end listener
139
+ _.contentPanel.addEventListener(
140
+ 'transitionend',
141
+ _.#handleTransitionEnd
142
+ );
264
143
  }
265
144
 
266
145
  /**
@@ -278,50 +157,108 @@ class DialogPanel extends HTMLElement {
278
157
  /**
279
158
  * Shows the dialog and traps focus within it
280
159
  * @param {HTMLElement} [triggerEl=null] - The element that triggered the dialog
160
+ * @fires DialogPanel#beforeShow - Fired before the dialog starts to show
161
+ * @fires DialogPanel#show - Fired when the dialog has been shown
162
+ * @returns {boolean} False if the show was prevented by a beforeShow event handler
281
163
  */
282
164
  show(triggerEl = null) {
283
- this.triggerEl = triggerEl || false;
165
+ const _ = this;
166
+ _.triggerEl = triggerEl || false;
284
167
 
285
- // Update ARIA states
286
- this.setAttribute('aria-hidden', 'false');
287
- if (this.triggerEl) {
288
- this.triggerEl.setAttribute('aria-expanded', 'true');
289
- }
168
+ // Dispatch beforeShow event - allows preventing the dialog from opening
169
+ const beforeShowEvent = new CustomEvent('beforeShow', {
170
+ bubbles: true,
171
+ cancelable: true,
172
+ detail: { triggerElement: _.triggerEl },
173
+ });
290
174
 
291
- // prevent body from scrolling
292
- document.body.classList.add('overflow-hidden');
175
+ const showAllowed = _.dispatchEvent(beforeShowEvent);
293
176
 
294
- // Focus management
295
- const firstFocusable = this.querySelector(
296
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
297
- );
298
- if (firstFocusable) {
299
- requestAnimationFrame(() => {
300
- firstFocusable.focus();
301
- });
302
- }
177
+ // If event was canceled (preventDefault was called), don't show the dialog
178
+ if (!showAllowed) return false;
179
+
180
+ // Remove the hidden class first to ensure content is rendered
181
+ _.contentPanel.classList.remove('hidden');
182
+
183
+ // Give the browser a moment to process before starting animation
184
+ requestAnimationFrame(() => {
185
+ // Update ARIA states
186
+ _.setAttribute('aria-hidden', 'false');
187
+ if (_.triggerEl) {
188
+ _.triggerEl.setAttribute('aria-expanded', 'true');
189
+ }
190
+
191
+ // Lock body scrolling and save scroll position
192
+ _.#lockScroll();
193
+
194
+ // Focus management
195
+ const firstFocusable = _.querySelector(
196
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
197
+ );
198
+ if (firstFocusable) {
199
+ requestAnimationFrame(() => {
200
+ firstFocusable.focus();
201
+ });
202
+ }
203
+
204
+ // Dispatch show event - dialog is now visible
205
+ _.dispatchEvent(
206
+ new CustomEvent('show', {
207
+ bubbles: true,
208
+ detail: { triggerElement: _.triggerEl },
209
+ })
210
+ );
211
+ });
212
+
213
+ return true;
303
214
  }
304
215
 
305
216
  /**
306
217
  * Hides the dialog and restores focus
218
+ * @fires DialogPanel#beforeHide - Fired before the dialog starts to hide
219
+ * @fires DialogPanel#hide - Fired when the dialog has started hiding (transition begins)
220
+ * @fires DialogPanel#afterHide - Fired when the dialog has completed its hide transition
221
+ * @returns {boolean} False if the hide was prevented by a beforeHide event handler
307
222
  */
308
223
  hide() {
309
- // allow body to scroll
310
- document.body.classList.remove('overflow-hidden');
224
+ const _ = this;
225
+
226
+ // Dispatch beforeHide event - allows preventing the dialog from closing
227
+ const beforeHideEvent = new CustomEvent('beforeHide', {
228
+ bubbles: true,
229
+ cancelable: true,
230
+ detail: { triggerElement: _.triggerEl },
231
+ });
232
+
233
+ const hideAllowed = _.dispatchEvent(beforeHideEvent);
234
+
235
+ // If event was canceled (preventDefault was called), don't hide the dialog
236
+ if (!hideAllowed) return false;
237
+
238
+ // Restore body scroll and scroll position
239
+ _.#restoreScroll();
311
240
 
312
241
  // Update ARIA states
313
- if (this.triggerEl) {
314
- this.triggerEl.setAttribute('aria-expanded', 'false');
315
- // Restore focus to trigger element
316
- this.triggerEl.focus();
317
- } else {
318
- console.log('we need to blur focus');
242
+ if (_.triggerEl) {
243
+ // remove focus from modal panel first
244
+ _.triggerEl.focus();
245
+ // mark trigger as no longer expanded
246
+ _.triggerEl.setAttribute('aria-expanded', 'false');
319
247
  }
320
248
 
321
- // hide dialog panel
322
- setTimeout(() => {
323
- this.setAttribute('aria-hidden', 'true');
324
- }, 1);
249
+ // Set aria-hidden to start transition
250
+ // The transitionend event handler will add display:none when complete
251
+ _.setAttribute('aria-hidden', 'true');
252
+
253
+ // Dispatch hide event - dialog is now starting to hide
254
+ _.dispatchEvent(
255
+ new CustomEvent('hide', {
256
+ bubbles: true,
257
+ detail: { triggerElement: _.triggerEl },
258
+ })
259
+ );
260
+
261
+ return true;
325
262
  }
326
263
  }
327
264
 
@@ -356,9 +293,15 @@ class DialogContent extends HTMLElement {
356
293
  }
357
294
  }
358
295
 
359
- customElements.define('dialog-panel', DialogPanel);
360
- customElements.define('dialog-overlay', DialogOverlay);
361
- customElements.define('dialog-content', DialogContent);
296
+ if (!customElements.get('dialog-panel')) {
297
+ customElements.define('dialog-panel', DialogPanel);
298
+ }
299
+ if (!customElements.get('dialog-overlay')) {
300
+ customElements.define('dialog-overlay', DialogOverlay);
301
+ }
302
+ if (!customElements.get('dialog-content')) {
303
+ customElements.define('dialog-content', DialogContent);
304
+ }
362
305
 
363
306
  export { DialogContent, DialogOverlay, DialogPanel, DialogPanel as default };
364
307
  //# sourceMappingURL=dialog-panel.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-panel.esm.js","sources":["../node_modules/@magic-spells/focus-trap/dist/focus-trap.esm.js","../src/dialog-panel.js"],"sourcesContent":["/**\n * Retrieves all focusable elements within a given container.\n *\n * @param {HTMLElement} container - The container element to search for focusable elements.\n * @returns {HTMLElement[]} An array of focusable elements found within the container.\n */\nconst getFocusableElements = (container) => {\n\tconst focusableSelectors =\n\t\t'summary, a[href], button:not(:disabled), [tabindex]:not([tabindex^=\"-\"]):not(focus-trap-start):not(focus-trap-end), [draggable], area, input:not([type=hidden]):not(:disabled), select:not(:disabled), textarea:not(:disabled), object, iframe';\n\treturn Array.from(container.querySelectorAll(focusableSelectors));\n};\n\nclass FocusTrap extends HTMLElement {\n\t/** @type {boolean} Indicates whether the styles have been injected into the DOM. */\n\tstatic styleInjected = false;\n\n\tconstructor() {\n\t\tsuper();\n\t\tthis.trapStart = null;\n\t\tthis.trapEnd = null;\n\n\t\t// Inject styles only once, when the first FocusTrap instance is created.\n\t\tif (!FocusTrap.styleInjected) {\n\t\t\tthis.injectStyles();\n\t\t\tFocusTrap.styleInjected = true;\n\t\t}\n\t}\n\n\t/**\n\t * Injects necessary styles for the focus trap into the document's head.\n\t * This ensures that focus-trap-start and focus-trap-end elements are hidden.\n\t */\n\tinjectStyles() {\n\t\tconst style = document.createElement('style');\n\t\tstyle.textContent = `\n focus-trap-start,\n focus-trap-end {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0, 0, 0, 0);\n overflow: hidden;\n white-space: nowrap;\n }\n `;\n\t\tdocument.head.appendChild(style);\n\t}\n\n\t/**\n\t * Called when the element is connected to the DOM.\n\t * Sets up the focus trap and adds the keydown event listener.\n\t */\n\tconnectedCallback() {\n\t\tthis.setupTrap();\n\t\tthis.addEventListener('keydown', this.handleKeyDown);\n\t}\n\n\t/**\n\t * Called when the element is disconnected from the DOM.\n\t * Removes the keydown event listener.\n\t */\n\tdisconnectedCallback() {\n\t\tthis.removeEventListener('keydown', this.handleKeyDown);\n\t}\n\n\t/**\n\t * Sets up the focus trap by adding trap start and trap end elements.\n\t * Focuses the trap start element to initiate the focus trap.\n\t */\n\tsetupTrap() {\n\t\t// check to see it there are any focusable children\n\t\tconst focusableElements = getFocusableElements(this);\n\t\t// exit if there aren't any\n\t\tif (focusableElements.length === 0) return;\n\n\t\t// create trap start and end elements\n\t\tthis.trapStart = document.createElement('focus-trap-start');\n\t\tthis.trapEnd = document.createElement('focus-trap-end');\n\n\t\t// add to DOM\n\t\tthis.prepend(this.trapStart);\n\t\tthis.append(this.trapEnd);\n\t}\n\n\t/**\n\t * Handles the keydown event. If the Escape key is pressed, the focus trap is exited.\n\t *\n\t * @param {KeyboardEvent} e - The keyboard event object.\n\t */\n\thandleKeyDown = (e) => {\n\t\tif (e.key === 'Escape') {\n\t\t\te.preventDefault();\n\t\t\tthis.exitTrap();\n\t\t}\n\t};\n\n\t/**\n\t * Exits the focus trap by hiding the current container and shifting focus\n\t * back to the trigger element that opened the trap.\n\t */\n\texitTrap() {\n\t\tconst container = this.closest('[aria-hidden=\"false\"]');\n\t\tif (!container) return;\n\n\t\tcontainer.setAttribute('aria-hidden', 'true');\n\n\t\tconst trigger = document.querySelector(\n\t\t\t`[aria-expanded=\"true\"][aria-controls=\"${container.id}\"]`\n\t\t);\n\t\tif (trigger) {\n\t\t\ttrigger.setAttribute('aria-expanded', 'false');\n\t\t\ttrigger.focus();\n\t\t}\n\t}\n}\n\nclass FocusTrapStart extends HTMLElement {\n\t/**\n\t * Called when the element is connected to the DOM.\n\t * Sets the tabindex and adds the focus event listener.\n\t */\n\tconnectedCallback() {\n\t\tthis.setAttribute('tabindex', '0');\n\t\tthis.addEventListener('focus', this.handleFocus);\n\t}\n\n\t/**\n\t * Called when the element is disconnected from the DOM.\n\t * Removes the focus event listener.\n\t */\n\tdisconnectedCallback() {\n\t\tthis.removeEventListener('focus', this.handleFocus);\n\t}\n\n\t/**\n\t * Handles the focus event. If focus moves backwards from the first focusable element,\n\t * it is cycled to the last focusable element, and vice versa.\n\t *\n\t * @param {FocusEvent} e - The focus event object.\n\t */\n\thandleFocus = (e) => {\n\t\tconst trap = this.closest('focus-trap');\n\t\tconst focusableElements = getFocusableElements(trap);\n\n\t\tif (focusableElements.length === 0) return;\n\n\t\tconst firstElement = focusableElements[0];\n\t\tconst lastElement =\n\t\t\tfocusableElements[focusableElements.length - 1];\n\n\t\tif (e.relatedTarget === firstElement) {\n\t\t\tlastElement.focus();\n\t\t} else {\n\t\t\tfirstElement.focus();\n\t\t}\n\t};\n}\n\nclass FocusTrapEnd extends HTMLElement {\n\t/**\n\t * Called when the element is connected to the DOM.\n\t * Sets the tabindex and adds the focus event listener.\n\t */\n\tconnectedCallback() {\n\t\tthis.setAttribute('tabindex', '0');\n\t\tthis.addEventListener('focus', this.handleFocus);\n\t}\n\n\t/**\n\t * Called when the element is disconnected from the DOM.\n\t * Removes the focus event listener.\n\t */\n\tdisconnectedCallback() {\n\t\tthis.removeEventListener('focus', this.handleFocus);\n\t}\n\n\t/**\n\t * Handles the focus event. When the trap end is focused, focus is shifted back to the trap start.\n\t */\n\thandleFocus = () => {\n\t\tconst trap = this.closest('focus-trap');\n\t\tconst trapStart = trap.querySelector('focus-trap-start');\n\t\ttrapStart.focus();\n\t};\n}\n\ncustomElements.define('focus-trap', FocusTrap);\ncustomElements.define('focus-trap-start', FocusTrapStart);\ncustomElements.define('focus-trap-end', FocusTrapEnd);\n//# sourceMappingURL=focus-trap.esm.js.map\n","import './dialog-panel.css';\nimport '@magic-spells/focus-trap';\n\n/**\n * Custom element that creates an accessible modal dialog panel with focus management\n * @extends HTMLElement\n */\nclass DialogPanel extends HTMLElement {\n\t/**\n\t * Initializes the dialog panel, sets up focus trap and overlay\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t\t_.id = _.getAttribute('id');\n\t\t_.setAttribute('role', 'dialog');\n\t\t_.setAttribute('aria-modal', 'true');\n\t\t_.setAttribute('aria-hidden', 'true');\n\n\t\t_.contentPanel = _.querySelector('dialog-content');\n\t\t_.focusTrap = document.createElement('focus-trap');\n\t\t_.triggerEl = null;\n\n\t\t// Ensure we have labelledby and describedby references\n\t\tif (!_.getAttribute('aria-labelledby')) {\n\t\t\tconst heading = _.querySelector('h1, h2, h3');\n\t\t\tif (heading && !heading.id) {\n\t\t\t\theading.id = `${_.id}-title`;\n\t\t\t}\n\t\t\tif (heading?.id) {\n\t\t\t\t_.setAttribute('aria-labelledby', heading.id);\n\t\t\t}\n\t\t}\n\n\t\t_.contentPanel.parentNode.insertBefore(\n\t\t\t_.focusTrap,\n\t\t\t_.contentPanel\n\t\t);\n\t\t_.focusTrap.appendChild(_.contentPanel);\n\n\t\t_.focusTrap.setupTrap();\n\n\t\t// Add modal overlay\n\t\t_.prepend(document.createElement('dialog-overlay'));\n\t\t_.#bindUI();\n\t\t_.#bindKeyboard();\n\t}\n\n\t/**\n\t * Binds click events for showing and hiding the dialog\n\t * @private\n\t */\n\t#bindUI() {\n\t\t// Handle trigger buttons\n\t\tdocument.addEventListener('click', (e) => {\n\t\t\tconst trigger = e.target.closest(\n\t\t\t\t`[aria-controls=\"${this.id}\"]`\n\t\t\t);\n\t\t\tif (!trigger) return;\n\n\t\t\tif (trigger.getAttribute('data-prevent-default') === 'true') {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\n\t\t\t// this.triggerEl = trigger;\n\t\t\tthis.show(trigger);\n\t\t});\n\n\t\t// Handle close buttons\n\t\tthis.addEventListener('click', (e) => {\n\t\t\tif (!e.target.closest('[data-action=\"hide-dialog\"]')) return;\n\t\t\tthis.hide();\n\t\t});\n\t}\n\n\t/**\n\t * Binds keyboard events for accessibility\n\t * @private\n\t */\n\t#bindKeyboard() {\n\t\tthis.addEventListener('keydown', (e) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\tthis.hide();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Shows the dialog and traps focus within it\n\t * @param {HTMLElement} [triggerEl=null] - The element that triggered the dialog\n\t */\n\tshow(triggerEl = null) {\n\t\tthis.triggerEl = triggerEl || false;\n\n\t\t// Update ARIA states\n\t\tthis.setAttribute('aria-hidden', 'false');\n\t\tif (this.triggerEl) {\n\t\t\tthis.triggerEl.setAttribute('aria-expanded', 'true');\n\t\t}\n\n\t\t// prevent body from scrolling\n\t\tdocument.body.classList.add('overflow-hidden');\n\n\t\t// Focus management\n\t\tconst firstFocusable = this.querySelector(\n\t\t\t'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n\t\t);\n\t\tif (firstFocusable) {\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tfirstFocusable.focus();\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Hides the dialog and restores focus\n\t */\n\thide() {\n\t\t// allow body to scroll\n\t\tdocument.body.classList.remove('overflow-hidden');\n\n\t\t// Update ARIA states\n\t\tif (this.triggerEl) {\n\t\t\tthis.triggerEl.setAttribute('aria-expanded', 'false');\n\t\t\t// Restore focus to trigger element\n\t\t\tthis.triggerEl.focus();\n\t\t} else {\n\t\t\tconsole.log('we need to blur focus');\n\t\t}\n\n\t\t// hide dialog panel\n\t\tsetTimeout(() => {\n\t\t\tthis.setAttribute('aria-hidden', 'true');\n\t\t}, 1);\n\t}\n}\n\n/**\n * Custom element that creates a clickable overlay for the dialog\n * @extends HTMLElement\n */\nclass DialogOverlay extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('tabindex', '-1'); // Changed to -1 as it shouldn't be focusable\n\t\tthis.setAttribute('aria-hidden', 'true');\n\t\tthis.dialogPanel = this.closest('dialog-panel');\n\t\tthis.#bindUI();\n\t}\n\n\t#bindUI() {\n\t\tthis.addEventListener('click', () => {\n\t\t\tthis.dialogPanel.hide();\n\t\t});\n\t}\n}\n\n/**\n * Custom element that wraps the content of the dialog\n * @extends HTMLElement\n */\nclass DialogContent extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('role', 'document'); // Optional: helps with document structure\n\t}\n}\n\ncustomElements.define('dialog-panel', DialogPanel);\ncustomElements.define('dialog-overlay', DialogOverlay);\ncustomElements.define('dialog-content', DialogContent);\n\nexport { DialogPanel, DialogOverlay, DialogContent };\nexport default DialogPanel;\n"],"names":[],"mappings":"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","x_google_ignoreList":[0]}
1
+ {"version":3,"file":"dialog-panel.esm.js","sources":["../src/dialog-panel.js"],"sourcesContent":["import './index.scss';\nimport '@magic-spells/focus-trap';\n\n/**\n * Custom element that creates an accessible modal dialog panel with focus management\n * @extends HTMLElement\n */\nclass DialogPanel extends HTMLElement {\n\t#handleTransitionEnd;\n\t#scrollPosition = 0;\n\n\t/**\n\t * Clean up event listeners when component is removed from DOM\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tif (_.contentPanel) {\n\t\t\t_.contentPanel.removeEventListener(\n\t\t\t\t'transitionend',\n\t\t\t\t_.#handleTransitionEnd\n\t\t\t);\n\t\t}\n\n\t\t// Ensure body scroll is restored if component is removed while open\n\t\tdocument.body.classList.remove('overflow-hidden');\n\t\tthis.#restoreScroll();\n\t}\n\n\t/**\n\t * Saves current scroll position and locks body scrolling\n\t * @private\n\t */\n\t#lockScroll() {\n\t\tconst _ = this;\n\t\t// Save current scroll position\n\t\t_.#scrollPosition = window.pageYOffset;\n\n\t\t// Apply fixed position to body\n\t\tdocument.body.classList.add('overflow-hidden');\n\t\tdocument.body.style.top = `-${_.#scrollPosition}px`;\n\t}\n\n\t/**\n\t * Restores scroll position when dialog is closed\n\t * @private\n\t */\n\t#restoreScroll() {\n\t\tconst _ = this;\n\t\t// Remove fixed positioning\n\t\tdocument.body.classList.remove('overflow-hidden');\n\t\tdocument.body.style.removeProperty('top');\n\n\t\t// Restore scroll position\n\t\twindow.scrollTo(0, _.#scrollPosition);\n\t}\n\t/**\n\t * Initializes the dialog panel, sets up focus trap and overlay\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t\t_.id = _.getAttribute('id');\n\t\t_.setAttribute('role', 'dialog');\n\t\t_.setAttribute('aria-modal', 'true');\n\t\t_.setAttribute('aria-hidden', 'true');\n\n\t\t_.contentPanel = _.querySelector('dialog-content');\n\t\t_.focusTrap = document.createElement('focus-trap');\n\t\t_.triggerEl = null;\n\n\t\t// Create a handler for transition end events\n\t\t_.#handleTransitionEnd = (e) => {\n\t\t\tif (\n\t\t\t\te.propertyName === 'opacity' &&\n\t\t\t\t_.getAttribute('aria-hidden') === 'true'\n\t\t\t) {\n\t\t\t\t_.contentPanel.classList.add('hidden');\n\n\t\t\t\t// Dispatch afterHide event - dialog has completed its transition\n\t\t\t\t_.dispatchEvent(\n\t\t\t\t\tnew CustomEvent('afterHide', {\n\t\t\t\t\t\tbubbles: true,\n\t\t\t\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t}\n\t\t};\n\n\t\t// Ensure we have labelledby and describedby references\n\t\tif (!_.getAttribute('aria-labelledby')) {\n\t\t\tconst heading = _.querySelector('h1, h2, h3');\n\t\t\tif (heading && !heading.id) {\n\t\t\t\theading.id = `${_.id}-title`;\n\t\t\t}\n\t\t\tif (heading?.id) {\n\t\t\t\t_.setAttribute('aria-labelledby', heading.id);\n\t\t\t}\n\t\t}\n\n\t\t_.contentPanel.parentNode.insertBefore(\n\t\t\t_.focusTrap,\n\t\t\t_.contentPanel\n\t\t);\n\t\t_.focusTrap.appendChild(_.contentPanel);\n\n\t\t_.focusTrap.setupTrap();\n\n\t\t// Add modal overlay\n\t\t_.prepend(document.createElement('dialog-overlay'));\n\t\t_.#bindUI();\n\t\t_.#bindKeyboard();\n\t}\n\n\t/**\n\t * Binds click events for showing and hiding the dialog\n\t * @private\n\t */\n\t#bindUI() {\n\t\tconst _ = this;\n\n\t\t// Handle trigger buttons\n\t\tdocument.addEventListener('click', (e) => {\n\t\t\tconst trigger = e.target.closest(`[aria-controls=\"${_.id}\"]`);\n\t\t\tif (!trigger) return;\n\n\t\t\tif (trigger.getAttribute('data-prevent-default') === 'true') {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\n\t\t\t_.show(trigger);\n\t\t});\n\n\t\t// Handle close buttons\n\t\t_.addEventListener('click', (e) => {\n\t\t\tif (!e.target.closest('[data-action=\"hide-dialog\"]')) return;\n\t\t\t_.hide();\n\t\t});\n\n\t\t// Add transition end listener\n\t\t_.contentPanel.addEventListener(\n\t\t\t'transitionend',\n\t\t\t_.#handleTransitionEnd\n\t\t);\n\t}\n\n\t/**\n\t * Binds keyboard events for accessibility\n\t * @private\n\t */\n\t#bindKeyboard() {\n\t\tthis.addEventListener('keydown', (e) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\tthis.hide();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Shows the dialog and traps focus within it\n\t * @param {HTMLElement} [triggerEl=null] - The element that triggered the dialog\n\t * @fires DialogPanel#beforeShow - Fired before the dialog starts to show\n\t * @fires DialogPanel#show - Fired when the dialog has been shown\n\t * @returns {boolean} False if the show was prevented by a beforeShow event handler\n\t */\n\tshow(triggerEl = null) {\n\t\tconst _ = this;\n\t\t_.triggerEl = triggerEl || false;\n\n\t\t// Dispatch beforeShow event - allows preventing the dialog from opening\n\t\tconst beforeShowEvent = new CustomEvent('beforeShow', {\n\t\t\tbubbles: true,\n\t\t\tcancelable: true,\n\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t});\n\n\t\tconst showAllowed = _.dispatchEvent(beforeShowEvent);\n\n\t\t// If event was canceled (preventDefault was called), don't show the dialog\n\t\tif (!showAllowed) return false;\n\n\t\t// Remove the hidden class first to ensure content is rendered\n\t\t_.contentPanel.classList.remove('hidden');\n\n\t\t// Give the browser a moment to process before starting animation\n\t\trequestAnimationFrame(() => {\n\t\t\t// Update ARIA states\n\t\t\t_.setAttribute('aria-hidden', 'false');\n\t\t\tif (_.triggerEl) {\n\t\t\t\t_.triggerEl.setAttribute('aria-expanded', 'true');\n\t\t\t}\n\n\t\t\t// Lock body scrolling and save scroll position\n\t\t\t_.#lockScroll();\n\n\t\t\t// Focus management\n\t\t\tconst firstFocusable = _.querySelector(\n\t\t\t\t'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n\t\t\t);\n\t\t\tif (firstFocusable) {\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tfirstFocusable.focus();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Dispatch show event - dialog is now visible\n\t\t\t_.dispatchEvent(\n\t\t\t\tnew CustomEvent('show', {\n\t\t\t\t\tbubbles: true,\n\t\t\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t\t\t})\n\t\t\t);\n\t\t});\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Hides the dialog and restores focus\n\t * @fires DialogPanel#beforeHide - Fired before the dialog starts to hide\n\t * @fires DialogPanel#hide - Fired when the dialog has started hiding (transition begins)\n\t * @fires DialogPanel#afterHide - Fired when the dialog has completed its hide transition\n\t * @returns {boolean} False if the hide was prevented by a beforeHide event handler\n\t */\n\thide() {\n\t\tconst _ = this;\n\n\t\t// Dispatch beforeHide event - allows preventing the dialog from closing\n\t\tconst beforeHideEvent = new CustomEvent('beforeHide', {\n\t\t\tbubbles: true,\n\t\t\tcancelable: true,\n\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t});\n\n\t\tconst hideAllowed = _.dispatchEvent(beforeHideEvent);\n\n\t\t// If event was canceled (preventDefault was called), don't hide the dialog\n\t\tif (!hideAllowed) return false;\n\n\t\t// Restore body scroll and scroll position\n\t\t_.#restoreScroll();\n\n\t\t// Update ARIA states\n\t\tif (_.triggerEl) {\n\t\t\t// remove focus from modal panel first\n\t\t\t_.triggerEl.focus();\n\t\t\t// mark trigger as no longer expanded\n\t\t\t_.triggerEl.setAttribute('aria-expanded', 'false');\n\t\t}\n\n\t\t// Set aria-hidden to start transition\n\t\t// The transitionend event handler will add display:none when complete\n\t\t_.setAttribute('aria-hidden', 'true');\n\n\t\t// Dispatch hide event - dialog is now starting to hide\n\t\t_.dispatchEvent(\n\t\t\tnew CustomEvent('hide', {\n\t\t\t\tbubbles: true,\n\t\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t\t})\n\t\t);\n\n\t\treturn true;\n\t}\n}\n\n/**\n * Custom element that creates a clickable overlay for the dialog\n * @extends HTMLElement\n */\nclass DialogOverlay extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('tabindex', '-1'); // Changed to -1 as it shouldn't be focusable\n\t\tthis.setAttribute('aria-hidden', 'true');\n\t\tthis.dialogPanel = this.closest('dialog-panel');\n\t\tthis.#bindUI();\n\t}\n\n\t#bindUI() {\n\t\tthis.addEventListener('click', () => {\n\t\t\tthis.dialogPanel.hide();\n\t\t});\n\t}\n}\n\n/**\n * Custom element that wraps the content of the dialog\n * @extends HTMLElement\n */\nclass DialogContent extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('role', 'document'); // Optional: helps with document structure\n\t}\n}\n\nif (!customElements.get('dialog-panel')) {\n\tcustomElements.define('dialog-panel', DialogPanel);\n}\nif (!customElements.get('dialog-overlay')) {\n\tcustomElements.define('dialog-overlay', DialogOverlay);\n}\nif (!customElements.get('dialog-content')) {\n\tcustomElements.define('dialog-content', DialogContent);\n}\n\nexport { DialogPanel, DialogOverlay, DialogContent };\nexport default DialogPanel;\n"],"names":[],"mappings":"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"}