universal-datetime-picker 2.0.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,2194 @@
1
+ 'use strict';
2
+
3
+ var dayjs = require('dayjs');
4
+ var customParseFormat = require('dayjs/plugin/customParseFormat');
5
+ var isSameOrAfter = require('dayjs/plugin/isSameOrAfter');
6
+ var isSameOrBefore = require('dayjs/plugin/isSameOrBefore');
7
+ var localeData = require('dayjs/plugin/localeData');
8
+ var weekday = require('dayjs/plugin/weekday');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var dayjs__default = /*#__PURE__*/_interopDefault(dayjs);
13
+ var customParseFormat__default = /*#__PURE__*/_interopDefault(customParseFormat);
14
+ var isSameOrAfter__default = /*#__PURE__*/_interopDefault(isSameOrAfter);
15
+ var isSameOrBefore__default = /*#__PURE__*/_interopDefault(isSameOrBefore);
16
+ var localeData__default = /*#__PURE__*/_interopDefault(localeData);
17
+ var weekday__default = /*#__PURE__*/_interopDefault(weekday);
18
+
19
+ // src/core/logic/date.ts
20
+ dayjs__default.default.extend(customParseFormat__default.default);
21
+ dayjs__default.default.extend(isSameOrAfter__default.default);
22
+ dayjs__default.default.extend(isSameOrBefore__default.default);
23
+ dayjs__default.default.extend(localeData__default.default);
24
+ dayjs__default.default.extend(weekday__default.default);
25
+ var DEFAULT_FORMAT = "YYYY-MM-DD HH:mm:ss";
26
+ var DATE_FORMAT = "YYYY-MM-DD";
27
+ function resolveFormat(options) {
28
+ if (options.format) {
29
+ return options.format;
30
+ }
31
+ const mode = options.mode ?? "datetime";
32
+ const showSeconds = options.showSeconds !== false;
33
+ const use12Hours = Boolean(options.use12Hours);
34
+ const timePart = use12Hours ? showSeconds ? "hh:mm:ss A" : "hh:mm A" : showSeconds ? "HH:mm:ss" : "HH:mm";
35
+ if (mode === "date") {
36
+ return DATE_FORMAT;
37
+ }
38
+ if (mode === "time") {
39
+ return timePart;
40
+ }
41
+ return `${DATE_FORMAT} ${timePart}`;
42
+ }
43
+ function buildTimeValue(value, format) {
44
+ const hour24 = value.hour();
45
+ const { hour, isAm } = to12Hour(hour24);
46
+ return {
47
+ hour,
48
+ hour24,
49
+ minute: value.minute(),
50
+ second: value.second(),
51
+ ampm: isAm ? "AM" : "PM",
52
+ formatted: value.format(format)
53
+ };
54
+ }
55
+ var asStringDeprecationWarned = false;
56
+ function warnAsStringDeprecation() {
57
+ if (asStringDeprecationWarned) {
58
+ return;
59
+ }
60
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") {
61
+ return;
62
+ }
63
+ asStringDeprecationWarned = true;
64
+ console.warn(
65
+ "[universal-datetime-picker] Omitting `asString` currently returns a formatted string. Set `asString={true}` to keep strings, or `asString={false}` to receive a Date / TimeValue. A future major release will default to Date / TimeValue."
66
+ );
67
+ }
68
+ function parseValue(value, format = DEFAULT_FORMAT) {
69
+ if (value === void 0 || value === null || value === "") {
70
+ return null;
71
+ }
72
+ if (dayjs__default.default.isDayjs(value)) {
73
+ return value.isValid() ? value : null;
74
+ }
75
+ if (value instanceof Date) {
76
+ const parsed2 = dayjs__default.default(value);
77
+ return parsed2.isValid() ? parsed2 : null;
78
+ }
79
+ if (typeof value === "string") {
80
+ const parsed2 = dayjs__default.default(value, format, true);
81
+ if (parsed2.isValid()) {
82
+ return parsed2;
83
+ }
84
+ const fallback = dayjs__default.default(value);
85
+ return fallback.isValid() ? fallback : null;
86
+ }
87
+ const parsed = dayjs__default.default(value);
88
+ return parsed.isValid() ? parsed : null;
89
+ }
90
+ function formatValue(value, format = DEFAULT_FORMAT) {
91
+ if (!value || !value.isValid()) {
92
+ return null;
93
+ }
94
+ return value.format(format);
95
+ }
96
+ function pad2(n) {
97
+ return String(n).padStart(2, "0");
98
+ }
99
+ var HOURS_24 = Array.from({ length: 24 }, (_, i) => pad2(i));
100
+ var HOURS_12 = Array.from(
101
+ { length: 12 },
102
+ (_, i) => pad2(i === 0 ? 12 : i)
103
+ );
104
+ var MINUTES = Array.from({ length: 60 }, (_, i) => pad2(i));
105
+ function to12Hour(hour24) {
106
+ const isAm = hour24 < 12;
107
+ const hour = hour24 % 12 === 0 ? 12 : hour24 % 12;
108
+ return { hour, isAm };
109
+ }
110
+ function to24Hour(hour12, isAm) {
111
+ if (hour12 === 12) {
112
+ return isAm ? 0 : 12;
113
+ }
114
+ return isAm ? hour12 : hour12 + 12;
115
+ }
116
+ function formatLocalized(date, template, locale) {
117
+ return date.locale(locale).format(template);
118
+ }
119
+ function getWeekdayLabels(locale, weekStartsOn) {
120
+ const labels = dayjs__default.default().locale(locale).localeData().weekdaysMin();
121
+ return [...labels.slice(weekStartsOn), ...labels.slice(0, weekStartsOn)];
122
+ }
123
+ function startOfWeek(date, weekStartsOn) {
124
+ const day = date.day();
125
+ const diff = (day - weekStartsOn + 7) % 7;
126
+ return date.subtract(diff, "day").startOf("day");
127
+ }
128
+ function endOfWeek(date, weekStartsOn) {
129
+ return startOfWeek(date, weekStartsOn).add(6, "day").endOf("day");
130
+ }
131
+
132
+ // src/core/logic/calendar.ts
133
+ function isDisabledDay(date, options) {
134
+ const today = dayjs__default.default().startOf("day");
135
+ const day = date.startOf("day");
136
+ if (options.disablePastDates && day.isBefore(today, "day")) {
137
+ return true;
138
+ }
139
+ if (options.disableFutureDates && day.isAfter(today, "day")) {
140
+ return true;
141
+ }
142
+ const min = parseValue(options.minDate ?? null);
143
+ const max = parseValue(options.maxDate ?? null);
144
+ if (min && day.isBefore(min.startOf("day"), "day")) {
145
+ return true;
146
+ }
147
+ if (max && day.isAfter(max.startOf("day"), "day")) {
148
+ return true;
149
+ }
150
+ return false;
151
+ }
152
+ function buildCalendarMonth(options) {
153
+ const weekStartsOn = options.weekStartsOn ?? 0;
154
+ const monthStart = options.viewMonth.startOf("month");
155
+ const monthEnd = options.viewMonth.endOf("month");
156
+ const gridStart = startOfWeek(monthStart, weekStartsOn);
157
+ const gridEnd = endOfWeek(monthEnd, weekStartsOn);
158
+ const weeks = [];
159
+ let cursor = gridStart.clone();
160
+ while (cursor.isBefore(gridEnd) || cursor.isSame(gridEnd, "day")) {
161
+ const week = [];
162
+ for (let i = 0; i < 7; i += 1) {
163
+ const date = cursor.clone();
164
+ const isCurrentMonth = date.isSame(options.viewMonth, "month");
165
+ const isSelected = Boolean(
166
+ options.selected && date.isSame(options.selected, "day")
167
+ );
168
+ const isRangeStart = Boolean(
169
+ options.rangeStart && date.isSame(options.rangeStart, "day")
170
+ );
171
+ const isRangeEnd = Boolean(
172
+ options.rangeEnd && date.isSame(options.rangeEnd, "day")
173
+ );
174
+ const effectiveEnd = options.rangeEnd ?? options.hoverEnd ?? null;
175
+ let isInRange = false;
176
+ if (options.rangeStart && effectiveEnd) {
177
+ const rangeLow = options.rangeStart.isBefore(effectiveEnd, "day") ? options.rangeStart : effectiveEnd;
178
+ const rangeHigh = options.rangeStart.isBefore(effectiveEnd, "day") ? effectiveEnd : options.rangeStart;
179
+ isInRange = date.isAfter(rangeLow, "day") && date.isBefore(rangeHigh, "day") || date.isSame(rangeLow, "day") || date.isSame(rangeHigh, "day");
180
+ }
181
+ week.push({
182
+ date,
183
+ isCurrentMonth,
184
+ isCurrentDate: date.isSame(dayjs__default.default(), "day"),
185
+ isFuture: date.isAfter(dayjs__default.default(), "day"),
186
+ isPast: date.isBefore(dayjs__default.default(), "day"),
187
+ isWeekend: date.day() === 0 || date.day() === 6,
188
+ isDisabled: isDisabledDay(date, options),
189
+ isSelected,
190
+ isInRange,
191
+ isRangeStart,
192
+ isRangeEnd
193
+ });
194
+ cursor = cursor.add(1, "day");
195
+ }
196
+ weeks.push(week);
197
+ }
198
+ return weeks;
199
+ }
200
+
201
+ // src/core/types.ts
202
+ var DEFAULT_LABELS = {
203
+ date: "Date",
204
+ time: "Time",
205
+ clear: "Clear",
206
+ close: "Close",
207
+ ok: "OK",
208
+ start: "Start",
209
+ end: "End",
210
+ chooseDate: "Choose date",
211
+ chooseDateRange: "Choose date range",
212
+ chooseMonth: "Choose month",
213
+ chooseYear: "Choose year",
214
+ previousMonth: "Previous month",
215
+ nextMonth: "Next month",
216
+ previousYear: "Previous year",
217
+ nextYear: "Next year",
218
+ selectEnd: "Select end date"
219
+ };
220
+
221
+ // src/core/controller.ts
222
+ function padDisplay(n) {
223
+ return String(n).padStart(2, "0");
224
+ }
225
+ var PickerController = class {
226
+ constructor(options = {}) {
227
+ this.listeners = /* @__PURE__ */ new Set();
228
+ this.calPanel = "day";
229
+ this.showHours = false;
230
+ this.showMinutes = false;
231
+ this.showSecondsOpen = false;
232
+ this.showAmPm = false;
233
+ this.subscribe = (listener) => {
234
+ this.listeners.add(listener);
235
+ return () => {
236
+ this.listeners.delete(listener);
237
+ };
238
+ };
239
+ this.getSnapshot = () => this.snapshot;
240
+ /** Stable for useSyncExternalStore — returns same reference until mutate. */
241
+ this.getServerSnapshot = () => this.snapshot;
242
+ this.options = { ...options };
243
+ const mode = options.mode ?? "datetime";
244
+ const showSeconds = options.showSeconds !== false;
245
+ const use12Hours = Boolean(options.use12Hours);
246
+ const format = resolveFormat({
247
+ mode,
248
+ format: options.format,
249
+ use12Hours,
250
+ showSeconds
251
+ });
252
+ const initial = parseValue(options.value ?? options.defaultValue ?? dayjs__default.default(), format) ?? dayjs__default.default();
253
+ this.draft = initial;
254
+ this.viewMonth = initial.startOf("month");
255
+ this.focusedDay = initial;
256
+ this.tab = mode === "time" ? "time" : "date";
257
+ this.open = options.open ?? (options.inline ? true : options.defaultOpen ?? true);
258
+ this.snapshot = this.buildSnapshot();
259
+ }
260
+ setOptions(partial) {
261
+ const prev = this.options;
262
+ this.options = { ...this.options, ...partial };
263
+ if (partial.mode !== void 0 && partial.mode !== prev.mode) {
264
+ this.tab = partial.mode === "time" ? "time" : "date";
265
+ }
266
+ if (partial.open !== void 0) {
267
+ this.open = partial.open;
268
+ }
269
+ if ("value" in partial) {
270
+ if (partial.value === null) {
271
+ const fallback = dayjs__default.default();
272
+ this.draft = fallback;
273
+ this.viewMonth = fallback.startOf("month");
274
+ this.focusedDay = fallback;
275
+ } else if (partial.value !== void 0) {
276
+ const format = this.getResolvedFormat();
277
+ const parsed = parseValue(partial.value, format);
278
+ if (parsed) {
279
+ this.draft = parsed;
280
+ this.viewMonth = parsed.startOf("month");
281
+ this.focusedDay = parsed;
282
+ }
283
+ }
284
+ }
285
+ this.emit();
286
+ }
287
+ getResolvedFormat() {
288
+ return resolveFormat({
289
+ mode: this.options.mode ?? "datetime",
290
+ format: this.options.format,
291
+ use12Hours: Boolean(this.options.use12Hours),
292
+ showSeconds: this.options.showSeconds !== false
293
+ });
294
+ }
295
+ getLabels() {
296
+ return { ...DEFAULT_LABELS, ...this.options.labels };
297
+ }
298
+ emit() {
299
+ this.snapshot = this.buildSnapshot();
300
+ this.listeners.forEach((l) => l());
301
+ }
302
+ buildSnapshot() {
303
+ const mode = this.options.mode ?? "datetime";
304
+ const layout = this.options.layout ?? "combined";
305
+ const showSeconds = this.options.showSeconds !== false;
306
+ const use12Hours = Boolean(this.options.use12Hours);
307
+ const inline = Boolean(this.options.inline);
308
+ const popover = Boolean(this.options.popover);
309
+ const weekStartsOn = this.options.weekStartsOn ?? 0;
310
+ const locale = this.options.locale ?? "en";
311
+ const resolvedFormat = this.getResolvedFormat();
312
+ const labels = this.getLabels();
313
+ const weeks = buildCalendarMonth({
314
+ viewMonth: this.viewMonth,
315
+ selected: this.draft,
316
+ minDate: this.options.minDate,
317
+ maxDate: this.options.maxDate,
318
+ disablePastDates: this.options.disablePastDates,
319
+ disableFutureDates: this.options.disableFutureDates,
320
+ weekStartsOn
321
+ });
322
+ const showDate = mode !== "time";
323
+ const showTime = mode !== "date";
324
+ const useTabs = mode === "datetime" && layout === "tabs";
325
+ const showDatePanel = showDate && (!useTabs || this.tab === "date");
326
+ const showTimePanel = showTime && (!useTabs || this.tab === "time");
327
+ const hour24 = this.draft.hour();
328
+ const { hour: hour12, isAm } = to12Hour(hour24);
329
+ return {
330
+ draft: this.draft,
331
+ viewMonth: this.viewMonth,
332
+ calPanel: this.calPanel,
333
+ tab: this.tab,
334
+ showHours: this.showHours,
335
+ showMinutes: this.showMinutes,
336
+ showSecondsOpen: this.showSecondsOpen,
337
+ showAmPm: this.showAmPm,
338
+ focusedDay: this.focusedDay,
339
+ open: this.open,
340
+ mode,
341
+ layout,
342
+ showSeconds,
343
+ use12Hours,
344
+ inline,
345
+ popover,
346
+ weekStartsOn,
347
+ locale,
348
+ labels,
349
+ resolvedFormat,
350
+ asString: this.options.asString,
351
+ className: this.options.className,
352
+ theme: this.options.theme,
353
+ weeks,
354
+ weekdayLabels: getWeekdayLabels(locale, weekStartsOn),
355
+ showDate,
356
+ showTime,
357
+ useTabs,
358
+ showDatePanel,
359
+ showTimePanel,
360
+ showModeTabs: useTabs,
361
+ hour24,
362
+ hour12,
363
+ isAm,
364
+ hourOptions: use12Hours ? HOURS_12 : HOURS_24,
365
+ displayHour: use12Hours ? padDisplay(hour12) : padDisplay(hour24),
366
+ displayMinute: padDisplay(this.draft.minute()),
367
+ displaySecond: padDisplay(this.draft.second()),
368
+ minuteOptions: MINUTES
369
+ };
370
+ }
371
+ setOpen(open) {
372
+ if (this.options.inline) {
373
+ return;
374
+ }
375
+ this.open = open;
376
+ this.options.onOpenChange?.(open);
377
+ this.emit();
378
+ }
379
+ close() {
380
+ if (!this.options.inline) {
381
+ this.setOpen(false);
382
+ }
383
+ }
384
+ setTab(tab) {
385
+ this.tab = tab;
386
+ this.emit();
387
+ }
388
+ setCalPanel(panel) {
389
+ this.calPanel = panel;
390
+ this.emit();
391
+ }
392
+ setViewMonth(next) {
393
+ this.viewMonth = typeof next === "function" ? next(this.viewMonth) : next;
394
+ this.emit();
395
+ }
396
+ selectDay(day) {
397
+ this.draft = this.draft.year(day.year()).month(day.month()).date(day.date());
398
+ this.focusedDay = day;
399
+ this.emit();
400
+ }
401
+ setHour(hourValue) {
402
+ this.draft = this.draft.hour(hourValue);
403
+ this.emit();
404
+ }
405
+ setMinute(minute) {
406
+ this.draft = this.draft.minute(minute);
407
+ this.emit();
408
+ }
409
+ setSecond(second) {
410
+ this.draft = this.draft.second(second);
411
+ this.emit();
412
+ }
413
+ setAmPm(isAm) {
414
+ const { hour } = to12Hour(this.draft.hour());
415
+ this.draft = this.draft.hour(to24Hour(hour, isAm));
416
+ this.emit();
417
+ }
418
+ toggleHours() {
419
+ this.showHours = !this.showHours;
420
+ this.showMinutes = false;
421
+ this.showSecondsOpen = false;
422
+ this.showAmPm = false;
423
+ this.emit();
424
+ }
425
+ toggleMinutes() {
426
+ this.showMinutes = !this.showMinutes;
427
+ this.showHours = false;
428
+ this.showSecondsOpen = false;
429
+ this.showAmPm = false;
430
+ this.emit();
431
+ }
432
+ toggleSeconds() {
433
+ this.showSecondsOpen = !this.showSecondsOpen;
434
+ this.showHours = false;
435
+ this.showMinutes = false;
436
+ this.showAmPm = false;
437
+ this.emit();
438
+ }
439
+ toggleAmPm() {
440
+ this.showAmPm = !this.showAmPm;
441
+ this.showHours = false;
442
+ this.showMinutes = false;
443
+ this.showSecondsOpen = false;
444
+ this.emit();
445
+ }
446
+ closeTimeColumns() {
447
+ this.showHours = false;
448
+ this.showMinutes = false;
449
+ this.showSecondsOpen = false;
450
+ this.showAmPm = false;
451
+ this.emit();
452
+ }
453
+ selectHourOption(opt) {
454
+ const snap = this.snapshot;
455
+ if (snap.use12Hours) {
456
+ this.draft = this.draft.hour(to24Hour(Number(opt), snap.isAm));
457
+ } else {
458
+ this.draft = this.draft.hour(Number(opt));
459
+ }
460
+ this.showHours = false;
461
+ this.emit();
462
+ }
463
+ selectMinuteOption(opt) {
464
+ this.draft = this.draft.minute(Number(opt));
465
+ this.showMinutes = false;
466
+ this.emit();
467
+ }
468
+ selectSecondOption(opt) {
469
+ this.draft = this.draft.second(Number(opt));
470
+ this.showSecondsOpen = false;
471
+ this.emit();
472
+ }
473
+ selectAmPmOption(opt) {
474
+ const { hour } = to12Hour(this.draft.hour());
475
+ this.draft = this.draft.hour(to24Hour(hour, opt === "AM"));
476
+ this.showAmPm = false;
477
+ this.emit();
478
+ }
479
+ handleGridKeyDown(key) {
480
+ const snap = this.snapshot;
481
+ let next = this.focusedDay;
482
+ switch (key) {
483
+ case "ArrowLeft":
484
+ next = this.focusedDay.subtract(1, "day");
485
+ break;
486
+ case "ArrowRight":
487
+ next = this.focusedDay.add(1, "day");
488
+ break;
489
+ case "ArrowUp":
490
+ next = this.focusedDay.subtract(7, "day");
491
+ break;
492
+ case "ArrowDown":
493
+ next = this.focusedDay.add(7, "day");
494
+ break;
495
+ case "Home":
496
+ next = startOfWeek(this.focusedDay, snap.weekStartsOn);
497
+ break;
498
+ case "End":
499
+ next = endOfWeek(this.focusedDay, snap.weekStartsOn);
500
+ break;
501
+ case "PageUp":
502
+ next = this.focusedDay.subtract(1, "month");
503
+ this.viewMonth = next.startOf("month");
504
+ break;
505
+ case "PageDown":
506
+ next = this.focusedDay.add(1, "month");
507
+ this.viewMonth = next.startOf("month");
508
+ break;
509
+ case "Enter":
510
+ case " ": {
511
+ const cell = snap.weeks.flat().find((d) => d.date.isSame(this.focusedDay, "day"));
512
+ if (cell && !cell.isDisabled && cell.isCurrentMonth) {
513
+ this.selectDay(cell.date);
514
+ }
515
+ return true;
516
+ }
517
+ default:
518
+ return false;
519
+ }
520
+ this.focusedDay = next;
521
+ if (!next.isSame(this.viewMonth, "month")) {
522
+ this.viewMonth = next.startOf("month");
523
+ }
524
+ this.emit();
525
+ return true;
526
+ }
527
+ confirm() {
528
+ const snap = this.snapshot;
529
+ let payload;
530
+ if (snap.asString === false) {
531
+ if (snap.mode === "time") {
532
+ payload = buildTimeValue(this.draft, snap.resolvedFormat);
533
+ } else if (snap.mode === "date") {
534
+ payload = this.draft.startOf("day").toDate();
535
+ } else {
536
+ payload = this.draft.toDate();
537
+ }
538
+ } else {
539
+ if (snap.asString === void 0) {
540
+ warnAsStringDeprecation();
541
+ }
542
+ payload = formatValue(this.draft, snap.resolvedFormat);
543
+ }
544
+ this.options.onChange?.(payload);
545
+ this.close();
546
+ return payload;
547
+ }
548
+ clear() {
549
+ this.options.onChange?.(null);
550
+ this.close();
551
+ }
552
+ /** Year window helpers for month/year panels */
553
+ yearWindowStart(year) {
554
+ return Math.floor(year / 12) * 12;
555
+ }
556
+ navigatePrev() {
557
+ if (this.calPanel === "day") {
558
+ this.viewMonth = this.viewMonth.subtract(1, "month");
559
+ } else if (this.calPanel === "month") {
560
+ this.viewMonth = this.viewMonth.subtract(1, "year");
561
+ } else {
562
+ this.viewMonth = this.viewMonth.subtract(12, "year");
563
+ }
564
+ this.emit();
565
+ }
566
+ navigateNext() {
567
+ if (this.calPanel === "day") {
568
+ this.viewMonth = this.viewMonth.add(1, "month");
569
+ } else if (this.calPanel === "month") {
570
+ this.viewMonth = this.viewMonth.add(1, "year");
571
+ } else {
572
+ this.viewMonth = this.viewMonth.add(12, "year");
573
+ }
574
+ this.emit();
575
+ }
576
+ selectMonth(monthIndex) {
577
+ this.viewMonth = this.viewMonth.month(monthIndex);
578
+ this.calPanel = "day";
579
+ this.emit();
580
+ }
581
+ selectYear(year) {
582
+ this.viewMonth = this.viewMonth.year(year);
583
+ this.calPanel = "month";
584
+ this.emit();
585
+ }
586
+ };
587
+
588
+ // src/vanilla/a11y.ts
589
+ var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
590
+ var POPOVER_GAP = 8;
591
+ var POPOVER_PADDING = 8;
592
+ var DEFAULT_PICKER_WIDTH = 300;
593
+ var DEFAULT_PICKER_HEIGHT = 360;
594
+ function attachFocusTrap(container, active) {
595
+ const previouslyFocused = document.activeElement;
596
+ const focusables = () => Array.from(container.querySelectorAll(FOCUSABLE)).filter(
597
+ (el3) => !el3.hasAttribute("disabled") && el3.offsetParent !== null
598
+ );
599
+ const first = focusables()[0];
600
+ first?.focus({ preventScroll: true });
601
+ const onKeyDown = (event) => {
602
+ if (event.key !== "Tab") {
603
+ return;
604
+ }
605
+ const items = focusables();
606
+ if (items.length === 0) {
607
+ return;
608
+ }
609
+ const firstItem = items[0];
610
+ const lastItem = items[items.length - 1];
611
+ if (event.shiftKey && document.activeElement === firstItem) {
612
+ event.preventDefault();
613
+ lastItem.focus({ preventScroll: true });
614
+ } else if (!event.shiftKey && document.activeElement === lastItem) {
615
+ event.preventDefault();
616
+ firstItem.focus({ preventScroll: true });
617
+ }
618
+ };
619
+ container.addEventListener("keydown", onKeyDown);
620
+ return () => {
621
+ container.removeEventListener("keydown", onKeyDown);
622
+ previouslyFocused?.focus?.({ preventScroll: true });
623
+ };
624
+ }
625
+ function attachEscape(handler, active) {
626
+ const onKeyDown = (event) => {
627
+ if (event.key === "Escape") {
628
+ event.preventDefault();
629
+ handler();
630
+ }
631
+ };
632
+ document.addEventListener("keydown", onKeyDown);
633
+ return () => document.removeEventListener("keydown", onKeyDown);
634
+ }
635
+ function attachClickOutside(handler, active, floating, anchorEl) {
636
+ const onPointerDown = (event) => {
637
+ const target = event.target;
638
+ if (!(target instanceof Node)) {
639
+ return;
640
+ }
641
+ if (floating?.contains(target)) {
642
+ return;
643
+ }
644
+ if (anchorEl?.contains(target)) {
645
+ return;
646
+ }
647
+ handler();
648
+ };
649
+ document.addEventListener("pointerdown", onPointerDown, true);
650
+ return () => {
651
+ document.removeEventListener("pointerdown", onPointerDown, true);
652
+ };
653
+ }
654
+ function computePopoverPosition(anchorEl, pickerWidth, pickerHeight) {
655
+ const rect = anchorEl.getBoundingClientRect();
656
+ let top = rect.bottom + POPOVER_GAP;
657
+ let left = rect.left;
658
+ if (left + pickerWidth > window.innerWidth - POPOVER_PADDING) {
659
+ left = Math.max(
660
+ POPOVER_PADDING,
661
+ window.innerWidth - pickerWidth - POPOVER_PADDING
662
+ );
663
+ }
664
+ if (left < POPOVER_PADDING) {
665
+ left = POPOVER_PADDING;
666
+ }
667
+ if (top + pickerHeight > window.innerHeight - POPOVER_PADDING) {
668
+ const above = rect.top - pickerHeight - POPOVER_GAP;
669
+ if (above >= POPOVER_PADDING) {
670
+ top = above;
671
+ } else {
672
+ top = Math.max(
673
+ POPOVER_PADDING,
674
+ window.innerHeight - pickerHeight - POPOVER_PADDING
675
+ );
676
+ }
677
+ }
678
+ return { top, left };
679
+ }
680
+ function attachPopoverPosition(anchorEl, open, enabled, floating, onPosition) {
681
+ if (!anchorEl) {
682
+ onPosition(null);
683
+ return () => {
684
+ };
685
+ }
686
+ const update = () => {
687
+ if (!floating) {
688
+ onPosition(
689
+ computePopoverPosition(
690
+ anchorEl,
691
+ DEFAULT_PICKER_WIDTH,
692
+ DEFAULT_PICKER_HEIGHT
693
+ )
694
+ );
695
+ return;
696
+ }
697
+ const width = floating.offsetWidth || DEFAULT_PICKER_WIDTH;
698
+ const height = floating.offsetHeight || DEFAULT_PICKER_HEIGHT;
699
+ onPosition(computePopoverPosition(anchorEl, width, height));
700
+ };
701
+ update();
702
+ window.addEventListener("resize", update);
703
+ window.addEventListener("scroll", update, true);
704
+ let observer;
705
+ if (floating && typeof ResizeObserver !== "undefined") {
706
+ observer = new ResizeObserver(() => update());
707
+ observer.observe(floating);
708
+ }
709
+ return () => {
710
+ window.removeEventListener("resize", update);
711
+ window.removeEventListener("scroll", update, true);
712
+ observer?.disconnect();
713
+ };
714
+ }
715
+ function resolveThemeAttr(theme, anchorEl) {
716
+ if (theme === "light" || theme === "dark") {
717
+ return theme;
718
+ }
719
+ let node = anchorEl ?? null;
720
+ while (node) {
721
+ const attr = node.getAttribute("data-ctp-theme");
722
+ if (attr === "dark" || attr === "light") {
723
+ return attr;
724
+ }
725
+ node = node.parentElement;
726
+ }
727
+ if (typeof document !== "undefined") {
728
+ const root = document.documentElement.getAttribute("data-ctp-theme");
729
+ if (root === "dark" || root === "light") {
730
+ return root;
731
+ }
732
+ }
733
+ return void 0;
734
+ }
735
+ function cx(...parts) {
736
+ return parts.filter(Boolean).join(" ");
737
+ }
738
+
739
+ // src/vanilla/renderer.ts
740
+ function applyStyle(el3, style) {
741
+ if (!style) {
742
+ return;
743
+ }
744
+ if (typeof style === "string") {
745
+ el3.setAttribute("style", style);
746
+ return;
747
+ }
748
+ Object.assign(el3.style, style);
749
+ }
750
+ function el(tag, props = {}, children = []) {
751
+ const node = document.createElement(tag);
752
+ for (const [key, value] of Object.entries(props)) {
753
+ if (value === void 0 || value === null || value === false) {
754
+ continue;
755
+ }
756
+ if (key === "className") {
757
+ node.className = String(value);
758
+ } else if (key === "textContent") {
759
+ node.textContent = String(value);
760
+ } else if (key.startsWith("on") && typeof value === "function") {
761
+ const event = key.slice(2).toLowerCase();
762
+ node.addEventListener(event, value);
763
+ } else if (key === "disabled") {
764
+ node.disabled = Boolean(value);
765
+ } else if (key === "tabIndex") {
766
+ node.tabIndex = Number(value);
767
+ } else {
768
+ node.setAttribute(
769
+ key === "ariaLabel" ? "aria-label" : key === "ariaSelected" ? "aria-selected" : key === "ariaDisabled" ? "aria-disabled" : key === "ariaExpanded" ? "aria-expanded" : key === "ariaControls" ? "aria-controls" : key === "ariaHaspopup" ? "aria-haspopup" : key === "ariaModal" ? "aria-modal" : key === "ariaLabelledby" ? "aria-labelledby" : key === "ariaCurrent" ? "aria-current" : key,
770
+ String(value)
771
+ );
772
+ }
773
+ }
774
+ for (const child of children) {
775
+ if (child == null) {
776
+ continue;
777
+ }
778
+ node.append(typeof child === "string" ? child : child);
779
+ }
780
+ return node;
781
+ }
782
+ function renderHeader(controller, snap, titleId) {
783
+ const year = snap.viewMonth.year();
784
+ const windowStart = controller.yearWindowStart(year);
785
+ const windowEnd = windowStart + 11;
786
+ const now = dayjs__default.default();
787
+ const prevLabel = snap.calPanel === "day" ? snap.labels.previousMonth : snap.labels.previousYear;
788
+ const nextLabel = snap.calPanel === "day" ? snap.labels.nextMonth : snap.labels.nextYear;
789
+ let title;
790
+ if (snap.calPanel === "day") {
791
+ title = el(
792
+ "button",
793
+ {
794
+ type: "button",
795
+ className: "ctp-current-month",
796
+ id: titleId,
797
+ ariaLabel: `${formatLocalized(snap.viewMonth, "MMMM YYYY", snap.locale)}. ${snap.labels.chooseMonth}`,
798
+ onClick: () => controller.setCalPanel("month"),
799
+ textContent: formatLocalized(snap.viewMonth, "MMMM YYYY", snap.locale)
800
+ }
801
+ );
802
+ } else if (snap.calPanel === "month") {
803
+ title = el(
804
+ "button",
805
+ {
806
+ type: "button",
807
+ className: "ctp-current-month",
808
+ id: titleId,
809
+ ariaLabel: `${year}. ${snap.labels.chooseYear}`,
810
+ onClick: () => controller.setCalPanel("year"),
811
+ textContent: String(year)
812
+ }
813
+ );
814
+ } else {
815
+ title = el("span", {
816
+ className: "ctp-current-month",
817
+ id: titleId,
818
+ textContent: `${windowStart} \u2013 ${windowEnd}`
819
+ });
820
+ }
821
+ const fragment = el("div", { className: "ctp-body ctp-body-calendar-date" }, [
822
+ el("div", { className: "ctp-month-year" }, [
823
+ el("button", {
824
+ type: "button",
825
+ className: "ctp-prev-month",
826
+ ariaLabel: prevLabel,
827
+ onClick: () => controller.navigatePrev(),
828
+ textContent: "\u2039"
829
+ }),
830
+ title,
831
+ el("button", {
832
+ type: "button",
833
+ className: "ctp-next-month",
834
+ ariaLabel: nextLabel,
835
+ onClick: () => controller.navigateNext(),
836
+ textContent: "\u203A"
837
+ })
838
+ ])
839
+ ]);
840
+ if (snap.calPanel === "month") {
841
+ const grid = el("div", {
842
+ className: "ctp-month-grid",
843
+ role: "grid",
844
+ ariaLabel: snap.labels.chooseMonth
845
+ });
846
+ for (let monthIndex = 0; monthIndex < 12; monthIndex += 1) {
847
+ const monthDate = snap.viewMonth.month(monthIndex);
848
+ const isCurrent = snap.viewMonth.year() === now.year() && monthIndex === now.month();
849
+ grid.append(
850
+ el("button", {
851
+ type: "button",
852
+ role: "gridcell",
853
+ ariaCurrent: isCurrent ? "date" : void 0,
854
+ ariaLabel: formatLocalized(monthDate, "MMMM YYYY", snap.locale),
855
+ className: cx(
856
+ "ctp-box",
857
+ "ctp-box-month",
858
+ isCurrent && "ctp-current"
859
+ ),
860
+ onClick: () => controller.selectMonth(monthIndex),
861
+ textContent: formatLocalized(monthDate, "MMM", snap.locale)
862
+ })
863
+ );
864
+ }
865
+ fragment.append(grid);
866
+ } else if (snap.calPanel === "year") {
867
+ const grid = el("div", {
868
+ className: "ctp-year-grid",
869
+ role: "grid",
870
+ ariaLabel: snap.labels.chooseYear
871
+ });
872
+ for (let offset = 0; offset < 12; offset += 1) {
873
+ const y = windowStart + offset;
874
+ const isCurrent = now.year() === y;
875
+ grid.append(
876
+ el("button", {
877
+ type: "button",
878
+ role: "gridcell",
879
+ ariaCurrent: isCurrent ? "date" : void 0,
880
+ ariaLabel: String(y),
881
+ className: cx("ctp-box", "ctp-box-year", isCurrent && "ctp-current"),
882
+ onClick: () => controller.selectYear(y),
883
+ textContent: String(y)
884
+ })
885
+ );
886
+ }
887
+ fragment.append(grid);
888
+ } else {
889
+ const grid = el("div", {
890
+ className: "ctp-main-calendar",
891
+ role: "grid",
892
+ ariaLabel: snap.labels.chooseDate,
893
+ tabIndex: 0,
894
+ onKeydown: (event) => {
895
+ const ke = event;
896
+ if (controller.handleGridKeyDown(ke.key)) {
897
+ ke.preventDefault();
898
+ }
899
+ }
900
+ });
901
+ for (const label of snap.weekdayLabels) {
902
+ grid.append(
903
+ el("div", {
904
+ className: "ctp-box ctp-box-days",
905
+ role: "columnheader",
906
+ textContent: label
907
+ })
908
+ );
909
+ }
910
+ for (const week of snap.weeks) {
911
+ for (const dayData of week) {
912
+ const selected = dayData.isSelected;
913
+ const focused = dayData.date.isSame(snap.focusedDay, "day");
914
+ const disabled = !dayData.isCurrentMonth || dayData.isDisabled;
915
+ grid.append(
916
+ el("button", {
917
+ type: "button",
918
+ role: "gridcell",
919
+ tabIndex: focused ? 0 : -1,
920
+ ariaSelected: selected,
921
+ ariaDisabled: disabled,
922
+ disabled,
923
+ ariaLabel: formatLocalized(
924
+ dayData.date,
925
+ "dddd, MMMM D, YYYY",
926
+ snap.locale
927
+ ),
928
+ className: cx(
929
+ "ctp-box",
930
+ "ctp-box-date",
931
+ !dayData.isCurrentMonth && "not-current-month",
932
+ selected && "selected",
933
+ dayData.isDisabled && "disabled-date",
934
+ dayData.isWeekend && "weekend-day",
935
+ dayData.isCurrentDate && "ctp-today",
936
+ dayData.isInRange && "ctp-in-range",
937
+ dayData.isRangeStart && "ctp-range-start",
938
+ dayData.isRangeEnd && "ctp-range-end"
939
+ ),
940
+ onClick: () => {
941
+ if (!disabled) {
942
+ controller.selectDay(dayData.date);
943
+ }
944
+ },
945
+ textContent: dayData.date.format("D")
946
+ })
947
+ );
948
+ }
949
+ }
950
+ fragment.append(grid);
951
+ }
952
+ return fragment;
953
+ }
954
+ function renderTimeColumn(label, open, display, options, onToggle, onSelect) {
955
+ const listId = `ctp-${label}-${Math.random().toString(36).slice(2, 8)}`;
956
+ const cls = label === "am-pm" ? "am-pm" : label.endsWith("s") ? label.slice(0, -1) : label;
957
+ const list = el(
958
+ "div",
959
+ {
960
+ id: listId,
961
+ role: "listbox",
962
+ ariaLabel: label,
963
+ className: cx(`ctp-overflow-${label}`, !open && "not-visible")
964
+ },
965
+ options.map(
966
+ (opt) => el("button", {
967
+ type: "button",
968
+ role: "option",
969
+ ariaSelected: opt === display,
970
+ className: `ctp-${cls}`,
971
+ onClick: () => onSelect(opt),
972
+ textContent: opt
973
+ })
974
+ )
975
+ );
976
+ return el(
977
+ "div",
978
+ { className: cx("ctp-box", "ctp-box-time", !open && "not-opened") },
979
+ [
980
+ el("button", {
981
+ type: "button",
982
+ className: `ctp-${cls} ctp-initial-time`,
983
+ ariaHaspopup: "listbox",
984
+ ariaExpanded: open,
985
+ ariaControls: listId,
986
+ ariaLabel: `Select ${label}`,
987
+ onClick: onToggle,
988
+ textContent: display
989
+ }),
990
+ list
991
+ ]
992
+ );
993
+ }
994
+ function renderTimePanel(controller, snap, titleId) {
995
+ const body = el("div", { className: "ctp-body ctp-body-calendar-time" });
996
+ if (snap.showDatePanel) {
997
+ body.append(
998
+ el("div", { className: "ctp-section-label", textContent: snap.labels.time })
999
+ );
1000
+ } else {
1001
+ body.append(
1002
+ el("span", {
1003
+ className: "ctp-visually-hidden",
1004
+ id: titleId,
1005
+ textContent: snap.labels.time
1006
+ })
1007
+ );
1008
+ }
1009
+ const header = el("div", { className: "ctp-main-time-header" }, [
1010
+ el("div", { className: "ctp-box", textContent: "Hr" }),
1011
+ el("div", { className: "ctp-box", textContent: "Min" }),
1012
+ snap.showSeconds ? el("div", { className: "ctp-box", textContent: "Sec" }) : null,
1013
+ snap.use12Hours ? el("div", { className: "ctp-box", textContent: "AM/PM" }) : null
1014
+ ]);
1015
+ const timeBody = el("div", { className: "ctp-main-time-body" }, [
1016
+ renderTimeColumn(
1017
+ "hours",
1018
+ snap.showHours,
1019
+ snap.displayHour,
1020
+ snap.hourOptions,
1021
+ () => controller.toggleHours(),
1022
+ (opt) => controller.selectHourOption(opt)
1023
+ ),
1024
+ renderTimeColumn(
1025
+ "minutes",
1026
+ snap.showMinutes,
1027
+ snap.displayMinute,
1028
+ snap.minuteOptions,
1029
+ () => controller.toggleMinutes(),
1030
+ (opt) => controller.selectMinuteOption(opt)
1031
+ ),
1032
+ snap.showSeconds ? renderTimeColumn(
1033
+ "seconds",
1034
+ snap.showSecondsOpen,
1035
+ snap.displaySecond,
1036
+ snap.minuteOptions,
1037
+ () => controller.toggleSeconds(),
1038
+ (opt) => controller.selectSecondOption(opt)
1039
+ ) : null,
1040
+ snap.use12Hours ? renderTimeColumn(
1041
+ "am-pm",
1042
+ snap.showAmPm,
1043
+ snap.isAm ? "AM" : "PM",
1044
+ ["AM", "PM"],
1045
+ () => controller.toggleAmPm(),
1046
+ (opt) => controller.selectAmPmOption(opt)
1047
+ ) : null
1048
+ ]);
1049
+ body.append(
1050
+ el("div", { className: "ctp-main-time" }, [header, timeBody])
1051
+ );
1052
+ return body;
1053
+ }
1054
+ function renderPicker(controller, snap, titleId, position, options) {
1055
+ const themeAttr = resolveThemeAttr(options.theme, options.anchorEl);
1056
+ const picker = el("div", {
1057
+ className: cx(
1058
+ "ctp-calendar-time-picker",
1059
+ snap.popover && !snap.inline && "ctp-popover",
1060
+ snap.use12Hours && "ctp-use-12h",
1061
+ snap.mode === "time" && "ctp-mode-time",
1062
+ !snap.showSeconds && "ctp-no-seconds",
1063
+ snap.showDatePanel && snap.showTimePanel && "ctp-layout-combined",
1064
+ snap.className
1065
+ ),
1066
+ role: snap.inline ? void 0 : "dialog",
1067
+ ariaModal: snap.inline ? void 0 : true,
1068
+ ariaLabelledby: titleId
1069
+ });
1070
+ if (themeAttr) {
1071
+ picker.setAttribute("data-ctp-theme", themeAttr);
1072
+ }
1073
+ applyStyle(picker, options.style);
1074
+ if (snap.popover && !snap.inline && position) {
1075
+ picker.style.position = "fixed";
1076
+ picker.style.top = `${position.top}px`;
1077
+ picker.style.left = `${position.left}px`;
1078
+ }
1079
+ if (snap.showModeTabs) {
1080
+ picker.append(
1081
+ el("div", { className: "ctp-header" }, [
1082
+ el(
1083
+ "div",
1084
+ {
1085
+ className: "ctp-button-container",
1086
+ role: "tablist",
1087
+ ariaLabel: "Picker mode"
1088
+ },
1089
+ [
1090
+ el("button", {
1091
+ type: "button",
1092
+ role: "tab",
1093
+ ariaSelected: snap.tab === "date",
1094
+ className: cx("ctp-date", snap.tab === "date" && "ctp-active"),
1095
+ onClick: () => controller.setTab("date"),
1096
+ textContent: snap.labels.date
1097
+ }),
1098
+ el("button", {
1099
+ type: "button",
1100
+ role: "tab",
1101
+ ariaSelected: snap.tab === "time",
1102
+ className: cx("ctp-time", snap.tab === "time" && "ctp-active"),
1103
+ onClick: () => controller.setTab("time"),
1104
+ textContent: snap.labels.time
1105
+ })
1106
+ ]
1107
+ )
1108
+ ])
1109
+ );
1110
+ }
1111
+ if (snap.showDatePanel) {
1112
+ picker.append(renderHeader(controller, snap, titleId));
1113
+ }
1114
+ if (snap.showTimePanel) {
1115
+ picker.append(renderTimePanel(controller, snap, titleId));
1116
+ }
1117
+ const footerChildren = [
1118
+ el("button", {
1119
+ type: "button",
1120
+ className: "close-button",
1121
+ onClick: () => controller.clear(),
1122
+ textContent: snap.labels.clear
1123
+ })
1124
+ ];
1125
+ if (!snap.inline) {
1126
+ footerChildren.push(
1127
+ el("button", {
1128
+ type: "button",
1129
+ className: "ctp-cancel",
1130
+ onClick: () => controller.close(),
1131
+ textContent: snap.labels.close
1132
+ })
1133
+ );
1134
+ }
1135
+ footerChildren.push(
1136
+ el("button", {
1137
+ type: "button",
1138
+ onClick: () => controller.confirm(),
1139
+ textContent: snap.labels.ok
1140
+ })
1141
+ );
1142
+ picker.append(el("div", { className: "ctp-footer" }, footerChildren));
1143
+ return picker;
1144
+ }
1145
+ function createDateTimePicker(target, options = {}) {
1146
+ const controller = new PickerController(options);
1147
+ const titleId = `ctp-title-${Math.random().toString(36).slice(2, 9)}`;
1148
+ let cleanups = [];
1149
+ let position = null;
1150
+ let host = null;
1151
+ let portalRoot = null;
1152
+ const teardown = () => {
1153
+ cleanups.forEach((c) => c());
1154
+ cleanups = [];
1155
+ portalRoot?.remove();
1156
+ portalRoot = null;
1157
+ if (host && host.parentElement === target) {
1158
+ host.remove();
1159
+ }
1160
+ host = null;
1161
+ };
1162
+ const paint = () => {
1163
+ teardown();
1164
+ const snap = controller.getSnapshot();
1165
+ if (!snap.open && !snap.inline) {
1166
+ return;
1167
+ }
1168
+ const opts = { ...options };
1169
+ const picker = renderPicker(
1170
+ controller,
1171
+ snap,
1172
+ titleId,
1173
+ position,
1174
+ opts
1175
+ );
1176
+ if (snap.inline) {
1177
+ host = picker;
1178
+ target.append(picker);
1179
+ return;
1180
+ }
1181
+ if (snap.popover) {
1182
+ portalRoot = picker;
1183
+ document.body.append(picker);
1184
+ cleanups.push(
1185
+ attachFocusTrap(picker),
1186
+ attachEscape(() => controller.close()),
1187
+ attachClickOutside(
1188
+ () => controller.close(),
1189
+ true,
1190
+ picker,
1191
+ options.anchorEl
1192
+ ),
1193
+ attachPopoverPosition(
1194
+ options.anchorEl,
1195
+ true,
1196
+ true,
1197
+ picker,
1198
+ (pos) => {
1199
+ position = pos;
1200
+ if (pos) {
1201
+ picker.style.position = "fixed";
1202
+ picker.style.top = `${pos.top}px`;
1203
+ picker.style.left = `${pos.left}px`;
1204
+ }
1205
+ }
1206
+ )
1207
+ );
1208
+ return;
1209
+ }
1210
+ const themeAttr = resolveThemeAttr(options.theme, options.anchorEl);
1211
+ const backdrop = el("div", {
1212
+ className: "ctp-calendar-time-picker-absolute-container",
1213
+ onClick: (event) => {
1214
+ if (event.target === event.currentTarget) {
1215
+ controller.close();
1216
+ }
1217
+ }
1218
+ });
1219
+ if (themeAttr) {
1220
+ backdrop.setAttribute("data-ctp-theme", themeAttr);
1221
+ }
1222
+ backdrop.append(picker);
1223
+ portalRoot = backdrop;
1224
+ document.body.append(backdrop);
1225
+ cleanups.push(
1226
+ attachFocusTrap(picker),
1227
+ attachEscape(() => controller.close())
1228
+ );
1229
+ };
1230
+ const unsub = controller.subscribe(paint);
1231
+ paint();
1232
+ return {
1233
+ update(partial) {
1234
+ Object.assign(options, partial);
1235
+ controller.setOptions(partial);
1236
+ },
1237
+ destroy() {
1238
+ unsub();
1239
+ teardown();
1240
+ },
1241
+ getController: () => controller
1242
+ };
1243
+ }
1244
+
1245
+ // src/core/rangeController.ts
1246
+ var RangeController = class {
1247
+ constructor(options = {}) {
1248
+ this.listeners = /* @__PURE__ */ new Set();
1249
+ this.hoverEnd = null;
1250
+ this.calPanel = "day";
1251
+ this.subscribe = (listener) => {
1252
+ this.listeners.add(listener);
1253
+ return () => {
1254
+ this.listeners.delete(listener);
1255
+ };
1256
+ };
1257
+ this.getSnapshot = () => this.snapshot;
1258
+ this.getServerSnapshot = () => this.snapshot;
1259
+ this.options = { ...options };
1260
+ const format = options.format ?? DATE_FORMAT;
1261
+ const parsed = this.parseRange(options.value ?? options.defaultValue, format);
1262
+ this.start = parsed.start;
1263
+ this.end = parsed.end;
1264
+ this.viewMonth = (parsed.start ?? dayjs__default.default()).startOf("month");
1265
+ this.focusedDay = parsed.start ?? dayjs__default.default();
1266
+ this.open = options.open ?? (options.inline ? true : options.defaultOpen ?? true);
1267
+ this.snapshot = this.buildSnapshot();
1268
+ }
1269
+ parseRange(range, format) {
1270
+ return {
1271
+ start: parseValue(range?.start ?? null, format),
1272
+ end: parseValue(range?.end ?? null, format)
1273
+ };
1274
+ }
1275
+ setOptions(partial) {
1276
+ this.options = { ...this.options, ...partial };
1277
+ if (partial.open !== void 0) {
1278
+ this.open = partial.open;
1279
+ }
1280
+ if ("value" in partial) {
1281
+ if (partial.value === null) {
1282
+ this.start = null;
1283
+ this.end = null;
1284
+ this.hoverEnd = null;
1285
+ } else if (partial.value !== void 0) {
1286
+ const format = this.options.format ?? DATE_FORMAT;
1287
+ const parsed = this.parseRange(partial.value, format);
1288
+ this.start = parsed.start;
1289
+ this.end = parsed.end;
1290
+ if (parsed.start) {
1291
+ this.viewMonth = parsed.start.startOf("month");
1292
+ this.focusedDay = parsed.start;
1293
+ }
1294
+ }
1295
+ }
1296
+ this.emit();
1297
+ }
1298
+ getLabels() {
1299
+ return { ...DEFAULT_LABELS, ...this.options.labels };
1300
+ }
1301
+ emit() {
1302
+ this.snapshot = this.buildSnapshot();
1303
+ this.listeners.forEach((l) => l());
1304
+ }
1305
+ buildSnapshot() {
1306
+ const format = this.options.format ?? DATE_FORMAT;
1307
+ const weekStartsOn = this.options.weekStartsOn ?? 0;
1308
+ const locale = this.options.locale ?? "en";
1309
+ const weeks = buildCalendarMonth({
1310
+ viewMonth: this.viewMonth,
1311
+ rangeStart: this.start,
1312
+ rangeEnd: this.end,
1313
+ hoverEnd: this.start && !this.end ? this.hoverEnd : null,
1314
+ minDate: this.options.minDate,
1315
+ maxDate: this.options.maxDate,
1316
+ disablePastDates: this.options.disablePastDates,
1317
+ disableFutureDates: this.options.disableFutureDates,
1318
+ weekStartsOn
1319
+ });
1320
+ return {
1321
+ start: this.start,
1322
+ end: this.end,
1323
+ hoverEnd: this.hoverEnd,
1324
+ viewMonth: this.viewMonth,
1325
+ focusedDay: this.focusedDay,
1326
+ calPanel: this.calPanel,
1327
+ open: this.open,
1328
+ inline: Boolean(this.options.inline),
1329
+ format,
1330
+ asString: this.options.asString,
1331
+ weekStartsOn,
1332
+ locale,
1333
+ labels: this.getLabels(),
1334
+ className: this.options.className,
1335
+ weeks,
1336
+ weekdayLabels: getWeekdayLabels(locale, weekStartsOn)
1337
+ };
1338
+ }
1339
+ setOpen(open) {
1340
+ if (this.options.inline) {
1341
+ return;
1342
+ }
1343
+ this.open = open;
1344
+ this.options.onOpenChange?.(open);
1345
+ this.emit();
1346
+ }
1347
+ close() {
1348
+ if (!this.options.inline) {
1349
+ this.setOpen(false);
1350
+ }
1351
+ }
1352
+ setCalPanel(panel) {
1353
+ this.calPanel = panel;
1354
+ this.emit();
1355
+ }
1356
+ setViewMonth(next) {
1357
+ this.viewMonth = typeof next === "function" ? next(this.viewMonth) : next;
1358
+ this.emit();
1359
+ }
1360
+ navigatePrev() {
1361
+ if (this.calPanel === "day") {
1362
+ this.viewMonth = this.viewMonth.subtract(1, "month");
1363
+ } else if (this.calPanel === "month") {
1364
+ this.viewMonth = this.viewMonth.subtract(1, "year");
1365
+ } else {
1366
+ this.viewMonth = this.viewMonth.subtract(12, "year");
1367
+ }
1368
+ this.emit();
1369
+ }
1370
+ navigateNext() {
1371
+ if (this.calPanel === "day") {
1372
+ this.viewMonth = this.viewMonth.add(1, "month");
1373
+ } else if (this.calPanel === "month") {
1374
+ this.viewMonth = this.viewMonth.add(1, "year");
1375
+ } else {
1376
+ this.viewMonth = this.viewMonth.add(12, "year");
1377
+ }
1378
+ this.emit();
1379
+ }
1380
+ selectMonth(monthIndex) {
1381
+ this.viewMonth = this.viewMonth.month(monthIndex);
1382
+ this.calPanel = "day";
1383
+ this.emit();
1384
+ }
1385
+ selectYear(year) {
1386
+ this.viewMonth = this.viewMonth.year(year);
1387
+ this.calPanel = "month";
1388
+ this.emit();
1389
+ }
1390
+ emitRange(nextStart, nextEnd) {
1391
+ if (this.options.asString === false) {
1392
+ this.options.onChange?.({
1393
+ start: nextStart ? nextStart.toDate() : null,
1394
+ end: nextEnd ? nextEnd.toDate() : null
1395
+ });
1396
+ } else {
1397
+ if (this.options.asString === void 0) {
1398
+ warnAsStringDeprecation();
1399
+ }
1400
+ const format = this.options.format ?? DATE_FORMAT;
1401
+ this.options.onChange?.({
1402
+ start: formatValue(nextStart, format),
1403
+ end: formatValue(nextEnd, format)
1404
+ });
1405
+ }
1406
+ }
1407
+ pickDay(day) {
1408
+ if (!this.start || this.start && this.end) {
1409
+ this.start = day;
1410
+ this.end = null;
1411
+ this.hoverEnd = null;
1412
+ this.focusedDay = day;
1413
+ } else if (day.isBefore(this.start, "day")) {
1414
+ this.start = day;
1415
+ this.end = null;
1416
+ this.hoverEnd = null;
1417
+ this.focusedDay = day;
1418
+ } else {
1419
+ this.end = day;
1420
+ this.hoverEnd = null;
1421
+ this.focusedDay = day;
1422
+ }
1423
+ this.emit();
1424
+ }
1425
+ setHoverEnd(day) {
1426
+ if (this.start && !this.end) {
1427
+ this.hoverEnd = day;
1428
+ this.emit();
1429
+ }
1430
+ }
1431
+ handleGridKeyDown(key) {
1432
+ const snap = this.snapshot;
1433
+ let next = this.focusedDay;
1434
+ switch (key) {
1435
+ case "ArrowLeft":
1436
+ next = this.focusedDay.subtract(1, "day");
1437
+ break;
1438
+ case "ArrowRight":
1439
+ next = this.focusedDay.add(1, "day");
1440
+ break;
1441
+ case "ArrowUp":
1442
+ next = this.focusedDay.subtract(7, "day");
1443
+ break;
1444
+ case "ArrowDown":
1445
+ next = this.focusedDay.add(7, "day");
1446
+ break;
1447
+ case "Home":
1448
+ next = startOfWeek(this.focusedDay, snap.weekStartsOn);
1449
+ break;
1450
+ case "End":
1451
+ next = endOfWeek(this.focusedDay, snap.weekStartsOn);
1452
+ break;
1453
+ case "PageUp":
1454
+ next = this.focusedDay.subtract(1, "month");
1455
+ this.viewMonth = next.startOf("month");
1456
+ break;
1457
+ case "PageDown":
1458
+ next = this.focusedDay.add(1, "month");
1459
+ this.viewMonth = next.startOf("month");
1460
+ break;
1461
+ case "Enter":
1462
+ case " ": {
1463
+ const cell = snap.weeks.flat().find((d) => d.date.isSame(this.focusedDay, "day"));
1464
+ if (cell && !cell.isDisabled && cell.isCurrentMonth) {
1465
+ this.pickDay(cell.date);
1466
+ }
1467
+ return true;
1468
+ }
1469
+ default:
1470
+ return false;
1471
+ }
1472
+ this.focusedDay = next;
1473
+ if (!next.isSame(this.viewMonth, "month")) {
1474
+ this.viewMonth = next.startOf("month");
1475
+ }
1476
+ this.emit();
1477
+ return true;
1478
+ }
1479
+ confirm() {
1480
+ if (!this.start || !this.end) {
1481
+ return null;
1482
+ }
1483
+ this.emitRange(this.start, this.end);
1484
+ this.close();
1485
+ return {
1486
+ start: this.start.toDate(),
1487
+ end: this.end.toDate()
1488
+ };
1489
+ }
1490
+ clear() {
1491
+ this.start = null;
1492
+ this.end = null;
1493
+ this.hoverEnd = null;
1494
+ this.emitRange(null, null);
1495
+ this.emit();
1496
+ this.close();
1497
+ }
1498
+ };
1499
+
1500
+ // src/vanilla/rangeRenderer.ts
1501
+ function el2(tag, props = {}, children = []) {
1502
+ const node = document.createElement(tag);
1503
+ for (const [key, value] of Object.entries(props)) {
1504
+ if (value === void 0 || value === null || value === false) {
1505
+ continue;
1506
+ }
1507
+ if (key === "className") {
1508
+ node.className = String(value);
1509
+ } else if (key === "textContent") {
1510
+ node.textContent = String(value);
1511
+ } else if (key.startsWith("on") && typeof value === "function") {
1512
+ node.addEventListener(
1513
+ key.slice(2).toLowerCase(),
1514
+ value
1515
+ );
1516
+ } else if (key === "disabled") {
1517
+ node.disabled = Boolean(value);
1518
+ } else if (key === "tabIndex") {
1519
+ node.tabIndex = Number(value);
1520
+ } else {
1521
+ const attr = key === "ariaLabel" ? "aria-label" : key === "ariaSelected" ? "aria-selected" : key === "ariaDisabled" ? "aria-disabled" : key === "ariaModal" ? "aria-modal" : key === "ariaLabelledby" ? "aria-labelledby" : key === "ariaCurrent" ? "aria-current" : key;
1522
+ node.setAttribute(attr, String(value));
1523
+ }
1524
+ }
1525
+ for (const child of children) {
1526
+ if (child == null) {
1527
+ continue;
1528
+ }
1529
+ node.append(typeof child === "string" ? child : child);
1530
+ }
1531
+ return node;
1532
+ }
1533
+ function applyStyle2(node, style) {
1534
+ if (!style) {
1535
+ return;
1536
+ }
1537
+ if (typeof style === "string") {
1538
+ node.setAttribute("style", style);
1539
+ return;
1540
+ }
1541
+ Object.assign(node.style, style);
1542
+ }
1543
+ function createDateTimeRangePicker(target, options = {}) {
1544
+ const controller = new RangeController(options);
1545
+ const titleId = `ctp-range-${Math.random().toString(36).slice(2, 9)}`;
1546
+ let cleanups = [];
1547
+ let host = null;
1548
+ let portalRoot = null;
1549
+ const teardown = () => {
1550
+ cleanups.forEach((c) => c());
1551
+ cleanups = [];
1552
+ portalRoot?.remove();
1553
+ portalRoot = null;
1554
+ host?.remove();
1555
+ host = null;
1556
+ };
1557
+ const paint = () => {
1558
+ teardown();
1559
+ const snap = controller.getSnapshot();
1560
+ if (!snap.open && !snap.inline) {
1561
+ return;
1562
+ }
1563
+ const now = dayjs__default.default();
1564
+ const year = snap.viewMonth.year();
1565
+ const windowStart = Math.floor(year / 12) * 12;
1566
+ const windowEnd = windowStart + 11;
1567
+ const statusText = snap.start && !snap.end ? snap.labels.selectEnd : `${snap.start ? formatLocalized(snap.start, "MMM D, YYYY", snap.locale) : snap.labels.start} \u2014 ${snap.end ? formatLocalized(snap.end, "MMM D, YYYY", snap.locale) : snap.labels.end}`;
1568
+ let title;
1569
+ if (snap.calPanel === "day") {
1570
+ title = el2("button", {
1571
+ type: "button",
1572
+ className: "ctp-current-month",
1573
+ ariaLabel: `${formatLocalized(snap.viewMonth, "MMMM YYYY", snap.locale)}. ${snap.labels.chooseMonth}`,
1574
+ onClick: () => controller.setCalPanel("month"),
1575
+ textContent: formatLocalized(snap.viewMonth, "MMMM YYYY", snap.locale)
1576
+ });
1577
+ } else if (snap.calPanel === "month") {
1578
+ title = el2("button", {
1579
+ type: "button",
1580
+ className: "ctp-current-month",
1581
+ ariaLabel: `${year}. ${snap.labels.chooseYear}`,
1582
+ onClick: () => controller.setCalPanel("year"),
1583
+ textContent: String(year)
1584
+ });
1585
+ } else {
1586
+ title = el2("span", {
1587
+ className: "ctp-current-month",
1588
+ textContent: `${windowStart} \u2013 ${windowEnd}`
1589
+ });
1590
+ }
1591
+ const body = el2("div", { className: "ctp-body ctp-body-calendar-date" }, [
1592
+ el2("div", { className: "ctp-month-year" }, [
1593
+ el2("button", {
1594
+ type: "button",
1595
+ className: "ctp-prev-month",
1596
+ ariaLabel: snap.calPanel === "day" ? snap.labels.previousMonth : snap.labels.previousYear,
1597
+ onClick: () => controller.navigatePrev(),
1598
+ textContent: "\u2039"
1599
+ }),
1600
+ title,
1601
+ el2("button", {
1602
+ type: "button",
1603
+ className: "ctp-next-month",
1604
+ ariaLabel: snap.calPanel === "day" ? snap.labels.nextMonth : snap.labels.nextYear,
1605
+ onClick: () => controller.navigateNext(),
1606
+ textContent: "\u203A"
1607
+ })
1608
+ ])
1609
+ ]);
1610
+ if (snap.calPanel === "day") {
1611
+ const grid = el2("div", {
1612
+ className: "ctp-main-calendar",
1613
+ role: "grid",
1614
+ ariaLabel: snap.labels.chooseDateRange,
1615
+ tabIndex: 0,
1616
+ onKeydown: (event) => {
1617
+ const ke = event;
1618
+ if (controller.handleGridKeyDown(ke.key)) {
1619
+ ke.preventDefault();
1620
+ }
1621
+ }
1622
+ });
1623
+ for (const label of snap.weekdayLabels) {
1624
+ grid.append(
1625
+ el2("div", {
1626
+ className: "ctp-box ctp-box-days",
1627
+ role: "columnheader",
1628
+ textContent: label
1629
+ })
1630
+ );
1631
+ }
1632
+ for (const week of snap.weeks) {
1633
+ for (const dayData of week) {
1634
+ const focused = dayData.date.isSame(snap.focusedDay, "day");
1635
+ const disabled = !dayData.isCurrentMonth || dayData.isDisabled;
1636
+ grid.append(
1637
+ el2("button", {
1638
+ type: "button",
1639
+ role: "gridcell",
1640
+ tabIndex: focused ? 0 : -1,
1641
+ ariaSelected: Boolean(
1642
+ dayData.isRangeStart || dayData.isRangeEnd
1643
+ ),
1644
+ ariaDisabled: disabled,
1645
+ disabled,
1646
+ ariaLabel: formatLocalized(
1647
+ dayData.date,
1648
+ "dddd, MMMM D, YYYY",
1649
+ snap.locale
1650
+ ),
1651
+ className: cx(
1652
+ "ctp-box",
1653
+ "ctp-box-date",
1654
+ !dayData.isCurrentMonth && "not-current-month",
1655
+ dayData.isDisabled && "disabled-date",
1656
+ dayData.isWeekend && "weekend-day",
1657
+ dayData.isCurrentDate && "ctp-today",
1658
+ dayData.isInRange && "ctp-in-range",
1659
+ dayData.isRangeStart && "ctp-range-start",
1660
+ dayData.isRangeEnd && "ctp-range-end"
1661
+ ),
1662
+ onClick: () => {
1663
+ if (!disabled) {
1664
+ controller.pickDay(dayData.date);
1665
+ }
1666
+ },
1667
+ onMouseenter: () => {
1668
+ if (!disabled && snap.start && !snap.end) {
1669
+ controller.setHoverEnd(dayData.date);
1670
+ }
1671
+ },
1672
+ textContent: dayData.date.format("D")
1673
+ })
1674
+ );
1675
+ }
1676
+ }
1677
+ body.append(grid);
1678
+ } else if (snap.calPanel === "month") {
1679
+ const grid = el2("div", {
1680
+ className: "ctp-month-grid",
1681
+ role: "grid",
1682
+ ariaLabel: snap.labels.chooseMonth
1683
+ });
1684
+ for (let monthIndex = 0; monthIndex < 12; monthIndex += 1) {
1685
+ const monthDate = snap.viewMonth.month(monthIndex);
1686
+ const isCurrent = snap.viewMonth.year() === now.year() && monthIndex === now.month();
1687
+ grid.append(
1688
+ el2("button", {
1689
+ type: "button",
1690
+ role: "gridcell",
1691
+ ariaCurrent: isCurrent ? "date" : void 0,
1692
+ className: cx(
1693
+ "ctp-box",
1694
+ "ctp-box-month",
1695
+ isCurrent && "ctp-current"
1696
+ ),
1697
+ onClick: () => controller.selectMonth(monthIndex),
1698
+ textContent: formatLocalized(monthDate, "MMM", snap.locale)
1699
+ })
1700
+ );
1701
+ }
1702
+ body.append(grid);
1703
+ } else {
1704
+ const grid = el2("div", {
1705
+ className: "ctp-year-grid",
1706
+ role: "grid",
1707
+ ariaLabel: snap.labels.chooseYear
1708
+ });
1709
+ for (let offset = 0; offset < 12; offset += 1) {
1710
+ const y = windowStart + offset;
1711
+ grid.append(
1712
+ el2("button", {
1713
+ type: "button",
1714
+ role: "gridcell",
1715
+ ariaCurrent: now.year() === y ? "date" : void 0,
1716
+ className: cx(
1717
+ "ctp-box",
1718
+ "ctp-box-year",
1719
+ now.year() === y && "ctp-current"
1720
+ ),
1721
+ onClick: () => controller.selectYear(y),
1722
+ textContent: String(y)
1723
+ })
1724
+ );
1725
+ }
1726
+ body.append(grid);
1727
+ }
1728
+ const footerChildren = [
1729
+ el2("button", {
1730
+ type: "button",
1731
+ className: "close-button",
1732
+ onClick: () => controller.clear(),
1733
+ textContent: snap.labels.clear
1734
+ })
1735
+ ];
1736
+ if (!snap.inline) {
1737
+ footerChildren.push(
1738
+ el2("button", {
1739
+ type: "button",
1740
+ className: "ctp-cancel",
1741
+ onClick: () => controller.close(),
1742
+ textContent: snap.labels.close
1743
+ })
1744
+ );
1745
+ }
1746
+ footerChildren.push(
1747
+ el2("button", {
1748
+ type: "button",
1749
+ disabled: !snap.start || !snap.end,
1750
+ onClick: () => controller.confirm(),
1751
+ textContent: snap.labels.ok
1752
+ })
1753
+ );
1754
+ const picker = el2(
1755
+ "div",
1756
+ {
1757
+ className: cx("ctp-calendar-time-picker", snap.className),
1758
+ role: snap.inline ? void 0 : "dialog",
1759
+ ariaModal: snap.inline ? void 0 : true,
1760
+ ariaLabelledby: titleId
1761
+ },
1762
+ [
1763
+ el2("div", { className: "ctp-header" }, [
1764
+ el2("span", {
1765
+ id: titleId,
1766
+ className: "ctp-range-title",
1767
+ textContent: statusText
1768
+ })
1769
+ ]),
1770
+ body,
1771
+ el2("div", { className: "ctp-footer" }, footerChildren)
1772
+ ]
1773
+ );
1774
+ applyStyle2(picker, options.style);
1775
+ if (snap.inline) {
1776
+ host = picker;
1777
+ target.append(picker);
1778
+ return;
1779
+ }
1780
+ const backdrop = el2("div", {
1781
+ className: "ctp-calendar-time-picker-absolute-container",
1782
+ onClick: (event) => {
1783
+ if (event.target === event.currentTarget) {
1784
+ controller.close();
1785
+ }
1786
+ }
1787
+ });
1788
+ backdrop.append(picker);
1789
+ portalRoot = backdrop;
1790
+ document.body.append(backdrop);
1791
+ cleanups.push(
1792
+ attachFocusTrap(picker),
1793
+ attachEscape(() => controller.close())
1794
+ );
1795
+ };
1796
+ const unsub = controller.subscribe(paint);
1797
+ paint();
1798
+ return {
1799
+ update(partial) {
1800
+ Object.assign(options, partial);
1801
+ controller.setOptions(partial);
1802
+ },
1803
+ destroy() {
1804
+ unsub();
1805
+ teardown();
1806
+ },
1807
+ getController: () => controller
1808
+ };
1809
+ }
1810
+
1811
+ // src/wc/index.ts
1812
+ function boolAttr(el3, name, fallback = false) {
1813
+ if (!el3.hasAttribute(name)) {
1814
+ return fallback;
1815
+ }
1816
+ const v = el3.getAttribute(name);
1817
+ return v === "" || v === "true" || v === name;
1818
+ }
1819
+ function parseAsString(el3) {
1820
+ if (!el3.hasAttribute("as-string")) {
1821
+ return void 0;
1822
+ }
1823
+ return boolAttr(el3, "as-string", true);
1824
+ }
1825
+ function resolveControlledOpen(inline, el3) {
1826
+ if (inline) {
1827
+ return true;
1828
+ }
1829
+ if (!el3.hasAttribute("open")) {
1830
+ return false;
1831
+ }
1832
+ return boolAttr(el3, "open");
1833
+ }
1834
+ function syncOpenAttribute(el3, isOpen) {
1835
+ if (isOpen) {
1836
+ el3.setAttribute("open", "");
1837
+ } else {
1838
+ el3.removeAttribute("open");
1839
+ }
1840
+ }
1841
+ var BaseCalendarElement = class extends HTMLElement {
1842
+ constructor() {
1843
+ super(...arguments);
1844
+ this.handle = null;
1845
+ this.mountEl = null;
1846
+ }
1847
+ connectedCallback() {
1848
+ if (!this.mountEl) {
1849
+ this.mountEl = document.createElement("div");
1850
+ this.append(this.mountEl);
1851
+ }
1852
+ this.mount();
1853
+ }
1854
+ disconnectedCallback() {
1855
+ this.handle?.destroy();
1856
+ this.handle = null;
1857
+ }
1858
+ emitChange(detail) {
1859
+ this.dispatchEvent(
1860
+ new CustomEvent("change", {
1861
+ detail,
1862
+ bubbles: true,
1863
+ composed: true
1864
+ })
1865
+ );
1866
+ }
1867
+ };
1868
+ var DateTimePickerElement = class extends BaseCalendarElement {
1869
+ static get observedAttributes() {
1870
+ return [
1871
+ "mode",
1872
+ "layout",
1873
+ "inline",
1874
+ "open",
1875
+ "use12hours",
1876
+ "show-seconds",
1877
+ "as-string",
1878
+ "locale",
1879
+ "format",
1880
+ "theme",
1881
+ "value",
1882
+ "popover"
1883
+ ];
1884
+ }
1885
+ attributeChangedCallback() {
1886
+ if (this.isConnected) {
1887
+ this.mount();
1888
+ }
1889
+ }
1890
+ get value() {
1891
+ return this._value ?? null;
1892
+ }
1893
+ set value(next) {
1894
+ this._value = next;
1895
+ if (this.isConnected) {
1896
+ this.mount();
1897
+ }
1898
+ }
1899
+ mount() {
1900
+ this.handle?.destroy();
1901
+ if (!this.mountEl) {
1902
+ return;
1903
+ }
1904
+ this.mountEl.replaceChildren();
1905
+ const mode = this.getAttribute("mode") || "datetime";
1906
+ const layout = this.getAttribute("layout") || "combined";
1907
+ const showSeconds = !this.hasAttribute("show-seconds") ? true : boolAttr(this, "show-seconds", true);
1908
+ const use12Hours = boolAttr(this, "use12hours");
1909
+ const inline = boolAttr(this, "inline");
1910
+ const popover = boolAttr(this, "popover");
1911
+ const asString = parseAsString(this);
1912
+ const format = this.getAttribute("format") || void 0;
1913
+ const locale = this.getAttribute("locale") || "en";
1914
+ const theme = this.getAttribute("theme") || void 0;
1915
+ const open = resolveControlledOpen(inline, this);
1916
+ const attrValue = this.getAttribute("value");
1917
+ const propValue = this._value;
1918
+ const value = propValue !== void 0 ? propValue : attrValue != null ? attrValue : void 0;
1919
+ this.handle = createDateTimePicker(this.mountEl, {
1920
+ mode,
1921
+ layout,
1922
+ showSeconds,
1923
+ use12Hours,
1924
+ inline,
1925
+ popover,
1926
+ asString,
1927
+ format,
1928
+ locale,
1929
+ theme,
1930
+ open,
1931
+ value: value === void 0 ? void 0 : value,
1932
+ onChange: (next) => {
1933
+ this._value = next;
1934
+ this.emitChange(next);
1935
+ },
1936
+ onOpenChange: (isOpen) => {
1937
+ syncOpenAttribute(this, isOpen);
1938
+ this.dispatchEvent(
1939
+ new CustomEvent("openchange", {
1940
+ detail: isOpen,
1941
+ bubbles: true,
1942
+ composed: true
1943
+ })
1944
+ );
1945
+ }
1946
+ });
1947
+ }
1948
+ };
1949
+ var DateTimePickerInputElement = class extends HTMLElement {
1950
+ constructor() {
1951
+ super(...arguments);
1952
+ this.handle = null;
1953
+ this.input = null;
1954
+ this.root = null;
1955
+ }
1956
+ static get observedAttributes() {
1957
+ return [
1958
+ "mode",
1959
+ "placeholder",
1960
+ "disabled",
1961
+ "use12hours",
1962
+ "show-seconds",
1963
+ "as-string",
1964
+ "locale",
1965
+ "format",
1966
+ "value",
1967
+ "theme"
1968
+ ];
1969
+ }
1970
+ connectedCallback() {
1971
+ if (!this.root) {
1972
+ this.root = document.createElement("div");
1973
+ this.root.className = "ctp-input-root";
1974
+ this.input = document.createElement("input");
1975
+ this.input.className = "ctp-input";
1976
+ this.input.readOnly = true;
1977
+ this.root.append(this.input);
1978
+ this.append(this.root);
1979
+ }
1980
+ this.sync();
1981
+ }
1982
+ disconnectedCallback() {
1983
+ this.handle?.destroy();
1984
+ this.handle = null;
1985
+ }
1986
+ attributeChangedCallback() {
1987
+ if (this.isConnected) {
1988
+ this.sync();
1989
+ }
1990
+ }
1991
+ sync() {
1992
+ if (!this.input || !this.root) {
1993
+ return;
1994
+ }
1995
+ this.handle?.destroy();
1996
+ const mode = this.getAttribute("mode") || "datetime";
1997
+ const showSeconds = !this.hasAttribute("show-seconds") ? true : boolAttr(this, "show-seconds", true);
1998
+ const use12Hours = boolAttr(this, "use12hours");
1999
+ const asString = parseAsString(this);
2000
+ const formatAttr = this.getAttribute("format") || void 0;
2001
+ const resolvedFormat = resolveFormat({
2002
+ mode,
2003
+ format: formatAttr,
2004
+ use12Hours,
2005
+ showSeconds
2006
+ });
2007
+ const placeholder = this.getAttribute("placeholder") || "Select date and time";
2008
+ const disabled = boolAttr(this, "disabled");
2009
+ const locale = this.getAttribute("locale") || "en";
2010
+ const theme = this.getAttribute("theme") || void 0;
2011
+ const attrValue = this.getAttribute("value");
2012
+ this.input.placeholder = placeholder;
2013
+ this.input.disabled = disabled;
2014
+ this.input.setAttribute("aria-haspopup", "dialog");
2015
+ this.input.setAttribute("aria-label", placeholder);
2016
+ const display = attrValue ? formatValue(parseValue(attrValue, resolvedFormat), resolvedFormat) ?? attrValue : "";
2017
+ this.input.value = display;
2018
+ const openPicker = () => {
2019
+ if (disabled) {
2020
+ return;
2021
+ }
2022
+ this.handle?.destroy();
2023
+ this.handle = createDateTimePicker(this.root, {
2024
+ mode,
2025
+ showSeconds,
2026
+ use12Hours,
2027
+ asString,
2028
+ format: formatAttr,
2029
+ locale,
2030
+ theme,
2031
+ popover: true,
2032
+ anchorEl: this.input,
2033
+ open: true,
2034
+ value: attrValue,
2035
+ onChange: (next) => {
2036
+ let text = "";
2037
+ if (next === null) {
2038
+ text = "";
2039
+ } else if (typeof next === "string") {
2040
+ text = next;
2041
+ } else if (next instanceof Date) {
2042
+ text = formatValue(dayjs__default.default(next), resolvedFormat) ?? "";
2043
+ } else {
2044
+ text = next.formatted;
2045
+ }
2046
+ if (this.input) {
2047
+ this.input.value = text;
2048
+ }
2049
+ if (text) {
2050
+ this.setAttribute("value", text);
2051
+ } else {
2052
+ this.removeAttribute("value");
2053
+ }
2054
+ this.dispatchEvent(
2055
+ new CustomEvent("change", {
2056
+ detail: next,
2057
+ bubbles: true,
2058
+ composed: true
2059
+ })
2060
+ );
2061
+ },
2062
+ onOpenChange: (isOpen) => {
2063
+ this.input?.setAttribute("aria-expanded", String(isOpen));
2064
+ if (!isOpen) {
2065
+ this.handle?.destroy();
2066
+ this.handle = null;
2067
+ }
2068
+ }
2069
+ });
2070
+ };
2071
+ this.input.onclick = openPicker;
2072
+ this.input.onkeydown = (event) => {
2073
+ if (disabled) {
2074
+ return;
2075
+ }
2076
+ if (event.key === "Enter" || event.key === " ") {
2077
+ event.preventDefault();
2078
+ openPicker();
2079
+ }
2080
+ };
2081
+ }
2082
+ };
2083
+ var DateTimePickerRangeElement = class extends BaseCalendarElement {
2084
+ static get observedAttributes() {
2085
+ return [
2086
+ "inline",
2087
+ "open",
2088
+ "as-string",
2089
+ "locale",
2090
+ "format",
2091
+ "value-start",
2092
+ "value-end"
2093
+ ];
2094
+ }
2095
+ attributeChangedCallback() {
2096
+ if (this.isConnected) {
2097
+ this.mount();
2098
+ }
2099
+ }
2100
+ mount() {
2101
+ this.handle?.destroy();
2102
+ if (!this.mountEl) {
2103
+ return;
2104
+ }
2105
+ this.mountEl.replaceChildren();
2106
+ const inline = boolAttr(this, "inline");
2107
+ const asString = parseAsString(this);
2108
+ const format = this.getAttribute("format") || void 0;
2109
+ const locale = this.getAttribute("locale") || "en";
2110
+ const open = resolveControlledOpen(inline, this);
2111
+ const start = this.getAttribute("value-start");
2112
+ const end = this.getAttribute("value-end");
2113
+ this.handle = createDateTimeRangePicker(this.mountEl, {
2114
+ inline,
2115
+ asString,
2116
+ format,
2117
+ locale,
2118
+ open,
2119
+ value: start || end ? { start: start ?? null, end: end ?? null } : void 0,
2120
+ onChange: (next) => {
2121
+ if (typeof next.start === "string" || next.start === null) {
2122
+ if (next.start) {
2123
+ this.setAttribute("value-start", next.start);
2124
+ } else {
2125
+ this.removeAttribute("value-start");
2126
+ }
2127
+ }
2128
+ if (typeof next.end === "string" || next.end === null) {
2129
+ if (next.end) {
2130
+ this.setAttribute("value-end", next.end);
2131
+ } else {
2132
+ this.removeAttribute("value-end");
2133
+ }
2134
+ }
2135
+ this.emitChange(next);
2136
+ },
2137
+ onOpenChange: (isOpen) => {
2138
+ syncOpenAttribute(this, isOpen);
2139
+ this.dispatchEvent(
2140
+ new CustomEvent("openchange", {
2141
+ detail: isOpen,
2142
+ bubbles: true,
2143
+ composed: true
2144
+ })
2145
+ );
2146
+ }
2147
+ });
2148
+ }
2149
+ };
2150
+ var defaultTagsDefined = false;
2151
+ function resolveTag(base, prefix) {
2152
+ const p = prefix?.trim();
2153
+ if (!p) {
2154
+ return base;
2155
+ }
2156
+ return `${p}-${base}`;
2157
+ }
2158
+ function defineElementTag(tag, Base, useUniqueConstructor) {
2159
+ if (customElements.get(tag)) {
2160
+ return;
2161
+ }
2162
+ const ctor = useUniqueConstructor ? class extends Base {
2163
+ } : Base;
2164
+ customElements.define(tag, ctor);
2165
+ }
2166
+ function defineCustomElements(options) {
2167
+ if (typeof customElements === "undefined") {
2168
+ return;
2169
+ }
2170
+ const prefix = options?.prefix?.trim();
2171
+ const useUniqueConstructor = Boolean(prefix);
2172
+ const pickerTag = resolveTag("datetime-picker", prefix);
2173
+ const inputTag = resolveTag("datetime-picker-input", prefix);
2174
+ const rangeTag = resolveTag("datetime-picker-range", prefix);
2175
+ if (!prefix) {
2176
+ if (defaultTagsDefined) {
2177
+ return;
2178
+ }
2179
+ defaultTagsDefined = true;
2180
+ }
2181
+ defineElementTag(pickerTag, DateTimePickerElement, useUniqueConstructor);
2182
+ defineElementTag(inputTag, DateTimePickerInputElement, useUniqueConstructor);
2183
+ defineElementTag(rangeTag, DateTimePickerRangeElement, useUniqueConstructor);
2184
+ }
2185
+
2186
+ // src/framework/svelte.ts
2187
+ defineCustomElements();
2188
+ function calendarTime(_node) {
2189
+ defineCustomElements();
2190
+ return {};
2191
+ }
2192
+
2193
+ exports.calendarTime = calendarTime;
2194
+ exports.defineCustomElements = defineCustomElements;