@vmz/ui 0.0.4 → 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 +15 -2
  2. package/contracts/token-requirements.v0.json +45 -10
  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 +91 -70
  14. package/src/components/Callout.vmz +61 -61
  15. package/src/components/Card.vmz +58 -52
  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 +650 -75
  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 -286
  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 +1192 -82
@@ -1,93 +1,668 @@
1
1
  <template>
2
- <div class="vmz-ui-date-picker" data-vmz-ui="date-picker" data-invalid={invalidAttr}>
3
- <label if={label} class="vmz-ui-date-picker__label" for={controlId}>{label}</label>
4
- <input
5
- id={controlId}
6
- class="vmz-ui-date-picker__control"
7
- type="date"
8
- value={value}
9
- min={min}
10
- max={max}
11
- disabled={disabled}
12
- aria-invalid={error ? 'true' : 'false'}
13
- aria-describedby={describedBy}
14
- onInput={onInput}
15
- onChange={onChange}
16
- />
17
- <p if={description} id={descriptionId} class="vmz-ui-date-picker__description">{description}</p>
18
- <p if={error} id={errorId} class="vmz-ui-date-picker__error" role="alert">{error}</p>
19
- </div>
2
+ <div
3
+ class={'vmz-ui-date-picker' + (open ? ' is-opened' : '')}
4
+ data-vmz-ui="date-picker"
5
+ data-mode={mode}
6
+ data-invalid={invalidAttr}
7
+ >
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>
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>
84
+ </div>
20
85
  </template>
21
86
 
22
87
  <style>
23
- .vmz-ui-date-picker {
24
- display: flex;
25
- flex-direction: column;
26
- gap: 0.35rem;
27
- max-width: 28rem;
28
- }
88
+ .vmz-ui-date-picker {
89
+ position: relative;
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: 0.35rem;
93
+ max-width: 28rem;
94
+ }
29
95
 
30
- .vmz-ui-date-picker__label {
31
- font: inherit;
32
- font-weight: 600;
33
- }
96
+ .vmz-ui-date-picker__label {
97
+ font: inherit;
98
+ font-weight: 600;
99
+ }
34
100
 
35
- .vmz-ui-date-picker__control {
36
- font: inherit;
37
- padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
38
- border: 1px solid var(--vmz-action-primary-background);
39
- border-radius: 2px;
40
- background: transparent;
41
- color: inherit;
42
- outline: none;
43
- }
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;
117
+ }
44
118
 
45
- .vmz-ui-date-picker[data-invalid='true'] .vmz-ui-date-picker__control {
46
- border-color: var(--vmz-status-danger-accent);
47
- }
119
+ .vmz-ui-date-picker[data-invalid='true'] .vmz-ui-date-picker__control {
120
+ border-color: var(--vmz-status-danger-accent);
121
+ }
48
122
 
49
- .vmz-ui-date-picker__control:focus-visible {
50
- box-shadow: 0 0 0 2px var(--vmz-focus-ring);
51
- }
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);
126
+ }
52
127
 
53
- .vmz-ui-date-picker__control:disabled {
54
- opacity: 0.55;
55
- cursor: not-allowed;
56
- }
128
+ .vmz-ui-date-picker__control:disabled {
129
+ opacity: 0.55;
130
+ cursor: not-allowed;
131
+ }
57
132
 
58
- .vmz-ui-date-picker__description {
59
- margin: 0;
60
- opacity: 0.75;
61
- font-size: 0.9em;
62
- }
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;
139
+ }
63
140
 
64
- .vmz-ui-date-picker__error {
65
- margin: 0;
66
- color: var(--vmz-status-danger-foreground);
67
- font-size: 0.9em;
68
- }
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;
149
+ }
150
+
151
+ .vmz-ui-date-picker.is-opened .vmz-ui-date-picker__chev {
152
+ transform: rotate(225deg) translate(-1px, -1px);
153
+ }
154
+
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;
169
+ }
170
+
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;
177
+ }
178
+
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;
185
+ }
186
+
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;
200
+ }
201
+
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);
206
+ }
207
+
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
+ }
217
+
218
+ .vmz-ui-date-picker__grid {
219
+ display: grid;
220
+ grid-template-columns: repeat(7, 1fr);
221
+ gap: 0.15rem;
222
+ }
223
+
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;
235
+ }
236
+
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);
241
+ }
242
+
243
+ .vmz-ui-date-picker__day.is-outside {
244
+ opacity: 0.4;
245
+ }
246
+
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;
250
+ }
251
+
252
+ .vmz-ui-date-picker__day.is-in-range {
253
+ background: color-mix(in srgb, var(--vmz-action-primary-background) 14%, transparent);
254
+ }
255
+
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;
259
+ }
260
+
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;
266
+ }
267
+
268
+ .vmz-ui-date-picker__description {
269
+ margin: 0;
270
+ opacity: 0.75;
271
+ font-size: 0.9em;
272
+ }
273
+
274
+ .vmz-ui-date-picker__error {
275
+ margin: 0;
276
+ color: var(--vmz-status-danger-foreground);
277
+ font-size: 0.9em;
278
+ }
69
279
  </style>
70
280
 
71
281
  <script client>
72
282
  /**
73
- * VMZ UI — DatePicker.
74
- * WebSurface thin gate: native date input. Parent owns ISO value (YYYY-MM-DD).
75
- * No calendar overlay runtime; richer picker stays out of @vmz/ui core until needed.
76
- */
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
+ */
77
289
  export default class DatePicker {
78
- public label: string = '';
79
- public description: string = '';
80
- public error: string = '';
81
- public value: string = '';
82
- public min: string = '';
83
- public max: string = '';
84
- public disabled: boolean = false;
85
- public invalidAttr: string = 'false';
86
- public controlId: string = 'vmz-date';
87
- public descriptionId: string = 'vmz-date-desc';
88
- public errorId: string = 'vmz-date-err';
89
- public describedBy: string = '';
90
- public onInput: ((e: Event) => void) | null = null;
91
- public onChange: ((e: Event) => void) | null = null;
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
+ }
92
667
  }
93
668
  </script>