@cahyo-dimas/freeday 2.0.0 → 2.2.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 (41) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/COMPONENTS.md +298 -2
  3. package/README.id.md +1 -1
  4. package/README.md +1 -1
  5. package/adapters/blazor/FdyAutocomplete.razor +4 -1
  6. package/adapters/blazor/FdyAutocomplete.razor.cs +26 -0
  7. package/adapters/blazor/FdyCascade.razor +6 -1
  8. package/adapters/blazor/FdyCascade.razor.cs +24 -0
  9. package/adapters/blazor/FdyCombo.razor +1 -0
  10. package/adapters/blazor/FdyCombo.razor.cs +13 -0
  11. package/adapters/blazor/FdyDatepicker.razor +15 -1
  12. package/adapters/blazor/FdyDatepicker.razor.cs +46 -0
  13. package/adapters/core/cfl-value.d.ts +11 -0
  14. package/adapters/core/cfl-value.js +31 -0
  15. package/adapters/react/components/FdyDrawer.tsx +3 -1
  16. package/adapters/react/components/FdyModal.tsx +3 -1
  17. package/adapters/react/index.d.ts +3 -0
  18. package/adapters/react/index.js +1 -0
  19. package/adapters/vue/components/FdyDrawer.vue +4 -1
  20. package/adapters/vue/components/FdyModal.vue +4 -1
  21. package/adapters/vue/index.d.ts +3 -0
  22. package/adapters/vue/index.js +1 -0
  23. package/dist/freeday-autocomplete.js +17 -1
  24. package/dist/freeday-carousel.js +5 -4
  25. package/dist/freeday-cascade.js +58 -7
  26. package/dist/freeday-cfl.js +5 -4
  27. package/dist/freeday-chart.js +33 -3
  28. package/dist/freeday-datepicker.js +109 -17
  29. package/dist/freeday-form.js +5 -4
  30. package/dist/freeday-mask.js +5 -4
  31. package/dist/freeday-select.js +18 -1
  32. package/dist/freeday-stepper.js +5 -4
  33. package/dist/freeday-table.js +9 -6
  34. package/dist/freeday-timepicker.js +19 -1
  35. package/dist/freeday-toast.js +5 -4
  36. package/dist/freeday-upload.js +5 -4
  37. package/dist/freeday.js +298 -64
  38. package/docs/agent-onboarding.md +9 -6
  39. package/docs/getting-started.md +15 -6
  40. package/docs/integrations.md +2 -2
  41. package/package.json +2 -2
package/dist/freeday.js CHANGED
@@ -250,6 +250,9 @@
250
250
  var _pop = null;
251
251
  function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(listbox, input); return _pop; }
252
252
  function open() {
253
+ /* The input carries these natively; a disabled one fires nothing, but a READONLY one still
254
+ takes focus and clicks, and the list used to open over a field nobody can edit. */
255
+ if (input.readOnly || input.disabled) return;
253
256
  if (!listbox.hidden) return;
254
257
  var p = popCtl(); if (p) p.show(); else listbox.hidden = false;
255
258
  input.setAttribute('aria-expanded', 'true');
@@ -344,7 +347,20 @@
344
347
  initAll();
345
348
  }
346
349
 
347
- window.FreedayAutocomplete = { init: initAutocomplete, initAll: initAll };
350
+ /* Same contract as the combo's: the states live on the input natively, and a host that stops
351
+ re-rendering after hydration needs a way to change them afterwards. */
352
+ function setState(root, state) {
353
+ var input = root ? root.querySelector('input') : null;
354
+ if (!input || !state) return;
355
+ if (state.disabled != null) input.disabled = !!state.disabled;
356
+ if (state.readonly != null) input.readOnly = !!state.readonly;
357
+ if (state.invalid != null) {
358
+ if (state.invalid) input.setAttribute('aria-invalid', 'true');
359
+ else input.removeAttribute('aria-invalid');
360
+ }
361
+ }
362
+
363
+ window.FreedayAutocomplete = { init: initAutocomplete, initAll: initAll, setState: setState };
348
364
  })();
349
365
 
