ajo-ui 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 (103) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +126 -0
  3. package/dist/accordion.js +130 -0
  4. package/dist/avatar.js +54 -0
  5. package/dist/calendar.js +2 -0
  6. package/dist/carousel.js +239 -0
  7. package/dist/chart.js +790 -0
  8. package/dist/checkbox-group.js +70 -0
  9. package/dist/checkbox.js +77 -0
  10. package/dist/chunks/bar-CVafh6C1.js +99 -0
  11. package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
  12. package/dist/chunks/collection-DtRB63U4.js +111 -0
  13. package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
  14. package/dist/chunks/menu-B45IyHHC.js +704 -0
  15. package/dist/chunks/native-BJdhd9XJ.js +20 -0
  16. package/dist/chunks/popup-C8Bb3l_g.js +459 -0
  17. package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
  18. package/dist/chunks/position-D6_i_SRn.js +434 -0
  19. package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
  20. package/dist/collapsible.js +106 -0
  21. package/dist/command.js +263 -0
  22. package/dist/context-menu.js +112 -0
  23. package/dist/data-table.js +5 -0
  24. package/dist/dialog.js +207 -0
  25. package/dist/direction.js +22 -0
  26. package/dist/drawer.js +139 -0
  27. package/dist/field.js +26 -0
  28. package/dist/index.js +38 -0
  29. package/dist/input-date.js +994 -0
  30. package/dist/input-group.js +52 -0
  31. package/dist/input-otp.js +179 -0
  32. package/dist/menu.js +2 -0
  33. package/dist/menubar.js +236 -0
  34. package/dist/message-scroller.js +446 -0
  35. package/dist/navigation-menu.js +330 -0
  36. package/dist/popover.js +307 -0
  37. package/dist/progress.js +39 -0
  38. package/dist/radio-group.js +107 -0
  39. package/dist/resizable.js +172 -0
  40. package/dist/select.js +961 -0
  41. package/dist/sidebar.js +343 -0
  42. package/dist/slider.js +259 -0
  43. package/dist/switch.js +53 -0
  44. package/dist/tabs.js +182 -0
  45. package/dist/toast.js +492 -0
  46. package/dist/toggle-group.js +111 -0
  47. package/dist/toggle.js +52 -0
  48. package/dist/toolbar.js +127 -0
  49. package/dist/tooltip.js +196 -0
  50. package/dist/utils.js +104 -0
  51. package/dist/virtual-list.js +2 -0
  52. package/package.json +250 -0
  53. package/src/accordion.tsx +261 -0
  54. package/src/availability.ts +261 -0
  55. package/src/avatar.tsx +99 -0
  56. package/src/bar.ts +156 -0
  57. package/src/calendar.tsx +1441 -0
  58. package/src/carousel.tsx +424 -0
  59. package/src/chart.tsx +1194 -0
  60. package/src/checkbox-group.tsx +132 -0
  61. package/src/checkbox.tsx +130 -0
  62. package/src/collapsible.tsx +188 -0
  63. package/src/collection.ts +154 -0
  64. package/src/command.tsx +511 -0
  65. package/src/context-menu.tsx +233 -0
  66. package/src/data-table-contract.ts +143 -0
  67. package/src/data-table-model.ts +760 -0
  68. package/src/data-table.tsx +475 -0
  69. package/src/dialog.tsx +393 -0
  70. package/src/direction.tsx +45 -0
  71. package/src/drawer.tsx +251 -0
  72. package/src/field.tsx +61 -0
  73. package/src/index.ts +37 -0
  74. package/src/input-date.tsx +1539 -0
  75. package/src/input-group.tsx +142 -0
  76. package/src/input-otp.tsx +324 -0
  77. package/src/menu-cluster.ts +124 -0
  78. package/src/menu.tsx +1095 -0
  79. package/src/menubar.tsx +459 -0
  80. package/src/message-scroller.tsx +732 -0
  81. package/src/native.ts +26 -0
  82. package/src/navigation-menu.tsx +578 -0
  83. package/src/popover.tsx +519 -0
  84. package/src/popup-surface.tsx +31 -0
  85. package/src/popup.ts +569 -0
  86. package/src/position.ts +523 -0
  87. package/src/progress.tsx +70 -0
  88. package/src/radio-group.tsx +186 -0
  89. package/src/resizable.tsx +310 -0
  90. package/src/segments.ts +922 -0
  91. package/src/select.tsx +1501 -0
  92. package/src/sidebar.tsx +683 -0
  93. package/src/slider.tsx +424 -0
  94. package/src/switch.tsx +104 -0
  95. package/src/tabs.tsx +314 -0
  96. package/src/toast.tsx +923 -0
  97. package/src/toggle-group.tsx +249 -0
  98. package/src/toggle.tsx +91 -0
  99. package/src/toolbar.tsx +212 -0
  100. package/src/tooltip.tsx +359 -0
  101. package/src/utils.ts +204 -0
  102. package/src/virtual-list.tsx +205 -0
  103. package/src/virtual.ts +385 -0
