@vmz/ui 0.2.3 → 0.2.5

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.
Files changed (63) hide show
  1. package/package.json +1 -1
  2. package/src/components/Accordion.vmz +46 -63
  3. package/src/components/Alert.vmz +35 -38
  4. package/src/components/AppShell.vmz +92 -75
  5. package/src/components/Autocomplete.vmz +91 -119
  6. package/src/components/Avatar.vmz +37 -8
  7. package/src/components/Badge.vmz +29 -23
  8. package/src/components/Breadcrumb.vmz +36 -34
  9. package/src/components/BulkActions.vmz +22 -28
  10. package/src/components/Button.vmz +139 -121
  11. package/src/components/Callout.vmz +33 -36
  12. package/src/components/Card.vmz +68 -51
  13. package/src/components/Checkbox.vmz +28 -47
  14. package/src/components/CodeBlock.vmz +31 -25
  15. package/src/components/Collapse.vmz +34 -16
  16. package/src/components/ConsoleShell.vmz +107 -110
  17. package/src/components/Content.vmz +8 -4
  18. package/src/components/DataTable.vmz +124 -150
  19. package/src/components/DatePicker.vmz +526 -521
  20. package/src/components/Dialog.vmz +408 -327
  21. package/src/components/Divider.vmz +38 -11
  22. package/src/components/Drawer.vmz +325 -318
  23. package/src/components/Dropdown.vmz +53 -19
  24. package/src/components/Empty.vmz +23 -21
  25. package/src/components/Field.vmz +77 -83
  26. package/src/components/FilterBar.vmz +8 -10
  27. package/src/components/Flex.vmz +21 -6
  28. package/src/components/Footer.vmz +9 -4
  29. package/src/components/Form.vmz +35 -46
  30. package/src/components/FormItem.vmz +44 -43
  31. package/src/components/Grid.vmz +22 -6
  32. package/src/components/Header.vmz +11 -4
  33. package/src/components/Icon.vmz +43 -44
  34. package/src/components/Layout.vmz +9 -4
  35. package/src/components/Link.vmz +17 -25
  36. package/src/components/List.vmz +62 -75
  37. package/src/components/Menu.vmz +182 -197
  38. package/src/components/Notification.vmz +35 -38
  39. package/src/components/Pagination.vmz +37 -47
  40. package/src/components/Popover.vmz +157 -171
  41. package/src/components/Progress.vmz +36 -10
  42. package/src/components/Prose.vmz +29 -31
  43. package/src/components/QueryForm.vmz +14 -22
  44. package/src/components/RadioGroup.vmz +70 -84
  45. package/src/components/Result.vmz +36 -38
  46. package/src/components/Segmented.vmz +43 -22
  47. package/src/components/Select.vmz +404 -411
  48. package/src/components/Sider.vmz +21 -4
  49. package/src/components/Skeleton.vmz +34 -42
  50. package/src/components/Space.vmz +24 -9
  51. package/src/components/Spinner.vmz +17 -23
  52. package/src/components/Steps.vmz +48 -51
  53. package/src/components/Switch.vmz +50 -60
  54. package/src/components/Table.vmz +81 -84
  55. package/src/components/Tabs.vmz +66 -88
  56. package/src/components/Tag.vmz +36 -11
  57. package/src/components/TextArea.vmz +73 -77
  58. package/src/components/Timeline.vmz +36 -32
  59. package/src/components/Toc.vmz +31 -37
  60. package/src/components/Tooltip.vmz +25 -35
  61. package/src/components/Tree.vmz +127 -144
  62. package/src/components/Typography.vmz +33 -7
  63. package/src/components/Upload.vmz +1135 -1057
@@ -1,75 +1,49 @@
1
1
  <template>
