@cahyo-dimas/freeday 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/LICENSE +21 -0
  3. package/README.md +205 -0
  4. package/adapters/blazor/freeday-blazor.js +79 -0
  5. package/adapters/react/components/FdyAutocomplete.tsx +159 -0
  6. package/adapters/react/components/FdyCascade.tsx +230 -0
  7. package/adapters/react/components/FdyCfl.tsx +362 -0
  8. package/adapters/react/components/FdyChart.tsx +69 -0
  9. package/adapters/react/components/FdyCombo.tsx +166 -0
  10. package/adapters/react/components/FdyDateRange.tsx +66 -0
  11. package/adapters/react/components/FdyDatepicker.tsx +354 -0
  12. package/adapters/react/index.d.ts +37 -0
  13. package/adapters/react/index.js +9 -0
  14. package/adapters/react/tsconfig.json +14 -0
  15. package/adapters/react/useFreeday.js +44 -0
  16. package/adapters/react/usePopover.ts +64 -0
  17. package/adapters/vue/components/FdyAutocomplete.vue +174 -0
  18. package/adapters/vue/components/FdyCascade.vue +241 -0
  19. package/adapters/vue/components/FdyCfl.vue +354 -0
  20. package/adapters/vue/components/FdyChart.vue +89 -0
  21. package/adapters/vue/components/FdyCombo.vue +232 -0
  22. package/adapters/vue/components/FdyDateRange.vue +78 -0
  23. package/adapters/vue/components/FdyDatepicker.vue +333 -0
  24. package/adapters/vue/index.d.ts +44 -0
  25. package/adapters/vue/index.js +8 -0
  26. package/adapters/vue/useFreeday.js +44 -0
  27. package/adapters/vue/usePopover.ts +78 -0
  28. package/dist/freeday-autocomplete.js +135 -0
  29. package/dist/freeday-breakpoint.js +51 -0
  30. package/dist/freeday-carousel.js +111 -0
  31. package/dist/freeday-cascade.js +256 -0
  32. package/dist/freeday-cfl.js +213 -0
  33. package/dist/freeday-chart.js +403 -0
  34. package/dist/freeday-chip.js +83 -0
  35. package/dist/freeday-datepicker.js +321 -0
  36. package/dist/freeday-datetime.js +59 -0
  37. package/dist/freeday-drawer.js +43 -0
  38. package/dist/freeday-form.js +181 -0
  39. package/dist/freeday-mask.js +114 -0
  40. package/dist/freeday-menu.js +93 -0
  41. package/dist/freeday-popover.js +69 -0
  42. package/dist/freeday-rating.js +50 -0
  43. package/dist/freeday-select.js +194 -0
  44. package/dist/freeday-slider.js +34 -0
  45. package/dist/freeday-stepper.js +90 -0
  46. package/dist/freeday-table.js +475 -0
  47. package/dist/freeday-tabs.js +68 -0
  48. package/dist/freeday-timepicker.js +180 -0
  49. package/dist/freeday-toast.js +84 -0
  50. package/dist/freeday-tree.js +94 -0
  51. package/dist/freeday-upload.js +206 -0
  52. package/dist/freeday.bundle.css +1352 -0
  53. package/dist/freeday.css +1007 -0
  54. package/dist/freeday.js +3632 -0
  55. package/dist/freeday.tokens.css +343 -0
  56. package/package.json +72 -0
  57. package/src/base.css +20 -0
  58. package/src/components/.gitkeep +0 -0
  59. package/src/components/accordion.css +13 -0
  60. package/src/components/alert.css +18 -0
  61. package/src/components/app-shell.css +88 -0
  62. package/src/components/appbar.css +15 -0
  63. package/src/components/autocomplete.css +12 -0
  64. package/src/components/avatar.css +8 -0
  65. package/src/components/badge.css +27 -0
  66. package/src/components/breadcrumb.css +9 -0
  67. package/src/components/breakpoints.css +15 -0
  68. package/src/components/button.css +58 -0
  69. package/src/components/card.css +9 -0
  70. package/src/components/carousel.css +17 -0
  71. package/src/components/cascade.css +36 -0
  72. package/src/components/cfl.css +28 -0
  73. package/src/components/chart.css +55 -0
  74. package/src/components/chip.css +18 -0
  75. package/src/components/combo.css +27 -0
  76. package/src/components/datepicker.css +44 -0
  77. package/src/components/datetimepicker.css +8 -0
  78. package/src/components/description-list.css +6 -0
  79. package/src/components/divider.css +3 -0
  80. package/src/components/drawer.css +25 -0
  81. package/src/components/file-upload.css +34 -0
  82. package/src/components/filterbar.css +25 -0
  83. package/src/components/form-grid.css +8 -0
  84. package/src/components/input-group.css +22 -0
  85. package/src/components/input.css +13 -0
  86. package/src/components/kbd.css +2 -0
  87. package/src/components/menu.css +28 -0
  88. package/src/components/modal.css +22 -0
  89. package/src/components/pagination.css +8 -0
  90. package/src/components/progress.css +7 -0
  91. package/src/components/rating.css +10 -0
  92. package/src/components/selection.css +25 -0
  93. package/src/components/skeleton.css +7 -0
  94. package/src/components/slider.css +12 -0
  95. package/src/components/spinner.css +6 -0
  96. package/src/components/states.css +8 -0
  97. package/src/components/stepper.css +21 -0
  98. package/src/components/table.css +55 -0
  99. package/src/components/tabs.css +10 -0
  100. package/src/components/timeline.css +12 -0
  101. package/src/components/timepicker.css +22 -0
  102. package/src/components/toast.css +17 -0
  103. package/src/components/tooltip.css +6 -0
  104. package/src/components/tree.css +21 -0
  105. package/src/freeday-autocomplete.js +135 -0
  106. package/src/freeday-breakpoint.js +51 -0
  107. package/src/freeday-carousel.js +111 -0
  108. package/src/freeday-cascade.js +256 -0
  109. package/src/freeday-cfl.js +213 -0
  110. package/src/freeday-chart.js +403 -0
  111. package/src/freeday-chip.js +83 -0
  112. package/src/freeday-datepicker.js +321 -0
  113. package/src/freeday-datetime.js +59 -0
  114. package/src/freeday-drawer.js +43 -0
  115. package/src/freeday-form.js +181 -0
  116. package/src/freeday-mask.js +114 -0
  117. package/src/freeday-menu.js +93 -0
  118. package/src/freeday-popover.js +69 -0
  119. package/src/freeday-rating.js +50 -0
  120. package/src/freeday-select.js +194 -0
  121. package/src/freeday-slider.js +34 -0
  122. package/src/freeday-stepper.js +90 -0
  123. package/src/freeday-table.js +475 -0
  124. package/src/freeday-tabs.js +68 -0
  125. package/src/freeday-timepicker.js +180 -0
  126. package/src/freeday-toast.js +84 -0
  127. package/src/freeday-tree.js +94 -0
  128. package/src/freeday-upload.js +206 -0
  129. package/tokens/breakpoints.mjs +4 -0
  130. package/tokens/tokens.json +123 -0