350
366
  /* Freeday, breakpoint provider (optional, zero-dependency).
@@ -419,10 +435,11 @@
419
435
  'use strict';
420
436
 
421
437
 
422
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
423
- * path, and every one overridable per element, so a host that speaks another language (the
424
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
425
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
438
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
439
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
440
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
441
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
442
+ * hard-coded further down. */
426
443
  var TEXT = {
427
444
  position: '{n} of {total}',
428
445
  slide: 'Slide {n}'
@@ -596,11 +613,14 @@
596
613
  }
597
614
 
598
615
 
599
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
600
- * path, and every one overridable per element, so a host that speaks another language (the
601
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
602
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
616
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
617
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
618
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
619
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
620
+ * hard-coded further down. */
603
621
  var TEXT = {
622
+ label: 'Select',
623
+ placeholder: 'Select…',
604
624
  back: 'Back one level',
605
625
  submenu: '{label}, submenu'
606
626
  };
@@ -630,8 +650,8 @@
630
650
  var root = sourceUl ? parse(sourceUl) : [];
631
651
  if (sourceUl) sourceUl.remove();
632
652
 
633
- var label = wrap.getAttribute('data-label') || 'Select';
634
- var placeholder = wrap.getAttribute('data-placeholder') || 'Select…';
653
+ var label = wrap.getAttribute('data-label') || textOf(wrap, 'label');
654
+ var placeholder = wrap.getAttribute('data-placeholder') || textOf(wrap, 'placeholder');
635
655
  var sep = wrap.getAttribute('data-separator') || ' / ';
636
656
 
637
657
  var trigger = document.createElement('button');
@@ -746,7 +766,44 @@
746
766
 
747
767
  var _pop = null;
748
768
  function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(panel, trigger); return _pop; }
769
+
770
+ /* The three field states the CSS has always styled (`:disabled`, `[aria-readonly="true"]`,
771
+ `[aria-invalid="true"]`) and this enhancer never set, so only the stacks that re-implement
772
+ the control natively had them. Read from the seed at init and settable afterwards, because
773
+ a host that renders once — every Blazor wrapper does, `ShouldRender => false` — cannot
774
+ express a later change any other way. */
775
+ function flagOf(name) {
776
+ var v = wrap.getAttribute('data-' + name);
777
+ return v != null && v !== 'false';
778
+ }
779
+ /* Named `state*` to match the datepicker, where `is*` collided with an older function. */
780
+ var stateDisabled = flagOf('disabled');
781
+ var stateReadonly = flagOf('readonly');
782
+ var stateInvalid = flagOf('invalid');
783
+ /* `data-id` and `data-describedby`: the trigger this enhancer BUILDS is the element a form
784
+ has to point its label and its error text at, and the raw path had no way to say so. */
785
+ if (wrap.getAttribute('data-id')) trigger.id = wrap.getAttribute('data-id');
786
+ if (wrap.getAttribute('data-describedby')) trigger.setAttribute('aria-describedby', wrap.getAttribute('data-describedby'));
787
+
788
+ function applyState() {
789
+ trigger.disabled = stateDisabled;
790
+ if (stateReadonly) trigger.setAttribute('aria-readonly', 'true');
791
+ else trigger.removeAttribute('aria-readonly');
792
+ if (stateInvalid) trigger.setAttribute('aria-invalid', 'true');
793
+ else trigger.removeAttribute('aria-invalid');
794
+ wrap.classList.toggle('fdy-cascade--error', stateInvalid);
795
+ if ((stateDisabled || stateReadonly) && !panel.hidden) close(false);
796
+ }
797
+ function setState(next) {
798
+ if (!next) return;
799
+ if (next.disabled != null) stateDisabled = !!next.disabled;
800
+ if (next.readonly != null) stateReadonly = !!next.readonly;
801
+ if (next.invalid != null) stateInvalid = !!next.invalid;
802
+ applyState();
803
+ }
804
+
749
805
  function open() {
806
+ if (stateDisabled || stateReadonly) return;
750
807
  if (!panel.hidden) return;
751
808
  // Re-open at the selected leaf's level for quick re-selection.
752
809
  var trail = selectedValue ? pathTo(root, selectedValue, []) : null;
@@ -801,8 +858,11 @@
801
858
  valueSpan.classList.add('fdy-cascade__value--placeholder');
802
859
  }