2
- <div
3
- class="vmz-ui-dialog-root"
4
- data-vmz-ui="dialog"
5
- :data-vmz-overlay-open="open"
6
- >
7
- <div
8
- v-if="open"
9
- class="vmz-ui-dialog"
10
- data-vmz-overlay="dialog"
11
- data-vmz-overlay-owner="dialog"
12
- :data-vmz-overlay-stack="stackLevel"
13
- >
14
- <button
15
- type="button"
16
- class="vmz-ui-dialog__backdrop"
17
- data-vmz-overlay-layer="backdrop"
18
- aria-label="Dismiss dialog"
19
- @click="dismiss"
20
- ></button>
21
- <div
22
- class="vmz-ui-dialog__panel"
23
- role="dialog"
24
- aria-modal="true"
25
- :aria-labelledby="titleId"
26
- tabindex="-1"
27
- data-vmz-overlay-layer="panel"
28
- data-vmz-focus="enter"
29
- data-vmz-motion="overlay-enter"
30
- data-vmz-motion-token="motion.overlay"
31
- :onKeyDown="onPanelKeyDown"
32
- >
33
- <h2 :id="titleId" class="vmz-ui-dialog__title">{{ title }}</h2>
34
- <div class="vmz-ui-dialog__body">
35
- <slot />
36
- </div>
37
- <div class="vmz-ui-dialog__actions">
38
- <button type="button" class="vmz-ui-dialog__close" data-vmz-focus="close" @click="dismiss">
39
- Close
40
- </button>
41
- </div>
2
+ <div data-vmz-ui="dialog" :data-vmz-overlay-open="open" class="vmz-ui-dialog-root">
3
+ <div v-if="open" data-vmz-overlay="dialog" data-vmz-overlay-owner="dialog" :data-vmz-overlay-stack="stackLevel" class="vmz-ui-dialog">
4
+ <button type="button" data-vmz-overlay-layer="backdrop" aria-label="Dismiss dialog" @click="dismiss" class="vmz-ui-dialog__backdrop" />
5
+ <div role="dialog" aria-modal="true" :aria-labelledby="titleId" tabindex="-1" data-vmz-overlay-layer="panel" data-vmz-focus="enter" data-vmz-motion="overlay-enter" data-vmz-motion-token="motion.overlay" :onKeyDown="onPanelKeyDown" class="vmz-ui-dialog__panel">
6
+ <h2 :id="titleId" class="vmz-ui-dialog__title">
7
+ {{ title }}
8
+ </h2>
9
+ <div class="vmz-ui-dialog__body">
10
+ <slot />
11
+ </div>
12
+ <div class="vmz-ui-dialog__actions">
13
+ <button type="button" data-vmz-focus="close" @click="dismiss" class="vmz-ui-dialog__close">
14
+ Close
15
+ </button>
16
+ </div>
17
+ </div>
42
18
  </div>
43
- </div>
44
19
  </div>
45
20
  </template>
46
-
47
21
  <style>
48
- .vmz-ui-dialog-root {
22
+ .vmz-ui-dialog-root {
49
23
  display: block;
50
- }
24
+ }
51
25
 
52
- .vmz-ui-dialog {
26
+ .vmz-ui-dialog {
53
27
  position: fixed;
54
28
  inset: 0;
55
29
  z-index: 40;
56
30
  display: grid;
57
31
  place-items: center;
58
- }
32
+ }
59
33
 
60
- .vmz-ui-dialog[data-vmz-overlay-stack='1'] {
34
+ .vmz-ui-dialog[data-vmz-overlay-stack="1"] {
61
35
  z-index: 50;
62
- }
36
+ }
63
37
 
64
- .vmz-ui-dialog[data-vmz-overlay-stack='2'] {
38
+ .vmz-ui-dialog[data-vmz-overlay-stack="2"] {
65
39
  z-index: 60;
66
- }
40
+ }
67
41
 
68
- .vmz-ui-dialog[data-vmz-overlay-stack='3'] {
42
+ .vmz-ui-dialog[data-vmz-overlay-stack="3"] {
69
43
  z-index: 70;
70
- }
44
+ }
71
45
 
72
- .vmz-ui-dialog__backdrop {
46
+ .vmz-ui-dialog__backdrop {
73
47
  position: absolute;
74
48
  inset: 0;
75
49
  border: 0;
@@ -77,10 +51,11 @@
77
51
  margin: 0;
78
52
  cursor: pointer;
79
53
  background: rgba(0, 0, 0, 0.45);
80
- animation: vmz-ui-overlay-fade-in var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
81
- }
54
+ animation: vmz-ui-overlay-fade-in var(--vmz-motion-overlay-duration)
55
+ var(--vmz-motion-overlay-easing);
56
+ }
82
57
 
83
- .vmz-ui-dialog__panel {
58
+ .vmz-ui-dialog__panel {
84
59
  position: relative;
85
60
  z-index: 1;
86
61
  width: min(32rem, calc(100vw - 2rem));
@@ -91,40 +66,43 @@
91
66
  color: var(--vmz-text-ink);
92
67
  box-shadow: var(--vmz-shadow-popup);
93
68
  outline: none;
94
- animation: vmz-ui-overlay-panel-in var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
95
- }
69
+ animation: vmz-ui-overlay-panel-in var(--vmz-motion-overlay-duration)
70
+ var(--vmz-motion-overlay-easing);
71
+ }
96
72
 
97
- .vmz-ui-dialog[data-vmz-motion-adopt='true'] .vmz-ui-dialog__backdrop,
98
- .vmz-ui-dialog[data-vmz-motion-adopt='true'] .vmz-ui-dialog__panel {
73
+ .vmz-ui-dialog[data-vmz-motion-adopt="true"] .vmz-ui-dialog__backdrop,
74
+ .vmz-ui-dialog[data-vmz-motion-adopt="true"] .vmz-ui-dialog__panel {
99
75
  animation: none;
100
- }
76
+ }
101
77
 
