@lekoala/date-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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +236 -0
  3. package/custom-elements.json +366 -0
  4. package/dist/date-picker.css +344 -0
  5. package/dist/date-picker.js +2693 -0
  6. package/dist/date-picker.min.css +1 -0
  7. package/dist/date-picker.min.js +17 -0
  8. package/dist/date-picker.standalone.min.js +361 -0
  9. package/dist/types/calendar-model.d.ts +34 -0
  10. package/dist/types/calendar-model.d.ts.map +1 -0
  11. package/dist/types/date-calendar.d.ts +228 -0
  12. package/dist/types/date-calendar.d.ts.map +1 -0
  13. package/dist/types/date-field.d.ts +94 -0
  14. package/dist/types/date-field.d.ts.map +1 -0
  15. package/dist/types/date-picker.d.ts +258 -0
  16. package/dist/types/date-picker.d.ts.map +1 -0
  17. package/dist/types/date-range.d.ts +89 -0
  18. package/dist/types/date-range.d.ts.map +1 -0
  19. package/dist/types/date.d.ts +61 -0
  20. package/dist/types/date.d.ts.map +1 -0
  21. package/dist/types/define.d.ts +2 -0
  22. package/dist/types/define.d.ts.map +1 -0
  23. package/dist/types/index.d.ts +9 -0
  24. package/dist/types/index.d.ts.map +1 -0
  25. package/dist/types/intl.d.ts +33 -0
  26. package/dist/types/intl.d.ts.map +1 -0
  27. package/dist/types/locales/ar.d.ts +22 -0
  28. package/dist/types/locales/ar.d.ts.map +1 -0
  29. package/dist/types/locales/de.d.ts +22 -0
  30. package/dist/types/locales/de.d.ts.map +1 -0
  31. package/dist/types/locales/en.d.ts +22 -0
  32. package/dist/types/locales/en.d.ts.map +1 -0
  33. package/dist/types/locales/es.d.ts +22 -0
  34. package/dist/types/locales/es.d.ts.map +1 -0
  35. package/dist/types/locales/fr.d.ts +22 -0
  36. package/dist/types/locales/fr.d.ts.map +1 -0
  37. package/dist/types/locales/hi.d.ts +22 -0
  38. package/dist/types/locales/hi.d.ts.map +1 -0
  39. package/dist/types/locales/id.d.ts +22 -0
  40. package/dist/types/locales/id.d.ts.map +1 -0
  41. package/dist/types/locales/it.d.ts +22 -0
  42. package/dist/types/locales/it.d.ts.map +1 -0
  43. package/dist/types/locales/ja.d.ts +22 -0
  44. package/dist/types/locales/ja.d.ts.map +1 -0
  45. package/dist/types/locales/ko.d.ts +22 -0
  46. package/dist/types/locales/ko.d.ts.map +1 -0
  47. package/dist/types/locales/nl.d.ts +22 -0
  48. package/dist/types/locales/nl.d.ts.map +1 -0
  49. package/dist/types/locales/pl.d.ts +22 -0
  50. package/dist/types/locales/pl.d.ts.map +1 -0
  51. package/dist/types/locales/pt-BR.d.ts +22 -0
  52. package/dist/types/locales/pt-BR.d.ts.map +1 -0
  53. package/dist/types/locales/pt-PT.d.ts +22 -0
  54. package/dist/types/locales/pt-PT.d.ts.map +1 -0
  55. package/dist/types/locales/ru.d.ts +22 -0
  56. package/dist/types/locales/ru.d.ts.map +1 -0
  57. package/dist/types/locales/tr.d.ts +22 -0
  58. package/dist/types/locales/tr.d.ts.map +1 -0
  59. package/dist/types/locales/zh-CN.d.ts +22 -0
  60. package/dist/types/locales/zh-CN.d.ts.map +1 -0
  61. package/dist/types/messages.d.ts +43 -0
  62. package/dist/types/messages.d.ts.map +1 -0
  63. package/dist/types/source.d.ts +28 -0
  64. package/dist/types/source.d.ts.map +1 -0
  65. package/docs/ACCESSIBILITY.md +128 -0
  66. package/docs/API.md +289 -0
  67. package/docs/ARCHITECTURE.md +183 -0
  68. package/docs/AT-CHECKLIST.md +63 -0
  69. package/docs/CALENDAR-INTEGRATION.md +70 -0
  70. package/docs/DECISIONS.md +40 -0
  71. package/docs/MIGRATION-FLATPICKR.md +92 -0
  72. package/docs/ROADMAP.md +105 -0
  73. package/docs/TESTING.md +73 -0
  74. package/docs/USE_CASES.md +128 -0
  75. package/package.json +174 -0
  76. package/src/calendar-model.js +80 -0
  77. package/src/date-calendar.js +800 -0
  78. package/src/date-field.js +203 -0
  79. package/src/date-picker.css +344 -0
  80. package/src/date-picker.js +1140 -0
  81. package/src/date-range.js +172 -0
  82. package/src/date.js +203 -0
  83. package/src/define.js +9 -0
  84. package/src/index.js +15 -0
  85. package/src/intl.js +155 -0
  86. package/src/locales/ar.js +20 -0
  87. package/src/locales/de.js +20 -0
  88. package/src/locales/en.js +20 -0
  89. package/src/locales/es.js +20 -0
  90. package/src/locales/fr.js +20 -0
  91. package/src/locales/hi.js +20 -0
  92. package/src/locales/id.js +20 -0
  93. package/src/locales/it.js +20 -0
  94. package/src/locales/ja.js +20 -0
  95. package/src/locales/ko.js +20 -0
  96. package/src/locales/nl.js +20 -0
  97. package/src/locales/pl.js +20 -0
  98. package/src/locales/pt-BR.js +20 -0
  99. package/src/locales/pt-PT.js +20 -0
  100. package/src/locales/ru.js +20 -0
  101. package/src/locales/tr.js +20 -0
  102. package/src/locales/zh-CN.js +20 -0
  103. package/src/messages.js +31 -0
  104. package/src/source.js +50 -0