@@ -0,0 +1,1923 @@
1
+ import { DirectionContext } from "../direction.js";
2
+ import { clamp, controlled, dom, grid, remember, statefulRootAttrs } from "ajo-cloves";
3
+ import { Fragment, jsx, jsxs } from "ajo/jsx-runtime";
4
+ //#region packages/ajo-ui/src/segments.ts
5
+ /**
6
+ * segments.ts — the pure engine behind the InputDate family (ai/ui.md).
7
+ *
8
+ * No JSX, no DOM; Intl is the only platform dependency. `input-date.tsx`
9
+ * owns rendering, focus, and events, and consumes this surface:
10
+ *
11
+ * - `derive(opts)` — fixed-probe Intl derivation → ordered `Segment`
12
+ * descriptors (editable units + verbatim literals), resolved hour cycle,
13
+ * dayPeriod display strings, and localized month names.
14
+ * - `fromISO` / `toISO` — strict parse/serialize of the native normalized
15
+ * forms (zero-padded, no milliseconds, no 24:00); year 1–9999.
16
+ * - `typeDigit` / `matchName` / `eraseDigit` / `stepValue` — one pure
17
+ * segment mutation each (digit buffer, name prefix match, backspace,
18
+ * spin protocol).
19
+ * - `validate` / `defaultMessage` / `constrain` / `isReversed` — reason-coded
20
+ * validation with localized default messages, the day clamp, and the range
21
+ * cross-check (`reversed` is a consumer-raised reason: validate never
22
+ * returns it).
23
+ * - `formatValue` — human formatting of a committed value (message bounds,
24
+ * the field's hidden value description).
25
+ * - `inferGranularity` — value → defaultValue → placeholderValue →
26
+ * granularity arg → 'minute'.
27
+ * - `reconciler()` — lastEmitted/lastObserved external-change detection, so
28
+ * echoes of self-emitted values never clobber edits.
29
+ * - `field(options)` — the stateful editing record wiring all of the above:
30
+ * independently nullable units (hour kept in the display cycle, dayPeriod
31
+ * its own 0/1 unit), the typing buffer, and the eager-constrain commit
32
+ * pipeline. Every mutation returns an `InputResult` telling the consumer
33
+ * whether to move focus and what to emit; `emit` is `undefined` when the
34
+ * public value did not change.
35
+ */
36
+ var PROBE_YEAR = 2224;
37
+ var PROBE = new Date(PROBE_YEAR, 2, 4, 5, 6, 7);
38
+ var EDITABLE = [
39
+ "year",
40
+ "month",
41
+ "day",
42
+ "hour",
43
+ "minute",
44
+ "second",
45
+ "dayPeriod"
46
+ ];
47
+ var VOCABULARY = {
48
+ de: [
49
+ "jjjj",
50
+ "mm",
51
+ "tt"
52
+ ],
53
+ en: [
54
+ "yyyy",
55
+ "mm",
56
+ "dd"
57
+ ],
58
+ es: [
59
+ "aaaa",
60
+ "mm",
61
+ "dd"
62
+ ],
63
+ fr: [
64
+ "aaaa",
65
+ "mm",
66
+ "jj"
67
+ ],
68
+ ja: [
69
+ "年",
70
+ "月",
71
+ "日"
72
+ ]
73
+ };
74
+ var TIME_PLACEHOLDER = "––";
75
+ var boundsOf = (unit, hourCycle) => {
76
+ switch (unit) {
77
+ case "year": return [1, 9999];
78
+ case "month": return [1, 12];
79
+ case "day": return [1, 31];
80
+ case "hour": return hourCycle === "h12" ? [1, 12] : [0, 23];
81
+ case "dayPeriod": return [0, 1];
82
+ default: return [0, 59];
83
+ }
84
+ };
85
+ var resolveHourCycle = (locale, override) => {
86
+ if (override) return override === 12 ? "h12" : "h23";
87
+ const cycle = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions().hourCycle;
88
+ return cycle === "h11" || cycle === "h12" ? "h12" : "h23";
89
+ };
90
+ /** Derives ordered segment descriptors for a locale via a fixed-probe formatToParts. */
91
+ var derive = (opts) => {
92
+ const { kind, locale } = opts;
93
+ const hourCycle = kind === "date" ? "h23" : resolveHourCycle(locale, opts.hourCycle);
94
+ const withSeconds = kind !== "date" && opts.granularity === "second";
95
+ let format;
96
+ if (kind !== "date") {
97
+ format = {
98
+ hour: "numeric",
99
+ minute: "numeric",
100
+ hourCycle
101
+ };
102
+ if (withSeconds) format.second = "numeric";
103
+ if (kind === "datetime") Object.assign(format, {
104
+ year: "numeric",
105
+ month: "numeric",
106
+ day: "numeric"
107
+ });
108
+ }
109
+ const vocabulary = VOCABULARY[locale.split("-")[0].toLowerCase()] ?? VOCABULARY.en;
110
+ const placeholderOf = (unit) => unit === "year" ? vocabulary[0] : unit === "month" ? vocabulary[1] : unit === "day" ? vocabulary[2] : TIME_PLACEHOLDER;
111
+ const segments = [];
112
+ for (const part of new Intl.DateTimeFormat(locale, format).formatToParts(PROBE)) if (EDITABLE.includes(part.type)) {
113
+ const unit = part.type;
114
+ const [min, max] = boundsOf(unit, hourCycle);
115
+ segments.push({
116
+ type: unit,
117
+ text: "",
118
+ editable: true,
119
+ placeholder: placeholderOf(unit),
120
+ min,
121
+ max,
122
+ digits: unit === "dayPeriod" ? 0 : String(max).length,
123
+ width: part.value.length
124
+ });
125
+ } else segments.push({
126
+ type: "literal",
127
+ text: part.value,
128
+ editable: false,
129
+ placeholder: "",
130
+ min: 0,
131
+ max: 0,
132
+ digits: 0,
133
+ width: part.value.length
134
+ });
135
+ let periods = ["", ""];
136
+ if (kind !== "date" && hourCycle === "h12") {
137
+ const formatter = new Intl.DateTimeFormat(locale, {
138
+ hour: "numeric",
139
+ hourCycle: "h12"
140
+ });
141
+ const periodOf = (hour) => formatter.formatToParts(new Date(PROBE_YEAR, 2, 4, hour)).find((part) => part.type === "dayPeriod")?.value ?? (hour ? "PM" : "AM");
142
+ periods = [periodOf(0), periodOf(12)];
143
+ }
144
+ let monthNames = [];
145
+ if (kind !== "time") {
146
+ const formatter = new Intl.DateTimeFormat(locale, { month: "long" });
147
+ monthNames = Array.from({ length: 12 }, (_, month) => formatter.format(new Date(PROBE_YEAR, month, 4, 12)));
148
+ }
149
+ return {
150
+ segments,
151
+ units: segments.filter((segment) => segment.editable).map((segment) => segment.type),
152
+ hourCycle,
153
+ periods,
154
+ monthNames,
155
+ withSeconds
156
+ };
157
+ };
158
+ var TIME_UNITS = new Set([
159
+ "dayPeriod",
160
+ "hour",
161
+ "minute",
162
+ "second"
163
+ ]);
164
+ /** Returns the contiguous time portion of a derived segment sequence. */
165
+ var timeRun = (segments) => {
166
+ let first = -1;
167
+ let last = -1;
168
+ for (let index = 0; index < segments.length; index++) {
169
+ if (!TIME_UNITS.has(segments[index].type)) continue;
170
+ if (first < 0) first = index;
171
+ last = index;
172
+ }
173
+ return first < 0 ? [] : segments.slice(first, last + 1);
174
+ };
175
+ var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
176
+ var TIME_RE = /^(\d{2}):(\d{2})(?::(\d{2}))?$/;
177
+ /** Month length; unknown month falls back to 31, unknown year keeps Feb 29 typable. */
178
+ var daysInMonth$1 = (year, month) => {
179
+ if (month == null) return 31;
180
+ const date = new Date(PROBE_YEAR, month, 0);
181
+ if (year != null) date.setFullYear(year, month, 0);
182
+ return date.getDate();
183
+ };
184
+ var parseDate = (text) => {
185
+ const match = DATE_RE.exec(text);
186
+ if (!match) return null;
187
+ const year = Number(match[1]);
188
+ const month = Number(match[2]);
189
+ const day = Number(match[3]);
190
+ if (year < 1 || month < 1 || month > 12 || day < 1 || day > daysInMonth$1(year, month)) return null;
191
+ return {
192
+ year,
193
+ month,
194
+ day
195
+ };
196
+ };
197
+ var parseTime = (text) => {
198
+ const match = TIME_RE.exec(text);
199
+ if (!match) return null;
200
+ const hour = Number(match[1]);
201
+ const minute = Number(match[2]);
202
+ const second = match[3] == null ? null : Number(match[3]);
203
+ if (hour > 23 || minute > 59 || second != null && second > 59) return null;
204
+ return {
205
+ hour,
206
+ minute,
207
+ second
208
+ };
209
+ };
210
+ /** A fresh editing record: every unit independently empty. */
211
+ var emptyUnits = () => ({
212
+ year: null,
213
+ month: null,
214
+ day: null,
215
+ hour: null,
216
+ minute: null,
217
+ second: null,
218
+ dayPeriod: null
219
+ });
220
+ /** Strict parse of the native normalized forms into the editing record; null on anything else. */
221
+ var fromISO = (kind, value, hourCycle = "h23") => {
222
+ let date = null;
223
+ let time = null;
224
+ if (kind === "date") {
225
+ date = parseDate(value);
226
+ if (!date) return null;
227
+ } else if (kind === "time") {
228
+ time = parseTime(value);
229
+ if (!time) return null;
230
+ } else {
231
+ const at = value.indexOf("T");
232
+ if (at < 0) return null;
233
+ date = parseDate(value.slice(0, at));
234
+ time = parseTime(value.slice(at + 1));
235
+ if (!date || !time) return null;
236
+ }
237
+ const units = emptyUnits();
238
+ if (date) Object.assign(units, date);
239
+ if (time) {
240
+ units.minute = time.minute;
241
+ units.second = time.second;
242
+ if (hourCycle === "h12") {
243
+ units.dayPeriod = time.hour < 12 ? 0 : 1;
244
+ units.hour = time.hour % 12 || 12;
245
+ } else units.hour = time.hour;
246
+ }
247
+ return units;
248
+ };
249
+ var pad$1 = (value, width) => String(value).padStart(width, "0");
250
+ var filled = (value) => value != null && !Number.isNaN(value);
251
+ /** Serializes complete units to the family's normalized form; null while a required unit is missing. */
252
+ var toISO = (kind, units, opts = {}) => {
253
+ const parts = [];
254
+ if (kind !== "time") {
255
+ if (!filled(units.year) || !filled(units.month) || !filled(units.day)) return null;
256
+ parts.push(`${pad$1(units.year, 4)}-${pad$1(units.month, 2)}-${pad$1(units.day, 2)}`);
257
+ }
258
+ if (kind !== "date") {
259
+ if (!filled(units.hour) || !filled(units.minute)) return null;
260
+ if (opts.seconds && !filled(units.second)) return null;
261
+ const h12 = (opts.hourCycle ?? "h23") === "h12";
262
+ if (h12 && !filled(units.dayPeriod)) return null;
263
+ const hour = h12 ? units.dayPeriod === 1 ? units.hour === 12 ? 12 : units.hour + 12 : units.hour % 12 : units.hour;
264
+ parts.push(`${pad$1(hour, 2)}:${pad$1(units.minute, 2)}${opts.seconds ? `:${pad$1(units.second, 2)}` : ""}`);
265
+ }
266
+ return parts.join("T");
267
+ };
268
+ /** Digit protocol: the buffer accumulates, overflow restarts from the last key, full advances. */
269
+ var typeDigit = (buffer, key, min, max, digits) => {
270
+ const typed = buffer + key;
271
+ let value = Number(typed);
272
+ if (Number.isNaN(value) || value > max) value = Number(key);
273
+ const set = value !== 0 || min === 0;
274
+ const full = Number(typed) * 10 > max || typed.length >= digits;
275
+ return {
276
+ value: set ? value : null,
277
+ buffer: full ? "" : typed,
278
+ advance: full && set
279
+ };
280
+ };
281
+ var nameCollators = /* @__PURE__ */ new Map();
282
+ /** Prefix-matches locale names (Collator sensitivity 'base'); a repeated first letter cycles its matches. */
283
+ var matchName = (locale, names, current, buffer, key) => {
284
+ let collator = nameCollators.get(locale);
285
+ if (!collator) {
286
+ collator = new Intl.Collator(locale, {
287
+ sensitivity: "base",
288
+ usage: "search"
289
+ });
290
+ remember(nameCollators, locale, collator);
291
+ }
292
+ const starts = (name, query) => collator.compare(name.slice(0, query.length), query) === 0;
293
+ for (const query of buffer ? [buffer + key, key] : [key]) {
294
+ const matches = [];
295
+ for (let index = 0; index < names.length; index++) if (starts(names[index], query)) matches.push(index);
296
+ if (!matches.length) continue;
297
+ return {
298
+ index: query.length === 1 && current != null && matches.includes(current) ? matches[(matches.indexOf(current) + 1) % matches.length] : matches[0],
299
+ buffer: query,
300
+ unique: matches.length === 1
301
+ };
302
+ }
303
+ return null;
304
+ };
305
+ var PAGE_STEP = {
306
+ year: 5,
307
+ month: 2,
308
+ day: 7,
309
+ hour: 2,
310
+ minute: 15,
311
+ second: 15,
312
+ dayPeriod: 1
313
+ };
314
+ var cycle = (value, amount, min, max, round) => {
315
+ if (!round) {
316
+ value += amount;
317
+ return value < min ? max - (min - value - 1) : value > max ? min + (value - max - 1) : value;
318
+ }
319
+ value += Math.sign(amount);
320
+ if (value < min) value = max;
321
+ const grid = Math.abs(amount);
322
+ value = amount > 0 ? Math.ceil(value / grid) * grid : Math.floor(value / grid) * grid;
323
+ if (value > max) value = min;
324
+ return clamp(value, min, max);
325
+ };
326
+ /** Spin protocol: ±1 wraps at the bounds without carrying, pages round to their grid, edges land on min/max. */
327
+ var stepValue = (unit, value, action, min, max, minuteStep = 1) => {
328
+ if ("edge" in action) return action.edge === "min" ? min : max;
329
+ if ("page" in action) return cycle(value, PAGE_STEP[unit] * action.page, min, max, true);
330
+ const step = unit === "minute" ? minuteStep : 1;
331
+ return cycle(value, action.step * step, min, max, step > 1);
332
+ };
333
+ /** Backspace semantics: strip the last digit, empty shows the placeholder, empty again retreats. */
334
+ var eraseDigit = (value, buffer) => {
335
+ const text = buffer || (value == null ? "" : String(value));
336
+ if (!text) return {
337
+ value: null,
338
+ buffer: "",
339
+ retreat: true
340
+ };
341
+ const stripped = text.slice(0, -1);
342
+ const parsed = Number(stripped);
343
+ if (Number.isNaN(parsed)) return {
344
+ value: null,
345
+ buffer: "",
346
+ retreat: false
347
+ };
348
+ return {
349
+ value: stripped && parsed !== 0 ? parsed : null,
350
+ buffer: stripped,
351
+ retreat: false
352
+ };
353
+ };
354
+ /** Reason-coded validation; null = valid. An all-empty record is valid — required is the consumer's. */
355
+ var validate = (kind, units, order, opts = {}) => {
356
+ if (order.every((unit) => units[unit] == null)) return null;
357
+ for (const unit of order) if (units[unit] == null) return {
358
+ code: "incomplete",
359
+ unit
360
+ };
361
+ if (units.day != null && units.day > daysInMonth$1(units.year, units.month)) return { code: "impossible" };
362
+ const value = toISO(kind, units, {
363
+ hourCycle: opts.hourCycle,
364
+ seconds: opts.seconds
365
+ });
366
+ if (value == null) return { code: "impossible" };
367
+ if (opts.min != null && value < opts.min) return {
368
+ code: "rangeUnderflow",
369
+ min: opts.min
370
+ };
371
+ if (opts.max != null && value > opts.max) return {
372
+ code: "rangeOverflow",
373
+ max: opts.max
374
+ };
375
+ if (opts.unavailable?.(value)) return { code: "unavailable" };
376
+ return null;
377
+ };
378
+ /** Eager constrain: clamps the day into the real month length, in place. */
379
+ var constrain = (units) => {
380
+ if (units.day != null) units.day = Math.min(units.day, daysInMonth$1(units.year, units.month));
381
+ return units;
382
+ };
383
+ /** Range cross-check: from > to, in the family's own string format. */
384
+ var isReversed = (from, to) => from != null && to != null && from > to;
385
+ var displayNames = /* @__PURE__ */ new Map();
386
+ /** Localized date/time segment label, shared by field ARIA and validation messages. */
387
+ var unitLabel = (locale, unit) => {
388
+ let names = displayNames.get(locale);
389
+ if (!names) {
390
+ names = new Intl.DisplayNames([locale], { type: "dateTimeField" });
391
+ remember(displayNames, locale, names);
392
+ }
393
+ return names.of(unit) ?? unit;
394
+ };
395
+ var valueFormatters = /* @__PURE__ */ new Map();
396
+ var valueFormatter = (opts, seconds) => {
397
+ const key = [
398
+ opts.locale,
399
+ opts.kind,
400
+ seconds ? "second" : "",
401
+ opts.kind === "date" ? "" : opts.hourCycle ?? ""
402
+ ].join("\0");
403
+ let formatter = valueFormatters.get(key);
404
+ if (!formatter) {
405
+ const format = {};
406
+ if (opts.kind !== "time") format.dateStyle = "medium";
407
+ if (opts.kind !== "date") {
408
+ format.timeStyle = seconds ? "medium" : "short";
409
+ if (opts.hourCycle) format.hourCycle = opts.hourCycle;
410
+ }
411
+ formatter = new Intl.DateTimeFormat(opts.locale, format);
412
+ remember(valueFormatters, key, formatter);
413
+ }
414
+ return formatter;
415
+ };
416
+ /** Formats a committed value for humans: message bounds, the hidden value description. */
417
+ var formatValue = (value, opts) => {
418
+ const units = fromISO(opts.kind, value);
419
+ if (!units) return value;
420
+ const date = new Date(PROBE_YEAR, (units.month ?? 3) - 1, units.day ?? 4, units.hour ?? 12, units.minute ?? 0, units.second ?? 0);
421
+ date.setFullYear(units.year ?? PROBE_YEAR);
422
+ return valueFormatter(opts, units.second != null).format(date);
423
+ };
424
+ /** Default message builders (GOV.UK hierarchy): localized unit names and bounds, overridable per field. */
425
+ var defaultMessage = (reason, opts) => {
426
+ switch (reason.code) {
427
+ case "incomplete": return `Must include a ${unitLabel(opts.locale, reason.unit)}`;
428
+ case "impossible": return opts.kind === "time" ? "Must be a real time" : "Must be a real date";
429
+ case "rangeUnderflow": return `Must be ${formatValue(reason.min, opts)} or later`;
430
+ case "rangeOverflow": return `Must be ${formatValue(reason.max, opts)} or earlier`;
431
+ case "reversed": return opts.kind === "time" ? "Start time must be before end time" : "Start date must be before end date";
432
+ case "unavailable": return opts.kind === "time" ? "This time is unavailable" : "This date is unavailable";
433
+ case "unavailableRange": return "Range includes unavailable dates";
434
+ }
435
+ };
436
+ /** Controlled ↔ editing-state reconciliation: only genuinely external values clobber edits. */
437
+ var reconciler = () => {
438
+ let lastEmitted;
439
+ let lastObserved;
440
+ return {
441
+ emit(value) {
442
+ lastEmitted = lastObserved = value;
443
+ },
444
+ observe(value) {
445
+ if (value === void 0) return false;
446
+ if (value === lastEmitted || value === lastObserved) {
447
+ lastObserved = value;
448
+ return false;
449
+ }
450
+ lastEmitted = lastObserved = value;
451
+ return true;
452
+ }
453
+ };
454
+ };
455
+ /** value → defaultValue → placeholderValue → granularity arg → 'minute'; seconds in a value force the segment. */
456
+ var inferGranularity = (kind, opts) => {
457
+ if (kind === "date") return "day";
458
+ for (const source of [
459
+ opts.value,
460
+ opts.defaultValue,
461
+ opts.placeholderValue
462
+ ]) {
463
+ if (typeof source !== "string") continue;
464
+ const time = kind === "time" ? source : source.indexOf("T") < 0 ? "" : source.slice(source.indexOf("T") + 1);
465
+ if (TIME_RE.test(time)) return time.length > 5 ? "second" : "minute";
466
+ }
467
+ return opts.granularity ?? "minute";
468
+ };
469
+ var convertHour = (units, hourCycle) => {
470
+ if (units.hour == null) {
471
+ units.dayPeriod = null;
472
+ return;
473
+ }
474
+ if (hourCycle === "h12") {
475
+ units.dayPeriod = units.hour < 12 ? 0 : 1;
476
+ units.hour = units.hour % 12 || 12;
477
+ } else {
478
+ units.hour = units.dayPeriod === 1 ? units.hour === 12 ? 12 : units.hour + 12 : units.hour % 12;
479
+ units.dayPeriod = null;
480
+ }
481
+ };
482
+ /** The stateful editing record consumed by the InputDate family; one per field side. */
483
+ var field = (options) => {
484
+ const units = emptyUnits();
485
+ const rec = reconciler();
486
+ let opts = options;
487
+ let derivation;
488
+ let buffer = "";
489
+ let bufferUnit = null;
490
+ let committed = null;
491
+ let cacheKey = "";
492
+ let granularity;
493
+ const locale = () => opts.locale ?? "en-US";
494
+ const inferFor = (next, value) => inferGranularity(next.kind, {
495
+ value,
496
+ defaultValue: next.defaultValue,
497
+ placeholderValue: next.placeholderValue,
498
+ granularity: next.granularity
499
+ });
500
+ const inferKey = (next) => [
501
+ next.granularity ?? "",
502
+ next.defaultValue ?? "",
503
+ next.placeholderValue ?? ""
504
+ ].join("|");
505
+ const configure = (next) => {
506
+ opts = next;
507
+ const key = [
508
+ next.kind,
509
+ locale(),
510
+ granularity,
511
+ next.hourCycle ?? 0
512
+ ].join("|");
513
+ if (key === cacheKey) return;
514
+ cacheKey = key;
515
+ const previous = derivation?.hourCycle;
516
+ derivation = derive({
517
+ kind: next.kind,
518
+ locale: locale(),
519
+ granularity,
520
+ hourCycle: next.hourCycle
521
+ });
522
+ if (previous && previous !== derivation.hourCycle) convertHour(units, derivation.hourCycle);
523
+ buffer = "";
524
+ bufferUnit = null;
525
+ };
526
+ const adopt = (value) => {
527
+ const parsed = value == null ? null : fromISO(opts.kind, value, derivation.hourCycle);
528
+ if (value != null && !parsed) console.warn(`[segments] invalid ${opts.kind} value: "${value}"`);
529
+ Object.assign(units, parsed ?? emptyUnits());
530
+ buffer = "";
531
+ bufferUnit = null;
532
+ committed = parsed ? value : null;
533
+ };
534
+ granularity = inferFor(options);
535
+ configure(options);
536
+ if (options.defaultValue != null) adopt(options.defaultValue);
537
+ rec.emit(committed);
538
+ const segmentOf = (unit) => derivation.segments.find((segment) => segment.type === unit);
539
+ const boundsFor = (unit) => {
540
+ const segment = segmentOf(unit);
541
+ return {
542
+ min: segment?.min ?? 0,
543
+ max: unit === "day" ? daysInMonth$1(units.year, units.month) : segment?.max ?? 0
544
+ };
545
+ };
546
+ const complete = () => derivation.units.every((unit) => units[unit] != null);
547
+ const resolve = (advance, retreat) => {
548
+ let emit;
549
+ if (complete()) {
550
+ constrain(units);
551
+ const value = toISO(opts.kind, units, {
552
+ hourCycle: derivation.hourCycle,
553
+ seconds: derivation.withSeconds
554
+ });
555
+ if (value != null && value !== committed) {
556
+ committed = value;
557
+ rec.emit(value);
558
+ emit = value;
559
+ }
560
+ } else if (committed != null) {
561
+ committed = null;
562
+ rec.emit(null);
563
+ emit = null;
564
+ }
565
+ return {
566
+ handled: true,
567
+ advance,
568
+ retreat,
569
+ emit
570
+ };
571
+ };
572
+ const seed = () => {
573
+ if (opts.placeholderValue != null) {
574
+ const parsed = fromISO(opts.kind, opts.placeholderValue, derivation.hourCycle);
575
+ if (parsed) return parsed;
576
+ }
577
+ const now = /* @__PURE__ */ new Date();
578
+ const values = {
579
+ year: now.getFullYear(),
580
+ month: now.getMonth() + 1,
581
+ day: now.getDate(),
582
+ hour: now.getHours(),
583
+ minute: now.getMinutes(),
584
+ second: now.getSeconds(),
585
+ dayPeriod: null
586
+ };
587
+ if (derivation.hourCycle === "h12") convertHour(values, "h12");
588
+ return values;
589
+ };
590
+ const retarget = (unit) => {
591
+ if (bufferUnit !== unit) {
592
+ buffer = "";
593
+ bufferUnit = unit;
594
+ }
595
+ };
596
+ const reason = () => validate(opts.kind, units, derivation.units, {
597
+ min: opts.min,
598
+ max: opts.max,
599
+ unavailable: opts.unavailable,
600
+ hourCycle: derivation.hourCycle,
601
+ seconds: derivation.withSeconds
602
+ });
603
+ return {
604
+ get segments() {
605
+ return derivation.segments;
606
+ },
607
+ get units() {
608
+ return units;
609
+ },
610
+ get hourCycle() {
611
+ return derivation.hourCycle;
612
+ },
613
+ get periods() {
614
+ return derivation.periods;
615
+ },
616
+ get monthNames() {
617
+ return derivation.monthNames;
618
+ },
619
+ sync(value, next) {
620
+ const target = next ?? opts;
621
+ const external = rec.observe(value);
622
+ if (external) granularity = inferFor(target, value);
623
+ else if (inferKey(target) !== inferKey(opts)) {
624
+ granularity = inferFor(target, committed);
625
+ if (granularity !== "second" && units.second != null) granularity = "second";
626
+ }
627
+ configure(target);
628
+ if (external) adopt(value);
629
+ },
630
+ text(unit) {
631
+ if (bufferUnit === unit && buffer) return buffer;
632
+ const value = units[unit];
633
+ if (value == null) return "";
634
+ if (unit === "dayPeriod") return derivation.periods[value] ?? "";
635
+ return pad$1(value, segmentOf(unit)?.width ?? 1);
636
+ },
637
+ bounds: boundsFor,
638
+ type(unit, key) {
639
+ if (!derivation.units.includes(unit)) return { handled: false };
640
+ retarget(unit);
641
+ if (/^\d$/.test(key) && unit !== "dayPeriod") {
642
+ if (!/^\d*$/.test(buffer)) buffer = "";
643
+ const { min, max } = boundsFor(unit);
644
+ const state = typeDigit(buffer, key, min, max, segmentOf(unit)?.digits ?? 2);
645
+ buffer = state.buffer;
646
+ if (state.value == null) return { handled: true };
647
+ units[unit] = state.value;
648
+ return resolve(state.advance);
649
+ }
650
+ if (unit === "month" && derivation.monthNames.length) {
651
+ const match = matchName(locale(), derivation.monthNames, units.month == null ? null : units.month - 1, buffer, key);
652
+ if (!match) return { handled: false };
653
+ buffer = match.buffer;
654
+ units.month = match.index + 1;
655
+ return resolve(match.unique);
656
+ }
657
+ if (unit === "dayPeriod") {
658
+ const match = matchName(locale(), derivation.periods, units.dayPeriod, "", key);
659
+ if (!match) return { handled: false };
660
+ units.dayPeriod = match.index;
661
+ return resolve(match.unique);
662
+ }
663
+ return { handled: false };
664
+ },
665
+ erase(unit) {
666
+ if (!derivation.units.includes(unit)) return { handled: false };
667
+ retarget(unit);
668
+ if (unit === "dayPeriod") {
669
+ buffer = "";
670
+ if (units.dayPeriod == null) return {
671
+ handled: true,
672
+ retreat: true
673
+ };
674
+ units.dayPeriod = null;
675
+ return resolve();
676
+ }
677
+ if (unit === "month" && buffer && !/^\d*$/.test(buffer)) {
678
+ buffer = buffer.slice(0, -1);
679
+ const match = buffer ? matchName(locale(), derivation.monthNames, null, "", buffer) : null;
680
+ units.month = match ? match.index + 1 : null;
681
+ return resolve();
682
+ }
683
+ const state = eraseDigit(units[unit], buffer);
684
+ if (state.retreat) return {
685
+ handled: true,
686
+ retreat: true
687
+ };
688
+ buffer = state.buffer;
689
+ units[unit] = state.value;
690
+ return resolve();
691
+ },
692
+ spin(unit, action) {
693
+ if (!derivation.units.includes(unit)) return { handled: false };
694
+ buffer = "";
695
+ bufferUnit = unit;
696
+ const { min, max } = boundsFor(unit);
697
+ const current = units[unit];
698
+ if ("edge" in action) units[unit] = action.edge === "min" ? min : max;
699
+ else if (current == null) units[unit] = clamp(seed()[unit] ?? min, min, max);
700
+ else units[unit] = stepValue(unit, current, action, min, max, opts.step ?? 1);
701
+ return resolve();
702
+ },
703
+ merge(next) {
704
+ buffer = "";
705
+ bufferUnit = null;
706
+ for (const unit of EDITABLE) {
707
+ const value = next[unit];
708
+ if (value !== void 0) units[unit] = value;
709
+ }
710
+ return resolve();
711
+ },
712
+ placeholder: () => seed(),
713
+ clear() {
714
+ buffer = "";
715
+ bufferUnit = null;
716
+ Object.assign(units, emptyUnits());
717
+ return resolve();
718
+ },
719
+ reset() {
720
+ adopt(opts.defaultValue ?? null);
721
+ },
722
+ blur() {
723
+ buffer = "";
724
+ bufferUnit = null;
725
+ },
726
+ value: () => committed,
727
+ complete,
728
+ reason,
729
+ message() {
730
+ const why = reason();
731
+ if (!why) return null;
732
+ return opts.errorMessage?.(why) ?? defaultMessage(why, {
733
+ kind: opts.kind,
734
+ locale: locale(),
735
+ hourCycle: derivation.hourCycle
736
+ });
737
+ }
738
+ };
739
+ };
740
+ //#endregion
741
+ //#region packages/ajo-ui/src/availability.ts
742
+ var clock = (value, fallback) => {
743
+ if (value == null) return fallback;
744
+ const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
745
+ if (!match) return NaN;
746
+ const hour = Number(match[1]);
747
+ const minute = Number(match[2]);
748
+ const second = Number(match[3] ?? 0);
749
+ if (hour > 23 || minute > 59 || second > 59) return NaN;
750
+ return hour * 3600 + minute * 60 + second;
751
+ };
752
+ var timePredicate = (window) => {
753
+ const from = clock(window.from, 0);
754
+ const to = clock(window.to, 24 * 3600);
755
+ const valid = Number.isFinite(from) && Number.isFinite(to) && from < to;
756
+ return (parts) => {
757
+ const value = parts.hour * 3600 + parts.minute * 60 + parts.second;
758
+ return valid && from <= value && value < to;
759
+ };
760
+ };
761
+ var partFormatters = /* @__PURE__ */ new Map();
762
+ var partFormatter = (timeZone) => {
763
+ let formatter = partFormatters.get(timeZone);
764
+ if (!formatter) {
765
+ formatter = new Intl.DateTimeFormat("en-CA", {
766
+ day: "2-digit",
767
+ hour: "2-digit",
768
+ hourCycle: "h23",
769
+ minute: "2-digit",
770
+ month: "2-digit",
771
+ second: "2-digit",
772
+ timeZone,
773
+ year: "numeric"
774
+ });
775
+ remember(partFormatters, timeZone, formatter);
776
+ }
777
+ return formatter;
778
+ };
779
+ var partsOf = (date, timeZone) => {
780
+ if (!Number.isFinite(date.getTime())) return {
781
+ day: NaN,
782
+ hour: NaN,
783
+ minute: NaN,
784
+ month: NaN,
785
+ second: NaN,
786
+ year: NaN
787
+ };
788
+ if (!timeZone) return {
789
+ day: date.getDate(),
790
+ hour: date.getHours(),
791
+ minute: date.getMinutes(),
792
+ month: date.getMonth() + 1,
793
+ second: date.getSeconds(),
794
+ year: date.getFullYear()
795
+ };
796
+ const parts = partFormatter(timeZone).formatToParts(date);
797
+ const value = (type) => Number(parts.find((part) => part.type === type)?.value);
798
+ return {
799
+ day: value("day"),
800
+ hour: value("hour"),
801
+ minute: value("minute"),
802
+ month: value("month"),
803
+ second: value("second"),
804
+ year: value("year")
805
+ };
806
+ };
807
+ var dayValue = (parts) => parts.year * 1e4 + parts.month * 100 + parts.day;
808
+ var exactUtcDate = (parts) => {
809
+ const date = /* @__PURE__ */ new Date(0);
810
+ date.setUTCFullYear(parts.year, parts.month - 1, parts.day);
811
+ date.setUTCHours(parts.hour ?? 0, parts.minute ?? 0, parts.second ?? 0, 0);
812
+ return date;
813
+ };
814
+ var exactLocalDate = (parts) => {
815
+ const date = /* @__PURE__ */ new Date(0);
816
+ date.setFullYear(parts.year, parts.month - 1, parts.day);
817
+ date.setHours(parts.hour, parts.minute, parts.second, 0);
818
+ return date;
819
+ };
820
+ var weekday$1 = (parts) => exactUtcDate(parts).getUTCDay();
821
+ /** Constructs a stable instant for one wall-clock value in a calendar time zone. */
822
+ var calendarDate = (parts, timeZone) => {
823
+ if (!timeZone) return exactLocalDate(parts);
824
+ const wanted = exactUtcDate(parts).getTime();
825
+ let instant = wanted;
826
+ for (let pass = 0; pass < 2; pass++) {
827
+ const actual = exactUtcDate(partsOf(new Date(instant), timeZone)).getTime();
828
+ instant += wanted - actual;
829
+ }
830
+ return new Date(instant);
831
+ };
832
+ var addDay = (parts) => {
833
+ const next = exactUtcDate({
834
+ ...parts,
835
+ day: parts.day + 1,
836
+ hour: 12
837
+ });
838
+ return {
839
+ day: next.getUTCDate(),
840
+ hour: 12,
841
+ minute: 0,
842
+ month: next.getUTCMonth() + 1,
843
+ second: 0,
844
+ year: next.getUTCFullYear()
845
+ };
846
+ };
847
+ /** Compiles date/time expressions into stable day- and instant-granular predicates. */
848
+ var compile = (matcher, options = {}) => {
849
+ if (matcher == null) return void 0;
850
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
851
+ if (!matchers.length) return void 0;
852
+ const timeZone = options.timeZone;
853
+ const parts = (date) => partsOf(date, timeZone);
854
+ const compileMatcher = (item) => {
855
+ if (item instanceof Date) {
856
+ const expected = dayValue(parts(item));
857
+ return { date: (_value, current) => dayValue(current()) === expected };
858
+ }
859
+ if (Array.isArray(item)) {
860
+ const expected = new Set(item.map((entry) => dayValue(parts(entry))).filter(Number.isFinite));
861
+ return { date: (_value, current) => expected.has(dayValue(current())) };
862
+ }
863
+ if (typeof item === "function") return { date: item };
864
+ const { after, before, dayOfWeek, from, to } = item;
865
+ const afterDay = after == null ? void 0 : dayValue(parts(after));
866
+ const beforeDay = before == null ? void 0 : dayValue(parts(before));
867
+ const fromDay = from == null ? void 0 : dayValue(parts(from));
868
+ const toDay = to == null ? void 0 : dayValue(parts(to));
869
+ const weekdays = dayOfWeek == null ? void 0 : new Set(dayOfWeek);
870
+ const hasDate = after != null || before != null || dayOfWeek != null || from != null || to != null;
871
+ const time = "time" in item ? timePredicate(item.time) : void 0;
872
+ return {
873
+ date: (_value, read) => {
874
+ if (!hasDate) return time != null;
875
+ const current = read();
876
+ const currentDay = dayValue(current);
877
+ if (weekdays != null && !weekdays.has(weekday$1(current))) return false;
878
+ if (beforeDay != null && !(currentDay < beforeDay)) return false;
879
+ if (afterDay != null && !(currentDay > afterDay)) return false;
880
+ if (fromDay != null && toDay != null) return fromDay <= currentDay && currentDay <= toDay;
881
+ if (fromDay != null) return currentDay === fromDay;
882
+ if (toDay != null) return currentDay === toDay;
883
+ return true;
884
+ },
885
+ time
886
+ };
887
+ };
888
+ const predicates = matchers.map(compileMatcher);
889
+ const day = (date) => {
890
+ let current;
891
+ const read = () => current ??= parts(date);
892
+ return predicates.some((predicate) => predicate.time == null && predicate.date(date, read));
893
+ };
894
+ const at = (date) => {
895
+ let current;
896
+ const read = () => current ??= parts(date);
897
+ return predicates.some((predicate) => predicate.date(date, read) && (predicate.time?.(read()) ?? true));
898
+ };
899
+ return {
900
+ day,
901
+ at,
902
+ value(kind, value) {
903
+ const units = fromISO(kind, value);
904
+ if (!units) return false;
905
+ const now = parts(/* @__PURE__ */ new Date());
906
+ const date = kind === "time" ? calendarDate({
907
+ ...now,
908
+ hour: units.hour ?? 0,
909
+ minute: units.minute ?? 0,
910
+ second: units.second ?? 0
911
+ }, timeZone) : calendarDate({
912
+ day: units.day,
913
+ hour: units.hour ?? 12,
914
+ minute: units.minute ?? 0,
915
+ month: units.month,
916
+ second: units.second ?? 0,
917
+ year: units.year
918
+ }, timeZone);
919
+ return kind === "date" ? day(date) : at(date);
920
+ },
921
+ crosses(from, to) {
922
+ const end = dayValue(parts(to));
923
+ let current = parts(from);
924
+ if (dayValue(current) >= end) return false;
925
+ for (current = addDay(current); dayValue(current) < end; current = addDay(current)) if (day(calendarDate(current, timeZone))) return true;
926
+ return false;
927
+ }
928
+ };
929
+ };
930
+ //#endregion
931
+ //#region packages/ajo-ui/src/calendar.tsx
932
+ var localeCode = (locale) => typeof locale === "string" ? locale : locale?.code;
933
+ var pad = (value) => String(value).padStart(2, "0");
934
+ var iso = ({ day, month, year }) => `${year}-${pad(month)}-${pad(day)}`;
935
+ var monthIso = ({ month, year }) => `${year}-${pad(month)}`;
936
+ var parseIso = (value) => {
937
+ const [year, month, day] = value.split("-").map(Number);
938
+ if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return void 0;
939
+ return normalize(year, month, day);
940
+ };
941
+ var parseMonthIso = (value) => {
942
+ const [year, month] = value.split("-").map(Number);
943
+ if (!Number.isFinite(year) || !Number.isFinite(month) || month < 1 || month > 12) return void 0;
944
+ return {
945
+ day: 1,
946
+ month,
947
+ year
948
+ };
949
+ };
950
+ var utcDate = (year, month, day) => {
951
+ const date = /* @__PURE__ */ new Date(0);
952
+ date.setUTCFullYear(year, month - 1, day);
953
+ date.setUTCHours(0, 0, 0, 0);
954
+ return date;
955
+ };
956
+ var normalize = (year, month, day) => {
957
+ const date = utcDate(year, month, day);
958
+ return {
959
+ day: date.getUTCDate(),
960
+ month: date.getUTCMonth() + 1,
961
+ year: date.getUTCFullYear()
962
+ };
963
+ };
964
+ var addDays = (date, days) => normalize(date.year, date.month, date.day + days);
965
+ var addMonths = (date, monthCount) => normalize(date.year, date.month + monthCount, Math.min(date.day, daysInMonth(normalize(date.year, date.month + monthCount, 1))));
966
+ var monthStart = (date) => ({
967
+ year: date.year,
968
+ month: date.month,
969
+ day: 1
970
+ });
971
+ var daysInMonth = (date) => utcDate(date.year, date.month + 1, 0).getUTCDate();
972
+ var comparePlain = (first, second) => iso(first).localeCompare(iso(second));
973
+ var samePlain = (first, second) => first.year === second.year && first.month === second.month && first.day === second.day;
974
+ var viewRank = {
975
+ day: 0,
976
+ month: 1,
977
+ year: 2
978
+ };
979
+ var minimumView = (view) => view ?? "day";
980
+ var clampView = (view, minView) => viewRank[view] < viewRank[minimumView(minView)] ? minimumView(minView) : view;
981
+ var periodStart = (date, view) => {
982
+ if (view === "year") return {
983
+ day: 1,
984
+ month: 1,
985
+ year: date.year
986
+ };
987
+ if (view === "month") return monthStart(date);
988
+ return date;
989
+ };
990
+ var periodEnd = (date, view) => {
991
+ if (view === "year") return {
992
+ day: 31,
993
+ month: 12,
994
+ year: date.year
995
+ };
996
+ if (view === "month") {
997
+ const start = monthStart(date);
998
+ return {
999
+ ...start,
1000
+ day: daysInMonth(start)
1001
+ };
1002
+ }
1003
+ return date;
1004
+ };
1005
+ var samePeriod = (first, second, view) => {
1006
+ if (view === "year") return first.year === second.year;
1007
+ if (view === "month") return first.year === second.year && first.month === second.month;
1008
+ return samePlain(first, second);
1009
+ };
1010
+ var periodIntersects = (range, date, view) => Boolean(range.from && range.to && comparePlain(range.from, periodEnd(date, view)) <= 0 && comparePlain(periodStart(date, view), range.to) <= 0);
1011
+ var weekday = (date) => utcDate(date.year, date.month, date.day).getUTCDay();
1012
+ var plainToDate = (date, timeZone) => calendarDate({
1013
+ ...date,
1014
+ hour: 12,
1015
+ minute: 0,
1016
+ second: 0
1017
+ }, timeZone);
1018
+ var zonedDateFormatters = /* @__PURE__ */ new Map();
1019
+ var zonedDateFormatter = (timeZone) => {
1020
+ let format = zonedDateFormatters.get(timeZone);
1021
+ if (!format) {
1022
+ format = new Intl.DateTimeFormat("en-CA", {
1023
+ day: "2-digit",
1024
+ month: "2-digit",
1025
+ timeZone,
1026
+ year: "numeric"
1027
+ });
1028
+ remember(zonedDateFormatters, timeZone, format);
1029
+ }
1030
+ return format;
1031
+ };
1032
+ var dateToPlain = (date, timeZone) => {
1033
+ if (timeZone) {
1034
+ const parts = zonedDateFormatter(timeZone).formatToParts(date);
1035
+ const value = (type) => Number(parts.find((part) => part.type === type)?.value);
1036
+ return normalize(value("year"), value("month"), value("day"));
1037
+ }
1038
+ return normalize(date.getFullYear(), date.getMonth() + 1, date.getDate());
1039
+ };
1040
+ var today = (timeZone) => dateToPlain(/* @__PURE__ */ new Date(), timeZone);
1041
+ var startOfWeek = (date, weekStartsOn) => addDays(date, -((weekday(date) - weekStartsOn + 7) % 7));
1042
+ var startOfGrid = (month, weekStartsOn) => {
1043
+ return startOfWeek(month, weekStartsOn);
1044
+ };
1045
+ var weeksForMonth = (month, fixedWeeks, weekStartsOn) => {
1046
+ const start = startOfGrid(month, weekStartsOn);
1047
+ const length = fixedWeeks ? 42 : Math.ceil(((weekday(month) - weekStartsOn + 7) % 7 + daysInMonth(month)) / 7) * 7;
1048
+ const weeks = [];
1049
+ for (let index = 0; index < length; index += 7) weeks.push(Array.from({ length: 7 }, (_, day) => addDays(start, index + day)));
1050
+ return weeks;
1051
+ };
1052
+ var months = (start, count, fixedWeeks, weekStartsOn) => Array.from({ length: Math.max(1, count) }, (_, index) => {
1053
+ const month = monthStart(addMonths(start, index));
1054
+ return {
1055
+ month,
1056
+ weeks: weeksForMonth(month, fixedWeeks, weekStartsOn)
1057
+ };
1058
+ });
1059
+ var DATE_FORMAT_OPTIONS = {
1060
+ day: { day: "numeric" },
1061
+ fullDate: { dateStyle: "full" },
1062
+ monthCaption: {
1063
+ month: "long",
1064
+ year: "numeric"
1065
+ },
1066
+ monthDropdown: { month: "short" },
1067
+ monthLabel: { month: "long" },
1068
+ weekday: { weekday: "short" }
1069
+ };
1070
+ var dateFormatters = /* @__PURE__ */ new Map();
1071
+ var formatter = (locale, name, timeZone) => {
1072
+ const key = `${locale ?? ""}\0${timeZone ?? ""}`;
1073
+ let formats = dateFormatters.get(key);
1074
+ if (!formats) {
1075
+ formats = {};
1076
+ remember(dateFormatters, key, formats);
1077
+ }
1078
+ return formats[name] ??= new Intl.DateTimeFormat(locale, {
1079
+ ...DATE_FORMAT_OPTIONS[name],
1080
+ timeZone
1081
+ });
1082
+ };
1083
+ var defaultFormatters = {
1084
+ day: (date, locale, timeZone) => formatter(locale, "day", timeZone).format(date),
1085
+ fullDate: (date, locale, timeZone) => formatter(locale, "fullDate", timeZone).format(date),
1086
+ monthCaption: (date, locale, timeZone) => formatter(locale, "monthCaption", timeZone).format(date),
1087
+ monthDropdown: (date, locale, timeZone) => formatter(locale, "monthDropdown", timeZone).format(date),
1088
+ weekNumber: (week) => pad(week),
1089
+ weekday: (date, locale, timeZone) => formatter(locale, "weekday", timeZone).format(date)
1090
+ };
1091
+ var toPlainArray = (dates, timeZone) => (dates ?? []).map((date) => dateToPlain(date, timeZone));
1092
+ var toRangePlain = (range, timeZone) => ({
1093
+ from: range?.from ? dateToPlain(range.from, timeZone) : void 0,
1094
+ to: range?.to ? dateToPlain(range.to, timeZone) : void 0
1095
+ });
1096
+ var plainIn = (date, dates) => dates.some((item) => samePlain(item, date));
1097
+ var rangeContains = (range, date) => Boolean(range.from && range.to && comparePlain(range.from, date) <= 0 && comparePlain(date, range.to) <= 0);
1098
+ var rangeMiddle = (range, date) => Boolean(range.from && range.to && comparePlain(range.from, date) < 0 && comparePlain(date, range.to) < 0);
1099
+ var weekNumber = (date) => {
1100
+ const target = utcDate(date.year, date.month, date.day);
1101
+ const day = target.getUTCDay() || 7;
1102
+ target.setUTCDate(target.getUTCDate() + 4 - day);
1103
+ const yearStart = utcDate(target.getUTCFullYear(), 1, 1);
1104
+ return Math.ceil(((target.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
1105
+ };
1106
+ var gridTemplate = (showWeekNumber) => showWeekNumber ? "grid-template-columns:var(--cell-size) repeat(7,minmax(var(--cell-size),1fr))" : "grid-template-columns:repeat(7,minmax(var(--cell-size),1fr))";
1107
+ var monthOptions = (year) => Array.from({ length: 12 }, (_, month) => ({
1108
+ day: 1,
1109
+ month: month + 1,
1110
+ year
1111
+ }));
1112
+ var yearBounds = (current, args, now) => {
1113
+ const first = args.startMonth ? dateToPlain(args.startMonth, args.timeZone).year : args.fromYear ?? now.year - 100;
1114
+ const last = args.endMonth ? dateToPlain(args.endMonth, args.timeZone).year : args.toYear ?? Math.max(now.year, current.year);
1115
+ return {
1116
+ first,
1117
+ last: Math.max(first, last)
1118
+ };
1119
+ };
1120
+ var yearRange = (current, args, now) => {
1121
+ const { first, last } = yearBounds(current, args, now);
1122
+ return Array.from({ length: last - first + 1 }, (_, index) => first + index);
1123
+ };
1124
+ var yearPage = (current, args, now) => {
1125
+ const allowed = yearRange(current, args, now);
1126
+ const first = allowed[0];
1127
+ const last = allowed[allowed.length - 1];
1128
+ const anchor = Math.min(last, Math.max(first, current.year));
1129
+ const start = first + Math.floor((anchor - first) / 12) * 12;
1130
+ return {
1131
+ allowed,
1132
+ start,
1133
+ years: Array.from({ length: 12 }, (_, index) => start + index)
1134
+ };
1135
+ };
1136
+ var canNavigateYear = (year, current, args, now) => {
1137
+ const { first, last } = yearBounds(current, args, now);
1138
+ return first <= year && year <= last;
1139
+ };
1140
+ var canNavigateTo = (month, args) => {
1141
+ const start = args.startMonth ? monthStart(dateToPlain(args.startMonth, args.timeZone)) : args.fromYear ? {
1142
+ day: 1,
1143
+ month: 1,
1144
+ year: args.fromYear
1145
+ } : void 0;
1146
+ const end = args.endMonth ? monthStart(dateToPlain(args.endMonth, args.timeZone)) : args.toYear ? {
1147
+ day: 1,
1148
+ month: 12,
1149
+ year: args.toYear
1150
+ } : void 0;
1151
+ if (start && comparePlain(monthStart(month), start) < 0) return false;
1152
+ if (end && comparePlain(monthStart(month), end) > 0) return false;
1153
+ return true;
1154
+ };
1155
+ var modifierNames = (modifiers, date) => Array.from(modifiers).filter(([, availability]) => availability.day(date)).map(([name]) => name);
1156
+ var modifierAttributes = (names) => Object.fromEntries(names.map((name) => [`data-modifier-${name}`, "true"]));
1157
+ var defaultSelectedRange = (value) => value && !Array.isArray(value) && !(value instanceof Date) ? value : void 0;
1158
+ var initialMonthDate = (month, defaultMonth, defaultSelected) => {
1159
+ if (month) return month;
1160
+ if (defaultMonth) return defaultMonth;
1161
+ if (defaultSelected instanceof Date) return defaultSelected;
1162
+ if (Array.isArray(defaultSelected)) return defaultSelected[0] ?? /* @__PURE__ */ new Date();
1163
+ return defaultSelectedRange(defaultSelected)?.from ?? /* @__PURE__ */ new Date();
1164
+ };
1165
+ /** Unstyled native month or year picker used when no custom dropdown is supplied. */
1166
+ var CalendarDropdown = ({ label, onValueChange, options, value }) => /* @__PURE__ */ jsx("select", {
1167
+ "aria-label": label,
1168
+ "data-slot": "calendar-dropdown",
1169
+ "set:onchange": (event) => onValueChange(event.currentTarget.value, event),
1170
+ "set:value": value,
1171
+ children: options.map((option) => /* @__PURE__ */ jsx("option", {
1172
+ value: option.value,
1173
+ children: option.label
1174
+ }, option.value))
1175
+ });
1176
+ var CalendarRoot = function* ({ defaultMonth, defaultSelected, defaultView, minView, month, timeZone, view }) {
1177
+ const initialMonth = monthStart(dateToPlain(initialMonthDate(month, defaultMonth, defaultSelected), timeZone));
1178
+ const initialView = clampView(view ?? defaultView ?? minimumView(minView), minView);
1179
+ const initialSingle = defaultSelected instanceof Date ? dateToPlain(defaultSelected, timeZone) : null;
1180
+ const initialMultiple = Array.isArray(defaultSelected) ? toPlainArray(defaultSelected, timeZone) : [];
1181
+ const initialRange = !Array.isArray(defaultSelected) && !(defaultSelected instanceof Date) ? toRangePlain(defaultSelected, timeZone) : {};
1182
+ let currentArgs = {};
1183
+ let currentView = initialView;
1184
+ let renderedView = initialView;
1185
+ let visible = initialMonth;
1186
+ const domReady = dom(this);
1187
+ let disabledSource;
1188
+ let disabledTimeZone;
1189
+ let disabledAvailability;
1190
+ let modifiersSource;
1191
+ let modifiersTimeZone;
1192
+ let modifierAvailability = /* @__PURE__ */ new Map();
1193
+ let unavailableSource;
1194
+ let unavailableTimeZone;
1195
+ let unavailableAvailability;
1196
+ const syncAvailability = (args) => {
1197
+ if (args.disabled !== disabledSource || args.timeZone !== disabledTimeZone) {
1198
+ disabledSource = args.disabled;
1199
+ disabledTimeZone = args.timeZone;
1200
+ disabledAvailability = compile(args.disabled, { timeZone: args.timeZone });
1201
+ }
1202
+ if (args.modifiers !== modifiersSource || args.timeZone !== modifiersTimeZone) {
1203
+ modifiersSource = args.modifiers;
1204
+ modifiersTimeZone = args.timeZone;
1205
+ modifierAvailability = new Map(Object.entries(args.modifiers ?? {}).flatMap(([name, matcher]) => {
1206
+ const availability = compile(matcher, { timeZone: args.timeZone });
1207
+ return availability ? [[name, availability]] : [];
1208
+ }));
1209
+ }
1210
+ if (args.unavailable !== unavailableSource || args.timeZone !== unavailableTimeZone) {
1211
+ unavailableSource = args.unavailable;
1212
+ unavailableTimeZone = args.timeZone;
1213
+ unavailableAvailability = compile(args.unavailable, { timeZone: args.timeZone });
1214
+ }
1215
+ };
1216
+ const monthState = controlled(this, {
1217
+ fallback: initialMonth,
1218
+ onChange: (next, event) => currentArgs.onMonthChange?.(plainToDate(next, currentArgs.timeZone), event)
1219
+ });
1220
+ const viewState = controlled(this, {
1221
+ fallback: initialView,
1222
+ onChange: (next, event) => currentArgs.onViewChange?.(next, event)
1223
+ });
1224
+ const singleState = controlled(this, {
1225
+ fallback: initialSingle,
1226
+ onChange: (next, event) => {
1227
+ if (currentArgs.mode === "multiple" || currentArgs.mode === "range" || !event) return;
1228
+ currentArgs.onSelect?.(next && plainToDate(next, currentArgs.timeZone), event);
1229
+ }
1230
+ });
1231
+ const multipleState = controlled(this, {
1232
+ fallback: initialMultiple,
1233
+ onChange: (next, event) => {
1234
+ if (currentArgs.mode !== "multiple" || !event) return;
1235
+ currentArgs.onSelect?.(next.map((item) => plainToDate(item, currentArgs.timeZone)), event);
1236
+ }
1237
+ });
1238
+ const rangeState = controlled(this, {
1239
+ fallback: initialRange,
1240
+ onChange: (next, event) => {
1241
+ if (currentArgs.mode !== "range" || !event) return;
1242
+ currentArgs.onSelect?.(next.from || next.to ? {
1243
+ from: next.from ? plainToDate(next.from, currentArgs.timeZone) : void 0,
1244
+ to: next.to ? plainToDate(next.to, currentArgs.timeZone) : void 0
1245
+ } : null, event);
1246
+ }
1247
+ });
1248
+ const focusDay = (date, args) => {
1249
+ queueMicrotask(() => {
1250
+ const find = () => this.querySelector(`button[data-day="${iso(date)}"]:not(:disabled)`);
1251
+ const target = find();
1252
+ if (target) {
1253
+ target.focus();
1254
+ return;
1255
+ }
1256
+ const visible = args.month ? monthStart(dateToPlain(args.month, args.timeZone)) : monthState.value;
1257
+ const count = Math.max(1, args.numberOfMonths ?? 1);
1258
+ const start = monthStart(date);
1259
+ if (comparePlain(start, visible) < 0) moveMonth(addMonths(visible, -1), args);
1260
+ else if (comparePlain(start, addMonths(visible, count - 1)) > 0) moveMonth(addMonths(visible, 1), args);
1261
+ else return;
1262
+ queueMicrotask(() => find()?.focus());
1263
+ });
1264
+ };
1265
+ const focusMonthCell = (month, args, event) => {
1266
+ const target = monthStart(month);
1267
+ queueMicrotask(() => {
1268
+ const find = () => this.querySelector(`button[data-month="${monthIso(target)}"]:not(:disabled)`);
1269
+ const found = find();
1270
+ if (found) {
1271
+ found.focus();
1272
+ return;
1273
+ }
1274
+ if (!canNavigateTo(target, args)) return;
1275
+ moveMonth(target, args, event);
1276
+ queueMicrotask(() => find()?.focus());
1277
+ });
1278
+ };
1279
+ const navigableMonth = (year, args) => {
1280
+ const options = monthOptions(year);
1281
+ return options.find((month) => month.month === visible.month && canNavigateTo(month, args)) ?? options.find((month) => canNavigateTo(month, args));
1282
+ };
1283
+ const canCommitYear = (year, args) => canNavigateTo({
1284
+ day: 1,
1285
+ month: 1,
1286
+ year
1287
+ }, args) && canNavigateTo({
1288
+ day: 1,
1289
+ month: 12,
1290
+ year
1291
+ }, args);
1292
+ const canUseYearCell = (year, args) => minimumView(args.minView) === "year" ? canCommitYear(year, args) : canNavigateYear(year, visible, args, today(args.timeZone));
1293
+ const focusYearCell = (year, args, event) => {
1294
+ queueMicrotask(() => {
1295
+ const find = () => this.querySelector(`button[data-year="${year}"]:not(:disabled)`);
1296
+ const found = find();
1297
+ if (found) {
1298
+ found.focus();
1299
+ return;
1300
+ }
1301
+ if (!canUseYearCell(year, args)) return;
1302
+ const month = navigableMonth(year, args);
1303
+ if (!month) return;
1304
+ moveMonth(month, args, event);
1305
+ queueMicrotask(() => find()?.focus());
1306
+ });
1307
+ };
1308
+ const moveMonth = (next, args, event) => {
1309
+ const target = monthStart(next);
1310
+ if (!canNavigateTo(target, args)) return;
1311
+ monthState.set(target, event);
1312
+ };
1313
+ const selectPeriod = (date, view, args, event) => {
1314
+ const start = periodStart(date, view);
1315
+ const end = periodEnd(date, view);
1316
+ if (args.mode === "multiple") {
1317
+ const current = multipleState.value;
1318
+ const next = current.some((item) => samePeriod(item, start, view)) ? current.filter((item) => !samePeriod(item, start, view)) : [...current, start];
1319
+ if (args.required && !next.length) return;
1320
+ multipleState.set(next, event);
1321
+ return;
1322
+ }
1323
+ if (args.mode === "range") {
1324
+ const current = rangeState.value;
1325
+ let next;
1326
+ if (!current.from || current.to) next = { from: start };
1327
+ else if (comparePlain(start, periodStart(current.from, view)) < 0) next = {
1328
+ from: start,
1329
+ to: periodEnd(current.from, view)
1330
+ };
1331
+ else if (samePeriod(start, current.from, view) && !args.required) next = {};
1332
+ else next = {
1333
+ from: periodStart(current.from, view),
1334
+ to: end
1335
+ };
1336
+ rangeState.set(next, event);
1337
+ return;
1338
+ }
1339
+ const current = singleState.value;
1340
+ const next = current && samePeriod(current, start, view) && !args.required ? null : start;
1341
+ singleState.set(next, event);
1342
+ };
1343
+ const selectDay = (date, args, event) => {
1344
+ if (disabledAvailability?.day(plainToDate(date, args.timeZone))) return;
1345
+ selectPeriod(date, "day", args, event);
1346
+ };
1347
+ const changeView = (next, args, event) => {
1348
+ const target = clampView(next, args.minView);
1349
+ if (target === currentView) return;
1350
+ viewState.set(target, event);
1351
+ };
1352
+ const drillUp = (anchor, args, event) => {
1353
+ if (currentView === "day") {
1354
+ if (!samePeriod(anchor, visible, "month")) moveMonth(anchor, args, event);
1355
+ changeView("month", args, event);
1356
+ focusMonthCell(anchor, args);
1357
+ return;
1358
+ }
1359
+ if (currentView === "month") {
1360
+ changeView("year", args, event);
1361
+ focusYearCell(anchor.year, args);
1362
+ }
1363
+ };
1364
+ const selectMonthCell = (month, args, event) => {
1365
+ if (minimumView(args.minView) === "month") {
1366
+ selectPeriod(month, "month", args, event);
1367
+ return;
1368
+ }
1369
+ moveMonth(month, args, event);
1370
+ changeView("day", args, event);
1371
+ focusDay(monthStart(month), args);
1372
+ };
1373
+ const selectYearCell = (year, args, event) => {
1374
+ if (!canUseYearCell(year, args)) return;
1375
+ const month = navigableMonth(year, args);
1376
+ if (!month) return;
1377
+ if (minimumView(args.minView) === "year") {
1378
+ selectPeriod(month, "year", args, event);
1379
+ return;
1380
+ }
1381
+ moveMonth(month, args, event);
1382
+ changeView("month", args, event);
1383
+ focusMonthCell(month, args);
1384
+ };
1385
+ const onMove = (move, event) => {
1386
+ const button = event.target instanceof Element ? event.target.closest("button[data-day],button[data-month],button[data-year]") : null;
1387
+ const args = currentArgs;
1388
+ const month = button?.dataset.month ? parseMonthIso(button.dataset.month) : void 0;
1389
+ const year = button?.dataset.year ? Number(button.dataset.year) : void 0;
1390
+ if (month) {
1391
+ if ("cols" in move) focusMonthCell(addMonths(month, move.cols), args, event);
1392
+ else if ("rows" in move) focusMonthCell(addMonths(month, move.rows * 3), args, event);
1393
+ else if ("page" in move) focusMonthCell(addMonths(month, move.page * 12), args, event);
1394
+ else {
1395
+ const rowStart = Math.floor((month.month - 1) / 3) * 3 + 1;
1396
+ const candidates = monthOptions(month.year).filter((candidate) => canNavigateTo(candidate, args) && (move.extent === "all" || rowStart <= candidate.month && candidate.month <= rowStart + 2));
1397
+ const target = move.edge === "start" ? candidates[0] : candidates[candidates.length - 1];
1398
+ if (target) focusMonthCell(target, args, event);
1399
+ }
1400
+ return;
1401
+ }
1402
+ if (year != null && Number.isFinite(year)) {
1403
+ if ("cols" in move) focusYearCell(year + move.cols, args, event);
1404
+ else if ("rows" in move) focusYearCell(year + move.rows * 3, args, event);
1405
+ else if ("page" in move) focusYearCell(year + move.page * 12, args, event);
1406
+ else {
1407
+ const page = yearPage(visible, args, today(args.timeZone));
1408
+ const rowStart = page.start + Math.floor((year - page.start) / 3) * 3;
1409
+ const candidates = page.years.filter((candidate) => canUseYearCell(candidate, args) && (move.extent === "all" || rowStart <= candidate && candidate <= rowStart + 2));
1410
+ const target = move.edge === "start" ? candidates[0] : candidates[candidates.length - 1];
1411
+ if (target != null) focusYearCell(target, args, event);
1412
+ }
1413
+ return;
1414
+ }
1415
+ const day = button?.dataset.day ? parseIso(button.dataset.day) : void 0;
1416
+ if (!day) return;
1417
+ if ("cols" in move) {
1418
+ focusDay(addDays(day, move.cols), args);
1419
+ return;
1420
+ }
1421
+ if ("rows" in move) {
1422
+ focusDay(addDays(day, move.rows * 7), args);
1423
+ return;
1424
+ }
1425
+ if ("page" in move) {
1426
+ const count = move.page * (move.large ? 12 : 1);
1427
+ const target = addMonths(day, count);
1428
+ if (!canNavigateTo(monthStart(target), args)) return;
1429
+ moveMonth(addMonths(visible, count), args, event);
1430
+ focusDay(target, args);
1431
+ return;
1432
+ }
1433
+ if (move.extent === "row") {
1434
+ const start = startOfWeek(day, args.weekStartsOn ?? 0);
1435
+ focusDay(move.edge === "start" ? start : addDays(start, 6), args);
1436
+ return;
1437
+ }
1438
+ const start = monthStart(day);
1439
+ focusDay(move.edge === "start" ? start : {
1440
+ ...start,
1441
+ day: daysInMonth(start)
1442
+ }, args);
1443
+ };
1444
+ const nav = grid(this, {
1445
+ rtl: () => currentArgs.dir === "rtl",
1446
+ onMove
1447
+ });
1448
+ const onCellKeydown = (event) => {
1449
+ if (event.key !== "Escape" || viewRank[currentView] <= viewRank[minimumView(currentArgs.minView)]) {
1450
+ nav.handle(event);
1451
+ return;
1452
+ }
1453
+ event.preventDefault();
1454
+ event.stopPropagation();
1455
+ if (currentView === "year") {
1456
+ changeView("month", currentArgs, event);
1457
+ focusMonthCell(visible, currentArgs);
1458
+ return;
1459
+ }
1460
+ changeView("day", currentArgs, event);
1461
+ focusDay(visible, currentArgs);
1462
+ };
1463
+ for (const args of this) {
1464
+ currentArgs = args;
1465
+ syncAvailability(args);
1466
+ monthState.sync(args.month ? monthStart(dateToPlain(args.month, args.timeZone)) : void 0);
1467
+ viewState.sync(args.view === void 0 ? void 0 : clampView(args.view, args.minView));
1468
+ const clampedView = clampView(viewState.value, args.minView);
1469
+ if (!viewState.controlled && clampedView !== viewState.value) viewState.init(clampedView);
1470
+ singleState.sync(args.mode === "multiple" || args.mode === "range" || args.selected === void 0 ? void 0 : args.selected && dateToPlain(args.selected, args.timeZone));
1471
+ multipleState.sync(args.mode === "multiple" && args.selected !== void 0 ? toPlainArray(args.selected, args.timeZone) : void 0);
1472
+ rangeState.sync(args.mode === "range" && args.selected !== void 0 ? toRangePlain(args.selected ?? void 0, args.timeZone) : void 0);
1473
+ const now = today(args.timeZone);
1474
+ visible = monthState.value;
1475
+ currentView = clampedView;
1476
+ if (renderedView !== currentView && domReady) {
1477
+ const active = document.activeElement;
1478
+ if (active instanceof HTMLElement && this.contains(active)) if (currentView === "day") focusDay(visible, args);
1479
+ else if (currentView === "month") focusMonthCell(visible, args);
1480
+ else focusYearCell(visible.year, args);
1481
+ }
1482
+ renderedView = currentView;
1483
+ const weekStartsOn = args.weekStartsOn ?? 0;
1484
+ const count = Math.max(1, args.numberOfMonths ?? 1);
1485
+ const locale = localeCode(args.locale);
1486
+ const formats = {
1487
+ ...defaultFormatters,
1488
+ ...args.formatters ?? {}
1489
+ };
1490
+ const shown = months(visible, count, Boolean(args.fixedWeeks), weekStartsOn);
1491
+ const single = singleState.value;
1492
+ const multiple = multipleState.value;
1493
+ const range = rangeState.value;
1494
+ const years = yearRange(visible, args, now);
1495
+ const dayColumns = gridTemplate(Boolean(args.showWeekNumber));
1496
+ const captionLayout = args.captionLayout ?? "button";
1497
+ const MonthDropdown = args.monthDropdown ?? CalendarDropdown;
1498
+ const YearDropdown = args.yearDropdown ?? CalendarDropdown;
1499
+ const page = yearPage(visible, args, now);
1500
+ const periodFlags = (date, view) => {
1501
+ const range_start = args.mode === "range" && Boolean(range.from && samePeriod(range.from, date, view));
1502
+ const range_end = args.mode === "range" && Boolean(range.to && samePeriod(range.to, date, view));
1503
+ const range_middle = args.mode === "range" && periodIntersects(range, date, view) && !range_start && !range_end;
1504
+ return {
1505
+ range_end,
1506
+ range_middle,
1507
+ range_start,
1508
+ selected: args.mode === "multiple" ? multiple.some((item) => samePeriod(item, date, view)) : args.mode === "range" ? range_start || range_end || range_middle : Boolean(single && samePeriod(single, date, view))
1509
+ };
1510
+ };
1511
+ const canPreviousView = currentView === "day" ? canNavigateTo(addMonths(visible, -1), args) : currentView === "month" ? canNavigateYear(visible.year - 1, visible, args, now) : page.start > page.allowed[0];
1512
+ const canNextView = currentView === "day" ? canNavigateTo(addMonths(visible, count), args) : currentView === "month" ? canNavigateYear(visible.year + 1, visible, args, now) : page.start + 12 <= page.allowed[page.allowed.length - 1];
1513
+ const navigateView = (direction, event) => {
1514
+ if (currentView === "day") {
1515
+ moveMonth(addMonths(visible, direction < 0 ? -1 : count), args, event);
1516
+ return;
1517
+ }
1518
+ const month = navigableMonth(visible.year + direction * (currentView === "month" ? 1 : 12), args);
1519
+ if (month) moveMonth(month, args, event);
1520
+ };
1521
+ const previousLabel = args.previousMonthLabel ?? (currentView === "day" ? "Previous month" : currentView === "month" ? "Previous year" : "Previous 12 years");
1522
+ const nextLabel = args.nextMonthLabel ?? (currentView === "day" ? "Next month" : currentView === "month" ? "Next year" : "Next 12 years");
1523
+ yield /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
1524
+ class: args.classNames?.months,
1525
+ "data-slot": "calendar-months",
1526
+ children: currentView === "day" ? shown.map(({ month: item, weeks }, monthIndex) => {
1527
+ const monthDate = plainToDate(item, args.timeZone);
1528
+ const showMonthDropdown = captionLayout === "dropdown" || captionLayout === "dropdown-months";
1529
+ const showYearDropdown = captionLayout === "dropdown" || captionLayout === "dropdown-years";
1530
+ const firstMonth = monthIndex === 0;
1531
+ const lastMonth = monthIndex === shown.length - 1;
1532
+ return /* @__PURE__ */ jsxs("div", {
1533
+ class: args.classNames?.month,
1534
+ "data-month": iso(item),
1535
+ "data-slot": "calendar-month",
1536
+ children: [/* @__PURE__ */ jsxs("div", {
1537
+ class: args.classNames?.caption,
1538
+ "data-slot": "calendar-caption",
1539
+ children: [
1540
+ firstMonth ? /* @__PURE__ */ jsx("button", {
1541
+ "aria-disabled": canPreviousView ? void 0 : "true",
1542
+ "aria-label": previousLabel,
1543
+ class: args.navButtonClass,
1544
+ "data-slot": "calendar-previous",
1545
+ disabled: !canPreviousView,
1546
+ type: "button",
1547
+ "set:onclick": (event) => navigateView(-1, event),
1548
+ children: args.previousIcon ?? /* @__PURE__ */ jsx("span", {
1549
+ "aria-hidden": "true",
1550
+ class: args.previousIconClass,
1551
+ "data-slot": "calendar-previous-icon"
1552
+ })
1553
+ }) : /* @__PURE__ */ jsx("span", {
1554
+ "aria-hidden": "true",
1555
+ class: args.navSpacerClass,
1556
+ "data-slot": "calendar-nav-spacer"
1557
+ }),
1558
+ captionLayout === "button" ? /* @__PURE__ */ jsx("button", {
1559
+ class: args.captionLabelClass,
1560
+ "data-slot": "calendar-view-trigger",
1561
+ type: "button",
1562
+ "set:onclick": (event) => drillUp(item, args, event),
1563
+ children: formats.monthCaption(monthDate, locale, args.timeZone)
1564
+ }) : captionLayout === "label" ? /* @__PURE__ */ jsx("div", {
1565
+ class: args.captionLabelClass,
1566
+ "data-slot": "calendar-caption-label",
1567
+ children: formats.monthCaption(monthDate, locale, args.timeZone)
1568
+ }) : /* @__PURE__ */ jsxs("div", {
1569
+ class: args.classNames?.dropdowns,
1570
+ "data-slot": "calendar-dropdowns",
1571
+ children: [showMonthDropdown ? /* @__PURE__ */ jsx(MonthDropdown, {
1572
+ label: args.monthSelectLabel ?? "Month",
1573
+ options: monthOptions(item.year).map((option) => ({
1574
+ label: formats.monthDropdown(plainToDate(option, args.timeZone), locale, args.timeZone),
1575
+ value: String(option.month)
1576
+ })),
1577
+ onValueChange: (value, event) => moveMonth({
1578
+ ...item,
1579
+ month: Number(value)
1580
+ }, args, event),
1581
+ value: String(item.month)
1582
+ }) : /* @__PURE__ */ jsx("div", {
1583
+ class: args.monthLabelClass,
1584
+ "data-slot": "calendar-month-label",
1585
+ children: formatter(locale, "monthLabel", args.timeZone).format(monthDate)
1586
+ }), showYearDropdown ? /* @__PURE__ */ jsx(YearDropdown, {
1587
+ label: args.yearSelectLabel ?? "Year",
1588
+ onValueChange: (value, event) => moveMonth({
1589
+ ...item,
1590
+ year: Number(value)
1591
+ }, args, event),
1592
+ options: years.map((year) => ({
1593
+ label: year,
1594
+ value: String(year)
1595
+ })),
1596
+ value: String(item.year)
1597
+ }) : /* @__PURE__ */ jsx("div", {
1598
+ class: args.yearLabelClass,
1599
+ "data-slot": "calendar-year-label",
1600
+ children: item.year
1601
+ })]
1602
+ }),
1603
+ lastMonth ? /* @__PURE__ */ jsx("button", {
1604
+ "aria-disabled": canNextView ? void 0 : "true",
1605
+ "aria-label": nextLabel,
1606
+ class: args.navButtonClass,
1607
+ "data-slot": "calendar-next",
1608
+ disabled: !canNextView,
1609
+ type: "button",
1610
+ "set:onclick": (event) => navigateView(1, event),
1611
+ children: args.nextIcon ?? /* @__PURE__ */ jsx("span", {
1612
+ "aria-hidden": "true",
1613
+ class: args.nextIconClass,
1614
+ "data-slot": "calendar-next-icon"
1615
+ })
1616
+ }) : /* @__PURE__ */ jsx("span", {
1617
+ "aria-hidden": "true",
1618
+ class: args.navSpacerClass,
1619
+ "data-slot": "calendar-nav-spacer"
1620
+ })
1621
+ ]
1622
+ }), /* @__PURE__ */ jsxs("div", {
1623
+ class: args.classNames?.grid,
1624
+ "data-slot": "calendar-grid",
1625
+ role: "grid",
1626
+ "aria-label": formats.monthCaption(monthDate, locale, args.timeZone),
1627
+ children: [/* @__PURE__ */ jsxs("div", {
1628
+ class: args.classNames?.head,
1629
+ "data-slot": "calendar-weekdays",
1630
+ role: "row",
1631
+ style: dayColumns,
1632
+ children: [args.showWeekNumber && /* @__PURE__ */ jsx("div", {
1633
+ "aria-hidden": "true",
1634
+ class: args.classNames?.week_number,
1635
+ "data-slot": "calendar-week-number-header"
1636
+ }), Array.from({ length: 7 }, (_, index) => addDays({
1637
+ year: 2026,
1638
+ month: 7,
1639
+ day: 5
1640
+ }, weekStartsOn + index)).map((day, index) => /* @__PURE__ */ jsx("div", {
1641
+ class: args.classNames?.weekday,
1642
+ "data-slot": "calendar-weekday",
1643
+ role: "columnheader",
1644
+ children: formats.weekday(plainToDate(day, args.timeZone), locale, args.timeZone)
1645
+ }, index))]
1646
+ }), weeks.map((week, row) => /* @__PURE__ */ jsxs("div", {
1647
+ class: args.classNames?.week,
1648
+ "data-slot": "calendar-week",
1649
+ role: "row",
1650
+ style: dayColumns,
1651
+ children: [args.showWeekNumber && /* @__PURE__ */ jsx("div", {
1652
+ class: args.classNames?.week_number,
1653
+ "data-slot": "calendar-week-number",
1654
+ role: "rowheader",
1655
+ children: formats.weekNumber(weekNumber(week[0]), locale)
1656
+ }), week.map((day) => {
1657
+ const outside = day.month !== item.month || day.year !== item.year;
1658
+ const date = plainToDate(day, args.timeZone);
1659
+ const disabled = outside && args.showOutsideDays === false ? true : Boolean(disabledAvailability?.day(date));
1660
+ const unavailable = Boolean(unavailableAvailability?.day(date));
1661
+ const range_start = !outside && args.mode === "range" && Boolean(range.from && samePlain(range.from, day));
1662
+ const range_end = !outside && args.mode === "range" && Boolean(range.to && samePlain(range.to, day));
1663
+ const rawRangeMiddle = !outside && args.mode === "range" && rangeMiddle(range, day);
1664
+ const rangeGap = rawRangeMiddle && unavailable && Boolean(args.allowNonContiguous);
1665
+ const selected = !rangeGap && !outside && (args.mode === "multiple" ? plainIn(day, multiple) : args.mode === "range" ? Boolean(range.from && samePlain(range.from, day) || range.to && samePlain(range.to, day) || rangeContains(range, day)) : Boolean(single && samePlain(single, day)));
1666
+ const range_middle = rawRangeMiddle && !rangeGap;
1667
+ const current = samePlain(day, now);
1668
+ const modifierList = modifierNames(modifierAvailability, date);
1669
+ const modifiers = {
1670
+ disabled,
1671
+ outside,
1672
+ range_end,
1673
+ range_middle,
1674
+ range_start,
1675
+ selected,
1676
+ today: current,
1677
+ unavailable
1678
+ };
1679
+ const hidden = outside && args.showOutsideDays === false;
1680
+ const state = {
1681
+ date,
1682
+ hidden,
1683
+ modifierNames: modifierList,
1684
+ modifiers,
1685
+ range: args.mode === "range" && Boolean(range.from && range.to)
1686
+ };
1687
+ return /* @__PURE__ */ jsx("div", {
1688
+ class: args.dayClassName?.(state) ?? args.classNames?.day,
1689
+ "data-disabled": disabled ? "true" : void 0,
1690
+ "data-outside": outside ? "true" : void 0,
1691
+ "data-selected": selected ? "true" : void 0,
1692
+ "data-slot": "calendar-day",
1693
+ "data-today": current ? "true" : void 0,
1694
+ "data-unavailable": unavailable ? "true" : void 0,
1695
+ role: "gridcell",
1696
+ ...modifierAttributes(modifierList),
1697
+ children: hidden ? /* @__PURE__ */ jsx("span", {
1698
+ "aria-hidden": "true",
1699
+ class: args.hiddenDayClass,
1700
+ children: day.day
1701
+ }) : /* @__PURE__ */ jsx(CalendarDayButton, {
1702
+ "aria-label": formats.fullDate(date, locale, args.timeZone),
1703
+ class: args.dayButtonClass,
1704
+ date,
1705
+ day: args.renderDay?.(date, modifiers) ?? formats.day(date, locale, args.timeZone),
1706
+ timeZone: args.timeZone,
1707
+ disabled: disabled || outside,
1708
+ modifiers,
1709
+ "set:onclick": (event) => selectDay(day, args, event),
1710
+ "set:onkeydown": onCellKeydown
1711
+ })
1712
+ }, iso(day));
1713
+ })]
1714
+ }, row))]
1715
+ })]
1716
+ }, iso(item));
1717
+ }) : /* @__PURE__ */ jsxs("div", {
1718
+ class: args.classNames?.month,
1719
+ children: [/* @__PURE__ */ jsxs("div", {
1720
+ class: args.classNames?.caption,
1721
+ "data-slot": "calendar-caption",
1722
+ children: [
1723
+ /* @__PURE__ */ jsx("button", {
1724
+ "aria-disabled": canPreviousView ? void 0 : "true",
1725
+ "aria-label": previousLabel,
1726
+ class: args.navButtonClass,
1727
+ "data-slot": "calendar-previous",
1728
+ disabled: !canPreviousView,
1729
+ type: "button",
1730
+ "set:onclick": (event) => navigateView(-1, event),
1731
+ children: args.previousIcon ?? /* @__PURE__ */ jsx("span", {
1732
+ "aria-hidden": "true",
1733
+ class: args.previousIconClass,
1734
+ "data-slot": "calendar-previous-icon"
1735
+ })
1736
+ }),
1737
+ captionLayout === "button" ? /* @__PURE__ */ jsx("button", {
1738
+ "aria-disabled": currentView === "year" ? "true" : void 0,
1739
+ class: args.captionLabelClass,
1740
+ "data-slot": "calendar-view-trigger",
1741
+ disabled: currentView === "year",
1742
+ type: "button",
1743
+ "set:onclick": (event) => drillUp(visible, args, event),
1744
+ children: currentView === "month" ? visible.year : `${page.start}–${page.start + 11}`
1745
+ }) : /* @__PURE__ */ jsx("div", {
1746
+ class: args.captionLabelClass,
1747
+ "data-slot": "calendar-caption-label",
1748
+ children: currentView === "month" ? visible.year : `${page.start}–${page.start + 11}`
1749
+ }),
1750
+ /* @__PURE__ */ jsx("button", {
1751
+ "aria-disabled": canNextView ? void 0 : "true",
1752
+ "aria-label": nextLabel,
1753
+ class: args.navButtonClass,
1754
+ "data-slot": "calendar-next",
1755
+ disabled: !canNextView,
1756
+ type: "button",
1757
+ "set:onclick": (event) => navigateView(1, event),
1758
+ children: args.nextIcon ?? /* @__PURE__ */ jsx("span", {
1759
+ "aria-hidden": "true",
1760
+ class: args.nextIconClass,
1761
+ "data-slot": "calendar-next-icon"
1762
+ })
1763
+ })
1764
+ ]
1765
+ }), currentView === "month" ? /* @__PURE__ */ jsx("div", {
1766
+ "aria-label": String(visible.year),
1767
+ class: args.classNames?.month_view,
1768
+ "data-slot": "calendar-month-view",
1769
+ role: "grid",
1770
+ style: "display:grid;grid-template-columns:repeat(3,minmax(0,1fr))",
1771
+ children: monthOptions(visible.year).map((month) => {
1772
+ const disabled = !canNavigateTo(month, args);
1773
+ const flags = periodFlags(month, "month");
1774
+ const current = samePeriod(month, now, "month");
1775
+ const value = monthIso(month);
1776
+ return /* @__PURE__ */ jsx("div", {
1777
+ role: "gridcell",
1778
+ children: /* @__PURE__ */ jsx("button", {
1779
+ "aria-disabled": disabled ? "true" : void 0,
1780
+ "aria-label": formats.monthCaption(plainToDate(month, args.timeZone), locale, args.timeZone),
1781
+ class: args.classNames?.month_cell,
1782
+ "data-disabled": disabled ? "true" : void 0,
1783
+ "data-month": value,
1784
+ "data-range-end": flags.range_end ? "true" : void 0,
1785
+ "data-range-middle": flags.range_middle ? "true" : void 0,
1786
+ "data-range-start": flags.range_start ? "true" : void 0,
1787
+ "data-selected": flags.selected ? "true" : void 0,
1788
+ "data-slot": "calendar-month-cell",
1789
+ "data-today": current ? "true" : void 0,
1790
+ disabled,
1791
+ type: "button",
1792
+ "set:onclick": (event) => selectMonthCell(month, args, event),
1793
+ "set:onkeydown": onCellKeydown,
1794
+ children: formatter(locale, "monthLabel", args.timeZone).format(plainToDate(month, args.timeZone))
1795
+ })
1796
+ }, value);
1797
+ })
1798
+ }) : /* @__PURE__ */ jsx("div", {
1799
+ "aria-label": `${page.start}–${page.start + 11}`,
1800
+ class: args.classNames?.year_view,
1801
+ "data-slot": "calendar-year-view",
1802
+ role: "grid",
1803
+ style: "display:grid;grid-template-columns:repeat(3,minmax(0,1fr))",
1804
+ children: page.years.map((year) => {
1805
+ const disabled = !canUseYearCell(year, args);
1806
+ const flags = periodFlags({
1807
+ day: 1,
1808
+ month: 1,
1809
+ year
1810
+ }, "year");
1811
+ const current = year === now.year;
1812
+ return /* @__PURE__ */ jsx("div", {
1813
+ role: "gridcell",
1814
+ children: /* @__PURE__ */ jsx("button", {
1815
+ "aria-disabled": disabled ? "true" : void 0,
1816
+ class: args.classNames?.year_cell,
1817
+ "data-disabled": disabled ? "true" : void 0,
1818
+ "data-range-end": flags.range_end ? "true" : void 0,
1819
+ "data-range-middle": flags.range_middle ? "true" : void 0,
1820
+ "data-range-start": flags.range_start ? "true" : void 0,
1821
+ "data-selected": flags.selected ? "true" : void 0,
1822
+ "data-slot": "calendar-year-cell",
1823
+ "data-today": current ? "true" : void 0,
1824
+ "data-year": String(year),
1825
+ disabled,
1826
+ type: "button",
1827
+ "set:onclick": (event) => selectYearCell(year, args, event),
1828
+ "set:onkeydown": onCellKeydown,
1829
+ children: year
1830
+ })
1831
+ }, year);
1832
+ })
1833
+ })]
1834
+ }, `${currentView}-${currentView === "year" ? page.start : visible.year}`)
1835
+ }) });
1836
+ }
1837
+ };
1838
+ /** Unstyled calendar with single, multiple, and range selection. */
1839
+ var Calendar = ({ allowNonContiguous, captionLabelClass, captionLayout, class: classes, classNames, dayButtonClass, dayClassName, defaultMonth, defaultSelected, defaultView, disabled, endMonth, fixedWeeks, formatters, fromYear, hiddenDayClass, locale, minView, month, monthDropdown, monthLabelClass, monthSelectLabel, nextMonthLabel, previousMonthLabel, yearSelectLabel, modifiers, navButtonClass, navSpacerClass, nextIcon, nextIconClass, numberOfMonths, onMonthChange, onViewChange, previousIcon, previousIconClass, renderDay, required, showOutsideDays = true, showWeekNumber, startMonth, timeZone, toYear, unavailable, view, weekStartsOn, yearDropdown, yearLabelClass, ...attrs }) => {
1840
+ const { dir, mode, onSelect, selected, ...rest } = attrs;
1841
+ const resolvedDir = dir ?? DirectionContext();
1842
+ return /* @__PURE__ */ jsx(CalendarRoot, {
1843
+ allowNonContiguous,
1844
+ captionLabelClass,
1845
+ captionLayout,
1846
+ classNames,
1847
+ dayButtonClass,
1848
+ dayClassName,
1849
+ defaultMonth,
1850
+ defaultSelected,
1851
+ defaultView,
1852
+ disabled,
1853
+ dir: resolvedDir,
1854
+ endMonth,
1855
+ fixedWeeks,
1856
+ formatters,
1857
+ fromYear,
1858
+ hiddenDayClass,
1859
+ locale,
1860
+ minView,
1861
+ mode,
1862
+ month,
1863
+ monthDropdown,
1864
+ monthLabelClass,
1865
+ monthSelectLabel,
1866
+ nextMonthLabel,
1867
+ previousMonthLabel,
1868
+ yearSelectLabel,
1869
+ modifiers,
1870
+ navButtonClass,
1871
+ navSpacerClass,
1872
+ nextIcon,
1873
+ nextIconClass,
1874
+ numberOfMonths,
1875
+ onMonthChange,
1876
+ onSelect,
1877
+ onViewChange,
1878
+ previousIcon,
1879
+ previousIconClass,
1880
+ renderDay,
1881
+ required,
1882
+ selected,
1883
+ showOutsideDays,
1884
+ showWeekNumber,
1885
+ startMonth,
1886
+ timeZone,
1887
+ toYear,
1888
+ unavailable,
1889
+ view,
1890
+ weekStartsOn,
1891
+ yearDropdown,
1892
+ yearLabelClass,
1893
+ ...statefulRootAttrs(rest),
1894
+ "attr:class": classes,
1895
+ "attr:data-slot": "calendar",
1896
+ "attr:dir": resolvedDir
1897
+ });
1898
+ };
1899
+ /** Unstyled day button used by Calendar. */
1900
+ var CalendarDayButton = ({ class: classes, date, day, modifiers, timeZone, type = "button", ...attrs }) => {
1901
+ const value = dateToPlain(date, timeZone);
1902
+ const single = modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle;
1903
+ return /* @__PURE__ */ jsx("button", {
1904
+ ...attrs,
1905
+ "aria-disabled": modifiers.unavailable ? "true" : attrs["aria-disabled"],
1906
+ class: classes,
1907
+ "data-day": iso(value),
1908
+ "data-disabled": modifiers.disabled ? "true" : void 0,
1909
+ "data-outside": modifiers.outside ? "true" : void 0,
1910
+ "data-range-end": modifiers.range_end ? "true" : void 0,
1911
+ "data-range-middle": modifiers.range_middle ? "true" : void 0,
1912
+ "data-range-start": modifiers.range_start ? "true" : void 0,
1913
+ "data-selected-single": single ? "true" : void 0,
1914
+ "data-slot": "calendar-day-button",
1915
+ "data-state": modifiers.selected ? "selected" : "unselected",
1916
+ "data-today": modifiers.today ? "true" : void 0,
1917
+ "data-unavailable": modifiers.unavailable ? "true" : void 0,
1918
+ type,
1919
+ children: day
1920
+ });
1921
+ };
1922
+ //#endregion
1923
+ export { field as a, isReversed as c, defaultMessage as i, timeRun as l, CalendarDayButton as n, formatValue as o, compile as r, fromISO as s, Calendar as t, unitLabel as u };