102
- .vmz-ui-dialog[data-vmz-motion-phase='exit'] .vmz-ui-dialog__backdrop {
103
- animation: vmz-ui-overlay-fade-out var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
104
- }
78
+ .vmz-ui-dialog[data-vmz-motion-phase="exit"] .vmz-ui-dialog__backdrop {
79
+ animation: vmz-ui-overlay-fade-out var(--vmz-motion-overlay-duration)
80
+ var(--vmz-motion-overlay-easing);
81
+ }
105
82
 
106
- .vmz-ui-dialog[data-vmz-motion-phase='exit'] .vmz-ui-dialog__panel {
107
- animation: vmz-ui-overlay-panel-out var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing);
108
- }
83
+ .vmz-ui-dialog[data-vmz-motion-phase="exit"] .vmz-ui-dialog__panel {
84
+ animation: vmz-ui-overlay-panel-out var(--vmz-motion-overlay-duration)
85
+ var(--vmz-motion-overlay-easing);
86
+ }
109
87
 
110
- .vmz-ui-dialog__panel:focus-visible {
88
+ .vmz-ui-dialog__panel:focus-visible {
111
89
  box-shadow: 0 0 0 2px var(--vmz-focus-ring);
112
- }
90
+ }
113
91
 
114
- .vmz-ui-dialog__title {
92
+ .vmz-ui-dialog__title {
115
93
  margin: 0 0 0.75rem;
116
94
  font-size: 1rem;
117
95
  font-weight: 600;
118
96
  line-height: 1.4;
119
97
  color: var(--vmz-text-ink);
120
- }
98
+ }
121
99
 
122
- .vmz-ui-dialog__body {
100
+ .vmz-ui-dialog__body {
123
101
  margin-bottom: 1rem;
124
102
  color: var(--vmz-text-ink);
125
- }
103
+ }
126
104
 