@@ -0,0 +1,83 @@
1
+ /* Freeday — chip enhancer (optional, zero-dependency).
2
+ * Two behaviours, both progressive enhancement:
3
+ *
4
+ * 1. Removable chips — any .fdy-chip__remove button removes its chip and emits a
5
+ * bubbling "fdy-chip-remove" (detail {value}).
6
+ * 2. Choice / filter groups — a [data-fdy-chips] container of interactive chips
7
+ * (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
8
+ * on click. Add data-single for one-at-a-time (radio-like) selection; filter
9
+ * chips get a leading check when pressed. Emits "fdy-chip-change" on the group
10
+ * (detail {value, pressed, selected}).
11
+ */
12
+ (function () {
13
+ 'use strict';
14
+
15
+ var CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>';
16
+
17
+ function initRemove(btn) {
18
+ if (btn.dataset.fdyChipRmReady === '1') return;
19
+ btn.dataset.fdyChipRmReady = '1';
20
+ btn.addEventListener('click', function () {
21
+ var chip = btn.closest('.fdy-chip');
22
+ if (!chip) return;
23
+ var value = chip.getAttribute('data-value');
24
+ if (!value) {
25
+ // Derive the label from the chip text, minus the remove button glyph.
26
+ var clone = chip.cloneNode(true);
27
+ var rm = clone.querySelector('.fdy-chip__remove');
28
+ if (rm) rm.remove();
29
+ value = (clone.textContent || '').trim();
30
+ }
31
+ chip.dispatchEvent(new CustomEvent('fdy-chip-remove', { bubbles: true, detail: { value: value } }));
32
+ chip.remove();
33
+ });
34
+ }
35
+
36
+ function initGroup(group) {
37
+ if (group.dataset.fdyChipsReady === '1') return;
38
+ group.dataset.fdyChipsReady = '1';
39
+ var single = group.hasAttribute('data-single');
40
+ if (!group.getAttribute('role')) group.setAttribute('role', 'group');
41
+ var groupLabel = group.getAttribute('data-label');
42
+ if (groupLabel && !group.getAttribute('aria-label')) group.setAttribute('aria-label', groupLabel);
43
+
44
+ var chips = Array.prototype.slice.call(group.querySelectorAll('.fdy-chip--choice, .fdy-chip--filter'));
45
+ function valueOf(chip) { return chip.getAttribute('data-value') || (chip.textContent || '').trim(); }
46
+ function selected() {
47
+ return chips.filter(function (c) { return c.getAttribute('aria-pressed') === 'true'; }).map(valueOf);
48
+ }
49
+
50
+ chips.forEach(function (chip) {
51
+ if (!chip.hasAttribute('aria-pressed')) chip.setAttribute('aria-pressed', 'false');
52
+ if (chip.classList.contains('fdy-chip--filter') && !chip.querySelector('.fdy-chip__check')) {
53
+ var check = document.createElement('span');
54
+ check.className = 'fdy-chip__check';
55
+ check.innerHTML = CHECK;
56
+ chip.insertBefore(check, chip.firstChild);
57
+ }
58
+ chip.addEventListener('click', function () {
59
+ var wasPressed = chip.getAttribute('aria-pressed') === 'true';
60
+ if (single) chips.forEach(function (c) { c.setAttribute('aria-pressed', 'false'); });
61
+ chip.setAttribute('aria-pressed', wasPressed ? 'false' : 'true');
62
+ group.dispatchEvent(new CustomEvent('fdy-chip-change', {
63
+ bubbles: true,
64
+ detail: { value: valueOf(chip), pressed: !wasPressed, selected: selected() }
65
+ }));
66
+ });
67
+ });
68
+ }
69
+
70
+ function initAll(context) {
71
+ var root = context || document;
72
+ Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-chips]'), initGroup);
73
+ Array.prototype.forEach.call(root.querySelectorAll('.fdy-chip__remove'), initRemove);
74
+ }
75
+
76
+ if (document.readyState === 'loading') {
77
+ document.addEventListener('DOMContentLoaded', function () { initAll(); });
78
+ } else {
79
+ initAll();
80
+ }
81
+
82
+ window.FreedayChip = { initGroup: initGroup, initRemove: initRemove, initAll: initAll };
83
+ })();
@@ -0,0 +1,321 @@
1
+ /* Freeday — date picker enhancer (optional, zero-dependency).
2
+ * Builds an input-styled trigger + calendar popover from an empty wrapper.
3
+ * Locale comes from <html lang> (via Intl) — month/weekday/value formatting is not hardcoded.
4
+ *
5
+ * Markup contract:
6
+ * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
7
+ * data-placeholder="Pilih tanggal" data-min="2026-01-01" data-max="2026-12-31"></div>
8
+ * - Range: <div data-fdy-daterange role="group" aria-label="Rentang tanggal">
9
+ * <div data-fdy-datepicker data-role="from" data-placeholder="Dari"></div>
10
+ * <span class="fdy-daterange__sep">–</span>
11
+ * <div data-fdy-datepicker data-role="to" data-placeholder="Sampai"></div>
12
+ * </div>
13
+ * The range links the two: the end can never precede the start (out-of-range days disable).
14
+ *
15
+ * Emits a bubbling "fdy-datepicker-change" CustomEvent on the wrapper: detail {value, date}.
16
+ * Keyboard in the grid: ←/→ ±1 day, ↑/↓ ±1 week, Home/End week edge, PageUp/Down ±month,
17
+ * Shift+PageUp/Down ±year, Enter/Space select, Esc close (focus returns to the trigger).
18
+ */
19
+ (function () {
20
+ 'use strict';
21
+
22
+ var LOCALE = document.documentElement.getAttribute('lang') || 'id';
23
+ var uidSeq = 0;
24
+ function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
25
+ function pad(n) { return n < 10 ? '0' + n : '' + n; }
26
+ function toISO(d) { return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); }
27
+ function parseISO(s) {
28
+ if (!s) return null;
29
+ var p = String(s).split('-');
30
+ if (p.length !== 3) return null;
31
+ var d = new Date(Number(p[0]), Number(p[1]) - 1, Number(p[2]));
32
+ return isNaN(d.getTime()) ? null : d;
33
+ }
34
+ function startOfDay(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }
35
+ function sameDay(a, b) { return !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); }
36
+ function addDays(d, n) { var x = new Date(d); x.setDate(x.getDate() + n); return x; }
37
+ function addMonths(d, n) { var x = new Date(d); x.setMonth(x.getMonth() + n); return x; }
38
+
39
+ var monthFmt = new Intl.DateTimeFormat(LOCALE, { month: 'long', year: 'numeric' });
40
+ var valueFmt = new Intl.DateTimeFormat(LOCALE, { day: '2-digit', month: 'short', year: 'numeric' });
41
+ var dayLabelFmt = new Intl.DateTimeFormat(LOCALE, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
42
+
43
+ function weekdayHeaders() {
44
+ var monday = new Date(2024, 0, 1); // a known Monday
45
+ var fmt = new Intl.DateTimeFormat(LOCALE, { weekday: 'short' });
46
+ var out = [];
47
+ for (var i = 0; i < 7; i++) out.push(fmt.format(addDays(monday, i)));
48
+ return out;
49
+ }
50
+
51
+ var CAL_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"></rect><path d="M16 2v4M8 2v4M3 10h18"></path></svg>';
52
+
53
+ function initPicker(wrap) {
54
+ if (wrap.dataset.fdyDpReady === '1') return wrap._fdyDp || null;
55
+ wrap.dataset.fdyDpReady = '1';
56
+ wrap.classList.add('fdy-datepicker');
57
+
58
+ var placeholder = wrap.getAttribute('data-placeholder') || 'Pilih tanggal';
59
+ var label = wrap.getAttribute('data-label') || 'Tanggal';
60
+ var selected = parseISO(wrap.getAttribute('data-value'));
61
+ var minDate = parseISO(wrap.getAttribute('data-min'));
62
+ var maxDate = parseISO(wrap.getAttribute('data-max'));
63
+ var rangeStart = null, rangeEnd = null; // for in-range shading
64
+ var view = startOfDay(selected || new Date());
65
+ view = new Date(view.getFullYear(), view.getMonth(), 1);
66
+ var focusDate = null;
67
+
68
+ var trigger = document.createElement('button');
69
+ trigger.type = 'button';
70
+ trigger.className = 'fdy-datepicker__trigger';
71
+ trigger.setAttribute('aria-haspopup', 'dialog');
72
+ trigger.setAttribute('aria-expanded', 'false');
73
+ trigger.setAttribute('aria-label', label);
74
+ var valueSpan = document.createElement('span');
75
+ valueSpan.className = 'fdy-datepicker__value';
76
+ trigger.appendChild(valueSpan);
77
+ // Icon variants (Freeday convention for JS-built controls): [data-fdy-no-icon] omits the
78
+ // icon; a [data-fdy-icon] child supplies a custom SVG; otherwise the default calendar.
79
+ if (!wrap.hasAttribute('data-fdy-no-icon')) {
80
+ var customIcon = wrap.querySelector('[data-fdy-icon]');
81
+ var icon = document.createElement('span');
82
+ icon.className = 'fdy-datepicker__icon';
83
+ icon.innerHTML = customIcon ? customIcon.innerHTML : CAL_ICON;
84
+ if (customIcon) customIcon.remove();
85
+ trigger.appendChild(icon);
86
+ }
87
+
88
+ var panel = document.createElement('div');
89
+ panel.className = 'fdy-datepicker__panel';
90
+ panel.setAttribute('role', 'dialog');
91
+ panel.setAttribute('aria-modal', 'false');
92
+ panel.setAttribute('aria-label', label);
93
+ panel.hidden = true;
94
+
95
+ wrap.appendChild(trigger);
96
+ wrap.appendChild(panel);
97
+
98
+ function updateDisplay() {
99
+ if (selected) {
100
+ valueSpan.textContent = valueFmt.format(selected);
101
+ valueSpan.classList.remove('fdy-datepicker__value--placeholder');
102
+ } else {
103
+ valueSpan.textContent = placeholder;
104
+ valueSpan.classList.add('fdy-datepicker__value--placeholder');
105
+ }
106
+ }
107
+
108
+ function isDisabled(d) {
109
+ if (minDate && startOfDay(d).getTime() < startOfDay(minDate).getTime()) return true;
110
+ if (maxDate && startOfDay(d).getTime() > startOfDay(maxDate).getTime()) return true;
111
+ return false;
112
+ }
113
+
114
+ function navButton(glyph, aria, onClick) {
115
+ var b = document.createElement('button');
116
+ b.type = 'button';
117
+ b.className = 'fdy-cal__nav';
118
+ b.textContent = glyph;
119
+ b.setAttribute('aria-label', aria);
120
+ b.addEventListener('click', onClick);
121
+ return b;
122
+ }
123
+
124
+ function render() {
125
+ panel.innerHTML = '';
126
+ var head = document.createElement('div');
127
+ head.className = 'fdy-cal__head';
128
+ var title = document.createElement('div');
129
+ title.className = 'fdy-cal__title';
130
+ title.id = uid('fdy-cal-title');
131
+ title.textContent = monthFmt.format(view);
132
+ panel.setAttribute('aria-labelledby', title.id);
133
+ head.appendChild(navButton('‹', 'Bulan sebelumnya', function () { view = addMonths(view, -1); render(); }));
134
+ head.appendChild(title);
135
+ head.appendChild(navButton('›', 'Bulan berikutnya', function () { view = addMonths(view, 1); render(); }));
136
+ panel.appendChild(head);
137
+
138
+ var grid = document.createElement('div');
139
+ grid.className = 'fdy-cal__grid';
140
+ grid.setAttribute('role', 'grid');
141
+ grid.setAttribute('aria-labelledby', title.id);
142
+ weekdayHeaders().forEach(function (w) {
143
+ var c = document.createElement('div');
144
+ c.className = 'fdy-cal__dow';
145
+ c.setAttribute('role', 'columnheader');
146
+ c.setAttribute('aria-label', w);
147
+ c.textContent = w;
148
+ grid.appendChild(c);
149
+ });
150
+
151
+ var first = new Date(view.getFullYear(), view.getMonth(), 1);
152
+ var offset = (first.getDay() + 6) % 7; // Monday-first
153
+ var startCell = addDays(first, -offset);
154
+ var today = new Date();
155
+ if (!focusDate) focusDate = selected || today;
156
+ // Keep the roving cell inside the visible month so the grid is always keyboard-reachable.
157
+ if (focusDate.getMonth() !== view.getMonth() || focusDate.getFullYear() !== view.getFullYear()) {
158
+ var dim = new Date(view.getFullYear(), view.getMonth() + 1, 0).getDate();
159
+ focusDate = new Date(view.getFullYear(), view.getMonth(), Math.min(focusDate.getDate(), dim));
160
+ }
161
+
162
+ for (var i = 0; i < 42; i++) {
163
+ var d = addDays(startCell, i);
164
+ var btn = document.createElement('button');
165
+ btn.type = 'button';
166
+ btn.className = 'fdy-cal__day';
167
+ btn.setAttribute('role', 'gridcell');
168
+ btn.setAttribute('aria-label', dayLabelFmt.format(d));
169
+ btn.textContent = d.getDate();
170
+ if (d.getMonth() !== view.getMonth()) btn.classList.add('is-outside');
171
+ if (sameDay(d, today)) btn.classList.add('is-today');
172
+ if (selected && sameDay(d, selected)) { btn.classList.add('is-selected'); btn.setAttribute('aria-selected', 'true'); }
173
+ if (rangeStart && rangeEnd) {
174
+ var t = startOfDay(d).getTime();
175
+ if (t > startOfDay(rangeStart).getTime() && t < startOfDay(rangeEnd).getTime()) btn.classList.add('in-range');
176
+ }
177
+ if (isDisabled(d)) btn.disabled = true;
178
+ btn.tabIndex = sameDay(d, focusDate) ? 0 : -1;
179
+ btn._date = d;
180
+ btn.addEventListener('click', (function (dd) { return function () { pick(dd); }; })(d));
181
+ grid.appendChild(btn);
182
+ }
183
+ grid.addEventListener('keydown', onGridKey);
184
+ panel.appendChild(grid);
185
+ }
186
+
187
+ function focusFocusDate() {
188
+ var cell = panel.querySelector('.fdy-cal__day[tabindex="0"]');
189
+ if (cell) cell.focus();
190
+ }
191
+
192
+ function moveFocus(next) {
193
+ focusDate = next;
194
+ if (next.getMonth() !== view.getMonth() || next.getFullYear() !== view.getFullYear()) {
195
+ view = new Date(next.getFullYear(), next.getMonth(), 1);
196
+ }
197
+ render();
198
+ focusFocusDate();
199
+ }
200
+
201
+ function onGridKey(e) {
202
+ var handled = true;
203
+ switch (e.key) {
204
+ case 'ArrowLeft': moveFocus(addDays(focusDate, -1)); break;
205
+ case 'ArrowRight': moveFocus(addDays(focusDate, 1)); break;
206
+ case 'ArrowUp': moveFocus(addDays(focusDate, -7)); break;
207
+ case 'ArrowDown': moveFocus(addDays(focusDate, 7)); break;
208
+ case 'Home': moveFocus(addDays(focusDate, -((focusDate.getDay() + 6) % 7))); break;
209
+ case 'End': moveFocus(addDays(focusDate, 6 - ((focusDate.getDay() + 6) % 7))); break;
210
+ case 'PageUp': moveFocus(addMonths(focusDate, e.shiftKey ? -12 : -1)); break;
211
+ case 'PageDown': moveFocus(addMonths(focusDate, e.shiftKey ? 12 : 1)); break;
212
+ case 'Enter':
213
+ case ' ': pick(focusDate); break;
214
+ case 'Escape': close(true); break;
215
+ default: handled = false;
216
+ }
217
+ if (handled) e.preventDefault();
218
+ }
219
+
220
+ function pick(d) {
221
+ if (isDisabled(d)) return;
222
+ selected = startOfDay(d);
223
+ focusDate = selected;
224
+ updateDisplay();
225
+ wrap.dispatchEvent(new CustomEvent('fdy-datepicker-change', {
226
+ bubbles: true, detail: { value: toISO(selected), date: new Date(selected) }
227
+ }));
228
+ close(true);
229
+ }
230
+
231
+ var _pop = null;
232
+ function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(panel, trigger); return _pop; }
233
+ function open() {
234
+ if (!panel.hidden) return;
235
+ focusDate = selected || focusDate || new Date();
236
+ view = new Date(focusDate.getFullYear(), focusDate.getMonth(), 1);
237
+ var p = popCtl(); if (p) p.show(); else panel.hidden = false;
238
+ trigger.setAttribute('aria-expanded', 'true');
239
+ trigger.classList.add('is-open');
240
+ render();
241
+ focusFocusDate();
242
+ document.addEventListener('click', onDocClick, true);
243
+ }
244
+
245
+ function close(returnFocus) {
246
+ if (panel.hidden) return;
247
+ var p = popCtl(); if (p) p.hide(); else panel.hidden = true;
248
+ trigger.setAttribute('aria-expanded', 'false');
249
+ trigger.classList.remove('is-open');
250
+ document.removeEventListener('click', onDocClick, true);
251
+ if (returnFocus === true) trigger.focus();
252
+ }
253
+
254
+ function onDocClick(e) {
255
+ if (!wrap.contains(e.target)) close(false);
256
+ }
257
+
258
+ trigger.addEventListener('click', function () { panel.hidden ? open() : close(true); });
259
+
260
+ updateDisplay();
261
+
262
+ var api = {
263
+ wrap: wrap,
264
+ getValue: function () { return selected ? toISO(selected) : ''; },
265
+ clear: function () { selected = null; updateDisplay(); if (!panel.hidden) render(); },
266
+ setMin: function (iso) { minDate = parseISO(iso); if (!panel.hidden) render(); },
267
+ setMax: function (iso) { maxDate = parseISO(iso); if (!panel.hidden) render(); },
268
+ setRange: function (fromIso, toIso) {
269
+ rangeStart = parseISO(fromIso);
270
+ rangeEnd = parseISO(toIso);
271
+ if (!panel.hidden) render();
272
+ }
273
+ };
274
+ wrap._fdyDp = api;
275
+ return api;
276
+ }
277
+
278
+ function initRange(root) {
279
+ if (root.dataset.fdyDrReady === '1') return;
280
+ root.dataset.fdyDrReady = '1';
281
+ var fromEl = root.querySelector('[data-fdy-datepicker][data-role="from"]');
282
+ var toEl = root.querySelector('[data-fdy-datepicker][data-role="to"]');
283
+ if (!fromEl || !toEl) return;
284
+ var fromApi = initPicker(fromEl);
285
+ var toApi = initPicker(toEl);
286
+ if (!fromApi || !toApi) return;
287
+
288
+ function sync() {
289
+ var f = fromApi.getValue();
290
+ var t = toApi.getValue();
291
+ toApi.setMin(f || null); // end can't precede start (one-directional, per Foundation)
292
+ fromApi.setRange(f, t); // start stays free — picking a later start clears the end below
293
+ toApi.setRange(f, t);
294
+ }
295
+ fromEl.addEventListener('fdy-datepicker-change', function () {
296
+ var f = fromApi.getValue(), t = toApi.getValue();
297
+ if (f && t && t < f) toApi.clear();
298
+ sync();
299
+ });
300
+ toEl.addEventListener('fdy-datepicker-change', function () {
301
+ var f = fromApi.getValue(), t = toApi.getValue();
302
+ if (f && t && t < f) fromApi.clear();
303
+ sync();
304
+ });
305
+ sync();
306
+ }
307
+
308
+ function initAll(context) {
309
+ var ctx = context || document;
310
+ Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-daterange]'), initRange);
311
+ Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-datepicker]'), initPicker);
312
+ }
313
+
314
+ if (document.readyState === 'loading') {
315
+ document.addEventListener('DOMContentLoaded', function () { initAll(); });
316
+ } else {
317
+ initAll();
318
+ }
319
+
320
+ window.FreedayDatepicker = { init: initPicker, initAll: initAll };
321
+ })();
@@ -0,0 +1,59 @@
1
+ /* Freeday — datetime picker composer (optional, zero-dependency).
2
+ * Combines a child date picker and time picker into one value. It does not build
3
+ * anything itself — freeday-datepicker.js and freeday-timepicker.js enhance the
4
+ * children; this only listens for their change events and emits a single combined
5
+ * "fdy-datetime-change" (detail {date, time, value}). value is "YYYY-MM-DDTHH:MM"
6
+ * when both parts are set, else "". Icon variants are controlled per child picker
7
+ * (data-fdy-no-icon / <template data-fdy-icon>), so a datetime picker inherits them.
8
+ *
9
+ * Markup: <div data-fdy-datetimepicker>
10
+ * <div data-fdy-datepicker data-value="2026-07-21"></div>
11
+ * <div data-fdy-timepicker data-value="14:30"></div></div>
12
+ */
13
+ (function () {
14
+ 'use strict';
15
+
16
+ function initDatetime(wrap) {
17
+ if (wrap.dataset.fdyDtReady === '1') return;
18
+ wrap.dataset.fdyDtReady = '1';
19
+ wrap.classList.add('fdy-datetimepicker');
20
+
21
+ var dp = wrap.querySelector('[data-fdy-datepicker]');
22
+ var tp = wrap.querySelector('[data-fdy-timepicker]');
23
+ if (!dp || !tp) return;
24
+
25
+ var date = dp.getAttribute('data-value') || '';
26
+ var time = tp.getAttribute('data-value') || '';
27
+
28
+ function emit() {
29
+ var value = (date && time) ? (date + 'T' + time) : '';
30
+ wrap.dispatchEvent(new CustomEvent('fdy-datetime-change', {
31
+ bubbles: true, detail: { date: date, time: time, value: value }
32
+ }));
33
+ }
34
+
35
+ // Listen for the children's bubbling change events (order-independent — no need
36
+ // for the child enhancers to have run before this composer).
37
+ wrap.addEventListener('fdy-datepicker-change', function (e) { date = e.detail.value; emit(); });
38
+ wrap.addEventListener('fdy-time-select', function (e) { time = e.detail.value; emit(); });
39
+
40
+ var api = {
41
+ wrap: wrap,
42
+ getValue: function () { return (date && time) ? (date + 'T' + time) : ''; }
43
+ };
44
+ wrap._fdyDt = api;
45
+ return api;
46
+ }
47
+
48
+ function initAll(context) {
49
+ Array.prototype.forEach.call((context || document).querySelectorAll('[data-fdy-datetimepicker]'), initDatetime);
50
+ }
51
+
52
+ if (document.readyState === 'loading') {
53
+ document.addEventListener('DOMContentLoaded', function () { initAll(); });
54
+ } else {
55
+ initAll();
56
+ }
57
+
58
+ window.FreedayDatetime = { init: initDatetime, initAll: initAll };
59
+ })();
@@ -0,0 +1,43 @@
1
+ /* Freeday — drawer enhancer (optional, zero-dependency).
2
+ * A trigger [data-fdy-drawer="drawerId"] opens the <dialog class="fdy-drawer" id="drawerId">
3
+ * as a modal side panel (native showModal gives focus-trap, Esc, and return-focus). Clicking
4
+ * the backdrop or any [data-close] inside closes it.
5
+ */
6
+ (function () {
7
+ 'use strict';
8
+
9
+ function initDrawer(dialog) {
10
+ if (dialog.dataset.fdyDrawerReady === '1') return;
11
+ dialog.dataset.fdyDrawerReady = '1';
12
+ // Click on the backdrop (target is the dialog box itself) closes it.
13
+ dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
14
+ Array.prototype.forEach.call(dialog.querySelectorAll('[data-close]'), function (b) {
15
+ b.addEventListener('click', function () { dialog.close(); });
16
+ });
17
+ }
18
+
19
+ function initTriggers(context) {
20
+ var ctx = context || document;
21
+ Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-drawer]'), function (btn) {
22
+ if (btn.dataset.fdyDrawerBound === '1') return;
23
+ var dialog = document.getElementById(btn.getAttribute('data-fdy-drawer'));
24
+ if (!dialog) return;
25
+ initDrawer(dialog);
26
+ btn.dataset.fdyDrawerBound = '1';
27
+ btn.addEventListener('click', function () {
28
+ if (typeof dialog.showModal === 'function') dialog.showModal();
29
+ else dialog.setAttribute('open', '');
30
+ });
31
+ });
32
+ // Init any drawer dialogs even without a wired trigger (backdrop/close still work).
33
+ Array.prototype.forEach.call(ctx.querySelectorAll('.fdy-drawer'), initDrawer);
34
+ }
35
+
36
+ if (document.readyState === 'loading') {
37
+ document.addEventListener('DOMContentLoaded', function () { initTriggers(); });
38
+ } else {
39
+ initTriggers();
40
+ }
41
+
42
+ window.FreedayDrawer = { init: initTriggers, initAll: initTriggers };
43
+ })();