803
860
 
861
+ applyState();
862
+
804
863
  var api = {
805
864
  wrap: wrap,
865
+ setState: setState,
806
866
  getValue: function () { return selectedValue; },
807
867
  clear: function () { selectedValue = ''; valueSpan.textContent = placeholder; valueSpan.classList.add('fdy-cascade__value--placeholder'); }
808
868
  };
@@ -823,7 +883,15 @@
823
883
  initAll();
824
884
  }
825
885
 
826
- window.FreedayCascade = { init: initCascade, initAll: initAll };
886
+ window.FreedayCascade = {
887
+ init: initCascade,
888
+ initAll: initAll,
889
+ /* Same reason as the datepicker's: a seed rendered once still has to be lockable later. */
890
+ setState: function (root, state) {
891
+ var api = root && root._fdyCascade ? root._fdyCascade : null;
892
+ if (api && api.setState) api.setState(state);
893
+ }
894
+ };
827
895
  })();
828
896
 
829
897
  /* Freeday, choose-from-list enhancer (optional, zero-dependency).
@@ -862,10 +930,11 @@
862
930
  }
863
931
 
864
932
 
865
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
866
- * path, and every one overridable per element, so a host that speaks another language (the
867
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
868
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
933
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
934
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
935
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
936
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
937
+ * hard-coded further down. */
869
938
  var TEXT = {
870
939
  selected: '{n} selected'
871
940
  };
@@ -1101,6 +1170,28 @@
1101
1170
 
1102
1171
  var NS = 'http://www.w3.org/2000/svg';
1103
1172
 
1173
+ /* User-facing strings. Two of them, and both shipped wrong until 2.2.0: the legend's fallback
1174
+ * label read `Seri 1` — Indonesian, three months after 2.0.0 turned every enhancer English —
1175
+ * and the donut's centre caption was hard-coded, so no host could rename it. Neither was
1176
+ * reachable by the guards: one goes into the DOM through `createTextNode`, the other through
1177
+ * `innerHTML`, and both guards look for `textContent` / `setAttribute`. Overridable per element
1178
+ * with `data-fdy-text-<key>`, like every other enhancer. */
1179
+ var TEXT = {
1180
+ series: 'Series {n}',
1181
+ total: 'Total'
1182
+ };
1183
+ function textAttr(root, key) {
1184
+ if (!root || !root.getAttribute) return null;
1185
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
1186
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
1187
+ }
1188
+ function textOf(root, key, vars) {
1189
+ var custom = textAttr(root, key);
1190
+ var s = custom != null && custom !== '' ? custom : TEXT[key];
1191
+ if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
1192
+ return s;
1193
+ }
1194
+
1104
1195
  // Categorical chart palette: 8 validated fixed-order slots (--chart-1..8). Series index i
1105
1196
  // (0-based) -> slot i+1; series beyond the 8-slot cap reuse --chart-8 (never cycled).
1106
1197
  function chartSlotVar(i) { return 'var(--chart-' + (i < 8 ? i + 1 : 8) + ')'; }
@@ -1309,7 +1400,7 @@
1309
1400
  var li = document.createElement('li');
1310
1401
  var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
1311
1402
  li.appendChild(sw);
1312
- li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
1403
+ li.appendChild(document.createTextNode(s.label || textOf(el, 'series', { n: si + 1 })));
1313
1404
  legend.appendChild(li);
1314
1405
  });
1315
1406
  el.appendChild(legend);
@@ -1483,8 +1574,16 @@
1483
1574
  ring.style.background = 'conic-gradient(' + stops.join(',') + ')';
1484
1575
  var center = document.createElement('div'); center.className = 'fdy-donut__center';
1485
1576
  var centerLabel = el.getAttribute('data-fdy-center');