127
- .vmz-ui-dialog__close {
105
+ .vmz-ui-dialog__close {
128
106
  font: inherit;
129
107
  font-size: 0.875rem;
130
108
  font-weight: 400;
@@ -135,291 +113,394 @@
135
113
  cursor: pointer;
136
114
  background: var(--vmz-surface-paper, #fff);
137
115
  color: var(--vmz-text-ink);
138
- }
116
+ }
139
117
 
140
- .vmz-ui-dialog__close:hover {
118
+ .vmz-ui-dialog__close:hover {
141
119
  color: var(--vmz-action-primary-background);
142
120
  border-color: var(--vmz-action-primary-background);
143
- }
121
+ }
144
122
 
145
- .vmz-ui-dialog__close:focus-visible {
123
+ .vmz-ui-dialog__close:focus-visible {
146
124
  box-shadow: 0 0 0 2px var(--vmz-focus-ring);
147
- }
125
+ }
148
126
 
149
- @keyframes vmz-ui-overlay-fade-in {
127
+ @keyframes vmz-ui-overlay-fade-in {
150
128
  from {
151
- opacity: 0;
129
+ opacity: 0;
152
130
  }
153
131
  to {
154
- opacity: 1;
132
+ opacity: 1;
155
133
  }
156
- }
134
+ }
157
135
 
158
- @keyframes vmz-ui-overlay-panel-in {
136
+ @keyframes vmz-ui-overlay-panel-in {
159
137
  from {
160
- opacity: 0;
161
- transform: translateY(0.35rem);
138
+ opacity: 0;
139
+ transform: translateY(0.35rem);
162
140
  }
163
141
  to {
164
- opacity: 1;
165
- transform: none;
142
+ opacity: 1;
143
+ transform: none;
166
144
  }
167
- }
145
+ }
168
146
 
169
- @keyframes vmz-ui-overlay-fade-out {
147
+ @keyframes vmz-ui-overlay-fade-out {
170
148
  from {
171
- opacity: 1;
149
+ opacity: 1;
172
150
  }
173
151
  to {
174
- opacity: 0;
152
+ opacity: 0;
175
153
  }
176
- }
154
+ }
177
155
 
178
- @keyframes vmz-ui-overlay-panel-out {
156
+ @keyframes vmz-ui-overlay-panel-out {
179
157
  from {
180
- opacity: 1;
181
- transform: none;
158
+ opacity: 1;
159
+ transform: none;
182
160
  }
183
161
  to {
184
- opacity: 0;
185
- transform: translateY(0.35rem);
162
+ opacity: 0;
163
+ transform: translateY(0.35rem);
186
164
  }
187
- }
165
+ }
188
166
 
189
- @media (prefers-reduced-motion: reduce) {
167
+ @media (prefers-reduced-motion: reduce) {
190
168
  .vmz-ui-dialog__backdrop,
191
169
  .vmz-ui-dialog__panel,
192
- .vmz-ui-dialog[data-vmz-motion-phase='exit'] .vmz-ui-dialog__backdrop,
193
- .vmz-ui-dialog[data-vmz-motion-phase='exit'] .vmz-ui-dialog__panel {
194
- animation: none;
170
+ .vmz-ui-dialog[data-vmz-motion-phase="exit"] .vmz-ui-dialog__backdrop,
171
+ .vmz-ui-dialog[data-vmz-motion-phase="exit"] .vmz-ui-dialog__panel {
172
+ animation: none;
195
173
  }
196
- }
174
+ }
197
175
  </style>
198
-
199
176
  <script client>
200
177
  /**
201
- * VMZ UI — Dialog (UI1).
202
- * Live `open` + overlay ownership + focus + Escape/outside dismiss + stackLevel.
203
- * SSR/resume adopt skips enter replay; dismiss plays overlay-exit then calls onClose.
204
- */
178
+ * VMZ UI — Dialog (UI1).
179
+ * Live `open` + overlay ownership + focus + Escape/outside dismiss + stackLevel.
180
+ * SSR/resume adopt skips enter replay; dismiss plays overlay-exit then calls onClose.
181
+ */
205
182
  export default class Dialog {
206
- public open: boolean = false;
207
- public title: string = "Dialog";
208
- public titleId: string = "vmz-dialog-title";
209
- public stackLevel: string = "0";
210
- public onClose: (() => void) | null = null;
211
- _restoreFocus = null;
212
- _panel = null;
213
- _docKeyHandler = null;
214
- _observer = null;
215
- _adoptNextEnter = false;
216
- _exiting = false;
217
- _exitTimer = null;
218
- /** Motion generation — exit timer and cancel edges key off this owner counter. */
219
- _motionGen = 0;
220
- async onMount() {
221
- if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
222
- const root = this.__vmzDomRoot;
223
- if (!root || typeof root.querySelector !== "function") return;
224
- if (root.querySelector("[data-vmz-overlay=\"dialog\"]")) this._adoptNextEnter = true;
225
- this._observer = new MutationObserver(() => this._syncFromDom());
226
- this._observer.observe(root, {
227
- childList: true,
228
- subtree: true
229
- });
230
- this._syncFromDom();
231
- }
232
- onDestroy() {
233
- if (this._exitTimer) {
234
- clearTimeout(this._exitTimer);
235
- this._exitTimer = null;
236
- }
237
- this._exiting = false;
238
- if (this._observer) {
239
- this._observer.disconnect();
240
- this._observer = null;
241
- }
242
- this._teardownDocKey();
243
- this._panel = null;
244
- this._restoreFocus = null;
245
- }
246
- dismiss() {
247
- if (!this._isTopmostStack()) return;
248
- // Reverse / interrupt: second dismiss during exit cancels close (stay open).
249
- if (this._exiting) {
250
- this._cancelExit("reverse");
251
- return;
252
- }
253
- const panel = this._panel;
254
- const overlay = panel && typeof panel.closest === "function" ? panel.closest("[data-vmz-overlay=\"dialog\"]") : null;
255
- const reduce = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
256
- if (panel && overlay && !reduce) {
257
- this._exiting = true;
258
- const exitGen = this._motionGen;
259
- overlay.setAttribute("data-vmz-motion-phase", "exit");
260
- overlay.removeAttribute("data-vmz-motion-cancelled");
261
- panel.setAttribute("data-vmz-motion", "overlay-exit");
262
- const ms = this._overlayDurationMs(panel);
263
- this._exitTimer = setTimeout(() => {
264
- this._exitTimer = null;
265
- // Stale exit after interrupt/re-enter must not call onClose.
266
- if (!this._exiting || exitGen !== this._motionGen) return;
267
- this._exiting = false;
268
- this.emit("close");
269
- if (typeof this.onClose === "function") this.onClose();
270
- }, ms);
271
- return;
272
- }
273
- this.emit("close");
274
- if (typeof this.onClose === "function") this.onClose();
275
- }
276
- _cancelExit(reason) {
277
- if (this._exitTimer) {
278
- clearTimeout(this._exitTimer);
279
- this._exitTimer = null;
280
- }
281
- this._exiting = false;
282
- this._motionGen += 1;
283
- const panel = this._panel;
284
- const overlay = panel && typeof panel.closest === "function" ? panel.closest("[data-vmz-overlay=\"dialog\"]") : null;
285
- if (overlay) {
286
- overlay.removeAttribute("data-vmz-motion-phase");
287
- overlay.setAttribute("data-vmz-motion-cancelled", reason || "true");
288
- overlay.setAttribute("data-vmz-motion-gen", String(this._motionGen));
289
- }
290
- if (panel) {
291
- panel.setAttribute("data-vmz-motion", "overlay-enter");
292
- }
293
- }
294
- _overlayDurationMs(panel) {
295
- try {
296
- const raw = (getComputedStyle(panel).getPropertyValue("--vmz-motion-overlay-duration") || "").trim() || "180ms";
297
- if (raw.endsWith("ms")) return Math.max(0, Number.parseFloat(raw) || 180);
298
- if (raw.endsWith("s")) return Math.max(0, (Number.parseFloat(raw) || .18) * 1e3);
299
- return 180;
300
- } catch {
301
- return 180;
302
- }
303
- }
304
- _isTopmostStack() {
305
- if (typeof document === "undefined") return true;
306
- const root = this.__vmzDomRoot;
307
- const mine = root && typeof root.querySelector === "function" ? root.querySelector("[data-vmz-overlay][data-vmz-overlay-stack]") : null;
308
- if (!mine) return true;
309
- const myStack = Number(mine.getAttribute("data-vmz-overlay-stack") || this.stackLevel || "0");
310
- const open = [...document.querySelectorAll("[data-vmz-overlay][data-vmz-overlay-stack]")];
311
- let max = -Infinity;
312
- for (const el of open) {
313
- const n = Number(el.getAttribute("data-vmz-overlay-stack") || "0");
314
- if (Number.isFinite(n) && n > max) max = n;
315
- }
316
- if (!Number.isFinite(max)) return true;
317
- if ((Number.isFinite(myStack) ? myStack : 0) < max) return false;
318
- if ((Number.isFinite(myStack) ? myStack : 0) > max) return true;
319
- const same = open.filter((el) => Number(el.getAttribute("data-vmz-overlay-stack") || "0") === max);
320
- return same[same.length - 1] === mine;
321
- }
322
- onPanelKeyDown(ev) {
323
- if (!ev || ev.key !== "Tab") return;
324
- const panel = this._panel;
325
- if (!panel) return;
326
- const nodes = panel.querySelectorAll("a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])");
327
- const list = [];
328
- for (const n of nodes) {
329
- if (n instanceof HTMLElement && n.offsetParent !== null) list.push(n);
330
- }
331
- if (panel.tabIndex >= 0) list.unshift(panel);
332
- if (!list.length) {
333
- ev.preventDefault();
334
- panel.focus();
335
- return;
336
- }
337
- const first = list[0];
338
- const last = list[list.length - 1];
339
- const active = document.activeElement;
340
- if (ev.shiftKey) {
341
- if (active === first || active === panel) {
342
- ev.preventDefault();
343
- last.focus();
344
- }
345
- } else if (active === last) {
346
- ev.preventDefault();
347
- first.focus();
348
- }
349
- }
350
- _syncFromDom() {
351
- const root = this.__vmzDomRoot;
352
- const panel = root && typeof root.querySelector === "function" ? root.querySelector("[data-vmz-focus=\"enter\"]") : null;
353
- if (panel && !this._panel) {
354
- this._enterFocus(panel);
355
- } else if (!panel && this._panel) {
356
- this._exitFocus();
357
- }
358
- }
359
- _enterFocus(panel) {
360
- if (typeof document === "undefined") return;
361
- const active = document.activeElement;
362
- if (!this._restoreFocus) {
363
- const root = this.__vmzDomRoot;
364
- const outside = active instanceof HTMLElement && active !== document.body && !(root && typeof root.contains === "function" && root.contains(active));
365
- if (outside) this._restoreFocus = active;
366
- }
367
- this._panel = panel instanceof HTMLElement ? panel : null;
368
- const overlay = this._panel && typeof this._panel.closest === "function" ? this._panel.closest("[data-vmz-overlay=\"dialog\"]") : null;
369
- // Owner remount / re-enter cancels any in-flight exit from a prior generation.
370
- if (this._exiting) {
371
- this._cancelExit("reenter");
372
- } else {
373
- this._motionGen += 1;
374
- }
375
- if (overlay) {
376
- overlay.setAttribute("data-vmz-motion-gen", String(this._motionGen));
377
- }
378
- if (this._adoptNextEnter && overlay) {
379
- overlay.setAttribute("data-vmz-motion-adopt", "true");
380
- this._panel.setAttribute("data-vmz-motion", "overlay-stable");
381
- this._adoptNextEnter = false;
382
- } else if (overlay) {
383
- overlay.removeAttribute("data-vmz-motion-adopt");
384
- overlay.removeAttribute("data-vmz-motion-phase");
385
- overlay.removeAttribute("data-vmz-motion-cancelled");
386
- this._panel.setAttribute("data-vmz-motion", "overlay-enter");
387
- }
388
- if (this._panel && typeof this._panel.focus === "function") {
389
- this._panel.focus();
390
- }
391
- if (!this._docKeyHandler) {
392
- this._docKeyHandler = (ev) => {
393
- if (ev.key !== "Escape") return;
394
- if (ev.defaultPrevented) return;
395
- if (!this._isTopmostStack()) return;
396
- ev.preventDefault();
397
- ev.stopPropagation();
398
- this.dismiss();
399
- };
400
- document.addEventListener("keydown", this._docKeyHandler);
401
- }
402
- }
403
- _exitFocus() {
404
- if (this._exitTimer) {
405
- clearTimeout(this._exitTimer);
406
- this._exitTimer = null;
407
- }
408
- this._exiting = false;
409
- this._teardownDocKey();
410
- this._panel = null;
411
- const restore = this._restoreFocus;
412
- this._restoreFocus = null;
413
- const focusTarget = restore && typeof restore.focus === "function" && document.contains(restore) ? restore : document.querySelector("button.vmz-ui-btn");
414
- if (focusTarget && typeof focusTarget.focus === "function") {
415
- focusTarget.focus();
416
- }
417
- }
418
- _teardownDocKey() {
419
- if (typeof document !== "undefined" && this._docKeyHandler) {
420
- document.removeEventListener("keydown", this._docKeyHandler);
421
- }
422
- this._docKeyHandler = null;
423
- }
183
+ public open: boolean = false;
184
+ public title: string = "Dialog";
185
+ public titleId: string = "vmz-dialog-title";
186
+ public stackLevel: string = "0";
187
+ public onClose: (() => void) | null = null;
188
+ _restoreFocus = null;
189
+ _panel = null;
190
+ _docKeyHandler = null;
191
+ _pointerCapture = null;
192
+ _observer = null;
193
+ _adoptNextEnter = false;
194
+ _exiting = false;
195
+ _exitTimer = null;
196
+ /** Motion generation — exit timer and cancel edges key off this owner counter. */
197
+ _motionGen = 0;
198
+ async onMount() {
199
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
200
+ const root = this.__vmzDomRoot;
201
+ if (!root || typeof root.querySelector !== "function") return;
202
+ if (root.querySelector('[data-vmz-overlay="dialog"]')) this._adoptNextEnter = true;
203
+ this._observer = new MutationObserver(() => this._syncFromDom());
204
+ this._observer.observe(root, {
205
+ childList: true,
206
+ subtree: true,
207
+ });
208
+ this._ensurePointerCapture();
209
+ this._syncFromDom();
210
+ }
211
+ onDestroy() {
212
+ if (this._exitTimer) {
213
+ clearTimeout(this._exitTimer);
214
+ this._exitTimer = null;
215
+ }
216
+ this._exiting = false;
217
+ if (this._observer) {
218
+ this._observer.disconnect();
219
+ this._observer = null;
220
+ }
221
+ this._exitFocus();
222
+ }
223
+ dismiss() {
224
+ if (!this._isTopmostStack()) return;
225
+ // Reverse / interrupt: second dismiss during exit cancels close (stay open).
226
+ if (this._exiting) {
227
+ this._cancelExit("reverse");
228
+ return;
229
+ }
230
+ const panel = this._panel;
231
+ const overlay =
232
+ panel && typeof panel.closest === "function"
233
+ ? panel.closest('[data-vmz-overlay="dialog"]')
234
+ : null;
235
+ const reduce =
236
+ typeof window !== "undefined" &&
237
+ typeof window.matchMedia === "function" &&
238
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
239
+ if (panel && overlay && !reduce) {
240
+ this._exiting = true;
241
+ const exitGen = this._motionGen;
242
+ overlay.setAttribute("data-vmz-motion-phase", "exit");
243
+ overlay.removeAttribute("data-vmz-motion-cancelled");
244
+ panel.setAttribute("data-vmz-motion", "overlay-exit");
245
+ const ms = this._overlayDurationMs(panel);
246
+ this._exitTimer = setTimeout(() => {
247
+ this._exitTimer = null;
248
+ // Stale exit after interrupt/re-enter must not call onClose.
249
+ if (!this._exiting || exitGen !== this._motionGen) return;
250
+ this._exiting = false;
251
+ this._finishDismiss();
252
+ }, ms);
253
+ return;
254
+ }
255
+ this._finishDismiss();
256
+ }
257
+ _finishDismiss() {
258
+ const restore = this._restoreFocus;
259
+ this._restoreFocus = null;
260
+ const opener =
261
+ restore && typeof restore.focus === "function" && document.contains(restore)
262
+ ? restore
263
+ : this._openerElement();
264
+ this._exiting = false;
265
+ if (this._exitTimer) {
266
+ clearTimeout(this._exitTimer);
267
+ this._exitTimer = null;
268
+ }
269
+ this._teardownDocKey();
270
+ this._panel = null;
271
+ this.emit("close");
272
+ if (typeof this.onClose === "function") this.onClose();
273
+ this._refocus(opener, true);
274
+ }
275
+ _refocus(el, sync = false) {
276
+ if (!(el instanceof HTMLElement) || typeof el.focus !== "function") return;
277
+ const focus = () => {
278
+ if (document.contains(el)) el.focus();
279
+ };
280
+ if (sync) {
281
+ focus();
282
+ return;
283
+ }
284
+ if (typeof requestAnimationFrame === "function") {
285
+ requestAnimationFrame(() => requestAnimationFrame(focus));
286
+ return;
287
+ }
288
+ focus();
289
+ }
290
+ _cancelExit(reason) {
291
+ if (this._exitTimer) {
292
+ clearTimeout(this._exitTimer);
293
+ this._exitTimer = null;
294
+ }
295
+ this._exiting = false;
296
+ this._motionGen += 1;
297
+ const panel = this._panel;
298
+ const overlay =
299
+ panel && typeof panel.closest === "function"
300
+ ? panel.closest('[data-vmz-overlay="dialog"]')
301
+ : null;
302
+ if (overlay) {
303
+ overlay.removeAttribute("data-vmz-motion-phase");
304
+ overlay.setAttribute("data-vmz-motion-cancelled", reason || "true");
305
+ overlay.setAttribute("data-vmz-motion-gen", String(this._motionGen));
306
+ }
307
+ if (panel) {
308
+ panel.setAttribute("data-vmz-motion", "overlay-enter");
309
+ }
310
+ }
311
+ _overlayDurationMs(panel) {
312
+ try {
313
+ const raw =
314
+ (
315
+ getComputedStyle(panel).getPropertyValue("--vmz-motion-overlay-duration") || ""
316
+ ).trim() || "180ms";
317
+ if (raw.endsWith("ms")) return Math.max(0, Number.parseFloat(raw) || 180);
318
+ if (raw.endsWith("s")) return Math.max(0, (Number.parseFloat(raw) || 0.18) * 1e3);
319
+ return 180;
320
+ } catch {
321
+ return 180;
322
+ }
323
+ }
324
+ _isTopmostStack() {
325
+ if (typeof document === "undefined") return true;
326
+ const root = this.__vmzDomRoot;
327
+ const mine =
328
+ root && typeof root.querySelector === "function"
329
+ ? root.querySelector("[data-vmz-overlay][data-vmz-overlay-stack]")
330
+ : null;
331
+ if (!mine) return true;
332
+ const myStack = Number(
333
+ mine.getAttribute("data-vmz-overlay-stack") || this.stackLevel || "0",
334
+ );
335
+ const open = [...document.querySelectorAll("[data-vmz-overlay][data-vmz-overlay-stack]")];
336
+ let max = -Infinity;
337
+ for (const el of open) {
338
+ const n = Number(el.getAttribute("data-vmz-overlay-stack") || "0");
339
+ if (Number.isFinite(n) && n > max) max = n;
340
+ }
341
+ if (!Number.isFinite(max)) return true;
342
+ if ((Number.isFinite(myStack) ? myStack : 0) < max) return false;
343
+ if ((Number.isFinite(myStack) ? myStack : 0) > max) return true;
344
+ const same = open.filter(
345
+ (el) => Number(el.getAttribute("data-vmz-overlay-stack") || "0") === max,
346
+ );
347
+ return same[same.length - 1] === mine;
348
+ }
349
+ onPanelKeyDown(ev) {
350
+ if (!ev || ev.key !== "Tab") return;
351
+ const panel = this._panel;
352
+ if (!panel) return;
353
+ const nodes = panel.querySelectorAll(
354
+ 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])',
355
+ );
356
+ const list = [];
357
+ for (const n of nodes) {
358
+ if (n instanceof HTMLElement && n.offsetParent !== null) list.push(n);
359
+ }
360
+ if (panel.tabIndex >= 0) list.unshift(panel);
361
+ if (!list.length) {
362
+ ev.preventDefault();
363
+ panel.focus();
364
+ return;
365
+ }
366
+ const first = list[0];
367
+ const last = list[list.length - 1];
368
+ const active = document.activeElement;
369
+ if (ev.shiftKey) {
370
+ if (active === first || active === panel) {
371
+ ev.preventDefault();
372
+ last.focus();
373
+ }
374
+ } else if (active === last) {
375
+ ev.preventDefault();
376
+ first.focus();
377
+ }
378
+ }
379
+ _syncFromDom() {
380
+ const root = this.__vmzDomRoot;
381
+ const panel =
382
+ root && typeof root.querySelector === "function"
383
+ ? root.querySelector('[data-vmz-focus="enter"]')
384
+ : null;
385
+ if (panel && !this._panel) {
386
+ this._enterFocus(panel);
387
+ } else if (!panel && this._panel) {
388
+ this._exitFocus();
389
+ }
390
+ }
391
+ _enterFocus(panel) {
392
+ if (typeof document === "undefined") return;
393
+ const active = document.activeElement;
394
+ if (!this._restoreFocus) {
395
+ if (
396
+ active instanceof HTMLElement &&
397
+ active !== document.body &&
398
+ active !== panel &&
399
+ !(panel instanceof HTMLElement && panel.contains(active))
400
+ ) {
401
+ this._restoreFocus = active;
402
+ }
403
+ }
404
+ this._panel = panel instanceof HTMLElement ? panel : null;
405
+ const overlay =
406
+ this._panel && typeof this._panel.closest === "function"
407
+ ? this._panel.closest('[data-vmz-overlay="dialog"]')
408
+ : null;
409
+ // Owner remount / re-enter cancels any in-flight exit from a prior generation.
410
+ if (this._exiting) {
411
+ this._cancelExit("reenter");
412
+ } else {
413
+ this._motionGen += 1;
414
+ }
415
+ if (overlay) {
416
+ overlay.setAttribute("data-vmz-motion-gen", String(this._motionGen));
417
+ }
418
+ if (this._adoptNextEnter && overlay) {
419
+ overlay.setAttribute("data-vmz-motion-adopt", "true");
420
+ this._panel.setAttribute("data-vmz-motion", "overlay-stable");
421
+ this._adoptNextEnter = false;
422
+ } else if (overlay) {
423
+ overlay.removeAttribute("data-vmz-motion-adopt");
424
+ overlay.removeAttribute("data-vmz-motion-phase");
425
+ overlay.removeAttribute("data-vmz-motion-cancelled");
426
+ this._panel.setAttribute("data-vmz-motion", "overlay-enter");
427
+ }
428
+ if (this._panel && typeof this._panel.focus === "function") {
429
+ this._panel.focus();
430
+ }
431
+ if (!this._docKeyHandler) {
432
+ this._docKeyHandler = (ev) => {
433
+ if (ev.key !== "Escape") return;
434
+ if (ev.defaultPrevented) return;
435
+ if (!this._isTopmostStack()) return;
436
+ ev.preventDefault();
437
+ ev.stopPropagation();
438
+ this.dismiss();
439
+ };
440
+ document.addEventListener("keydown", this._docKeyHandler);
441
+ }
442
+ }
443
+ _exitFocus() {
444
+ if (this._exitTimer) {
445
+ clearTimeout(this._exitTimer);
446
+ this._exitTimer = null;
447
+ }
448
+ this._exiting = false;
449
+ this._teardownDocKey();
450
+ this._panel = null;
451
+ const restore = this._restoreFocus;
452
+ this._restoreFocus = null;
453
+ const focusTarget =
454
+ restore && typeof restore.focus === "function" && document.contains(restore)
455
+ ? restore
456
+ : this._openerElement();
457
+ if (focusTarget && typeof focusTarget.focus === "function") {
458
+ focusTarget.focus();
459
+ }
460
+ }
461
+ _openerElement() {
462
+ if (typeof document === "undefined") return null;
463
+ const byTitle = document.querySelector(`[aria-controls="${this.titleId}"]`);
464
+ if (byTitle instanceof HTMLElement) return byTitle;
465
+ const byTrigger = document.querySelector(
466
+ `[data-vmz-dialog-trigger-for="${this.titleId}"] button, [data-vmz-dialog-trigger-for="${this.titleId}"]`,
467
+ );
468
+ if (byTrigger instanceof HTMLElement) return byTrigger;
469
+ const root = this.__vmzDomRoot;
470
+ if (!(root instanceof HTMLElement)) return null;
471
+ let host = root.parentElement;
472
+ while (host) {
473
+ const buttons = [...host.querySelectorAll("button.vmz-ui-btn")].filter(
474
+ (btn) => btn instanceof HTMLElement,
475
+ );
476
+ if (buttons.length === 1) return buttons[0];
477
+ for (const btn of buttons) {
478
+ if (root.compareDocumentPosition(btn) & Node.DOCUMENT_POSITION_PRECEDING) {
479
+ return btn;
480
+ }
481
+ }
482
+ host = host.parentElement;
483
+ }
484
+ return null;
485
+ }
486
+ _ensurePointerCapture() {
487
+ if (typeof document === "undefined" || this._pointerCapture) return;
488
+ this._pointerCapture = (ev) => {
489
+ const t =
490
+ ev.target instanceof HTMLElement
491
+ ? ev.target.closest("button, a[href], input, select, textarea, [tabindex]")
492
+ : null;
493
+ if (!(t instanceof HTMLElement)) return;
494
+ if (t.closest('[data-vmz-overlay="dialog"]')) return;
495
+ this._restoreFocus = t;
496
+ };
497
+ document.addEventListener("mousedown", this._pointerCapture, true);
498
+ }
499
+ _teardownDocKey() {
500
+ if (typeof document !== "undefined" && this._docKeyHandler) {
501
+ document.removeEventListener("keydown", this._docKeyHandler);
502
+ }
503
+ this._docKeyHandler = null;
504
+ }
424
505
  }
425
506
  </script>