@vmz/ui 0.0.3 → 0.1.0

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 (53) hide show
  1. package/README.md +28 -1
  2. package/conformance/fixtures/button-control-motion.json +22 -0
  3. package/conformance/fixtures/dialog-overlay-motion.json +27 -0
  4. package/conformance/fixtures/drawer-overlay-motion.json +27 -0
  5. package/conformance/pack.v0.json +7 -0
  6. package/contracts/token-requirements.v0.json +643 -2
  7. package/package.json +50 -2
  8. package/presets/web-surface.v0.json +34 -0
  9. package/src/components/Accordion.vmz +112 -0
  10. package/src/components/Alert.vmz +84 -0
  11. package/src/components/AppShell.vmz +97 -0
  12. package/src/components/Autocomplete.vmz +176 -0
  13. package/src/components/Badge.vmz +63 -0
  14. package/src/components/Breadcrumb.vmz +66 -0
  15. package/src/components/BulkActions.vmz +52 -0
  16. package/src/components/Button.vmz +78 -5
  17. package/src/components/Callout.vmz +80 -0
  18. package/src/components/Card.vmz +80 -0
  19. package/src/components/Checkbox.vmz +69 -0
  20. package/src/components/CodeBlock.vmz +67 -0
  21. package/src/components/ConsoleShell.vmz +127 -0
  22. package/src/components/DataTable.vmz +220 -0
  23. package/src/components/DatePicker.vmz +591 -0
  24. package/src/components/Dialog.vmz +442 -0
  25. package/src/components/Drawer.vmz +435 -0
  26. package/src/components/Empty.vmz +52 -0
  27. package/src/components/Field.vmz +88 -0
  28. package/src/components/FilterBar.vmz +27 -0
  29. package/src/components/Form.vmz +72 -0
  30. package/src/components/FormItem.vmz +78 -0
  31. package/src/components/Link.vmz +48 -0
  32. package/src/components/List.vmz +116 -0
  33. package/src/components/Menu.vmz +270 -0
  34. package/src/components/Notification.vmz +83 -0
  35. package/src/components/Pagination.vmz +81 -0
  36. package/src/components/Popover.vmz +253 -0
  37. package/src/components/Prose.vmz +83 -0
  38. package/src/components/QueryForm.vmz +39 -0
  39. package/src/components/RadioGroup.vmz +135 -0
  40. package/src/components/Result.vmz +84 -0
  41. package/src/components/Select.vmz +355 -0
  42. package/src/components/Skeleton.vmz +80 -0
  43. package/src/components/Spinner.vmz +45 -0
  44. package/src/components/Steps.vmz +107 -0
  45. package/src/components/Switch.vmz +109 -0
  46. package/src/components/Table.vmz +134 -0
  47. package/src/components/Tabs.vmz +132 -0
  48. package/src/components/TextArea.vmz +91 -0
  49. package/src/components/Timeline.vmz +67 -0
  50. package/src/components/Toc.vmz +77 -0
  51. package/src/components/Tooltip.vmz +68 -0
  52. package/src/components/Tree.vmz +215 -0
  53. package/src/components/Upload.vmz +1233 -0