@@ -0,0 +1,2693 @@
1
+ /*** @lekoala/date-picker v0.1.0 - https://github.com/lekoala/date-picker ***/
2
+ (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toCommonJS = (from) => {
11
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
12
+ if (entry)
13
+ return entry;
14
+ entry = __defProp({}, "__esModule", { value: true });
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (var key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(entry, key))
18
+ __defProp(entry, key, {
19
+ get: __accessProp.bind(from, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ __moduleCache.set(from, entry);
24
+ return entry;
25
+ };
26
+ var __moduleCache;
27
+ var __returnValue = (v) => v;
28
+ function __exportSetter(name, newValue) {
29
+ this[name] = __returnValue.bind(null, newValue);
30
+ }
31
+ var __export = (target, all) => {
32
+ for (var name in all)
33
+ __defProp(target, name, {
34
+ get: all[name],
35
+ enumerable: true,
36
+ configurable: true,
37
+ set: __exportSetter.bind(all, name)
38
+ });
39
+ };
40
+
41
+ // src/define.js
42
+ var exports_define = {};
43
+ __export(exports_define, {
44
+ defineDatePicker: () => defineDatePicker
45
+ });
46
+
47
+ // src/date.js
48
+ var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
49
+ var MONTH_RE = /^(\d{4})-(\d{2})$/;
50
+ function isLeapYear(year) {
51
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
52
+ }
53
+ function daysInMonth(year, month) {
54
+ if (month === 2)
55
+ return isLeapYear(year) ? 29 : 28;
56
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
57
+ }
58
+ function parseDate(value) {
59
+ const match = DATE_RE.exec(String(value || ""));
60
+ if (!match)
61
+ return null;
62
+ const year = Number(match[1]);
63
+ const month = Number(match[2]);
64
+ const day = Number(match[3]);
65
+ if (year < 1 || year > 9999 || month < 1 || month > 12)
66
+ return null;
67
+ if (day < 1 || day > daysInMonth(year, month))
68
+ return null;
69
+ return { year, month, day };
70
+ }
71
+ function isDate(value) {
72
+ return parseDate(value) !== null;
73
+ }
74
+ function monthKey(value) {
75
+ const text = String(value || "");
76
+ const monthMatch = MONTH_RE.exec(text);
77
+ if (monthMatch) {
78
+ const month = Number(monthMatch[2]);
79
+ if (month >= 1 && month <= 12)
80
+ return `${monthMatch[1]}-${String(month).padStart(2, "0")}`;
81
+ }
82
+ const parsed = parseDate(text);
83
+ if (!parsed)
84
+ throw new TypeError(`Invalid civil date/month: ${value}`);
85
+ return `${String(parsed.year).padStart(4, "0")}-${String(parsed.month).padStart(2, "0")}`;
86
+ }
87
+ function toISODate(year, month, day) {
88
+ const value = `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
89
+ if (!isDate(value))
90
+ throw new TypeError(`Invalid civil date: ${value}`);
91
+ return value;
92
+ }
93
+ function toUTCDate(parts) {
94
+ const date = new Date(0);
95
+ date.setUTCHours(12, 0, 0, 0);
96
+ date.setUTCFullYear(parts.year, parts.month - 1, parts.day);
97
+ return date;
98
+ }
99
+ function fromUTCDate(date) {
100
+ return toISODate(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
101
+ }
102
+ function toIntlDate(value) {
103
+ const parsed = parseDate(value);
104
+ if (!parsed)
105
+ throw new TypeError(`Invalid civil date: ${value}`);
106
+ return toUTCDate(parsed);
107
+ }
108
+ function compareDates(a, b) {
109
+ if (!isDate(a) || !isDate(b))
110
+ throw new TypeError("compareDates() expects YYYY-MM-DD values");
111
+ return a === b ? 0 : a < b ? -1 : 1;
112
+ }
113
+ function addDays(value, amount) {
114
+ const parsed = parseDate(value);
115
+ if (!parsed || !Number.isInteger(amount))
116
+ throw new TypeError("addDays() expects a date and integer amount");
117
+ const date = toUTCDate(parsed);
118
+ date.setUTCDate(date.getUTCDate() + amount);
119
+ return fromUTCDate(date);
120
+ }
121
+ function addMonths(value, amount) {
122
+ const parsed = parseDate(value);
123
+ if (!parsed || !Number.isInteger(amount))
124
+ throw new TypeError("addMonths() expects a date and integer amount");
125
+ const absoluteMonth = parsed.year * 12 + (parsed.month - 1) + amount;
126
+ const year = Math.floor(absoluteMonth / 12);
127
+ const monthIndex = (absoluteMonth % 12 + 12) % 12;
128
+ const month = monthIndex + 1;
129
+ const day = Math.min(parsed.day, daysInMonth(year, month));
130
+ return toISODate(year, month, day);
131
+ }
132
+ function addYears(value, amount) {
133
+ const parsed = parseDate(value);
134
+ if (!parsed || !Number.isInteger(amount))
135
+ throw new TypeError("addYears() expects a date and integer amount");
136
+ const year = parsed.year + amount;
137
+ const day = Math.min(parsed.day, daysInMonth(year, parsed.month));
138
+ return toISODate(year, parsed.month, day);
139
+ }
140
+ function shiftMonth(month, amount) {
141
+ const start = `${monthKey(month)}-01`;
142
+ return monthKey(addMonths(start, amount));
143
+ }
144
+ function startOfMonth(value) {
145
+ return `${monthKey(value)}-01`;
146
+ }
147
+ function endOfMonth(value) {
148
+ const parsed = parseDate(`${monthKey(value)}-01`);
149
+ if (!parsed)
150
+ throw new TypeError(`Invalid month: ${value}`);
151
+ return toISODate(parsed.year, parsed.month, daysInMonth(parsed.year, parsed.month));
152
+ }
153
+ function dayOfWeek(value) {
154
+ const parsed = parseDate(value);
155
+ if (!parsed)
156
+ throw new TypeError(`Invalid civil date: ${value}`);
157
+ return toUTCDate(parsed).getUTCDay();
158
+ }
159
+ function startOfWeek(value, firstDay = 1) {
160
+ if (!Number.isInteger(firstDay) || firstDay < 0 || firstDay > 6)
161
+ throw new RangeError("firstDay must be 0..6");
162
+ const offset = (dayOfWeek(value) - firstDay + 7) % 7;
163
+ return addDays(value, -offset);
164
+ }
165
+ function endOfWeek(value, firstDay = 1) {
166
+ return addDays(startOfWeek(value, firstDay), 6);
167
+ }
168
+ function getMonthWeeks(value, options = {}) {
169
+ const firstDay = options.firstDay ?? 1;
170
+ const start = startOfWeek(startOfMonth(value), firstDay);
171
+ const end = endOfWeek(endOfMonth(value), firstDay);
172
+ const weeks = [];
173
+ let cursor = start;
174
+ while (compareDates(cursor, end) <= 0) {
175
+ const week = [];
176
+ for (let i = 0;i < 7; i++)
177
+ week.push(addDays(cursor, i));
178
+ weeks.push(week);
179
+ cursor = addDays(cursor, 7);
180
+ }
181
+ return weeks;
182
+ }
183
+ function clampDate(value, min = "", max = "") {
184
+ if (!isDate(value))
185
+ throw new TypeError(`Invalid civil date: ${value}`);
186
+ if (min && compareDates(value, min) < 0)
187
+ return min;
188
+ if (max && compareDates(value, max) > 0)
189
+ return max;
190
+ return value;
191
+ }
192
+ function todayISO() {
193
+ const now = new Date;
194
+ return toISODate(now.getFullYear(), now.getMonth() + 1, now.getDate());
195
+ }
196
+ function isoWeekNumber(value) {
197
+ const parsed = parseDate(value);
198
+ if (!parsed)
199
+ throw new TypeError(`Invalid civil date: ${value}`);
200
+ const date = toUTCDate(parsed);
201
+ const day = date.getUTCDay() || 7;
202
+ date.setUTCDate(date.getUTCDate() + 4 - day);
203
+ const yearStart = new Date(0);
204
+ yearStart.setUTCHours(12, 0, 0, 0);
205
+ yearStart.setUTCFullYear(date.getUTCFullYear(), 0, 1);
206
+ return Math.ceil(((date.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
207
+ }
208
+
209
+ // src/calendar-model.js
210
+ class CalendarModel {
211
+ constructor(options = {}) {
212
+ const today = todayISO();
213
+ this.firstDay = options.firstDay ?? 1;
214
+ this.value = options.value && isDate(options.value) ? options.value : "";
215
+ this.focused = options.focused && isDate(options.focused) ? options.focused : this.value || today;
216
+ this.display = options.display ? monthKey(options.display) : monthKey(this.focused);
217
+ }
218
+ setValue(value) {
219
+ if (value && !isDate(value))
220
+ throw new TypeError(`Invalid value: ${value}`);
221
+ this.value = value || "";
222
+ return this.value;
223
+ }
224
+ setDisplay(value) {
225
+ const target = monthKey(value);
226
+ const day = Number(this.focused.slice(8, 10)) || 1;
227
+ const next = addMonths(`${target}-01`, 0);
228
+ const candidate = addDays(next, Math.max(0, day - 1));
229
+ this.focused = monthKey(candidate) === target ? candidate : addDays(`${shiftMonth(target, 1)}-01`, -1);
230
+ this.display = target;
231
+ return this.display;
232
+ }
233
+ setFocused(value, options = {}) {
234
+ if (!isDate(value))
235
+ throw new TypeError(`Invalid focused date: ${value}`);
236
+ this.focused = value;
237
+ if (options.follow !== false)
238
+ this.display = monthKey(value);
239
+ return this.focused;
240
+ }
241
+ moveFocusDays(amount) {
242
+ return this.setFocused(addDays(this.focused, amount));
243
+ }
244
+ moveFocusMonths(amount) {
245
+ return this.setFocused(addMonths(this.focused, amount));
246
+ }
247
+ moveFocusYears(amount) {
248
+ return this.setFocused(addYears(this.focused, amount));
249
+ }
250
+ focusWeekStart() {
251
+ return this.setFocused(startOfWeek(this.focused, this.firstDay));
252
+ }
253
+ focusWeekEnd() {
254
+ return this.setFocused(endOfWeek(this.focused, this.firstDay));
255
+ }
256
+ navigateMonth(amount) {
257
+ const target = shiftMonth(this.display, amount);
258
+ this.setDisplay(target);
259
+ return this.display;
260
+ }
261
+ }
262
+
263
+ // src/date-range.js
264
+ function normalizeRange(range) {
265
+ if (range == null)
266
+ return { start: "", end: "" };
267
+ if (typeof range !== "object")
268
+ throw new TypeError("highlightedRange expects { start, end }");
269
+ const { start = "", end = "" } = range;
270
+ if (start && !isDate(start))
271
+ throw new TypeError(`Invalid range start: ${start}`);
272
+ if (end && !isDate(end))
273
+ throw new TypeError(`Invalid range end: ${end}`);
274
+ if (!start && end)
275
+ throw new TypeError("highlightedRange cannot define an end without a start");
276
+ if (start && end && compareDates(end, start) < 0)
277
+ throw new TypeError("highlightedRange must be ordered; start must be on or before end");
278
+ return { start, end };
279
+ }
280
+ function rangePosition(date, range) {
281
+ if (!range || !isDate(date))
282
+ return "";
283
+ const { start, end } = range;
284
+ if (!start || !isDate(start))
285
+ return "";
286
+ if (start === date)
287
+ return end ? end === date ? "single" : "start" : "start";
288
+ if (end && end === date)
289
+ return "end";
290
+ if (end && compareDates(date, start) >= 0 && compareDates(date, end) <= 0)
291
+ return "in";
292
+ return "";
293
+ }
294
+
295
+ class DateRangeController {
296
+ constructor() {
297
+ this.activeEndpoint = "";
298
+ this.start = "";
299
+ this.end = "";
300
+ }
301
+ get range() {
302
+ return { start: this.start, end: this.end };
303
+ }
304
+ get complete() {
305
+ return Boolean(this.start && this.end && compareDates(this.end, this.start) >= 0);
306
+ }
307
+ focus(endpoint) {
308
+ this.activeEndpoint = endpoint;
309
+ }
310
+ activate(date, editable = () => true) {
311
+ if (!isDate(date))
312
+ throw new TypeError(`Invalid range activation: ${date}`);
313
+ if (this.complete && (compareDates(date, this.start) < 0 || compareDates(date, this.end) > 0)) {
314
+ const endpoint = compareDates(date, this.start) < 0 ? "start" : "end";
315
+ if (!editable(endpoint))
316
+ return { status: "refused", endpoint };
317
+ this[endpoint] = date;
318
+ this.activeEndpoint = endpoint;
319
+ return { status: "complete", endpoint };
320
+ }
321
+ if (this.activeEndpoint === "end") {
322
+ if (this.start && compareDates(date, this.start) < 0) {
323
+ return { status: "refused", endpoint: "end" };
324
+ }
325
+ if (!editable("end"))
326
+ return { status: "refused", endpoint: "end" };
327
+ this.end = date;
328
+ return { status: this.start ? "complete" : "pending", endpoint: "end" };
329
+ }
330
+ if (!editable("start"))
331
+ return { status: "refused", endpoint: "start" };
332
+ this.start = date;
333
+ if (!editable("end")) {
334
+ return { status: "pending", endpoint: "start", close: true };
335
+ }
336
+ this.activeEndpoint = "end";
337
+ return { status: "pending", endpoint: "start" };
338
+ }
339
+ }
340
+
341
+ // src/intl.js
342
+ var BIDI = /[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069]/g;
343
+ function resolveLocale(locale = "") {
344
+ if (locale)
345
+ return locale;
346
+ if (typeof document !== "undefined" && document.documentElement.lang)
347
+ return document.documentElement.lang;
348
+ if (typeof navigator !== "undefined" && navigator.language)
349
+ return navigator.language;
350
+ return "en-US";
351
+ }
352
+ function digitMap(locale) {
353
+ const formatter = new Intl.NumberFormat(locale, { useGrouping: false });
354
+ const map = new Map;
355
+ for (let i = 0;i <= 9; i++)
356
+ map.set(formatter.format(i), String(i));
357
+ return map;
358
+ }
359
+ function normalizeDigits(text, locale) {
360
+ let result = String(text || "").replace(BIDI, "").trim();
361
+ for (const [local, ascii] of digitMap(locale))
362
+ result = result.split(local).join(ascii);
363
+ return result;
364
+ }
365
+ function formatLongDate(value, locale = "") {
366
+ const resolved = resolveLocale(locale);
367
+ return new Intl.DateTimeFormat(resolved, {
368
+ calendar: "gregory",
369
+ weekday: "long",
370
+ year: "numeric",
371
+ month: "long",
372
+ day: "numeric",
373
+ timeZone: "UTC"
374
+ }).format(toIntlDate(value));
375
+ }
376
+ function formatMonthYear(value, locale = "") {
377
+ const resolved = resolveLocale(locale);
378
+ return new Intl.DateTimeFormat(resolved, {
379
+ calendar: "gregory",
380
+ year: "numeric",
381
+ month: "long",
382
+ timeZone: "UTC"
383
+ }).format(toIntlDate(`${value.slice(0, 7)}-15`));
384
+ }
385
+ function monthNames(locale = "", style = "long") {
386
+ const resolved = resolveLocale(locale);
387
+ const formatter = new Intl.DateTimeFormat(resolved, { calendar: "gregory", month: style, timeZone: "UTC" });
388
+ return Array.from({ length: 12 }, (_, index) => formatter.format(toIntlDate(`2026-${String(index + 1).padStart(2, "0")}-15`)));
389
+ }
390
+ function weekdayNames(locale = "", firstDay = 1, style = "short") {
391
+ const resolved = resolveLocale(locale);
392
+ const formatter = new Intl.DateTimeFormat(resolved, {
393
+ calendar: "gregory",
394
+ weekday: style,
395
+ timeZone: "UTC"
396
+ });
397
+ const sunday = toIntlDate("2026-01-04");
398
+ return Array.from({ length: 7 }, (_, index) => {
399
+ const date = new Date(sunday);
400
+ date.setUTCDate(sunday.getUTCDate() + (firstDay + index) % 7);
401
+ return formatter.format(date);
402
+ });
403
+ }
404
+ function escapeRegExp(value) {
405
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
406
+ }
407
+ function createDateAdapter(locale = "") {
408
+ const resolved = resolveLocale(locale);
409
+ const formatter = new Intl.DateTimeFormat(resolved, {
410
+ calendar: "gregory",
411
+ year: "numeric",
412
+ month: "2-digit",
413
+ day: "2-digit",
414
+ timeZone: "UTC"
415
+ });
416
+ const sample = toIntlDate("2006-11-22");
417
+ const parts = formatter.formatToParts(sample).filter((part) => part.type !== "literal" || part.value);
418
+ const order = parts.filter((part) => ["day", "month", "year"].includes(part.type)).map((part) => part.type);
419
+ const placeholder = parts.map((part) => {
420
+ if (part.type === "day")
421
+ return "DD";
422
+ if (part.type === "month")
423
+ return "MM";
424
+ if (part.type === "year")
425
+ return "YYYY";
426
+ return part.value.replace(BIDI, "");
427
+ }).join("");
428
+ const pattern = parts.map((part) => {
429
+ if (part.type === "day" || part.type === "month")
430
+ return "(\\d{1,2})";
431
+ if (part.type === "year")
432
+ return "(\\d{4})";
433
+ const literal = normalizeDigits(part.value, resolved).replace(/\s+/g, " ");
434
+ return escapeRegExp(literal).replace(/\\ /g, "\\s*");
435
+ }).join("");
436
+ const regex = new RegExp(`^\\s*${pattern}\\s*$`);
437
+ return {
438
+ locale: resolved,
439
+ placeholder,
440
+ format(value) {
441
+ if (!isDate(value))
442
+ return "";
443
+ return formatter.format(toIntlDate(value));
444
+ },
445
+ parse(text) {
446
+ const normalized = normalizeDigits(text, resolved);
447
+ if (isDate(normalized))
448
+ return normalized;
449
+ const match = regex.exec(normalized);
450
+ if (!match)
451
+ return "";
452
+ const values = {};
453
+ order.forEach((type, index) => {
454
+ values[type] = Number(match[index + 1]);
455
+ });
456
+ if (!values.year || !values.month || !values.day)
457
+ return "";
458
+ try {
459
+ return toISODate(values.year, values.month, values.day);
460
+ } catch {
461
+ return "";
462
+ }
463
+ }
464
+ };
465
+ }
466
+
467
+ // src/messages.js
468
+ var DEFAULT_MESSAGES = {
469
+ chooseDate: "Choose date",
470
+ changeDate: "Change date",
471
+ previousMonth: "Previous month",
472
+ nextMonth: "Next month",
473
+ month: "Month",
474
+ year: "Year",
475
+ calendar: "Choose a date",
476
+ unavailable: "Unavailable",
477
+ invalidDate: "Enter a valid date",
478
+ unavailableDate: "This date is unavailable",
479
+ formatHint: "Format",
480
+ week: "Week",
481
+ rangeStart: "Range start",
482
+ rangeInRange: "Inside range",
483
+ rangeEnd: "Range end",
484
+ rangeSingle: "Range of one day",
485
+ rangeOrderStart: "Start must be on or before end",
486
+ rangeOrderEnd: "End must be on or after start"
487
+ };
488
+ var defaults = { ...DEFAULT_MESSAGES };
489
+ function getDefaultMessages() {
490
+ return { ...defaults };
491
+ }
492
+
493
+ // src/source.js
494
+ function normalizeDateStates(payload) {
495
+ const raw = payload && typeof payload === "object" && !Array.isArray(payload) && "dates" in payload ? payload.dates : payload;
496
+ const map = new Map;
497
+ if (Array.isArray(raw)) {
498
+ for (const item of raw) {
499
+ if (!item || typeof item !== "object" || !("date" in item))
500
+ continue;
501
+ const { date, ...state } = item;
502
+ map.set(String(date), state);
503
+ }
504
+ return map;
505
+ }
506
+ if (raw && typeof raw === "object") {
507
+ for (const [date, state] of Object.entries(raw)) {
508
+ map.set(date, state && typeof state === "object" ? state : {});
509
+ }
510
+ }
511
+ return map;
512
+ }
513
+
514
+ // src/date-calendar.js
515
+ var uid = 0;
516
+ function escapeHtml(value) {
517
+ return String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#039;");
518
+ }
519
+ function parseFirstDay(value) {
520
+ const parsed = Number(value);
521
+ return Number.isInteger(parsed) && parsed >= 0 && parsed <= 6 ? parsed : 1;
522
+ }
523
+ function yearOf(month) {
524
+ return Number(month.slice(0, 4));
525
+ }
526
+ function rangeAttributes(position) {
527
+ switch (position) {
528
+ case "single":
529
+ return ' data-range-start="true" data-range-end="true"';
530
+ case "start":
531
+ return ' data-range-start="true"';
532
+ case "end":
533
+ return ' data-range-end="true"';
534
+ case "in":
535
+ return ' data-in-range="true"';
536
+ default:
537
+ return "";
538
+ }
539
+ }
540
+ function rangeLabelKey(position) {
541
+ if (position === "single")
542
+ return "rangeSingle";
543
+ if (position === "start")
544
+ return "rangeStart";
545
+ if (position === "end")
546
+ return "rangeEnd";
547
+ if (position === "in")
548
+ return "rangeInRange";
549
+ return "";
550
+ }
551
+
552
+ class DateCalendarElement extends HTMLElement {
553
+ static observedAttributes = [
554
+ "value",
555
+ "display",
556
+ "min",
557
+ "max",
558
+ "first-day",
559
+ "locale",
560
+ "fixed-weeks",
561
+ "show-week-numbers",
562
+ "selection"
563
+ ];
564
+ constructor() {
565
+ super();
566
+ this._id = `date-calendar-${++uid}`;
567
+ this._model = new CalendarModel;
568
+ this._connected = false;
569
+ this._rendering = false;
570
+ this._source = null;
571
+ this._sourceStates = new Map;
572
+ this._loadedRanges = new Set;
573
+ this._loadController = null;
574
+ this._loadingKey = "";
575
+ this._loadingPromise = null;
576
+ this._dateState = null;
577
+ this._renderDay = null;
578
+ this._isDateDisabled = null;
579
+ this._highlightedRange = { start: "", end: "" };
580
+ this._messages = getDefaultMessages();
581
+ this._onClick = this._onClick.bind(this);
582
+ this._onChange = this._onChange.bind(this);
583
+ this._onKeyDown = this._onKeyDown.bind(this);
584
+ this._onFocusIn = this._onFocusIn.bind(this);
585
+ }
586
+ connectedCallback() {
587
+ if (this._connected)
588
+ return;
589
+ this._connected = true;
590
+ this.addEventListener("click", this._onClick);
591
+ this.addEventListener("change", this._onChange);
592
+ this.addEventListener("keydown", this._onKeyDown);
593
+ this.addEventListener("focusin", this._onFocusIn);
594
+ this._syncModel();
595
+ this.render();
596
+ }
597
+ disconnectedCallback() {
598
+ this._connected = false;
599
+ this._loadController?.abort();
600
+ this._loadController = null;
601
+ this._loadingKey = "";
602
+ this._loadingPromise = null;
603
+ this.removeEventListener("click", this._onClick);
604
+ this.removeEventListener("change", this._onChange);
605
+ this.removeEventListener("keydown", this._onKeyDown);
606
+ this.removeEventListener("focusin", this._onFocusIn);
607
+ }
608
+ attributeChangedCallback() {
609
+ if (!this._connected || this._rendering)
610
+ return;
611
+ this._syncModel();
612
+ this.render();
613
+ }
614
+ get locale() {
615
+ return resolveLocale(this.getAttribute("locale") || "");
616
+ }
617
+ set locale(value) {
618
+ if (value)
619
+ this.setAttribute("locale", value);
620
+ else
621
+ this.removeAttribute("locale");
622
+ }
623
+ get value() {
624
+ return this._model.value;
625
+ }
626
+ set value(value) {
627
+ const next = value || "";
628
+ if (next && !isDate(next))
629
+ throw new TypeError(`Invalid calendar value: ${next}`);
630
+ if (next === this._model.value)
631
+ return;
632
+ this._model.setValue(next);
633
+ this._reflect("value", next);
634
+ if (this._connected)
635
+ this.render();
636
+ }
637
+ get display() {
638
+ return this._model.display;
639
+ }
640
+ set display(value) {
641
+ this._setDisplay(value, { emit: false });
642
+ }
643
+ get focusedDate() {
644
+ return this._model.focused;
645
+ }
646
+ set focusedDate(value) {
647
+ const next = value || "";
648
+ if (next === this._model.focused)
649
+ return;
650
+ this.focusDate(next, { moveFocus: false });
651
+ }
652
+ get min() {
653
+ const value = this.getAttribute("min") || "";
654
+ return isDate(value) ? value : "";
655
+ }
656
+ set min(value) {
657
+ if (value)
658
+ this.setAttribute("min", value);
659
+ else
660
+ this.removeAttribute("min");
661
+ }
662
+ get max() {
663
+ const value = this.getAttribute("max") || "";
664
+ return isDate(value) ? value : "";
665
+ }
666
+ set max(value) {
667
+ if (value)
668
+ this.setAttribute("max", value);
669
+ else
670
+ this.removeAttribute("max");
671
+ }
672
+ get firstDay() {
673
+ return parseFirstDay(this.getAttribute("first-day") || "1");
674
+ }
675
+ set firstDay(value) {
676
+ this.setAttribute("first-day", String(value));
677
+ }
678
+ get selection() {
679
+ return this.getAttribute("selection") === "none" ? "none" : "single";
680
+ }
681
+ set selection(value) {
682
+ this.setAttribute("selection", value === "none" ? "none" : "single");
683
+ }
684
+ get highlightedRange() {
685
+ return { ...this._highlightedRange };
686
+ }
687
+ set highlightedRange(value) {
688
+ const next = normalizeRange(value);
689
+ if (next.start === this._highlightedRange.start && next.end === this._highlightedRange.end)
690
+ return;
691
+ this._highlightedRange = next;
692
+ if (this._connected)
693
+ this.render();
694
+ }
695
+ get fixedWeeks() {
696
+ return this.hasAttribute("fixed-weeks");
697
+ }
698
+ set fixedWeeks(value) {
699
+ this.toggleAttribute("fixed-weeks", Boolean(value));
700
+ }
701
+ get showWeekNumbers() {
702
+ return this.hasAttribute("show-week-numbers");
703
+ }
704
+ set showWeekNumbers(value) {
705
+ this.toggleAttribute("show-week-numbers", Boolean(value));
706
+ }
707
+ get messages() {
708
+ return this._messages;
709
+ }
710
+ set messages(value) {
711
+ this._messages = { ...getDefaultMessages(), ...value || {} };
712
+ if (this._connected)
713
+ this.render();
714
+ }
715
+ get source() {
716
+ return this._source;
717
+ }
718
+ set source(value) {
719
+ this._source = value || null;
720
+ this.refreshSource();
721
+ }
722
+ get dateState() {
723
+ return this._dateState;
724
+ }
725
+ set dateState(value) {
726
+ this._dateState = typeof value === "function" ? value : null;
727
+ if (this._connected)
728
+ this.render();
729
+ }
730
+ get renderDay() {
731
+ return this._renderDay;
732
+ }
733
+ set renderDay(value) {
734
+ this._renderDay = typeof value === "function" ? value : null;
735
+ if (this._connected)
736
+ this.render();
737
+ }
738
+ get isDateDisabled() {
739
+ return this._isDateDisabled;
740
+ }
741
+ set isDateDisabled(value) {
742
+ this._isDateDisabled = typeof value === "function" ? value : null;
743
+ if (this._connected)
744
+ this.render();
745
+ }
746
+ _syncModel() {
747
+ const firstDay = this.firstDay;
748
+ this._model.firstDay = firstDay;
749
+ const attrValue = this.getAttribute("value") || "";
750
+ if (!attrValue || isDate(attrValue))
751
+ this._model.value = attrValue;
752
+ const attrDisplay = this.getAttribute("display") || "";
753
+ if (attrDisplay) {
754
+ try {
755
+ this._model.setDisplay(attrDisplay);
756
+ } catch {}
757
+ } else if (this._model.value) {
758
+ this._model.display = monthKey(this._model.value);
759
+ this._model.focused = this._model.value;
760
+ }
761
+ this._model.focused = this._clamp(this._model.focused);
762
+ if (!this.getAttribute("display"))
763
+ this._model.display = monthKey(this._model.focused);
764
+ }
765
+ _reflect(name, value) {
766
+ this._rendering = true;
767
+ if (value) {
768
+ if (this.getAttribute(name) !== value)
769
+ this.setAttribute(name, value);
770
+ } else if (this.hasAttribute(name)) {
771
+ this.removeAttribute(name);
772
+ }
773
+ this._rendering = false;
774
+ }
775
+ _clamp(value) {
776
+ return clampDate(value, this.min, this.max);
777
+ }
778
+ _weeks(display = this.display) {
779
+ const weeks = getMonthWeeks(display, { firstDay: this.firstDay });
780
+ if (!this.fixedWeeks || weeks.length >= 6)
781
+ return weeks;
782
+ let cursor = weeks[weeks.length - 1]?.[6] || "";
783
+ while (weeks.length < 6 && cursor) {
784
+ const week = [];
785
+ for (let i = 1;i <= 7; i++)
786
+ week.push(addDays(cursor, i));
787
+ weeks.push(week);
788
+ cursor = week[6];
789
+ }
790
+ return weeks;
791
+ }
792
+ _focusTargetKey(element) {
793
+ if (element.matches(".dp-month-select, .dp-year-select, .dp-year-input") && element.id) {
794
+ return `#${CSS.escape(element.id)}`;
795
+ }
796
+ if (element.matches(".dp-nav[data-calendar-action]")) {
797
+ return `.dp-nav[data-calendar-action="${CSS.escape(element.dataset.calendarAction || "")}"]`;
798
+ }
799
+ if (element.matches(".dp-day[data-date]")) {
800
+ const date = element.getAttribute("data-date") || "";
801
+ if (isDate(date))
802
+ return `.dp-day[data-date="${date}"]`;
803
+ }
804
+ return "";
805
+ }
806
+ _range(display = this.display) {
807
+ const weeks = this._weeks(display);
808
+ const first = weeks[0];
809
+ const last = weeks[weeks.length - 1];
810
+ if (!first || !last)
811
+ throw new Error("Calendar month produced no weeks");
812
+ return { start: first[0], end: last[6] };
813
+ }
814
+ getDateState(date) {
815
+ const fromSource = this._sourceStates.get(date) || {};
816
+ const local = this._dateState?.(date, { ...fromSource }) || {};
817
+ const state = { ...fromSource, ...local };
818
+ const outsideBounds = this.min && compareDates(date, this.min) < 0 || this.max && compareDates(date, this.max) > 0;
819
+ const explicitlyEnabled = state.enabled === true;
820
+ const disabledByRule = explicitlyEnabled ? false : Boolean(this._isDateDisabled?.(date));
821
+ const disabled = Boolean(outsideBounds) || Boolean(state.disabled) || disabledByRule;
822
+ return { ...state, disabled };
823
+ }
824
+ async refreshSource() {
825
+ this._loadController?.abort();
826
+ this._loadController = null;
827
+ this._loadingKey = "";
828
+ this._loadingPromise = null;
829
+ this._loadedRanges.clear();
830
+ this._sourceStates.clear();
831
+ if (this._connected) {
832
+ this.render();
833
+ await this._loadDisplay(this.display);
834
+ }
835
+ }
836
+ async ensureDate(date) {
837
+ if (!isDate(date) || !this._source)
838
+ return true;
839
+ return await this._loadDisplay(monthKey(date), false) === "loaded";
840
+ }
841
+ async _loadDisplay(display, rerender = true) {
842
+ if (!this._source)
843
+ return "loaded";
844
+ const range = this._range(display);
845
+ const key = `${range.start}/${range.end}`;
846
+ if (this._loadedRanges.has(key))
847
+ return "loaded";
848
+ if (key === this._loadingKey && this._loadingPromise)
849
+ return this._loadingPromise;
850
+ this._loadController?.abort();
851
+ const controller = new AbortController;
852
+ this._loadController = controller;
853
+ this._loadingKey = key;
854
+ const source = this._source;
855
+ const promise = (async () => {
856
+ this.toggleAttribute("data-loading", true);
857
+ this.dispatchEvent(new CustomEvent("dateloadstart", { detail: range, bubbles: true }));
858
+ try {
859
+ const loader = typeof source === "function" ? source : source?.load?.bind(source);
860
+ if (!loader)
861
+ throw new TypeError("Date source must be a function or expose load(range, { signal })");
862
+ const payload = await loader(range, { signal: controller.signal });
863
+ if (controller.signal.aborted)
864
+ return "cancelled";
865
+ for (const [date, state] of normalizeDateStates(payload))
866
+ this._sourceStates.set(date, state);
867
+ this._loadedRanges.add(key);
868
+ this.dispatchEvent(new CustomEvent("dateloadend", { detail: range, bubbles: true }));
869
+ if (rerender && this._connected && monthKey(this.display) === monthKey(display))
870
+ this.render(false);
871
+ return "loaded";
872
+ } catch (error) {
873
+ if (controller.signal.aborted)
874
+ return "cancelled";
875
+ this.dispatchEvent(new CustomEvent("dateloaderror", { detail: { ...range, error }, bubbles: true }));
876
+ return "failed";
877
+ } finally {
878
+ if (this._loadController === controller) {
879
+ this._loadController = null;
880
+ this.removeAttribute("data-loading");
881
+ }
882
+ if (this._loadingKey === key) {
883
+ this._loadingKey = "";
884
+ this._loadingPromise = null;
885
+ }
886
+ }
887
+ })();
888
+ this._loadingPromise = promise;
889
+ return promise;
890
+ }
891
+ _setDisplay(display, options = {}) {
892
+ const previous = this._model.display;
893
+ this._model.setDisplay(display);
894
+ this._model.focused = this._clamp(this._model.focused);
895
+ this._model.display = monthKey(this._model.focused);
896
+ this._reflect("display", this._model.display);
897
+ if (this._connected)
898
+ this.render();
899
+ if (options.emit !== false && previous !== this._model.display)
900
+ this._emitDisplayChange();
901
+ return this._model.display;
902
+ }
903
+ _emitDisplayChange() {
904
+ const range = this._range();
905
+ this.dispatchEvent(new CustomEvent("displaychange", {
906
+ detail: { display: this.display, ...range },
907
+ bubbles: true
908
+ }));
909
+ }
910
+ previousMonth() {
911
+ return this._setDisplay(shiftMonth(this.display, -1));
912
+ }
913
+ nextMonth() {
914
+ return this._setDisplay(shiftMonth(this.display, 1));
915
+ }
916
+ focusDate(date, options = {}) {
917
+ if (!isDate(date))
918
+ throw new TypeError(`Invalid date: ${date}`);
919
+ const next = this._clamp(date);
920
+ const previousDisplay = this.display;
921
+ const previousFocused = this._model.focused;
922
+ const moveFocus = options.moveFocus !== false;
923
+ this._model.setFocused(next);
924
+ this._reflect("display", this._model.display);
925
+ if (previousDisplay !== this.display) {
926
+ if (this._connected)
927
+ this.render();
928
+ this._emitDisplayChange();
929
+ } else if (this._connected && previousFocused !== this._model.focused) {
930
+ this._moveFocusDom(previousFocused, this._model.focused, moveFocus);
931
+ }
932
+ if (moveFocus)
933
+ queueMicrotask(() => this.focusGrid());
934
+ }
935
+ _moveFocusDom(previous, next, moveFocus = true) {
936
+ const prevCell = previous ? this.querySelector(`.dp-day[data-date="${CSS.escape(previous)}"]`) : null;
937
+ const nextCell = this.querySelector(`.dp-day[data-date="${CSS.escape(next)}"]`);
938
+ if (prevCell instanceof HTMLElement)
939
+ prevCell.tabIndex = -1;
940
+ if (nextCell instanceof HTMLElement) {
941
+ nextCell.tabIndex = 0;
942
+ if (moveFocus)
943
+ nextCell.focus();
944
+ } else if (moveFocus) {
945
+ this.focusGrid();
946
+ }
947
+ }
948
+ _moveSelectionDom(previousValue, nextValue) {
949
+ if (previousValue) {
950
+ const prev = this.querySelector(`.dp-day[data-date="${CSS.escape(previousValue)}"]`);
951
+ if (prev instanceof HTMLElement) {
952
+ prev.removeAttribute("data-selected");
953
+ prev.removeAttribute("aria-selected");
954
+ }
955
+ }
956
+ if (nextValue) {
957
+ const next = this.querySelector(`.dp-day[data-date="${CSS.escape(nextValue)}"]`);
958
+ if (next instanceof HTMLElement) {
959
+ next.setAttribute("data-selected", "true");
960
+ next.setAttribute("aria-selected", "true");
961
+ return true;
962
+ }
963
+ }
964
+ return false;
965
+ }
966
+ focusGrid() {
967
+ const target = this.querySelector(`.dp-day[data-date="${CSS.escape(this.focusedDate)}"]`);
968
+ if (target instanceof HTMLElement)
969
+ target.focus();
970
+ }
971
+ async _activate(date) {
972
+ const confirmed = await this.ensureDate(date);
973
+ const state = this.getDateState(date);
974
+ if (!confirmed || state.disabled) {
975
+ this.dispatchEvent(new CustomEvent("dateinvalid", { detail: { date, state }, bubbles: true }));
976
+ return false;
977
+ }
978
+ const accepted = this.dispatchEvent(new CustomEvent("dateactivate", {
979
+ detail: { date, state },
980
+ bubbles: true,
981
+ cancelable: true
982
+ }));
983
+ if (!accepted)
984
+ return false;
985
+ if (this.selection === "single") {
986
+ const previousValue = this._model.value;
987
+ this._model.setValue(date);
988
+ this._reflect("value", date);
989
+ this.dispatchEvent(new CustomEvent("datechange", { detail: { value: date }, bubbles: true }));
990
+ this.dispatchEvent(new Event("change", { bubbles: true }));
991
+ if (this._connected) {
992
+ if (monthKey(date) === this.display)
993
+ this._moveSelectionDom(previousValue, date);
994
+ else
995
+ this.render();
996
+ }
997
+ } else if (this._connected) {
998
+ if (monthKey(date) !== this.display)
999
+ this.render();
1000
+ }
1001
+ return true;
1002
+ }
1003
+ _onClick(event) {
1004
+ const target = event.target;
1005
+ if (!(target instanceof Element))
1006
+ return;
1007
+ const nav = target.closest("[data-calendar-action]");
1008
+ if (nav instanceof HTMLButtonElement) {
1009
+ const action = nav.dataset.calendarAction;
1010
+ if (action === "previous")
1011
+ this.previousMonth();
1012
+ if (action === "next")
1013
+ this.nextMonth();
1014
+ return;
1015
+ }
1016
+ const cell = target.closest(".dp-day[data-date]");
1017
+ if (!(cell instanceof HTMLTableCellElement))
1018
+ return;
1019
+ const date = cell.dataset.date || "";
1020
+ if (!isDate(date))
1021
+ return;
1022
+ const previousDisplay = this.display;
1023
+ this._model.setFocused(this._clamp(date));
1024
+ this._reflect("display", this._model.display);
1025
+ if (previousDisplay !== this.display)
1026
+ this._emitDisplayChange();
1027
+ this._activate(date);
1028
+ }
1029
+ _onChange(event) {
1030
+ const target = event.target;
1031
+ if (target instanceof HTMLSelectElement && target.matches(".dp-month-select")) {
1032
+ this._setDisplay(`${this.display.slice(0, 4)}-${target.value}`);
1033
+ return;
1034
+ }
1035
+ if (target instanceof HTMLSelectElement && target.matches(".dp-year-select")) {
1036
+ this._setDisplay(`${target.value}-${this.display.slice(5, 7)}`);
1037
+ return;
1038
+ }
1039
+ if (target instanceof HTMLInputElement && target.matches(".dp-year-input")) {
1040
+ const year = Number(target.value);
1041
+ if (!Number.isInteger(year) || year < 1 || year > 9999) {
1042
+ if (this._connected)
1043
+ this.render(false);
1044
+ return;
1045
+ }
1046
+ this._setDisplay(`${String(year).padStart(4, "0")}-${this.display.slice(5, 7)}`);
1047
+ }
1048
+ }
1049
+ _onFocusIn(event) {
1050
+ const target = event.target;
1051
+ if (!(target instanceof HTMLTableCellElement) || !target.matches(".dp-day[data-date]"))
1052
+ return;
1053
+ const date = target.dataset.date || "";
1054
+ if (isDate(date))
1055
+ this._model.focused = date;
1056
+ }
1057
+ _onKeyDown(event) {
1058
+ const target = event.target;
1059
+ if (!(target instanceof HTMLTableCellElement) || !target.matches(".dp-day[data-date]"))
1060
+ return;
1061
+ const date = target.dataset.date || this.focusedDate;
1062
+ if (event.key === "Enter" || event.key === " ") {
1063
+ event.preventDefault();
1064
+ this._activate(date);
1065
+ return;
1066
+ }
1067
+ const anchorFocused = this._model.focused;
1068
+ const anchorDisplay = this._model.display;
1069
+ this._model.firstDay = this.firstDay;
1070
+ this._model.setFocused(date, { follow: false });
1071
+ if (event.key === "ArrowLeft")
1072
+ this._model.moveFocusDays(-1);
1073
+ else if (event.key === "ArrowRight")
1074
+ this._model.moveFocusDays(1);
1075
+ else if (event.key === "ArrowUp")
1076
+ this._model.moveFocusDays(-7);
1077
+ else if (event.key === "ArrowDown")
1078
+ this._model.moveFocusDays(7);
1079
+ else if (event.key === "Home")
1080
+ this._model.focusWeekStart();
1081
+ else if (event.key === "End")
1082
+ this._model.focusWeekEnd();
1083
+ else if (event.key === "PageUp")
1084
+ event.shiftKey ? this._model.moveFocusYears(-1) : this._model.moveFocusMonths(-1);
1085
+ else if (event.key === "PageDown")
1086
+ event.shiftKey ? this._model.moveFocusYears(1) : this._model.moveFocusMonths(1);
1087
+ else
1088
+ return;
1089
+ const next = this._model.focused;
1090
+ this._model.focused = anchorFocused;
1091
+ this._model.display = anchorDisplay;
1092
+ if (!next)
1093
+ return;
1094
+ event.preventDefault();
1095
+ this.focusDate(this._clamp(next));
1096
+ }
1097
+ render(load = true) {
1098
+ if (!this._connected)
1099
+ return;
1100
+ const locale = this.locale;
1101
+ const display = this.display;
1102
+ const displayYear = yearOf(display);
1103
+ const displayMonth = Number(display.slice(5, 7));
1104
+ const names = monthNames(locale, "long");
1105
+ const weekdayShort = weekdayNames(locale, this.firstDay, "short");
1106
+ const weekdayLong = weekdayNames(locale, this.firstDay, "long");
1107
+ const weeks = this._weeks();
1108
+ const today = todayISO();
1109
+ const headingId = `${this._id}-heading`;
1110
+ const gridId = `${this._id}-grid`;
1111
+ const minMonth = this.min ? monthKey(this.min) : "";
1112
+ const maxMonth = this.max ? monthKey(this.max) : "";
1113
+ const prevDisabled = Boolean(minMonth && shiftMonth(display, -1) < minMonth);
1114
+ const nextDisabled = Boolean(maxMonth && shiftMonth(display, 1) > maxMonth);
1115
+ const minYearAttr = this.min ? ` min="${Number(this.min.slice(0, 4))}"` : "";
1116
+ const maxYearAttr = this.max ? ` max="${Number(this.max.slice(0, 4))}"` : "";
1117
+ const showWeeks = this.showWeekNumbers && this.firstDay === 1;
1118
+ const weekHeader = showWeeks ? `<th scope="col" class="dp-week-heading" abbr="${escapeHtml(this._messages.week)}">#</th>` : "";
1119
+ const monthOptions = names.map((name, index) => {
1120
+ const month = index + 1;
1121
+ const key = `${String(displayYear).padStart(4, "0")}-${String(month).padStart(2, "0")}`;
1122
+ const disabled = minMonth && key < minMonth || maxMonth && key > maxMonth;
1123
+ return `<option value="${String(month).padStart(2, "0")}"${month === displayMonth ? " selected" : ""}${disabled ? " disabled" : ""}>${escapeHtml(name)}</option>`;
1124
+ }).join("");
1125
+ const dayHeaders = weekdayShort.map((name, index) => `<th scope="col" abbr="${escapeHtml(weekdayLong[index])}">${escapeHtml(name.replace(".", ""))}</th>`).join("");
1126
+ const rows = weeks.map((week) => {
1127
+ const weekNumber = showWeeks ? `<th scope="row" class="dp-week-number">${isoWeekNumber(week[0])}</th>` : "";
1128
+ const cells = week.map((date) => {
1129
+ const state = this.getDateState(date);
1130
+ const outside = monthKey(date) !== display;
1131
+ const selected = this.value === date;
1132
+ const focused = this.focusedDate === date;
1133
+ const isToday = date === today;
1134
+ const position = rangePosition(date, this._highlightedRange);
1135
+ const rangeLabel = rangeLabelKey(position);
1136
+ const description = typeof state.description === "string" ? state.description : "";
1137
+ const label = [
1138
+ formatLongDate(date, locale),
1139
+ description,
1140
+ rangeLabel ? this._messages[rangeLabel] : "",
1141
+ state.disabled ? this._messages.unavailable : ""
1142
+ ].filter(Boolean).join(". ");
1143
+ return `<td class="dp-day" data-date="${date}"${outside ? ' data-outside-month="true"' : ""}${isToday ? ' data-today="true" aria-current="date"' : ""}${selected ? ' data-selected="true" aria-selected="true"' : ""}${rangeAttributes(position)}${state.disabled ? ' data-disabled="true" aria-disabled="true"' : ""} tabindex="${focused ? "0" : "-1"}" aria-label="${escapeHtml(label)}"><span class="dp-day-number" aria-hidden="true">${Number(date.slice(8, 10))}</span><span class="dp-day-extra" aria-hidden="true"></span></td>`;
1144
+ }).join("");
1145
+ return `<tr>${weekNumber}${cells}</tr>`;
1146
+ }).join("");
1147
+ const focusedElement = document.activeElement;
1148
+ const focusKey = focusedElement instanceof HTMLElement && this.contains(focusedElement) ? this._focusTargetKey(focusedElement) : "";
1149
+ this._rendering = true;
1150
+ this.innerHTML = `
1151
+ <div class="dp-calendar-shell">
1152
+ <div class="dp-calendar-header">
1153
+ <label class="dp-visually-hidden" for="${this._id}-month">${escapeHtml(this._messages.month)}</label>
1154
+ <select id="${this._id}-month" class="dp-month-select" aria-label="${escapeHtml(this._messages.month)}">${monthOptions}</select>
1155
+ <label class="dp-visually-hidden" for="${this._id}-year">${escapeHtml(this._messages.year)}</label>
1156
+ <input id="${this._id}-year" class="dp-year-input" type="number" inputmode="numeric" value="${displayYear}"${minYearAttr}${maxYearAttr} aria-label="${escapeHtml(this._messages.year)}">
1157
+ <button type="button" class="dp-nav dp-prev" data-calendar-action="previous" aria-label="${escapeHtml(this._messages.previousMonth)}"${prevDisabled ? " disabled" : ""}><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m10 4-4 4 4 4"/></svg></button>
1158
+ <button type="button" class="dp-nav dp-next" data-calendar-action="next" aria-label="${escapeHtml(this._messages.nextMonth)}"${nextDisabled ? " disabled" : ""}><svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 4 4 4-4 4"/></svg></button>
1159
+ </div>
1160
+ <h2 id="${headingId}" class="dp-calendar-heading" aria-live="polite">${escapeHtml(formatMonthYear(`${display}-15`, locale))}</h2>
1161
+ <table id="${gridId}" class="dp-grid" role="grid" aria-labelledby="${headingId}">
1162
+ <thead><tr>${weekHeader}${dayHeaders}</tr></thead>
1163
+ <tbody>${rows}</tbody>
1164
+ </table>
1165
+ </div>`;
1166
+ this._rendering = false;
1167
+ if (focusKey) {
1168
+ queueMicrotask(() => {
1169
+ const target = this.querySelector(focusKey);
1170
+ if (target instanceof HTMLElement)
1171
+ target.focus();
1172
+ });
1173
+ }
1174
+ if (this._renderDay) {
1175
+ for (const cell of this.querySelectorAll(".dp-day[data-date]")) {
1176
+ if (!(cell instanceof HTMLTableCellElement))
1177
+ continue;
1178
+ const date = cell.dataset.date || "";
1179
+ const extra = cell.querySelector(".dp-day-extra");
1180
+ if (!(extra instanceof HTMLElement))
1181
+ continue;
1182
+ const content = this._renderDay(date, this.getDateState(date));
1183
+ if (content instanceof Node)
1184
+ extra.append(content);
1185
+ else if (content !== undefined && content !== null)
1186
+ extra.textContent = String(content);
1187
+ }
1188
+ }
1189
+ if (load)
1190
+ this._loadDisplay(display);
1191
+ }
1192
+ }
1193
+
1194
+ // node_modules/@lekoala/floating/src/floating.js
1195
+ function crossAxisFor(side) {
1196
+ return side === "top" || side === "bottom" ? "x" : "y";
1197
+ }
1198
+ function parsePlacement(placement) {
1199
+ const [side, align = null] = placement.split("-");
1200
+ return { side, align, crossAxis: crossAxisFor(side) };
1201
+ }
1202
+ function flipSide(side) {
1203
+ return { top: "bottom", bottom: "top", left: "right", right: "left" }[side] || side;
1204
+ }
1205
+ function computeCoords(reference, floating, side, align, rtl, distance) {
1206
+ const crossAxis = crossAxisFor(side);
1207
+ const commonX = reference.x + reference.width / 2 - floating.width / 2;
1208
+ const commonY = reference.y + reference.height / 2 - floating.height / 2;
1209
+ const commonAlign = reference[crossAxis === "x" ? "width" : "height"] / 2 - floating[crossAxis === "x" ? "width" : "height"] / 2;
1210
+ let coords;
1211
+ switch (side) {
1212
+ case "top":
1213
+ coords = { x: commonX, y: reference.y - floating.height - distance };
1214
+ break;
1215
+ case "bottom":
1216
+ coords = { x: commonX, y: reference.y + reference.height + distance };
1217
+ break;
1218
+ case "right":
1219
+ coords = { x: reference.x + reference.width + distance, y: commonY };
1220
+ break;
1221
+ case "left":
1222
+ coords = { x: reference.x - floating.width - distance, y: commonY };
1223
+ break;
1224
+ default:
1225
+ coords = { x: reference.x, y: reference.y };
1226
+ }
1227
+ if (align === "start" || align === "end") {
1228
+ const direction = (rtl && crossAxis === "x" ? -1 : 1) * (align === "end" ? 1 : -1);
1229
+ coords[crossAxis] += commonAlign * direction;
1230
+ }
1231
+ return coords;
1232
+ }
1233
+ function overflowOn(position, size, start, end) {
1234
+ return Math.max(start - position, 0) + Math.max(position + size - end, 0);
1235
+ }
1236
+ function isRTL(element) {
1237
+ const direction = "dir" in element ? element.dir : "";
1238
+ if (direction === "rtl")
1239
+ return true;
1240
+ if (direction === "ltr")
1241
+ return false;
1242
+ const win = element.ownerDocument?.defaultView;
1243
+ if (win?.CSS?.supports?.("selector(:dir(rtl))") && typeof element.matches === "function") {
1244
+ return element.matches(":dir(rtl)");
1245
+ }
1246
+ return Boolean(win?.Element && element instanceof win.Element && win.getComputedStyle(element).direction === "rtl");
1247
+ }
1248
+ var STABLE_SCROLLBAR_MAX_WIDTH = 25;
1249
+ function getViewportBoundary(doc) {
1250
+ const win = doc.defaultView;
1251
+ if (!win)
1252
+ return null;
1253
+ const docEl = doc.documentElement;
1254
+ const visualViewport = win.visualViewport;
1255
+ const x = visualViewport?.offsetLeft || 0;
1256
+ const y = visualViewport?.offsetTop || 0;
1257
+ let width = visualViewport?.width || docEl.clientWidth || win.innerWidth;
1258
+ const height = visualViewport?.height || docEl.clientHeight || win.innerHeight;
1259
+ const reserved = doc.compatMode === "BackCompat" ? width - docEl.clientWidth : docEl.clientWidth - docEl.getBoundingClientRect().width;
1260
+ if (reserved > 0 && reserved <= STABLE_SCROLLBAR_MAX_WIDTH) {
1261
+ const gutter = win.getComputedStyle?.(docEl).scrollbarGutter;
1262
+ if (gutter && gutter !== "auto")
1263
+ width -= reserved;
1264
+ }
1265
+ return { x, y, width, height, right: x + width, bottom: y + height };
1266
+ }
1267
+ function getBoundary(reference, options) {
1268
+ return options.scope ? options.scope.getBoundingClientRect() : getViewportBoundary(reference.ownerDocument);
1269
+ }
1270
+ function clampToBoundary(position, size, start, end, padding) {
1271
+ const paddedMin = start + padding;
1272
+ const paddedMax = end - size - padding;
1273
+ const fitsPadded = paddedMax >= paddedMin;
1274
+ const min = fitsPadded ? paddedMin : start;
1275
+ const max = fitsPadded ? paddedMax : end - size;
1276
+ return Math.max(min, Math.min(position, max));
1277
+ }
1278
+ function arrowPercent(referenceCenter, boxStart, size) {
1279
+ if (!size)
1280
+ return 50;
1281
+ const percent = (referenceCenter - boxStart) / size * 100;
1282
+ return Math.round(Math.min(100, Math.max(0, percent)) * 1000) / 1000;
1283
+ }
1284
+ function isOutsideBoundary(rect, boundary) {
1285
+ return rect.right < boundary.x || rect.left > boundary.right || rect.bottom < boundary.y || rect.top > boundary.bottom;
1286
+ }
1287
+ function getAvailableHeight(referenceRect, side, boundary, distance, padding) {
1288
+ if (side === "top") {
1289
+ return Math.max(0, referenceRect.top - boundary.y - distance - padding);
1290
+ }
1291
+ if (side === "bottom") {
1292
+ return Math.max(0, boundary.bottom - referenceRect.bottom - distance - padding);
1293
+ }
1294
+ return Math.max(0, boundary.height - padding * 2);
1295
+ }
1296
+ function isVisible(element) {
1297
+ if (element.hidden)
1298
+ return false;
1299
+ if (typeof element.checkVisibility === "function")
1300
+ return element.checkVisibility();
1301
+ return element.getClientRects().length > 0;
1302
+ }
1303
+ function getFloatingSize(floating) {
1304
+ const width = floating.offsetWidth;
1305
+ const height = floating.offsetHeight;
1306
+ if (width && height)
1307
+ return { width, height };
1308
+ const rect = floating.getBoundingClientRect();
1309
+ return { width: width || rect.width, height: height || rect.height };
1310
+ }
1311
+ var trackers = new WeakMap;
1312
+ var TYPE_PRIORITY = { scroll: 0, resize: 1, "element-resize": 2 };
1313
+ function createTracker(doc) {
1314
+ const win = doc.defaultView;
1315
+ if (!win)
1316
+ throw new TypeError("floating must belong to a document with a browsing context");
1317
+ const subscriptions = new Set;
1318
+ const pending = new Map;
1319
+ const ResizeObserverCtor = win.ResizeObserver;
1320
+ let tick = false;
1321
+ let listening = false;
1322
+ const visualViewport = win.visualViewport;
1323
+ function queue(subscription, type) {
1324
+ const current = pending.get(subscription);
1325
+ if (current === undefined || TYPE_PRIORITY[type] > TYPE_PRIORITY[current]) {
1326
+ pending.set(subscription, type);
1327
+ }
1328
+ }
1329
+ function scheduleFlush() {
1330
+ if (tick)
1331
+ return;
1332
+ tick = true;
1333
+ win.requestAnimationFrame(() => {
1334
+ const notifications = [...pending];
1335
+ pending.clear();
1336
+ tick = false;
1337
+ for (const [subscription, type] of notifications) {
1338
+ if (!subscriptions.has(subscription) || !subscription.floating.isConnected)
1339
+ continue;
1340
+ subscription.callback({ type });
1341
+ }
1342
+ });
1343
+ }
1344
+ function notifyAll(type) {
1345
+ for (const subscription of subscriptions)
1346
+ queue(subscription, type);
1347
+ scheduleFlush();
1348
+ }
1349
+ function observeSizes(subscription) {
1350
+ if (!ResizeObserverCtor)
1351
+ return null;
1352
+ const primed = new Set;
1353
+ const observer = new ResizeObserverCtor((entries) => {
1354
+ let changed = false;
1355
+ for (const entry of entries) {
1356
+ if (primed.has(entry.target))
1357
+ changed = true;
1358
+ else
1359
+ primed.add(entry.target);
1360
+ }
1361
+ if (!changed)
1362
+ return;
1363
+ queue(subscription, "element-resize");
1364
+ scheduleFlush();
1365
+ });
1366
+ const { reference, floating } = subscription;
1367
+ if (reference)
1368
+ observer.observe(reference);
1369
+ if (floating !== reference)
1370
+ observer.observe(floating);
1371
+ return observer;
1372
+ }
1373
+ const onScroll = () => notifyAll("scroll");
1374
+ const onResize = () => notifyAll("resize");
1375
+ function startListening() {
1376
+ if (listening)
1377
+ return;
1378
+ doc.addEventListener("scroll", onScroll, { passive: true, capture: true });
1379
+ win.addEventListener("resize", onResize, { passive: true });
1380
+ visualViewport?.addEventListener("scroll", onScroll, { passive: true });
1381
+ visualViewport?.addEventListener("resize", onResize, { passive: true });
1382
+ listening = true;
1383
+ }
1384
+ function stopListening() {
1385
+ if (!listening)
1386
+ return;
1387
+ doc.removeEventListener("scroll", onScroll, { capture: true });
1388
+ win.removeEventListener("resize", onResize);
1389
+ visualViewport?.removeEventListener("scroll", onScroll);
1390
+ visualViewport?.removeEventListener("resize", onResize);
1391
+ listening = false;
1392
+ }
1393
+ return {
1394
+ add(reference, floating, callback) {
1395
+ const subscription = { reference, floating, callback };
1396
+ subscriptions.add(subscription);
1397
+ startListening();
1398
+ const observer = observeSizes(subscription);
1399
+ let stopped = false;
1400
+ return () => {
1401
+ if (stopped)
1402
+ return;
1403
+ stopped = true;
1404
+ subscriptions.delete(subscription);
1405
+ pending.delete(subscription);
1406
+ observer?.disconnect();
1407
+ if (subscriptions.size === 0)
1408
+ stopListening();
1409
+ };
1410
+ }
1411
+ };
1412
+ }
1413
+ function trackerFor(element) {
1414
+ const doc = element.ownerDocument;
1415
+ let tracker = trackers.get(doc);
1416
+ if (!tracker) {
1417
+ tracker = createTracker(doc);
1418
+ trackers.set(doc, tracker);
1419
+ }
1420
+ return tracker;
1421
+ }
1422
+ function autoUpdate(reference, floating, callback) {
1423
+ if (!floating?.ownerDocument) {
1424
+ throw new TypeError("autoUpdate() expects a floating HTMLElement");
1425
+ }
1426
+ if (reference && reference.ownerDocument !== floating.ownerDocument) {
1427
+ throw new TypeError("reference and floating must belong to the same document");
1428
+ }
1429
+ if (typeof callback !== "function")
1430
+ throw new TypeError("callback must be a function");
1431
+ return trackerFor(floating).add(reference, floating, callback);
1432
+ }
1433
+ function positionOnce(reference, floating, options) {
1434
+ if (!isVisible(floating))
1435
+ return null;
1436
+ const placement = options.placement || "bottom-start";
1437
+ const distance = options.distance || 0;
1438
+ const flip = options.flip !== false;
1439
+ const shift = options.shift !== false;
1440
+ const shiftPadding = options.shiftPadding ?? 4;
1441
+ let { side, align, crossAxis } = parsePlacement(placement);
1442
+ const rtl = align ? isRTL(reference) : false;
1443
+ const rects = reference.getClientRects();
1444
+ const referenceRect = side === "bottom" ? rects[rects.length - 1] : rects[0];
1445
+ if (!referenceRect)
1446
+ return null;
1447
+ const boundary = getBoundary(reference, options);
1448
+ if (!boundary || isOutsideBoundary(referenceRect, boundary))
1449
+ return null;
1450
+ const floatingRect = getFloatingSize(floating);
1451
+ const limits = {
1452
+ x: { size: floatingRect.width, start: boundary.x, end: boundary.right },
1453
+ y: { size: floatingRect.height, start: boundary.y, end: boundary.bottom }
1454
+ };
1455
+ const place = (nextSide, nextAlign) => computeCoords(referenceRect, floatingRect, nextSide, nextAlign, rtl, distance);
1456
+ const overflowAt = (position, axis) => {
1457
+ const { size, start, end } = limits[axis];
1458
+ return overflowOn(position, size, start + shiftPadding, end - shiftPadding);
1459
+ };
1460
+ const shiftedOverflowAt = (position, axis) => {
1461
+ const { size, start, end } = limits[axis];
1462
+ return overflowAt(shift ? clampToBoundary(position, size, start, end, shiftPadding) : position, axis);
1463
+ };
1464
+ let coords = place(side, align);
1465
+ if (flip) {
1466
+ const mainAxis = crossAxis === "x" ? "y" : "x";
1467
+ let overflow = overflowAt(coords[mainAxis], mainAxis);
1468
+ if (overflow > 0) {
1469
+ const opposite = flipSide(side);
1470
+ const flipped = place(opposite, align);
1471
+ const flippedOverflow = overflowAt(flipped[mainAxis], mainAxis);
1472
+ if (flippedOverflow < overflow) {
1473
+ side = opposite;
1474
+ coords = flipped;
1475
+ overflow = flippedOverflow;
1476
+ }
1477
+ }
1478
+ if (mainAxis === "x" && overflow > 0) {
1479
+ const above = place("top", align);
1480
+ const below = place("bottom", align);
1481
+ const useBottom = overflowAt(below.y, "y") < overflowAt(above.y, "y");
1482
+ const candidate = useBottom ? below : above;
1483
+ const swapped = overflowAt(candidate.y, "y") + shiftedOverflowAt(candidate.x, "x");
1484
+ if (swapped < overflow + shiftedOverflowAt(coords.y, "y")) {
1485
+ side = useBottom ? "bottom" : "top";
1486
+ crossAxis = "x";
1487
+ coords = candidate;
1488
+ }
1489
+ }
1490
+ }
1491
+ if (crossAxis === "x" && shift && align) {
1492
+ const overflow = overflowAt(coords.x, "x");
1493
+ if (overflow > 0) {
1494
+ const nextAlign = align === "end" ? "start" : "end";
1495
+ const candidate = place(side, nextAlign);
1496
+ if (overflowAt(candidate.x, "x") < overflow) {
1497
+ align = nextAlign;
1498
+ coords = candidate;
1499
+ }
1500
+ }
1501
+ }
1502
+ if (shift) {
1503
+ const { size, start, end } = limits[crossAxis];
1504
+ coords[crossAxis] = clampToBoundary(coords[crossAxis], size, start, end, shiftPadding);
1505
+ }
1506
+ const arrowX = arrowPercent(referenceRect.x + referenceRect.width / 2, coords.x, floatingRect.width);
1507
+ const arrowY = arrowPercent(referenceRect.y + referenceRect.height / 2, coords.y, floatingRect.height);
1508
+ const availableHeight = `${getAvailableHeight(referenceRect, side, boundary, distance, shiftPadding)}px`;
1509
+ const { style } = floating;
1510
+ const roomChanged = style.getPropertyValue("--available-height") !== availableHeight;
1511
+ style.left = `${coords.x}px`;
1512
+ style.top = `${coords.y}px`;
1513
+ style.setProperty("--arrow-x", `${arrowX}%`);
1514
+ style.setProperty("--arrow-y", `${arrowY}%`);
1515
+ style.setProperty("--available-height", availableHeight);
1516
+ const resolved = align ? `${side}-${align}` : side;
1517
+ floating.dataset.placement = resolved;
1518
+ return {
1519
+ width: floatingRect.width,
1520
+ height: floatingRect.height,
1521
+ roomChanged,
1522
+ placement: resolved
1523
+ };
1524
+ }
1525
+ function reposition(reference, floating, options = {}) {
1526
+ const measured = positionOnce(reference, floating, options);
1527
+ if (!measured)
1528
+ return false;
1529
+ if (measured.roomChanged) {
1530
+ const settled = getFloatingSize(floating);
1531
+ if (settled.width !== measured.width || settled.height !== measured.height) {
1532
+ positionOnce(reference, floating, {
1533
+ ...options,
1534
+ placement: measured.placement,
1535
+ flip: false
1536
+ });
1537
+ }
1538
+ }
1539
+ return true;
1540
+ }
1541
+
1542
+ // src/date-field.js
1543
+ class DateFieldController {
1544
+ constructor(input, options = {}) {
1545
+ this.input = input;
1546
+ this.messages = options.messages || {};
1547
+ this.adapter = createDateAdapter(options.locale || "");
1548
+ this.hidden = null;
1549
+ this.canonical = "";
1550
+ this._dirty = false;
1551
+ this._commitId = 0;
1552
+ this._originalName = input.getAttribute("name") || "";
1553
+ this._attributeObserver = null;
1554
+ this.onAttributesChanged = null;
1555
+ this.confirm = null;
1556
+ }
1557
+ setLocale(locale) {
1558
+ this.adapter = createDateAdapter(locale || "");
1559
+ }
1560
+ setMessages(messages) {
1561
+ this.messages = messages || {};
1562
+ }
1563
+ format(value) {
1564
+ return this.adapter.format(value);
1565
+ }
1566
+ parse(text) {
1567
+ return this.adapter.parse(text);
1568
+ }
1569
+ get placeholder() {
1570
+ return this.adapter.placeholder;
1571
+ }
1572
+ get isDisabled() {
1573
+ return this.input.disabled;
1574
+ }
1575
+ get isReadOnly() {
1576
+ return this.input.readOnly;
1577
+ }
1578
+ setupFormValue() {
1579
+ const input = this.input;
1580
+ this._originalName = input.getAttribute("name") || "";
1581
+ this._attributeObserver = new MutationObserver(() => this.onAttributesChanged?.());
1582
+ this._attributeObserver.observe(input, {
1583
+ attributes: true,
1584
+ attributeFilter: ["disabled", "readonly", "name", "form"]
1585
+ });
1586
+ this.syncInputState();
1587
+ }
1588
+ syncInputState() {
1589
+ const input = this.input;
1590
+ const name = input.getAttribute("name") || "";
1591
+ if (name) {
1592
+ if (!this.hidden) {
1593
+ const hidden = document.createElement("input");
1594
+ hidden.type = "hidden";
1595
+ hidden.value = this.canonical;
1596
+ input.insertAdjacentElement("afterend", hidden);
1597
+ this.hidden = hidden;
1598
+ }
1599
+ if (this.hidden)
1600
+ this.hidden.name = name;
1601
+ input.removeAttribute("name");
1602
+ }
1603
+ if (this.hidden) {
1604
+ if (input.hasAttribute("form"))
1605
+ this.hidden.setAttribute("form", input.getAttribute("form") || "");
1606
+ else
1607
+ this.hidden.removeAttribute("form");
1608
+ this.hidden.disabled = input.disabled;
1609
+ }
1610
+ }
1611
+ dirty() {
1612
+ this._commitId++;
1613
+ }
1614
+ get isDirty() {
1615
+ return this._dirty;
1616
+ }
1617
+ handleInput() {
1618
+ const input = this.input;
1619
+ this._dirty = true;
1620
+ this.dirty();
1621
+ if (this.hidden)
1622
+ this.hidden.value = "";
1623
+ const text = input.value.trim();
1624
+ if (!text) {
1625
+ input.setCustomValidity("");
1626
+ return;
1627
+ }
1628
+ const canonical = this.canonical ? this.adapter.format(this.canonical) : "";
1629
+ input.setCustomValidity(text === canonical ? "" : this.messages.invalidDate);
1630
+ }
1631
+ async commit() {
1632
+ const input = this.input;
1633
+ const commitId = ++this._commitId;
1634
+ const text = input.value.trim();
1635
+ if (!text) {
1636
+ if (commitId !== this._commitId)
1637
+ return { status: "stale" };
1638
+ input.setCustomValidity("");
1639
+ return { status: "clear", value: "" };
1640
+ }
1641
+ const parsed = this.adapter.parse(text);
1642
+ if (!parsed) {
1643
+ if (commitId !== this._commitId)
1644
+ return { status: "stale" };
1645
+ if (this.hidden)
1646
+ this.hidden.value = "";
1647
+ input.setCustomValidity(this.messages.invalidDate);
1648
+ return { status: "invalid" };
1649
+ }
1650
+ if (this.confirm) {
1651
+ const result = await this.confirm(parsed);
1652
+ if (commitId !== this._commitId)
1653
+ return { status: "stale" };
1654
+ if (!result?.ok) {
1655
+ if (this.hidden)
1656
+ this.hidden.value = "";
1657
+ input.setCustomValidity(result?.message || this.messages.unavailableDate);
1658
+ return { status: "invalid" };
1659
+ }
1660
+ }
1661
+ return { status: "ok", value: parsed };
1662
+ }
1663
+ setCanonical(value, options = {}) {
1664
+ const next = value || "";
1665
+ if (next && !isDate(next))
1666
+ throw new TypeError(`Invalid date-picker value: ${next}`);
1667
+ this.dirty();
1668
+ this.canonical = next;
1669
+ this._dirty = false;
1670
+ if (this.hidden)
1671
+ this.hidden.value = next;
1672
+ if (options.format !== false)
1673
+ this.input.value = next ? this.adapter.format(next) : "";
1674
+ this.input.setCustomValidity("");
1675
+ }
1676
+ restoreDefault() {
1677
+ const text = String(this.input.defaultValue ?? "").trim();
1678
+ const parsed = isDate(text) ? text : this.adapter.parse(text);
1679
+ return parsed && isDate(parsed) ? parsed : "";
1680
+ }
1681
+ teardown() {
1682
+ this._attributeObserver?.disconnect();
1683
+ this._attributeObserver = null;
1684
+ const hiddenName = this.hidden?.getAttribute("name") || this._originalName;
1685
+ if (hiddenName)
1686
+ this.input.setAttribute("name", hiddenName);
1687
+ this.hidden?.remove();
1688
+ this.hidden = null;
1689
+ }
1690
+ }
1691
+
1692
+ // src/date-picker.js
1693
+ var uid2 = 0;
1694
+
1695
+ class DatePickerElement extends HTMLElement {
1696
+ static observedAttributes = ["value", "locale", "min", "max", "open-on-focus"];
1697
+ constructor() {
1698
+ super();
1699
+ this._id = `date-picker-${++uid2}`;
1700
+ this._connected = false;
1701
+ this._reflecting = false;
1702
+ this._value = "";
1703
+ this._input = null;
1704
+ this._field = null;
1705
+ this._fields = null;
1706
+ this._range = null;
1707
+ this._orderTags = new Set;
1708
+ this._lastFocusEndpoint = "";
1709
+ this._rangeCommitId = 0;
1710
+ this._pendingIntents = new Map;
1711
+ this._button = null;
1712
+ this._panel = null;
1713
+ this._calendar = null;
1714
+ this._originalDescribedBy = "";
1715
+ this._rangeOriginalDescribedBy = ["", ""];
1716
+ this._formatHint = null;
1717
+ this._generatedPlaceholder = false;
1718
+ this._controller = null;
1719
+ this._stopTracking = null;
1720
+ this._open = false;
1721
+ this._suppressFocusOpen = false;
1722
+ this._messages = getDefaultMessages();
1723
+ this._source = null;
1724
+ this._dateState = null;
1725
+ this._renderDay = null;
1726
+ this._isDateDisabled = null;
1727
+ }
1728
+ _rangeMode() {
1729
+ return this.hasAttribute("range") && Boolean(this._range);
1730
+ }
1731
+ connectedCallback() {
1732
+ if (this._connected)
1733
+ return;
1734
+ if (this.hasAttribute("range")) {
1735
+ this._connectRange();
1736
+ return;
1737
+ }
1738
+ this._connectSingle();
1739
+ }
1740
+ _connectSingle() {
1741
+ const input = this.querySelector(":scope > input:not([type=hidden])");
1742
+ if (!(input instanceof HTMLInputElement)) {
1743
+ console.warn("<date-picker> expects a direct child text input");
1744
+ return;
1745
+ }
1746
+ this._connected = true;
1747
+ this._input = input;
1748
+ this._field = new DateFieldController(input, { messages: this._messages, locale: this.locale });
1749
+ this._field.onAttributesChanged = () => this._syncInputState();
1750
+ this._field.confirm = (date) => this._confirmDate(date);
1751
+ this._setupFormValue();
1752
+ this._build();
1753
+ this._syncInputState();
1754
+ this._bind();
1755
+ this._syncCalendarOptions();
1756
+ const adapter = this._adapter();
1757
+ const initial = this.getAttribute("value") || (isDate(input.value) ? input.value : adapter.parse(input.value));
1758
+ if (initial && isDate(initial))
1759
+ this._setValue(initial, { emit: false, format: true });
1760
+ else if (!input.value)
1761
+ this._setValue("", { emit: false, format: false });
1762
+ else
1763
+ input.setCustomValidity(this._messages.invalidDate);
1764
+ if (this._field.hidden)
1765
+ this._field.hidden.defaultValue = this._value;
1766
+ if (input.getAttribute("value") == null && input.defaultValue === "") {
1767
+ input.defaultValue = input.value;
1768
+ }
1769
+ }
1770
+ _connectRange() {
1771
+ const startInput = this.querySelector(":scope > input[data-range-start]");
1772
+ const endInput = this.querySelector(":scope > input[data-range-end]");
1773
+ if (!(startInput instanceof HTMLInputElement) || !(endInput instanceof HTMLInputElement)) {
1774
+ console.warn("<date-picker range> expects direct child inputs marked [data-range-start] and [data-range-end]");
1775
+ return;
1776
+ }
1777
+ this._connected = true;
1778
+ this._input = null;
1779
+ this._fields = [
1780
+ new DateFieldController(startInput, { messages: this._messages, locale: this.locale }),
1781
+ new DateFieldController(endInput, { messages: this._messages, locale: this.locale })
1782
+ ];
1783
+ this._range = new DateRangeController;
1784
+ this._rangeOriginalDescribedBy = [
1785
+ startInput.getAttribute("aria-describedby") || "",
1786
+ endInput.getAttribute("aria-describedby") || ""
1787
+ ];
1788
+ for (const field of this._fields) {
1789
+ field.onAttributesChanged = () => this._syncRangeFields();
1790
+ field.confirm = (date) => this._confirmDate(date);
1791
+ field.setupFormValue();
1792
+ }
1793
+ this._build(endInput);
1794
+ this._setRangeAria();
1795
+ this._syncRangeFields();
1796
+ this._bindRange();
1797
+ this._syncCalendarOptions();
1798
+ const adapter = this._adapter();
1799
+ const initialStart = isDate(startInput.value) ? startInput.value : adapter.parse(startInput.value);
1800
+ const initialEnd = isDate(endInput.value) ? endInput.value : adapter.parse(endInput.value);
1801
+ this._setRange(initialStart || "", initialEnd || "", { emit: false });
1802
+ if (startInput.value.trim() && !isDate(initialStart))
1803
+ startInput.setCustomValidity(this._messages.invalidDate);
1804
+ if (endInput.value.trim() && !isDate(initialEnd))
1805
+ endInput.setCustomValidity(this._messages.invalidDate);
1806
+ if (this._fields[0].hidden)
1807
+ this._fields[0].hidden.defaultValue = this._range.start;
1808
+ if (this._fields[1].hidden)
1809
+ this._fields[1].hidden.defaultValue = this._range.end;
1810
+ if (startInput.getAttribute("value") == null && startInput.defaultValue === "") {
1811
+ startInput.defaultValue = startInput.value;
1812
+ }
1813
+ if (endInput.getAttribute("value") == null && endInput.defaultValue === "") {
1814
+ endInput.defaultValue = endInput.value;
1815
+ }
1816
+ }
1817
+ disconnectedCallback() {
1818
+ this._connected = false;
1819
+ this.hide(false);
1820
+ this._controller?.abort();
1821
+ this._controller = null;
1822
+ if (this._rangeMode()) {
1823
+ for (const field of this._fields || [])
1824
+ this._teardownField(field);
1825
+ this._fields = null;
1826
+ this._range = null;
1827
+ } else {
1828
+ this._teardownField(this._field);
1829
+ }
1830
+ this._field = null;
1831
+ this._button?.remove();
1832
+ this._panel?.remove();
1833
+ this._formatHint?.remove();
1834
+ this._button = null;
1835
+ this._panel = null;
1836
+ this._calendar = null;
1837
+ this._formatHint = null;
1838
+ }
1839
+ _teardownField(field) {
1840
+ if (!field)
1841
+ return;
1842
+ const input = field.input;
1843
+ const original = this._fields?.includes(field) ? this._rangeOriginalDescribedBy[this._fields?.indexOf(field) ?? 0] || "" : this._originalDescribedBy;
1844
+ field.teardown();
1845
+ if (original)
1846
+ input.setAttribute("aria-describedby", original);
1847
+ else
1848
+ input.removeAttribute("aria-describedby");
1849
+ input.removeAttribute("role");
1850
+ input.removeAttribute("aria-haspopup");
1851
+ input.removeAttribute("aria-expanded");
1852
+ input.removeAttribute("aria-controls");
1853
+ }
1854
+ attributeChangedCallback(name, oldValue, newValue) {
1855
+ if (!this._connected || this._reflecting || oldValue === newValue)
1856
+ return;
1857
+ if (name === "value") {
1858
+ if (this._rangeMode())
1859
+ return;
1860
+ if (!newValue || isDate(newValue))
1861
+ this._setValue(newValue || "", { emit: false, format: true, reflect: false });
1862
+ return;
1863
+ }
1864
+ if (name === "locale")
1865
+ this._refreshLocale();
1866
+ if (name === "open-on-focus")
1867
+ return;
1868
+ this._syncCalendarOptions();
1869
+ this.validate();
1870
+ }
1871
+ get input() {
1872
+ return this._rangeMode() ? undefined : this._input;
1873
+ }
1874
+ get calendar() {
1875
+ return this._calendar;
1876
+ }
1877
+ get value() {
1878
+ return this._rangeMode() ? undefined : this._value;
1879
+ }
1880
+ set value(value) {
1881
+ if (this._rangeMode()) {
1882
+ throw new TypeError("value is not available in range mode; use range");
1883
+ }
1884
+ if (value && !isDate(value))
1885
+ throw new TypeError(`Invalid date-picker value: ${value}`);
1886
+ this._setValue(value || "", { emit: false, format: true });
1887
+ }
1888
+ get range() {
1889
+ return this._rangeMode() && this._range ? { ...this._range.range } : undefined;
1890
+ }
1891
+ set range(value) {
1892
+ if (!this._rangeMode())
1893
+ throw new TypeError("range is only available on <date-picker range>");
1894
+ const { start, end } = normalizeRange(value);
1895
+ this._setRange(start, end);
1896
+ }
1897
+ get locale() {
1898
+ return resolveLocale(this.getAttribute("locale") || "");
1899
+ }
1900
+ set locale(value) {
1901
+ if (value)
1902
+ this.setAttribute("locale", value);
1903
+ else
1904
+ this.removeAttribute("locale");
1905
+ }
1906
+ get min() {
1907
+ const value = this.getAttribute("min") || "";
1908
+ return isDate(value) ? value : "";
1909
+ }
1910
+ set min(value) {
1911
+ if (value)
1912
+ this.setAttribute("min", value);
1913
+ else
1914
+ this.removeAttribute("min");
1915
+ }
1916
+ get max() {
1917
+ const value = this.getAttribute("max") || "";
1918
+ return isDate(value) ? value : "";
1919
+ }
1920
+ set max(value) {
1921
+ if (value)
1922
+ this.setAttribute("max", value);
1923
+ else
1924
+ this.removeAttribute("max");
1925
+ }
1926
+ get openOnFocus() {
1927
+ return this.getAttribute("open-on-focus") !== "false";
1928
+ }
1929
+ set openOnFocus(value) {
1930
+ if (value === false)
1931
+ this.setAttribute("open-on-focus", "false");
1932
+ else
1933
+ this.removeAttribute("open-on-focus");
1934
+ }
1935
+ get open() {
1936
+ return this._open;
1937
+ }
1938
+ get messages() {
1939
+ return this._messages;
1940
+ }
1941
+ set messages(value) {
1942
+ this._messages = { ...getDefaultMessages(), ...value || {} };
1943
+ if (this._calendar)
1944
+ this._calendar.messages = this._messages;
1945
+ for (const field of this._fields || [])
1946
+ field.setMessages(this._messages);
1947
+ this._field?.setMessages(this._messages);
1948
+ this._refreshButtonLabel();
1949
+ this.validate();
1950
+ }
1951
+ get source() {
1952
+ return this._source;
1953
+ }
1954
+ set source(value) {
1955
+ this._source = value || null;
1956
+ if (this._calendar)
1957
+ this._calendar.source = this._source;
1958
+ if (this._connected)
1959
+ this.validate();
1960
+ }
1961
+ get dateState() {
1962
+ return this._dateState;
1963
+ }
1964
+ set dateState(value) {
1965
+ this._dateState = typeof value === "function" ? value : null;
1966
+ if (this._calendar)
1967
+ this._calendar.dateState = this._dateState;
1968
+ if (this._connected)
1969
+ this.validate();
1970
+ }
1971
+ get renderDay() {
1972
+ return this._renderDay;
1973
+ }
1974
+ set renderDay(value) {
1975
+ this._renderDay = typeof value === "function" ? value : null;
1976
+ if (this._calendar)
1977
+ this._calendar.renderDay = this._renderDay;
1978
+ }
1979
+ get isDateDisabled() {
1980
+ return this._isDateDisabled;
1981
+ }
1982
+ set isDateDisabled(value) {
1983
+ this._isDateDisabled = typeof value === "function" ? value : null;
1984
+ if (this._calendar)
1985
+ this._calendar.isDateDisabled = this._isDateDisabled;
1986
+ if (this._connected)
1987
+ this.validate();
1988
+ }
1989
+ _adapter() {
1990
+ return this._field?.adapter ?? this._fields?.[0]?.adapter ?? createDateAdapter(this.locale);
1991
+ }
1992
+ _setupFormValue() {
1993
+ this._field?.setupFormValue();
1994
+ }
1995
+ _syncInputState() {
1996
+ const input = this._input;
1997
+ if (!input)
1998
+ return;
1999
+ this._field?.syncInputState();
2000
+ if (this._button)
2001
+ this._button.disabled = input.disabled || input.readOnly;
2002
+ if (this._open && (input.disabled || input.readOnly))
2003
+ this.hide(false);
2004
+ }
2005
+ _syncRangeFields() {
2006
+ if (!this._fields)
2007
+ return;
2008
+ for (const field of this._fields)
2009
+ field.syncInputState();
2010
+ const active = this._range?.activeEndpoint === "end" ? 1 : 0;
2011
+ const disabled = this._fields.every((field) => field.input.disabled || field.input.readOnly);
2012
+ if (this._button)
2013
+ this._button.disabled = disabled;
2014
+ if (this._open && (this._fields[active]?.input.disabled || this._fields[active]?.input.readOnly)) {
2015
+ this.hide(false);
2016
+ }
2017
+ }
2018
+ _setRangeAria() {
2019
+ if (!this._fields || !this._panel || !this._formatHint)
2020
+ return;
2021
+ for (const field of this._fields) {
2022
+ const input = field.input;
2023
+ const describedBy = [this._rangeOriginalDescribedBy[this._fields.indexOf(field)], this._formatHint.id].filter(Boolean).join(" ");
2024
+ input.setAttribute("aria-describedby", describedBy);
2025
+ input.setAttribute("role", "combobox");
2026
+ input.setAttribute("aria-haspopup", "dialog");
2027
+ input.setAttribute("aria-expanded", "false");
2028
+ input.setAttribute("aria-controls", this._panel.id);
2029
+ }
2030
+ }
2031
+ _build(anchorInput) {
2032
+ const input = anchorInput || this._input;
2033
+ if (!input)
2034
+ return;
2035
+ const panelId = `${this._id}-panel`;
2036
+ const hintId = `${this._id}-format`;
2037
+ const button = document.createElement("button");
2038
+ button.type = "button";
2039
+ button.className = "dp-picker-button";
2040
+ button.setAttribute("aria-haspopup", "dialog");
2041
+ button.setAttribute("aria-expanded", "false");
2042
+ button.setAttribute("aria-controls", panelId);
2043
+ button.innerHTML = '<span aria-hidden="true"><svg viewBox="0 0 16 16" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"><rect x="1.75" y="3" width="12.5" height="11" rx="1.75"/><path d="M1.75 6.75h12.5"/><path d="M5.25 1.75v2.5M10.75 1.75v2.5"/></svg></span>';
2044
+ const panel = document.createElement("div");
2045
+ panel.id = panelId;
2046
+ panel.className = "dp-picker-panel";
2047
+ panel.setAttribute("popover", "manual");
2048
+ panel.setAttribute("role", "dialog");
2049
+ panel.setAttribute("aria-label", this._messages.calendar);
2050
+ const calendar = new DateCalendarElement;
2051
+ calendar.selection = "none";
2052
+ calendar.fixedWeeks = true;
2053
+ panel.append(calendar);
2054
+ const hint = document.createElement("span");
2055
+ hint.id = hintId;
2056
+ hint.className = "dp-visually-hidden";
2057
+ input.insertAdjacentElement("afterend", button);
2058
+ button.insertAdjacentElement("afterend", panel);
2059
+ panel.insertAdjacentElement("afterend", hint);
2060
+ this._button = button;
2061
+ this._panel = panel;
2062
+ this._calendar = calendar;
2063
+ this._formatHint = hint;
2064
+ if (!this._rangeMode()) {
2065
+ this._originalDescribedBy = input.getAttribute("aria-describedby") || "";
2066
+ const describedBy = [this._originalDescribedBy, hintId].filter(Boolean).join(" ");
2067
+ input.setAttribute("aria-describedby", describedBy);
2068
+ input.setAttribute("role", "combobox");
2069
+ input.setAttribute("aria-haspopup", "dialog");
2070
+ input.setAttribute("aria-expanded", "false");
2071
+ input.setAttribute("aria-controls", panelId);
2072
+ }
2073
+ this._refreshLocale();
2074
+ this._refreshButtonLabel();
2075
+ }
2076
+ _bind() {
2077
+ const input = this._input;
2078
+ const button = this._button;
2079
+ const calendar = this._calendar;
2080
+ if (!input || !button || !calendar)
2081
+ return;
2082
+ const controller = new AbortController;
2083
+ this._controller = controller;
2084
+ const { signal } = controller;
2085
+ input.addEventListener("input", () => {
2086
+ this._field?.handleInput();
2087
+ }, { signal });
2088
+ input.addEventListener("change", () => void this._commitText(false), { signal });
2089
+ input.addEventListener("blur", () => void this._commitText(false), { signal });
2090
+ input.addEventListener("focus", (event) => {
2091
+ if (!this.openOnFocus || this._suppressFocusOpen)
2092
+ return;
2093
+ const related = event.relatedTarget;
2094
+ if (related instanceof Node && this.contains(related))
2095
+ return;
2096
+ if (!this._open)
2097
+ this.show({ moveFocus: false });
2098
+ }, { signal });
2099
+ input.addEventListener("keydown", (event) => {
2100
+ this._onFieldKeyDown("", event);
2101
+ }, { signal });
2102
+ button.addEventListener("click", (event) => {
2103
+ if (this._open) {
2104
+ if (event.detail === 0) {
2105
+ setTimeout(() => this._calendar?.focusGrid(), 0);
2106
+ return;
2107
+ }
2108
+ this.hide(false);
2109
+ return;
2110
+ }
2111
+ this.show();
2112
+ }, { signal });
2113
+ calendar.addEventListener("dateactivate", (event) => {
2114
+ const custom = event;
2115
+ const date = custom.detail?.date;
2116
+ if (!isDate(date))
2117
+ return;
2118
+ queueMicrotask(() => {
2119
+ if (custom.defaultPrevented || !this._connected)
2120
+ return;
2121
+ if (this._input?.disabled || this._input?.readOnly)
2122
+ return;
2123
+ this._field?.dirty();
2124
+ this._setValue(date, { emit: true, format: true });
2125
+ this.hide(false);
2126
+ this._focusInput();
2127
+ });
2128
+ }, { signal });
2129
+ calendar.addEventListener("dateloadend", () => void this.validate(), { signal });
2130
+ this.ownerDocument.addEventListener("pointerdown", (event) => {
2131
+ if (!this._open || event.composedPath().includes(this))
2132
+ return;
2133
+ this.hide(false);
2134
+ }, { capture: true, signal });
2135
+ this.ownerDocument.addEventListener("reset", (event) => {
2136
+ const form = event.target;
2137
+ if (form instanceof HTMLFormElement && this._input && form === this._input.form) {
2138
+ this.hide(false);
2139
+ queueMicrotask(() => {
2140
+ if (!event.defaultPrevented)
2141
+ this._restoreDefault();
2142
+ });
2143
+ }
2144
+ }, { capture: true, signal });
2145
+ this.addEventListener("keydown", (event) => this._onEscape(event), { signal });
2146
+ }
2147
+ _bindRange() {
2148
+ const button = this._button;
2149
+ const calendar = this._calendar;
2150
+ if (!this._fields || !button || !calendar)
2151
+ return;
2152
+ const controller = new AbortController;
2153
+ this._controller = controller;
2154
+ const { signal } = controller;
2155
+ for (const [index, field] of this._fields.entries()) {
2156
+ const endpoint = index === 1 ? "end" : "start";
2157
+ field.input.addEventListener("input", () => field.handleInput(), { signal });
2158
+ field.input.addEventListener("change", () => void this._commitFieldText(endpoint), { signal });
2159
+ field.input.addEventListener("blur", () => void this._commitFieldText(endpoint), { signal });
2160
+ field.input.addEventListener("focus", (event) => this._onFieldFocus(endpoint, event), { signal });
2161
+ field.input.addEventListener("keydown", (event) => this._onFieldKeyDown(endpoint, event), { signal });
2162
+ }
2163
+ button.addEventListener("click", (event) => {
2164
+ if (this._open) {
2165
+ if (event.detail === 0) {
2166
+ setTimeout(() => this._calendar?.focusGrid(), 0);
2167
+ return;
2168
+ }
2169
+ this.hide(false);
2170
+ return;
2171
+ }
2172
+ this.show();
2173
+ }, { signal });
2174
+ calendar.addEventListener("click", (event) => this._captureGridIntent(event), { capture: true, signal });
2175
+ calendar.addEventListener("keydown", (event) => this._captureGridIntent(event), {
2176
+ capture: true,
2177
+ signal
2178
+ });
2179
+ calendar.addEventListener("dateactivate", (event) => {
2180
+ const custom = event;
2181
+ const date = custom.detail?.date;
2182
+ if (!isDate(date))
2183
+ return;
2184
+ queueMicrotask(() => {
2185
+ if (custom.defaultPrevented || !this._connected || !this._range)
2186
+ return;
2187
+ const pending = this._pendingIntents.get(date);
2188
+ if (pending === undefined || pending !== this._rangeCommitId)
2189
+ return;
2190
+ this._pendingIntents.delete(date);
2191
+ const result = this._range.activate(date, (which) => {
2192
+ const index = which === "end" ? 1 : 0;
2193
+ const field = this._fields?.[index];
2194
+ return field != null && !field.input.disabled && !field.input.readOnly;
2195
+ });
2196
+ if (result.status === "refused") {
2197
+ this._calendar?.dispatchEvent(new CustomEvent("dateinvalid", {
2198
+ detail: { date, state: this._calendar.getDateState(date) },
2199
+ bubbles: true
2200
+ }));
2201
+ return;
2202
+ }
2203
+ this._setBound(result.endpoint, date, { emit: true, user: true });
2204
+ if (result.close || result.status === "complete") {
2205
+ this.hide(false);
2206
+ this._focusField(result.endpoint);
2207
+ }
2208
+ });
2209
+ }, { signal });
2210
+ calendar.addEventListener("dateloadend", () => void this.validate(), { signal });
2211
+ this.ownerDocument.addEventListener("pointerdown", (event) => {
2212
+ if (!this._open || event.composedPath().includes(this))
2213
+ return;
2214
+ this.hide(false);
2215
+ }, { capture: true, signal });
2216
+ this.ownerDocument.addEventListener("reset", (event) => {
2217
+ const form = event.target;
2218
+ if (form instanceof HTMLFormElement && this._fields && form === this._fields[0].input.form) {
2219
+ this.hide(false);
2220
+ queueMicrotask(() => {
2221
+ if (!event.defaultPrevented)
2222
+ this._restoreRangeDefault();
2223
+ });
2224
+ }
2225
+ }, { capture: true, signal });
2226
+ this.addEventListener("keydown", (event) => this._onEscape(event), { signal });
2227
+ }
2228
+ _onFieldKeyDown(endpoint, event) {
2229
+ if (event.key === "ArrowDown" || event.altKey && event.key === "ArrowDown") {
2230
+ event.preventDefault();
2231
+ if (this._rangeMode() && endpoint) {
2232
+ this._lastFocusEndpoint = endpoint;
2233
+ this._range?.focus(endpoint);
2234
+ }
2235
+ if (this._open) {
2236
+ setTimeout(() => this._calendar?.focusGrid(), 0);
2237
+ } else {
2238
+ this.show();
2239
+ }
2240
+ } else if (event.key === "Escape" && this._open) {
2241
+ event.preventDefault();
2242
+ this.hide(false);
2243
+ }
2244
+ }
2245
+ _onFieldFocus(endpoint, event) {
2246
+ if (!this._rangeMode() || !endpoint)
2247
+ return;
2248
+ const switched = endpoint !== this._range?.activeEndpoint;
2249
+ this._lastFocusEndpoint = endpoint;
2250
+ this._range?.focus(endpoint);
2251
+ if (switched) {
2252
+ this._rangeCommitId++;
2253
+ this._pendingIntents.clear();
2254
+ }
2255
+ if (!this.openOnFocus || this._suppressFocusOpen)
2256
+ return;
2257
+ const related = event.relatedTarget;
2258
+ if (related instanceof Node && this.contains(related))
2259
+ return;
2260
+ if (!this._open)
2261
+ this.show({ moveFocus: false });
2262
+ }
2263
+ _captureGridIntent(event) {
2264
+ if (!this._rangeMode() || !this._range)
2265
+ return;
2266
+ if (event.type === "keydown") {
2267
+ const key = event.key;
2268
+ if (key !== "Enter" && key !== " ")
2269
+ return;
2270
+ }
2271
+ if (!(event.target instanceof Element))
2272
+ return;
2273
+ const cell = event.target.closest(".dp-day[data-date]");
2274
+ const date = cell?.getAttribute("data-date") || "";
2275
+ if (!isDate(date))
2276
+ return;
2277
+ this._pendingIntents.set(date, ++this._rangeCommitId);
2278
+ }
2279
+ _onEscape(event) {
2280
+ if (event.key === "Escape" && this._open) {
2281
+ event.preventDefault();
2282
+ event.stopPropagation();
2283
+ this.hide(true);
2284
+ }
2285
+ }
2286
+ _syncCalendarOptions() {
2287
+ const calendar = this._calendar;
2288
+ const panel = this._panel;
2289
+ if (!calendar)
2290
+ return;
2291
+ calendar.locale = this.locale;
2292
+ calendar.min = this.min;
2293
+ calendar.max = this.max;
2294
+ calendar.messages = this._messages;
2295
+ calendar.source = this._source;
2296
+ calendar.dateState = this._dateState;
2297
+ calendar.renderDay = this._renderDay;
2298
+ calendar.isDateDisabled = this._isDateDisabled;
2299
+ if (!this._rangeMode() && this._value)
2300
+ calendar.value = this._value;
2301
+ if (panel)
2302
+ panel.setAttribute("aria-label", this._messages.calendar);
2303
+ }
2304
+ _refreshLocale() {
2305
+ const hint = this._formatHint;
2306
+ if (!hint)
2307
+ return;
2308
+ if (this._rangeMode() && this._fields) {
2309
+ this._fields[0].setLocale(this.locale);
2310
+ this._fields[1].setLocale(this.locale);
2311
+ hint.textContent = `${this._messages.formatHint}: ${this._fields[0].placeholder}`;
2312
+ for (const [index, field] of this._fields.entries()) {
2313
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
2314
+ field.input.placeholder = field.placeholder;
2315
+ this._generatedPlaceholder = true;
2316
+ }
2317
+ const canonical = index === 1 ? this._range?.end || "" : this._range?.start || "";
2318
+ if (canonical)
2319
+ field.input.value = field.format(canonical);
2320
+ }
2321
+ this._refreshButtonLabel();
2322
+ return;
2323
+ }
2324
+ const field = this._field;
2325
+ if (!field)
2326
+ return;
2327
+ field.setLocale(this.locale);
2328
+ hint.textContent = `${this._messages.formatHint}: ${field.placeholder}`;
2329
+ if (!field.input.hasAttribute("placeholder") || this._generatedPlaceholder) {
2330
+ field.input.placeholder = field.placeholder;
2331
+ this._generatedPlaceholder = true;
2332
+ }
2333
+ if (this._value)
2334
+ field.input.value = field.format(this._value);
2335
+ this._refreshButtonLabel();
2336
+ }
2337
+ _refreshButtonLabel() {
2338
+ if (!this._button)
2339
+ return;
2340
+ if (this._rangeMode()) {
2341
+ this._refreshRangeButtonLabel();
2342
+ return;
2343
+ }
2344
+ const prefix = this._value ? this._messages.changeDate : this._messages.chooseDate;
2345
+ const suffix = this._value ? `, ${formatLongDate(this._value, this.locale)}` : "";
2346
+ this._button.setAttribute("aria-label", `${prefix}${suffix}`);
2347
+ }
2348
+ _refreshRangeButtonLabel() {
2349
+ if (!this._button || !this._range)
2350
+ return;
2351
+ const { start, end } = this._range;
2352
+ const hasRange = Boolean(start && end);
2353
+ const prefix = hasRange ? this._messages.changeDate : this._messages.chooseDate;
2354
+ const suffix = hasRange ? `, ${formatLongDate(start, this.locale)} – ${formatLongDate(end, this.locale)}` : "";
2355
+ this._button.setAttribute("aria-label", `${prefix}${suffix}`);
2356
+ }
2357
+ _reflectValue(value) {
2358
+ this._reflecting = true;
2359
+ if (value)
2360
+ this.setAttribute("value", value);
2361
+ else
2362
+ this.removeAttribute("value");
2363
+ this._reflecting = false;
2364
+ }
2365
+ _setValue(value, options = {}) {
2366
+ if (value && !isDate(value))
2367
+ throw new TypeError(`Invalid date-picker value: ${value}`);
2368
+ const previous = this._value;
2369
+ this._value = value || "";
2370
+ if (options.reflect !== false)
2371
+ this._reflectValue(this._value);
2372
+ this._field?.setCanonical(this._value, { format: options.format !== false });
2373
+ if (this._calendar) {
2374
+ this._calendar.value = this._value;
2375
+ if (this._value)
2376
+ this._calendar.focusedDate = this._value;
2377
+ }
2378
+ this._refreshButtonLabel();
2379
+ if (previous !== this._value) {
2380
+ this.dispatchEvent(new CustomEvent("valuechange", { detail: { value: this._value }, bubbles: true }));
2381
+ if (options.emit && this._field) {
2382
+ const input = this._field.input;
2383
+ input.dispatchEvent(new Event("input", { bubbles: true }));
2384
+ input.dispatchEvent(new Event("change", { bubbles: true }));
2385
+ }
2386
+ }
2387
+ }
2388
+ async _confirmDate(date) {
2389
+ const calendar = this._calendar;
2390
+ if (!calendar)
2391
+ return { ok: true };
2392
+ const display = monthKey(date);
2393
+ if (calendar.display !== display)
2394
+ calendar.display = display;
2395
+ const confirmed = await calendar.ensureDate(date);
2396
+ const state = calendar.getDateState(date);
2397
+ if (!confirmed || state.disabled)
2398
+ return { ok: false, message: this._messages.unavailableDate };
2399
+ return { ok: true };
2400
+ }
2401
+ async _commitText(emit, options = {}) {
2402
+ const field = this._field;
2403
+ if (!field)
2404
+ return false;
2405
+ if (!options.force && !field.isDirty)
2406
+ return false;
2407
+ const result = await field.commit();
2408
+ if (result.status === "stale" || result.status === "invalid")
2409
+ return false;
2410
+ this._setValue(result.value || "", { emit, format: result.status === "ok" });
2411
+ return true;
2412
+ }
2413
+ async _commitFieldText(which) {
2414
+ const fields = this._fields;
2415
+ if (!fields)
2416
+ return false;
2417
+ const index = which === "end" ? 1 : 0;
2418
+ const field = fields[index];
2419
+ if (!field?.isDirty)
2420
+ return false;
2421
+ const result = await field.commit();
2422
+ if (result.status === "stale")
2423
+ return false;
2424
+ if (result.status === "invalid") {
2425
+ this._orderTags.delete(index);
2426
+ return false;
2427
+ }
2428
+ this._setBound(which, result.value || "", { emit: true, user: true, format: result.status === "ok" });
2429
+ return true;
2430
+ }
2431
+ _setBound(which, value, options = {}) {
2432
+ if (!this._range)
2433
+ return false;
2434
+ const fields = this._fields;
2435
+ if (!fields)
2436
+ return false;
2437
+ const index = which === "end" ? 1 : 0;
2438
+ const field = fields[index];
2439
+ if (!field)
2440
+ return false;
2441
+ if (options.user === true && (field.input.disabled || field.input.readOnly))
2442
+ return false;
2443
+ const next = value || "";
2444
+ const previous = field.canonical;
2445
+ field.setCanonical(next, { format: options.format !== false });
2446
+ if (which === "end")
2447
+ this._range.end = next;
2448
+ else
2449
+ this._range.start = next;
2450
+ const changed = previous !== next;
2451
+ if (changed) {
2452
+ this._syncHighlight();
2453
+ this._refreshRangeButtonLabel();
2454
+ this._emitRangeChange();
2455
+ if (options.emit) {
2456
+ field.input.dispatchEvent(new Event("input", { bubbles: true }));
2457
+ field.input.dispatchEvent(new Event("change", { bubbles: true }));
2458
+ }
2459
+ }
2460
+ this._revalidateRange(which);
2461
+ return changed;
2462
+ }
2463
+ _setRange(start, end, options = {}) {
2464
+ if (!this._range || !this._fields)
2465
+ return;
2466
+ this._range.start = start || "";
2467
+ this._range.end = end || "";
2468
+ this._fields[0].setCanonical(start || "", { format: options.format !== false });
2469
+ this._fields[1].setCanonical(end || "", { format: options.format !== false });
2470
+ this._syncHighlight();
2471
+ this._refreshRangeButtonLabel();
2472
+ this._revalidateRange("start");
2473
+ this._revalidateRange("end");
2474
+ if (options.emit !== false)
2475
+ this._emitRangeChange();
2476
+ }
2477
+ _boundCanonical(which) {
2478
+ return this._range ? which === "end" ? this._range.end : this._range.start : "";
2479
+ }
2480
+ _emitRangeChange() {
2481
+ if (!this._range)
2482
+ return;
2483
+ this.dispatchEvent(new CustomEvent("rangechange", { detail: { ...this._range.range }, bubbles: true }));
2484
+ }
2485
+ _syncHighlight() {
2486
+ if (!this._calendar || !this._range)
2487
+ return;
2488
+ const { start, end } = this._range;
2489
+ if (!start) {
2490
+ this._calendar.highlightedRange = { start: "", end: "" };
2491
+ return;
2492
+ }
2493
+ if (end && compareDates(end, start) < 0) {
2494
+ this._calendar.highlightedRange = { start, end: "" };
2495
+ } else {
2496
+ this._calendar.highlightedRange = { start, end };
2497
+ }
2498
+ }
2499
+ _revalidateRange(which) {
2500
+ if (!this._fields || !this._range)
2501
+ return;
2502
+ const { start, end } = this._range;
2503
+ const inverted = Boolean(start && end && compareDates(end, start) < 0);
2504
+ const bounds = ["start", "end"];
2505
+ for (const bound of bounds) {
2506
+ const index = bound === "end" ? 1 : 0;
2507
+ const input = this._fields[index].input;
2508
+ const message = bound === "end" ? this._messages.rangeOrderEnd : this._messages.rangeOrderStart;
2509
+ const shouldHaveOrder = inverted && bound === which;
2510
+ if (shouldHaveOrder) {
2511
+ input.setCustomValidity(message);
2512
+ this._orderTags.add(index);
2513
+ } else if (this._orderTags.has(index)) {
2514
+ input.setCustomValidity("");
2515
+ this._orderTags.delete(index);
2516
+ }
2517
+ }
2518
+ }
2519
+ _restoreDefault() {
2520
+ const field = this._field;
2521
+ if (!field)
2522
+ return;
2523
+ const value = field.restoreDefault();
2524
+ this._setValue(value, { emit: false, format: true });
2525
+ if (this._calendar) {
2526
+ if (this._value) {
2527
+ this._calendar.display = monthKey(this._value);
2528
+ this._calendar.focusedDate = this._value;
2529
+ } else {
2530
+ this._calendar.display = monthKey(todayISO());
2531
+ this._calendar.focusedDate = todayISO();
2532
+ }
2533
+ }
2534
+ this.validate();
2535
+ }
2536
+ _restoreRangeDefault() {
2537
+ if (!this._fields)
2538
+ return;
2539
+ const start = this._fields[0].restoreDefault();
2540
+ const end = this._fields[1].restoreDefault();
2541
+ this._setRange(start, end);
2542
+ if (this._calendar) {
2543
+ const target = this._range?.start || todayISO();
2544
+ this._calendar.display = monthKey(target);
2545
+ this._calendar.focusedDate = target;
2546
+ }
2547
+ this.validate();
2548
+ }
2549
+ _focusField(which) {
2550
+ const index = which === "end" ? 1 : 0;
2551
+ const input = this._fields?.[index]?.input;
2552
+ if (!input)
2553
+ return;
2554
+ this._suppressFocusOpen = true;
2555
+ setTimeout(() => {
2556
+ input.focus();
2557
+ this._suppressFocusOpen = false;
2558
+ }, 0);
2559
+ }
2560
+ _focusInput() {
2561
+ this._suppressFocusOpen = true;
2562
+ setTimeout(() => {
2563
+ this._input?.focus();
2564
+ this._suppressFocusOpen = false;
2565
+ }, 0);
2566
+ }
2567
+ _resolveRangeEndpoint() {
2568
+ if (!this._fields)
2569
+ return "";
2570
+ const preferred = this._lastFocusEndpoint || "start";
2571
+ const preferredIndex = preferred === "end" ? 1 : 0;
2572
+ if (!this._fields[preferredIndex].input.disabled && !this._fields[preferredIndex].input.readOnly) {
2573
+ return preferred;
2574
+ }
2575
+ const alternative = preferred === "end" ? "start" : "end";
2576
+ const alternativeIndex = alternative === "end" ? 1 : 0;
2577
+ if (!this._fields[alternativeIndex].input.disabled && !this._fields[alternativeIndex].input.readOnly) {
2578
+ return alternative;
2579
+ }
2580
+ return "";
2581
+ }
2582
+ async validate() {
2583
+ if (this._rangeMode()) {
2584
+ const fields = this._fields;
2585
+ if (!fields)
2586
+ return true;
2587
+ for (const [index, field] of fields.entries()) {
2588
+ const result = await field.commit();
2589
+ if (result.status === "ok" || result.status === "clear") {
2590
+ const which = index === 1 ? "end" : "start";
2591
+ this._setBound(which, result.value || "", { format: result.status === "ok" });
2592
+ }
2593
+ }
2594
+ this._revalidateRange("start");
2595
+ this._revalidateRange("end");
2596
+ return fields.every((field) => field.input.checkValidity());
2597
+ }
2598
+ const input = this._field?.input;
2599
+ if (!input)
2600
+ return true;
2601
+ if (!input.value.trim()) {
2602
+ input.setCustomValidity("");
2603
+ return input.checkValidity();
2604
+ }
2605
+ await this._commitText(false, { force: true });
2606
+ return input.checkValidity();
2607
+ }
2608
+ show(options = {}) {
2609
+ const panel = this._panel;
2610
+ const calendar = this._calendar;
2611
+ const button = this._button;
2612
+ if (!panel || !calendar || !button || this._open)
2613
+ return;
2614
+ if (this._rangeMode()) {
2615
+ const endpoint = this._resolveRangeEndpoint();
2616
+ if (!endpoint)
2617
+ return;
2618
+ this._range?.focus(endpoint);
2619
+ this._rangeCommitId++;
2620
+ this._pendingIntents.clear();
2621
+ const target = this._boundCanonical(endpoint) || todayISO();
2622
+ calendar.display = monthKey(target);
2623
+ calendar.focusedDate = target;
2624
+ this._lastFocusEndpoint = endpoint;
2625
+ } else {
2626
+ const input = this._input;
2627
+ if (!input || input.disabled || input.readOnly)
2628
+ return;
2629
+ const target = this._value || this._adapter().parse(input.value) || todayISO();
2630
+ calendar.display = monthKey(target);
2631
+ calendar.focusedDate = target;
2632
+ }
2633
+ panel.showPopover();
2634
+ this._open = true;
2635
+ this._setExpanded(true);
2636
+ const position = () => reposition(this, panel, { placement: "bottom-start", distance: 4, shiftPadding: 8 });
2637
+ position();
2638
+ this._stopTracking = autoUpdate(this, panel, position);
2639
+ if (options.moveFocus !== false)
2640
+ queueMicrotask(() => calendar.focusGrid());
2641
+ this.dispatchEvent(new Event("open", { bubbles: true }));
2642
+ }
2643
+ _setExpanded(expanded) {
2644
+ const value = expanded ? "true" : "false";
2645
+ this._input?.setAttribute("aria-expanded", value);
2646
+ this._button?.setAttribute("aria-expanded", value);
2647
+ for (const field of this._fields || [])
2648
+ field.input.setAttribute("aria-expanded", value);
2649
+ }
2650
+ hide(restoreFocus = false) {
2651
+ if (!this._panel || !this._open)
2652
+ return;
2653
+ this._stopTracking?.();
2654
+ this._stopTracking = null;
2655
+ try {
2656
+ this._panel.hidePopover();
2657
+ } catch {}
2658
+ this._open = false;
2659
+ this._setExpanded(false);
2660
+ this._rangeCommitId++;
2661
+ this._pendingIntents.clear();
2662
+ if (restoreFocus) {
2663
+ if (this._rangeMode()) {
2664
+ const endpoint = this._restoreFocusEndpoint();
2665
+ if (endpoint)
2666
+ this._focusField(endpoint);
2667
+ } else {
2668
+ this._focusInput();
2669
+ }
2670
+ }
2671
+ this.dispatchEvent(new Event("close", { bubbles: true }));
2672
+ }
2673
+ _restoreFocusEndpoint() {
2674
+ const active = this._range?.activeEndpoint || "";
2675
+ if (active) {
2676
+ const index = active === "end" ? 1 : 0;
2677
+ const field = this._fields?.[index];
2678
+ if (field && !field.input.disabled && !field.input.readOnly)
2679
+ return active;
2680
+ }
2681
+ return this._resolveRangeEndpoint();
2682
+ }
2683
+ }
2684
+
2685
+ // src/define.js
2686
+ function defineDatePicker() {
2687
+ if (!customElements.get("date-calendar"))
2688
+ customElements.define("date-calendar", DateCalendarElement);
2689
+ if (!customElements.get("date-picker"))
2690
+ customElements.define("date-picker", DatePickerElement);
2691
+ }
2692
+ defineDatePicker();
2693
+ })();