@vmz/ui 0.1.0 → 0.1.1

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