@@ -0,0 +1,591 @@
1
+ <template>
2
+ <div
3
+ class={'vmz-ui-date-picker' + (open ? ' is-opened' : '')}
4
+ data-vmz-ui="date-picker"
5
+ data-invalid={invalidAttr}
6
+ >
7
+ <label if={label} class="vmz-ui-date-picker__label" for={controlId}>{label}</label>
8
+ <button
9
+ type="button"
10
+ id={controlId}
11
+ class="vmz-ui-date-picker__control"
12
+ disabled={disabled}
13
+ aria-haspopup="dialog"
14
+ aria-expanded={open ? 'true' : 'false'}
15
+ aria-controls={panelId}
16
+ aria-invalid={error ? 'true' : 'false'}
17
+ aria-describedby={describedBy}
18
+ onClick={toggle}
19
+ onKeyDown={onTriggerKeyDown}
20
+ >
21
+ <span class="vmz-ui-date-picker__value" data-vmz-date="value">{displayLabel}</span>
22
+ <span class="vmz-ui-date-picker__chev" aria-hidden="true"></span>
23
+ </button>
24
+ <div
25
+ if={open}
26
+ id={panelId}
27
+ class="vmz-ui-date-picker__panel"
28
+ role="dialog"
29
+ aria-modal="false"
30
+ aria-label={panelLabel}
31
+ tabindex="-1"
32
+ data-vmz-overlay="date-picker"
33
+ data-vmz-overlay-owner="date-picker"
34
+ data-vmz-focus="enter"
35
+ data-vmz-date="panel"
36
+ onKeyDown={onPanelKeyDown}
37
+ >
38
+ <div class="vmz-ui-date-picker__monthbar">
39
+ <button
40
+ type="button"
41
+ class="vmz-ui-date-picker__nav"
42
+ data-vmz-date="prev"
43
+ aria-label="Previous month"
44
+ onClick={prevMonth}
45
+ >‹</button>
46
+ <p class="vmz-ui-date-picker__month" data-vmz-date="month">{monthLabel}</p>
47
+ <button
48
+ type="button"
49
+ class="vmz-ui-date-picker__nav"
50
+ data-vmz-date="next"
51
+ aria-label="Next month"
52
+ onClick={nextMonth}
53
+ >›</button>
54
+ </div>
55
+ <div class="vmz-ui-date-picker__weekdays" aria-hidden="true">
56
+ <span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span><span>Su</span>
57
+ </div>
58
+ <div class="vmz-ui-date-picker__grid" role="grid" aria-labelledby={panelId}>
59
+ <button
60
+ each={days}
61
+ as="day"
62
+ key={day.key}
63
+ type="button"
64
+ role="gridcell"
65
+ class={
66
+ 'vmz-ui-date-picker__day' +
67
+ (day.inMonth ? '' : ' is-outside') +
68
+ (day.selected ? ' is-on' : '') +
69
+ (day.disabled ? ' is-disabled' : '')
70
+ }
71
+ data-vmz-date-iso={day.iso}
72
+ aria-selected={day.selected ? 'true' : 'false'}
73
+ aria-disabled={day.disabled ? 'true' : 'false'}
74
+ disabled={day.disabled}
75
+ onClick={onDayClick}
76
+ >{day.label}</button>
77
+ </div>
78
+ </div>
79
+ <p if={description} id={descriptionId} class="vmz-ui-date-picker__description">{description}</p>
80
+ <p if={error} id={errorId} class="vmz-ui-date-picker__error" role="alert">{error}</p>
81
+ </div>
82
+ </template>
83
+
84
+ <style>
85
+ .vmz-ui-date-picker {
86
+ position: relative;
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 0.35rem;
90
+ max-width: 28rem;
91
+ }
92
+
93
+ .vmz-ui-date-picker__label {
94
+ font: inherit;
95
+ font-weight: 600;
96
+ }
97
+
98
+ .vmz-ui-date-picker__control {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ gap: 0.55rem;
103
+ width: 100%;
104
+ box-sizing: border-box;
105
+ font: inherit;
106
+ text-align: left;
107
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
108
+ border: 1px solid var(--vmz-action-primary-background);
109
+ border-radius: 2px;
110
+ background: var(--vmz-surface-paper, transparent);
111
+ color: inherit;
112
+ outline: none;
113
+ cursor: pointer;
114
+ }
115
+
116
+ .vmz-ui-date-picker[data-invalid='true'] .vmz-ui-date-picker__control {
117
+ border-color: var(--vmz-status-danger-accent);
118
+ }
119
+
120
+ .vmz-ui-date-picker__control:focus-visible,
121
+ .vmz-ui-date-picker.is-opened .vmz-ui-date-picker__control {
122
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
123
+ }
124
+
125
+ .vmz-ui-date-picker__control:disabled {
126
+ opacity: 0.55;
127
+ cursor: not-allowed;
128
+ }
129
+
130
+ .vmz-ui-date-picker__value {
131
+ flex: 1 1 auto;
132
+ min-width: 0;
133
+ overflow: hidden;
134
+ text-overflow: ellipsis;
135
+ white-space: nowrap;
136
+ }
137
+
138
+ .vmz-ui-date-picker__chev {
139
+ flex: 0 0 auto;
140
+ width: 0.45rem;
141
+ height: 0.45rem;
142
+ border-right: 1.5px solid currentColor;
143
+ border-bottom: 1.5px solid currentColor;
144
+ transform: rotate(45deg) translate(-1px, -2px);
145
+ opacity: 0.7;
146
+ }
147
+
148
+ .vmz-ui-date-picker.is-opened .vmz-ui-date-picker__chev {
149
+ transform: rotate(225deg) translate(-1px, -1px);
150
+ }
151
+
152
+ .vmz-ui-date-picker__panel {
153
+ position: absolute;
154
+ z-index: 40;
155
+ top: calc(100% + 0.25rem);
156
+ left: 0;
157
+ right: 0;
158
+ box-sizing: border-box;
159
+ padding: 0.65rem 0.75rem 0.75rem;
160
+ border: 1px solid var(--vmz-action-primary-background);
161
+ border-radius: 2px;
162
+ background: var(--vmz-surface-paper, #fff);
163
+ color: inherit;
164
+ box-shadow: 0 10px 24px color-mix(in srgb, #000 16%, transparent);
165
+ outline: none;
166
+ }
167
+
168
+ .vmz-ui-date-picker__monthbar {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: space-between;
172
+ gap: 0.5rem;
173
+ margin-bottom: 0.55rem;
174
+ }
175
+
176
+ .vmz-ui-date-picker__month {
177
+ margin: 0;
178
+ font: inherit;
179
+ font-weight: 600;
180
+ text-align: center;
181
+ flex: 1 1 auto;
182
+ }
183
+
184
+ .vmz-ui-date-picker__nav {
185
+ flex: 0 0 auto;
186
+ width: 2rem;
187
+ height: 2rem;
188
+ font: inherit;
189
+ font-size: 1.15rem;
190
+ line-height: 1;
191
+ border: 1px solid transparent;
192
+ border-radius: 2px;
193
+ background: transparent;
194
+ color: inherit;
195
+ cursor: pointer;
196
+ outline: none;
197
+ }
198
+
199
+ .vmz-ui-date-picker__nav:hover,
200
+ .vmz-ui-date-picker__nav:focus-visible {
201
+ border-color: var(--vmz-action-primary-background);
202
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
203
+ }
204
+
205
+ .vmz-ui-date-picker__weekdays {
206
+ display: grid;
207
+ grid-template-columns: repeat(7, 1fr);
208
+ gap: 0.15rem;
209
+ margin-bottom: 0.25rem;
210
+ font-size: 0.75rem;
211
+ opacity: 0.7;
212
+ text-align: center;
213
+ }
214
+
215
+ .vmz-ui-date-picker__grid {
216
+ display: grid;
217
+ grid-template-columns: repeat(7, 1fr);
218
+ gap: 0.15rem;
219
+ }
220
+
221
+ .vmz-ui-date-picker__day {
222
+ aspect-ratio: 1;
223
+ min-height: 2rem;
224
+ font: inherit;
225
+ font-size: 0.9rem;
226
+ border: 0;
227
+ border-radius: 2px;
228
+ background: transparent;
229
+ color: inherit;
230
+ cursor: pointer;
231
+ outline: none;
232
+ }
233
+
234
+ .vmz-ui-date-picker__day:hover,
235
+ .vmz-ui-date-picker__day:focus-visible {
236
+ background: color-mix(in srgb, var(--vmz-action-primary-background) 18%, transparent);
237
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
238
+ }
239
+
240
+ .vmz-ui-date-picker__day.is-outside {
241
+ opacity: 0.4;
242
+ }
243
+
244
+ .vmz-ui-date-picker__day.is-on {
245
+ background: color-mix(in srgb, var(--vmz-action-primary-background) 28%, transparent);
246
+ font-weight: 600;
247
+ }
248
+
249
+ .vmz-ui-date-picker__day.is-disabled,
250
+ .vmz-ui-date-picker__day:disabled {
251
+ opacity: 0.35;
252
+ cursor: not-allowed;
253
+ box-shadow: none;
254
+ }
255
+
256
+ .vmz-ui-date-picker__description {
257
+ margin: 0;
258
+ opacity: 0.75;
259
+ font-size: 0.9em;
260
+ }
261
+
262
+ .vmz-ui-date-picker__error {
263
+ margin: 0;
264
+ color: var(--vmz-status-danger-foreground);
265
+ font-size: 0.9em;
266
+ }
267
+ </style>
268
+
269
+ <script client>
270
+ /**
271
+ * VMZ UI — DatePicker.
272
+ * Owned calendar overlay (trigger + dialog grid). Never native <input type="date"> popup:
273
+ * OS date UI cannot carry Style Theme / density / overlay ownership.
274
+ * Parent owns ISO value (YYYY-MM-DD) via onChange Event-like { target: { value } }.
275
+ * Not @vmz/ui-scheduler — month grid only; no recurrence / resource layout.
276
+ */
277
+ export default class DatePicker {
278
+ public label: string = '';
279
+ public description: string = '';
280
+ public error: string = '';
281
+ public value: string = '';
282
+ public min: string = '';
283
+ public max: string = '';
284
+ public disabled: boolean = false;
285
+ public invalidAttr: string = 'false';
286
+ public controlId: string = 'vmz-date';
287
+ public descriptionId: string = 'vmz-date-desc';
288
+ public errorId: string = 'vmz-date-err';
289
+ public describedBy: string = '';
290
+ public onInput: ((e: { target: { value: string } }) => void) | null = null;
291
+ public onChange: ((e: { target: { value: string } }) => void) | null = null;
292
+
293
+ open = false;
294
+ displayLabel = '—';
295
+ panelId = 'vmz-date-panel';
296
+ panelLabel = 'Choose date';
297
+ monthLabel = '';
298
+ viewYear = 0;
299
+ viewMonth = 0;
300
+ days: Array<{
301
+ key: string;
302
+ iso: string;
303
+ label: string;
304
+ inMonth: boolean;
305
+ selected: boolean;
306
+ disabled: boolean;
307
+ }> = [];
308
+
309
+ _docKeyHandler = null;
310
+ _docPointerHandler = null;
311
+ _observer = null;
312
+
313
+ onMount() {
314
+ this.panelId = `${this.controlId}-panel`;
315
+ this.syncFromValue(true);
316
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return;
317
+ const root = this.__vmzDomRoot;
318
+ if (!root || typeof root.querySelector !== 'function') return;
319
+ this._observer = new MutationObserver(() => this._syncOverlay());
320
+ this._observer.observe(root, { childList: true, subtree: true });
321
+ }
322
+
323
+ onDestroy() {
324
+ if (this._observer) {
325
+ this._observer.disconnect();
326
+ this._observer = null;
327
+ }
328
+ this._teardownDoc();
329
+ }
330
+
331
+ syncFromValue(resetView: boolean) {
332
+ const parsed = this._parseIso(this.value);
333
+ this.displayLabel = parsed ? this.value : '—';
334
+ if (resetView) {
335
+ const base = parsed || this._todayParts();
336
+ this.viewYear = base.y;
337
+ this.viewMonth = base.m;
338
+ }
339
+ this._rebuildDays();
340
+ }
341
+
342
+ toggle() {
343
+ if (this.disabled) return;
344
+ if (this.open) this.close();
345
+ else this.openPanel();
346
+ }
347
+
348
+ openPanel() {
349
+ this.syncFromValue(true);
350
+ this.open = true;
351
+ }
352
+
353
+ close() {
354
+ this.open = false;
355
+ }
356
+
357
+ prevMonth() {
358
+ if (this.viewMonth <= 1) {
359
+ this.viewYear -= 1;
360
+ this.viewMonth = 12;
361
+ } else {
362
+ this.viewMonth -= 1;
363
+ }
364
+ this._rebuildDays();
365
+ }
366
+
367
+ nextMonth() {
368
+ if (this.viewMonth >= 12) {
369
+ this.viewYear += 1;
370
+ this.viewMonth = 1;
371
+ } else {
372
+ this.viewMonth += 1;
373
+ }
374
+ this._rebuildDays();
375
+ }
376
+
377
+ choose(iso: string) {
378
+ if (!iso || this._isOutOfRange(iso)) return;
379
+ this.close();
380
+ if (iso === this.value) return;
381
+ const ev = { target: { value: iso } };
382
+ if (typeof this.onChange === 'function') this.onChange(ev);
383
+ if (typeof this.onInput === 'function') this.onInput(ev);
384
+ }
385
+
386
+ onDayClick(ev: Event) {
387
+ const t = ev?.target as HTMLElement | null;
388
+ const el =
389
+ t && typeof t.closest === 'function'
390
+ ? (t.closest('[data-vmz-date-iso]') as HTMLElement | null)
391
+ : ((ev?.currentTarget as HTMLElement | null) ?? null);
392
+ const iso = el && typeof el.getAttribute === 'function' ? el.getAttribute('data-vmz-date-iso') : null;
393
+ if (iso) this.choose(iso);
394
+ }
395
+
396
+ onTriggerKeyDown(ev: KeyboardEvent) {
397
+ if (!ev || this.disabled) return;
398
+ if ((ev.key === 'ArrowDown' || ev.key === 'Enter' || ev.key === ' ') && !this.open) {
399
+ ev.preventDefault();
400
+ this.openPanel();
401
+ } else if (ev.key === 'Escape' && this.open) {
402
+ ev.preventDefault();
403
+ this.close();
404
+ }
405
+ }
406
+
407
+ onPanelKeyDown(ev: KeyboardEvent) {
408
+ if (!ev) return;
409
+ if (ev.key === 'Escape') {
410
+ ev.preventDefault();
411
+ this.close();
412
+ return;
413
+ }
414
+ const root = this.__vmzDomRoot;
415
+ const panel =
416
+ root && typeof root.querySelector === 'function'
417
+ ? root.querySelector('[data-vmz-overlay="date-picker"]')
418
+ : null;
419
+ if (!panel) return;
420
+ const nodes = [...panel.querySelectorAll('[data-vmz-date-iso]')].filter(
421
+ (n) => n instanceof HTMLElement && !(n as HTMLButtonElement).disabled,
422
+ );
423
+ if (!nodes.length) return;
424
+ const idx = nodes.indexOf(document.activeElement as Element);
425
+ if (ev.key === 'ArrowRight') {
426
+ ev.preventDefault();
427
+ (nodes[(idx + 1 + nodes.length) % nodes.length] as HTMLElement).focus();
428
+ } else if (ev.key === 'ArrowLeft') {
429
+ ev.preventDefault();
430
+ (nodes[(idx - 1 + nodes.length) % nodes.length] as HTMLElement).focus();
431
+ } else if (ev.key === 'ArrowDown') {
432
+ ev.preventDefault();
433
+ (nodes[Math.min(idx + 7, nodes.length - 1)] as HTMLElement).focus();
434
+ } else if (ev.key === 'ArrowUp') {
435
+ ev.preventDefault();
436
+ (nodes[Math.max(idx - 7, 0)] as HTMLElement).focus();
437
+ } else if (ev.key === 'Home') {
438
+ ev.preventDefault();
439
+ (nodes[0] as HTMLElement).focus();
440
+ } else if (ev.key === 'End') {
441
+ ev.preventDefault();
442
+ (nodes[nodes.length - 1] as HTMLElement).focus();
443
+ } else if (ev.key === 'Enter' || ev.key === ' ') {
444
+ const active = document.activeElement as HTMLElement | null;
445
+ const iso = active?.getAttribute?.('data-vmz-date-iso');
446
+ if (iso) {
447
+ ev.preventDefault();
448
+ this.choose(iso);
449
+ }
450
+ }
451
+ }
452
+
453
+ _rebuildDays() {
454
+ const y = this.viewYear || this._todayParts().y;
455
+ const m = this.viewMonth || this._todayParts().m;
456
+ this.viewYear = y;
457
+ this.viewMonth = m;
458
+ this.monthLabel = `${this._monthName(m)} ${y}`;
459
+ this.panelLabel = `Choose date — ${this.monthLabel}`;
460
+
461
+ const firstDow = (new Date(y, m - 1, 1).getDay() + 6) % 7; // Mon=0
462
+ const dim = new Date(y, m, 0).getDate();
463
+ const prevDim = new Date(y, m - 1, 0).getDate();
464
+ const cells: typeof this.days = [];
465
+
466
+ for (let i = 0; i < 42; i++) {
467
+ let cy = y;
468
+ let cm = m;
469
+ let cd = 0;
470
+ let inMonth = true;
471
+ if (i < firstDow) {
472
+ inMonth = false;
473
+ cd = prevDim - firstDow + i + 1;
474
+ cm = m === 1 ? 12 : m - 1;
475
+ cy = m === 1 ? y - 1 : y;
476
+ } else if (i >= firstDow + dim) {
477
+ inMonth = false;
478
+ cd = i - firstDow - dim + 1;
479
+ cm = m === 12 ? 1 : m + 1;
480
+ cy = m === 12 ? y + 1 : y;
481
+ } else {
482
+ cd = i - firstDow + 1;
483
+ }
484
+ const iso = this._toIso(cy, cm, cd);
485
+ cells.push({
486
+ key: iso,
487
+ iso,
488
+ label: String(cd),
489
+ inMonth,
490
+ selected: iso === this.value,
491
+ disabled: this._isOutOfRange(iso),
492
+ });
493
+ }
494
+ this.days = cells;
495
+ this.displayLabel = this._parseIso(this.value) ? this.value : '—';
496
+ }
497
+
498
+ _parseIso(iso: string) {
499
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso || ''));
500
+ if (!m) return null;
501
+ const y = Number(m[1]);
502
+ const mo = Number(m[2]);
503
+ const d = Number(m[3]);
504
+ if (mo < 1 || mo > 12 || d < 1 || d > 31) return null;
505
+ return { y, m: mo, d };
506
+ }
507
+
508
+ _toIso(y: number, m: number, d: number) {
509
+ return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
510
+ }
511
+
512
+ _todayParts() {
513
+ const now = new Date();
514
+ return { y: now.getFullYear(), m: now.getMonth() + 1, d: now.getDate() };
515
+ }
516
+
517
+ _monthName(m: number) {
518
+ const names = [
519
+ 'January',
520
+ 'February',
521
+ 'March',
522
+ 'April',
523
+ 'May',
524
+ 'June',
525
+ 'July',
526
+ 'August',
527
+ 'September',
528
+ 'October',
529
+ 'November',
530
+ 'December',
531
+ ];
532
+ return names[m - 1] || String(m);
533
+ }
534
+
535
+ _isOutOfRange(iso: string) {
536
+ if (this.min && iso < this.min) return true;
537
+ if (this.max && iso > this.max) return true;
538
+ return false;
539
+ }
540
+
541
+ _syncOverlay() {
542
+ const root = this.__vmzDomRoot;
543
+ const panel =
544
+ root && typeof root.querySelector === 'function'
545
+ ? root.querySelector('[data-vmz-overlay="date-picker"][data-vmz-focus="enter"]')
546
+ : null;
547
+ if (panel && this.open) this._bindDoc(panel as HTMLElement);
548
+ else if (!panel) this._teardownDoc();
549
+ this.displayLabel = this._parseIso(this.value) ? this.value : '—';
550
+ }
551
+
552
+ _bindDoc(panel: HTMLElement) {
553
+ if (typeof document === 'undefined') return;
554
+ if (!this._docKeyHandler) {
555
+ this._docKeyHandler = (ev: KeyboardEvent) => {
556
+ if (ev.key === 'Escape') {
557
+ ev.preventDefault();
558
+ this.close();
559
+ }
560
+ };
561
+ document.addEventListener('keydown', this._docKeyHandler);
562
+ }
563
+ if (!this._docPointerHandler) {
564
+ const root = this.__vmzDomRoot;
565
+ this._docPointerHandler = (ev: Event) => {
566
+ const t = ev.target;
567
+ if (!(t instanceof Node)) return;
568
+ if (root && typeof root.contains === 'function' && root.contains(t)) return;
569
+ this.close();
570
+ };
571
+ document.addEventListener('pointerdown', this._docPointerHandler, true);
572
+ }
573
+ const selected = panel.querySelector('[data-vmz-date-iso][aria-selected="true"]:not([disabled])');
574
+ const first = panel.querySelector('[data-vmz-date-iso]:not([disabled])');
575
+ const focusTarget = (selected || first) as HTMLElement | null;
576
+ if (focusTarget) focusTarget.focus();
577
+ else panel.focus();
578
+ }
579
+
580
+ _teardownDoc() {
581
+ if (typeof document !== 'undefined') {
582
+ if (this._docKeyHandler) document.removeEventListener('keydown', this._docKeyHandler);
583
+ if (this._docPointerHandler) {
584
+ document.removeEventListener('pointerdown', this._docPointerHandler, true);
585
+ }
586
+ }
587
+ this._docKeyHandler = null;
588
+ this._docPointerHandler = null;
589
+ }
590
+ }
591
+ </script>