1486
- center.innerHTML = centerLabel ? '<b></b>' : '<b></b><span>Total</span>';
1487
- center.querySelector('b').textContent = centerLabel != null ? centerLabel : String(total);
1577
+ var centerValue = document.createElement('b');
1578
+ centerValue.textContent = centerLabel != null ? centerLabel : String(total);
1579
+ center.appendChild(centerValue);
1580
+ /* Built rather than assigned as innerHTML: the caption is overridable now, and an author's
1581
+ string is not markup. */
1582
+ if (!centerLabel) {
1583
+ var centerCaption = document.createElement('span');
1584
+ centerCaption.textContent = textOf(el, 'total');
1585
+ center.appendChild(centerCaption);
1586
+ }
1488
1587
  ring.appendChild(center);
1489
1588
  var svg = svgEl('svg');
1490
1589
  svg.setAttribute('class', 'fdy-donut__hit');
@@ -1643,12 +1742,12 @@
1643
1742
  * Locale comes from <html lang> (via Intl), month/weekday/value formatting is not hardcoded.
1644
1743
  *
1645
1744
  * Markup contract:
1646
- * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
1647
- * data-placeholder="Pilih tanggal" data-min="2026-01-01" data-max="2026-12-31"></div>
1648
- * - Range: <div data-fdy-daterange role="group" aria-label="Rentang tanggal">
1649
- * <div data-fdy-datepicker data-role="from" data-placeholder="Dari"></div>
1745
+ * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Upload date"
1746
+ * data-placeholder="Choose a date" data-min="2026-01-01" data-max="2026-12-31"></div>
1747
+ * - Range: <div data-fdy-daterange role="group" aria-label="Date range">
1748
+ * <div data-fdy-datepicker data-role="from" data-placeholder="From"></div>
1650
1749
  * <span class="fdy-daterange__sep">–</span>
1651
- * <div data-fdy-datepicker data-role="to" data-placeholder="Sampai"></div>
1750
+ * <div data-fdy-datepicker data-role="to" data-placeholder="To"></div>
1652
1751
  * </div>
1653
1752
  * The range links the two: the end can never precede the start (out-of-range days disable).
1654
1753
  *
@@ -1664,6 +1763,47 @@
1664
1763
  weekday names back automatically. The FALLBACK follows the kit's default language, or a
1665
1764
  page without `lang` would read English labels around Indonesian month names. */
1666
1765
  var LOCALE = document.documentElement.getAttribute('lang') || 'en';
1766
+
1767
+ /* User-facing strings. English by default, and every one overridable per element with
1768
+ * `data-fdy-text-<key>`, so a host that speaks another language (an Indonesian app on the raw
1769
+ * path, and every Blazor app, whose picker IS this enhancer) supplies its own without forking
1770
+ * this file.
1771
+ *
1772
+ * This table arrived late, in 2.2.0: the ten labels below were written as literals passed to
1773
+ * `navButton()` / `titleButton()`, so the guard that proves no enhancer string is hard-coded
1774
+ * never saw them — it looks for the line that writes to the DOM, and here that line only ever
1775
+ * sees a variable. Month and weekday names are NOT here on purpose: they come from `Intl`
1776
+ * through the page's `lang`, which is a better hatch than anything the kit could invent.
1777
+ * The `{label}` in the three title strings is the period the button drills into. */
1778
+ var TEXT = {
1779
+ label: 'Date',
1780
+ placeholder: 'Choose a date',
1781
+ prevMonth: 'Previous month',
1782
+ nextMonth: 'Next month',
1783
+ prevYear: 'Previous year',
1784
+ nextYear: 'Next year',
1785
+ prevYears: 'Previous years',
1786
+ nextYears: 'Next years',
1787
+ chooseMonth: '{label}, choose month',
1788
+ chooseYear: '{label}, choose year',
1789
+ backToMonths: '{start} to {end}, back to months'
1790
+ };
1791
+ /* HTML lowercases attribute names, so a camelCase key like `prevMonth` can only ever be written
1792
+ as `data-fdy-text-prevmonth`, while the kebab form anybody would reach for,
1793
+ `data-fdy-text-prev-month`, becomes a DIFFERENT attribute the enhancer never reads, and the
1794
+ override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
1795
+ still resolves. */
1796
+ function textAttr(root, key) {
1797
+ if (!root || !root.getAttribute) return null;
1798
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
1799
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
1800
+ }
1801
+ function textOf(root, key, vars) {
1802
+ var custom = textAttr(root, key);
1803
+ var s = custom != null && custom !== '' ? custom : TEXT[key];
1804
+ if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
1805
+ return s;
1806
+ }
1667
1807
  var uidSeq = 0;
