@wpmoo/ui 0.9.0 → 1.0.0-rc.10

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