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