@wpmoo/ui 1.0.0-rc.2 → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1154 @@
1
+ /*!
2
+ * Moo UI datepicker.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
3
+ * Copyright 2026 WPMoo (https://wpmoo.org)
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
6
+ // src/js/components/datepicker.js
7
+ var datepickerInstances = /* @__PURE__ */ new WeakMap();
8
+ var calendarInstances = /* @__PURE__ */ new WeakMap();
9
+ var rangePickerInstances = /* @__PURE__ */ new WeakMap();
10
+ var DEFAULT_LOCALE = "en";
11
+ var DAY_MS = 24 * 60 * 60 * 1e3;
12
+ var WEEKDAY_BASE_SUNDAY = new Date(2026, 7, 16);
13
+ var POPOVER_VIEWPORT_PADDING = 8;
14
+ function isElement(element, selector) {
15
+ return element?.nodeType === 1 && Boolean(element.matches?.(selector));
16
+ }
17
+ function addListener(listeners, target, type, handler, options) {
18
+ if (!target) return;
19
+ target.addEventListener(type, handler, options);
20
+ listeners.push({ target, type, handler, options });
21
+ }
22
+ function removeListeners(listeners) {
23
+ listeners.forEach(({ target, type, handler, options }) => {
24
+ target.removeEventListener(type, handler, options);
25
+ });
26
+ listeners.length = 0;
27
+ }
28
+ function parseDate(value) {
29
+ if (!value) return null;
30
+ if (value instanceof Date) {
31
+ if (Number.isNaN(value.getTime())) return null;
32
+ return new Date(value.getFullYear(), value.getMonth(), value.getDate());
33
+ }
34
+ if (typeof value === "number") {
35
+ return parseDate(new Date(value));
36
+ }
37
+ const text = String(value).trim();
38
+ const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
39
+ if (iso) {
40
+ const date = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));
41
+ if (date.getFullYear() === Number(iso[1]) && date.getMonth() === Number(iso[2]) - 1 && date.getDate() === Number(iso[3])) {
42
+ return date;
43
+ }
44
+ return null;
45
+ }
46
+ return null;
47
+ }
48
+ function pad(value) {
49
+ return String(value).padStart(2, "0");
50
+ }
51
+ function toIso(date) {
52
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
53
+ }
54
+ function compareDates(left, right) {
55
+ if (!left || !right) return 0;
56
+ return left.getTime() - right.getTime();
57
+ }
58
+ function sameDate(left, right) {
59
+ return Boolean(left && right && toIso(left) === toIso(right));
60
+ }
61
+ function addDays(date, amount) {
62
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
63
+ }
64
+ function addMonths(date, amount) {
65
+ const targetStart = new Date(date.getFullYear(), date.getMonth() + amount, 1);
66
+ const targetDay = Math.min(
67
+ date.getDate(),
68
+ daysInMonth(targetStart.getFullYear(), targetStart.getMonth())
69
+ );
70
+ return new Date(targetStart.getFullYear(), targetStart.getMonth(), targetDay);
71
+ }
72
+ function firstDayOfWeek(locale = DEFAULT_LOCALE) {
73
+ try {
74
+ if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
75
+ const intlLocale = new Intl.Locale(locale || DEFAULT_LOCALE);
76
+ const weekInfo = intlLocale.weekInfo || intlLocale.getWeekInfo?.();
77
+ if (Number.isInteger(weekInfo?.firstDay)) {
78
+ return weekInfo.firstDay % 7;
79
+ }
80
+ }
81
+ } catch {
82
+ }
83
+ return 0;
84
+ }
85
+ function weekdayDates(locale = DEFAULT_LOCALE) {
86
+ const firstDay = firstDayOfWeek(locale);
87
+ return Array.from({ length: 7 }, (_, offset) => addDays(WEEKDAY_BASE_SUNDAY, (firstDay + offset) % 7));
88
+ }
89
+ function startOfMonth(date) {
90
+ return new Date(date.getFullYear(), date.getMonth(), 1);
91
+ }
92
+ function daysInMonth(year, month) {
93
+ return new Date(year, month + 1, 0).getDate();
94
+ }
95
+ function limitToRange(date, minDate, maxDate) {
96
+ if (minDate && compareDates(date, minDate) < 0) return new Date(minDate);
97
+ if (maxDate && compareDates(date, maxDate) > 0) return new Date(maxDate);
98
+ return date;
99
+ }
100
+ function parseDateList(value) {
101
+ if (Array.isArray(value)) {
102
+ return value.map(parseDate).filter(Boolean).map(toIso);
103
+ }
104
+ if (!value) return [];
105
+ const text = String(value).trim();
106
+ if (!text) return [];
107
+ try {
108
+ const parsed = JSON.parse(text);
109
+ if (Array.isArray(parsed)) {
110
+ return parsed.map(parseDate).filter(Boolean).map(toIso);
111
+ }
112
+ } catch {
113
+ }
114
+ return text.split(",").map(parseDate).filter(Boolean).map(toIso);
115
+ }
116
+ function formatDisplay(date, locale = DEFAULT_LOCALE) {
117
+ if (!date) return "";
118
+ return new Intl.DateTimeFormat(locale || DEFAULT_LOCALE, {
119
+ month: "short",
120
+ day: "numeric",
121
+ year: "numeric"
122
+ }).format(date);
123
+ }
124
+ function formatMonthYear(date, locale = DEFAULT_LOCALE) {
125
+ return new Intl.DateTimeFormat(locale || DEFAULT_LOCALE, {
126
+ month: "long",
127
+ year: "numeric"
128
+ }).format(date);
129
+ }
130
+ function formatReturn(date, format, locale = DEFAULT_LOCALE) {
131
+ if (!date) return void 0;
132
+ if (format === "yyyy-mm-dd") return toIso(date);
133
+ if (format) return formatDisplay(date, locale);
134
+ return new Date(date);
135
+ }
136
+ function readDataConfig(element, config = {}) {
137
+ const dataset = element.dataset || {};
138
+ const locale = config.locale || dataset.datepickerLocale || dataset.calendarLocale || DEFAULT_LOCALE;
139
+ const minDate = parseDate(config.minDate || dataset.calendarMinDate || dataset.datepickerMinDate);
140
+ const maxDate = parseDate(config.maxDate || dataset.calendarMaxDate || dataset.datepickerMaxDate);
141
+ return {
142
+ locale,
143
+ mode: config.mode || dataset.calendarMode || dataset.datepickerMode || "single",
144
+ placeholder: config.placeholder || dataset.datepickerPlaceholder || "Pick a date",
145
+ value: config.value || dataset.calendarValue || dataset.datepickerValue || "",
146
+ startValue: config.startValue || dataset.calendarStartValue || dataset.datepickerStartValue || "",
147
+ endValue: config.endValue || dataset.calendarEndValue || dataset.datepickerEndValue || "",
148
+ minDate,
149
+ maxDate,
150
+ disabledDates: parseDateList(config.disabledDates || dataset.calendarDisabledDates || dataset.datepickerDisabledDates),
151
+ captionLayout: config.captionLayout || dataset.calendarCaptionLayout || "",
152
+ showPresets: config.showPresets ?? (dataset.calendarShowPresets === "true" || dataset.datepickerShowPresets === "true")
153
+ };
154
+ }
155
+ function dispatch(element, window2, name, detail = {}, cancelable = false) {
156
+ return element.dispatchEvent(
157
+ new window2.CustomEvent(name, {
158
+ bubbles: true,
159
+ cancelable,
160
+ detail
161
+ })
162
+ );
163
+ }
164
+ function setHiddenValue(input, value) {
165
+ if (!input) return;
166
+ input.value = value || "";
167
+ }
168
+ function setValidationValue(input, value) {
169
+ if (!input) return;
170
+ input.value = value || "";
171
+ }
172
+ function setDatepickerInvalid(element, trigger, invalid) {
173
+ if (invalid) {
174
+ element.dataset.invalid = "true";
175
+ trigger.classList.add("is-invalid");
176
+ trigger.setAttribute("aria-invalid", "true");
177
+ return;
178
+ }
179
+ delete element.dataset.invalid;
180
+ trigger.classList.remove("is-invalid");
181
+ trigger.removeAttribute("aria-invalid");
182
+ }
183
+ function syncDatepickerValidation(element, trigger, validationInput) {
184
+ if (validationInput?.required && validationInput.checkValidity()) {
185
+ setDatepickerInvalid(element, trigger, false);
186
+ }
187
+ }
188
+ function clamp(value, min, max) {
189
+ if (max < min) return min;
190
+ return Math.min(Math.max(value, min), max);
191
+ }
192
+ function cssPixelValue(element, property, fallback) {
193
+ const view = element?.ownerDocument?.defaultView || window;
194
+ const value = element ? view.getComputedStyle(element).getPropertyValue(property).trim() : "";
195
+ const parsed = Number.parseFloat(value);
196
+ if (!Number.isFinite(parsed)) return fallback;
197
+ if (value.endsWith("rem")) {
198
+ const root = element.ownerDocument.documentElement;
199
+ const fontSize = Number.parseFloat(view.getComputedStyle(root).fontSize);
200
+ return Number.isFinite(fontSize) ? parsed * fontSize : fallback;
201
+ }
202
+ if (value.endsWith("em")) {
203
+ const fontSize = Number.parseFloat(view.getComputedStyle(element).fontSize);
204
+ return Number.isFinite(fontSize) ? parsed * fontSize : fallback;
205
+ }
206
+ return Number.isFinite(parsed) ? parsed : fallback;
207
+ }
208
+ function datepickerContains(instance, target) {
209
+ return Boolean(
210
+ target && (instance._element.contains(target) || instance._popover.contains(target))
211
+ );
212
+ }
213
+ function syncPortaledPopoverContext(instance) {
214
+ const theme = instance._element.closest("[data-bs-theme]")?.getAttribute("data-bs-theme") || instance._document.documentElement.getAttribute("data-bs-theme");
215
+ const direction = getComputedStyle(instance._trigger).direction;
216
+ if (theme) {
217
+ instance._popover.setAttribute("data-bs-theme", theme);
218
+ }
219
+ if (direction === "rtl") {
220
+ instance._popover.setAttribute("dir", "rtl");
221
+ }
222
+ }
223
+ function portalDatepickerPopover(instance) {
224
+ const popover = instance._popover;
225
+ if (instance._popoverPortal?.host?.parentElement === instance._document.body) {
226
+ syncPortaledPopoverContext(instance);
227
+ return;
228
+ }
229
+ const host = instance._document.createElement("div");
230
+ host.className = "moo-ui";
231
+ host.dataset.datepickerPortalHost = "";
232
+ instance._popoverPortal = {
233
+ parent: popover.parentNode,
234
+ nextSibling: popover.nextSibling,
235
+ host,
236
+ hadTheme: popover.hasAttribute("data-bs-theme"),
237
+ theme: popover.getAttribute("data-bs-theme"),
238
+ hadDirection: popover.hasAttribute("dir"),
239
+ direction: popover.getAttribute("dir")
240
+ };
241
+ host.appendChild(popover);
242
+ instance._document.body.appendChild(host);
243
+ syncPortaledPopoverContext(instance);
244
+ }
245
+ function restoreDatepickerPopover(instance) {
246
+ const portal = instance._popoverPortal;
247
+ if (!portal) return;
248
+ const popover = instance._popover;
249
+ if (portal.parent?.isConnected) {
250
+ if (portal.nextSibling?.parentNode === portal.parent) {
251
+ portal.parent.insertBefore(popover, portal.nextSibling);
252
+ } else {
253
+ portal.parent.appendChild(popover);
254
+ }
255
+ } else {
256
+ popover.remove();
257
+ }
258
+ portal.host?.remove();
259
+ if (portal.hadTheme) {
260
+ popover.setAttribute("data-bs-theme", portal.theme);
261
+ } else {
262
+ popover.removeAttribute("data-bs-theme");
263
+ }
264
+ if (portal.hadDirection) {
265
+ popover.setAttribute("dir", portal.direction);
266
+ } else {
267
+ popover.removeAttribute("dir");
268
+ }
269
+ instance._popoverPortal = null;
270
+ }
271
+ function positionDatepickerPopover(trigger, popover, root, window2) {
272
+ const offset = cssPixelValue(root, "--moo-datepicker-popover-offset", 6);
273
+ const direction = getComputedStyle(trigger).direction;
274
+ popover.style.position = "fixed";
275
+ popover.style.inset = "auto";
276
+ popover.style.right = "auto";
277
+ popover.style.bottom = "auto";
278
+ popover.style.maxHeight = "";
279
+ popover.style.overflowY = "";
280
+ const triggerRect = trigger.getBoundingClientRect();
281
+ const popoverRect = popover.getBoundingClientRect();
282
+ const viewportWidth = window2.innerWidth;
283
+ const viewportHeight = window2.innerHeight;
284
+ const preferredLeft = direction === "rtl" ? triggerRect.right - popoverRect.width : triggerRect.left;
285
+ const left = clamp(
286
+ preferredLeft,
287
+ POPOVER_VIEWPORT_PADDING,
288
+ viewportWidth - popoverRect.width - POPOVER_VIEWPORT_PADDING
289
+ );
290
+ const spaceBelow = triggerRect.bottom + offset <= viewportHeight ? viewportHeight - triggerRect.bottom - offset - POPOVER_VIEWPORT_PADDING : 0;
291
+ const spaceAbove = triggerRect.top - offset - POPOVER_VIEWPORT_PADDING;
292
+ const placeAbove = popoverRect.height > spaceBelow && spaceAbove > spaceBelow;
293
+ const placement = placeAbove ? "top" : "bottom";
294
+ const availableHeight = Math.max(
295
+ 0,
296
+ placeAbove ? spaceAbove : spaceBelow
297
+ );
298
+ const renderedHeight = Math.min(popoverRect.height, availableHeight);
299
+ const top = placeAbove ? triggerRect.top - offset - renderedHeight : triggerRect.bottom + offset;
300
+ popover.style.left = `${Math.round(left)}px`;
301
+ popover.style.top = `${Math.round(clamp(
302
+ top,
303
+ POPOVER_VIEWPORT_PADDING,
304
+ viewportHeight - renderedHeight - POPOVER_VIEWPORT_PADDING
305
+ ))}px`;
306
+ if (availableHeight > 0) {
307
+ popover.style.maxHeight = `${Math.floor(availableHeight)}px`;
308
+ popover.style.overflowY = "auto";
309
+ }
310
+ popover.dataset.datepickerPlacement = placement;
311
+ }
312
+ function clearDatepickerPopoverPosition(popover) {
313
+ popover.style.position = "";
314
+ popover.style.inset = "";
315
+ popover.style.right = "";
316
+ popover.style.bottom = "";
317
+ popover.style.left = "";
318
+ popover.style.top = "";
319
+ popover.style.maxHeight = "";
320
+ popover.style.overflowY = "";
321
+ delete popover.dataset.datepickerPlacement;
322
+ }
323
+ function startDatepickerPopoverPositioning(instance) {
324
+ removeListeners(instance._floatingListeners);
325
+ portalDatepickerPopover(instance);
326
+ const update = () => {
327
+ if (instance.isOpen()) {
328
+ positionDatepickerPopover(
329
+ instance._trigger,
330
+ instance._popover,
331
+ instance._element,
332
+ instance._window
333
+ );
334
+ }
335
+ };
336
+ instance._positionPopover = update;
337
+ update();
338
+ addListener(instance._floatingListeners, instance._window, "resize", update);
339
+ addListener(instance._floatingListeners, instance._window, "scroll", update, true);
340
+ addListener(instance._floatingListeners, instance._document, "scroll", update, true);
341
+ }
342
+ function stopDatepickerPopoverPositioning(instance) {
343
+ removeListeners(instance._floatingListeners);
344
+ instance._positionPopover = null;
345
+ clearDatepickerPopoverPosition(instance._popover);
346
+ restoreDatepickerPopover(instance);
347
+ }
348
+ function monthName(monthIndex, locale) {
349
+ return new Intl.DateTimeFormat(locale || DEFAULT_LOCALE, { month: "short" }).format(new Date(2026, monthIndex, 1));
350
+ }
351
+ function yearOptions(minDate, maxDate, viewDate) {
352
+ const min = minDate ? minDate.getFullYear() : viewDate.getFullYear() - 100;
353
+ const max = maxDate ? maxDate.getFullYear() : viewDate.getFullYear() + 20;
354
+ const years = [];
355
+ for (let year = min; year <= max; year += 1) {
356
+ years.push(year);
357
+ }
358
+ return years;
359
+ }
360
+ function createIcon(document, glyph, className = "") {
361
+ const span = document.createElement("span");
362
+ span.className = className;
363
+ span.setAttribute("aria-hidden", "true");
364
+ span.textContent = glyph;
365
+ return span;
366
+ }
367
+ var MooCalendar = class _MooCalendar {
368
+ static getInstance(element) {
369
+ return element?.nodeType === 1 ? calendarInstances.get(element) || null : null;
370
+ }
371
+ static getOrCreateInstance(element, config = {}) {
372
+ return _MooCalendar.getInstance(element) || new _MooCalendar(element, config);
373
+ }
374
+ constructor(element, config = {}) {
375
+ if (!isElement(element, "[data-calendar]")) {
376
+ throw new TypeError("MooCalendar requires a [data-calendar] root element.");
377
+ }
378
+ const existing = calendarInstances.get(element);
379
+ if (existing) return existing;
380
+ this._element = element;
381
+ this._document = element.ownerDocument;
382
+ this._window = this._document.defaultView || window;
383
+ this._listeners = [];
384
+ this._disposed = false;
385
+ this._config = readDataConfig(element, config);
386
+ this._mode = this._config.mode === "range" ? "range" : "single";
387
+ this._disabledDates = new Set(this._config.disabledDates);
388
+ this._selectedDate = parseDate(this._config.value);
389
+ this._rangeStart = parseDate(this._config.startValue);
390
+ this._rangeEnd = parseDate(this._config.endValue);
391
+ this._activeDate = this._nearestEnabledDate(
392
+ this._rangeStart || this._selectedDate || /* @__PURE__ */ new Date()
393
+ );
394
+ this._viewDate = startOfMonth(this._activeDate);
395
+ this._captionId = this._element.id ? `${this._element.id}-caption` : `moo-calendar-caption-${Math.random().toString(36).slice(2)}`;
396
+ this._labelId = this._element.id ? `${this._element.id}-label` : `moo-calendar-label-${Math.random().toString(36).slice(2)}`;
397
+ calendarInstances.set(element, this);
398
+ this._bindEvents();
399
+ this._render();
400
+ }
401
+ dispose() {
402
+ if (this._disposed) return;
403
+ this._disposed = true;
404
+ removeListeners(this._listeners);
405
+ this._element.replaceChildren();
406
+ if (calendarInstances.get(this._element) === this) {
407
+ calendarInstances.delete(this._element);
408
+ }
409
+ }
410
+ getDate(format = void 0) {
411
+ if (this._mode === "range") {
412
+ return this.getDates(format);
413
+ }
414
+ return formatReturn(this._selectedDate, format, this._config.locale);
415
+ }
416
+ getDates(format = void 0) {
417
+ return [
418
+ formatReturn(this._rangeStart, format, this._config.locale),
419
+ formatReturn(this._rangeEnd, format, this._config.locale)
420
+ ];
421
+ }
422
+ setDate(date, options = {}) {
423
+ if (this._mode !== "single") return false;
424
+ const parsed = parseDate(date);
425
+ if (!parsed || this._isDisabled(parsed)) return false;
426
+ this._selectedDate = parsed;
427
+ this._activeDate = parsed;
428
+ this._viewDate = startOfMonth(parsed);
429
+ this._render();
430
+ if (options.emit !== false) {
431
+ this._emitChange(true);
432
+ }
433
+ return true;
434
+ }
435
+ setDates(startDate, endDate, options = {}) {
436
+ if (this._mode !== "range") return false;
437
+ const start = parseDate(startDate);
438
+ const end = parseDate(endDate);
439
+ if (start && this._isDisabled(start)) return false;
440
+ if (end && this._isDisabled(end)) return false;
441
+ this._rangeStart = start;
442
+ this._rangeEnd = end;
443
+ if (this._rangeStart && this._rangeEnd && compareDates(this._rangeStart, this._rangeEnd) > 0) {
444
+ [this._rangeStart, this._rangeEnd] = [this._rangeEnd, this._rangeStart];
445
+ }
446
+ this._activeDate = this._rangeEnd || this._rangeStart || this._activeDate;
447
+ this._viewDate = startOfMonth(this._activeDate);
448
+ this._render();
449
+ if (options.emit !== false) {
450
+ this._emitChange(Boolean(this._rangeStart && this._rangeEnd));
451
+ }
452
+ return true;
453
+ }
454
+ clear(options = {}) {
455
+ this._selectedDate = null;
456
+ this._rangeStart = null;
457
+ this._rangeEnd = null;
458
+ this._render();
459
+ if (options.emit !== false) {
460
+ this._emitChange(false);
461
+ }
462
+ }
463
+ focusActiveDay() {
464
+ this._dayButton(this._activeDate)?.focus();
465
+ }
466
+ _bindEvents() {
467
+ addListener(this._listeners, this._element, "click", (event) => {
468
+ const target = event.target;
469
+ const day = target.closest?.("[data-calendar-day]");
470
+ const prev = target.closest?.("[data-calendar-prev]");
471
+ const next = target.closest?.("[data-calendar-next]");
472
+ const preset = target.closest?.("[data-calendar-preset]");
473
+ if (prev) {
474
+ this._changeMonth(-1, true);
475
+ } else if (next) {
476
+ this._changeMonth(1, true);
477
+ } else if (preset) {
478
+ this._selectPreset(preset.dataset.calendarPreset);
479
+ } else if (day) {
480
+ this._selectDate(parseDate(day.dataset.calendarDay), true);
481
+ }
482
+ });
483
+ addListener(this._listeners, this._element, "keydown", (event) => {
484
+ const day = event.target.closest?.("[data-calendar-day]");
485
+ if (!day) return;
486
+ this._handleDayKeydown(event);
487
+ });
488
+ addListener(this._listeners, this._element, "change", (event) => {
489
+ if (event.target.matches?.("[data-calendar-month]")) {
490
+ this._setActiveMonth(
491
+ this._viewDate.getFullYear(),
492
+ Number(event.target.value),
493
+ true
494
+ );
495
+ }
496
+ if (event.target.matches?.("[data-calendar-year]")) {
497
+ this._setActiveMonth(
498
+ Number(event.target.value),
499
+ this._viewDate.getMonth(),
500
+ true
501
+ );
502
+ }
503
+ });
504
+ }
505
+ _emitChange(complete) {
506
+ dispatch(this._element, this._window, "change.moo.calendar", {
507
+ date: this._selectedDate ? new Date(this._selectedDate) : void 0,
508
+ value: this._selectedDate ? toIso(this._selectedDate) : "",
509
+ startDate: this._rangeStart ? new Date(this._rangeStart) : void 0,
510
+ endDate: this._rangeEnd ? new Date(this._rangeEnd) : void 0,
511
+ startValue: this._rangeStart ? toIso(this._rangeStart) : "",
512
+ endValue: this._rangeEnd ? toIso(this._rangeEnd) : "",
513
+ complete
514
+ });
515
+ }
516
+ _changeMonth(amount, focus) {
517
+ const nextView = startOfMonth(addMonths(this._viewDate, amount));
518
+ this._setActiveMonth(nextView.getFullYear(), nextView.getMonth(), focus);
519
+ }
520
+ _setActiveMonth(year, month, focus = false) {
521
+ const activeDay = Math.min(
522
+ this._activeDate.getDate(),
523
+ daysInMonth(year, month)
524
+ );
525
+ this._activeDate = this._nearestEnabledDate(new Date(year, month, activeDay));
526
+ this._viewDate = startOfMonth(this._activeDate);
527
+ this._render();
528
+ if (focus) this.focusActiveDay();
529
+ }
530
+ _selectPreset(name) {
531
+ if (!name) return;
532
+ const today = /* @__PURE__ */ new Date();
533
+ const offsets = {
534
+ today: 0,
535
+ tomorrow: 1,
536
+ "in-3-days": 3,
537
+ "in-a-week": 7,
538
+ "in-2-weeks": 14
539
+ };
540
+ if (!(name in offsets)) return;
541
+ this._selectDate(addDays(today, offsets[name]), true);
542
+ }
543
+ _handleDayKeydown(event) {
544
+ const focusedDay = event.target.closest?.("[data-calendar-day]");
545
+ const focusedDate = parseDate(focusedDay?.dataset?.calendarDay);
546
+ if (focusedDate && compareDates(focusedDate, this._activeDate) !== 0) {
547
+ this._activeDate = focusedDate;
548
+ }
549
+ const keyMap = {
550
+ ArrowRight: 1,
551
+ ArrowLeft: -1,
552
+ ArrowDown: 7,
553
+ ArrowUp: -7
554
+ };
555
+ let nextDate = null;
556
+ if (event.key in keyMap) {
557
+ nextDate = addDays(this._activeDate, keyMap[event.key]);
558
+ } else if (event.key === "Home") {
559
+ const weekStart = firstDayOfWeek(this._config.locale);
560
+ const weekdayOffset = (this._activeDate.getDay() - weekStart + 7) % 7;
561
+ nextDate = addDays(this._activeDate, -weekdayOffset);
562
+ } else if (event.key === "End") {
563
+ const weekStart = firstDayOfWeek(this._config.locale);
564
+ const weekdayOffset = (this._activeDate.getDay() - weekStart + 7) % 7;
565
+ nextDate = addDays(this._activeDate, 6 - weekdayOffset);
566
+ } else if (event.key === "PageUp") {
567
+ nextDate = addMonths(this._activeDate, -1);
568
+ } else if (event.key === "PageDown") {
569
+ nextDate = addMonths(this._activeDate, 1);
570
+ } else if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
571
+ event.preventDefault();
572
+ this._selectDate(this._activeDate, true);
573
+ return;
574
+ } else {
575
+ return;
576
+ }
577
+ event.preventDefault();
578
+ this._setActiveDate(nextDate, true, compareDates(nextDate, this._activeDate) < 0 ? -1 : 1);
579
+ }
580
+ _setActiveDate(date, focus = false, step = 0) {
581
+ const nextDate = step === 0 ? this._nearestEnabledDate(date) : this._nextEnabledDate(date, step);
582
+ this._activeDate = nextDate;
583
+ this._viewDate = startOfMonth(nextDate);
584
+ this._render();
585
+ if (focus) this.focusActiveDay();
586
+ }
587
+ _nextEnabledDate(date, step) {
588
+ let candidate = parseDate(date);
589
+ if (!candidate) return this._activeDate;
590
+ const dayStep = step < 0 ? -1 : 1;
591
+ for (let guard = 0; guard < 366; guard += 1) {
592
+ if (this._config.minDate && compareDates(candidate, this._config.minDate) < 0) break;
593
+ if (this._config.maxDate && compareDates(candidate, this._config.maxDate) > 0) break;
594
+ if (!this._isDisabled(candidate)) return candidate;
595
+ candidate = addDays(candidate, dayStep);
596
+ }
597
+ return this._activeDate;
598
+ }
599
+ _nearestEnabledDate(date) {
600
+ const preferred = limitToRange(
601
+ parseDate(date) || /* @__PURE__ */ new Date(),
602
+ this._config.minDate,
603
+ this._config.maxDate
604
+ );
605
+ if (!this._isDisabled(preferred)) return preferred;
606
+ const monthStart = startOfMonth(preferred);
607
+ const monthLength = daysInMonth(monthStart.getFullYear(), monthStart.getMonth());
608
+ for (let day = 1; day <= monthLength; day += 1) {
609
+ const candidate = new Date(monthStart.getFullYear(), monthStart.getMonth(), day);
610
+ if (!this._isDisabled(candidate)) return candidate;
611
+ }
612
+ return preferred;
613
+ }
614
+ _selectDate(date, emit) {
615
+ if (!date || this._isDisabled(date)) return;
616
+ this._activeDate = date;
617
+ this._viewDate = startOfMonth(date);
618
+ if (this._mode === "range") {
619
+ if (!this._rangeStart || this._rangeEnd || compareDates(date, this._rangeStart) < 0) {
620
+ this._rangeStart = date;
621
+ this._rangeEnd = null;
622
+ } else {
623
+ this._rangeEnd = date;
624
+ }
625
+ this._render();
626
+ if (emit) this._emitChange(Boolean(this._rangeStart && this._rangeEnd));
627
+ return;
628
+ }
629
+ this._selectedDate = date;
630
+ this._render();
631
+ if (emit) this._emitChange(true);
632
+ }
633
+ _isDisabled(date) {
634
+ if (!date) return true;
635
+ if (this._config.minDate && compareDates(date, this._config.minDate) < 0) return true;
636
+ if (this._config.maxDate && compareDates(date, this._config.maxDate) > 0) return true;
637
+ return this._disabledDates.has(toIso(date));
638
+ }
639
+ _isSelected(date) {
640
+ if (this._mode === "range") {
641
+ return sameDate(date, this._rangeStart) || sameDate(date, this._rangeEnd);
642
+ }
643
+ return sameDate(date, this._selectedDate);
644
+ }
645
+ _rangeState(date) {
646
+ if (this._mode !== "range" || !this._rangeStart) return "";
647
+ if (sameDate(date, this._rangeStart)) return "start";
648
+ if (sameDate(date, this._rangeEnd)) return "end";
649
+ if (this._rangeStart && this._rangeEnd && compareDates(date, this._rangeStart) > 0 && compareDates(date, this._rangeEnd) < 0) {
650
+ return "middle";
651
+ }
652
+ return "";
653
+ }
654
+ _dayButton(date) {
655
+ return this._element.querySelector(`[data-calendar-day="${toIso(date)}"]`);
656
+ }
657
+ _render() {
658
+ const document = this._document;
659
+ const fragment = document.createDocumentFragment();
660
+ const header = document.createElement("div");
661
+ header.className = "moo-calendar__header";
662
+ const prev = document.createElement("button");
663
+ prev.className = "btn btn-ghost btn-icon-sm moo-calendar__nav";
664
+ prev.type = "button";
665
+ prev.dataset.calendarPrev = "true";
666
+ prev.setAttribute("aria-label", "Previous month");
667
+ prev.appendChild(createIcon(document, "\u2039", "moo-calendar__nav-icon"));
668
+ const next = document.createElement("button");
669
+ next.className = "btn btn-ghost btn-icon-sm moo-calendar__nav";
670
+ next.type = "button";
671
+ next.dataset.calendarNext = "true";
672
+ next.setAttribute("aria-label", "Next month");
673
+ next.appendChild(createIcon(document, "\u203A", "moo-calendar__nav-icon"));
674
+ const caption = document.createElement("div");
675
+ caption.className = "moo-calendar__caption";
676
+ caption.id = this._captionId;
677
+ caption.setAttribute("aria-live", "polite");
678
+ if (this._config.captionLayout === "dropdown") {
679
+ const month = document.createElement("select");
680
+ month.className = "moo-calendar__select";
681
+ month.dataset.calendarMonth = "true";
682
+ month.setAttribute("aria-label", "Month");
683
+ for (let index = 0; index < 12; index += 1) {
684
+ const option = document.createElement("option");
685
+ option.value = String(index);
686
+ option.textContent = monthName(index, this._config.locale);
687
+ option.selected = index === this._viewDate.getMonth();
688
+ month.appendChild(option);
689
+ }
690
+ const year = document.createElement("select");
691
+ year.className = "moo-calendar__select";
692
+ year.dataset.calendarYear = "true";
693
+ year.setAttribute("aria-label", "Year");
694
+ yearOptions(this._config.minDate, this._config.maxDate, this._viewDate).forEach((value) => {
695
+ const option = document.createElement("option");
696
+ option.value = String(value);
697
+ option.textContent = String(value);
698
+ option.selected = value === this._viewDate.getFullYear();
699
+ year.appendChild(option);
700
+ });
701
+ caption.append(month, year);
702
+ } else {
703
+ caption.textContent = formatMonthYear(this._viewDate, this._config.locale);
704
+ }
705
+ header.append(prev, caption, next);
706
+ fragment.appendChild(header);
707
+ const gridLabel = document.createElement("div");
708
+ gridLabel.id = this._labelId;
709
+ gridLabel.className = "visually-hidden";
710
+ gridLabel.textContent = this._element.getAttribute("aria-label") || formatMonthYear(this._viewDate, this._config.locale);
711
+ fragment.appendChild(gridLabel);
712
+ const weekdays = document.createElement("div");
713
+ weekdays.className = "moo-calendar__weekdays";
714
+ weekdays.setAttribute("role", "row");
715
+ weekdayDates(this._config.locale).forEach((date) => {
716
+ const label = document.createElement("span");
717
+ label.className = "moo-calendar__weekday";
718
+ label.setAttribute("role", "columnheader");
719
+ label.textContent = new Intl.DateTimeFormat(this._config.locale, { weekday: "narrow" }).format(date);
720
+ weekdays.appendChild(label);
721
+ });
722
+ const grid = document.createElement("div");
723
+ grid.className = "moo-calendar__grid";
724
+ grid.setAttribute("role", "grid");
725
+ grid.setAttribute("aria-labelledby", this._labelId);
726
+ grid.appendChild(weekdays);
727
+ const first = startOfMonth(this._viewDate);
728
+ const weekStart = firstDayOfWeek(this._config.locale);
729
+ const start = addDays(first, -((first.getDay() - weekStart + 7) % 7));
730
+ for (let week = 0; week < 6; week += 1) {
731
+ const row = document.createElement("div");
732
+ row.className = "moo-calendar__week";
733
+ row.setAttribute("role", "row");
734
+ for (let day = 0; day < 7; day += 1) {
735
+ const date = addDays(start, week * 7 + day);
736
+ const iso = toIso(date);
737
+ const cell = document.createElement("div");
738
+ cell.className = "moo-calendar__cell";
739
+ cell.setAttribute("role", "gridcell");
740
+ const button = document.createElement("button");
741
+ button.className = "moo-calendar__day";
742
+ button.type = "button";
743
+ button.dataset.calendarDay = iso;
744
+ button.textContent = String(date.getDate());
745
+ button.tabIndex = sameDate(date, this._activeDate) ? 0 : -1;
746
+ button.setAttribute("aria-label", formatDisplay(date, this._config.locale));
747
+ const selected = this._isSelected(date);
748
+ button.dataset.calendarSelected = String(selected);
749
+ cell.setAttribute("aria-selected", selected ? "true" : "false");
750
+ if (date.getMonth() !== this._viewDate.getMonth()) {
751
+ button.dataset.calendarOutside = "true";
752
+ }
753
+ if (sameDate(date, /* @__PURE__ */ new Date())) {
754
+ button.dataset.calendarToday = "true";
755
+ button.setAttribute("aria-current", "date");
756
+ }
757
+ const rangeState = this._rangeState(date);
758
+ if (rangeState) {
759
+ button.dataset.calendarRange = rangeState;
760
+ }
761
+ if (this._isDisabled(date)) {
762
+ button.disabled = true;
763
+ button.setAttribute("aria-disabled", "true");
764
+ }
765
+ cell.appendChild(button);
766
+ row.appendChild(cell);
767
+ }
768
+ grid.appendChild(row);
769
+ }
770
+ fragment.appendChild(grid);
771
+ if (this._config.showPresets) {
772
+ const presets = document.createElement("div");
773
+ presets.className = "moo-calendar__presets";
774
+ [
775
+ ["today", "Today"],
776
+ ["tomorrow", "Tomorrow"],
777
+ ["in-3-days", "In 3 days"],
778
+ ["in-a-week", "In a week"],
779
+ ["in-2-weeks", "In 2 weeks"]
780
+ ].forEach(([value, label]) => {
781
+ const button = document.createElement("button");
782
+ button.className = "btn btn-outline-secondary btn-sm moo-calendar__preset";
783
+ button.type = "button";
784
+ button.dataset.calendarPreset = value;
785
+ button.textContent = label;
786
+ presets.appendChild(button);
787
+ });
788
+ fragment.appendChild(presets);
789
+ }
790
+ this._element.classList.add("moo-calendar");
791
+ this._element.dataset.calendar = this._element.dataset.calendar || "";
792
+ this._element.dataset.calendarMode = this._mode;
793
+ this._element.dataset.calendarActiveDate = toIso(this._activeDate);
794
+ if (this._mode === "single" && this._selectedDate) {
795
+ this._element.dataset.calendarValue = toIso(this._selectedDate);
796
+ } else {
797
+ delete this._element.dataset.calendarValue;
798
+ }
799
+ if (this._mode === "range") {
800
+ this._element.dataset.calendarStartValue = this._rangeStart ? toIso(this._rangeStart) : "";
801
+ this._element.dataset.calendarEndValue = this._rangeEnd ? toIso(this._rangeEnd) : "";
802
+ } else {
803
+ delete this._element.dataset.calendarStartValue;
804
+ delete this._element.dataset.calendarEndValue;
805
+ }
806
+ this._element.replaceChildren(fragment);
807
+ }
808
+ };
809
+ var MooDatepicker = class _MooDatepicker {
810
+ static getInstance(element) {
811
+ return element?.nodeType === 1 ? datepickerInstances.get(element) || null : null;
812
+ }
813
+ static getOrCreateInstance(element, config = {}) {
814
+ return _MooDatepicker.getInstance(element) || new _MooDatepicker(element, config);
815
+ }
816
+ constructor(element, config = {}) {
817
+ if (!isElement(element, "[data-datepicker]")) {
818
+ throw new TypeError("MooDatepicker requires a [data-datepicker] root element.");
819
+ }
820
+ const existing = datepickerInstances.get(element);
821
+ if (existing) return existing;
822
+ this._element = element;
823
+ this._document = element.ownerDocument;
824
+ this._window = this._document.defaultView || window;
825
+ this._listeners = [];
826
+ this._floatingListeners = [];
827
+ this._disposed = false;
828
+ this._positionPopover = null;
829
+ this._popoverPortal = null;
830
+ this._trigger = element.querySelector("[data-datepicker-trigger]");
831
+ this._popover = element.querySelector("[data-datepicker-popover]");
832
+ this._label = element.querySelector("[data-datepicker-label]");
833
+ this._input = element.querySelector("[data-datepicker-input]") || element.querySelector('input[type="hidden"]');
834
+ this._validationInput = element.querySelector("[data-datepicker-validation]");
835
+ this._calendarRoot = element.querySelector("[data-calendar]");
836
+ if (!this._trigger || !this._popover || !this._label || !this._input || !this._calendarRoot) {
837
+ throw new TypeError("MooDatepicker requires a trigger, popover, calendar, and hidden input.");
838
+ }
839
+ this._config = readDataConfig(element, {
840
+ ...config,
841
+ value: config.value || this._input.value || element.dataset.datepickerValue
842
+ });
843
+ this._placeholder = this._config.placeholder;
844
+ this._defaultValue = this._input.defaultValue || this._input.getAttribute("value") || "";
845
+ this.calendar = MooCalendar.getOrCreateInstance(this._calendarRoot, {
846
+ ...this._config,
847
+ mode: "single",
848
+ value: this._input.value || this._config.value
849
+ });
850
+ datepickerInstances.set(element, this);
851
+ this._syncFromCalendar();
852
+ this._bindEvents();
853
+ }
854
+ dispose() {
855
+ if (this._disposed) return;
856
+ this._disposed = true;
857
+ removeListeners(this._listeners);
858
+ this.hide(false);
859
+ stopDatepickerPopoverPositioning(this);
860
+ restoreDatepickerPopover(this);
861
+ removeListeners(this._floatingListeners);
862
+ this.calendar?.dispose();
863
+ if (datepickerInstances.get(this._element) === this) {
864
+ datepickerInstances.delete(this._element);
865
+ }
866
+ }
867
+ show() {
868
+ if (this._trigger.disabled || this._element.dataset.datepickerDisabled === "true") return;
869
+ if (this.isOpen()) return;
870
+ if (!dispatch(this._element, this._window, "show.moo.datepicker", {}, true)) return;
871
+ this._popover.hidden = false;
872
+ this._popover.classList.add("show");
873
+ startDatepickerPopoverPositioning(this);
874
+ this._trigger.setAttribute("aria-expanded", "true");
875
+ this.calendar.focusActiveDay();
876
+ dispatch(this._element, this._window, "shown.moo.datepicker");
877
+ }
878
+ hide(returnFocus = true) {
879
+ const wasOpen = this.isOpen();
880
+ if (!wasOpen) return;
881
+ if (!dispatch(this._element, this._window, "hide.moo.datepicker", {}, true)) return;
882
+ this._popover.hidden = true;
883
+ this._popover.classList.remove("show");
884
+ stopDatepickerPopoverPositioning(this);
885
+ this._trigger.setAttribute("aria-expanded", "false");
886
+ if (returnFocus) this._trigger.focus();
887
+ dispatch(this._element, this._window, "hidden.moo.datepicker");
888
+ }
889
+ toggle() {
890
+ if (this.isOpen()) {
891
+ this.hide();
892
+ } else {
893
+ this.show();
894
+ }
895
+ }
896
+ isOpen() {
897
+ return !this._popover.hidden;
898
+ }
899
+ getDate(format = void 0) {
900
+ return this.calendar.getDate(format);
901
+ }
902
+ setDate(date, options = {}) {
903
+ const changed = this.calendar.setDate(date, { emit: false });
904
+ if (changed) {
905
+ this._syncFromCalendar();
906
+ if (options.emit !== false) this._emitChange();
907
+ }
908
+ return changed;
909
+ }
910
+ clear(options = {}) {
911
+ this.calendar.clear({ emit: false });
912
+ this._syncFromCalendar();
913
+ if (options.emit !== false) this._emitChange();
914
+ }
915
+ _bindEvents() {
916
+ addListener(this._listeners, this._trigger, "click", () => this.toggle());
917
+ addListener(this._listeners, this._popover, "keydown", (event) => {
918
+ if (event.key === "Escape") {
919
+ event.preventDefault();
920
+ this.hide();
921
+ }
922
+ });
923
+ addListener(this._listeners, this._document, "pointerdown", (event) => {
924
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
925
+ this._window.setTimeout(() => {
926
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
927
+ this.hide();
928
+ }
929
+ }, 0);
930
+ }
931
+ });
932
+ addListener(this._listeners, this._calendarRoot, "change.moo.calendar", (event) => {
933
+ this._syncFromCalendar();
934
+ this._emitChange();
935
+ if (event.detail.complete) {
936
+ this.hide();
937
+ }
938
+ });
939
+ const form = this._input.form || this._element.closest("form");
940
+ addListener(this._listeners, this._validationInput, "invalid", (event) => {
941
+ event.preventDefault();
942
+ setDatepickerInvalid(this._element, this._trigger, true);
943
+ this._trigger.focus({ preventScroll: true });
944
+ });
945
+ addListener(this._listeners, form, "reset", () => {
946
+ this._window.setTimeout(() => {
947
+ if (this._defaultValue) {
948
+ this.setDate(this._defaultValue, { emit: false });
949
+ } else {
950
+ this.clear({ emit: false });
951
+ }
952
+ }, 0);
953
+ });
954
+ }
955
+ _syncFromCalendar() {
956
+ const value = this.calendar.getDate("yyyy-mm-dd") || "";
957
+ setHiddenValue(this._input, value);
958
+ setValidationValue(this._validationInput, value);
959
+ syncDatepickerValidation(this._element, this._trigger, this._validationInput);
960
+ this._element.dataset.datepickerValue = value;
961
+ this._label.textContent = value ? formatDisplay(parseDate(value), this._config.locale) : this._placeholder;
962
+ }
963
+ _emitChange() {
964
+ dispatch(this._element, this._window, "change.moo.datepicker", {
965
+ date: this.getDate(),
966
+ value: this.getDate("yyyy-mm-dd") || ""
967
+ });
968
+ }
969
+ };
970
+ var MooDateRangePicker = class _MooDateRangePicker {
971
+ static getInstance(element) {
972
+ return element?.nodeType === 1 ? rangePickerInstances.get(element) || null : null;
973
+ }
974
+ static getOrCreateInstance(element, config = {}) {
975
+ return _MooDateRangePicker.getInstance(element) || new _MooDateRangePicker(element, config);
976
+ }
977
+ constructor(element, config = {}) {
978
+ if (!isElement(element, "[data-datepicker-range]")) {
979
+ throw new TypeError("MooDateRangePicker requires a [data-datepicker-range] root element.");
980
+ }
981
+ const existing = rangePickerInstances.get(element);
982
+ if (existing) return existing;
983
+ this._element = element;
984
+ this._document = element.ownerDocument;
985
+ this._window = this._document.defaultView || window;
986
+ this._listeners = [];
987
+ this._floatingListeners = [];
988
+ this._disposed = false;
989
+ this._positionPopover = null;
990
+ this._popoverPortal = null;
991
+ this._trigger = element.querySelector("[data-datepicker-trigger]");
992
+ this._popover = element.querySelector("[data-datepicker-popover]");
993
+ this._label = element.querySelector("[data-datepicker-label]");
994
+ this._startInput = element.querySelector("[data-datepicker-range-start]");
995
+ this._endInput = element.querySelector("[data-datepicker-range-end]");
996
+ this._validationInput = element.querySelector("[data-datepicker-validation]");
997
+ this._calendarRoot = element.querySelector("[data-calendar]");
998
+ if (!this._trigger || !this._popover || !this._label || !this._startInput || !this._endInput || !this._calendarRoot) {
999
+ throw new TypeError("MooDateRangePicker requires a trigger, popover, calendar, and start/end hidden inputs.");
1000
+ }
1001
+ this._config = readDataConfig(element, {
1002
+ ...config,
1003
+ mode: "range",
1004
+ startValue: config.startValue || this._startInput.value || element.dataset.datepickerStartValue,
1005
+ endValue: config.endValue || this._endInput.value || element.dataset.datepickerEndValue
1006
+ });
1007
+ this._placeholder = this._config.placeholder;
1008
+ this._defaultStartValue = this._startInput.defaultValue || this._startInput.getAttribute("value") || "";
1009
+ this._defaultEndValue = this._endInput.defaultValue || this._endInput.getAttribute("value") || "";
1010
+ this.calendar = MooCalendar.getOrCreateInstance(this._calendarRoot, this._config);
1011
+ rangePickerInstances.set(element, this);
1012
+ this._syncFromCalendar();
1013
+ this._bindEvents();
1014
+ }
1015
+ dispose() {
1016
+ if (this._disposed) return;
1017
+ this._disposed = true;
1018
+ removeListeners(this._listeners);
1019
+ this.hide(false);
1020
+ stopDatepickerPopoverPositioning(this);
1021
+ restoreDatepickerPopover(this);
1022
+ removeListeners(this._floatingListeners);
1023
+ this.calendar?.dispose();
1024
+ if (rangePickerInstances.get(this._element) === this) {
1025
+ rangePickerInstances.delete(this._element);
1026
+ }
1027
+ }
1028
+ show() {
1029
+ if (this._trigger.disabled || this._element.dataset.datepickerDisabled === "true") return;
1030
+ if (this.isOpen()) return;
1031
+ if (!dispatch(this._element, this._window, "show.moo.datepicker", {}, true)) return;
1032
+ this._popover.hidden = false;
1033
+ this._popover.classList.add("show");
1034
+ startDatepickerPopoverPositioning(this);
1035
+ this._trigger.setAttribute("aria-expanded", "true");
1036
+ this.calendar.focusActiveDay();
1037
+ dispatch(this._element, this._window, "shown.moo.datepicker");
1038
+ }
1039
+ hide(returnFocus = true) {
1040
+ const wasOpen = this.isOpen();
1041
+ if (!wasOpen) return;
1042
+ if (!dispatch(this._element, this._window, "hide.moo.datepicker", {}, true)) return;
1043
+ this._popover.hidden = true;
1044
+ this._popover.classList.remove("show");
1045
+ stopDatepickerPopoverPositioning(this);
1046
+ this._trigger.setAttribute("aria-expanded", "false");
1047
+ if (returnFocus) this._trigger.focus();
1048
+ dispatch(this._element, this._window, "hidden.moo.datepicker");
1049
+ }
1050
+ toggle() {
1051
+ if (this.isOpen()) {
1052
+ this.hide();
1053
+ } else {
1054
+ this.show();
1055
+ }
1056
+ }
1057
+ isOpen() {
1058
+ return !this._popover.hidden;
1059
+ }
1060
+ getDates(format = void 0) {
1061
+ return this.calendar.getDates(format);
1062
+ }
1063
+ setDates(startDate, endDate, options = {}) {
1064
+ const changed = this.calendar.setDates(startDate, endDate, { emit: false });
1065
+ if (changed) {
1066
+ this._syncFromCalendar();
1067
+ if (options.emit !== false) this._emitChange();
1068
+ }
1069
+ return changed;
1070
+ }
1071
+ clear(options = {}) {
1072
+ this.calendar.clear({ emit: false });
1073
+ this._syncFromCalendar();
1074
+ if (options.emit !== false) this._emitChange();
1075
+ }
1076
+ _bindEvents() {
1077
+ addListener(this._listeners, this._trigger, "click", () => this.toggle());
1078
+ addListener(this._listeners, this._popover, "keydown", (event) => {
1079
+ if (event.key === "Escape") {
1080
+ event.preventDefault();
1081
+ this.hide();
1082
+ }
1083
+ });
1084
+ addListener(this._listeners, this._document, "pointerdown", (event) => {
1085
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
1086
+ this._window.setTimeout(() => {
1087
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
1088
+ this.hide();
1089
+ }
1090
+ }, 0);
1091
+ }
1092
+ });
1093
+ const closeWhenFocusLeaves = () => {
1094
+ this._window.setTimeout(() => {
1095
+ if (this.isOpen() && !datepickerContains(this, this._document.activeElement)) {
1096
+ this.hide(false);
1097
+ }
1098
+ }, 0);
1099
+ };
1100
+ addListener(this._listeners, this._element, "focusout", closeWhenFocusLeaves);
1101
+ addListener(this._listeners, this._popover, "focusout", closeWhenFocusLeaves);
1102
+ addListener(this._listeners, this._calendarRoot, "change.moo.calendar", (event) => {
1103
+ this._syncFromCalendar();
1104
+ this._emitChange();
1105
+ if (event.detail.complete) {
1106
+ this.hide();
1107
+ } else {
1108
+ this.calendar.focusActiveDay();
1109
+ }
1110
+ });
1111
+ const form = this._startInput.form || this._element.closest("form");
1112
+ addListener(this._listeners, this._validationInput, "invalid", (event) => {
1113
+ event.preventDefault();
1114
+ setDatepickerInvalid(this._element, this._trigger, true);
1115
+ this._trigger.focus({ preventScroll: true });
1116
+ });
1117
+ addListener(this._listeners, form, "reset", () => {
1118
+ this._window.setTimeout(() => {
1119
+ this.setDates(this._defaultStartValue, this._defaultEndValue, { emit: false });
1120
+ }, 0);
1121
+ });
1122
+ }
1123
+ _syncFromCalendar() {
1124
+ const [startValue = "", endValue = ""] = this.calendar.getDates("yyyy-mm-dd");
1125
+ setHiddenValue(this._startInput, startValue || "");
1126
+ setHiddenValue(this._endInput, endValue || "");
1127
+ setValidationValue(this._validationInput, startValue && endValue ? `${startValue} - ${endValue}` : "");
1128
+ syncDatepickerValidation(this._element, this._trigger, this._validationInput);
1129
+ this._element.dataset.datepickerStartValue = startValue || "";
1130
+ this._element.dataset.datepickerEndValue = endValue || "";
1131
+ if (startValue && endValue) {
1132
+ this._label.textContent = `${formatDisplay(parseDate(startValue), this._config.locale)} - ${formatDisplay(parseDate(endValue), this._config.locale)}`;
1133
+ } else if (startValue) {
1134
+ this._label.textContent = formatDisplay(parseDate(startValue), this._config.locale);
1135
+ } else {
1136
+ this._label.textContent = this._placeholder;
1137
+ }
1138
+ }
1139
+ _emitChange() {
1140
+ const [startDate, endDate] = this.getDates();
1141
+ const [startValue = "", endValue = ""] = this.getDates("yyyy-mm-dd");
1142
+ dispatch(this._element, this._window, "change.moo.datepicker", {
1143
+ startDate,
1144
+ endDate,
1145
+ startValue,
1146
+ endValue
1147
+ });
1148
+ }
1149
+ };
1150
+ export {
1151
+ MooCalendar,
1152
+ MooDateRangePicker,
1153
+ MooDatepicker as default
1154
+ };