@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.
- package/README.md +85 -8
- package/dist/dialog-panel.cjs.js +161 -218
- package/dist/dialog-panel.cjs.js.map +1 -1
- package/dist/dialog-panel.css +71 -0
- package/dist/dialog-panel.esm.js +161 -218
- package/dist/dialog-panel.esm.js.map +1 -1
- package/dist/dialog-panel.js +511 -0
- package/dist/dialog-panel.js.map +1 -0
- package/dist/dialog-panel.min.css +1 -1
- package/dist/dialog-panel.min.js +1 -1
- package/dist/dialog-panel.scss +2 -0
- package/dist/scss/dialog-panel.scss +73 -0
- package/dist/scss/variables.scss +54 -0
- package/package.json +10 -4
- package/src/dialog-panel.js +174 -40
- package/src/index.scss +2 -0
- package/src/scss/dialog-panel.scss +73 -0
- package/src/scss/variables.scss +54 -0
- package/src/dialog-panel.css +0 -47
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, customizable Web Component for creating accessible modal dialogs. Ideal for dialogs, alerts, cart panels, or content panels with smooth animations and accessibility features.
|
|
4
4
|
|
|
5
|
+
[**Live Demo**](https://magic-spells.github.io/dialog-panel/demo/)
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- No dependencies
|
|
@@ -82,7 +84,55 @@ Or include directly in your HTML:
|
|
|
82
84
|
|
|
83
85
|
### Styling
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
#### Using CSS Custom Properties
|
|
88
|
+
|
|
89
|
+
You can style the Dialog Panel by overriding the CSS custom properties:
|
|
90
|
+
|
|
91
|
+
```css
|
|
92
|
+
:root {
|
|
93
|
+
/* Layout */
|
|
94
|
+
--dp-panel-z-index: 100;
|
|
95
|
+
|
|
96
|
+
/* Overlay */
|
|
97
|
+
--dp-overlay-background: rgba(0, 0, 0, 0.7);
|
|
98
|
+
--dp-overlay-backdrop-filter: blur(5px) saturate(120%);
|
|
99
|
+
|
|
100
|
+
/* Content */
|
|
101
|
+
--dp-content-background: #f8f8f8;
|
|
102
|
+
|
|
103
|
+
/* Animation */
|
|
104
|
+
--dp-transition-duration: 400ms;
|
|
105
|
+
--dp-transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Using SCSS
|
|
110
|
+
|
|
111
|
+
For more advanced customization, you can import the SCSS directly:
|
|
112
|
+
|
|
113
|
+
```scss
|
|
114
|
+
// Option 1: Import the compiled CSS
|
|
115
|
+
@import '@magic-spells/dialog-panel/css';
|
|
116
|
+
|
|
117
|
+
// Option 2: Import the SCSS and override variables
|
|
118
|
+
@use '@magic-spells/dialog-panel/scss' with (
|
|
119
|
+
$overlay-background: rgba(0, 0, 0, 0.7),
|
|
120
|
+
$overlay-backdrop-filter: blur(5px) saturate(120%),
|
|
121
|
+
$content-background: #f8f8f8,
|
|
122
|
+
$transition-duration: 400ms,
|
|
123
|
+
$transition-timing: cubic-bezier(0.4, 0, 0.2, 1)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Option 3: Import specific parts
|
|
127
|
+
@use '@magic-spells/dialog-panel/scss/variables' with (
|
|
128
|
+
$panel-z-index: 100
|
|
129
|
+
);
|
|
130
|
+
@use '@magic-spells/dialog-panel/scss/dialog-panel';
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Direct Element Styling
|
|
134
|
+
|
|
135
|
+
You can also style the elements directly:
|
|
86
136
|
|
|
87
137
|
```css
|
|
88
138
|
dialog-panel {
|
|
@@ -92,19 +142,46 @@ dialog-panel {
|
|
|
92
142
|
dialog-overlay {
|
|
93
143
|
background-color: rgba(0, 0, 0, 0.5);
|
|
94
144
|
}
|
|
95
|
-
|
|
96
|
-
[data-action='hide-dialog'] {
|
|
97
|
-
font-size: 24px;
|
|
98
|
-
color: #333;
|
|
99
|
-
}
|
|
100
145
|
```
|
|
101
146
|
|
|
102
147
|
### JavaScript API
|
|
103
148
|
|
|
104
149
|
#### Methods
|
|
105
150
|
|
|
106
|
-
- `show()`: Opens the dialog panel
|
|
107
|
-
- `hide()`: Closes the dialog panel
|
|
151
|
+
- `show(triggerEl)`: Opens the dialog panel. Returns false if the action was prevented.
|
|
152
|
+
- `hide()`: Closes the dialog panel. Returns false if the action was prevented.
|
|
153
|
+
|
|
154
|
+
#### Events
|
|
155
|
+
|
|
156
|
+
The dialog panel emits the following events that you can listen for:
|
|
157
|
+
|
|
158
|
+
- `beforeShow`: Fired before the dialog starts to show. Cancelable - you can call `preventDefault()` to prevent the dialog from opening.
|
|
159
|
+
- `show`: Fired when the dialog has been shown (after transitions).
|
|
160
|
+
- `beforeHide`: Fired before the dialog starts to hide. Cancelable - you can call `preventDefault()` to prevent the dialog from closing.
|
|
161
|
+
- `hide`: Fired when the dialog has started hiding (transition begins).
|
|
162
|
+
- `afterHide`: Fired when the dialog has completed its hide transition.
|
|
163
|
+
|
|
164
|
+
Each event provides a `detail` object with the `triggerElement` that initiated the action (if any).
|
|
165
|
+
|
|
166
|
+
Example usage:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
const dialog = document.getElementById('my-dialog');
|
|
170
|
+
|
|
171
|
+
// Prevent dialog from closing based on some condition
|
|
172
|
+
dialog.addEventListener('beforeHide', (e) => {
|
|
173
|
+
if (someFormIsUnsaved) {
|
|
174
|
+
e.preventDefault(); // Prevents the dialog from closing
|
|
175
|
+
// Show a confirmation message instead
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Do something after the dialog is fully hidden
|
|
180
|
+
dialog.addEventListener('afterHide', () => {
|
|
181
|
+
console.log('Dialog is now fully hidden');
|
|
182
|
+
// Clean up or reset form fields, etc.
|
|
183
|
+
});
|
|
184
|
+
```
|
|
108
185
|
|
|
109
186
|
## Browser Support
|
|
110
187
|
|
package/dist/dialog-panel.cjs.js
CHANGED
|
@@ -2,204 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
require('@magic-spells/focus-trap');
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {HTMLElement} container - The container element to search for focusable elements.
|
|
9
|
-
* @returns {HTMLElement[]} An array of focusable elements found within the container.
|
|
8
|
+
* Custom element that creates an accessible modal dialog panel with focus management
|
|
9
|
+
* @extends HTMLElement
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return Array.from(container.querySelectorAll(focusableSelectors));
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
class FocusTrap extends HTMLElement {
|
|
18
|
-
/** @type {boolean} Indicates whether the styles have been injected into the DOM. */
|
|
19
|
-
static styleInjected = false;
|
|
20
|
-
|
|
21
|
-
constructor() {
|
|
22
|
-
super();
|
|
23
|
-
this.trapStart = null;
|
|
24
|
-
this.trapEnd = null;
|
|
25
|
-
|
|
26
|
-
// Inject styles only once, when the first FocusTrap instance is created.
|
|
27
|
-
if (!FocusTrap.styleInjected) {
|
|
28
|
-
this.injectStyles();
|
|
29
|
-
FocusTrap.styleInjected = true;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Injects necessary styles for the focus trap into the document's head.
|
|
35
|
-
* This ensures that focus-trap-start and focus-trap-end elements are hidden.
|
|
36
|
-
*/
|
|
37
|
-
injectStyles() {
|
|
38
|
-
const style = document.createElement('style');
|
|
39
|
-
style.textContent = `
|
|
40
|
-
focus-trap-start,
|
|
41
|
-
focus-trap-end {
|
|
42
|
-
position: absolute;
|
|
43
|
-
width: 1px;
|
|
44
|
-
height: 1px;
|
|
45
|
-
margin: -1px;
|
|
46
|
-
padding: 0;
|
|
47
|
-
border: 0;
|
|
48
|
-
clip: rect(0, 0, 0, 0);
|
|
49
|
-
overflow: hidden;
|
|
50
|
-
white-space: nowrap;
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
53
|
-
document.head.appendChild(style);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Called when the element is connected to the DOM.
|
|
58
|
-
* Sets up the focus trap and adds the keydown event listener.
|
|
59
|
-
*/
|
|
60
|
-
connectedCallback() {
|
|
61
|
-
this.setupTrap();
|
|
62
|
-
this.addEventListener('keydown', this.handleKeyDown);
|
|
63
|
-
}
|
|
11
|
+
class DialogPanel extends HTMLElement {
|
|
12
|
+
#handleTransitionEnd;
|
|
13
|
+
#scrollPosition = 0;
|
|
64
14
|
|
|
65
15
|
/**
|
|
66
|
-
*
|
|
67
|
-
* Removes the keydown event listener.
|
|
16
|
+
* Clean up event listeners when component is removed from DOM
|
|
68
17
|
*/
|
|
69
18
|
disconnectedCallback() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*/
|
|
77
|
-
setupTrap() {
|
|
78
|
-
// check to see it there are any focusable children
|
|
79
|
-
const focusableElements = getFocusableElements(this);
|
|
80
|
-
// exit if there aren't any
|
|
81
|
-
if (focusableElements.length === 0) return;
|
|
82
|
-
|
|
83
|
-
// create trap start and end elements
|
|
84
|
-
this.trapStart = document.createElement('focus-trap-start');
|
|
85
|
-
this.trapEnd = document.createElement('focus-trap-end');
|
|
86
|
-
|
|
87
|
-
// add to DOM
|
|
88
|
-
this.prepend(this.trapStart);
|
|
89
|
-
this.append(this.trapEnd);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Handles the keydown event. If the Escape key is pressed, the focus trap is exited.
|
|
94
|
-
*
|
|
95
|
-
* @param {KeyboardEvent} e - The keyboard event object.
|
|
96
|
-
*/
|
|
97
|
-
handleKeyDown = (e) => {
|
|
98
|
-
if (e.key === 'Escape') {
|
|
99
|
-
e.preventDefault();
|
|
100
|
-
this.exitTrap();
|
|
19
|
+
const _ = this;
|
|
20
|
+
if (_.contentPanel) {
|
|
21
|
+
_.contentPanel.removeEventListener(
|
|
22
|
+
'transitionend',
|
|
23
|
+
_.#handleTransitionEnd
|
|
24
|
+
);
|
|
101
25
|
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Exits the focus trap by hiding the current container and shifting focus
|
|
106
|
-
* back to the trigger element that opened the trap.
|
|
107
|
-
*/
|
|
108
|
-
exitTrap() {
|
|
109
|
-
const container = this.closest('[aria-hidden="false"]');
|
|
110
|
-
if (!container) return;
|
|
111
26
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`[aria-expanded="true"][aria-controls="${container.id}"]`
|
|
116
|
-
);
|
|
117
|
-
if (trigger) {
|
|
118
|
-
trigger.setAttribute('aria-expanded', 'false');
|
|
119
|
-
trigger.focus();
|
|
120
|
-
}
|
|
27
|
+
// Ensure body scroll is restored if component is removed while open
|
|
28
|
+
document.body.classList.remove('overflow-hidden');
|
|
29
|
+
this.#restoreScroll();
|
|
121
30
|
}
|
|
122
|
-
}
|
|
123
31
|
|
|
124
|
-
class FocusTrapStart extends HTMLElement {
|
|
125
32
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
33
|
+
* Saves current scroll position and locks body scrolling
|
|
34
|
+
* @private
|
|
128
35
|
*/
|
|
129
|
-
|
|
130
|
-
this
|
|
131
|
-
|
|
132
|
-
|
|
36
|
+
#lockScroll() {
|
|
37
|
+
const _ = this;
|
|
38
|
+
// Save current scroll position
|
|
39
|
+
_.#scrollPosition = window.pageYOffset;
|
|
133
40
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*/
|
|
138
|
-
disconnectedCallback() {
|
|
139
|
-
this.removeEventListener('focus', this.handleFocus);
|
|
41
|
+
// Apply fixed position to body
|
|
42
|
+
document.body.classList.add('overflow-hidden');
|
|
43
|
+
document.body.style.top = `-${_.#scrollPosition}px`;
|
|
140
44
|
}
|
|
141
45
|
|
|
142
46
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* @param {FocusEvent} e - The focus event object.
|
|
147
|
-
*/
|
|
148
|
-
handleFocus = (e) => {
|
|
149
|
-
const trap = this.closest('focus-trap');
|
|
150
|
-
const focusableElements = getFocusableElements(trap);
|
|
151
|
-
|
|
152
|
-
if (focusableElements.length === 0) return;
|
|
153
|
-
|
|
154
|
-
const firstElement = focusableElements[0];
|
|
155
|
-
const lastElement =
|
|
156
|
-
focusableElements[focusableElements.length - 1];
|
|
157
|
-
|
|
158
|
-
if (e.relatedTarget === firstElement) {
|
|
159
|
-
lastElement.focus();
|
|
160
|
-
} else {
|
|
161
|
-
firstElement.focus();
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
class FocusTrapEnd extends HTMLElement {
|
|
167
|
-
/**
|
|
168
|
-
* Called when the element is connected to the DOM.
|
|
169
|
-
* Sets the tabindex and adds the focus event listener.
|
|
47
|
+
* Restores scroll position when dialog is closed
|
|
48
|
+
* @private
|
|
170
49
|
*/
|
|
171
|
-
|
|
172
|
-
this
|
|
173
|
-
|
|
174
|
-
|
|
50
|
+
#restoreScroll() {
|
|
51
|
+
const _ = this;
|
|
52
|
+
// Remove fixed positioning
|
|
53
|
+
document.body.classList.remove('overflow-hidden');
|
|
54
|
+
document.body.style.removeProperty('top');
|
|
175
55
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
* Removes the focus event listener.
|
|
179
|
-
*/
|
|
180
|
-
disconnectedCallback() {
|
|
181
|
-
this.removeEventListener('focus', this.handleFocus);
|
|
56
|
+
// Restore scroll position
|
|
57
|
+
window.scrollTo(0, _.#scrollPosition);
|
|
182
58
|
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Handles the focus event. When the trap end is focused, focus is shifted back to the trap start.
|
|
186
|
-
*/
|
|
187
|
-
handleFocus = () => {
|
|
188
|
-
const trap = this.closest('focus-trap');
|
|
189
|
-
const trapStart = trap.querySelector('focus-trap-start');
|
|
190
|
-
trapStart.focus();
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
customElements.define('focus-trap', FocusTrap);
|
|
195
|
-
customElements.define('focus-trap-start', FocusTrapStart);
|
|
196
|
-
customElements.define('focus-trap-end', FocusTrapEnd);
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Custom element that creates an accessible modal dialog panel with focus management
|
|
200
|
-
* @extends HTMLElement
|
|
201
|
-
*/
|
|
202
|
-
class DialogPanel extends HTMLElement {
|
|
203
59
|
/**
|
|
204
60
|
* Initializes the dialog panel, sets up focus trap and overlay
|
|
205
61
|
*/
|
|
@@ -215,6 +71,24 @@ class DialogPanel extends HTMLElement {
|
|
|
215
71
|
_.focusTrap = document.createElement('focus-trap');
|
|
216
72
|
_.triggerEl = null;
|
|
217
73
|
|
|
74
|
+
// Create a handler for transition end events
|
|
75
|
+
_.#handleTransitionEnd = (e) => {
|
|
76
|
+
if (
|
|
77
|
+
e.propertyName === 'opacity' &&
|
|
78
|
+
_.getAttribute('aria-hidden') === 'true'
|
|
79
|
+
) {
|
|
80
|
+
_.contentPanel.classList.add('hidden');
|
|
81
|
+
|
|
82
|
+
// Dispatch afterHide event - dialog has completed its transition
|
|
83
|
+
_.dispatchEvent(
|
|
84
|
+
new CustomEvent('afterHide', {
|
|
85
|
+
bubbles: true,
|
|
86
|
+
detail: { triggerElement: _.triggerEl },
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
218
92
|
// Ensure we have labelledby and describedby references
|
|
219
93
|
if (!_.getAttribute('aria-labelledby')) {
|
|
220
94
|
const heading = _.querySelector('h1, h2, h3');
|
|
@@ -245,26 +119,31 @@ class DialogPanel extends HTMLElement {
|
|
|
245
119
|
* @private
|
|
246
120
|
*/
|
|
247
121
|
#bindUI() {
|
|
122
|
+
const _ = this;
|
|
123
|
+
|
|
248
124
|
// Handle trigger buttons
|
|
249
125
|
document.addEventListener('click', (e) => {
|
|
250
|
-
const trigger = e.target.closest(
|
|
251
|
-
`[aria-controls="${this.id}"]`
|
|
252
|
-
);
|
|
126
|
+
const trigger = e.target.closest(`[aria-controls="${_.id}"]`);
|
|
253
127
|
if (!trigger) return;
|
|
254
128
|
|
|
255
129
|
if (trigger.getAttribute('data-prevent-default') === 'true') {
|
|
256
130
|
e.preventDefault();
|
|
257
131
|
}
|
|
258
132
|
|
|
259
|
-
|
|
260
|
-
this.show(trigger);
|
|
133
|
+
_.show(trigger);
|
|
261
134
|
});
|
|
262
135
|
|
|
263
136
|
// Handle close buttons
|
|
264
|
-
|
|
137
|
+
_.addEventListener('click', (e) => {
|
|
265
138
|
if (!e.target.closest('[data-action="hide-dialog"]')) return;
|
|
266
|
-
|
|
139
|
+
_.hide();
|
|
267
140
|
});
|
|
141
|
+
|
|
142
|
+
// Add transition end listener
|
|
143
|
+
_.contentPanel.addEventListener(
|
|
144
|
+
'transitionend',
|
|
145
|
+
_.#handleTransitionEnd
|
|
146
|
+
);
|
|
268
147
|
}
|
|
269
148
|
|
|
270
149
|
/**
|
|
@@ -282,50 +161,108 @@ class DialogPanel extends HTMLElement {
|
|
|
282
161
|
/**
|
|
283
162
|
* Shows the dialog and traps focus within it
|
|
284
163
|
* @param {HTMLElement} [triggerEl=null] - The element that triggered the dialog
|
|
164
|
+
* @fires DialogPanel#beforeShow - Fired before the dialog starts to show
|
|
165
|
+
* @fires DialogPanel#show - Fired when the dialog has been shown
|
|
166
|
+
* @returns {boolean} False if the show was prevented by a beforeShow event handler
|
|
285
167
|
*/
|
|
286
168
|
show(triggerEl = null) {
|
|
287
|
-
|
|
169
|
+
const _ = this;
|
|
170
|
+
_.triggerEl = triggerEl || false;
|
|
288
171
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
172
|
+
// Dispatch beforeShow event - allows preventing the dialog from opening
|
|
173
|
+
const beforeShowEvent = new CustomEvent('beforeShow', {
|
|
174
|
+
bubbles: true,
|
|
175
|
+
cancelable: true,
|
|
176
|
+
detail: { triggerElement: _.triggerEl },
|
|
177
|
+
});
|
|
294
178
|
|
|
295
|
-
|
|
296
|
-
document.body.classList.add('overflow-hidden');
|
|
179
|
+
const showAllowed = _.dispatchEvent(beforeShowEvent);
|
|
297
180
|
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
181
|
+
// If event was canceled (preventDefault was called), don't show the dialog
|
|
182
|
+
if (!showAllowed) return false;
|
|
183
|
+
|
|
184
|
+
// Remove the hidden class first to ensure content is rendered
|
|
185
|
+
_.contentPanel.classList.remove('hidden');
|
|
186
|
+
|
|
187
|
+
// Give the browser a moment to process before starting animation
|
|
188
|
+
requestAnimationFrame(() => {
|
|
189
|
+
// Update ARIA states
|
|
190
|
+
_.setAttribute('aria-hidden', 'false');
|
|
191
|
+
if (_.triggerEl) {
|
|
192
|
+
_.triggerEl.setAttribute('aria-expanded', 'true');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Lock body scrolling and save scroll position
|
|
196
|
+
_.#lockScroll();
|
|
197
|
+
|
|
198
|
+
// Focus management
|
|
199
|
+
const firstFocusable = _.querySelector(
|
|
200
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
201
|
+
);
|
|
202
|
+
if (firstFocusable) {
|
|
203
|
+
requestAnimationFrame(() => {
|
|
204
|
+
firstFocusable.focus();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Dispatch show event - dialog is now visible
|
|
209
|
+
_.dispatchEvent(
|
|
210
|
+
new CustomEvent('show', {
|
|
211
|
+
bubbles: true,
|
|
212
|
+
detail: { triggerElement: _.triggerEl },
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return true;
|
|
307
218
|
}
|
|
308
219
|
|
|
309
220
|
/**
|
|
310
221
|
* Hides the dialog and restores focus
|
|
222
|
+
* @fires DialogPanel#beforeHide - Fired before the dialog starts to hide
|
|
223
|
+
* @fires DialogPanel#hide - Fired when the dialog has started hiding (transition begins)
|
|
224
|
+
* @fires DialogPanel#afterHide - Fired when the dialog has completed its hide transition
|
|
225
|
+
* @returns {boolean} False if the hide was prevented by a beforeHide event handler
|
|
311
226
|
*/
|
|
312
227
|
hide() {
|
|
313
|
-
|
|
314
|
-
|
|
228
|
+
const _ = this;
|
|
229
|
+
|
|
230
|
+
// Dispatch beforeHide event - allows preventing the dialog from closing
|
|
231
|
+
const beforeHideEvent = new CustomEvent('beforeHide', {
|
|
232
|
+
bubbles: true,
|
|
233
|
+
cancelable: true,
|
|
234
|
+
detail: { triggerElement: _.triggerEl },
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const hideAllowed = _.dispatchEvent(beforeHideEvent);
|
|
238
|
+
|
|
239
|
+
// If event was canceled (preventDefault was called), don't hide the dialog
|
|
240
|
+
if (!hideAllowed) return false;
|
|
241
|
+
|
|
242
|
+
// Restore body scroll and scroll position
|
|
243
|
+
_.#restoreScroll();
|
|
315
244
|
|
|
316
245
|
// Update ARIA states
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
console.log('we need to blur focus');
|
|
246
|
+
if (_.triggerEl) {
|
|
247
|
+
// remove focus from modal panel first
|
|
248
|
+
_.triggerEl.focus();
|
|
249
|
+
// mark trigger as no longer expanded
|
|
250
|
+
_.triggerEl.setAttribute('aria-expanded', 'false');
|
|
323
251
|
}
|
|
324
252
|
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
253
|
+
// Set aria-hidden to start transition
|
|
254
|
+
// The transitionend event handler will add display:none when complete
|
|
255
|
+
_.setAttribute('aria-hidden', 'true');
|
|
256
|
+
|
|
257
|
+
// Dispatch hide event - dialog is now starting to hide
|
|
258
|
+
_.dispatchEvent(
|
|
259
|
+
new CustomEvent('hide', {
|
|
260
|
+
bubbles: true,
|
|
261
|
+
detail: { triggerElement: _.triggerEl },
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return true;
|
|
329
266
|
}
|
|
330
267
|
}
|
|
331
268
|
|
|
@@ -360,9 +297,15 @@ class DialogContent extends HTMLElement {
|
|
|
360
297
|
}
|
|
361
298
|
}
|
|
362
299
|
|
|
363
|
-
customElements.
|
|
364
|
-
customElements.define('dialog-
|
|
365
|
-
|
|
300
|
+
if (!customElements.get('dialog-panel')) {
|
|
301
|
+
customElements.define('dialog-panel', DialogPanel);
|
|
302
|
+
}
|
|
303
|
+
if (!customElements.get('dialog-overlay')) {
|
|
304
|
+
customElements.define('dialog-overlay', DialogOverlay);
|
|
305
|
+
}
|
|
306
|
+
if (!customElements.get('dialog-content')) {
|
|
307
|
+
customElements.define('dialog-content', DialogContent);
|
|
308
|
+
}
|
|
366
309
|
|
|
367
310
|
exports.DialogContent = DialogContent;
|
|
368
311
|
exports.DialogOverlay = DialogOverlay;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-panel.cjs.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.cjs.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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"}
|