1668
1808
  function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
1669
1809
  function pad(n) { return n < 10 ? '0' + n : '' + n; }
@@ -1701,8 +1841,8 @@
1701
1841
  wrap.dataset.fdyDpReady = '1';
1702
1842
  wrap.classList.add('fdy-datepicker');
1703
1843
 
1704
- var placeholder = wrap.getAttribute('data-placeholder') || 'Choose a date';
1705
- var label = wrap.getAttribute('data-label') || 'Date';
1844
+ var placeholder = wrap.getAttribute('data-placeholder') || textOf(wrap, 'placeholder');
1845
+ var label = wrap.getAttribute('data-label') || textOf(wrap, 'label');
1706
1846
  var selected = parseISO(wrap.getAttribute('data-value'));
1707
1847
  var minDate = parseISO(wrap.getAttribute('data-min'));
1708
1848
  var maxDate = parseISO(wrap.getAttribute('data-max'));
@@ -1800,16 +1940,16 @@
1800
1940
  panel.innerHTML = '';
1801
1941
  var head = document.createElement('div');
1802
1942
  head.className = 'fdy-cal__head';
1803
- var title = titleButton(monthFmt.format(view), monthFmt.format(view) + ', choose month', function () {
1943
+ var title = titleButton(monthFmt.format(view), textOf(wrap, 'chooseMonth', { label: monthFmt.format(view) }), function () {
1804
1944
  mode = 'months';
1805
1945
  focusMonth = view.getMonth();
1806
1946
  render();
1807
1947
  focusMonthCell();
1808
1948
  });
1809
1949
  panel.setAttribute('aria-labelledby', title.id);
1810
- head.appendChild(navButton('‹', 'Previous month', function () { view = addMonths(view, -1); render(); }));
1950
+ head.appendChild(navButton('‹', textOf(wrap, 'prevMonth'), function () { view = addMonths(view, -1); render(); }));
1811
1951
  head.appendChild(title);
1812
- head.appendChild(navButton('›', 'Next month', function () { view = addMonths(view, 1); render(); }));
1952
+ head.appendChild(navButton('›', textOf(wrap, 'nextMonth'), function () { view = addMonths(view, 1); render(); }));
1813
1953
  panel.appendChild(head);
1814
1954
 
1815
1955
  var grid = document.createElement('div');
@@ -1866,16 +2006,16 @@
1866
2006
  var year = view.getFullYear();
1867
2007
  var head = document.createElement('div');
1868
2008
  head.className = 'fdy-cal__head';
1869
- var title = titleButton(String(year), year + ', choose year', function () {
2009
+ var title = titleButton(String(year), textOf(wrap, 'chooseYear', { label: year }), function () {
1870
2010
  mode = 'years';
1871
2011
  focusYear = year;
1872
2012
  render();
1873
2013
  focusYearCell();
1874
2014
  });
1875
2015
  panel.setAttribute('aria-labelledby', title.id);
1876
- head.appendChild(navButton('‹', 'Previous year', function () { view = addMonths(view, -12); render(); focusMonthCell(); }));
2016
+ head.appendChild(navButton('‹', textOf(wrap, 'prevYear'), function () { view = addMonths(view, -12); render(); focusMonthCell(); }));
1877
2017
  head.appendChild(title);
1878
- head.appendChild(navButton('›', 'Next year', function () { view = addMonths(view, 12); render(); focusMonthCell(); }));
2018
+ head.appendChild(navButton('›', textOf(wrap, 'nextYear'), function () { view = addMonths(view, 12); render(); focusMonthCell(); }));
1879
2019
  panel.appendChild(head);
1880
2020
 
1881
2021
  var grid = document.createElement('div');
@@ -1927,16 +2067,16 @@
1927
2067
  var end = start + YEARS_PER_PAGE - 1;
1928
2068
  var head = document.createElement('div');
1929
2069
  head.className = 'fdy-cal__head';
1930
- var title = titleButton(start + ' – ' + end, start + ' to ' + end + ', back to months', function () {
2070
+ var title = titleButton(start + ' – ' + end, textOf(wrap, 'backToMonths', { start: start, end: end }), function () {
1931
2071
  mode = 'months';
1932
2072
  focusMonth = view.getMonth();
1933
2073
  render();
1934
2074
  focusMonthCell();
1935
2075
  });
1936
2076
  panel.setAttribute('aria-labelledby', title.id);
1937
- head.appendChild(navButton('‹', 'Previous years', function () { moveYearFocus(focusYear - YEARS_PER_PAGE); }));
2077
+ head.appendChild(navButton('‹', textOf(wrap, 'prevYears'), function () { moveYearFocus(focusYear - YEARS_PER_PAGE); }));
1938
2078
  head.appendChild(title);
1939
- head.appendChild(navButton('›', 'Next years', function () { moveYearFocus(focusYear + YEARS_PER_PAGE); }));
2079
+ head.appendChild(navButton('›', textOf(wrap, 'nextYears'), function () { moveYearFocus(focusYear + YEARS_PER_PAGE); }));
1940
2080
  panel.appendChild(head);
1941
2081
 
1942
2082
  var grid = document.createElement('div');
@@ -2097,7 +2237,46 @@
2097
2237
 
2098
2238
  var _pop = null;
2099
2239
  function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(panel, trigger); return _pop; }
2240
+
2241
+ /* The three field states the CSS has always styled (`:disabled`, `[aria-readonly="true"]`,
2242
+ `[aria-invalid="true"]`) and this enhancer never set, so only the stacks that re-implement
2243
+ the control natively had them. Read from the seed at init and settable afterwards, because
2244
+ a host that renders once — every Blazor wrapper does, `ShouldRender => false` — cannot
2245
+ express a later change any other way. */
2246
+ function flagOf(name) {
2247
+ var v = wrap.getAttribute('data-' + name);
2248
+ return v != null && v !== 'false';
2249
+ }
2250
+ /* Named `state*`, not `is*`: this file already has an `isDisabled(date)` deciding whether a
2251
+ DAY falls outside min/max, and shadowing it with a boolean made the day grid throw on every
2252
+ render — silently, since the panel still opened and only its cells went missing. */
2253
+ var stateDisabled = flagOf('disabled');
2254
+ var stateReadonly = flagOf('readonly');
2255
+ var stateInvalid = flagOf('invalid');
2256
+ /* `data-id` and `data-describedby`: the trigger this enhancer BUILDS is the element a form
2257
+ has to point its label and its error text at, and the raw path had no way to say so. */
2258
+ if (wrap.getAttribute('data-id')) trigger.id = wrap.getAttribute('data-id');
2259
+ if (wrap.getAttribute('data-describedby')) trigger.setAttribute('aria-describedby', wrap.getAttribute('data-describedby'));
2260
+
2261
+ function applyState() {
2262
+ trigger.disabled = stateDisabled;
2263
+ if (stateReadonly) trigger.setAttribute('aria-readonly', 'true');
2264
+ else trigger.removeAttribute('aria-readonly');
2265
+ if (stateInvalid) trigger.setAttribute('aria-invalid', 'true');
2266
+ else trigger.removeAttribute('aria-invalid');
2267
+ wrap.classList.toggle('fdy-datepicker--error', stateInvalid);
2268
+ if ((stateDisabled || stateReadonly) && !panel.hidden) close(false);
2269
+ }
2270
+ function setState(next) {
2271
+ if (!next) return;
2272
+ if (next.disabled != null) stateDisabled = !!next.disabled;
2273
+ if (next.readonly != null) stateReadonly = !!next.readonly;
2274
+ if (next.invalid != null) stateInvalid = !!next.invalid;
2275
+ applyState();
2276
+ }
2277
+
2100
2278
  function open() {
2279
+ if (stateDisabled || stateReadonly) return;
2101
2280
  if (!panel.hidden) return;
2102
2281
  mode = 'days';
2103
2282
  focusDate = selected || focusDate || new Date();
@@ -2127,8 +2306,11 @@
2127
2306
 
2128
2307
  updateDisplay();
2129
2308
 
2309
+ applyState();
2310
+
2130
2311
  var api = {
2131
2312
  wrap: wrap,
2313
+ setState: setState,
2132
2314
  getValue: function () { return selected ? toISO(selected) : ''; },
2133
2315
  clear: function () { selected = null; updateDisplay(); if (!panel.hidden) render(); },
2134
2316
  setMin: function (iso) { minDate = parseISO(iso); if (!panel.hidden) render(); },
@@ -2188,7 +2370,16 @@
2188
2370
  initAll();
2189
2371
  }
2190
2372
 
2191
- window.FreedayDatepicker = { init: initPicker, initAll: initAll };
2373
+ window.FreedayDatepicker = {
2374
+ init: initPicker,
2375
+ initAll: initAll,
2376
+ /* A host that rendered its seed once and cannot re-render it (Blazor) still has to be able to
2377
+ disable, lock or invalidate the field later. */
2378
+ setState: function (root, state) {
2379
+ var api = root && root._fdyDp ? root._fdyDp : null;
2380
+ if (api && api.setState) api.setState(state);
2381
+ }
2382
+ };
2192
2383
  })();
2193
2384
 
2194
2385
  /* Freeday, datetime picker composer (optional, zero-dependency).
@@ -2367,10 +2558,11 @@
2367
2558
  badInput: 'type',
2368
2559
  customError: 'mismatch'
2369
2560
  };
2370
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
2371
- * path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on the field,
2372
- * `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
2373
- * a host in another language needs: it sets nine messages once instead of on every input.
2561
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
2562
+ * enhancer path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on
2563
+ * the field, `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form
2564
+ * level is what a host in another language needs: it sets nine messages once instead of on
2565
+ * every input.
2374
2566
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2375
2567
  var TEXT = {
2376
2568
  required: 'Required.',
@@ -2581,10 +2773,11 @@
2581
2773
  }
2582
2774
 
2583
2775
 
2584
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
2585
- * path, and every one overridable per element, so a host that speaks another language (the
2586
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
2587
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2776
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
2777
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
2778
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
2779
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
2780
+ * hard-coded further down. */
2588
2781
  var TEXT = {
2589
2782
  show: 'Show password',
2590
2783
  hide: 'Hide password'
@@ -3210,7 +3403,24 @@
3210
3403
  if (root && root._fdyCombo) root._fdyCombo.setValue(value);
3211
3404
  }
3212
3405
 
3213
- window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue };
3406
+ /* Beside setValue for the same reason it exists: a host that renders its markup once (every
3407
+ Blazor wrapper, `ShouldRender => false`) cannot express a later state change any other way,
3408
+ and a parameter that silently stops working after the first render is worse than none. */
3409
+ function setState(root, state) {
3410
+ var button = root ? root.querySelector('.fdy-combo__button') : null;
3411
+ if (!button || !state) return;
3412
+ if (state.disabled != null) button.disabled = !!state.disabled;
3413
+ if (state.readonly != null) {
3414
+ if (state.readonly) button.setAttribute('aria-readonly', 'true');
3415
+ else button.removeAttribute('aria-readonly');
3416
+ }
3417
+ if (state.invalid != null) {
3418
+ if (state.invalid) button.setAttribute('aria-invalid', 'true');
3419
+ else button.removeAttribute('aria-invalid');
3420
+ }
3421
+ }
3422
+
3423
+ window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue, setState: setState };
3214
3424
  })();
