@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,3947 @@
1
+ /*!
2
+ * Moo UI moo-ui.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/components/combobox.js
8
+ var instances = /* @__PURE__ */ new WeakMap();
9
+ var normalize = (value) => value.trim().toLowerCase();
10
+ var Combobox = class _Combobox {
11
+ static getInstance(element) {
12
+ return element?.nodeType === 1 ? instances.get(element) || null : null;
13
+ }
14
+ static getOrCreateInstance(element, config = {}) {
15
+ return _Combobox.getInstance(element) || new _Combobox(element, config);
16
+ }
17
+ constructor(element, config = {}) {
18
+ if (element?.nodeType !== 1 || !element.matches(".combobox")) {
19
+ throw new TypeError("Combobox requires a .combobox root element.");
20
+ }
21
+ const existing = instances.get(element);
22
+ if (existing) {
23
+ return existing;
24
+ }
25
+ this._element = element;
26
+ this._document = element.ownerDocument;
27
+ this._window = this._document.defaultView;
28
+ this._config = { noResultsText: "No results found.", ...config };
29
+ this._input = element.querySelector(".combobox-input");
30
+ this._menu = element.querySelector(".combobox-menu");
31
+ this._options = Array.from(element.querySelectorAll(".combobox-option"));
32
+ if (!this._input || !this._menu || this._options.length === 0) {
33
+ throw new TypeError("Combobox requires an input, menu, and at least one option.");
34
+ }
35
+ this._isMultiple = element.dataset.comboboxMultiple === "true";
36
+ this._hidden = this._isMultiple ? null : element.querySelector('input[type="hidden"]');
37
+ this._chipValue = element.querySelector(".combobox-value");
38
+ this._valueStore = element.querySelector("[data-combobox-value-store]");
39
+ this._chipIconTemplate = element.querySelector("[data-combobox-chip-icon]");
40
+ this._clearTrigger = element.querySelector("[data-combobox-clear]");
41
+ this._indicator = element.querySelector(".combobox-indicator");
42
+ this._groups = Array.from(element.querySelectorAll("[data-combobox-group]"));
43
+ this._separators = Array.from(element.querySelectorAll("[data-combobox-separator]"));
44
+ this._listeners = [];
45
+ this._generated = [];
46
+ this._startsOpen = this._menu.classList.contains("show");
47
+ this._empty = this._menu.querySelector("[data-combobox-empty]");
48
+ if (!this._empty) {
49
+ this._empty = this._document.createElement("li");
50
+ this._empty.className = "combobox-empty";
51
+ this._empty.hidden = true;
52
+ this._empty.dataset.comboboxEmpty = "true";
53
+ this._empty.textContent = this._config.noResultsText;
54
+ this._menu.appendChild(this._empty);
55
+ this._generated.push(this._empty);
56
+ }
57
+ this._liveRegion = element.querySelector("[data-combobox-live]");
58
+ if (!this._liveRegion) {
59
+ this._liveRegion = this._document.createElement("span");
60
+ this._liveRegion.className = "visually-hidden";
61
+ this._liveRegion.setAttribute("aria-live", "polite");
62
+ this._liveRegion.dataset.comboboxLive = "true";
63
+ element.appendChild(this._liveRegion);
64
+ this._generated.push(this._liveRegion);
65
+ }
66
+ instances.set(element, this);
67
+ this._bindEvents();
68
+ this._toggleClear(
69
+ this._input.dataset.comboboxSelected === "true" || element.dataset.comboboxSelected === "true"
70
+ );
71
+ this._syncMultipleValue();
72
+ this._filterOptions({ open: this._startsOpen, activate: this._startsOpen });
73
+ if (!this._startsOpen) {
74
+ this._closeMenu(false);
75
+ }
76
+ }
77
+ dispose() {
78
+ this._listeners.forEach(({ target, type, handler, options }) => {
79
+ target.removeEventListener(type, handler, options);
80
+ });
81
+ this._listeners = [];
82
+ this._closeMenu(false);
83
+ this._generated.forEach((element) => element.remove());
84
+ this._generated = [];
85
+ this._chipValue?.querySelectorAll("[data-combobox-generated]").forEach((item) => item.remove());
86
+ this._valueStore?.querySelectorAll("[data-combobox-generated]").forEach((item) => item.remove());
87
+ instances.delete(this._element);
88
+ }
89
+ _listen(target, type, handler, options) {
90
+ target?.addEventListener(type, handler, options);
91
+ if (target) {
92
+ this._listeners.push({ target, type, handler, options });
93
+ }
94
+ }
95
+ _trigger(name, detail = {}) {
96
+ return this._element.dispatchEvent(
97
+ new this._window.CustomEvent(`${name}.moo.combobox`, {
98
+ bubbles: true,
99
+ cancelable: name === "show" || name === "hide",
100
+ detail
101
+ })
102
+ );
103
+ }
104
+ _optionGroupIsHidden(option) {
105
+ return option.closest("[data-combobox-group]")?.hidden || false;
106
+ }
107
+ _visibleOptions() {
108
+ return this._options.filter(
109
+ (option) => !option.hidden && !option.disabled && !this._optionGroupIsHidden(option)
110
+ );
111
+ }
112
+ _syncGroupVisibility() {
113
+ if (this._groups.length === 0) {
114
+ return;
115
+ }
116
+ let visibleGroupCount = 0;
117
+ this._groups.forEach((group) => {
118
+ const visible = Array.from(group.querySelectorAll(".combobox-option")).some(
119
+ (option) => !option.hidden
120
+ );
121
+ group.hidden = !visible;
122
+ visibleGroupCount += visible ? 1 : 0;
123
+ });
124
+ this._separators.forEach((separator) => {
125
+ separator.hidden = visibleGroupCount < 2;
126
+ });
127
+ }
128
+ _openMenu(emit = true) {
129
+ if (this._menu.classList.contains("show")) {
130
+ this._input.setAttribute("aria-expanded", "true");
131
+ return;
132
+ }
133
+ if (emit && !this._trigger("show")) {
134
+ return;
135
+ }
136
+ this._menu.classList.add("show");
137
+ this._input.setAttribute("aria-expanded", "true");
138
+ if (emit) {
139
+ this._trigger("shown");
140
+ }
141
+ }
142
+ _closeMenu(emit = true) {
143
+ const wasOpen = this._menu.classList.contains("show");
144
+ if (wasOpen && emit && !this._trigger("hide")) {
145
+ return;
146
+ }
147
+ this._menu.classList.remove("show");
148
+ this._input.setAttribute("aria-expanded", "false");
149
+ this._input.removeAttribute("aria-activedescendant");
150
+ this._options.forEach((option) => {
151
+ option.removeAttribute("aria-current");
152
+ option.classList.remove("active");
153
+ });
154
+ if (wasOpen && emit) {
155
+ this._trigger("hidden");
156
+ }
157
+ }
158
+ _optionLabel(option) {
159
+ return option?.querySelector(".combobox-option__label")?.textContent?.trim() || "";
160
+ }
161
+ _selectedOptions() {
162
+ return this._options.filter((option) => option.getAttribute("aria-selected") === "true");
163
+ }
164
+ _toggleClear(selected) {
165
+ const value = selected ? "true" : "false";
166
+ this._element.dataset.comboboxSelected = value;
167
+ this._input.dataset.comboboxSelected = value;
168
+ if (this._clearTrigger) {
169
+ this._clearTrigger.hidden = !selected;
170
+ if (this._indicator) {
171
+ this._indicator.hidden = selected;
172
+ }
173
+ }
174
+ }
175
+ _createChip(option) {
176
+ const value = option.dataset.value || "";
177
+ const label = this._optionLabel(option);
178
+ const chip = this._document.createElement("span");
179
+ chip.className = "badge text-bg-secondary combobox-chip";
180
+ chip.dataset.value = value;
181
+ chip.dataset.comboboxGenerated = "true";
182
+ const labelElement = this._document.createElement("span");
183
+ labelElement.className = "combobox-chip__label";
184
+ labelElement.textContent = label;
185
+ chip.appendChild(labelElement);
186
+ const remove = this._document.createElement("button");
187
+ remove.className = "combobox-chip__remove";
188
+ remove.type = "button";
189
+ remove.dataset.value = value;
190
+ remove.dataset.comboboxChipRemove = "true";
191
+ remove.setAttribute("aria-label", `Remove ${label}`);
192
+ remove.append(this._chipIconTemplate?.content.cloneNode(true) || "x");
193
+ chip.appendChild(remove);
194
+ return chip;
195
+ }
196
+ _syncMultipleValue() {
197
+ if (!this._isMultiple) {
198
+ return;
199
+ }
200
+ const selected = this._selectedOptions();
201
+ if (this._chipValue) {
202
+ this._chipValue.replaceChildren(...selected.map((option) => this._createChip(option)));
203
+ }
204
+ if (this._valueStore) {
205
+ this._valueStore.replaceChildren();
206
+ const name = this._valueStore.dataset.comboboxName || "";
207
+ selected.forEach((option) => {
208
+ const field = this._document.createElement("input");
209
+ field.type = "hidden";
210
+ field.name = name;
211
+ field.value = option.dataset.value || "";
212
+ field.dataset.comboboxGenerated = "true";
213
+ this._valueStore.appendChild(field);
214
+ });
215
+ }
216
+ this._toggleClear(selected.length > 0);
217
+ }
218
+ _emitChange() {
219
+ const selected = this._selectedOptions();
220
+ this._trigger("change", {
221
+ values: selected.map((option) => option.dataset.value || "")
222
+ });
223
+ }
224
+ _removeChip(value) {
225
+ const option = this._options.find((candidate) => candidate.dataset.value === value);
226
+ if (!option) {
227
+ return;
228
+ }
229
+ option.setAttribute("aria-selected", "false");
230
+ this._syncMultipleValue();
231
+ this._emitChange();
232
+ }
233
+ _clearSelection(emit = true) {
234
+ if (this._hidden) {
235
+ this._hidden.value = "";
236
+ }
237
+ this._options.forEach((option) => option.setAttribute("aria-selected", "false"));
238
+ if (this._isMultiple) {
239
+ this._syncMultipleValue();
240
+ } else {
241
+ this._toggleClear(false);
242
+ }
243
+ if (emit) {
244
+ this._emitChange();
245
+ }
246
+ }
247
+ _clearStaleSelection() {
248
+ if (this._isMultiple) {
249
+ this._input.value = "";
250
+ this._options.forEach((option) => {
251
+ option.hidden = false;
252
+ });
253
+ this._syncGroupVisibility();
254
+ this._empty.hidden = true;
255
+ return;
256
+ }
257
+ const selected = this._selectedOptions()[0] || null;
258
+ const label = this._optionLabel(selected);
259
+ if (!this._input.value || label && normalize(this._input.value) === normalize(label)) {
260
+ return;
261
+ }
262
+ this._clearSelection();
263
+ this._input.value = "";
264
+ }
265
+ _activeOption() {
266
+ const activeId = this._input.getAttribute("aria-activedescendant");
267
+ if (!activeId) {
268
+ return null;
269
+ }
270
+ return this._visibleOptions().find((option) => option.id === activeId) || null;
271
+ }
272
+ _selectedOption() {
273
+ if (this._isMultiple) {
274
+ return null;
275
+ }
276
+ return this._visibleOptions().find(
277
+ (option) => option.getAttribute("aria-selected") === "true"
278
+ ) || null;
279
+ }
280
+ _preferredActiveOption() {
281
+ return this._selectedOption() || this._visibleOptions()[0] || null;
282
+ }
283
+ _activatePreferredOptionIfNeeded() {
284
+ if (!this._activeOption()) {
285
+ this._setActiveOption(this._preferredActiveOption());
286
+ }
287
+ }
288
+ _setActiveOption(option, { scroll = true } = {}) {
289
+ this._options.forEach((candidate) => {
290
+ const active = candidate === option;
291
+ if (active) {
292
+ candidate.setAttribute("aria-current", "true");
293
+ } else {
294
+ candidate.removeAttribute("aria-current");
295
+ }
296
+ candidate.classList.toggle("active", active);
297
+ });
298
+ if (!option) {
299
+ this._input.removeAttribute("aria-activedescendant");
300
+ return;
301
+ }
302
+ this._input.setAttribute("aria-activedescendant", option.id);
303
+ if (scroll) {
304
+ const optionRect = option.getBoundingClientRect();
305
+ const menuRect = this._menu.getBoundingClientRect();
306
+ if (optionRect.top < menuRect.top) {
307
+ this._menu.scrollTop -= menuRect.top - optionRect.top;
308
+ } else if (optionRect.bottom > menuRect.bottom) {
309
+ this._menu.scrollTop += optionRect.bottom - menuRect.bottom;
310
+ }
311
+ }
312
+ }
313
+ _activateOptionFromPointer(option, event) {
314
+ if (event.pointerType === "touch" || option.disabled || option.getAttribute("aria-disabled") === "true" || option === this._activeOption()) {
315
+ return;
316
+ }
317
+ this._setActiveOption(option, { scroll: false });
318
+ }
319
+ _restoreSelectedActiveOption() {
320
+ if (!this._menu.classList.contains("show")) {
321
+ return;
322
+ }
323
+ const selected = this._selectedOption();
324
+ if (selected) {
325
+ this._setActiveOption(selected, { scroll: false });
326
+ }
327
+ }
328
+ _chooseOption(option) {
329
+ if (!option || option.disabled) {
330
+ return;
331
+ }
332
+ if (this._isMultiple) {
333
+ option.setAttribute(
334
+ "aria-selected",
335
+ option.getAttribute("aria-selected") === "true" ? "false" : "true"
336
+ );
337
+ this._input.value = "";
338
+ this._syncMultipleValue();
339
+ this._filterOptions();
340
+ this._setActiveOption(option);
341
+ this._emitChange();
342
+ return;
343
+ }
344
+ this._options.forEach((candidate) => {
345
+ candidate.setAttribute("aria-selected", candidate === option ? "true" : "false");
346
+ });
347
+ this._input.value = this._optionLabel(option);
348
+ if (this._hidden) {
349
+ this._hidden.value = option.dataset.value || "";
350
+ }
351
+ this._toggleClear(true);
352
+ this._setActiveOption(option);
353
+ this._emitChange();
354
+ }
355
+ _filterOptions({ open = true, activate = true, resetActive = false } = {}) {
356
+ if (open) {
357
+ this._openMenu();
358
+ }
359
+ const needle = normalize(this._input.value);
360
+ let count = 0;
361
+ this._options.forEach((option) => {
362
+ const matches = !needle || normalize(option.textContent).includes(needle);
363
+ option.hidden = !matches;
364
+ count += matches ? 1 : 0;
365
+ });
366
+ this._syncGroupVisibility();
367
+ this._empty.hidden = count !== 0;
368
+ this._liveRegion.textContent = count === 0 ? "No results" : `${count} result${count === 1 ? "" : "s"}`;
369
+ if (resetActive) {
370
+ this._setActiveOption(null, { scroll: false });
371
+ }
372
+ if (activate) {
373
+ this._activatePreferredOptionIfNeeded();
374
+ }
375
+ }
376
+ _bindEvents() {
377
+ this._listen(this._input, "focus", () => {
378
+ this._openMenu();
379
+ if (!this._isMultiple && this._input.dataset.comboboxSelected === "true") {
380
+ this._input.select();
381
+ }
382
+ this._activatePreferredOptionIfNeeded();
383
+ });
384
+ this._listen(this._input, "input", () => {
385
+ if (!this._isMultiple) {
386
+ this._clearSelection();
387
+ }
388
+ this._filterOptions({ resetActive: true });
389
+ });
390
+ this._listen(this._input, "blur", (event) => {
391
+ const nextTarget = event.relatedTarget;
392
+ if (nextTarget instanceof this._window.Node && this._element.contains(nextTarget)) {
393
+ return;
394
+ }
395
+ this._clearStaleSelection();
396
+ });
397
+ this._listen(this._input, "click", () => {
398
+ this._openMenu();
399
+ this._setActiveOption(this._preferredActiveOption());
400
+ });
401
+ this._listen(this._input, "keydown", (event) => this._handleKeydown(event));
402
+ this._options.forEach((option) => {
403
+ this._listen(option, "keydown", (event) => this._handleKeydown(event));
404
+ this._listen(option, "pointerenter", (event) => {
405
+ this._activateOptionFromPointer(option, event);
406
+ });
407
+ this._listen(option, "click", () => {
408
+ this._chooseOption(option);
409
+ if (this._isMultiple) {
410
+ this._input.focus();
411
+ } else {
412
+ this._closeMenu();
413
+ }
414
+ });
415
+ });
416
+ this._listen(this._clearTrigger, "click", (event) => {
417
+ event.preventDefault();
418
+ this._clearSelection();
419
+ this._input.value = "";
420
+ this._input.focus();
421
+ this._filterOptions({ resetActive: true });
422
+ });
423
+ this._listen(this._menu, "pointerleave", () => this._restoreSelectedActiveOption());
424
+ this._listen(this._element, "click", (event) => this._handleRootClick(event));
425
+ this._listen(this._document, "click", (event) => {
426
+ if (event.target instanceof this._window.Node && !this._element.contains(event.target)) {
427
+ this._closeMenu();
428
+ }
429
+ });
430
+ }
431
+ _handleRootClick(event) {
432
+ const target = event.target;
433
+ const trigger = target instanceof this._window.Element ? target.closest("[data-combobox-chip-remove]") : null;
434
+ if (trigger) {
435
+ event.preventDefault();
436
+ this._removeChip(trigger.dataset.value || "");
437
+ this._input.focus();
438
+ } else if (this._isMultiple && target instanceof this._window.Element && target.closest(".combobox-chips")) {
439
+ this._input.focus();
440
+ }
441
+ }
442
+ _handleKeydown(event) {
443
+ const available = this._visibleOptions();
444
+ const current = available.findIndex(
445
+ (option) => option.id === this._input.getAttribute("aria-activedescendant")
446
+ );
447
+ const isNextKey = event.key === "ArrowDown" || event.key === "Down";
448
+ const isPreviousKey = event.key === "ArrowUp" || event.key === "Up";
449
+ if (this._isMultiple && event.key === "Backspace" && this._input.value === "") {
450
+ const selected = this._selectedOptions();
451
+ const last = selected[selected.length - 1];
452
+ if (last) {
453
+ event.preventDefault();
454
+ this._removeChip(last.dataset.value || "");
455
+ }
456
+ } else if ((isNextKey || isPreviousKey) && available.length) {
457
+ event.preventDefault();
458
+ this._openMenu();
459
+ const offset = isNextKey ? 1 : -1;
460
+ const next = current === -1 ? 0 : (current + offset + available.length) % available.length;
461
+ this._setActiveOption(available[next]);
462
+ if (event.target !== this._input) {
463
+ this._input.focus({ preventScroll: true });
464
+ }
465
+ } else if (event.key === "Enter") {
466
+ const option = available[current];
467
+ if (option) {
468
+ event.preventDefault();
469
+ this._chooseOption(option);
470
+ if (!this._isMultiple) {
471
+ this._closeMenu();
472
+ }
473
+ }
474
+ } else if (event.key === "Escape") {
475
+ this._closeMenu();
476
+ this._input.focus();
477
+ } else if (event.key === "Tab") {
478
+ this._closeMenu();
479
+ }
480
+ }
481
+ };
482
+
483
+ // src/js/components/context-menu.js
484
+ var instances2 = /* @__PURE__ */ new WeakMap();
485
+ function pointerRect(event) {
486
+ const { clientX: x, clientY: y } = event;
487
+ return { top: y, left: x, right: x, bottom: y, width: 0, height: 0, x, y };
488
+ }
489
+ function isDisabledTrigger(trigger) {
490
+ return Boolean(trigger?.disabled || trigger?.getAttribute?.("aria-disabled") === "true");
491
+ }
492
+ function menuItemFromEvent(event) {
493
+ const target = event.target;
494
+ const item = target.closest?.(".dropdown-item");
495
+ if (item) {
496
+ return item;
497
+ }
498
+ return target.closest?.("li")?.querySelector(":scope > .dropdown-item") || null;
499
+ }
500
+ var ContextMenu = class _ContextMenu {
501
+ static getInstance(element) {
502
+ return element?.nodeType === 1 ? instances2.get(element) || null : null;
503
+ }
504
+ static getOrCreateInstance(element, config = {}) {
505
+ return _ContextMenu.getInstance(element) || new _ContextMenu(element, config);
506
+ }
507
+ constructor(element, config = {}) {
508
+ if (element?.nodeType !== 1 || !element.matches(".context-menu")) {
509
+ throw new TypeError("ContextMenu requires a .context-menu root element.");
510
+ }
511
+ const existing = instances2.get(element);
512
+ if (existing) {
513
+ return existing;
514
+ }
515
+ this._element = element;
516
+ this._document = element.ownerDocument;
517
+ this._window = this._document.defaultView;
518
+ this._menu = element.querySelector(":scope > .context-menu-menu");
519
+ this._fallback = element.querySelector(":scope > [data-context-menu-fallback]");
520
+ if (!this._menu) {
521
+ throw new TypeError("ContextMenu requires a .context-menu-menu element.");
522
+ }
523
+ if (!this._fallback) {
524
+ throw new TypeError("ContextMenu requires an explicit fallback trigger.");
525
+ }
526
+ const Dropdown = this._bootstrap("Dropdown");
527
+ if (!Dropdown) {
528
+ throw new TypeError("ContextMenu requires bootstrap.Dropdown (bootstrap.bundle.js).");
529
+ }
530
+ this._config = { ...config };
531
+ this._listeners = [];
532
+ this._invoker = null;
533
+ this._pointerRect = null;
534
+ this._openMethod = null;
535
+ this._reference = {
536
+ getBoundingClientRect: () => this._currentRect(),
537
+ contextElement: this._fallback
538
+ };
539
+ this._dropdown = new Dropdown(this._fallback, {
540
+ reference: this._reference,
541
+ autoClose: "outside"
542
+ });
543
+ instances2.set(element, this);
544
+ this._bindEvents();
545
+ }
546
+ dispose() {
547
+ this._listeners.forEach(({ target, type, handler, options }) => {
548
+ target.removeEventListener(type, handler, options);
549
+ });
550
+ this._listeners = [];
551
+ this._dropdown.dispose();
552
+ this._syncExpanded(false);
553
+ this._invoker = null;
554
+ this._pointerRect = null;
555
+ instances2.delete(this._element);
556
+ }
557
+ show(anchor = this._fallback) {
558
+ this._open(anchor, null);
559
+ }
560
+ hide() {
561
+ this._dropdown.hide();
562
+ }
563
+ _listen(target, type, handler, options) {
564
+ target.addEventListener(type, handler, options);
565
+ this._listeners.push({ target, type, handler, options });
566
+ }
567
+ _bootstrap(name) {
568
+ return this._window.bootstrap?.[name] || null;
569
+ }
570
+ _currentRect() {
571
+ if (this._pointerRect) {
572
+ return this._pointerRect;
573
+ }
574
+ return (this._invoker || this._fallback).getBoundingClientRect();
575
+ }
576
+ _trigger(name, cancelable = false) {
577
+ return this._element.dispatchEvent(
578
+ new this._window.CustomEvent(`${name}.moo.context-menu`, {
579
+ bubbles: true,
580
+ cancelable
581
+ })
582
+ );
583
+ }
584
+ _triggers() {
585
+ return Array.from(this._element.querySelectorAll(":scope > .context-menu-trigger"));
586
+ }
587
+ _syncExpanded(expanded) {
588
+ this._triggers().forEach((trigger) => {
589
+ trigger.setAttribute("aria-expanded", String(expanded));
590
+ });
591
+ }
592
+ _menuItems() {
593
+ return Array.from(this._menu.querySelectorAll(".dropdown-item")).filter(
594
+ (item) => !isDisabledTrigger(item) && !item.classList.contains("disabled")
595
+ );
596
+ }
597
+ _open(trigger, pointerEvent, openMethod) {
598
+ if (isDisabledTrigger(trigger)) {
599
+ return;
600
+ }
601
+ if (this._menu.classList.contains("show")) {
602
+ this._dropdown.hide();
603
+ }
604
+ this._invoker = trigger;
605
+ this._pointerRect = pointerEvent ? pointerRect(pointerEvent) : null;
606
+ this._openMethod = openMethod;
607
+ this._dropdown.show();
608
+ }
609
+ _handleContextMenu(event) {
610
+ const trigger = event.target.closest(".context-menu-trigger");
611
+ if (!trigger || !this._element.contains(trigger)) {
612
+ return;
613
+ }
614
+ event.preventDefault();
615
+ this._open(trigger, event, "pointer");
616
+ }
617
+ _handleKeydown(event) {
618
+ const trigger = event.target.closest(".context-menu-trigger");
619
+ if (!trigger || !this._element.contains(trigger)) {
620
+ return;
621
+ }
622
+ const isShiftF10 = event.shiftKey && event.key === "F10";
623
+ const isContextMenuKey = event.key === "ContextMenu";
624
+ if (!isShiftF10 && !isContextMenuKey) {
625
+ return;
626
+ }
627
+ event.preventDefault();
628
+ this._open(trigger, null, "keyboard");
629
+ }
630
+ _handleFallbackClick() {
631
+ this._invoker = this._fallback;
632
+ this._pointerRect = null;
633
+ this._openMethod = "pointer";
634
+ }
635
+ _handleFallbackKeydown(event) {
636
+ if (event.key !== " " && event.key !== "Spacebar") {
637
+ return;
638
+ }
639
+ event.preventDefault();
640
+ event.stopPropagation();
641
+ if (this._menu.classList.contains("show")) {
642
+ this._dropdown.hide();
643
+ return;
644
+ }
645
+ this._open(this._fallback, null, "fallback-keyboard");
646
+ }
647
+ _handleMenuClick(event) {
648
+ const item = menuItemFromEvent(event);
649
+ if (!item) {
650
+ return;
651
+ }
652
+ const persistentItem = item.matches(".dropdown-item-check, [data-context-menu-persistent]");
653
+ const disabledItem = item.matches('.dropdown-item.disabled, .dropdown-item[aria-disabled="true"], .dropdown-item:disabled') || isDisabledTrigger(item);
654
+ if (disabledItem) {
655
+ event.preventDefault();
656
+ event.stopPropagation();
657
+ return;
658
+ }
659
+ const radioButton = item.matches("[data-context-menu-radio-group]") ? item : null;
660
+ if (radioButton) {
661
+ event.preventDefault();
662
+ event.stopPropagation();
663
+ const group = radioButton.dataset.contextMenuRadioGroup;
664
+ this._menu.querySelectorAll(`[data-context-menu-radio-group="${group}"]`).forEach((button) => {
665
+ const checked = button === radioButton;
666
+ button.classList.toggle("active", checked);
667
+ button.setAttribute("aria-pressed", String(checked));
668
+ });
669
+ return;
670
+ }
671
+ if (persistentItem) {
672
+ return;
673
+ }
674
+ this._dropdown.hide();
675
+ }
676
+ _bindEvents() {
677
+ this._listen(this._element, "contextmenu", (event) => this._handleContextMenu(event));
678
+ this._listen(this._element, "keydown", (event) => this._handleKeydown(event));
679
+ this._listen(this._fallback, "click", () => this._handleFallbackClick());
680
+ this._listen(this._fallback, "keydown", (event) => this._handleFallbackKeydown(event));
681
+ this._listen(this._menu, "click", (event) => this._handleMenuClick(event));
682
+ this._listen(this._fallback, "show.bs.dropdown", (event) => {
683
+ if (!this._trigger("show", true)) {
684
+ event.preventDefault();
685
+ }
686
+ });
687
+ this._listen(this._fallback, "shown.bs.dropdown", () => {
688
+ this._syncExpanded(true);
689
+ if (this._openMethod === "fallback-keyboard") {
690
+ this._fallback.focus();
691
+ } else if (this._openMethod === "keyboard") {
692
+ this._menuItems()[0]?.focus();
693
+ } else {
694
+ this._menu.focus();
695
+ }
696
+ this._trigger("shown");
697
+ });
698
+ this._listen(this._fallback, "hide.bs.dropdown", (event) => {
699
+ if (!this._trigger("hide", true)) {
700
+ event.preventDefault();
701
+ }
702
+ });
703
+ this._listen(this._fallback, "hidden.bs.dropdown", () => {
704
+ this._syncExpanded(false);
705
+ const invoker = this._invoker;
706
+ this._pointerRect = null;
707
+ this._invoker = null;
708
+ this._openMethod = null;
709
+ this._trigger("hidden");
710
+ queueMicrotask(() => {
711
+ if (invoker && invoker !== this._fallback && this._document.activeElement === this._fallback) {
712
+ invoker.focus();
713
+ }
714
+ });
715
+ });
716
+ }
717
+ };
718
+
719
+ // src/js/theme-owner.js
720
+ var OWNER_SELECTOR = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]';
721
+ var PORTAL_SELECTOR = "[data-moo-overlay-host]";
722
+ function documentFor(node) {
723
+ if (node?.nodeType === 9) return node;
724
+ if (node?.ownerDocument) return node.ownerDocument;
725
+ return typeof document === "undefined" ? null : document;
726
+ }
727
+ function isResolvedOwner(node) {
728
+ return Boolean(node?.matches?.(OWNER_SELECTOR));
729
+ }
730
+ function ownerRootFor(node) {
731
+ const ownerDocument = documentFor(node);
732
+ if (node?.nodeType === 9 || node === ownerDocument?.body) {
733
+ return ownerDocument?.body?.firstElementChild || null;
734
+ }
735
+ return node;
736
+ }
737
+ function findThemeOwner(node = typeof document === "undefined" ? null : document) {
738
+ const start = ownerRootFor(node);
739
+ if (!start) return null;
740
+ if (isResolvedOwner(start)) return start;
741
+ return start.closest?.(OWNER_SELECTOR) || null;
742
+ }
743
+ function ownerPortalRoot(owner) {
744
+ if (!owner) return null;
745
+ return Array.from(owner.children || []).find(
746
+ (child) => child.matches?.(PORTAL_SELECTOR)
747
+ ) || owner;
748
+ }
749
+
750
+ // src/js/components/datatable.js
751
+ var instances3 = /* @__PURE__ */ new WeakMap();
752
+ function normalize2(value) {
753
+ return (value ?? "").toString().trim().toLowerCase();
754
+ }
755
+ var DataTable = class _DataTable {
756
+ static getInstance(element) {
757
+ return element?.nodeType === 1 ? instances3.get(element) || null : null;
758
+ }
759
+ static getOrCreateInstance(element, config = {}) {
760
+ return _DataTable.getInstance(element) || new _DataTable(element, config);
761
+ }
762
+ constructor(element, config = {}) {
763
+ if (element?.nodeType !== 1 || !element.matches(".datatable")) {
764
+ throw new TypeError("DataTable requires a .datatable root element.");
765
+ }
766
+ const existing = instances3.get(element);
767
+ if (existing) {
768
+ return existing;
769
+ }
770
+ this._element = element;
771
+ this._document = element.ownerDocument;
772
+ this._window = this._document.defaultView;
773
+ this._table = element.querySelector(":scope .datatable-table");
774
+ this._tbody = this._table?.querySelector(":scope > tbody");
775
+ if (!this._table || !this._tbody) {
776
+ throw new TypeError("DataTable requires a .datatable-table with a tbody.");
777
+ }
778
+ this._cards = element.querySelector("[data-datatable-cards]");
779
+ const cardById = new Map(
780
+ Array.from(element.querySelectorAll("[data-datatable-card]")).map((card) => [card.dataset.datatableCardFor, card])
781
+ );
782
+ this._config = { ...config };
783
+ this._listeners = [];
784
+ this._pageSize = Number(element.dataset.datatablePageSize) || 10;
785
+ this._currentPage = 1;
786
+ this._sortKey = null;
787
+ this._sortDirection = null;
788
+ this._selectedIds = /* @__PURE__ */ new Set();
789
+ this._facetSelections = /* @__PURE__ */ new Map();
790
+ this._visibleFacets = /* @__PURE__ */ new Map();
791
+ this._searchTerm = "";
792
+ this._tooltips = [];
793
+ this._filterMode = element.dataset.datatableFilterMode || "inline";
794
+ this._reparentedRowMenus = /* @__PURE__ */ new Map();
795
+ this._reparentedRowMenuByTrigger = /* @__PURE__ */ new WeakMap();
796
+ this._reparentedSortMenus = /* @__PURE__ */ new Map();
797
+ this._reparentedSortMenuByTrigger = /* @__PURE__ */ new WeakMap();
798
+ this._rows = Array.from(this._tbody.querySelectorAll(":scope > tr[data-datatable-row]")).map((tr, index) => ({
799
+ element: tr,
800
+ cardElement: cardById.get(tr.id) || null,
801
+ index,
802
+ search: normalize2(tr.dataset.datatableSearch),
803
+ facets: this._readFacets(tr)
804
+ }));
805
+ this._readInitialFacetVisibility();
806
+ this._readInitialSort();
807
+ instances3.set(element, this);
808
+ this._bindEvents();
809
+ this._render();
810
+ this._initBulkTooltips();
811
+ this._initViewToggle();
812
+ this._initRowActionDropdowns();
813
+ this._initSortDropdowns();
814
+ }
815
+ dispose() {
816
+ this._disposeRowActionDropdowns();
817
+ this._disposeSortDropdowns();
818
+ this._restoreRowActionMenus();
819
+ this._restoreSortMenus();
820
+ this._listeners.forEach(({ target, type, handler, options }) => {
821
+ target.removeEventListener(type, handler, options);
822
+ });
823
+ this._listeners = [];
824
+ this._tooltips.forEach((tooltip) => tooltip.dispose());
825
+ this._tooltips = [];
826
+ instances3.delete(this._element);
827
+ }
828
+ getSelectedIds() {
829
+ return Array.from(this._selectedIds);
830
+ }
831
+ _listen(target, type, handler, options) {
832
+ if (!target) {
833
+ return;
834
+ }
835
+ target.addEventListener(type, handler, options);
836
+ this._listeners.push({ target, type, handler, options });
837
+ }
838
+ // Row menus are authored next to their trigger so the public HTML stays
839
+ // inspectable, but when opened they are temporarily moved to their nearest
840
+ // Moo owner portal.
841
+ // That keeps the menu out of the scroll wrapper's layout math, so Safari
842
+ // cannot clip it at the rounded frame edge or shift the visible table
843
+ // slice while Popper positions it against the viewport.
844
+ // Public Dropdown compositions need the same behavior as the built-in
845
+ // row actions. Scope by row/card ownership so toolbar and sort menus keep
846
+ // their own lifecycle.
847
+ _rowActionTriggers() {
848
+ return this._element.querySelectorAll(
849
+ 'tr[data-datatable-row] .dropdown [data-bs-toggle="dropdown"], [data-datatable-card] .dropdown [data-bs-toggle="dropdown"]'
850
+ );
851
+ }
852
+ _initRowActionDropdowns() {
853
+ const Dropdown = this._bootstrap("Dropdown");
854
+ if (!Dropdown) {
855
+ return;
856
+ }
857
+ this._rowActionTriggers().forEach((trigger) => {
858
+ Dropdown.getOrCreateInstance(trigger, {
859
+ popperConfig: (defaultConfig) => this._clippingSafePopperConfig(defaultConfig)
860
+ });
861
+ });
862
+ }
863
+ _sortTriggers() {
864
+ return this._element.querySelectorAll(
865
+ '.datatable-sort [data-bs-toggle="dropdown"]'
866
+ );
867
+ }
868
+ _initSortDropdowns() {
869
+ const Dropdown = this._bootstrap("Dropdown");
870
+ if (!Dropdown) {
871
+ return;
872
+ }
873
+ this._sortTriggers().forEach((trigger) => {
874
+ Dropdown.getOrCreateInstance(trigger, {
875
+ popperConfig: (defaultConfig) => this._clippingSafePopperConfig(defaultConfig)
876
+ });
877
+ });
878
+ }
879
+ _clippingSafePopperConfig(defaultConfig) {
880
+ return {
881
+ ...defaultConfig,
882
+ strategy: "fixed",
883
+ modifiers: [
884
+ ...defaultConfig.modifiers || [],
885
+ {
886
+ name: "flip",
887
+ options: {
888
+ fallbackPlacements: ["top-end", "top-start", "bottom-end", "bottom-start"]
889
+ }
890
+ },
891
+ {
892
+ name: "preventOverflow",
893
+ options: {
894
+ boundary: "viewport",
895
+ padding: 8
896
+ }
897
+ }
898
+ ]
899
+ };
900
+ }
901
+ // dispose() alone leaves any open row-action Dropdown's show/aria-expanded/
902
+ // data-bs-popper state and Bootstrap instance behind; hide and dispose each
903
+ // one first so a subsequent re-init starts from a clean state.
904
+ _disposeRowActionDropdowns() {
905
+ const Dropdown = this._bootstrap("Dropdown");
906
+ if (!Dropdown) {
907
+ return;
908
+ }
909
+ this._rowActionTriggers().forEach((trigger) => {
910
+ const instance = Dropdown.getInstance(trigger);
911
+ if (!instance) {
912
+ return;
913
+ }
914
+ instance.hide();
915
+ instance.dispose();
916
+ });
917
+ }
918
+ _disposeSortDropdowns() {
919
+ const Dropdown = this._bootstrap("Dropdown");
920
+ if (!Dropdown) {
921
+ return;
922
+ }
923
+ this._sortTriggers().forEach((trigger) => {
924
+ const instance = Dropdown.getInstance(trigger);
925
+ if (!instance) {
926
+ return;
927
+ }
928
+ instance.hide();
929
+ instance.dispose();
930
+ });
931
+ }
932
+ _rowActionMenuForTrigger(trigger) {
933
+ if (!trigger?.closest?.("tr[data-datatable-row], [data-datatable-card]")) {
934
+ return null;
935
+ }
936
+ return trigger.closest(".dropdown")?.querySelector(":scope > .dropdown-menu") || null;
937
+ }
938
+ _rowActionOwnerIdForTrigger(trigger) {
939
+ const rowId = trigger?.closest?.("tr[data-datatable-row]")?.id;
940
+ if (rowId) {
941
+ return rowId;
942
+ }
943
+ return trigger?.closest?.("[data-datatable-card]")?.getAttribute("data-datatable-card-for") || "";
944
+ }
945
+ _reparentRowActionMenu(trigger) {
946
+ const menu = this._rowActionMenuForTrigger(trigger);
947
+ if (!menu || this._reparentedRowMenus.has(menu)) {
948
+ return;
949
+ }
950
+ const ownerId = this._rowActionOwnerIdForTrigger(trigger);
951
+ if (ownerId) {
952
+ menu.setAttribute("data-datatable-row-action-owner", ownerId);
953
+ }
954
+ if (trigger.id) {
955
+ menu.setAttribute("data-datatable-row-action-trigger", trigger.id);
956
+ }
957
+ this._reparentedRowMenus.set(menu, {
958
+ parent: menu.parentNode,
959
+ nextSibling: menu.nextSibling,
960
+ trigger,
961
+ owner: findThemeOwner(trigger)
962
+ });
963
+ this._reparentedRowMenuByTrigger.set(trigger, menu);
964
+ this._portalRoot(trigger).appendChild(menu);
965
+ }
966
+ _restoreRowActionMenuForTrigger(trigger) {
967
+ const menu = this._reparentedRowMenuByTrigger.get(trigger) || this._rowActionMenuForTrigger(trigger);
968
+ if (menu) {
969
+ this._restoreRowActionMenu(menu);
970
+ }
971
+ }
972
+ _restoreRowActionMenu(menu) {
973
+ const original = this._reparentedRowMenus.get(menu);
974
+ if (!original) {
975
+ return;
976
+ }
977
+ const { parent, nextSibling, trigger, owner } = original;
978
+ if (owner?.isConnected === false) {
979
+ menu.remove();
980
+ } else if (parent?.isConnected) {
981
+ if (nextSibling?.parentNode === parent) {
982
+ parent.insertBefore(menu, nextSibling);
983
+ } else {
984
+ parent.appendChild(menu);
985
+ }
986
+ } else {
987
+ menu.remove();
988
+ }
989
+ if (trigger) {
990
+ this._reparentedRowMenuByTrigger.delete(trigger);
991
+ }
992
+ menu.removeAttribute("data-datatable-row-action-owner");
993
+ menu.removeAttribute("data-datatable-row-action-trigger");
994
+ this._reparentedRowMenus.delete(menu);
995
+ }
996
+ _restoreRowActionMenus() {
997
+ Array.from(this._reparentedRowMenus.keys()).forEach((menu) => {
998
+ this._restoreRowActionMenu(menu);
999
+ });
1000
+ }
1001
+ _sortMenuForTrigger(trigger) {
1002
+ if (!trigger?.closest?.(".datatable-sort")) {
1003
+ return null;
1004
+ }
1005
+ return this._reparentedSortMenuByTrigger.get(trigger) || trigger?.closest?.(".dropdown")?.querySelector(":scope > .dropdown-menu") || null;
1006
+ }
1007
+ // Sort menus are authored inside their table header, but must leave the
1008
+ // overflow frame while open just like row-action menus do.
1009
+ _reparentSortMenu(trigger) {
1010
+ const menu = this._sortMenuForTrigger(trigger);
1011
+ if (!menu || this._reparentedSortMenus.has(menu)) {
1012
+ return;
1013
+ }
1014
+ this._reparentedSortMenus.set(menu, {
1015
+ parent: menu.parentNode,
1016
+ nextSibling: menu.nextSibling,
1017
+ trigger,
1018
+ owner: trigger.closest(".datatable-sort"),
1019
+ themeOwner: findThemeOwner(trigger)
1020
+ });
1021
+ this._reparentedSortMenuByTrigger.set(trigger, menu);
1022
+ this._portalRoot(trigger).appendChild(menu);
1023
+ }
1024
+ _restoreSortMenuForTrigger(trigger) {
1025
+ const menu = this._reparentedSortMenuByTrigger.get(trigger) || this._sortMenuForTrigger(trigger);
1026
+ if (menu) {
1027
+ this._restoreSortMenu(menu);
1028
+ }
1029
+ }
1030
+ _restoreSortMenu(menu) {
1031
+ const original = this._reparentedSortMenus.get(menu);
1032
+ if (!original) {
1033
+ return;
1034
+ }
1035
+ const { parent, nextSibling, trigger, themeOwner } = original;
1036
+ if (themeOwner?.isConnected === false) {
1037
+ menu.remove();
1038
+ } else if (parent?.isConnected) {
1039
+ if (nextSibling?.parentNode === parent) {
1040
+ parent.insertBefore(menu, nextSibling);
1041
+ } else {
1042
+ parent.appendChild(menu);
1043
+ }
1044
+ } else {
1045
+ menu.remove();
1046
+ }
1047
+ if (trigger) {
1048
+ this._reparentedSortMenuByTrigger.delete(trigger);
1049
+ }
1050
+ this._reparentedSortMenus.delete(menu);
1051
+ }
1052
+ _restoreSortMenus() {
1053
+ Array.from(this._reparentedSortMenus.keys()).forEach((menu) => {
1054
+ this._restoreSortMenu(menu);
1055
+ });
1056
+ }
1057
+ _bootstrap(name) {
1058
+ return this._window.bootstrap?.[name] || null;
1059
+ }
1060
+ _portalRoot(trigger = this._element) {
1061
+ return ownerPortalRoot(findThemeOwner(trigger)) || this._document.body || this._document.documentElement;
1062
+ }
1063
+ // The bulk-actions bar's icon-only buttons carry data-bs-title instead of
1064
+ // a bare title attribute so Bootstrap's own Tooltip owns the hover/focus
1065
+ // affordance (matching the rest of Moo's icon-button contracts) rather
1066
+ // than the browser's unstyled, delayed native tooltip. Tooltip has no
1067
+ // Data API auto-init the way Dropdown does, so it needs this explicit,
1068
+ // one-time setup; the bar's markup is static, so it never needs re-init.
1069
+ // animation: false keeps show()/hide() synchronous -- with the default
1070
+ // CSS fade, a click on a tooltip's own dropdown-toggle button fires
1071
+ // "show.bs.dropdown" while the hover-triggered tooltip is still mid-fade,
1072
+ // so the hide() below (see show.bs.dropdown in _bindEvents) would find
1073
+ // it not yet fully shown and no-op, leaving it stuck open over the menu.
1074
+ _initBulkTooltips() {
1075
+ const Tooltip = this._bootstrap("Tooltip");
1076
+ if (!Tooltip) {
1077
+ return;
1078
+ }
1079
+ this._element.querySelectorAll('[data-datatable-bulk-actions] [data-bs-title]:not([data-bs-toggle="dropdown"])').forEach((trigger) => {
1080
+ this._tooltips.push(Tooltip.getOrCreateInstance(trigger, {
1081
+ animation: false,
1082
+ container: this._portalRoot(trigger)
1083
+ }));
1084
+ });
1085
+ }
1086
+ _hideBulkTooltips({ blur = false } = {}) {
1087
+ this._tooltips.forEach((tooltip) => {
1088
+ tooltip.hide();
1089
+ });
1090
+ if (!blur) {
1091
+ return;
1092
+ }
1093
+ const active = this._document.activeElement;
1094
+ if (active?.closest?.("[data-datatable-bulk-actions]")) {
1095
+ active.blur();
1096
+ }
1097
+ }
1098
+ // Auto mode remembers independent narrow/wide choices; toggle mode keeps
1099
+ // its existing viewport-independent preference.
1100
+ _initViewToggle() {
1101
+ const toggles = Array.from(this._element.querySelectorAll(".datatable-view-toggle"));
1102
+ if (!toggles.length) {
1103
+ return;
1104
+ }
1105
+ const inputs = toggles.flatMap((toggle) => Array.from(toggle.querySelectorAll("input")));
1106
+ const breakpoint = this._window.getComputedStyle(this._element).getPropertyValue("--moo-datatable-responsive-breakpoint").trim();
1107
+ const auto = this._element.hasAttribute("data-datatable-responsive-breakpoint");
1108
+ const media = auto && breakpoint ? this._window.matchMedia(`(min-width: ${breakpoint})`) : null;
1109
+ const preferences = /* @__PURE__ */ new Map();
1110
+ const storageKey = () => `moo-datatable-view:${this._element.id}${media ? media.matches ? ":wide" : ":narrow" : ""}`;
1111
+ const setView = (value, { persist = false } = {}) => {
1112
+ if (this._element.dataset.datatableView !== value) {
1113
+ const Dropdown = this._bootstrap("Dropdown");
1114
+ [...this._rowActionTriggers(), ...this._sortTriggers()].forEach((trigger) => {
1115
+ Dropdown?.getInstance(trigger)?.hide();
1116
+ });
1117
+ }
1118
+ this._element.dataset.datatableView = value;
1119
+ inputs.forEach((input) => {
1120
+ input.checked = input.value === value;
1121
+ });
1122
+ if (persist) {
1123
+ preferences.set(storageKey(), value);
1124
+ try {
1125
+ this._window.localStorage.setItem(storageKey(), value);
1126
+ } catch {
1127
+ }
1128
+ }
1129
+ };
1130
+ const restoreView = () => {
1131
+ let stored = preferences.get(storageKey());
1132
+ try {
1133
+ stored ?? (stored = this._window.localStorage.getItem(storageKey()));
1134
+ } catch {
1135
+ }
1136
+ setView(stored === "table" || stored === "cards" ? stored : media ? media.matches ? "table" : "cards" : this._element.dataset.datatableView);
1137
+ };
1138
+ if (!auto || media) {
1139
+ restoreView();
1140
+ } else {
1141
+ inputs.forEach((input) => {
1142
+ input.checked = false;
1143
+ });
1144
+ }
1145
+ if (media) this._listen(media, "change", restoreView);
1146
+ toggles.forEach((toggle) => {
1147
+ this._listen(toggle, "change", (event) => {
1148
+ const value = event.target.value;
1149
+ if (value !== "table" && value !== "cards") {
1150
+ return;
1151
+ }
1152
+ setView(value, { persist: true });
1153
+ });
1154
+ });
1155
+ }
1156
+ _readFacets(tr) {
1157
+ const facets = {};
1158
+ Array.from(tr.attributes).forEach((attr) => {
1159
+ const match = /^data-datatable-facet-(.+)$/.exec(attr.name);
1160
+ if (match) {
1161
+ facets[match[1]] = attr.value;
1162
+ }
1163
+ });
1164
+ return facets;
1165
+ }
1166
+ _readInitialFacetVisibility() {
1167
+ this._element.querySelectorAll("[data-datatable-facet]").forEach((facetRoot) => {
1168
+ const key = facetRoot.dataset.datatableFacet;
1169
+ if (key) {
1170
+ this._visibleFacets.set(key, !facetRoot.hidden);
1171
+ }
1172
+ });
1173
+ this._element.querySelectorAll("[data-datatable-filter-group]").forEach((group) => {
1174
+ const key = group.dataset.datatableFilterGroup;
1175
+ if (key && !this._visibleFacets.has(key)) {
1176
+ this._visibleFacets.set(key, false);
1177
+ }
1178
+ });
1179
+ this._element.querySelectorAll("[data-datatable-filter-option-key]").forEach((option) => {
1180
+ const key = option.dataset.datatableFilterOptionKey;
1181
+ if (key && !this._visibleFacets.has(key)) {
1182
+ this._visibleFacets.set(key, false);
1183
+ }
1184
+ });
1185
+ }
1186
+ _readInitialSort() {
1187
+ const th = this._element.querySelector("th[data-datatable-initial-sort][data-datatable-column]");
1188
+ const direction = th?.dataset.datatableInitialSort;
1189
+ if (direction !== "asc" && direction !== "desc") {
1190
+ return;
1191
+ }
1192
+ this._sortKey = th.dataset.datatableColumn || null;
1193
+ this._sortDirection = this._sortKey ? direction : null;
1194
+ }
1195
+ _trigger(name, detail = {}) {
1196
+ this._element.dispatchEvent(
1197
+ new this._window.CustomEvent(`${name}.moo.datatable`, {
1198
+ bubbles: true,
1199
+ detail
1200
+ })
1201
+ );
1202
+ }
1203
+ _sortValue(row, key) {
1204
+ const cell = row.element.querySelector(`:scope > [data-datatable-column="${key}"]`);
1205
+ if (!cell) {
1206
+ return "";
1207
+ }
1208
+ return normalize2(cell.dataset.datatableSortValue ?? cell.textContent);
1209
+ }
1210
+ _matchesFilters(row) {
1211
+ if (this._searchTerm && !row.search.includes(this._searchTerm)) {
1212
+ return false;
1213
+ }
1214
+ for (const [facetKey, selected] of this._facetSelections) {
1215
+ if (selected.size === 0) {
1216
+ continue;
1217
+ }
1218
+ if (!selected.has(row.facets[facetKey])) {
1219
+ return false;
1220
+ }
1221
+ }
1222
+ return true;
1223
+ }
1224
+ _visibleRows() {
1225
+ const filtered = this._rows.filter((row) => this._matchesFilters(row));
1226
+ if (this._sortKey) {
1227
+ const direction = this._sortDirection === "desc" ? -1 : 1;
1228
+ filtered.sort((a, b) => {
1229
+ const aValue = this._sortValue(a, this._sortKey);
1230
+ const bValue = this._sortValue(b, this._sortKey);
1231
+ if (aValue === bValue) {
1232
+ return a.index - b.index;
1233
+ }
1234
+ return aValue > bValue ? direction : -direction;
1235
+ });
1236
+ } else {
1237
+ filtered.sort((a, b) => a.index - b.index);
1238
+ }
1239
+ return filtered;
1240
+ }
1241
+ _pageCount(rowCount) {
1242
+ return Math.max(1, Math.ceil(rowCount / this._pageSize));
1243
+ }
1244
+ _render() {
1245
+ const filtered = this._visibleRows();
1246
+ const pageCount = this._pageCount(filtered.length);
1247
+ this._currentPage = Math.min(Math.max(1, this._currentPage), pageCount);
1248
+ const start = (this._currentPage - 1) * this._pageSize;
1249
+ const pageRows = filtered.slice(start, start + this._pageSize);
1250
+ const pageRowIds = new Set(pageRows.map((row) => row.element.id));
1251
+ this._rows.forEach((row) => {
1252
+ const isVisible = pageRowIds.has(row.element.id);
1253
+ row.element.hidden = !isVisible;
1254
+ if (row.cardElement) {
1255
+ row.cardElement.hidden = !isVisible;
1256
+ }
1257
+ });
1258
+ const currentOrder = Array.from(this._tbody.children).filter((element) => pageRowIds.has(element.id)).map((element) => element.id);
1259
+ const desiredOrder = pageRows.map((row) => row.element.id);
1260
+ const orderChanged = currentOrder.length !== desiredOrder.length || currentOrder.some((id, index) => id !== desiredOrder[index]);
1261
+ if (orderChanged) {
1262
+ pageRows.forEach((row) => {
1263
+ this._tbody.appendChild(row.element);
1264
+ if (this._cards && row.cardElement) {
1265
+ this._cards.appendChild(row.cardElement);
1266
+ }
1267
+ });
1268
+ }
1269
+ this._renderToolbarState();
1270
+ this._renderSortHeaders();
1271
+ this._renderSelection(pageRows);
1272
+ this._renderFrameState(filtered.length);
1273
+ this._renderEmptyState(filtered.length);
1274
+ this._renderPagination(filtered.length, pageCount);
1275
+ this._renderPageSize();
1276
+ this._renderBulkActions();
1277
+ }
1278
+ _renderToolbarState() {
1279
+ const hasSearch = Boolean(this._searchTerm);
1280
+ const hasFacets = Array.from(this._facetSelections.values()).some((set) => set.size > 0);
1281
+ const resetButton = this._element.querySelector("[data-datatable-reset]");
1282
+ if (resetButton) {
1283
+ resetButton.hidden = !(hasSearch || hasFacets);
1284
+ }
1285
+ this._element.querySelectorAll("[data-datatable-facet]").forEach((facetRoot) => {
1286
+ const key = facetRoot.dataset.datatableFacet;
1287
+ const selected = this._facetSelections.get(key) || /* @__PURE__ */ new Set();
1288
+ if (this._visibleFacets.has(key)) {
1289
+ facetRoot.hidden = !this._visibleFacets.get(key);
1290
+ }
1291
+ const trigger = facetRoot.querySelector(".datatable-facet-trigger");
1292
+ trigger?.classList.toggle("datatable-facet-trigger-active", selected.size > 0);
1293
+ const summary = facetRoot.querySelector("[data-datatable-facet-summary]");
1294
+ if (summary) {
1295
+ summary.hidden = selected.size === 0;
1296
+ summary.replaceChildren();
1297
+ if (selected.size > 2) {
1298
+ summary.appendChild(this._createFacetBadge(`${selected.size} selected`));
1299
+ } else {
1300
+ selected.forEach((value) => {
1301
+ const option = facetRoot.querySelector(`[data-datatable-facet-option="${value}"]`);
1302
+ const label = option?.querySelector("[data-datatable-facet-option-label]")?.textContent ?? value;
1303
+ summary.appendChild(this._createFacetBadge(label));
1304
+ });
1305
+ }
1306
+ }
1307
+ facetRoot.querySelectorAll("[data-datatable-facet-option]").forEach((option) => {
1308
+ const isSelected = selected.has(option.dataset.datatableFacetOption);
1309
+ option.classList.toggle("active", isSelected);
1310
+ option.setAttribute("aria-pressed", String(isSelected));
1311
+ });
1312
+ });
1313
+ this._element.querySelectorAll("[data-datatable-filter-toggle]").forEach((toggle) => {
1314
+ const key = toggle.dataset.datatableFilterToggle;
1315
+ const visible = this._visibleFacets.get(key) ?? true;
1316
+ toggle.classList.toggle("active", visible);
1317
+ toggle.setAttribute("aria-pressed", visible ? "true" : "false");
1318
+ });
1319
+ this._element.querySelectorAll("[data-datatable-filter-option]").forEach((option) => {
1320
+ const key = option.dataset.datatableFilterOptionKey;
1321
+ const selected = this._facetSelections.get(key) || /* @__PURE__ */ new Set();
1322
+ const active = selected.has(option.dataset.datatableFilterOption);
1323
+ option.classList.toggle("active", active);
1324
+ option.setAttribute("aria-pressed", active ? "true" : "false");
1325
+ });
1326
+ this._element.querySelectorAll("[data-datatable-filter-group-summary]").forEach((summary) => {
1327
+ const selected = this._facetSelections.get(summary.dataset.datatableFilterGroupSummary) || /* @__PURE__ */ new Set();
1328
+ summary.hidden = selected.size === 0;
1329
+ summary.textContent = selected.size > 0 ? `${selected.size} selected` : "";
1330
+ });
1331
+ }
1332
+ _createFacetBadge(text) {
1333
+ const badge = this._document.createElement("span");
1334
+ badge.className = "datatable-facet-badge";
1335
+ badge.textContent = text;
1336
+ return badge;
1337
+ }
1338
+ _renderSelection(pageRows) {
1339
+ const selectAllControls = Array.from(this._element.querySelectorAll("[data-datatable-select-all]"));
1340
+ pageRows.forEach((row) => {
1341
+ [row.element, row.cardElement].forEach((container) => {
1342
+ const checkbox = container?.querySelector("[data-datatable-select-row]");
1343
+ if (checkbox) {
1344
+ checkbox.checked = this._selectedIds.has(row.element.id);
1345
+ }
1346
+ container?.classList.toggle("datatable-row-selected", this._selectedIds.has(row.element.id));
1347
+ });
1348
+ });
1349
+ const total = pageRows.length;
1350
+ const checked = pageRows.filter((row) => this._selectedIds.has(row.element.id)).length;
1351
+ selectAllControls.forEach((control) => {
1352
+ control.checked = total > 0 && checked === total;
1353
+ control.indeterminate = checked > 0 && checked < total;
1354
+ });
1355
+ }
1356
+ _renderBulkActions() {
1357
+ const bar = this._element.querySelector("[data-datatable-bulk-actions]");
1358
+ if (!bar) {
1359
+ return;
1360
+ }
1361
+ bar.hidden = this._selectedIds.size === 0;
1362
+ const count = bar.querySelector("[data-datatable-bulk-count]");
1363
+ if (count) {
1364
+ count.textContent = String(this._selectedIds.size);
1365
+ }
1366
+ }
1367
+ _renderFrameState(totalRows) {
1368
+ const isEmpty = totalRows === 0;
1369
+ this._element.querySelectorAll(".datatable-frame, .datatable-card-frame").forEach((frame) => {
1370
+ frame.hidden = isEmpty;
1371
+ });
1372
+ }
1373
+ _renderEmptyState(totalRows) {
1374
+ const empty = this._element.querySelector("[data-datatable-empty]");
1375
+ if (!empty) {
1376
+ return;
1377
+ }
1378
+ const hasRows = this._rows.length > 0;
1379
+ empty.hidden = totalRows !== 0;
1380
+ empty.querySelector(".datatable-empty-title")?.replaceChildren(
1381
+ this._document.createTextNode(hasRows ? "No matching results" : "No rows to display")
1382
+ );
1383
+ const copy = empty.querySelector(".datatable-empty-copy");
1384
+ if (copy) {
1385
+ copy.hidden = !hasRows;
1386
+ }
1387
+ const reset = empty.querySelector("[data-datatable-empty-reset]");
1388
+ if (reset) {
1389
+ reset.hidden = !hasRows;
1390
+ }
1391
+ }
1392
+ _sortAriaValue(direction) {
1393
+ if (direction === "asc") {
1394
+ return "ascending";
1395
+ }
1396
+ if (direction === "desc") {
1397
+ return "descending";
1398
+ }
1399
+ return "none";
1400
+ }
1401
+ _renderSortHeaders() {
1402
+ this._element.querySelectorAll("th[data-datatable-column]").forEach((th) => {
1403
+ const trigger = th.querySelector("[data-datatable-sort-key]");
1404
+ if (!trigger) {
1405
+ delete th.dataset.datatableSortState;
1406
+ delete th.dataset.datatableSortDirection;
1407
+ th.removeAttribute("aria-sort");
1408
+ return;
1409
+ }
1410
+ const key = th.dataset.datatableColumn;
1411
+ const direction = key && key === this._sortKey ? this._sortDirection : "none";
1412
+ th.dataset.datatableSortState = direction || "none";
1413
+ th.setAttribute("aria-sort", this._sortAriaValue(direction));
1414
+ if (direction === "asc" || direction === "desc") {
1415
+ th.dataset.datatableSortDirection = direction;
1416
+ } else {
1417
+ delete th.dataset.datatableSortDirection;
1418
+ }
1419
+ });
1420
+ }
1421
+ _renderPagination(totalRows, pageCount) {
1422
+ const first = this._element.querySelector("[data-datatable-page-first]");
1423
+ const prev = this._element.querySelector("[data-datatable-page-prev]");
1424
+ const next = this._element.querySelector("[data-datatable-page-next]");
1425
+ const last = this._element.querySelector("[data-datatable-page-last]");
1426
+ const atStart = this._currentPage <= 1;
1427
+ const atEnd = this._currentPage >= pageCount;
1428
+ [first, prev].forEach((button) => this._setPageItemDisabled(button, atStart));
1429
+ [next, last].forEach((button) => this._setPageItemDisabled(button, atEnd));
1430
+ const summary = this._element.querySelector("[data-datatable-results-summary]");
1431
+ if (summary) {
1432
+ if (totalRows === 0) {
1433
+ summary.textContent = "No results";
1434
+ } else {
1435
+ const start = (this._currentPage - 1) * this._pageSize + 1;
1436
+ const end = Math.min(this._currentPage * this._pageSize, totalRows);
1437
+ summary.textContent = `Showing ${start}-${end} of ${totalRows}`;
1438
+ }
1439
+ }
1440
+ const template = this._element.querySelector("template[data-datatable-page-numbers]");
1441
+ if (!template) {
1442
+ return;
1443
+ }
1444
+ const pages = this._pageWindow(this._currentPage, pageCount);
1445
+ const existing = Array.from(
1446
+ template.parentNode.querySelectorAll(
1447
+ "[data-datatable-page-number], [data-datatable-page-ellipsis]"
1448
+ )
1449
+ );
1450
+ const existingWindow = existing.map(
1451
+ (node) => node.dataset.datatablePageEllipsis === void 0 ? Number(node.dataset.datatablePageNumber) : "\u2026"
1452
+ );
1453
+ const windowChanged = existingWindow.length !== pages.length || existingWindow.some((page, index) => page !== pages[index]);
1454
+ if (windowChanged) {
1455
+ existing.forEach((node) => node.remove());
1456
+ const fragment = this._document.createDocumentFragment();
1457
+ pages.forEach((page) => {
1458
+ const li = this._document.createElement("li");
1459
+ li.className = "page-item";
1460
+ if (page === "\u2026") {
1461
+ li.dataset.datatablePageEllipsis = "";
1462
+ li.innerHTML = '<span class="page-link">\u2026</span>';
1463
+ } else {
1464
+ li.dataset.datatablePageNumber = String(page);
1465
+ const button = this._document.createElement("button");
1466
+ button.type = "button";
1467
+ button.className = "page-link";
1468
+ button.textContent = String(page);
1469
+ button.setAttribute("aria-label", `Go to page ${page}`);
1470
+ button.dataset.datatablePageGo = String(page);
1471
+ li.appendChild(button);
1472
+ }
1473
+ fragment.appendChild(li);
1474
+ });
1475
+ template.parentNode.insertBefore(fragment, template);
1476
+ }
1477
+ template.parentNode.querySelectorAll("[data-datatable-page-number]").forEach((li) => {
1478
+ const isActive = Number(li.dataset.datatablePageNumber) === this._currentPage;
1479
+ li.classList.toggle("active", isActive);
1480
+ if (isActive) {
1481
+ li.setAttribute("aria-current", "page");
1482
+ } else {
1483
+ li.removeAttribute("aria-current");
1484
+ }
1485
+ });
1486
+ }
1487
+ _renderPageSize() {
1488
+ const currentSize = String(this._pageSize);
1489
+ const value = this._element.querySelector("[data-datatable-page-size-value]");
1490
+ if (value) {
1491
+ value.textContent = currentSize;
1492
+ }
1493
+ const select = this._element.querySelector("[data-datatable-page-size-select]");
1494
+ if (select) {
1495
+ select.value = currentSize;
1496
+ }
1497
+ this._element.querySelectorAll("[data-datatable-page-size-option]").forEach((option) => {
1498
+ const active = option.dataset.datatablePageSizeOption === currentSize;
1499
+ option.classList.toggle("active", active);
1500
+ option.setAttribute("aria-pressed", active ? "true" : "false");
1501
+ });
1502
+ }
1503
+ _setPageItemDisabled(button, disabled) {
1504
+ if (!button) {
1505
+ return;
1506
+ }
1507
+ button.disabled = disabled;
1508
+ button.closest(".page-item")?.classList.toggle("disabled", disabled);
1509
+ }
1510
+ _pageWindow(current, total) {
1511
+ if (total <= 7) {
1512
+ return Array.from({ length: total }, (_, index) => index + 1);
1513
+ }
1514
+ const pages = /* @__PURE__ */ new Set([1, total, current - 1, current, current + 1]);
1515
+ const sorted = Array.from(pages).filter((page) => page >= 1 && page <= total).sort((a, b) => a - b);
1516
+ const withEllipsis = [];
1517
+ sorted.forEach((page, index) => {
1518
+ if (index > 0 && page - sorted[index - 1] > 1) {
1519
+ withEllipsis.push("\u2026");
1520
+ }
1521
+ withEllipsis.push(page);
1522
+ });
1523
+ return withEllipsis;
1524
+ }
1525
+ _isColumnFixed(key) {
1526
+ return this._element.querySelector(`th[data-datatable-column="${key}"]`)?.hasAttribute("data-datatable-column-fixed") || false;
1527
+ }
1528
+ _setColumnVisible(key, visible, { syncViewToggle = true } = {}) {
1529
+ if (!visible && this._isColumnFixed(key)) {
1530
+ return;
1531
+ }
1532
+ this._element.querySelectorAll(`[data-datatable-column="${key}"], [data-datatable-detail-column="${key}"]`).forEach((cell) => {
1533
+ cell.classList.toggle("datatable-col-hidden", !visible);
1534
+ });
1535
+ if (syncViewToggle) {
1536
+ this._element.querySelectorAll(`[data-datatable-column-toggle="${key}"]`).forEach((toggle) => {
1537
+ toggle.classList.toggle("active", visible);
1538
+ toggle.setAttribute("aria-pressed", String(visible));
1539
+ });
1540
+ }
1541
+ }
1542
+ _setSort(key, direction) {
1543
+ const validDirection = direction === "asc" || direction === "desc" ? direction : null;
1544
+ this._sortKey = validDirection ? key : null;
1545
+ this._sortDirection = validDirection;
1546
+ this._render();
1547
+ this._trigger("sort", { key: this._sortKey, direction: this._sortDirection });
1548
+ }
1549
+ _handleSortAction(event) {
1550
+ const trigger = event.target.closest("[data-datatable-sort-action]");
1551
+ if (!trigger) {
1552
+ return;
1553
+ }
1554
+ const menu = trigger.closest(".dropdown-menu");
1555
+ const sort = menu?.closest(".datatable-sort") || this._reparentedSortMenus.get(menu)?.owner;
1556
+ if (!sort || !this._element.contains(sort)) {
1557
+ return;
1558
+ }
1559
+ const key = sort.querySelector("[data-datatable-sort-key]")?.dataset.datatableSortKey;
1560
+ if (!key) {
1561
+ return;
1562
+ }
1563
+ const action = trigger.dataset.datatableSortAction;
1564
+ if (action === "hide") {
1565
+ this._setColumnVisible(key, false);
1566
+ return;
1567
+ }
1568
+ this._setSort(key, action);
1569
+ }
1570
+ _handleFacetClick(event) {
1571
+ const option = event.target.closest("[data-datatable-facet-option]");
1572
+ const clear = event.target.closest("[data-datatable-facet-clear]");
1573
+ if (!option && !clear) {
1574
+ return;
1575
+ }
1576
+ const facetRoot = event.target.closest("[data-datatable-facet]");
1577
+ const key = facetRoot?.dataset.datatableFacet;
1578
+ if (!key) {
1579
+ return;
1580
+ }
1581
+ const selected = this._facetSelections.get(key) || /* @__PURE__ */ new Set();
1582
+ if (clear) {
1583
+ selected.clear();
1584
+ } else {
1585
+ const value = option.dataset.datatableFacetOption;
1586
+ if (selected.has(value)) {
1587
+ selected.delete(value);
1588
+ } else {
1589
+ selected.add(value);
1590
+ }
1591
+ }
1592
+ this._facetSelections.set(key, selected);
1593
+ if (this._filterMode === "picker" && selected.size === 0) {
1594
+ this._visibleFacets.set(key, false);
1595
+ }
1596
+ this._currentPage = 1;
1597
+ this._render();
1598
+ this._trigger("filter");
1599
+ }
1600
+ _handleFacetSearch(event) {
1601
+ const search = event.target.closest("[data-datatable-facet-search]");
1602
+ if (!search) {
1603
+ return;
1604
+ }
1605
+ const facetRoot = search.closest("[data-datatable-facet]");
1606
+ const query = normalize2(search.value);
1607
+ facetRoot?.querySelectorAll("[data-datatable-facet-option]").forEach((option) => {
1608
+ const label = normalize2(option.querySelector("[data-datatable-facet-option-label]")?.textContent || option.textContent);
1609
+ option.closest("li")?.classList.toggle("datatable-facet-search-hidden", query.length > 0 && !label.includes(query));
1610
+ });
1611
+ }
1612
+ // Bootstrap's own dropdown keyboard navigation moves focus between
1613
+ // .dropdown-item siblings, but it deliberately leaves focus alone while an
1614
+ // <input> has it (arrow keys inside a text field move the caret, not a
1615
+ // list). A facet's own search box needs an explicit bridge so ArrowDown
1616
+ // hands off to the first result instead of doing nothing.
1617
+ _handleFacetSearchKeydown(event) {
1618
+ if (event.key !== "ArrowDown") {
1619
+ return;
1620
+ }
1621
+ const search = event.target.closest("[data-datatable-facet-search]");
1622
+ if (!search) {
1623
+ return;
1624
+ }
1625
+ const facetRoot = search.closest("[data-datatable-facet]");
1626
+ const options = Array.from(facetRoot?.querySelectorAll("[data-datatable-facet-option]") || []);
1627
+ const firstVisible = options.find(
1628
+ (option) => !option.closest("li")?.classList.contains("datatable-facet-search-hidden")
1629
+ );
1630
+ if (firstVisible) {
1631
+ event.preventDefault();
1632
+ firstVisible.focus();
1633
+ }
1634
+ }
1635
+ _handleFilterOptionClick(event) {
1636
+ const option = event.target.closest("[data-datatable-filter-option]");
1637
+ if (!option) {
1638
+ return;
1639
+ }
1640
+ const key = option.dataset.datatableFilterOptionKey;
1641
+ const value = option.dataset.datatableFilterOption;
1642
+ if (!key || !this._visibleFacets.has(key)) {
1643
+ return;
1644
+ }
1645
+ this._visibleFacets.set(key, true);
1646
+ const selected = this._facetSelections.get(key) || /* @__PURE__ */ new Set();
1647
+ if (selected.has(value)) {
1648
+ selected.delete(value);
1649
+ } else {
1650
+ selected.add(value);
1651
+ }
1652
+ this._facetSelections.set(key, selected);
1653
+ if (selected.size === 0) {
1654
+ this._visibleFacets.set(key, false);
1655
+ }
1656
+ this._currentPage = 1;
1657
+ this._render();
1658
+ this._trigger("filter");
1659
+ }
1660
+ _showFilterPickerPanel(key = null) {
1661
+ this._element.querySelectorAll("[data-datatable-filter-panel]").forEach((panel) => {
1662
+ const isFacetList = panel.dataset.datatableFilterPanel === "facets";
1663
+ const isRequestedOptions = panel.dataset.datatableFilterPanelKey === key;
1664
+ panel.hidden = key ? !isRequestedOptions : !isFacetList;
1665
+ });
1666
+ this._element.querySelector(".datatable-search-filter-menu")?.setAttribute("data-datatable-filter-menu-panel", key ? "options" : "facets");
1667
+ }
1668
+ _handleFilterPickerClick(event) {
1669
+ const group = event.target.closest("[data-datatable-filter-group]");
1670
+ if (group) {
1671
+ const key = group.dataset.datatableFilterGroup;
1672
+ this._showFilterPickerPanel(key);
1673
+ const search = this._element.querySelector(`[data-datatable-filter-option-search="${key}"]`);
1674
+ const option = this._element.querySelector(`[data-datatable-filter-option-key="${key}"]`);
1675
+ (search || option)?.focus({ preventScroll: true });
1676
+ return;
1677
+ }
1678
+ if (event.target.closest("[data-datatable-filter-back]")) {
1679
+ this._showFilterPickerPanel();
1680
+ }
1681
+ }
1682
+ _handleFilterOptionSearch(event) {
1683
+ const search = event.target.closest("[data-datatable-filter-option-search]");
1684
+ if (!search) {
1685
+ return;
1686
+ }
1687
+ const key = search.dataset.datatableFilterOptionSearch;
1688
+ const query = normalize2(search.value);
1689
+ this._element.querySelectorAll(`[data-datatable-filter-option-key="${key}"]`).forEach((option) => {
1690
+ const label = normalize2(option.querySelector("[data-datatable-filter-option-label]")?.textContent || option.textContent);
1691
+ option.closest("li")?.classList.toggle("datatable-filter-option-search-hidden", query.length > 0 && !label.includes(query));
1692
+ });
1693
+ }
1694
+ // Same bridge as _handleFacetSearchKeydown, for the filter-picker's
1695
+ // drill-down option search box.
1696
+ _handleFilterOptionSearchKeydown(event) {
1697
+ if (event.key !== "ArrowDown") {
1698
+ return;
1699
+ }
1700
+ const search = event.target.closest("[data-datatable-filter-option-search]");
1701
+ if (!search) {
1702
+ return;
1703
+ }
1704
+ const key = search.dataset.datatableFilterOptionSearch;
1705
+ const options = Array.from(
1706
+ this._element.querySelectorAll(`[data-datatable-filter-option-key="${key}"]`)
1707
+ );
1708
+ const firstVisible = options.find(
1709
+ (option) => !option.closest("li")?.classList.contains("datatable-filter-option-search-hidden")
1710
+ );
1711
+ if (firstVisible) {
1712
+ event.preventDefault();
1713
+ firstVisible.focus();
1714
+ }
1715
+ }
1716
+ _handleFilterToggleClick(event) {
1717
+ const toggle = event.target.closest("[data-datatable-filter-toggle]");
1718
+ if (!toggle) {
1719
+ return;
1720
+ }
1721
+ const key = toggle.dataset.datatableFilterToggle;
1722
+ if (!key || !this._visibleFacets.has(key)) {
1723
+ return;
1724
+ }
1725
+ const nextVisible = !this._visibleFacets.get(key);
1726
+ this._visibleFacets.set(key, nextVisible);
1727
+ if (!nextVisible) {
1728
+ this._facetSelections.get(key)?.clear();
1729
+ this._currentPage = 1;
1730
+ this._trigger("filter");
1731
+ }
1732
+ this._render();
1733
+ }
1734
+ _handleColumnToggleClick(event) {
1735
+ const toggle = event.target.closest("[data-datatable-column-toggle]");
1736
+ if (!toggle) {
1737
+ return;
1738
+ }
1739
+ const key = toggle.dataset.datatableColumnToggle;
1740
+ const visible = toggle.getAttribute("aria-pressed") === "true";
1741
+ this._setColumnVisible(key, visible, { syncViewToggle: false });
1742
+ }
1743
+ _handleReset() {
1744
+ const search = this._element.querySelector("[data-datatable-search]");
1745
+ if (search) {
1746
+ search.value = "";
1747
+ }
1748
+ this._searchTerm = "";
1749
+ this._facetSelections.forEach((selected) => selected.clear());
1750
+ if (this._filterMode === "picker") {
1751
+ this._visibleFacets.forEach((value, key) => {
1752
+ this._visibleFacets.set(key, false);
1753
+ });
1754
+ }
1755
+ this._currentPage = 1;
1756
+ this._render();
1757
+ this._trigger("filter");
1758
+ }
1759
+ _handleSelectAll(event) {
1760
+ const checked = event.target.checked;
1761
+ const filtered = this._visibleRows();
1762
+ const start = (this._currentPage - 1) * this._pageSize;
1763
+ const pageRows = filtered.slice(start, start + this._pageSize);
1764
+ pageRows.forEach((row) => {
1765
+ if (checked) {
1766
+ this._selectedIds.add(row.element.id);
1767
+ } else {
1768
+ this._selectedIds.delete(row.element.id);
1769
+ }
1770
+ });
1771
+ this._render();
1772
+ this._trigger("select");
1773
+ }
1774
+ _handleSelectRow(event) {
1775
+ const checkbox = event.target.closest("[data-datatable-select-row]");
1776
+ if (!checkbox) {
1777
+ return;
1778
+ }
1779
+ const row = checkbox.closest("[data-datatable-row]");
1780
+ const card = checkbox.closest("[data-datatable-card]");
1781
+ const rowId = row?.id || card?.dataset.datatableCardFor;
1782
+ if (!rowId) {
1783
+ return;
1784
+ }
1785
+ if (checkbox.checked) {
1786
+ this._selectedIds.add(rowId);
1787
+ } else {
1788
+ this._selectedIds.delete(rowId);
1789
+ }
1790
+ this._render();
1791
+ this._trigger("select");
1792
+ }
1793
+ _handleBulkClick(event) {
1794
+ if (event.target.closest("[data-datatable-bulk-clear]")) {
1795
+ this._clearSelection();
1796
+ return;
1797
+ }
1798
+ const updateTrigger = event.target.closest("[data-datatable-bulk-update]");
1799
+ if (updateTrigger) {
1800
+ const key = updateTrigger.dataset.datatableBulkUpdate;
1801
+ const value = updateTrigger.dataset.datatableBulkUpdateValue;
1802
+ this._applyBulkUpdate(key, value, updateTrigger);
1803
+ return;
1804
+ }
1805
+ const actionTrigger = event.target.closest("[data-datatable-bulk-action]");
1806
+ if (actionTrigger) {
1807
+ const action = actionTrigger.dataset.datatableBulkAction;
1808
+ if (action === "delete") {
1809
+ this._applyBulkDelete();
1810
+ return;
1811
+ }
1812
+ this._trigger(`bulk-${action}`, { ids: this.getSelectedIds() });
1813
+ }
1814
+ }
1815
+ _clearSelection() {
1816
+ this._hideBulkTooltips({ blur: true });
1817
+ this._selectedIds.clear();
1818
+ this._render();
1819
+ this._trigger("select");
1820
+ }
1821
+ _applyBulkUpdate(key, value, trigger) {
1822
+ const icon = trigger.querySelector("svg")?.cloneNode(true);
1823
+ const label = trigger.querySelector("span")?.textContent?.trim() ?? value;
1824
+ this._selectedIds.forEach((id) => {
1825
+ const row = this._rows.find((candidate) => candidate.element.id === id);
1826
+ if (!row) {
1827
+ return;
1828
+ }
1829
+ row.element.setAttribute(`data-datatable-facet-${key}`, value);
1830
+ row.facets[key] = value;
1831
+ const cell = row.element.querySelector(`[data-datatable-column="${key}"]`);
1832
+ if (!cell) {
1833
+ return;
1834
+ }
1835
+ const labelTarget = cell.querySelector("[data-datatable-cell-label]");
1836
+ if (labelTarget) {
1837
+ labelTarget.textContent = label;
1838
+ } else {
1839
+ cell.textContent = label;
1840
+ }
1841
+ const currentIcon = cell.querySelector("svg");
1842
+ if (currentIcon && icon) {
1843
+ currentIcon.replaceWith(icon.cloneNode(true));
1844
+ }
1845
+ const cardValue = row.cardElement?.querySelector(`[data-datatable-detail-column="${key}"] .datatable-card-value`);
1846
+ if (cardValue) {
1847
+ cardValue.innerHTML = cell.innerHTML;
1848
+ }
1849
+ });
1850
+ this._render();
1851
+ this._trigger("bulk-update", { key, value, ids: this.getSelectedIds() });
1852
+ }
1853
+ _applyBulkDelete() {
1854
+ const ids = this.getSelectedIds();
1855
+ ids.forEach((id) => {
1856
+ const index = this._rows.findIndex((row) => row.element.id === id);
1857
+ if (index !== -1) {
1858
+ this._rows[index].element.remove();
1859
+ this._rows[index].cardElement?.remove();
1860
+ this._rows.splice(index, 1);
1861
+ }
1862
+ });
1863
+ this._selectedIds.clear();
1864
+ this._render();
1865
+ this._trigger("bulk-delete", { ids });
1866
+ }
1867
+ _handlePageClick(event) {
1868
+ const goButton = event.target.closest("[data-datatable-page-go]");
1869
+ if (goButton) {
1870
+ this._currentPage = Number(goButton.dataset.datatablePageGo);
1871
+ this._render();
1872
+ return;
1873
+ }
1874
+ if (event.target.closest("[data-datatable-page-first]")) {
1875
+ this._currentPage = 1;
1876
+ this._render();
1877
+ return;
1878
+ }
1879
+ if (event.target.closest("[data-datatable-page-prev]")) {
1880
+ this._currentPage -= 1;
1881
+ this._render();
1882
+ return;
1883
+ }
1884
+ if (event.target.closest("[data-datatable-page-next]")) {
1885
+ this._currentPage += 1;
1886
+ this._render();
1887
+ return;
1888
+ }
1889
+ if (event.target.closest("[data-datatable-page-last]")) {
1890
+ this._currentPage = this._pageCount(this._visibleRows().length);
1891
+ this._render();
1892
+ }
1893
+ }
1894
+ _setPageSize(value) {
1895
+ const nextPageSize = Number(value);
1896
+ if (!Number.isFinite(nextPageSize) || nextPageSize <= 0) {
1897
+ return;
1898
+ }
1899
+ this._pageSize = nextPageSize;
1900
+ this._element.dataset.datatablePageSize = String(nextPageSize);
1901
+ this._currentPage = 1;
1902
+ this._render();
1903
+ }
1904
+ _bindEvents() {
1905
+ const search = this._element.querySelector("[data-datatable-search]");
1906
+ this._listen(search, "input", (event) => {
1907
+ this._searchTerm = normalize2(event.target.value);
1908
+ this._currentPage = 1;
1909
+ this._render();
1910
+ this._trigger("filter");
1911
+ });
1912
+ this._listen(search, "keydown", (event) => {
1913
+ if (event.key !== "Enter") {
1914
+ return;
1915
+ }
1916
+ event.preventDefault();
1917
+ const trigger = this._element.querySelector("[data-datatable-filter-menu-trigger]");
1918
+ const Dropdown = this._bootstrap("Dropdown");
1919
+ if (trigger && Dropdown) {
1920
+ Dropdown.getOrCreateInstance(trigger).hide();
1921
+ }
1922
+ });
1923
+ this._listen(this._element, "click", (event) => {
1924
+ this._handleFilterPickerClick(event);
1925
+ this._handleFilterOptionClick(event);
1926
+ this._handleFilterToggleClick(event);
1927
+ this._handleFacetClick(event);
1928
+ this._handleColumnToggleClick(event);
1929
+ this._handlePageClick(event);
1930
+ this._handleBulkClick(event);
1931
+ if (event.target.closest("[data-datatable-empty-reset]")) {
1932
+ this._handleReset();
1933
+ }
1934
+ if (event.target.closest("[data-datatable-select-row]")) {
1935
+ this._handleSelectRow(event);
1936
+ }
1937
+ });
1938
+ this._listen(this._document, "click", (event) => this._handleSortAction(event));
1939
+ this._listen(this._document, "keydown", (event) => {
1940
+ if (event.key === "Escape" && this._selectedIds.size > 0) {
1941
+ this._clearSelection();
1942
+ }
1943
+ });
1944
+ this._listen(this._element, "show.bs.dropdown", (event) => {
1945
+ this._reparentRowActionMenu(event.target);
1946
+ this._reparentSortMenu(event.target);
1947
+ const tooltip = this._bootstrap("Tooltip")?.getInstance(event.target);
1948
+ tooltip?.hide();
1949
+ tooltip?.disable();
1950
+ });
1951
+ this._listen(this._element, "hidden.bs.dropdown", (event) => {
1952
+ this._restoreRowActionMenuForTrigger(event.target);
1953
+ this._restoreSortMenuForTrigger(event.target);
1954
+ this._bootstrap("Tooltip")?.getInstance(event.target)?.enable();
1955
+ if (event.target.matches("[data-datatable-filter-menu-trigger]")) {
1956
+ this._showFilterPickerPanel();
1957
+ }
1958
+ });
1959
+ this._element.querySelectorAll("[data-datatable-filter-option-search]").forEach((searchInput) => {
1960
+ this._listen(searchInput, "input", (event) => this._handleFilterOptionSearch(event));
1961
+ this._listen(searchInput, "keydown", (event) => this._handleFilterOptionSearchKeydown(event));
1962
+ });
1963
+ this._element.querySelectorAll("[data-datatable-facet-search]").forEach((searchInput) => {
1964
+ this._listen(searchInput, "input", (event) => this._handleFacetSearch(event));
1965
+ this._listen(searchInput, "keydown", (event) => this._handleFacetSearchKeydown(event));
1966
+ });
1967
+ const resetButton = this._element.querySelector("[data-datatable-reset]");
1968
+ this._listen(resetButton, "click", () => this._handleReset());
1969
+ this._element.querySelectorAll("[data-datatable-select-all]").forEach((selectAll) => {
1970
+ this._listen(selectAll, "change", (event) => this._handleSelectAll(event));
1971
+ });
1972
+ const pageSizeSelect = this._element.querySelector("[data-datatable-page-size-select]");
1973
+ this._listen(pageSizeSelect, "change", (event) => this._setPageSize(event.target.value));
1974
+ this._element.querySelectorAll("[data-datatable-page-size-option]").forEach((option) => {
1975
+ this._listen(option, "click", () => {
1976
+ this._setPageSize(option.dataset.datatablePageSizeOption);
1977
+ });
1978
+ });
1979
+ }
1980
+ };
1981
+
1982
+ // src/js/components/datepicker.js
1983
+ var datepickerInstances = /* @__PURE__ */ new WeakMap();
1984
+ var calendarInstances = /* @__PURE__ */ new WeakMap();
1985
+ var rangePickerInstances = /* @__PURE__ */ new WeakMap();
1986
+ var DEFAULT_LOCALE = "en";
1987
+ var DAY_MS = 24 * 60 * 60 * 1e3;
1988
+ var WEEKDAY_BASE_SUNDAY = new Date(2026, 7, 16);
1989
+ var POPOVER_VIEWPORT_PADDING = 8;
1990
+ function isElement(element, selector) {
1991
+ return element?.nodeType === 1 && Boolean(element.matches?.(selector));
1992
+ }
1993
+ function addListener(listeners, target, type, handler, options) {
1994
+ if (!target) return;
1995
+ target.addEventListener(type, handler, options);
1996
+ listeners.push({ target, type, handler, options });
1997
+ }
1998
+ function removeListeners(listeners) {
1999
+ listeners.forEach(({ target, type, handler, options }) => {
2000
+ target.removeEventListener(type, handler, options);
2001
+ });
2002
+ listeners.length = 0;
2003
+ }
2004
+ function parseDate(value) {
2005
+ if (!value) return null;
2006
+ if (value instanceof Date) {
2007
+ if (Number.isNaN(value.getTime())) return null;
2008
+ return new Date(value.getFullYear(), value.getMonth(), value.getDate());
2009
+ }
2010
+ if (typeof value === "number") {
2011
+ return parseDate(new Date(value));
2012
+ }
2013
+ const text = String(value).trim();
2014
+ const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
2015
+ if (iso) {
2016
+ const date = new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));
2017
+ if (date.getFullYear() === Number(iso[1]) && date.getMonth() === Number(iso[2]) - 1 && date.getDate() === Number(iso[3])) {
2018
+ return date;
2019
+ }
2020
+ return null;
2021
+ }
2022
+ return null;
2023
+ }
2024
+ function pad(value) {
2025
+ return String(value).padStart(2, "0");
2026
+ }
2027
+ function toIso(date) {
2028
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
2029
+ }
2030
+ function compareDates(left, right) {
2031
+ if (!left || !right) return 0;
2032
+ return left.getTime() - right.getTime();
2033
+ }
2034
+ function sameDate(left, right) {
2035
+ return Boolean(left && right && toIso(left) === toIso(right));
2036
+ }
2037
+ function addDays(date, amount) {
2038
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
2039
+ }
2040
+ function addMonths(date, amount) {
2041
+ const targetStart = new Date(date.getFullYear(), date.getMonth() + amount, 1);
2042
+ const targetDay = Math.min(
2043
+ date.getDate(),
2044
+ daysInMonth(targetStart.getFullYear(), targetStart.getMonth())
2045
+ );
2046
+ return new Date(targetStart.getFullYear(), targetStart.getMonth(), targetDay);
2047
+ }
2048
+ function firstDayOfWeek(locale = DEFAULT_LOCALE) {
2049
+ try {
2050
+ if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
2051
+ const intlLocale = new Intl.Locale(locale || DEFAULT_LOCALE);
2052
+ const weekInfo = intlLocale.weekInfo || intlLocale.getWeekInfo?.();
2053
+ if (Number.isInteger(weekInfo?.firstDay)) {
2054
+ return weekInfo.firstDay % 7;
2055
+ }
2056
+ }
2057
+ } catch {
2058
+ }
2059
+ return 0;
2060
+ }
2061
+ function weekdayDates(locale = DEFAULT_LOCALE) {
2062
+ const firstDay = firstDayOfWeek(locale);
2063
+ return Array.from({ length: 7 }, (_, offset) => addDays(WEEKDAY_BASE_SUNDAY, (firstDay + offset) % 7));
2064
+ }
2065
+ function startOfMonth(date) {
2066
+ return new Date(date.getFullYear(), date.getMonth(), 1);
2067
+ }
2068
+ function daysInMonth(year, month) {
2069
+ return new Date(year, month + 1, 0).getDate();
2070
+ }
2071
+ function limitToRange(date, minDate, maxDate) {
2072
+ if (minDate && compareDates(date, minDate) < 0) return new Date(minDate);
2073
+ if (maxDate && compareDates(date, maxDate) > 0) return new Date(maxDate);
2074
+ return date;
2075
+ }
2076
+ function parseDateList(value) {
2077
+ if (Array.isArray(value)) {
2078
+ return value.map(parseDate).filter(Boolean).map(toIso);
2079
+ }
2080
+ if (!value) return [];
2081
+ const text = String(value).trim();
2082
+ if (!text) return [];
2083
+ try {
2084
+ const parsed = JSON.parse(text);
2085
+ if (Array.isArray(parsed)) {
2086
+ return parsed.map(parseDate).filter(Boolean).map(toIso);
2087
+ }
2088
+ } catch {
2089
+ }
2090
+ return text.split(",").map(parseDate).filter(Boolean).map(toIso);
2091
+ }
2092
+ function formatDisplay(date, locale = DEFAULT_LOCALE) {
2093
+ if (!date) return "";
2094
+ return new Intl.DateTimeFormat(locale || DEFAULT_LOCALE, {
2095
+ month: "short",
2096
+ day: "numeric",
2097
+ year: "numeric"
2098
+ }).format(date);
2099
+ }
2100
+ function formatMonthYear(date, locale = DEFAULT_LOCALE) {
2101
+ return new Intl.DateTimeFormat(locale || DEFAULT_LOCALE, {
2102
+ month: "long",
2103
+ year: "numeric"
2104
+ }).format(date);
2105
+ }
2106
+ function formatReturn(date, format, locale = DEFAULT_LOCALE) {
2107
+ if (!date) return void 0;
2108
+ if (format === "yyyy-mm-dd") return toIso(date);
2109
+ if (format) return formatDisplay(date, locale);
2110
+ return new Date(date);
2111
+ }
2112
+ function readDataConfig(element, config = {}) {
2113
+ const dataset = element.dataset || {};
2114
+ const locale = config.locale || dataset.datepickerLocale || dataset.calendarLocale || DEFAULT_LOCALE;
2115
+ const minDate = parseDate(config.minDate || dataset.calendarMinDate || dataset.datepickerMinDate);
2116
+ const maxDate = parseDate(config.maxDate || dataset.calendarMaxDate || dataset.datepickerMaxDate);
2117
+ return {
2118
+ locale,
2119
+ mode: config.mode || dataset.calendarMode || dataset.datepickerMode || "single",
2120
+ placeholder: config.placeholder || dataset.datepickerPlaceholder || "Pick a date",
2121
+ value: config.value || dataset.calendarValue || dataset.datepickerValue || "",
2122
+ startValue: config.startValue || dataset.calendarStartValue || dataset.datepickerStartValue || "",
2123
+ endValue: config.endValue || dataset.calendarEndValue || dataset.datepickerEndValue || "",
2124
+ minDate,
2125
+ maxDate,
2126
+ disabledDates: parseDateList(config.disabledDates || dataset.calendarDisabledDates || dataset.datepickerDisabledDates),
2127
+ captionLayout: config.captionLayout || dataset.calendarCaptionLayout || "",
2128
+ showPresets: config.showPresets ?? (dataset.calendarShowPresets === "true" || dataset.datepickerShowPresets === "true")
2129
+ };
2130
+ }
2131
+ function dispatch(element, window2, name, detail = {}, cancelable = false) {
2132
+ return element.dispatchEvent(
2133
+ new window2.CustomEvent(name, {
2134
+ bubbles: true,
2135
+ cancelable,
2136
+ detail
2137
+ })
2138
+ );
2139
+ }
2140
+ function setHiddenValue(input, value) {
2141
+ if (!input) return;
2142
+ input.value = value || "";
2143
+ }
2144
+ function setValidationValue(input, value) {
2145
+ if (!input) return;
2146
+ input.value = value || "";
2147
+ }
2148
+ function setDatepickerInvalid(element, trigger, invalid) {
2149
+ if (invalid) {
2150
+ element.dataset.invalid = "true";
2151
+ trigger.classList.add("is-invalid");
2152
+ trigger.setAttribute("aria-invalid", "true");
2153
+ return;
2154
+ }
2155
+ delete element.dataset.invalid;
2156
+ trigger.classList.remove("is-invalid");
2157
+ trigger.removeAttribute("aria-invalid");
2158
+ }
2159
+ function syncDatepickerValidation(element, trigger, validationInput) {
2160
+ if (validationInput?.required && validationInput.checkValidity()) {
2161
+ setDatepickerInvalid(element, trigger, false);
2162
+ }
2163
+ }
2164
+ function clamp(value, min, max) {
2165
+ if (max < min) return min;
2166
+ return Math.min(Math.max(value, min), max);
2167
+ }
2168
+ function cssPixelValue(element, property, fallback) {
2169
+ const view = element?.ownerDocument?.defaultView || window;
2170
+ const value = element ? view.getComputedStyle(element).getPropertyValue(property).trim() : "";
2171
+ const parsed = Number.parseFloat(value);
2172
+ if (!Number.isFinite(parsed)) return fallback;
2173
+ if (value.endsWith("rem")) {
2174
+ const root = element.ownerDocument.documentElement;
2175
+ const fontSize = Number.parseFloat(view.getComputedStyle(root).fontSize);
2176
+ return Number.isFinite(fontSize) ? parsed * fontSize : fallback;
2177
+ }
2178
+ if (value.endsWith("em")) {
2179
+ const fontSize = Number.parseFloat(view.getComputedStyle(element).fontSize);
2180
+ return Number.isFinite(fontSize) ? parsed * fontSize : fallback;
2181
+ }
2182
+ return Number.isFinite(parsed) ? parsed : fallback;
2183
+ }
2184
+ function datepickerContains(instance, target) {
2185
+ return Boolean(
2186
+ target && (instance._element.contains(target) || instance._popover.contains(target))
2187
+ );
2188
+ }
2189
+ function syncPortaledPopoverContext(instance) {
2190
+ const owner = findThemeOwner(instance._trigger);
2191
+ const theme = owner?.getAttribute("data-bs-theme") || null;
2192
+ const direction = instance._document.defaultView?.getComputedStyle(
2193
+ instance._trigger
2194
+ ).direction;
2195
+ if (theme) {
2196
+ instance._popover.setAttribute("data-bs-theme", theme);
2197
+ } else {
2198
+ instance._popover.removeAttribute("data-bs-theme");
2199
+ }
2200
+ if (direction === "ltr" || direction === "rtl") {
2201
+ instance._popover.setAttribute("dir", direction);
2202
+ } else {
2203
+ instance._popover.removeAttribute("dir");
2204
+ }
2205
+ }
2206
+ function portalDatepickerPopover(instance) {
2207
+ const popover = instance._popover;
2208
+ const owner = findThemeOwner(instance._trigger);
2209
+ const portalRoot = ownerPortalRoot(owner) || instance._document.body || instance._document.documentElement;
2210
+ if (!portalRoot) {
2211
+ return;
2212
+ }
2213
+ if (instance._popoverPortal?.portal === portalRoot && popover.parentElement === portalRoot) {
2214
+ syncPortaledPopoverContext(instance);
2215
+ return;
2216
+ }
2217
+ restoreDatepickerPopover(instance);
2218
+ instance._popoverPortal = {
2219
+ parent: popover.parentNode,
2220
+ nextSibling: popover.nextSibling,
2221
+ owner,
2222
+ portal: portalRoot,
2223
+ hadTheme: popover.hasAttribute("data-bs-theme"),
2224
+ theme: popover.getAttribute("data-bs-theme"),
2225
+ hadDirection: popover.hasAttribute("dir"),
2226
+ direction: popover.getAttribute("dir")
2227
+ };
2228
+ portalRoot.appendChild(popover);
2229
+ syncPortaledPopoverContext(instance);
2230
+ }
2231
+ function restoreDatepickerPopover(instance) {
2232
+ const portal = instance._popoverPortal;
2233
+ if (!portal) return;
2234
+ const popover = instance._popover;
2235
+ if (portal.owner?.isConnected === false) {
2236
+ popover.remove();
2237
+ } else if (portal.parent?.isConnected) {
2238
+ if (portal.nextSibling?.parentNode === portal.parent) {
2239
+ portal.parent.insertBefore(popover, portal.nextSibling);
2240
+ } else {
2241
+ portal.parent.appendChild(popover);
2242
+ }
2243
+ } else {
2244
+ popover.remove();
2245
+ }
2246
+ if (portal.hadTheme) {
2247
+ popover.setAttribute("data-bs-theme", portal.theme);
2248
+ } else {
2249
+ popover.removeAttribute("data-bs-theme");
2250
+ }
2251
+ if (portal.hadDirection) {
2252
+ popover.setAttribute("dir", portal.direction);
2253
+ } else {
2254
+ popover.removeAttribute("dir");
2255
+ }
2256
+ instance._popoverPortal = null;
2257
+ }
2258
+ function positionDatepickerPopover(trigger, popover, root, window2) {
2259
+ const offset = cssPixelValue(root, "--moo-datepicker-popover-offset", 6);
2260
+ const direction = getComputedStyle(trigger).direction;
2261
+ popover.style.position = "fixed";
2262
+ popover.style.inset = "auto";
2263
+ popover.style.right = "auto";
2264
+ popover.style.bottom = "auto";
2265
+ popover.style.maxHeight = "";
2266
+ popover.style.overflowY = "";
2267
+ const triggerRect = trigger.getBoundingClientRect();
2268
+ const popoverRect = popover.getBoundingClientRect();
2269
+ const viewportWidth = window2.innerWidth;
2270
+ const viewportHeight = window2.innerHeight;
2271
+ const preferredLeft = direction === "rtl" ? triggerRect.right - popoverRect.width : triggerRect.left;
2272
+ const left = clamp(
2273
+ preferredLeft,
2274
+ POPOVER_VIEWPORT_PADDING,
2275
+ viewportWidth - popoverRect.width - POPOVER_VIEWPORT_PADDING
2276
+ );
2277
+ const spaceBelow = triggerRect.bottom + offset <= viewportHeight ? viewportHeight - triggerRect.bottom - offset - POPOVER_VIEWPORT_PADDING : 0;
2278
+ const spaceAbove = triggerRect.top - offset - POPOVER_VIEWPORT_PADDING;
2279
+ const placeAbove = popoverRect.height > spaceBelow && spaceAbove > spaceBelow;
2280
+ const placement = placeAbove ? "top" : "bottom";
2281
+ const availableHeight = Math.max(
2282
+ 0,
2283
+ placeAbove ? spaceAbove : spaceBelow
2284
+ );
2285
+ const renderedHeight = Math.min(popoverRect.height, availableHeight);
2286
+ const top = placeAbove ? triggerRect.top - offset - renderedHeight : triggerRect.bottom + offset;
2287
+ popover.style.left = `${Math.round(left)}px`;
2288
+ popover.style.top = `${Math.round(clamp(
2289
+ top,
2290
+ POPOVER_VIEWPORT_PADDING,
2291
+ viewportHeight - renderedHeight - POPOVER_VIEWPORT_PADDING
2292
+ ))}px`;
2293
+ if (availableHeight > 0) {
2294
+ popover.style.maxHeight = `${Math.floor(availableHeight)}px`;
2295
+ popover.style.overflowY = "auto";
2296
+ }
2297
+ popover.dataset.datepickerPlacement = placement;
2298
+ }
2299
+ function clearDatepickerPopoverPosition(popover) {
2300
+ popover.style.position = "";
2301
+ popover.style.inset = "";
2302
+ popover.style.right = "";
2303
+ popover.style.bottom = "";
2304
+ popover.style.left = "";
2305
+ popover.style.top = "";
2306
+ popover.style.maxHeight = "";
2307
+ popover.style.overflowY = "";
2308
+ delete popover.dataset.datepickerPlacement;
2309
+ }
2310
+ function startDatepickerPopoverPositioning(instance) {
2311
+ removeListeners(instance._floatingListeners);
2312
+ portalDatepickerPopover(instance);
2313
+ const update = () => {
2314
+ if (instance.isOpen()) {
2315
+ positionDatepickerPopover(
2316
+ instance._trigger,
2317
+ instance._popover,
2318
+ instance._element,
2319
+ instance._window
2320
+ );
2321
+ }
2322
+ };
2323
+ instance._positionPopover = update;
2324
+ update();
2325
+ addListener(instance._floatingListeners, instance._window, "resize", update);
2326
+ addListener(instance._floatingListeners, instance._window, "scroll", update, true);
2327
+ addListener(instance._floatingListeners, instance._document, "scroll", update, true);
2328
+ }
2329
+ function stopDatepickerPopoverPositioning(instance) {
2330
+ removeListeners(instance._floatingListeners);
2331
+ instance._positionPopover = null;
2332
+ clearDatepickerPopoverPosition(instance._popover);
2333
+ restoreDatepickerPopover(instance);
2334
+ }
2335
+ function monthName(monthIndex, locale) {
2336
+ return new Intl.DateTimeFormat(locale || DEFAULT_LOCALE, { month: "short" }).format(new Date(2026, monthIndex, 1));
2337
+ }
2338
+ function yearOptions(minDate, maxDate, viewDate) {
2339
+ const min = minDate ? minDate.getFullYear() : viewDate.getFullYear() - 100;
2340
+ const max = maxDate ? maxDate.getFullYear() : viewDate.getFullYear() + 20;
2341
+ const years = [];
2342
+ for (let year = min; year <= max; year += 1) {
2343
+ years.push(year);
2344
+ }
2345
+ return years;
2346
+ }
2347
+ function createIcon(document2, glyph, className = "") {
2348
+ const span = document2.createElement("span");
2349
+ span.className = className;
2350
+ span.setAttribute("aria-hidden", "true");
2351
+ span.textContent = glyph;
2352
+ return span;
2353
+ }
2354
+ var MooCalendar = class _MooCalendar {
2355
+ static getInstance(element) {
2356
+ return element?.nodeType === 1 ? calendarInstances.get(element) || null : null;
2357
+ }
2358
+ static getOrCreateInstance(element, config = {}) {
2359
+ return _MooCalendar.getInstance(element) || new _MooCalendar(element, config);
2360
+ }
2361
+ constructor(element, config = {}) {
2362
+ if (!isElement(element, "[data-calendar]")) {
2363
+ throw new TypeError("MooCalendar requires a [data-calendar] root element.");
2364
+ }
2365
+ const existing = calendarInstances.get(element);
2366
+ if (existing) return existing;
2367
+ this._element = element;
2368
+ this._document = element.ownerDocument;
2369
+ this._window = this._document.defaultView || window;
2370
+ this._listeners = [];
2371
+ this._disposed = false;
2372
+ this._config = readDataConfig(element, config);
2373
+ this._mode = this._config.mode === "range" ? "range" : "single";
2374
+ this._disabledDates = new Set(this._config.disabledDates);
2375
+ this._selectedDate = parseDate(this._config.value);
2376
+ this._rangeStart = parseDate(this._config.startValue);
2377
+ this._rangeEnd = parseDate(this._config.endValue);
2378
+ this._activeDate = this._nearestEnabledDate(
2379
+ this._rangeStart || this._selectedDate || /* @__PURE__ */ new Date()
2380
+ );
2381
+ this._viewDate = startOfMonth(this._activeDate);
2382
+ this._captionId = this._element.id ? `${this._element.id}-caption` : `moo-calendar-caption-${Math.random().toString(36).slice(2)}`;
2383
+ this._labelId = this._element.id ? `${this._element.id}-label` : `moo-calendar-label-${Math.random().toString(36).slice(2)}`;
2384
+ calendarInstances.set(element, this);
2385
+ this._bindEvents();
2386
+ this._render();
2387
+ }
2388
+ dispose() {
2389
+ if (this._disposed) return;
2390
+ this._disposed = true;
2391
+ removeListeners(this._listeners);
2392
+ this._element.replaceChildren();
2393
+ if (calendarInstances.get(this._element) === this) {
2394
+ calendarInstances.delete(this._element);
2395
+ }
2396
+ }
2397
+ getDate(format = void 0) {
2398
+ if (this._mode === "range") {
2399
+ return this.getDates(format);
2400
+ }
2401
+ return formatReturn(this._selectedDate, format, this._config.locale);
2402
+ }
2403
+ getDates(format = void 0) {
2404
+ return [
2405
+ formatReturn(this._rangeStart, format, this._config.locale),
2406
+ formatReturn(this._rangeEnd, format, this._config.locale)
2407
+ ];
2408
+ }
2409
+ setDate(date, options = {}) {
2410
+ if (this._mode !== "single") return false;
2411
+ const parsed = parseDate(date);
2412
+ if (!parsed || this._isDisabled(parsed)) return false;
2413
+ this._selectedDate = parsed;
2414
+ this._activeDate = parsed;
2415
+ this._viewDate = startOfMonth(parsed);
2416
+ this._render();
2417
+ if (options.emit !== false) {
2418
+ this._emitChange(true);
2419
+ }
2420
+ return true;
2421
+ }
2422
+ setDates(startDate, endDate, options = {}) {
2423
+ if (this._mode !== "range") return false;
2424
+ const start = parseDate(startDate);
2425
+ const end = parseDate(endDate);
2426
+ if (start && this._isDisabled(start)) return false;
2427
+ if (end && this._isDisabled(end)) return false;
2428
+ this._rangeStart = start;
2429
+ this._rangeEnd = end;
2430
+ if (this._rangeStart && this._rangeEnd && compareDates(this._rangeStart, this._rangeEnd) > 0) {
2431
+ [this._rangeStart, this._rangeEnd] = [this._rangeEnd, this._rangeStart];
2432
+ }
2433
+ this._activeDate = this._rangeEnd || this._rangeStart || this._activeDate;
2434
+ this._viewDate = startOfMonth(this._activeDate);
2435
+ this._render();
2436
+ if (options.emit !== false) {
2437
+ this._emitChange(Boolean(this._rangeStart && this._rangeEnd));
2438
+ }
2439
+ return true;
2440
+ }
2441
+ clear(options = {}) {
2442
+ this._selectedDate = null;
2443
+ this._rangeStart = null;
2444
+ this._rangeEnd = null;
2445
+ this._render();
2446
+ if (options.emit !== false) {
2447
+ this._emitChange(false);
2448
+ }
2449
+ }
2450
+ focusActiveDay() {
2451
+ this._dayButton(this._activeDate)?.focus();
2452
+ }
2453
+ _bindEvents() {
2454
+ addListener(this._listeners, this._element, "click", (event) => {
2455
+ const target = event.target;
2456
+ const day = target.closest?.("[data-calendar-day]");
2457
+ const prev = target.closest?.("[data-calendar-prev]");
2458
+ const next = target.closest?.("[data-calendar-next]");
2459
+ const preset = target.closest?.("[data-calendar-preset]");
2460
+ if (prev) {
2461
+ this._changeMonth(-1, true);
2462
+ } else if (next) {
2463
+ this._changeMonth(1, true);
2464
+ } else if (preset) {
2465
+ this._selectPreset(preset.dataset.calendarPreset);
2466
+ } else if (day) {
2467
+ this._selectDate(parseDate(day.dataset.calendarDay), true);
2468
+ }
2469
+ });
2470
+ addListener(this._listeners, this._element, "keydown", (event) => {
2471
+ const day = event.target.closest?.("[data-calendar-day]");
2472
+ if (!day) return;
2473
+ this._handleDayKeydown(event);
2474
+ });
2475
+ addListener(this._listeners, this._element, "change", (event) => {
2476
+ if (event.target.matches?.("[data-calendar-month]")) {
2477
+ this._setActiveMonth(
2478
+ this._viewDate.getFullYear(),
2479
+ Number(event.target.value),
2480
+ true
2481
+ );
2482
+ }
2483
+ if (event.target.matches?.("[data-calendar-year]")) {
2484
+ this._setActiveMonth(
2485
+ Number(event.target.value),
2486
+ this._viewDate.getMonth(),
2487
+ true
2488
+ );
2489
+ }
2490
+ });
2491
+ }
2492
+ _emitChange(complete) {
2493
+ dispatch(this._element, this._window, "change.moo.calendar", {
2494
+ date: this._selectedDate ? new Date(this._selectedDate) : void 0,
2495
+ value: this._selectedDate ? toIso(this._selectedDate) : "",
2496
+ startDate: this._rangeStart ? new Date(this._rangeStart) : void 0,
2497
+ endDate: this._rangeEnd ? new Date(this._rangeEnd) : void 0,
2498
+ startValue: this._rangeStart ? toIso(this._rangeStart) : "",
2499
+ endValue: this._rangeEnd ? toIso(this._rangeEnd) : "",
2500
+ complete
2501
+ });
2502
+ }
2503
+ _changeMonth(amount, focus) {
2504
+ const nextView = startOfMonth(addMonths(this._viewDate, amount));
2505
+ this._setActiveMonth(nextView.getFullYear(), nextView.getMonth(), focus);
2506
+ }
2507
+ _setActiveMonth(year, month, focus = false) {
2508
+ const activeDay = Math.min(
2509
+ this._activeDate.getDate(),
2510
+ daysInMonth(year, month)
2511
+ );
2512
+ this._activeDate = this._nearestEnabledDate(new Date(year, month, activeDay));
2513
+ this._viewDate = startOfMonth(this._activeDate);
2514
+ this._render();
2515
+ if (focus) this.focusActiveDay();
2516
+ }
2517
+ _selectPreset(name) {
2518
+ if (!name) return;
2519
+ const today = /* @__PURE__ */ new Date();
2520
+ const offsets = {
2521
+ today: 0,
2522
+ tomorrow: 1,
2523
+ "in-3-days": 3,
2524
+ "in-a-week": 7,
2525
+ "in-2-weeks": 14
2526
+ };
2527
+ if (!(name in offsets)) return;
2528
+ this._selectDate(addDays(today, offsets[name]), true);
2529
+ }
2530
+ _handleDayKeydown(event) {
2531
+ const focusedDay = event.target.closest?.("[data-calendar-day]");
2532
+ const focusedDate = parseDate(focusedDay?.dataset?.calendarDay);
2533
+ if (focusedDate && compareDates(focusedDate, this._activeDate) !== 0) {
2534
+ this._activeDate = focusedDate;
2535
+ }
2536
+ const keyMap = {
2537
+ ArrowRight: 1,
2538
+ ArrowLeft: -1,
2539
+ ArrowDown: 7,
2540
+ ArrowUp: -7
2541
+ };
2542
+ let nextDate = null;
2543
+ if (event.key in keyMap) {
2544
+ nextDate = addDays(this._activeDate, keyMap[event.key]);
2545
+ } else if (event.key === "Home") {
2546
+ const weekStart = firstDayOfWeek(this._config.locale);
2547
+ const weekdayOffset = (this._activeDate.getDay() - weekStart + 7) % 7;
2548
+ nextDate = addDays(this._activeDate, -weekdayOffset);
2549
+ } else if (event.key === "End") {
2550
+ const weekStart = firstDayOfWeek(this._config.locale);
2551
+ const weekdayOffset = (this._activeDate.getDay() - weekStart + 7) % 7;
2552
+ nextDate = addDays(this._activeDate, 6 - weekdayOffset);
2553
+ } else if (event.key === "PageUp") {
2554
+ nextDate = addMonths(this._activeDate, -1);
2555
+ } else if (event.key === "PageDown") {
2556
+ nextDate = addMonths(this._activeDate, 1);
2557
+ } else if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
2558
+ event.preventDefault();
2559
+ this._selectDate(this._activeDate, true);
2560
+ return;
2561
+ } else {
2562
+ return;
2563
+ }
2564
+ event.preventDefault();
2565
+ this._setActiveDate(nextDate, true, compareDates(nextDate, this._activeDate) < 0 ? -1 : 1);
2566
+ }
2567
+ _setActiveDate(date, focus = false, step = 0) {
2568
+ const nextDate = step === 0 ? this._nearestEnabledDate(date) : this._nextEnabledDate(date, step);
2569
+ this._activeDate = nextDate;
2570
+ this._viewDate = startOfMonth(nextDate);
2571
+ this._render();
2572
+ if (focus) this.focusActiveDay();
2573
+ }
2574
+ _nextEnabledDate(date, step) {
2575
+ let candidate = parseDate(date);
2576
+ if (!candidate) return this._activeDate;
2577
+ const dayStep = step < 0 ? -1 : 1;
2578
+ for (let guard = 0; guard < 366; guard += 1) {
2579
+ if (this._config.minDate && compareDates(candidate, this._config.minDate) < 0) break;
2580
+ if (this._config.maxDate && compareDates(candidate, this._config.maxDate) > 0) break;
2581
+ if (!this._isDisabled(candidate)) return candidate;
2582
+ candidate = addDays(candidate, dayStep);
2583
+ }
2584
+ return this._activeDate;
2585
+ }
2586
+ _nearestEnabledDate(date) {
2587
+ const preferred = limitToRange(
2588
+ parseDate(date) || /* @__PURE__ */ new Date(),
2589
+ this._config.minDate,
2590
+ this._config.maxDate
2591
+ );
2592
+ if (!this._isDisabled(preferred)) return preferred;
2593
+ const monthStart = startOfMonth(preferred);
2594
+ const monthLength = daysInMonth(monthStart.getFullYear(), monthStart.getMonth());
2595
+ for (let day = 1; day <= monthLength; day += 1) {
2596
+ const candidate = new Date(monthStart.getFullYear(), monthStart.getMonth(), day);
2597
+ if (!this._isDisabled(candidate)) return candidate;
2598
+ }
2599
+ return preferred;
2600
+ }
2601
+ _selectDate(date, emit) {
2602
+ if (!date || this._isDisabled(date)) return;
2603
+ this._activeDate = date;
2604
+ this._viewDate = startOfMonth(date);
2605
+ if (this._mode === "range") {
2606
+ if (!this._rangeStart || this._rangeEnd || compareDates(date, this._rangeStart) < 0) {
2607
+ this._rangeStart = date;
2608
+ this._rangeEnd = null;
2609
+ } else {
2610
+ this._rangeEnd = date;
2611
+ }
2612
+ this._render();
2613
+ if (emit) this._emitChange(Boolean(this._rangeStart && this._rangeEnd));
2614
+ return;
2615
+ }
2616
+ this._selectedDate = date;
2617
+ this._render();
2618
+ if (emit) this._emitChange(true);
2619
+ }
2620
+ _isDisabled(date) {
2621
+ if (!date) return true;
2622
+ if (this._config.minDate && compareDates(date, this._config.minDate) < 0) return true;
2623
+ if (this._config.maxDate && compareDates(date, this._config.maxDate) > 0) return true;
2624
+ return this._disabledDates.has(toIso(date));
2625
+ }
2626
+ _isSelected(date) {
2627
+ if (this._mode === "range") {
2628
+ return sameDate(date, this._rangeStart) || sameDate(date, this._rangeEnd);
2629
+ }
2630
+ return sameDate(date, this._selectedDate);
2631
+ }
2632
+ _rangeState(date) {
2633
+ if (this._mode !== "range" || !this._rangeStart) return "";
2634
+ if (sameDate(date, this._rangeStart)) return "start";
2635
+ if (sameDate(date, this._rangeEnd)) return "end";
2636
+ if (this._rangeStart && this._rangeEnd && compareDates(date, this._rangeStart) > 0 && compareDates(date, this._rangeEnd) < 0) {
2637
+ return "middle";
2638
+ }
2639
+ return "";
2640
+ }
2641
+ _dayButton(date) {
2642
+ return this._element.querySelector(`[data-calendar-day="${toIso(date)}"]`);
2643
+ }
2644
+ _render() {
2645
+ const document2 = this._document;
2646
+ const fragment = document2.createDocumentFragment();
2647
+ const header = document2.createElement("div");
2648
+ header.className = "moo-calendar__header";
2649
+ const prev = document2.createElement("button");
2650
+ prev.className = "btn btn-ghost btn-icon-sm moo-calendar__nav";
2651
+ prev.type = "button";
2652
+ prev.dataset.calendarPrev = "true";
2653
+ prev.setAttribute("aria-label", "Previous month");
2654
+ prev.appendChild(createIcon(document2, "\u2039", "moo-calendar__nav-icon"));
2655
+ const next = document2.createElement("button");
2656
+ next.className = "btn btn-ghost btn-icon-sm moo-calendar__nav";
2657
+ next.type = "button";
2658
+ next.dataset.calendarNext = "true";
2659
+ next.setAttribute("aria-label", "Next month");
2660
+ next.appendChild(createIcon(document2, "\u203A", "moo-calendar__nav-icon"));
2661
+ const caption = document2.createElement("div");
2662
+ caption.className = "moo-calendar__caption";
2663
+ caption.id = this._captionId;
2664
+ caption.setAttribute("aria-live", "polite");
2665
+ if (this._config.captionLayout === "dropdown") {
2666
+ const month = document2.createElement("select");
2667
+ month.className = "moo-calendar__select";
2668
+ month.dataset.calendarMonth = "true";
2669
+ month.setAttribute("aria-label", "Month");
2670
+ for (let index = 0; index < 12; index += 1) {
2671
+ const option = document2.createElement("option");
2672
+ option.value = String(index);
2673
+ option.textContent = monthName(index, this._config.locale);
2674
+ option.selected = index === this._viewDate.getMonth();
2675
+ month.appendChild(option);
2676
+ }
2677
+ const year = document2.createElement("select");
2678
+ year.className = "moo-calendar__select";
2679
+ year.dataset.calendarYear = "true";
2680
+ year.setAttribute("aria-label", "Year");
2681
+ yearOptions(this._config.minDate, this._config.maxDate, this._viewDate).forEach((value) => {
2682
+ const option = document2.createElement("option");
2683
+ option.value = String(value);
2684
+ option.textContent = String(value);
2685
+ option.selected = value === this._viewDate.getFullYear();
2686
+ year.appendChild(option);
2687
+ });
2688
+ caption.append(month, year);
2689
+ } else {
2690
+ caption.textContent = formatMonthYear(this._viewDate, this._config.locale);
2691
+ }
2692
+ header.append(prev, caption, next);
2693
+ fragment.appendChild(header);
2694
+ const gridLabel = document2.createElement("div");
2695
+ gridLabel.id = this._labelId;
2696
+ gridLabel.className = "visually-hidden";
2697
+ gridLabel.textContent = this._element.getAttribute("aria-label") || formatMonthYear(this._viewDate, this._config.locale);
2698
+ fragment.appendChild(gridLabel);
2699
+ const weekdays = document2.createElement("div");
2700
+ weekdays.className = "moo-calendar__weekdays";
2701
+ weekdays.setAttribute("role", "row");
2702
+ weekdayDates(this._config.locale).forEach((date) => {
2703
+ const label = document2.createElement("span");
2704
+ label.className = "moo-calendar__weekday";
2705
+ label.setAttribute("role", "columnheader");
2706
+ label.textContent = new Intl.DateTimeFormat(this._config.locale, { weekday: "narrow" }).format(date);
2707
+ weekdays.appendChild(label);
2708
+ });
2709
+ const grid = document2.createElement("div");
2710
+ grid.className = "moo-calendar__grid";
2711
+ grid.setAttribute("role", "grid");
2712
+ grid.setAttribute("aria-labelledby", this._labelId);
2713
+ grid.appendChild(weekdays);
2714
+ const first = startOfMonth(this._viewDate);
2715
+ const weekStart = firstDayOfWeek(this._config.locale);
2716
+ const start = addDays(first, -((first.getDay() - weekStart + 7) % 7));
2717
+ for (let week = 0; week < 6; week += 1) {
2718
+ const row = document2.createElement("div");
2719
+ row.className = "moo-calendar__week";
2720
+ row.setAttribute("role", "row");
2721
+ for (let day = 0; day < 7; day += 1) {
2722
+ const date = addDays(start, week * 7 + day);
2723
+ const iso = toIso(date);
2724
+ const cell = document2.createElement("div");
2725
+ cell.className = "moo-calendar__cell";
2726
+ cell.setAttribute("role", "gridcell");
2727
+ const button = document2.createElement("button");
2728
+ button.className = "moo-calendar__day";
2729
+ button.type = "button";
2730
+ button.dataset.calendarDay = iso;
2731
+ button.textContent = String(date.getDate());
2732
+ button.tabIndex = sameDate(date, this._activeDate) ? 0 : -1;
2733
+ button.setAttribute("aria-label", formatDisplay(date, this._config.locale));
2734
+ const selected = this._isSelected(date);
2735
+ button.dataset.calendarSelected = String(selected);
2736
+ cell.setAttribute("aria-selected", selected ? "true" : "false");
2737
+ if (date.getMonth() !== this._viewDate.getMonth()) {
2738
+ button.dataset.calendarOutside = "true";
2739
+ }
2740
+ if (sameDate(date, /* @__PURE__ */ new Date())) {
2741
+ button.dataset.calendarToday = "true";
2742
+ button.setAttribute("aria-current", "date");
2743
+ }
2744
+ const rangeState = this._rangeState(date);
2745
+ if (rangeState) {
2746
+ button.dataset.calendarRange = rangeState;
2747
+ }
2748
+ if (this._isDisabled(date)) {
2749
+ button.disabled = true;
2750
+ button.setAttribute("aria-disabled", "true");
2751
+ }
2752
+ cell.appendChild(button);
2753
+ row.appendChild(cell);
2754
+ }
2755
+ grid.appendChild(row);
2756
+ }
2757
+ fragment.appendChild(grid);
2758
+ if (this._config.showPresets) {
2759
+ const presets = document2.createElement("div");
2760
+ presets.className = "moo-calendar__presets";
2761
+ [
2762
+ ["today", "Today"],
2763
+ ["tomorrow", "Tomorrow"],
2764
+ ["in-3-days", "In 3 days"],
2765
+ ["in-a-week", "In a week"],
2766
+ ["in-2-weeks", "In 2 weeks"]
2767
+ ].forEach(([value, label]) => {
2768
+ const button = document2.createElement("button");
2769
+ button.className = "btn btn-outline-secondary btn-sm moo-calendar__preset";
2770
+ button.type = "button";
2771
+ button.dataset.calendarPreset = value;
2772
+ button.textContent = label;
2773
+ presets.appendChild(button);
2774
+ });
2775
+ fragment.appendChild(presets);
2776
+ }
2777
+ this._element.classList.add("moo-calendar");
2778
+ this._element.dataset.calendar = this._element.dataset.calendar || "";
2779
+ this._element.dataset.calendarMode = this._mode;
2780
+ this._element.dataset.calendarActiveDate = toIso(this._activeDate);
2781
+ if (this._mode === "single" && this._selectedDate) {
2782
+ this._element.dataset.calendarValue = toIso(this._selectedDate);
2783
+ } else {
2784
+ delete this._element.dataset.calendarValue;
2785
+ }
2786
+ if (this._mode === "range") {
2787
+ this._element.dataset.calendarStartValue = this._rangeStart ? toIso(this._rangeStart) : "";
2788
+ this._element.dataset.calendarEndValue = this._rangeEnd ? toIso(this._rangeEnd) : "";
2789
+ } else {
2790
+ delete this._element.dataset.calendarStartValue;
2791
+ delete this._element.dataset.calendarEndValue;
2792
+ }
2793
+ this._element.replaceChildren(fragment);
2794
+ }
2795
+ };
2796
+ var MooDatepicker = class _MooDatepicker {
2797
+ static getInstance(element) {
2798
+ return element?.nodeType === 1 ? datepickerInstances.get(element) || null : null;
2799
+ }
2800
+ static getOrCreateInstance(element, config = {}) {
2801
+ return _MooDatepicker.getInstance(element) || new _MooDatepicker(element, config);
2802
+ }
2803
+ constructor(element, config = {}) {
2804
+ if (!isElement(element, "[data-datepicker]")) {
2805
+ throw new TypeError("MooDatepicker requires a [data-datepicker] root element.");
2806
+ }
2807
+ const existing = datepickerInstances.get(element);
2808
+ if (existing) return existing;
2809
+ this._element = element;
2810
+ this._document = element.ownerDocument;
2811
+ this._window = this._document.defaultView || window;
2812
+ this._listeners = [];
2813
+ this._floatingListeners = [];
2814
+ this._disposed = false;
2815
+ this._positionPopover = null;
2816
+ this._popoverPortal = null;
2817
+ this._trigger = element.querySelector("[data-datepicker-trigger]");
2818
+ this._popover = element.querySelector("[data-datepicker-popover]");
2819
+ this._label = element.querySelector("[data-datepicker-label]");
2820
+ this._input = element.querySelector("[data-datepicker-input]") || element.querySelector('input[type="hidden"]');
2821
+ this._validationInput = element.querySelector("[data-datepicker-validation]");
2822
+ this._calendarRoot = element.querySelector("[data-calendar]");
2823
+ if (!this._trigger || !this._popover || !this._label || !this._input || !this._calendarRoot) {
2824
+ throw new TypeError("MooDatepicker requires a trigger, popover, calendar, and hidden input.");
2825
+ }
2826
+ this._config = readDataConfig(element, {
2827
+ ...config,
2828
+ value: config.value || this._input.value || element.dataset.datepickerValue
2829
+ });
2830
+ this._placeholder = this._config.placeholder;
2831
+ this._defaultValue = this._input.defaultValue || this._input.getAttribute("value") || "";
2832
+ this.calendar = MooCalendar.getOrCreateInstance(this._calendarRoot, {
2833
+ ...this._config,
2834
+ mode: "single",
2835
+ value: this._input.value || this._config.value
2836
+ });
2837
+ datepickerInstances.set(element, this);
2838
+ this._syncFromCalendar();
2839
+ this._bindEvents();
2840
+ }
2841
+ dispose() {
2842
+ if (this._disposed) return;
2843
+ this._disposed = true;
2844
+ removeListeners(this._listeners);
2845
+ this.hide(false);
2846
+ stopDatepickerPopoverPositioning(this);
2847
+ restoreDatepickerPopover(this);
2848
+ removeListeners(this._floatingListeners);
2849
+ this.calendar?.dispose();
2850
+ if (datepickerInstances.get(this._element) === this) {
2851
+ datepickerInstances.delete(this._element);
2852
+ }
2853
+ }
2854
+ show() {
2855
+ if (this._trigger.disabled || this._element.dataset.datepickerDisabled === "true") return;
2856
+ if (this.isOpen()) return;
2857
+ if (!dispatch(this._element, this._window, "show.moo.datepicker", {}, true)) return;
2858
+ this._popover.hidden = false;
2859
+ this._popover.classList.add("show");
2860
+ startDatepickerPopoverPositioning(this);
2861
+ this._trigger.setAttribute("aria-expanded", "true");
2862
+ this.calendar.focusActiveDay();
2863
+ dispatch(this._element, this._window, "shown.moo.datepicker");
2864
+ }
2865
+ hide(returnFocus = true) {
2866
+ const wasOpen = this.isOpen();
2867
+ if (!wasOpen) return;
2868
+ if (!dispatch(this._element, this._window, "hide.moo.datepicker", {}, true)) return;
2869
+ this._popover.hidden = true;
2870
+ this._popover.classList.remove("show");
2871
+ stopDatepickerPopoverPositioning(this);
2872
+ this._trigger.setAttribute("aria-expanded", "false");
2873
+ if (returnFocus) this._trigger.focus();
2874
+ dispatch(this._element, this._window, "hidden.moo.datepicker");
2875
+ }
2876
+ toggle() {
2877
+ if (this.isOpen()) {
2878
+ this.hide();
2879
+ } else {
2880
+ this.show();
2881
+ }
2882
+ }
2883
+ isOpen() {
2884
+ return !this._popover.hidden;
2885
+ }
2886
+ getDate(format = void 0) {
2887
+ return this.calendar.getDate(format);
2888
+ }
2889
+ setDate(date, options = {}) {
2890
+ const changed = this.calendar.setDate(date, { emit: false });
2891
+ if (changed) {
2892
+ this._syncFromCalendar();
2893
+ if (options.emit !== false) this._emitChange();
2894
+ }
2895
+ return changed;
2896
+ }
2897
+ clear(options = {}) {
2898
+ this.calendar.clear({ emit: false });
2899
+ this._syncFromCalendar();
2900
+ if (options.emit !== false) this._emitChange();
2901
+ }
2902
+ _bindEvents() {
2903
+ addListener(this._listeners, this._trigger, "click", () => this.toggle());
2904
+ addListener(this._listeners, this._popover, "keydown", (event) => {
2905
+ if (event.key === "Escape") {
2906
+ event.preventDefault();
2907
+ this.hide();
2908
+ }
2909
+ });
2910
+ addListener(this._listeners, this._document, "pointerdown", (event) => {
2911
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
2912
+ this._window.setTimeout(() => {
2913
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
2914
+ this.hide();
2915
+ }
2916
+ }, 0);
2917
+ }
2918
+ });
2919
+ addListener(this._listeners, this._calendarRoot, "change.moo.calendar", (event) => {
2920
+ this._syncFromCalendar();
2921
+ this._emitChange();
2922
+ if (event.detail.complete) {
2923
+ this.hide();
2924
+ }
2925
+ });
2926
+ const form = this._input.form || this._element.closest("form");
2927
+ addListener(this._listeners, this._validationInput, "invalid", (event) => {
2928
+ event.preventDefault();
2929
+ setDatepickerInvalid(this._element, this._trigger, true);
2930
+ this._trigger.focus({ preventScroll: true });
2931
+ });
2932
+ addListener(this._listeners, form, "reset", () => {
2933
+ this._window.setTimeout(() => {
2934
+ if (this._defaultValue) {
2935
+ this.setDate(this._defaultValue, { emit: false });
2936
+ } else {
2937
+ this.clear({ emit: false });
2938
+ }
2939
+ }, 0);
2940
+ });
2941
+ }
2942
+ _syncFromCalendar() {
2943
+ const value = this.calendar.getDate("yyyy-mm-dd") || "";
2944
+ setHiddenValue(this._input, value);
2945
+ setValidationValue(this._validationInput, value);
2946
+ syncDatepickerValidation(this._element, this._trigger, this._validationInput);
2947
+ this._element.dataset.datepickerValue = value;
2948
+ this._label.textContent = value ? formatDisplay(parseDate(value), this._config.locale) : this._placeholder;
2949
+ }
2950
+ _emitChange() {
2951
+ dispatch(this._element, this._window, "change.moo.datepicker", {
2952
+ date: this.getDate(),
2953
+ value: this.getDate("yyyy-mm-dd") || ""
2954
+ });
2955
+ }
2956
+ };
2957
+ var MooDateRangePicker = class _MooDateRangePicker {
2958
+ static getInstance(element) {
2959
+ return element?.nodeType === 1 ? rangePickerInstances.get(element) || null : null;
2960
+ }
2961
+ static getOrCreateInstance(element, config = {}) {
2962
+ return _MooDateRangePicker.getInstance(element) || new _MooDateRangePicker(element, config);
2963
+ }
2964
+ constructor(element, config = {}) {
2965
+ if (!isElement(element, "[data-datepicker-range]")) {
2966
+ throw new TypeError("MooDateRangePicker requires a [data-datepicker-range] root element.");
2967
+ }
2968
+ const existing = rangePickerInstances.get(element);
2969
+ if (existing) return existing;
2970
+ this._element = element;
2971
+ this._document = element.ownerDocument;
2972
+ this._window = this._document.defaultView || window;
2973
+ this._listeners = [];
2974
+ this._floatingListeners = [];
2975
+ this._disposed = false;
2976
+ this._positionPopover = null;
2977
+ this._popoverPortal = null;
2978
+ this._trigger = element.querySelector("[data-datepicker-trigger]");
2979
+ this._popover = element.querySelector("[data-datepicker-popover]");
2980
+ this._label = element.querySelector("[data-datepicker-label]");
2981
+ this._startInput = element.querySelector("[data-datepicker-range-start]");
2982
+ this._endInput = element.querySelector("[data-datepicker-range-end]");
2983
+ this._validationInput = element.querySelector("[data-datepicker-validation]");
2984
+ this._calendarRoot = element.querySelector("[data-calendar]");
2985
+ if (!this._trigger || !this._popover || !this._label || !this._startInput || !this._endInput || !this._calendarRoot) {
2986
+ throw new TypeError("MooDateRangePicker requires a trigger, popover, calendar, and start/end hidden inputs.");
2987
+ }
2988
+ this._config = readDataConfig(element, {
2989
+ ...config,
2990
+ mode: "range",
2991
+ startValue: config.startValue || this._startInput.value || element.dataset.datepickerStartValue,
2992
+ endValue: config.endValue || this._endInput.value || element.dataset.datepickerEndValue
2993
+ });
2994
+ this._placeholder = this._config.placeholder;
2995
+ this._defaultStartValue = this._startInput.defaultValue || this._startInput.getAttribute("value") || "";
2996
+ this._defaultEndValue = this._endInput.defaultValue || this._endInput.getAttribute("value") || "";
2997
+ this.calendar = MooCalendar.getOrCreateInstance(this._calendarRoot, this._config);
2998
+ rangePickerInstances.set(element, this);
2999
+ this._syncFromCalendar();
3000
+ this._bindEvents();
3001
+ }
3002
+ dispose() {
3003
+ if (this._disposed) return;
3004
+ this._disposed = true;
3005
+ removeListeners(this._listeners);
3006
+ this.hide(false);
3007
+ stopDatepickerPopoverPositioning(this);
3008
+ restoreDatepickerPopover(this);
3009
+ removeListeners(this._floatingListeners);
3010
+ this.calendar?.dispose();
3011
+ if (rangePickerInstances.get(this._element) === this) {
3012
+ rangePickerInstances.delete(this._element);
3013
+ }
3014
+ }
3015
+ show() {
3016
+ if (this._trigger.disabled || this._element.dataset.datepickerDisabled === "true") return;
3017
+ if (this.isOpen()) return;
3018
+ if (!dispatch(this._element, this._window, "show.moo.datepicker", {}, true)) return;
3019
+ this._popover.hidden = false;
3020
+ this._popover.classList.add("show");
3021
+ startDatepickerPopoverPositioning(this);
3022
+ this._trigger.setAttribute("aria-expanded", "true");
3023
+ this.calendar.focusActiveDay();
3024
+ dispatch(this._element, this._window, "shown.moo.datepicker");
3025
+ }
3026
+ hide(returnFocus = true) {
3027
+ const wasOpen = this.isOpen();
3028
+ if (!wasOpen) return;
3029
+ if (!dispatch(this._element, this._window, "hide.moo.datepicker", {}, true)) return;
3030
+ this._popover.hidden = true;
3031
+ this._popover.classList.remove("show");
3032
+ stopDatepickerPopoverPositioning(this);
3033
+ this._trigger.setAttribute("aria-expanded", "false");
3034
+ if (returnFocus) this._trigger.focus();
3035
+ dispatch(this._element, this._window, "hidden.moo.datepicker");
3036
+ }
3037
+ toggle() {
3038
+ if (this.isOpen()) {
3039
+ this.hide();
3040
+ } else {
3041
+ this.show();
3042
+ }
3043
+ }
3044
+ isOpen() {
3045
+ return !this._popover.hidden;
3046
+ }
3047
+ getDates(format = void 0) {
3048
+ return this.calendar.getDates(format);
3049
+ }
3050
+ setDates(startDate, endDate, options = {}) {
3051
+ const changed = this.calendar.setDates(startDate, endDate, { emit: false });
3052
+ if (changed) {
3053
+ this._syncFromCalendar();
3054
+ if (options.emit !== false) this._emitChange();
3055
+ }
3056
+ return changed;
3057
+ }
3058
+ clear(options = {}) {
3059
+ this.calendar.clear({ emit: false });
3060
+ this._syncFromCalendar();
3061
+ if (options.emit !== false) this._emitChange();
3062
+ }
3063
+ _bindEvents() {
3064
+ addListener(this._listeners, this._trigger, "click", () => this.toggle());
3065
+ addListener(this._listeners, this._popover, "keydown", (event) => {
3066
+ if (event.key === "Escape") {
3067
+ event.preventDefault();
3068
+ this.hide();
3069
+ }
3070
+ });
3071
+ addListener(this._listeners, this._document, "pointerdown", (event) => {
3072
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
3073
+ this._window.setTimeout(() => {
3074
+ if (this.isOpen() && !datepickerContains(this, event.target)) {
3075
+ this.hide();
3076
+ }
3077
+ }, 0);
3078
+ }
3079
+ });
3080
+ const closeWhenFocusLeaves = () => {
3081
+ this._window.setTimeout(() => {
3082
+ if (this.isOpen() && !datepickerContains(this, this._document.activeElement)) {
3083
+ this.hide(false);
3084
+ }
3085
+ }, 0);
3086
+ };
3087
+ addListener(this._listeners, this._element, "focusout", closeWhenFocusLeaves);
3088
+ addListener(this._listeners, this._popover, "focusout", closeWhenFocusLeaves);
3089
+ addListener(this._listeners, this._calendarRoot, "change.moo.calendar", (event) => {
3090
+ this._syncFromCalendar();
3091
+ this._emitChange();
3092
+ if (event.detail.complete) {
3093
+ this.hide();
3094
+ } else {
3095
+ this.calendar.focusActiveDay();
3096
+ }
3097
+ });
3098
+ const form = this._startInput.form || this._element.closest("form");
3099
+ addListener(this._listeners, this._validationInput, "invalid", (event) => {
3100
+ event.preventDefault();
3101
+ setDatepickerInvalid(this._element, this._trigger, true);
3102
+ this._trigger.focus({ preventScroll: true });
3103
+ });
3104
+ addListener(this._listeners, form, "reset", () => {
3105
+ this._window.setTimeout(() => {
3106
+ this.setDates(this._defaultStartValue, this._defaultEndValue, { emit: false });
3107
+ }, 0);
3108
+ });
3109
+ }
3110
+ _syncFromCalendar() {
3111
+ const [startValue = "", endValue = ""] = this.calendar.getDates("yyyy-mm-dd");
3112
+ setHiddenValue(this._startInput, startValue || "");
3113
+ setHiddenValue(this._endInput, endValue || "");
3114
+ setValidationValue(this._validationInput, startValue && endValue ? `${startValue} - ${endValue}` : "");
3115
+ syncDatepickerValidation(this._element, this._trigger, this._validationInput);
3116
+ this._element.dataset.datepickerStartValue = startValue || "";
3117
+ this._element.dataset.datepickerEndValue = endValue || "";
3118
+ if (startValue && endValue) {
3119
+ this._label.textContent = `${formatDisplay(parseDate(startValue), this._config.locale)} - ${formatDisplay(parseDate(endValue), this._config.locale)}`;
3120
+ } else if (startValue) {
3121
+ this._label.textContent = formatDisplay(parseDate(startValue), this._config.locale);
3122
+ } else {
3123
+ this._label.textContent = this._placeholder;
3124
+ }
3125
+ }
3126
+ _emitChange() {
3127
+ const [startDate, endDate] = this.getDates();
3128
+ const [startValue = "", endValue = ""] = this.getDates("yyyy-mm-dd");
3129
+ dispatch(this._element, this._window, "change.moo.datepicker", {
3130
+ startDate,
3131
+ endDate,
3132
+ startValue,
3133
+ endValue
3134
+ });
3135
+ }
3136
+ };
3137
+
3138
+ // src/js/components/sidebar.js
3139
+ var instances4 = /* @__PURE__ */ new WeakMap();
3140
+ var Sidebar = class _Sidebar {
3141
+ static getInstance(element) {
3142
+ return element?.nodeType === 1 ? instances4.get(element) || null : null;
3143
+ }
3144
+ static getOrCreateInstance(element, config = {}) {
3145
+ return _Sidebar.getInstance(element) || new _Sidebar(element, config);
3146
+ }
3147
+ constructor(element, config = {}) {
3148
+ if (element?.nodeType !== 1 || !element.matches('[data-slot="sidebar-wrapper"]')) {
3149
+ throw new TypeError("Sidebar requires a [data-slot=sidebar-wrapper] root.");
3150
+ }
3151
+ const existing = instances4.get(element);
3152
+ if (existing) {
3153
+ return existing;
3154
+ }
3155
+ this._element = element;
3156
+ this._document = element.ownerDocument;
3157
+ this._window = this._document.defaultView;
3158
+ this._documentElement = this._document.documentElement;
3159
+ this._root = findThemeOwner(element) || this._document.body || this._documentElement;
3160
+ this._sidebar = element.querySelector('[data-slot="sidebar"]');
3161
+ this._config = {
3162
+ breakpoint: "(min-width: 992px)",
3163
+ storagePrefix: "moo-sidebar:",
3164
+ keyboard: true,
3165
+ ...config
3166
+ };
3167
+ this._listeners = [];
3168
+ this._tooltipAnchors = /* @__PURE__ */ new Set();
3169
+ this._flyout = null;
3170
+ this._flyoutOwner = null;
3171
+ this._offcanvas = null;
3172
+ this._offcanvasTrigger = null;
3173
+ instances4.set(element, this);
3174
+ this._bindEvents();
3175
+ this._restoreState();
3176
+ this._scrollActiveItemIntoView();
3177
+ this._element.setAttribute("data-sidebar-ready", "");
3178
+ this._observeDirection();
3179
+ }
3180
+ dispose() {
3181
+ this._listeners.forEach(({ target, type, handler, options }) => {
3182
+ target.removeEventListener(type, handler, options);
3183
+ });
3184
+ this._listeners = [];
3185
+ this._directionObserver?.disconnect();
3186
+ this._closeFlyouts();
3187
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
3188
+ this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => this._disposeTooltip(control));
3189
+ this._offcanvas?.dispose();
3190
+ this._offcanvas = null;
3191
+ this._offcanvasTrigger = null;
3192
+ this._element.removeAttribute("data-sidebar-ready");
3193
+ instances4.delete(this._element);
3194
+ }
3195
+ _listen(target, type, handler, options) {
3196
+ target?.addEventListener(type, handler, options);
3197
+ if (target) {
3198
+ this._listeners.push({ target, type, handler, options });
3199
+ }
3200
+ }
3201
+ _bootstrap(name) {
3202
+ return this._window.bootstrap?.[name] || null;
3203
+ }
3204
+ _portalRoot(trigger = this._element) {
3205
+ return ownerPortalRoot(findThemeOwner(trigger)) || this._document.body || this._documentElement;
3206
+ }
3207
+ _isDesktop() {
3208
+ return this._window.matchMedia(this._config.breakpoint).matches;
3209
+ }
3210
+ _isCollapsed() {
3211
+ return this._isDesktop() && this._sidebar?.dataset?.collapsible === "icon" && this._element.dataset.sidebarState === "collapsed";
3212
+ }
3213
+ _trigger(name, detail = {}) {
3214
+ return this._element.dispatchEvent(
3215
+ new this._window.CustomEvent(`${name}.moo.sidebar`, {
3216
+ bubbles: true,
3217
+ cancelable: name === "show" || name === "hide",
3218
+ detail
3219
+ })
3220
+ );
3221
+ }
3222
+ _observeDirection() {
3223
+ const Observer = this._window.MutationObserver;
3224
+ if (!Observer) {
3225
+ return;
3226
+ }
3227
+ this._directionObserver = new Observer(() => {
3228
+ this._closeFlyouts();
3229
+ this._syncTooltips();
3230
+ });
3231
+ this._directionObserver.observe(this._documentElement, {
3232
+ attributes: true,
3233
+ attributeFilter: ["dir"]
3234
+ });
3235
+ }
3236
+ _restoreState() {
3237
+ const key = this._element.dataset.sidebarKey;
3238
+ let stored = null;
3239
+ if (key) {
3240
+ try {
3241
+ stored = this._window.localStorage.getItem(this._config.storagePrefix + key);
3242
+ } catch (_) {
3243
+ stored = null;
3244
+ }
3245
+ }
3246
+ const initial = stored === "collapsed" || stored === "expanded" ? stored : this._element.dataset.sidebarState === "collapsed" ? "collapsed" : "expanded";
3247
+ this._setState(initial, false, false);
3248
+ }
3249
+ _scrollActiveItemIntoView() {
3250
+ const activeRoute = this._element.querySelector(
3251
+ '[data-slot="sidebar-content"] a[data-slot="sidebar-menu-button"][aria-current="page"], [data-slot="sidebar-content"] a.sidebar-menu-button[aria-current="page"], [data-slot="sidebar-content"] a.sidebar-menu-button.active'
3252
+ );
3253
+ const active = activeRoute || this._element.querySelector(
3254
+ '[data-slot="sidebar-content"] [data-slot="sidebar-menu-button"][aria-current="page"], [data-slot="sidebar-content"] .sidebar-menu-button[aria-current="page"], [data-slot="sidebar-content"] .sidebar-menu-button.active'
3255
+ );
3256
+ const content = active?.closest?.('[data-slot="sidebar-content"]');
3257
+ if (!active || !content || content.clientHeight <= 0) {
3258
+ return;
3259
+ }
3260
+ const maxScrollTop = content.scrollHeight - content.clientHeight;
3261
+ if (maxScrollTop <= 0) {
3262
+ return;
3263
+ }
3264
+ const contentRect = content.getBoundingClientRect();
3265
+ const activeRect = active.getBoundingClientRect();
3266
+ if (contentRect.height <= 0 || activeRect.height <= 0) {
3267
+ return;
3268
+ }
3269
+ const targetScrollTop = content.scrollTop + activeRect.top - contentRect.top - (content.clientHeight - activeRect.height) / 2;
3270
+ content.scrollTop = Math.round(
3271
+ Math.min(Math.max(targetScrollTop, 0), maxScrollTop)
3272
+ );
3273
+ }
3274
+ _setState(state, persist = true, emit = true) {
3275
+ const next = state === "collapsed" ? "collapsed" : "expanded";
3276
+ const previous = this._element.dataset.sidebarState;
3277
+ this._element.dataset.sidebarState = next;
3278
+ const key = this._element.dataset.sidebarKey;
3279
+ if (key === "catalog-shell") {
3280
+ this._root.dataset.sidebarCatalogState = next;
3281
+ }
3282
+ if (persist && key) {
3283
+ try {
3284
+ this._window.localStorage.setItem(this._config.storagePrefix + key, next);
3285
+ } catch (_) {
3286
+ }
3287
+ }
3288
+ this._syncControls();
3289
+ if (next === "expanded") {
3290
+ this._element.querySelectorAll(".sidebar-menu-item").forEach((item) => this._resetFlyoutTrigger(item));
3291
+ }
3292
+ this._syncTooltips();
3293
+ if (emit && previous !== next) {
3294
+ this._trigger("change", { state: next, previousState: previous || null });
3295
+ }
3296
+ }
3297
+ _toggle() {
3298
+ this._setState(
3299
+ this._element.dataset.sidebarState === "collapsed" ? "expanded" : "collapsed"
3300
+ );
3301
+ }
3302
+ _syncControls() {
3303
+ const expanded = this._isDesktop() ? this._element.dataset.sidebarState === "expanded" : this._sidebar?.classList.contains("show") || false;
3304
+ this._element.querySelectorAll("[data-sidebar-trigger], [data-sidebar-rail]").forEach((control) => control.setAttribute("aria-expanded", String(expanded)));
3305
+ }
3306
+ _resetFlyoutTrigger(item, expanded = null) {
3307
+ const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
3308
+ const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
3309
+ if (trigger && submenu) {
3310
+ trigger.setAttribute(
3311
+ "aria-expanded",
3312
+ String(expanded ?? submenu.classList.contains("show"))
3313
+ );
3314
+ }
3315
+ }
3316
+ _clearDropdownPosition(control) {
3317
+ const item = control?.matches?.(".sidebar-menu-item") ? control : control?.closest?.(".sidebar-menu-item");
3318
+ if (!item) {
3319
+ return;
3320
+ }
3321
+ delete item.dataset.sidebarDropdownPositioned;
3322
+ item.style.removeProperty("--moo-sidebar-dropdown-block-start");
3323
+ item.style.removeProperty("--moo-sidebar-dropdown-block-end");
3324
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
3325
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
3326
+ }
3327
+ _closeDropdowns(exceptControl = null) {
3328
+ const Dropdown = this._bootstrap("Dropdown");
3329
+ this._element.querySelectorAll('[data-bs-toggle="dropdown"][aria-expanded="true"]').forEach((control) => {
3330
+ if (control === exceptControl) {
3331
+ return;
3332
+ }
3333
+ if (Dropdown) {
3334
+ Dropdown.getOrCreateInstance(control).hide();
3335
+ } else {
3336
+ control.setAttribute("aria-expanded", "false");
3337
+ control.closest(".dropdown")?.querySelector(".dropdown-menu.show")?.classList.remove("show");
3338
+ this._clearDropdownPosition(control);
3339
+ }
3340
+ });
3341
+ }
3342
+ _positionDropdown(control) {
3343
+ const item = control?.closest?.(".sidebar-menu-item");
3344
+ const isHeaderWorkspace = control?.classList.contains("sidebar-menu-button--workspace") && control.closest('[data-slot="sidebar-header"]');
3345
+ const isFooterAccount = control?.closest(".sidebar-menu-item--account") && control.closest('[data-slot="sidebar-footer"]');
3346
+ if (!control || !item || !(isHeaderWorkspace || isFooterAccount) || !this._isDesktop()) {
3347
+ this._clearDropdownPosition(control);
3348
+ return;
3349
+ }
3350
+ this._closeFlyouts();
3351
+ const rect = control.getBoundingClientRect();
3352
+ const gap = 4;
3353
+ const sidebar = control.closest('[data-slot="sidebar"]');
3354
+ const side = sidebar?.dataset.side || "left";
3355
+ if (side === "right") {
3356
+ const right = this._window.innerWidth - rect.left + gap;
3357
+ item.style.setProperty(
3358
+ "--moo-sidebar-dropdown-right",
3359
+ `${Math.round(right)}px`
3360
+ );
3361
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
3362
+ } else {
3363
+ const left = rect.right + gap;
3364
+ item.style.setProperty(
3365
+ "--moo-sidebar-dropdown-left",
3366
+ `${Math.round(left)}px`
3367
+ );
3368
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
3369
+ }
3370
+ if (isHeaderWorkspace) {
3371
+ item.style.setProperty(
3372
+ "--moo-sidebar-dropdown-block-start",
3373
+ `${Math.round(rect.bottom + gap)}px`
3374
+ );
3375
+ item.style.removeProperty("--moo-sidebar-dropdown-block-end");
3376
+ } else {
3377
+ item.style.setProperty(
3378
+ "--moo-sidebar-dropdown-block-end",
3379
+ `${Math.round(this._window.innerHeight - rect.top + gap)}px`
3380
+ );
3381
+ item.style.removeProperty("--moo-sidebar-dropdown-block-start");
3382
+ }
3383
+ item.dataset.sidebarDropdownPositioned = "";
3384
+ }
3385
+ _removeFlyoutPortal() {
3386
+ this._flyout?.remove();
3387
+ if (this._flyoutOwner) {
3388
+ this._resetFlyoutTrigger(this._flyoutOwner, false);
3389
+ }
3390
+ this._flyout = null;
3391
+ this._flyoutOwner = null;
3392
+ }
3393
+ _closeFlyouts() {
3394
+ this._removeFlyoutPortal();
3395
+ this._element.querySelectorAll(".sidebar-menu-item--flyout-open").forEach((item) => {
3396
+ item.classList.remove("sidebar-menu-item--flyout-open");
3397
+ item.style.removeProperty("--moo-sidebar-flyout-block-start");
3398
+ item.style.removeProperty("--moo-sidebar-flyout-left");
3399
+ this._resetFlyoutTrigger(item, false);
3400
+ });
3401
+ }
3402
+ _openFlyout(item) {
3403
+ const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
3404
+ const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
3405
+ if (!item || !submenu || !this._isCollapsed()) {
3406
+ return;
3407
+ }
3408
+ if (this._flyoutOwner === item && this._flyout) {
3409
+ return;
3410
+ }
3411
+ this._closeDropdowns();
3412
+ this._closeFlyouts();
3413
+ const rect = item.getBoundingClientRect();
3414
+ const sidebar = this._sidebar;
3415
+ const sidebarRect = sidebar?.getBoundingClientRect() || rect;
3416
+ const gap = 4;
3417
+ const flyout = submenu.cloneNode(true);
3418
+ flyout.removeAttribute("id");
3419
+ flyout.classList.remove("collapse", "show", "collapsing");
3420
+ flyout.classList.add("sidebar-menu-flyout");
3421
+ flyout.dataset.sidebarFlyout = "";
3422
+ flyout.removeAttribute("style");
3423
+ flyout.style.setProperty("--moo-sidebar-flyout-block-start", `${Math.round(rect.top)}px`);
3424
+ const side = sidebar?.dataset.side || "left";
3425
+ this._portalRoot(item).appendChild(flyout);
3426
+ const flyoutWidth = flyout.getBoundingClientRect().width;
3427
+ const left = side === "right" ? sidebarRect.left - flyoutWidth - gap : sidebarRect.right + gap;
3428
+ const boundedLeft = Math.max(0, Math.min(this._window.innerWidth - flyoutWidth, left));
3429
+ flyout.style.setProperty("--moo-sidebar-flyout-left", `${Math.round(boundedLeft)}px`);
3430
+ this._flyout = flyout;
3431
+ this._flyoutOwner = item;
3432
+ item.classList.add("sidebar-menu-item--flyout-open");
3433
+ trigger?.setAttribute("aria-expanded", "true");
3434
+ }
3435
+ _tooltipAnchor(control) {
3436
+ return control.closest("li") || control;
3437
+ }
3438
+ _isIdentityTrigger(control) {
3439
+ return control.closest(".sidebar-menu-item--account") || control.classList.contains("sidebar-menu-button--workspace");
3440
+ }
3441
+ _disposeTooltip(control) {
3442
+ const Tooltip = this._bootstrap("Tooltip");
3443
+ if (!Tooltip || !control) {
3444
+ return;
3445
+ }
3446
+ const anchor = this._tooltipAnchor(control);
3447
+ Tooltip.getInstance(anchor)?.dispose();
3448
+ this._tooltipAnchors.delete(anchor);
3449
+ anchor.removeAttribute("title");
3450
+ anchor.removeAttribute("data-bs-title");
3451
+ anchor.removeAttribute("data-bs-original-title");
3452
+ anchor.removeAttribute("aria-describedby");
3453
+ }
3454
+ _syncTooltips() {
3455
+ const Tooltip = this._bootstrap("Tooltip");
3456
+ if (!Tooltip) {
3457
+ return;
3458
+ }
3459
+ const collapsed = this._isCollapsed();
3460
+ const side = this._sidebar?.dataset.side || "left";
3461
+ const placement = side === "right" ? "left" : "right";
3462
+ this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => {
3463
+ this._disposeTooltip(control);
3464
+ if (!collapsed || this._isIdentityTrigger(control) || control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")) {
3465
+ return;
3466
+ }
3467
+ const anchor = this._tooltipAnchor(control);
3468
+ new Tooltip(anchor, {
3469
+ title: control.getAttribute("data-sidebar-tooltip"),
3470
+ placement,
3471
+ // Bootstrap's default tooltip offset is 6px. The icon rail's
3472
+ // boundary calculation still lets the tooltip box overlap the
3473
+ // physical Sidebar edge at that distance, so keep an explicit 8px
3474
+ // separation for both physical placements.
3475
+ offset: [0, 8],
3476
+ container: this._portalRoot(control),
3477
+ trigger: "hover focus"
3478
+ });
3479
+ this._tooltipAnchors.add(anchor);
3480
+ });
3481
+ }
3482
+ _bindEvents() {
3483
+ this._listen(this._sidebar, "shown.bs.offcanvas", () => this._syncControls());
3484
+ this._listen(this._sidebar, "hidden.bs.offcanvas", () => {
3485
+ this._syncControls();
3486
+ const trigger = this._offcanvasTrigger;
3487
+ this._offcanvasTrigger = null;
3488
+ if (trigger?.isConnected) {
3489
+ trigger.focus();
3490
+ }
3491
+ });
3492
+ this._listen(this._document, "show.bs.dropdown", (event) => {
3493
+ const control = this._dropdownControl(event.target);
3494
+ if (!control) {
3495
+ return;
3496
+ }
3497
+ this._closeDropdowns(control);
3498
+ this._disposeTooltip(control);
3499
+ this._positionDropdown(control);
3500
+ });
3501
+ this._listen(this._document, "hidden.bs.dropdown", (event) => {
3502
+ const control = this._dropdownControl(event.target);
3503
+ if (!control) {
3504
+ return;
3505
+ }
3506
+ this._clearDropdownPosition(control);
3507
+ this._syncTooltips();
3508
+ });
3509
+ this._listen(this._element, "click", (event) => this._handleControlClick(event));
3510
+ this._listen(this._window, "click", (event) => this._handleSubmenuClick(event), true);
3511
+ this._listen(this._document, "click", (event) => this._handleOutsideFlyoutClick(event));
3512
+ this._listen(this._document, "keydown", (event) => this._handleShortcut(event));
3513
+ this._listen(this._window, "resize", () => {
3514
+ this._closeFlyouts();
3515
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
3516
+ this._syncControls();
3517
+ this._syncTooltips();
3518
+ });
3519
+ }
3520
+ _dropdownControl(target) {
3521
+ if (!(target instanceof this._window.Element) || !this._element.contains(target)) {
3522
+ return null;
3523
+ }
3524
+ if (target.matches('[data-bs-toggle="dropdown"][data-sidebar-tooltip]')) {
3525
+ return target;
3526
+ }
3527
+ return target.querySelector?.(
3528
+ '[data-bs-toggle="dropdown"][data-sidebar-tooltip]'
3529
+ ) || null;
3530
+ }
3531
+ _handleControlClick(event) {
3532
+ const target = event.target;
3533
+ const control = target instanceof this._window.Element ? target.closest("[data-sidebar-trigger], [data-sidebar-rail]") : null;
3534
+ if (!control || !this._element.contains(control)) {
3535
+ return;
3536
+ }
3537
+ if (this._isDesktop()) {
3538
+ event.preventDefault();
3539
+ this._closeFlyouts();
3540
+ this._toggle();
3541
+ return;
3542
+ }
3543
+ if (!control.matches("[data-sidebar-trigger]")) {
3544
+ return;
3545
+ }
3546
+ const sidebar = this._document.getElementById(control.getAttribute("aria-controls"));
3547
+ const Offcanvas = this._bootstrap("Offcanvas");
3548
+ if (sidebar && Offcanvas) {
3549
+ this._offcanvasTrigger = control;
3550
+ this._offcanvas = Offcanvas.getOrCreateInstance(sidebar);
3551
+ this._offcanvas.toggle();
3552
+ }
3553
+ }
3554
+ _handleSubmenuClick(event) {
3555
+ const target = event.target;
3556
+ const trigger = target instanceof this._window.Element ? target.closest(".sidebar-menu-sub-trigger") : null;
3557
+ const item = trigger?.closest(".sidebar-menu-item");
3558
+ if (!trigger || !item || !this._element.contains(item) || !this._isCollapsed()) {
3559
+ return;
3560
+ }
3561
+ event.preventDefault();
3562
+ event.stopImmediatePropagation();
3563
+ if (this._flyoutOwner === item && this._flyout) {
3564
+ this._closeFlyouts();
3565
+ } else {
3566
+ this._openFlyout(item);
3567
+ }
3568
+ }
3569
+ _handleOutsideFlyoutClick(event) {
3570
+ if (!this._flyout) {
3571
+ return;
3572
+ }
3573
+ if (this._flyout.contains(event.target)) {
3574
+ this._closeFlyouts();
3575
+ } else if (!this._flyoutOwner?.contains(event.target)) {
3576
+ this._closeFlyouts();
3577
+ }
3578
+ }
3579
+ _handleShortcut(event) {
3580
+ const target = event.target;
3581
+ const isEditable = target instanceof this._window.Element && (target.matches("input, textarea, select") || target.isContentEditable);
3582
+ if (event.defaultPrevented || event.isComposing || isEditable || !this._config.keyboard || !(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "b" || !this._isDesktop()) {
3583
+ return;
3584
+ }
3585
+ const preferred = this._document.querySelector(
3586
+ '[data-slot="sidebar-wrapper"][data-sidebar-key]'
3587
+ ) || this._document.querySelector('[data-slot="sidebar-wrapper"]');
3588
+ if (preferred !== this._element) {
3589
+ return;
3590
+ }
3591
+ event.preventDefault();
3592
+ this._closeFlyouts();
3593
+ this._toggle();
3594
+ }
3595
+ };
3596
+
3597
+ // src/js/components/slider.js
3598
+ var instances5 = /* @__PURE__ */ new WeakMap();
3599
+ function asNumber(value, fallback) {
3600
+ const number = Number(value);
3601
+ return Number.isFinite(number) ? number : fallback;
3602
+ }
3603
+ function inputMinimum(input) {
3604
+ return asNumber(input.min, 0);
3605
+ }
3606
+ function inputMaximum(input) {
3607
+ return asNumber(input.max, 100);
3608
+ }
3609
+ function inputStep(input) {
3610
+ if (!input.step || input.step === "any") {
3611
+ return 1;
3612
+ }
3613
+ const step = Number(input.step);
3614
+ return Number.isFinite(step) && step > 0 ? step : 1;
3615
+ }
3616
+ function clamp2(value, min, max) {
3617
+ return Math.min(Math.max(value, min), max);
3618
+ }
3619
+ function decimalPlaces(value) {
3620
+ const text = String(value);
3621
+ if (!text.includes(".")) {
3622
+ return 0;
3623
+ }
3624
+ return text.split(".").at(-1).length;
3625
+ }
3626
+ function snapToStep(value, input) {
3627
+ const min = inputMinimum(input);
3628
+ const max = inputMaximum(input);
3629
+ if (input.step === "any") {
3630
+ return Number(clamp2(value, min, max).toFixed(4));
3631
+ }
3632
+ const step = inputStep(input);
3633
+ const snapped = min + Math.round((value - min) / step) * step;
3634
+ const precision = Math.max(decimalPlaces(step), decimalPlaces(min));
3635
+ return clamp2(Number(snapped.toFixed(precision)), min, max);
3636
+ }
3637
+ function valuePercent(input) {
3638
+ const min = inputMinimum(input);
3639
+ const max = inputMaximum(input);
3640
+ if (max <= min) {
3641
+ return 0;
3642
+ }
3643
+ return (asNumber(input.value, min) - min) / (max - min) * 100;
3644
+ }
3645
+ function displayValue(input) {
3646
+ return input.value || String(inputMinimum(input));
3647
+ }
3648
+ var MooSlider = class _MooSlider {
3649
+ static getInstance(element) {
3650
+ return element?.nodeType === 1 ? instances5.get(element) || null : null;
3651
+ }
3652
+ static getOrCreateInstance(element, config = {}) {
3653
+ return _MooSlider.getInstance(element) || new _MooSlider(element, config);
3654
+ }
3655
+ constructor(element, config = {}) {
3656
+ if (element?.nodeType !== 1 || !element.matches("[data-slider]")) {
3657
+ throw new TypeError("MooSlider requires a [data-slider] root element.");
3658
+ }
3659
+ const existing = instances5.get(element);
3660
+ if (existing) {
3661
+ return existing;
3662
+ }
3663
+ this._element = element;
3664
+ this._document = element.ownerDocument;
3665
+ this._window = this._document.defaultView;
3666
+ this._config = { ...config };
3667
+ this._listeners = [];
3668
+ this._dragListeners = [];
3669
+ this._draggingInput = null;
3670
+ this._disposed = false;
3671
+ this._inputs = Array.from(
3672
+ element.querySelectorAll(':scope input[type="range"][data-slider-input]')
3673
+ );
3674
+ this._track = element.querySelector("[data-slider-track]");
3675
+ this._output = element.querySelector("[data-slider-output]");
3676
+ if (this._inputs.length < 1 || this._inputs.length > 2) {
3677
+ throw new TypeError("MooSlider requires one or two native range inputs.");
3678
+ }
3679
+ if (!this._track) {
3680
+ throw new TypeError("MooSlider requires a [data-slider-track] element.");
3681
+ }
3682
+ instances5.set(element, this);
3683
+ this._bindEvents();
3684
+ this._sync();
3685
+ }
3686
+ dispose() {
3687
+ if (this._disposed) return;
3688
+ this._disposed = true;
3689
+ this._clearDragListeners();
3690
+ this._clearPointerFocus();
3691
+ this._listeners.forEach(({ target, type, handler, options }) => {
3692
+ target.removeEventListener(type, handler, options);
3693
+ });
3694
+ this._listeners = [];
3695
+ if (instances5.get(this._element) === this) {
3696
+ instances5.delete(this._element);
3697
+ }
3698
+ }
3699
+ _listen(target, type, handler, options) {
3700
+ if (!target) {
3701
+ return;
3702
+ }
3703
+ target.addEventListener(type, handler, options);
3704
+ this._listeners.push({ target, type, handler, options });
3705
+ }
3706
+ _listenDuringDrag(target, type, handler, options) {
3707
+ if (!target) {
3708
+ return;
3709
+ }
3710
+ target.addEventListener(type, handler, options);
3711
+ this._dragListeners.push({ target, type, handler, options });
3712
+ }
3713
+ _clearDragListeners() {
3714
+ this._dragListeners.forEach(({ target, type, handler, options }) => {
3715
+ target.removeEventListener(type, handler, options);
3716
+ });
3717
+ this._dragListeners = [];
3718
+ this._draggingInput = null;
3719
+ this._element.removeAttribute("data-slider-dragging");
3720
+ }
3721
+ _bindEvents() {
3722
+ this._inputs.forEach((input) => {
3723
+ this._listen(input, "input", () => this._sync(input));
3724
+ this._listen(input, "change", () => {
3725
+ this._sync(input);
3726
+ this._trigger("change");
3727
+ });
3728
+ this._listen(input, "keydown", () => this._clearPointerFocus());
3729
+ this._listen(input, "focusout", () => this._clearPointerFocus());
3730
+ });
3731
+ const forms = new Set(
3732
+ this._inputs.map((input) => input.form || input.closest?.("form")).filter(Boolean)
3733
+ );
3734
+ forms.forEach((form) => {
3735
+ this._listen(form, "reset", () => {
3736
+ this._window.setTimeout(() => this._sync(), 0);
3737
+ });
3738
+ });
3739
+ this._listen(this._track, "pointerdown", (event) => this._handleTrackPointer(event));
3740
+ }
3741
+ _trigger(name) {
3742
+ this._element.dispatchEvent(
3743
+ new this._window.CustomEvent(`${name}.moo.slider`, {
3744
+ bubbles: true
3745
+ })
3746
+ );
3747
+ }
3748
+ _isRange() {
3749
+ return this._inputs.length === 2;
3750
+ }
3751
+ _sync(changedInput = null) {
3752
+ if (this._isRange()) {
3753
+ const [startInput, endInput] = this._inputs;
3754
+ const start = asNumber(startInput.value, inputMinimum(startInput));
3755
+ const end = asNumber(endInput.value, inputMaximum(endInput));
3756
+ if (start > end) {
3757
+ if (changedInput === endInput) {
3758
+ endInput.value = startInput.value;
3759
+ } else {
3760
+ startInput.value = endInput.value;
3761
+ }
3762
+ }
3763
+ this._writeFill(valuePercent(startInput), valuePercent(endInput));
3764
+ this._writeOutput(`${displayValue(startInput)} - ${displayValue(endInput)}`);
3765
+ this._syncAria(startInput);
3766
+ this._syncAria(endInput);
3767
+ return;
3768
+ }
3769
+ const input = this._inputs[0];
3770
+ this._writeFill(0, valuePercent(input));
3771
+ this._writeOutput(displayValue(input));
3772
+ this._syncAria(input);
3773
+ }
3774
+ _syncAria(input) {
3775
+ input.setAttribute("aria-valuemin", String(inputMinimum(input)));
3776
+ input.setAttribute("aria-valuemax", String(inputMaximum(input)));
3777
+ input.setAttribute("aria-valuenow", displayValue(input));
3778
+ }
3779
+ _writeFill(start, end) {
3780
+ const clampedStart = clamp2(start, 0, 100);
3781
+ const clampedEnd = clamp2(end, 0, 100);
3782
+ this._element.style.setProperty("--moo-slider-start", `${Math.min(clampedStart, clampedEnd)}%`);
3783
+ this._element.style.setProperty("--moo-slider-end", `${Math.max(clampedStart, clampedEnd)}%`);
3784
+ }
3785
+ _writeOutput(value) {
3786
+ if (this._output) {
3787
+ this._output.textContent = value;
3788
+ }
3789
+ }
3790
+ _handleTrackPointer(event) {
3791
+ if (event.button !== void 0 && event.button !== 0) {
3792
+ return;
3793
+ }
3794
+ const enabledInputs = this._inputs.filter((input) => !input.disabled);
3795
+ if (!enabledInputs.length) {
3796
+ return;
3797
+ }
3798
+ event.preventDefault?.();
3799
+ const percent = this._pointerPercent(event);
3800
+ const target = this._nearestInput(percent * 100, enabledInputs);
3801
+ this._setPointerFocus();
3802
+ this._startDrag(target, event);
3803
+ this._setInputFromPointer(target, event);
3804
+ }
3805
+ _setPointerFocus() {
3806
+ this._element.setAttribute("data-slider-pointer-focus", "true");
3807
+ }
3808
+ _clearPointerFocus() {
3809
+ this._element.removeAttribute("data-slider-pointer-focus");
3810
+ }
3811
+ _startDrag(input, event) {
3812
+ this._clearDragListeners();
3813
+ this._draggingInput = input;
3814
+ this._element.setAttribute("data-slider-dragging", "true");
3815
+ this._track.setPointerCapture?.(event.pointerId);
3816
+ const pointerId = event.pointerId;
3817
+ const samePointer = (nextEvent) => pointerId === void 0 || nextEvent.pointerId === pointerId;
3818
+ const onMove = (moveEvent) => {
3819
+ if (!samePointer(moveEvent) || this._draggingInput !== input) {
3820
+ return;
3821
+ }
3822
+ moveEvent.preventDefault?.();
3823
+ this._setInputFromPointer(input, moveEvent);
3824
+ };
3825
+ const onEnd = (endEvent) => {
3826
+ if (!samePointer(endEvent) || this._draggingInput !== input) {
3827
+ return;
3828
+ }
3829
+ endEvent.preventDefault?.();
3830
+ this._setInputFromPointer(input, endEvent, false);
3831
+ input.dispatchEvent(new this._window.Event("change", { bubbles: true }));
3832
+ this._track.releasePointerCapture?.(pointerId);
3833
+ this._clearDragListeners();
3834
+ };
3835
+ this._listenDuringDrag(this._document, "pointermove", onMove);
3836
+ this._listenDuringDrag(this._document, "pointerup", onEnd);
3837
+ this._listenDuringDrag(this._document, "pointercancel", onEnd);
3838
+ }
3839
+ _setInputFromPointer(input, event, emitInput = true) {
3840
+ const percent = this._pointerPercent(event);
3841
+ const min = inputMinimum(input);
3842
+ const max = inputMaximum(input);
3843
+ input.value = String(snapToStep(min + percent * (max - min), input));
3844
+ input.focus({ preventScroll: true });
3845
+ this._sync(input);
3846
+ if (emitInput) {
3847
+ input.dispatchEvent(new this._window.Event("input", { bubbles: true }));
3848
+ }
3849
+ }
3850
+ _pointerPercent(event) {
3851
+ const rect = this._track.getBoundingClientRect();
3852
+ const orientation = this._element.dataset.sliderOrientation || "horizontal";
3853
+ if (orientation === "vertical") {
3854
+ return clamp2((rect.bottom - event.clientY) / (rect.height || 1), 0, 1);
3855
+ }
3856
+ return this._horizontalPointerPercent(event, rect);
3857
+ }
3858
+ _horizontalPointerPercent(event, rect) {
3859
+ const direction = this._window.getComputedStyle?.(this._element).direction;
3860
+ const offset = direction === "rtl" ? rect.right - event.clientX : event.clientX - rect.left;
3861
+ return clamp2(offset / (rect.width || 1), 0, 1);
3862
+ }
3863
+ _nearestInput(percent, inputs) {
3864
+ if (inputs.length === 1) {
3865
+ return inputs[0];
3866
+ }
3867
+ const [startInput, endInput] = inputs;
3868
+ const startPercent = valuePercent(startInput);
3869
+ const endPercent = valuePercent(endInput);
3870
+ const startDistance = Math.abs(startPercent - percent);
3871
+ const endDistance = Math.abs(endPercent - percent);
3872
+ if (startDistance === endDistance) {
3873
+ return percent >= startPercent ? endInput : startInput;
3874
+ }
3875
+ return endDistance < startDistance ? endInput : startInput;
3876
+ }
3877
+ };
3878
+
3879
+ // src/js/components/sheet.js
3880
+ var states = /* @__PURE__ */ new WeakMap();
3881
+ function initSheets(root = document) {
3882
+ if (states.has(root)) {
3883
+ return states.get(root);
3884
+ }
3885
+ const documentRoot = root.nodeType === 9 ? root : root.ownerDocument;
3886
+ const Offcanvas = documentRoot?.defaultView?.bootstrap?.Offcanvas;
3887
+ if (!Offcanvas) {
3888
+ return () => {
3889
+ };
3890
+ }
3891
+ const sheets = Array.from(root.querySelectorAll(".offcanvas.sheet"));
3892
+ const focusInitialField = (event) => {
3893
+ if (event.target === event.currentTarget) {
3894
+ event.currentTarget.querySelector("[autofocus]:not(:disabled)")?.focus();
3895
+ }
3896
+ };
3897
+ const dispose = () => {
3898
+ if (states.get(root) !== dispose) {
3899
+ return;
3900
+ }
3901
+ sheets.forEach((sheet) => {
3902
+ sheet.removeEventListener("shown.bs.offcanvas", focusInitialField);
3903
+ });
3904
+ states.delete(root);
3905
+ };
3906
+ states.set(root, dispose);
3907
+ sheets.forEach((sheet) => {
3908
+ sheet.addEventListener("shown.bs.offcanvas", focusInitialField);
3909
+ if (sheet.dataset.sheetOpenOnLoad === "true") {
3910
+ sheet.removeAttribute("data-sheet-open-on-load");
3911
+ Offcanvas.getOrCreateInstance(sheet).show();
3912
+ }
3913
+ });
3914
+ return dispose;
3915
+ }
3916
+
3917
+ // src/js/moo-ui.js
3918
+ async function loadChart() {
3919
+ const { default: Chart } = await import("./chart.js");
3920
+ return Chart;
3921
+ }
3922
+ var MooUI = {
3923
+ loadChart,
3924
+ Combobox,
3925
+ ContextMenu,
3926
+ DataTable,
3927
+ Datepicker: MooDatepicker,
3928
+ MooCalendar,
3929
+ MooDateRangePicker,
3930
+ Sidebar,
3931
+ Slider: MooSlider,
3932
+ initSheets
3933
+ };
3934
+ var moo_ui_default = MooUI;
3935
+ export {
3936
+ Combobox,
3937
+ ContextMenu,
3938
+ DataTable,
3939
+ MooDatepicker as Datepicker,
3940
+ MooCalendar,
3941
+ MooDateRangePicker,
3942
+ Sidebar,
3943
+ MooSlider as Slider,
3944
+ moo_ui_default as default,
3945
+ initSheets,
3946
+ loadChart
3947
+ };