@salyra-ui/date-time-picker 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.
@@ -0,0 +1,1554 @@
1
+ "use strict";
2
+ var SalyraDateTimePicker = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // packages/date-time-picker/src/vanilla.ts
22
+ var vanilla_exports = {};
23
+ __export(vanilla_exports, {
24
+ addDays: () => addDays,
25
+ addMonths: () => addMonths,
26
+ attribute: () => attribute,
27
+ bindCalendar: () => bindCalendar,
28
+ bindDateField: () => bindDateField,
29
+ bindDayTrigger: () => bindDayTrigger,
30
+ bindMonthScroller: () => bindMonthScroller,
31
+ bindPickerForm: () => bindPickerForm,
32
+ bindPickerPopover: () => bindPickerPopover,
33
+ bindTimeSegment: () => bindTimeSegment,
34
+ calendarMarkup: () => calendarMarkup,
35
+ createCalendarEngine: () => createCalendarEngine,
36
+ createDateTimePickerStore: () => createDateTimePickerStore,
37
+ createPickerStore: () => createPickerStore2,
38
+ dateFormatter: () => dateFormatter,
39
+ dateString: () => dateString,
40
+ dayAttributes: () => dayAttributes,
41
+ dayNumber: () => dayNumber,
42
+ dayTriggerAttributes: () => dayTriggerAttributes,
43
+ daysInMonth: () => daysInMonth,
44
+ escapeHTML: () => escapeHTML,
45
+ formatDate: () => formatDate,
46
+ formatMonth: () => formatMonth,
47
+ fromDayNumber: () => fromDayNumber,
48
+ isRange: () => isRange,
49
+ leapYear: () => leapYear,
50
+ monthScrollerMarkup: () => monthScrollerMarkup,
51
+ mountDateTimePicker: () => mountDateTimePicker,
52
+ mountPicker: () => mountPicker2,
53
+ parseDate: () => parseDate,
54
+ pointFor: () => pointFor,
55
+ resolveZonedDateTime: () => resolveZonedDateTime,
56
+ shiftDateMonths: () => shiftDateMonths,
57
+ timeParts: () => timeParts,
58
+ timeSeconds: () => timeSeconds,
59
+ timeSegmentValue: () => timeSegmentValue,
60
+ timeSlots: () => timeSlots,
61
+ timeString: () => timeString,
62
+ today: () => today,
63
+ updateTime: () => updateTime,
64
+ utcDate: () => utcDate,
65
+ validateWeekStart: () => validateWeekStart,
66
+ weekNumber: () => weekNumber,
67
+ weekday: () => weekday,
68
+ weekdayLabels: () => weekdayLabels
69
+ });
70
+
71
+ // packages/calendar/src/core/calendar.ts
72
+ function leapYear(year) {
73
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
74
+ }
75
+ function daysInMonth(year, month) {
76
+ if (!Number.isInteger(year) || year < 1 || year > 9999 || !Number.isInteger(month) || month < 1 || month > 12)
77
+ throw new RangeError(
78
+ "Calendar supports Gregorian years 1\u20139999 and months 1\u201312"
79
+ );
80
+ return month === 2 ? leapYear(year) ? 29 : 28 : [4, 6, 9, 11].includes(month) ? 30 : 31;
81
+ }
82
+ function parseDate(value) {
83
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
84
+ if (!match) throw new RangeError("Use YYYY-MM-DD");
85
+ const [, y, m, d] = match;
86
+ const date = { year: Number(y), month: Number(m), day: Number(d) };
87
+ if (date.day < 1 || date.day > daysInMonth(date.year, date.month))
88
+ throw new RangeError("Invalid calendar date");
89
+ return date;
90
+ }
91
+ function dateString(date) {
92
+ if (!Number.isInteger(date.day) || date.day < 1 || date.day > daysInMonth(date.year, date.month))
93
+ throw new RangeError("Invalid calendar date");
94
+ return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
95
+ }
96
+ function dayNumber(input) {
97
+ const date = typeof input === "string" ? parseDate(input) : parseDate(dateString(input));
98
+ let y = date.year - (date.month <= 2 ? 1 : 0);
99
+ const era = Math.floor(y / 400), yo = y - era * 400;
100
+ const m = date.month + (date.month > 2 ? -3 : 9);
101
+ const doy = Math.floor((153 * m + 2) / 5) + date.day - 1;
102
+ return era * 146097 + yo * 365 + Math.floor(yo / 4) - Math.floor(yo / 100) + doy - 719468;
103
+ }
104
+ function fromDayNumber(n) {
105
+ if (!Number.isInteger(n))
106
+ throw new RangeError("Day number must be an integer");
107
+ const z = n + 719468, era = Math.floor(z / 146097), doe = z - era * 146097;
108
+ const yo = Math.floor(
109
+ (doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365
110
+ );
111
+ let y = yo + era * 400;
112
+ const doy = doe - (365 * yo + Math.floor(yo / 4) - Math.floor(yo / 100));
113
+ const mp = Math.floor((5 * doy + 2) / 153);
114
+ const day = doy - Math.floor((153 * mp + 2) / 5) + 1;
115
+ const month = mp + (mp < 10 ? 3 : -9);
116
+ y += month <= 2 ? 1 : 0;
117
+ const result = { year: y, month, day };
118
+ dateString(result);
119
+ return result;
120
+ }
121
+ function addDays(date, count) {
122
+ return dateString(fromDayNumber(dayNumber(date) + count));
123
+ }
124
+ function weekday(date) {
125
+ return ((dayNumber(date) + 4) % 7 + 7) % 7;
126
+ }
127
+ function addMonths(month, count) {
128
+ daysInMonth(month.year, month.month);
129
+ if (!Number.isInteger(count))
130
+ throw new RangeError("Month offset must be an integer");
131
+ const n = (month.year - 1) * 12 + month.month - 1 + count;
132
+ const next = {
133
+ year: Math.floor(n / 12) + 1,
134
+ month: (n % 12 + 12) % 12 + 1
135
+ };
136
+ daysInMonth(next.year, next.month);
137
+ return next;
138
+ }
139
+ function shiftDateMonths(value, count) {
140
+ const date = parseDate(value), next = addMonths(date, count);
141
+ return dateString({
142
+ ...next,
143
+ day: Math.min(date.day, daysInMonth(next.year, next.month))
144
+ });
145
+ }
146
+ function weekNumber(value) {
147
+ const n = dayNumber(value), dow = (weekday(value) + 6) % 7;
148
+ const thursday = fromDayNumber(n + 3 - dow);
149
+ const jan4 = `${String(thursday.year).padStart(4, "0")}-01-04`;
150
+ const first = dayNumber(jan4) - (weekday(jan4) + 6) % 7;
151
+ return { year: thursday.year, week: Math.floor((n - first) / 7) + 1 };
152
+ }
153
+ function validateWeekStart(value) {
154
+ if (!Number.isInteger(value) || value < 0 || value > 6)
155
+ throw new RangeError("weekStartsOn must be 0\u20136");
156
+ return value;
157
+ }
158
+ function createCalendarEngine(capacity = 48) {
159
+ if (!Number.isInteger(capacity) || capacity < 1)
160
+ throw new RangeError("Cache capacity must be positive");
161
+ const cache = /* @__PURE__ */ new Map();
162
+ return {
163
+ month(year, month, options = {}) {
164
+ daysInMonth(year, month);
165
+ const start = validateWeekStart(options.weekStartsOn ?? 1);
166
+ const key = `${year}:${month}:${start}:${!!options.fixedWeeks}:${options.outsideDays ?? "visible"}`;
167
+ const cached = cache.get(key);
168
+ if (cached) {
169
+ cache.delete(key);
170
+ cache.set(key, cached);
171
+ return cached;
172
+ }
173
+ const first = dateString({ year, month, day: 1 }), leading = (weekday(first) - start + 7) % 7;
174
+ const firstNumber = dayNumber(first), minNumber = dayNumber("0001-01-01"), maxNumber = dayNumber("9999-12-31");
175
+ const length = options.fixedWeeks ? 42 : Math.ceil((leading + daysInMonth(year, month)) / 7) * 7;
176
+ const weeks = [];
177
+ for (let row = 0; row < length / 7; row++) {
178
+ const cells = [];
179
+ for (let col = 0; col < 7; col++) {
180
+ const n = firstNumber - leading + row * 7 + col;
181
+ if (n < minNumber || n > maxNumber) {
182
+ cells.push(
183
+ Object.freeze({
184
+ date: "",
185
+ day: 0,
186
+ weekday: (start + col) % 7,
187
+ outside: true,
188
+ hidden: true
189
+ })
190
+ );
191
+ continue;
192
+ }
193
+ const date = fromDayNumber(n), outside = date.month !== month;
194
+ cells.push(
195
+ Object.freeze({
196
+ date: dateString(date),
197
+ day: date.day,
198
+ weekday: (start + col) % 7,
199
+ outside,
200
+ hidden: outside && options.outsideDays === "hidden"
201
+ })
202
+ );
203
+ }
204
+ weeks.push(Object.freeze(cells));
205
+ }
206
+ const result = Object.freeze({
207
+ year,
208
+ month,
209
+ weeks: Object.freeze(weeks)
210
+ });
211
+ cache.set(key, result);
212
+ if (cache.size > capacity) cache.delete(cache.keys().next().value);
213
+ return result;
214
+ },
215
+ clear() {
216
+ cache.clear();
217
+ },
218
+ get cacheSize() {
219
+ return cache.size;
220
+ }
221
+ };
222
+ }
223
+
224
+ // packages/calendar/src/core/format.ts
225
+ var formatters = /* @__PURE__ */ new Map();
226
+ function dateFormatter(locale, options) {
227
+ const key = JSON.stringify([locale, options]);
228
+ let f = formatters.get(key);
229
+ if (!f) {
230
+ f = new Intl.DateTimeFormat(locale, {
231
+ ...options,
232
+ calendar: "gregory",
233
+ timeZone: "UTC"
234
+ });
235
+ formatters.set(key, f);
236
+ if (formatters.size > 32)
237
+ formatters.delete(formatters.keys().next().value);
238
+ }
239
+ return f;
240
+ }
241
+ function utcDate(value) {
242
+ const { year, month, day } = parseDate(value);
243
+ const date = /* @__PURE__ */ new Date(0);
244
+ date.setUTCFullYear(year, month - 1, day);
245
+ date.setUTCHours(12, 0, 0, 0);
246
+ return date;
247
+ }
248
+ function formatDate(value, locale = "en-GB", options = { dateStyle: "long" }) {
249
+ return dateFormatter(locale, options).format(utcDate(value));
250
+ }
251
+ function formatMonth(month, locale = "en-GB") {
252
+ return formatDate(
253
+ `${String(month.year).padStart(4, "0")}-${String(month.month).padStart(2, "0")}-01`,
254
+ locale,
255
+ { month: "long", year: "numeric" }
256
+ );
257
+ }
258
+ function weekdayLabels(locale, weekStartsOn, width = "short") {
259
+ const formatter = dateFormatter(locale, { weekday: width });
260
+ return Array.from({ length: 7 }, (_, i) => {
261
+ const day = (weekStartsOn + i) % 7;
262
+ return {
263
+ weekday: day,
264
+ label: formatter.format(
265
+ utcDate(`2023-01-${String(day + 1).padStart(2, "0")}`)
266
+ )
267
+ };
268
+ });
269
+ }
270
+ function today(timeZone = "UTC", now = /* @__PURE__ */ new Date()) {
271
+ const p = new Intl.DateTimeFormat("en-CA", {
272
+ timeZone,
273
+ calendar: "gregory",
274
+ year: "numeric",
275
+ month: "2-digit",
276
+ day: "2-digit"
277
+ }).formatToParts(now);
278
+ const get = (type) => p.find((x) => x.type === type).value;
279
+ return `${get("year").padStart(4, "0")}-${get("month")}-${get("day")}`;
280
+ }
281
+
282
+ // packages/calendar/src/core/time.ts
283
+ function timeSeconds(value) {
284
+ const m = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
285
+ if (!m || Number(m[1]) > 23 || Number(m[2]) > 59 || Number(m[3] ?? 0) > 59)
286
+ throw new RangeError("Use HH:mm or HH:mm:ss");
287
+ return Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3] ?? 0);
288
+ }
289
+ function timeString(seconds, withSeconds = false) {
290
+ if (!Number.isInteger(seconds) || seconds < 0 || seconds > 86399)
291
+ throw new RangeError("Time must fit within a day");
292
+ const parts = [Math.floor(seconds / 3600), Math.floor(seconds / 60) % 60];
293
+ if (withSeconds) parts.push(seconds % 60);
294
+ return parts.map((n) => String(n).padStart(2, "0")).join(":");
295
+ }
296
+ function timeParts(value, hourCycle = 24) {
297
+ const seconds = timeSeconds(value), hour = Math.floor(seconds / 3600);
298
+ return {
299
+ hour: hourCycle === 12 ? hour % 12 || 12 : hour,
300
+ minute: Math.floor(seconds / 60) % 60,
301
+ second: seconds % 60,
302
+ period: hour < 12 ? "AM" : "PM"
303
+ };
304
+ }
305
+ function updateTime(value, part, next, hourCycle = 24, seconds = false) {
306
+ const p = timeParts(value, 24);
307
+ let h = p.hour, m = p.minute, s = p.second;
308
+ if (part === "period") {
309
+ if (next !== "AM" && next !== "PM") throw new RangeError("Use AM or PM");
310
+ h = h % 12 + (next === "PM" ? 12 : 0);
311
+ } else {
312
+ const n = Number(next), max = part === "hour" ? hourCycle === 12 ? 12 : 23 : 59, min = part === "hour" && hourCycle === 12 ? 1 : 0;
313
+ if (!Number.isInteger(n) || n < min || n > max)
314
+ throw new RangeError("Invalid time segment");
315
+ if (part === "hour")
316
+ h = hourCycle === 12 ? n % 12 + (p.period === "PM" ? 12 : 0) : n;
317
+ if (part === "minute") m = n;
318
+ if (part === "second") s = n;
319
+ }
320
+ return timeString(h * 3600 + m * 60 + s, seconds);
321
+ }
322
+ function timeSlots(stepMinutes = 30, min = "00:00", max = "23:59") {
323
+ if (!Number.isInteger(stepMinutes) || stepMinutes < 1 || stepMinutes > 1440)
324
+ throw new RangeError("Time step must be 1\u20131440 minutes");
325
+ const slots = [], first = timeSeconds(min), last = timeSeconds(max), seconds = min.length === 8 || max.length === 8;
326
+ if (first > last)
327
+ throw new RangeError("Time slot bounds must be ordered within one day");
328
+ for (let n = first; n <= last; n += stepMinutes * 60)
329
+ slots.push(timeString(n, seconds));
330
+ return Object.freeze(slots);
331
+ }
332
+ function timeSegmentValue(time, part, hourCycle = 24) {
333
+ return !time && part !== "period" ? "" : String(timeParts(time ?? "00:00", hourCycle)[part]);
334
+ }
335
+
336
+ // packages/calendar/src/core/store.ts
337
+ function isRange(value) {
338
+ return value !== null && "start" in value;
339
+ }
340
+ function pointFor(value, endpoint = "start") {
341
+ return isRange(value) ? value[endpoint] : endpoint === "start" ? value : null;
342
+ }
343
+ var eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
344
+ function resolve(options) {
345
+ if (options.kind && !["date", "time", "datetime"].includes(options.kind))
346
+ throw new RangeError("Invalid picker kind");
347
+ if (options.selection && !["single", "range", "none"].includes(options.selection))
348
+ throw new RangeError("Invalid selection");
349
+ if (options.hourCycle !== void 0 && options.hourCycle !== 12 && options.hourCycle !== 24)
350
+ throw new RangeError("hourCycle must be 12 or 24");
351
+ const referenceDate = options.referenceDate ?? today(options.timeZone ?? "UTC");
352
+ parseDate(referenceDate);
353
+ const months = options.months ?? 1, minuteStep = options.minuteStep ?? 1;
354
+ if (!Number.isInteger(months) || months < 1 || months > 12)
355
+ throw new RangeError("months must be 1\u201312");
356
+ if (!Number.isInteger(minuteStep) || minuteStep < 1 || minuteStep > 60)
357
+ throw new RangeError("minuteStep must be 1\u201360");
358
+ if (options.minDate) parseDate(options.minDate);
359
+ if (options.maxDate) parseDate(options.maxDate);
360
+ if (options.minDate && options.maxDate && options.minDate > options.maxDate)
361
+ throw new RangeError("minDate must precede maxDate");
362
+ if (options.minTime) timeSeconds(options.minTime);
363
+ if (options.maxTime) timeSeconds(options.maxTime);
364
+ if (options.minTime && options.maxTime && timeSeconds(options.minTime) > timeSeconds(options.maxTime))
365
+ throw new RangeError("minTime must precede maxTime");
366
+ for (const limit of [options.minDuration, options.maxDuration])
367
+ if (limit !== void 0 && (!Number.isFinite(limit) || limit < 0))
368
+ throw new RangeError("Duration must be nonnegative");
369
+ if (options.minDuration !== void 0 && options.maxDuration !== void 0 && options.minDuration > options.maxDuration)
370
+ throw new RangeError("minDuration must not exceed maxDuration");
371
+ const weekend = Object.freeze([...options.weekend ?? [0, 6]]);
372
+ weekend.forEach(validateWeekStart);
373
+ const timeZone = options.timeZone ?? "UTC";
374
+ new Intl.DateTimeFormat("en", { timeZone });
375
+ return Object.freeze({
376
+ ...options,
377
+ kind: options.kind ?? "date",
378
+ selection: options.selection ?? "single",
379
+ referenceDate,
380
+ locale: options.locale ?? "en-GB",
381
+ timeZone,
382
+ weekStartsOn: validateWeekStart(options.weekStartsOn ?? 1),
383
+ months,
384
+ weekend,
385
+ hourCycle: options.hourCycle ?? 24,
386
+ minuteStep
387
+ });
388
+ }
389
+ function freezePoint(point, options) {
390
+ if (!point) return null;
391
+ const p = {};
392
+ if (options.kind !== "time") {
393
+ if (!point.date) throw new RangeError("Date is required");
394
+ parseDate(point.date);
395
+ p.date = point.date;
396
+ } else if (point.date) {
397
+ parseDate(point.date);
398
+ p.date = point.date;
399
+ }
400
+ if (options.kind !== "date") {
401
+ if (point.time) {
402
+ timeSeconds(point.time);
403
+ p.time = point.time;
404
+ }
405
+ if (point.dayOffset !== void 0) {
406
+ if (p.date) throw new RangeError("Use date or dayOffset, not both");
407
+ if (!Number.isInteger(point.dayOffset) || point.dayOffset < 0)
408
+ throw new RangeError("dayOffset must be nonnegative");
409
+ p.dayOffset = point.dayOffset;
410
+ }
411
+ }
412
+ return Object.freeze(p);
413
+ }
414
+ function normalize(value, options) {
415
+ if (!value)
416
+ return options.selection === "range" ? Object.freeze({ start: null, end: null }) : null;
417
+ if (options.selection === "range") {
418
+ if (!isRange(value)) throw new RangeError("A range requires start and end");
419
+ return Object.freeze({
420
+ start: freezePoint(value.start, options),
421
+ end: freezePoint(value.end, options)
422
+ });
423
+ }
424
+ if (isRange(value))
425
+ throw new RangeError("Single selection expects one point");
426
+ return freezePoint(value, options);
427
+ }
428
+ function position(point) {
429
+ return (point.date ? dayNumber(point.date) : point.dayOffset ?? 0) * 86400 + (point.time ? timeSeconds(point.time) : 0);
430
+ }
431
+ function createPickerStore(input = {}) {
432
+ let options = resolve(input);
433
+ const initial = normalize(
434
+ input.value === void 0 ? input.defaultValue ?? null : input.value,
435
+ options
436
+ );
437
+ const engine = createCalendarEngine();
438
+ const month = input.month ?? parseDate(pointFor(initial)?.date ?? options.referenceDate);
439
+ addMonths(month, options.months - 1);
440
+ let snapshot = Object.freeze({
441
+ options,
442
+ value: initial,
443
+ draft: initial,
444
+ visibleMonth: Object.freeze({ year: month.year, month: month.month }),
445
+ focusedDate: pointFor(initial)?.date ?? options.referenceDate,
446
+ hoverDate: null,
447
+ activeEndpoint: "start",
448
+ open: false,
449
+ error: null
450
+ });
451
+ const listeners = /* @__PURE__ */ new Set(), valueListeners = /* @__PURE__ */ new Set();
452
+ const update = (patch) => {
453
+ const previous = snapshot;
454
+ if (Object.entries(patch).every(
455
+ ([k, v]) => k === "options" ? previous.options === v : eq(previous[k], v)
456
+ ))
457
+ return;
458
+ snapshot = Object.freeze({ ...snapshot, ...patch });
459
+ listeners.forEach((fn) => fn());
460
+ if (!eq(previous.value, snapshot.value)) {
461
+ valueListeners.forEach((fn) => fn(snapshot.value));
462
+ options.onValueChange?.(snapshot.value);
463
+ }
464
+ };
465
+ const unavailable = (date) => !!(options.minDate && date < options.minDate || options.maxDate && date > options.maxDate || options.isDateUnavailable?.(date));
466
+ function validation(value, complete = false) {
467
+ const points = isRange(value) ? [value.start, value.end] : [value];
468
+ if (points.every((point) => point === null)) return null;
469
+ for (const p of points) {
470
+ if (!p) {
471
+ if (complete) return "Choose a complete value";
472
+ continue;
473
+ }
474
+ if (p.date && unavailable(p.date)) return "This date is unavailable";
475
+ if (options.kind !== "date") {
476
+ if (!p.time) {
477
+ if (complete) return "Choose a time";
478
+ continue;
479
+ }
480
+ const t = timeSeconds(p.time);
481
+ if (options.minTime && t < timeSeconds(options.minTime) || options.maxTime && t > timeSeconds(options.maxTime) || options.isTimeUnavailable?.(p.time, p))
482
+ return "This time is unavailable";
483
+ }
484
+ }
485
+ if (isRange(value) && value.start && value.end && (options.kind === "date" || value.start.time && value.end.time)) {
486
+ if (options.kind === "time" && !!value.start.date !== !!value.end.date)
487
+ return "Both endpoints need dates, or neither";
488
+ const span = position(value.end) - position(value.start);
489
+ if (span < 0) return "The end must follow the start";
490
+ const duration = options.kind === "date" ? span / 86400 + 1 : span;
491
+ if (options.minDuration !== void 0 && duration < options.minDuration)
492
+ return "The interval is too short";
493
+ if (options.maxDuration !== void 0 && duration > options.maxDuration)
494
+ return "The interval is too long";
495
+ if (options.isRangeUnavailable?.(value.start, value.end))
496
+ return "This interval is unavailable";
497
+ }
498
+ return null;
499
+ }
500
+ function change(value, endpoint = snapshot.activeEndpoint, keepInvalid = false) {
501
+ const next = normalize(value, options), error2 = validation(next);
502
+ if (error2) {
503
+ update(
504
+ keepInvalid ? { draft: next, error: error2, activeEndpoint: endpoint } : { error: error2 }
505
+ );
506
+ return false;
507
+ }
508
+ const complete = validation(next, true) === null;
509
+ update({
510
+ draft: next,
511
+ error: null,
512
+ activeEndpoint: endpoint,
513
+ ...options.commit !== "explicit" && complete ? { value: next } : {}
514
+ });
515
+ return true;
516
+ }
517
+ function replacePoint(point, endpoint) {
518
+ return change(
519
+ options.selection === "range" ? {
520
+ start: pointFor(snapshot.draft, "start"),
521
+ end: pointFor(snapshot.draft, "end"),
522
+ [endpoint]: point
523
+ } : point,
524
+ endpoint,
525
+ true
526
+ );
527
+ }
528
+ const store = {
529
+ getSnapshot: () => snapshot,
530
+ subscribe(fn) {
531
+ listeners.add(fn);
532
+ return () => {
533
+ listeners.delete(fn);
534
+ };
535
+ },
536
+ subscribeValue(fn) {
537
+ valueListeners.add(fn);
538
+ return () => {
539
+ valueListeners.delete(fn);
540
+ };
541
+ },
542
+ getMonth(offset = 0) {
543
+ const m = addMonths(snapshot.visibleMonth, offset);
544
+ return engine.month(m.year, m.month, options);
545
+ },
546
+ getDay(date) {
547
+ if (!date)
548
+ return {
549
+ selected: false,
550
+ rangeStart: false,
551
+ rangeEnd: false,
552
+ inRange: false,
553
+ preview: false,
554
+ disabled: true,
555
+ today: false,
556
+ weekend: false,
557
+ focused: false
558
+ };
559
+ const d = snapshot.draft, start = pointFor(d)?.date, end = isRange(d) ? d.end?.date : void 0;
560
+ const hover = !end && start ? snapshot.hoverDate : null;
561
+ const lo = hover && hover < start ? hover : start, hi = hover && hover < start ? start : hover ?? end;
562
+ return {
563
+ selected: date === start || date === end,
564
+ rangeStart: date === start,
565
+ rangeEnd: date === end,
566
+ inRange: !!(lo && hi && date >= lo && date <= hi),
567
+ preview: !!hover,
568
+ disabled: !!options.disabled || unavailable(date),
569
+ today: date === options.referenceDate,
570
+ weekend: options.weekend.includes(weekday(date)),
571
+ focused: date === snapshot.focusedDate
572
+ };
573
+ },
574
+ setVisibleMonth(next) {
575
+ addMonths(next, options.months - 1);
576
+ update({
577
+ visibleMonth: Object.freeze({ year: next.year, month: next.month }),
578
+ hoverDate: null
579
+ });
580
+ },
581
+ navigate(count) {
582
+ store.setVisibleMonth(addMonths(snapshot.visibleMonth, count));
583
+ },
584
+ focus(date) {
585
+ parseDate(date);
586
+ update({ focusedDate: date });
587
+ const d = parseDate(date), first = dateString({ ...snapshot.visibleMonth, day: 1 }), last = addMonths(snapshot.visibleMonth, options.months - 1);
588
+ if (date < first || date > dateString({ ...last, day: daysInMonth(last.year, last.month) })) {
589
+ const lastStart = addMonths(
590
+ { year: 9999, month: 12 },
591
+ -(options.months - 1)
592
+ );
593
+ store.setVisibleMonth(
594
+ d.year > lastStart.year || d.year === lastStart.year && d.month > lastStart.month ? lastStart : d
595
+ );
596
+ }
597
+ },
598
+ moveFocus(key, shift = false) {
599
+ let date = snapshot.focusedDate;
600
+ const offsets = {
601
+ ArrowLeft: -1,
602
+ ArrowRight: 1,
603
+ ArrowUp: -7,
604
+ ArrowDown: 7
605
+ };
606
+ if (key in offsets) date = addDays(date, offsets[key]);
607
+ else if (key === "Home")
608
+ date = addDays(date, -((weekday(date) - options.weekStartsOn + 7) % 7));
609
+ else if (key === "End")
610
+ date = addDays(
611
+ date,
612
+ 6 - (weekday(date) - options.weekStartsOn + 7) % 7
613
+ );
614
+ else if (key === "PageUp" || key === "PageDown")
615
+ date = shiftDateMonths(
616
+ date,
617
+ (key === "PageUp" ? -1 : 1) * (shift ? 12 : 1)
618
+ );
619
+ else return false;
620
+ store.focus(date);
621
+ return true;
622
+ },
623
+ hover(date) {
624
+ if (date) parseDate(date);
625
+ if (options.selection === "range" && pointFor(snapshot.draft)?.date && !pointFor(snapshot.draft, "end"))
626
+ update({ hoverDate: date });
627
+ },
628
+ selectDate(date) {
629
+ parseDate(date);
630
+ if (options.disabled || options.readOnly || unavailable(date))
631
+ return false;
632
+ if (options.selection === "none") {
633
+ options.onSelect?.(date);
634
+ return true;
635
+ }
636
+ if (options.kind === "time")
637
+ return store.setDate(date, snapshot.activeEndpoint);
638
+ const previous = pointFor(snapshot.draft, snapshot.activeEndpoint);
639
+ let point = {
640
+ date,
641
+ ...options.kind === "datetime" && previous?.time ? { time: previous.time } : {}
642
+ };
643
+ let next = point, active = "start";
644
+ if (options.selection === "range") {
645
+ const start = pointFor(snapshot.draft), end = pointFor(snapshot.draft, "end");
646
+ if (!start || end) {
647
+ next = { start: point, end: null };
648
+ active = "end";
649
+ } else {
650
+ next = date < start.date ? { start: point, end: start } : { start, end: point };
651
+ active = "end";
652
+ }
653
+ }
654
+ const accepted = change(next, active);
655
+ if (accepted) {
656
+ update({ focusedDate: date, hoverDate: null });
657
+ options.onSelect?.(date);
658
+ }
659
+ return accepted;
660
+ },
661
+ setDate(date, endpoint = "start") {
662
+ parseDate(date);
663
+ if (options.disabled || options.readOnly) return false;
664
+ const current = pointFor(snapshot.draft, endpoint);
665
+ return replacePoint(
666
+ {
667
+ date,
668
+ ...options.kind !== "date" && current?.time ? { time: current.time } : {}
669
+ },
670
+ endpoint
671
+ );
672
+ },
673
+ setTime(time, endpoint = "start") {
674
+ timeSeconds(time);
675
+ if (options.disabled || options.readOnly || options.kind === "date")
676
+ return false;
677
+ const current = pointFor(snapshot.draft, endpoint);
678
+ return replacePoint(
679
+ {
680
+ ...current,
681
+ time,
682
+ ...options.kind === "datetime" && !current?.date ? { date: options.referenceDate } : {}
683
+ },
684
+ endpoint
685
+ );
686
+ },
687
+ setDayOffset(offset, endpoint = "end") {
688
+ if (!Number.isInteger(offset) || offset < 0)
689
+ throw new RangeError("Day offset must be nonnegative");
690
+ if (options.disabled || options.readOnly || options.kind !== "time")
691
+ return false;
692
+ const current = pointFor(snapshot.draft, endpoint);
693
+ return replacePoint(
694
+ { ...current, date: void 0, dayOffset: offset },
695
+ endpoint
696
+ );
697
+ },
698
+ setEndpoint(endpoint) {
699
+ if (endpoint === "end" && options.selection !== "range")
700
+ throw new RangeError("End is available only for a range");
701
+ update({ activeEndpoint: endpoint });
702
+ },
703
+ setValue(value) {
704
+ const next = normalize(value, options), error2 = validation(next);
705
+ if (error2) throw new RangeError(error2);
706
+ update({ value: next, draft: next, error: null, hoverDate: null });
707
+ },
708
+ apply() {
709
+ if (options.disabled || options.readOnly) return false;
710
+ const error2 = validation(snapshot.draft, true);
711
+ if (error2) {
712
+ update({ error: error2 });
713
+ return false;
714
+ }
715
+ update({ value: snapshot.draft, error: null, open: false });
716
+ return true;
717
+ },
718
+ cancel() {
719
+ update({
720
+ draft: snapshot.value,
721
+ error: null,
722
+ hoverDate: null,
723
+ open: false
724
+ });
725
+ },
726
+ clear() {
727
+ if (options.disabled || options.readOnly) return;
728
+ const next = normalize(null, options);
729
+ update({
730
+ draft: next,
731
+ value: options.commit === "explicit" ? snapshot.value : next,
732
+ error: null,
733
+ hoverDate: null,
734
+ activeEndpoint: "start"
735
+ });
736
+ },
737
+ setOpen(open) {
738
+ if (open && options.disabled) return;
739
+ update({ open });
740
+ },
741
+ setOptions(patch) {
742
+ if (Object.entries(patch).every(
743
+ ([key, value]) => Object.is(options[key], value)
744
+ ))
745
+ return;
746
+ if (patch.kind && patch.kind !== options.kind || patch.selection && patch.selection !== options.selection)
747
+ throw new RangeError("Create a new store to change kind or selection");
748
+ const next = resolve({ ...options, ...patch });
749
+ addMonths(snapshot.visibleMonth, next.months - 1);
750
+ options = next;
751
+ update({ options: next, error: validation(snapshot.draft) });
752
+ },
753
+ validate: () => validation(snapshot.draft, true),
754
+ destroy() {
755
+ listeners.clear();
756
+ valueListeners.clear();
757
+ engine.clear();
758
+ },
759
+ get cacheSize() {
760
+ return engine.cacheSize;
761
+ }
762
+ };
763
+ const error = validation(initial);
764
+ if (error) throw new RangeError(error);
765
+ return store;
766
+ }
767
+
768
+ // packages/calendar/src/core/timezone.ts
769
+ function resolveZonedDateTime(point, timeZone, disambiguation = "reject") {
770
+ if (!point.date || !point.time)
771
+ throw new RangeError("Date and time are required");
772
+ parseDate(point.date);
773
+ const target = dayNumber(point.date) * 864e5 + timeSeconds(point.time) * 1e3;
774
+ const formatter = new Intl.DateTimeFormat("en-CA", {
775
+ timeZone,
776
+ calendar: "gregory",
777
+ numberingSystem: "latn",
778
+ year: "numeric",
779
+ month: "2-digit",
780
+ day: "2-digit",
781
+ hour: "2-digit",
782
+ minute: "2-digit",
783
+ second: "2-digit",
784
+ hourCycle: "h23"
785
+ });
786
+ const wall = (stamp) => {
787
+ const p = formatter.formatToParts(new Date(stamp));
788
+ const get = (key) => p.find((v) => v.type === key).value;
789
+ return dayNumber(
790
+ `${get("year").padStart(4, "0")}-${get("month")}-${get("day")}`
791
+ ) * 864e5 + timeSeconds(`${get("hour")}:${get("minute")}:${get("second")}`) * 1e3;
792
+ };
793
+ const offsets = /* @__PURE__ */ new Set();
794
+ for (const hours of [-48, -24, -12, 0, 12, 24, 48]) {
795
+ const n = target + hours * 36e5;
796
+ offsets.add(wall(n) - n);
797
+ }
798
+ const candidates = [...offsets].map((offset) => target - offset).sort((a, b) => a - b);
799
+ const matches = candidates.filter((n) => wall(n) === target);
800
+ if (matches.length === 1) return new Date(matches[0]);
801
+ if (disambiguation === "reject")
802
+ throw new RangeError(
803
+ matches.length ? "This local time occurs twice" : "This local time does not exist"
804
+ );
805
+ const choice = matches.length ? matches : candidates;
806
+ return new Date(
807
+ disambiguation === "earlier" ? choice[0] : choice[choice.length - 1]
808
+ );
809
+ }
810
+
811
+ // packages/calendar/src/core/dom.ts
812
+ function escapeHTML(value) {
813
+ return String(value ?? "").replace(
814
+ /[&<>"']/g,
815
+ (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]
816
+ );
817
+ }
818
+ function attribute(el, name, value) {
819
+ if (value === false || value == null) {
820
+ if (el.hasAttribute(name)) el.removeAttribute(name);
821
+ } else {
822
+ const next = value === true ? "" : value;
823
+ if (el.getAttribute(name) !== next) el.setAttribute(name, next);
824
+ }
825
+ }
826
+ function dayAttributes(store, date, outside = false) {
827
+ const state = store.getDay(date), options = store.getSnapshot().options;
828
+ return {
829
+ "data-date": date,
830
+ "data-selected": state.selected ? "" : void 0,
831
+ "data-range-start": state.rangeStart ? "" : void 0,
832
+ "data-range-end": state.rangeEnd ? "" : void 0,
833
+ "data-in-range": state.inRange ? "" : void 0,
834
+ "data-preview": state.preview && state.inRange ? "" : void 0,
835
+ "data-weekend": state.weekend ? "" : void 0,
836
+ "data-outside": outside ? "" : void 0,
837
+ "data-today": state.today ? "" : void 0,
838
+ "aria-selected": state.selected || state.inRange && !state.preview ? "true" : "false"
839
+ };
840
+ }
841
+ function dayTriggerAttributes(store, date, outside = false) {
842
+ const { "aria-selected": selected, ...attributes } = dayAttributes(
843
+ store,
844
+ date,
845
+ outside
846
+ );
847
+ return attributes;
848
+ }
849
+ function calendarMarkup(store, render = {}) {
850
+ const { options, focusedDate } = store.getSnapshot(), labels = weekdayLabels(options.locale, options.weekStartsOn), fullLabels = weekdayLabels(options.locale, options.weekStartsOn, "long");
851
+ const grids = Array.from(
852
+ { length: options.months },
853
+ (_, offset) => store.getMonth(offset)
854
+ );
855
+ const days = grids.flatMap((month) => month.weeks.flat()).filter((day) => !day.hidden && !day.outside);
856
+ const tabDate = days.some(
857
+ (day) => day.date === focusedDate && !store.getDay(day.date).disabled
858
+ ) ? focusedDate : days.find((day) => !store.getDay(day.date).disabled)?.date;
859
+ return Array.from({ length: options.months }, (_, index) => {
860
+ const month = grids[index];
861
+ const headings = labels.map((day) => {
862
+ const custom = render.weekdays?.[day.weekday], label = custom?.label ?? day.label, accessible = custom?.ariaLabel ?? (custom?.label || fullLabels.find((v) => v.weekday === day.weekday).label);
863
+ return `<th scope="col" data-part="weekday" data-weekday="${day.weekday}" class="${escapeHTML(custom?.class ?? "")}" ${options.weekend.includes(day.weekday) ? "data-weekend" : ""} aria-label="${escapeHTML(accessible)}">${render.renderWeekday?.(day.weekday, label) ?? escapeHTML(label)}</th>`;
864
+ }).join("");
865
+ return `<section class="sp-month" data-part="month"><h3 data-part="month-title" aria-live="polite">${escapeHTML(formatMonth(month, options.locale))}</h3><table role="grid" aria-readonly="${!!options.readOnly}" aria-multiselectable="${options.selection === "range"}" aria-label="${escapeHTML(formatMonth(month, options.locale))}" class="sp-grid"><thead><tr>${headings}</tr></thead><tbody>${month.weeks.map(
866
+ (week) => `<tr>${week.map((day) => {
867
+ if (day.hidden)
868
+ return '<td role="gridcell" aria-disabled="true"></td>';
869
+ const state = store.getDay(day.date), disabled = state.disabled || day.outside && options.outsideSelectable === false;
870
+ const attrs = Object.entries(
871
+ dayAttributes(store, day.date, day.outside)
872
+ ).map(([k, v]) => v == null ? "" : `${k}="${escapeHTML(v)}"`).join(" ");
873
+ const content = render.renderCell?.(day, state) ?? `<button type="button" data-day-trigger data-date="${day.date}" aria-label="${escapeHTML(formatDate(day.date, options.locale))}" ${disabled ? "disabled" : ""} tabindex="${day.date === tabDate && !day.outside ? "0" : "-1"}"><span data-part="number">${day.day}</span></button>`;
874
+ return `<td role="gridcell" data-part="cell" ${attrs}>${content}</td>`;
875
+ }).join("")}</tr>`
876
+ ).join("")}</tbody></table></section>`;
877
+ }).join("");
878
+ }
879
+ function bindCalendar(root, store, render = {}) {
880
+ let gridKey = "", active = true;
881
+ const pending = /* @__PURE__ */ new Set();
882
+ const update = () => {
883
+ const { options, visibleMonth, focusedDate } = store.getSnapshot();
884
+ const key2 = JSON.stringify([
885
+ visibleMonth,
886
+ options.months,
887
+ options.weekStartsOn,
888
+ options.outsideDays,
889
+ options.fixedWeeks,
890
+ options.locale,
891
+ options.weekend,
892
+ render.weekdays
893
+ ]);
894
+ const hadFocus = root.contains(root.ownerDocument.activeElement);
895
+ if (key2 !== gridKey) {
896
+ root.innerHTML = calendarMarkup(store, render);
897
+ gridKey = key2;
898
+ }
899
+ let focusable;
900
+ for (const cell of root.querySelectorAll(
901
+ '[data-part="cell"]'
902
+ )) {
903
+ const date = cell.dataset.date, outside = cell.hasAttribute("data-outside"), s = store.getDay(date);
904
+ for (const [k, v] of Object.entries(dayAttributes(store, date, outside)))
905
+ attribute(cell, k, v);
906
+ const trigger = cell.querySelector("[data-day-trigger]");
907
+ if (!trigger) continue;
908
+ trigger.disabled = s.disabled || outside && options.outsideSelectable === false;
909
+ if (!trigger.hasAttribute("aria-label"))
910
+ attribute(trigger, "aria-label", formatDate(date, options.locale));
911
+ attribute(trigger, "aria-current", s.today ? "date" : null);
912
+ trigger.tabIndex = -1;
913
+ if (!trigger.disabled && !outside && (!focusable || date === focusedDate))
914
+ focusable = trigger;
915
+ }
916
+ if (focusable) focusable.tabIndex = 0;
917
+ if (hadFocus && focusable && focusedDate === focusable.dataset.date && root.ownerDocument.activeElement !== focusable)
918
+ focusable.focus();
919
+ for (const grid of root.querySelectorAll('[role="grid"]')) {
920
+ attribute(grid, "aria-readonly", String(!!options.readOnly));
921
+ attribute(
922
+ grid,
923
+ "aria-multiselectable",
924
+ String(options.selection === "range")
925
+ );
926
+ }
927
+ };
928
+ const click = (event) => {
929
+ const trigger = event.target.closest(
930
+ "[data-day-trigger]"
931
+ );
932
+ if (!trigger || !root.contains(trigger) || trigger.disabled) return;
933
+ const date = trigger.dataset.date;
934
+ render.onDayClick?.(date, event);
935
+ const task = setTimeout(() => {
936
+ pending.delete(task);
937
+ if (active && root.isConnected && !event.defaultPrevented && store.selectDate(date))
938
+ render.onSelect?.(date);
939
+ }, 0);
940
+ pending.add(task);
941
+ };
942
+ const hover = (event) => {
943
+ if (event.pointerType === "touch") return;
944
+ const trigger = event.target.closest(
945
+ "[data-day-trigger]"
946
+ );
947
+ if (trigger && !trigger.hasAttribute("disabled"))
948
+ store.hover(trigger.dataset.date);
949
+ };
950
+ const leave = () => store.hover(null);
951
+ const key = (event) => {
952
+ const trigger = event.target.closest(
953
+ "[data-day-trigger]"
954
+ );
955
+ if (!trigger) return;
956
+ if (event.key === "Escape") {
957
+ store.cancel();
958
+ event.preventDefault();
959
+ return;
960
+ }
961
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Home" || event.key === "End" || event.key === "PageUp" || event.key === "PageDown") {
962
+ event.preventDefault();
963
+ store.focus(trigger.dataset.date);
964
+ const rtl = getComputedStyle(root).direction === "rtl";
965
+ const key2 = rtl && event.key === "ArrowLeft" ? "ArrowRight" : rtl && event.key === "ArrowRight" ? "ArrowLeft" : event.key;
966
+ try {
967
+ store.moveFocus(key2, event.shiftKey);
968
+ } catch (error) {
969
+ if (!(error instanceof RangeError)) throw error;
970
+ }
971
+ }
972
+ };
973
+ update();
974
+ const stop = store.subscribe(update);
975
+ root.addEventListener("click", click);
976
+ root.addEventListener("pointerover", hover);
977
+ root.addEventListener("pointerleave", leave);
978
+ root.addEventListener("keydown", key);
979
+ return () => {
980
+ active = false;
981
+ for (const task of pending) clearTimeout(task);
982
+ pending.clear();
983
+ stop();
984
+ root.removeEventListener("click", click);
985
+ root.removeEventListener("pointerover", hover);
986
+ root.removeEventListener("pointerleave", leave);
987
+ root.removeEventListener("keydown", key);
988
+ };
989
+ }
990
+ function bindDateField(input, store, endpoint = "start") {
991
+ const original = input.value, ownDisabled = input.disabled, ownReadonly = input.readOnly;
992
+ const update = () => {
993
+ const s = store.getSnapshot();
994
+ input.disabled = ownDisabled || !!s.options.disabled;
995
+ input.readOnly = ownReadonly || !!s.options.readOnly;
996
+ if (input.ownerDocument.activeElement !== input) {
997
+ input.value = pointFor(s.draft, endpoint)?.date ?? "";
998
+ attribute(input, "aria-invalid", String(!!s.error));
999
+ }
1000
+ };
1001
+ const change = () => {
1002
+ try {
1003
+ parseDate(input.value);
1004
+ const ok = store.setDate(input.value, endpoint);
1005
+ attribute(input, "aria-invalid", String(!ok));
1006
+ } catch {
1007
+ attribute(input, "aria-invalid", "true");
1008
+ }
1009
+ };
1010
+ const blur = () => {
1011
+ input.value = pointFor(store.getSnapshot().draft, endpoint)?.date ?? "";
1012
+ attribute(input, "aria-invalid", String(!!store.getSnapshot().error));
1013
+ };
1014
+ update();
1015
+ const stop = store.subscribe(update);
1016
+ input.addEventListener("input", change);
1017
+ input.addEventListener("blur", blur);
1018
+ return () => {
1019
+ stop();
1020
+ input.removeEventListener("input", change);
1021
+ input.removeEventListener("blur", blur);
1022
+ input.value = original;
1023
+ };
1024
+ }
1025
+ function bindTimeSegment(input, store, part, endpoint = "start") {
1026
+ let invalid = false;
1027
+ const ownDisabled = input.disabled, ownReadonly = input instanceof input.ownerDocument.defaultView.HTMLInputElement && input.readOnly;
1028
+ const value = () => timeSegmentValue(
1029
+ pointFor(store.getSnapshot().draft, endpoint)?.time,
1030
+ part,
1031
+ store.getSnapshot().options.hourCycle
1032
+ );
1033
+ const update = () => {
1034
+ const s = store.getSnapshot();
1035
+ input.disabled = ownDisabled || !!s.options.disabled;
1036
+ if (input instanceof input.ownerDocument.defaultView.HTMLInputElement) {
1037
+ input.readOnly = ownReadonly || !!s.options.readOnly;
1038
+ input.min = part === "hour" && s.options.hourCycle === 12 ? "1" : "0";
1039
+ input.max = part === "hour" ? s.options.hourCycle === 12 ? "12" : "23" : "59";
1040
+ input.step = part === "minute" ? String(s.options.minuteStep) : "1";
1041
+ } else
1042
+ input.disabled = ownDisabled || !!s.options.disabled || !!s.options.readOnly;
1043
+ if (input.ownerDocument.activeElement !== input || !invalid) {
1044
+ input.value = value();
1045
+ if (!s.error) invalid = false;
1046
+ }
1047
+ attribute(input, "aria-invalid", String(invalid || !!s.error));
1048
+ };
1049
+ const edit = () => {
1050
+ if (input.value.trim() === "") {
1051
+ invalid = true;
1052
+ attribute(input, "aria-invalid", "true");
1053
+ return;
1054
+ }
1055
+ try {
1056
+ const s = store.getSnapshot(), current = pointFor(s.draft, endpoint)?.time ?? "00:00";
1057
+ if (part === "minute" && Number(input.value) % s.options.minuteStep !== 0)
1058
+ throw new RangeError("Minute must match the configured step");
1059
+ const time = updateTime(
1060
+ current,
1061
+ part,
1062
+ input.value,
1063
+ s.options.hourCycle,
1064
+ !!s.options.seconds || current.length === 8 || part === "second"
1065
+ );
1066
+ invalid = !store.setTime(time, endpoint);
1067
+ attribute(input, "aria-invalid", String(invalid));
1068
+ } catch {
1069
+ invalid = true;
1070
+ attribute(input, "aria-invalid", "true");
1071
+ }
1072
+ };
1073
+ const blur = () => {
1074
+ invalid = false;
1075
+ input.value = value();
1076
+ attribute(input, "aria-invalid", String(!!store.getSnapshot().error));
1077
+ };
1078
+ update();
1079
+ const stop = store.subscribe(update);
1080
+ input.addEventListener("input", edit);
1081
+ input.addEventListener("blur", blur);
1082
+ return () => {
1083
+ stop();
1084
+ input.removeEventListener("input", edit);
1085
+ input.removeEventListener("blur", blur);
1086
+ };
1087
+ }
1088
+ function bindPickerForm(form, store, {
1089
+ name = "date",
1090
+ required = false
1091
+ } = {}) {
1092
+ const field = form.ownerDocument.createElement("input");
1093
+ field.type = "hidden";
1094
+ field.name = name;
1095
+ form.append(field);
1096
+ const initial = store.getSnapshot().value;
1097
+ const update = () => {
1098
+ field.disabled = !!store.getSnapshot().options.disabled;
1099
+ field.value = JSON.stringify(store.getSnapshot().value);
1100
+ };
1101
+ const submit = (event) => {
1102
+ if (field.disabled) return;
1103
+ const v = store.getSnapshot().value, empty = !v || "start" in v && !v.start && !v.end;
1104
+ if (empty && !required) return;
1105
+ if (required && empty || store.validate()) {
1106
+ event.preventDefault();
1107
+ form.dispatchEvent(
1108
+ new CustomEvent("picker-invalid", {
1109
+ detail: store.validate() ?? "Choose a value"
1110
+ })
1111
+ );
1112
+ }
1113
+ };
1114
+ const reset = () => queueMicrotask(() => store.setValue(initial));
1115
+ update();
1116
+ const stop = store.subscribe(update);
1117
+ form.addEventListener("submit", submit);
1118
+ form.addEventListener("reset", reset);
1119
+ return () => {
1120
+ stop();
1121
+ field.remove();
1122
+ form.removeEventListener("submit", submit);
1123
+ form.removeEventListener("reset", reset);
1124
+ };
1125
+ }
1126
+
1127
+ // packages/calendar/src/core/day.ts
1128
+ function bindDayTrigger(button, store, date, {
1129
+ outside = false,
1130
+ onSelect,
1131
+ disabled
1132
+ } = {}) {
1133
+ let active = true;
1134
+ const pending = /* @__PURE__ */ new Set();
1135
+ const ownDisabled = disabled ?? button.disabled, ownLabel = button.getAttribute("aria-label");
1136
+ const update = () => {
1137
+ const s = store.getSnapshot(), state = store.getDay(date);
1138
+ for (const [k, v] of Object.entries(
1139
+ dayTriggerAttributes(store, date, outside)
1140
+ ))
1141
+ attribute(button, k, v);
1142
+ button.disabled = ownDisabled || state.disabled || outside && s.options.outsideSelectable === false;
1143
+ button.tabIndex = state.focused && !outside ? 0 : -1;
1144
+ attribute(button, "data-day-trigger", true);
1145
+ attribute(button, "aria-current", state.today ? "date" : null);
1146
+ attribute(
1147
+ button,
1148
+ "aria-label",
1149
+ ownLabel ?? formatDate(date, s.options.locale)
1150
+ );
1151
+ const active2 = button.ownerDocument.activeElement;
1152
+ if (state.focused && !outside && active2 !== button && active2?.matches("[data-day-trigger]") && active2.closest('[role="grid"]') === button.closest('[role="grid"]'))
1153
+ button.focus();
1154
+ };
1155
+ const click = (event) => {
1156
+ const task = setTimeout(() => {
1157
+ pending.delete(task);
1158
+ if (active && button.isConnected && !button.disabled && !event.defaultPrevented && !(outside && store.getSnapshot().options.outsideSelectable === false) && store.selectDate(date))
1159
+ onSelect?.(date);
1160
+ }, 0);
1161
+ pending.add(task);
1162
+ };
1163
+ const hover = (event) => {
1164
+ if (event.pointerType !== "touch" && !button.disabled) store.hover(date);
1165
+ };
1166
+ const key = (event) => {
1167
+ if (event.defaultPrevented) return;
1168
+ try {
1169
+ if (event.key === "Escape") {
1170
+ store.cancel();
1171
+ event.preventDefault();
1172
+ return;
1173
+ }
1174
+ store.focus(date);
1175
+ const rtl = getComputedStyle(button).direction === "rtl";
1176
+ const key2 = rtl && event.key === "ArrowLeft" ? "ArrowRight" : rtl && event.key === "ArrowRight" ? "ArrowLeft" : event.key;
1177
+ if (store.moveFocus(key2, event.shiftKey)) event.preventDefault();
1178
+ } catch (error) {
1179
+ if (!(error instanceof RangeError)) throw error;
1180
+ }
1181
+ };
1182
+ update();
1183
+ const stop = store.subscribe(update);
1184
+ button.addEventListener("click", click);
1185
+ button.addEventListener("pointerenter", hover);
1186
+ button.addEventListener("keydown", key);
1187
+ return () => {
1188
+ active = false;
1189
+ for (const task of pending) clearTimeout(task);
1190
+ pending.clear();
1191
+ stop();
1192
+ button.removeEventListener("click", click);
1193
+ button.removeEventListener("pointerenter", hover);
1194
+ button.removeEventListener("keydown", key);
1195
+ };
1196
+ }
1197
+
1198
+ // packages/calendar/src/core/popover.ts
1199
+ function bindPickerPopover(trigger, content, store, {
1200
+ modal = false,
1201
+ label = "Choose a date"
1202
+ } = {}) {
1203
+ const doc = content.ownerDocument, oldHidden = content.hidden, ownDisabled = trigger.disabled;
1204
+ content.setAttribute("role", "dialog");
1205
+ content.setAttribute(
1206
+ "aria-label",
1207
+ content.getAttribute("aria-label") ?? label
1208
+ );
1209
+ if (modal) content.setAttribute("aria-modal", "true");
1210
+ trigger.setAttribute("aria-haspopup", "dialog");
1211
+ let wasOpen = store.getSnapshot().open;
1212
+ const update = () => {
1213
+ const s = store.getSnapshot();
1214
+ content.hidden = !s.open;
1215
+ trigger.disabled = ownDisabled || !!s.options.disabled;
1216
+ trigger.setAttribute("aria-expanded", String(s.open));
1217
+ if (s.open && !wasOpen)
1218
+ queueMicrotask(() => {
1219
+ if (store.getSnapshot().open)
1220
+ (content.querySelector(
1221
+ '[data-day-trigger][tabindex="0"],input:not(:disabled),button:not(:disabled)'
1222
+ ) ?? content).focus();
1223
+ });
1224
+ if (!s.open && wasOpen) trigger.focus();
1225
+ wasOpen = s.open;
1226
+ };
1227
+ const click = (event) => {
1228
+ if (event.defaultPrevented) return;
1229
+ if (store.getSnapshot().open) store.cancel();
1230
+ else store.setOpen(true);
1231
+ };
1232
+ const outside = (event) => {
1233
+ if (store.getSnapshot().open && !content.contains(event.target) && !trigger.contains(event.target))
1234
+ store.cancel();
1235
+ };
1236
+ const key = (event) => {
1237
+ if (!store.getSnapshot().open) return;
1238
+ if (event.key === "Escape") {
1239
+ event.preventDefault();
1240
+ store.cancel();
1241
+ }
1242
+ if (modal && event.key === "Tab") {
1243
+ const controls = [
1244
+ ...content.querySelectorAll(
1245
+ 'button:not(:disabled),input:not(:disabled),select:not(:disabled),a[href],[tabindex="0"]'
1246
+ )
1247
+ ].filter(
1248
+ (el) => el.tabIndex >= 0 && !el.hidden && el.getClientRects().length
1249
+ );
1250
+ const first = controls[0], last = controls.at(-1);
1251
+ if (!first) {
1252
+ event.preventDefault();
1253
+ content.focus();
1254
+ } else if (event.shiftKey && doc.activeElement === first) {
1255
+ event.preventDefault();
1256
+ last.focus();
1257
+ } else if (!event.shiftKey && doc.activeElement === last) {
1258
+ event.preventDefault();
1259
+ first.focus();
1260
+ }
1261
+ }
1262
+ };
1263
+ update();
1264
+ const stop = store.subscribe(update);
1265
+ trigger.addEventListener("click", click);
1266
+ doc.addEventListener("pointerdown", outside);
1267
+ doc.addEventListener("keydown", key);
1268
+ return () => {
1269
+ stop();
1270
+ content.hidden = oldHidden;
1271
+ trigger.removeEventListener("click", click);
1272
+ doc.removeEventListener("pointerdown", outside);
1273
+ doc.removeEventListener("keydown", key);
1274
+ };
1275
+ }
1276
+
1277
+ // packages/calendar/src/core/scroller.ts
1278
+ function monthScrollerMarkup(store, options = {}) {
1279
+ const state = store.getSnapshot(), engine = createCalendarEngine(1);
1280
+ const first = options.from ?? { year: 1900, month: 1 }, last = options.to ?? { year: 2100, month: 12 };
1281
+ const key = (month2) => month2.year * 12 + month2.month;
1282
+ const month = key(state.visibleMonth) < key(first) ? first : key(state.visibleMonth) > key(last) ? last : state.visibleMonth;
1283
+ const view = {
1284
+ ...store,
1285
+ getSnapshot: () => ({
1286
+ ...state,
1287
+ visibleMonth: month,
1288
+ options: { ...state.options, months: 1, fixedWeeks: true }
1289
+ }),
1290
+ getMonth: (offset = 0) => {
1291
+ const m = addMonths(month, offset);
1292
+ return engine.month(m.year, m.month, {
1293
+ ...state.options,
1294
+ fixedWeeks: true
1295
+ });
1296
+ }
1297
+ };
1298
+ return calendarMarkup(view, options);
1299
+ }
1300
+ function bindMonthScroller(root, store, options = {}) {
1301
+ const from = options.from ?? { year: 1900, month: 1 }, to = options.to ?? { year: 2100, month: 12 };
1302
+ addMonths(from, 0);
1303
+ addMonths(to, 0);
1304
+ const index = (m) => (m.year - from.year) * 12 + m.month - from.month;
1305
+ const count = index(to) + 1, height = options.monthHeight ?? 340, overscan = options.overscan ?? 1;
1306
+ if (count < 1 || count > 1e4 || height < 280 || !Number.isFinite(height) || !Number.isInteger(overscan) || overscan < 0 || overscan > 4)
1307
+ throw new RangeError("Invalid scrolling window or month geometry");
1308
+ const document = root.ownerDocument, container = document.createElement("div");
1309
+ container.style.height = `${count * height}px`;
1310
+ container.style.position = "relative";
1311
+ root.replaceChildren(container);
1312
+ const engine = createCalendarEngine(12);
1313
+ const entries = /* @__PURE__ */ new Map();
1314
+ let frame = 0, syncing = false;
1315
+ const view = (month) => {
1316
+ let previous, next;
1317
+ return {
1318
+ ...store,
1319
+ getSnapshot() {
1320
+ const source = store.getSnapshot();
1321
+ if (source !== previous) {
1322
+ previous = source;
1323
+ next = Object.freeze({
1324
+ ...source,
1325
+ visibleMonth: month,
1326
+ options: Object.freeze({
1327
+ ...source.options,
1328
+ months: 1,
1329
+ fixedWeeks: true
1330
+ })
1331
+ });
1332
+ }
1333
+ return next;
1334
+ },
1335
+ getMonth(offset = 0) {
1336
+ const m = addMonths(month, offset);
1337
+ return engine.month(m.year, m.month, {
1338
+ ...store.getSnapshot().options,
1339
+ fixedWeeks: true
1340
+ });
1341
+ }
1342
+ };
1343
+ };
1344
+ const render = () => {
1345
+ const top = Math.max(
1346
+ 0,
1347
+ Math.min(count - 1, Math.floor(root.scrollTop / height))
1348
+ ), visible = Math.ceil((root.clientHeight || height) / height), first = Math.max(0, top - overscan), last = Math.min(count - 1, top + visible + overscan);
1349
+ for (const [n, entry] of entries)
1350
+ if (n < first || n > last) {
1351
+ entry.stop();
1352
+ entry.element.remove();
1353
+ entries.delete(n);
1354
+ }
1355
+ for (let n = first; n <= last; n++)
1356
+ if (!entries.has(n)) {
1357
+ const element = document.createElement("div");
1358
+ element.className = "sp-scroll-month";
1359
+ element.style.cssText = `position:absolute;top:${n * height}px;left:0;right:0;height:${height}px;overflow:hidden`;
1360
+ container.append(element);
1361
+ const month = addMonths(from, n);
1362
+ entries.set(n, {
1363
+ element,
1364
+ stop: bindCalendar(element, view(month), options)
1365
+ });
1366
+ }
1367
+ if (!syncing) {
1368
+ const month = addMonths(from, top), s = store.getSnapshot();
1369
+ if (month.year !== s.visibleMonth.year || month.month !== s.visibleMonth.month) {
1370
+ syncing = true;
1371
+ store.setVisibleMonth(month);
1372
+ syncing = false;
1373
+ }
1374
+ }
1375
+ };
1376
+ const onScroll = () => {
1377
+ if (!frame)
1378
+ frame = root.ownerDocument.defaultView.requestAnimationFrame(() => {
1379
+ frame = 0;
1380
+ render();
1381
+ });
1382
+ };
1383
+ const onStore = () => {
1384
+ if (syncing) return;
1385
+ const n = index(store.getSnapshot().visibleMonth);
1386
+ if (n < 0 || n >= count) return;
1387
+ if (Math.floor(root.scrollTop / height) !== n) {
1388
+ root.scrollTop = n * height;
1389
+ render();
1390
+ }
1391
+ };
1392
+ root.scrollTop = Math.max(0, Math.min(count - 1, index(store.getSnapshot().visibleMonth))) * height;
1393
+ render();
1394
+ const stop = store.subscribe(onStore);
1395
+ root.addEventListener("scroll", onScroll, { passive: true });
1396
+ const Observer = root.ownerDocument.defaultView?.ResizeObserver;
1397
+ const observer = Observer ? new Observer(onScroll) : void 0;
1398
+ observer?.observe(root);
1399
+ return () => {
1400
+ observer?.disconnect();
1401
+ stop();
1402
+ root.removeEventListener("scroll", onScroll);
1403
+ if (frame) root.ownerDocument.defaultView.cancelAnimationFrame(frame);
1404
+ for (const entry of entries.values()) entry.stop();
1405
+ entries.clear();
1406
+ engine.clear();
1407
+ container.remove();
1408
+ };
1409
+ }
1410
+
1411
+ // packages/date-time-picker/src/index.ts
1412
+ function createPickerStore2(options = {}) {
1413
+ return createPickerStore({ ...options, kind: "datetime" });
1414
+ }
1415
+ var createDateTimePickerStore = createPickerStore2;
1416
+
1417
+ // packages/calendar/src/vanilla/index.ts
1418
+ function mountPicker(root, options = {}) {
1419
+ const provided = "getSnapshot" in options;
1420
+ const store = provided ? options : createPickerStore(options);
1421
+ const cleanups = [];
1422
+ const own = (selector) => [...root.querySelectorAll(selector)].filter(
1423
+ (el) => !el.parentElement?.closest("[data-picker-root]") || el.parentElement.closest("[data-picker-root]") === root
1424
+ );
1425
+ attribute(root, "data-picker-root", true);
1426
+ for (const el of own("[data-continuous]"))
1427
+ cleanups.push(
1428
+ bindMonthScroller(el, store, JSON.parse(el.dataset.continuous ?? "{}"))
1429
+ );
1430
+ for (const el of own("[data-calendar]"))
1431
+ cleanups.push(
1432
+ bindCalendar(el, store, JSON.parse(el.dataset.calendarOptions ?? "{}"))
1433
+ );
1434
+ for (const el of own("[data-day-trigger]"))
1435
+ if (!el.closest("[data-calendar]"))
1436
+ cleanups.push(
1437
+ bindDayTrigger(el, store, el.dataset.date)
1438
+ );
1439
+ for (const el of own("[data-date-field]"))
1440
+ cleanups.push(
1441
+ bindDateField(
1442
+ el,
1443
+ store,
1444
+ el.dataset.endpoint ?? "start"
1445
+ )
1446
+ );
1447
+ for (const el of own("[data-time-segment]"))
1448
+ cleanups.push(
1449
+ bindTimeSegment(
1450
+ el,
1451
+ store,
1452
+ el.dataset.timeSegment,
1453
+ el.dataset.endpoint ?? "start"
1454
+ )
1455
+ );
1456
+ const click = (event) => {
1457
+ if (event.defaultPrevented) return;
1458
+ const button = event.target.closest(
1459
+ "[data-picker-action]"
1460
+ );
1461
+ if (!button || !root.contains(button) || button.closest("[data-picker-root]") !== root)
1462
+ return;
1463
+ const state = store.getSnapshot();
1464
+ if (state.options.disabled) return;
1465
+ const action = button.dataset.pickerAction;
1466
+ try {
1467
+ if (action === "previous") store.navigate(-1);
1468
+ else if (action === "next") store.navigate(1);
1469
+ else if (action === "open") store.setOpen(true);
1470
+ else if (action === "cancel" || action === "apply" || action === "clear")
1471
+ store[action]();
1472
+ } catch (error) {
1473
+ if (!(error instanceof RangeError)) throw error;
1474
+ }
1475
+ };
1476
+ const change = (event) => {
1477
+ const el = event.target;
1478
+ if (el.closest("[data-picker-root]") !== root || store.getSnapshot().options.disabled)
1479
+ return;
1480
+ try {
1481
+ if (el.hasAttribute("data-month-select"))
1482
+ store.setVisibleMonth({
1483
+ ...store.getSnapshot().visibleMonth,
1484
+ month: Number(el.value)
1485
+ });
1486
+ if (el.hasAttribute("data-year-select"))
1487
+ store.setVisibleMonth({
1488
+ ...store.getSnapshot().visibleMonth,
1489
+ year: Number(el.value)
1490
+ });
1491
+ if (el.hasAttribute("data-day-offset"))
1492
+ store.setDayOffset(
1493
+ Number(el.value),
1494
+ el.dataset.endpoint ?? "end"
1495
+ );
1496
+ } catch (error) {
1497
+ if (!(error instanceof RangeError)) throw error;
1498
+ }
1499
+ };
1500
+ const update = () => {
1501
+ const s = store.getSnapshot();
1502
+ for (const cell of own("[data-calendar-cell]"))
1503
+ for (const [key, value] of Object.entries(
1504
+ dayAttributes(
1505
+ store,
1506
+ cell.dataset.date,
1507
+ cell.hasAttribute("data-outside")
1508
+ )
1509
+ ))
1510
+ attribute(cell, key, value);
1511
+ for (const el of own("[data-picker-output]"))
1512
+ el.textContent = JSON.stringify(s.value, null, 2);
1513
+ for (const el of own("[data-picker-error]")) {
1514
+ el.textContent = s.error ?? "";
1515
+ el.hidden = !s.error;
1516
+ }
1517
+ for (const el of own("[data-month-heading]"))
1518
+ el.textContent = formatMonth(s.visibleMonth, s.options.locale);
1519
+ for (const el of own("[data-month-select]"))
1520
+ el.value = String(s.visibleMonth.month);
1521
+ for (const el of own("[data-year-select]"))
1522
+ el.value = String(s.visibleMonth.year);
1523
+ for (const el of own(
1524
+ "[data-picker-action], [data-month-select], [data-year-select], [data-day-offset]"
1525
+ ))
1526
+ el.disabled = !!s.options.disabled;
1527
+ };
1528
+ update();
1529
+ cleanups.push(store.subscribe(update));
1530
+ root.addEventListener("click", click);
1531
+ root.addEventListener("change", change);
1532
+ return {
1533
+ store,
1534
+ destroy() {
1535
+ cleanups.forEach((fn) => fn());
1536
+ root.removeEventListener("click", click);
1537
+ root.removeEventListener("change", change);
1538
+ if (!provided) store.destroy();
1539
+ }
1540
+ };
1541
+ }
1542
+
1543
+ // packages/date-time-picker/src/vanilla.ts
1544
+ function mountPicker2(element, options = {}) {
1545
+ if ("getSnapshot" in options) {
1546
+ if (options.getSnapshot().options.kind !== "datetime")
1547
+ throw new RangeError("Use a datetime store with DateTimePicker");
1548
+ return mountPicker(element, options);
1549
+ }
1550
+ return mountPicker(element, { ...options, kind: "datetime" });
1551
+ }
1552
+ var mountDateTimePicker = mountPicker2;
1553
+ return __toCommonJS(vanilla_exports);
1554
+ })();