3215
3425
 
3216
3426
  /* Freeday, slider value binding (optional, zero-dependency).
@@ -3272,10 +3482,11 @@
3272
3482
  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>';
3273
3483
 
3274
3484
 
3275
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
3276
- * path, and every one overridable per element, so a host that speaks another language (the
3277
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3278
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3485
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
3486
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
3487
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
3488
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
3489
+ * hard-coded further down. */
3279
3490
  var TEXT = {
3280
3491
  done: 'Done',
3281
3492
  next: 'Next'
@@ -3407,10 +3618,11 @@
3407
3618
  }
3408
3619
 
3409
3620
 
3410
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
3411
- * path, and every one overridable per element, so a host that speaks another language (the
3412
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3413
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3621
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
3622
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
3623
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
3624
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
3625
+ * hard-coded further down. */
3414
3626
  var TEXT = {
3415
3627
  prev: 'Previous',
3416
3628
  next: 'Next',
@@ -3419,6 +3631,8 @@
3419
3631
  filterText: 'Contains text',
3420
3632
  filterTextPlaceholder: 'Contains…',
3421
3633
  filterEnum: 'Show values',
3634
+ filterMin: 'Min',
3635
+ filterMax: 'Max',
3422
3636
  filterRange: 'Value range',
3423
3637
  reset: 'Reset',
3424
3638
  close: 'Close',
@@ -3691,8 +3905,8 @@
3691
3905
  pop.appendChild(filterTitle(textOf(root, 'filterRange')));
3692
3906
  var range = document.createElement('div');
3693
3907
  range.className = 'fdy-filter__range';
3694
- var minI = numberInput('Min', f.min);
3695
- var maxI = numberInput('Maks', f.max);
3908
+ var minI = numberInput(textOf(root, 'filterMin'), f.min);
3909
+ var maxI = numberInput(textOf(root, 'filterMax'), f.max);
3696
3910
  var applyRange = function () {
3697
3911
  f.min = minI.value !== '' ? parseNum(minI.value) : null;
3698
3912
  f.max = maxI.value !== '' ? parseNum(maxI.value) : null;
@@ -3991,6 +4205,24 @@
3991
4205
  (function () {
3992
4206
  'use strict';
3993
4207
 
4208
+ /* User-facing strings, overridable per element with `data-fdy-text-<key>`. One entry, and it
4209
+ * still needed the table: it reached the DOM as an argument to a helper, which is how it sat
4210
+ * outside every string guard until 2.2.0. */
4211
+ var TEXT = {
4212
+ label: 'Choose a time'
4213
+ };
4214
+ function textAttr(root, key) {
4215
+ if (!root || !root.getAttribute) return null;
4216
+ var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
4217
+ return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
4218
+ }
4219
+ function textOf(root, key, vars) {
4220
+ var custom = textAttr(root, key);
4221
+ var s = custom != null && custom !== '' ? custom : TEXT[key];
4222
+ if (vars) for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
4223
+ return s;
4224
+ }
4225
+
3994
4226
  var seq = 0;
3995
4227
  var CLOCK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>';
3996
4228
 
@@ -4005,7 +4237,7 @@
4005
4237
  wrap.dataset.fdyTpReady = '1';
4006
4238
  wrap.classList.add('fdy-timepicker');
4007
4239
 
4008
- var label = wrap.getAttribute('data-label') || 'Choose a time';
4240
+ var label = wrap.getAttribute('data-label') || textOf(wrap, 'label');
4009
4241
  var placeholder = wrap.getAttribute('data-placeholder') || '--:--';
4010
4242
  var step = Math.max(1, parseInt(wrap.getAttribute('data-step') || '30', 10));
4011
4243
  var minM = valid(wrap.getAttribute('data-min')) ? toMin(wrap.getAttribute('data-min')) : 0;
@@ -4238,10 +4470,11 @@
4238
4470
  }
4239
4471
 
4240
4472
 
4241
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
4242
- * path, and every one overridable per element, so a host that speaks another language (the
4243
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4244
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4473
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
4474
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
4475
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
4476
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
4477
+ * hard-coded further down. */
4245
4478
  var TEXT = {
4246
4479
  close: 'Close'
4247
4480
  };
@@ -4472,10 +4705,11 @@
4472
4705
  }
4473
4706
 
4474
4707
 
4475
- /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
4476
- * path, and every one overridable per element, so a host that speaks another language (the
4477
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4478
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4708
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
4709
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
4710
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
4711
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
4712
+ * hard-coded further down. */
4479
4713
  var TEXT = {
4480
4714
  remove: 'Remove {name}',
4481
4715
  progress: 'Upload progress for {name}',