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