@cahyo-dimas/freeday 1.54.0 → 2.1.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 (147) hide show
  1. package/CHANGELOG.md +713 -545
  2. package/COMPONENTS.md +417 -139
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +34 -34
  6. package/adapters/blazor/FdyAppShell.razor +2 -2
  7. package/adapters/blazor/FdyAppShell.razor.cs +6 -6
  8. package/adapters/blazor/FdyCfl.razor.cs +1 -1
  9. package/adapters/blazor/FdyChart.razor +1 -1
  10. package/adapters/blazor/FdyChart.razor.cs +3 -3
  11. package/adapters/blazor/FdyChartSeries.cs +1 -1
  12. package/adapters/blazor/FdyCombo.razor.cs +2 -2
  13. package/adapters/blazor/FdyModal.razor +1 -1
  14. package/adapters/blazor/FdyTable.razor.cs +7 -7
  15. package/adapters/blazor/FdyTableFilter.razor +1 -1
  16. package/adapters/blazor/FdyTableFooter.razor +1 -1
  17. package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
  18. package/adapters/blazor/Freeday.Blazor.csproj +1 -1
  19. package/adapters/blazor/FreedayComponentBase.cs +1 -1
  20. package/adapters/blazor/TableModel.cs +3 -3
  21. package/adapters/blazor/TableTypes.cs +3 -3
  22. package/adapters/blazor/freeday-blazor.js +5 -5
  23. package/adapters/core/app-shell.d.ts +1 -1
  24. package/adapters/core/app-shell.js +6 -6
  25. package/adapters/core/cfl-value.d.ts +11 -0
  26. package/adapters/core/cfl-value.js +31 -0
  27. package/adapters/core/table-model.d.ts +2 -2
  28. package/adapters/core/table-model.js +8 -8
  29. package/adapters/react/components/FdyAppShell.tsx +5 -5
  30. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  31. package/adapters/react/components/FdyCascade.tsx +1 -1
  32. package/adapters/react/components/FdyCfl.tsx +11 -11
  33. package/adapters/react/components/FdyDateRange.tsx +2 -2
  34. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  35. package/adapters/react/components/FdyDrawer.tsx +1 -1
  36. package/adapters/react/components/FdyModal.tsx +2 -2
  37. package/adapters/react/components/FdyTable.tsx +7 -7
  38. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  39. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  40. package/adapters/react/index.d.ts +4 -1
  41. package/adapters/react/index.js +1 -0
  42. package/adapters/react/useFreeday.js +3 -3
  43. package/adapters/vue/components/FdyAppShell.vue +5 -5
  44. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  45. package/adapters/vue/components/FdyCascade.vue +1 -1
  46. package/adapters/vue/components/FdyCfl.vue +8 -8
  47. package/adapters/vue/components/FdyChart.vue +2 -2
  48. package/adapters/vue/components/FdyCombo.vue +1 -1
  49. package/adapters/vue/components/FdyDateRange.vue +2 -2
  50. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  51. package/adapters/vue/components/FdyDrawer.vue +3 -3
  52. package/adapters/vue/components/FdyModal.vue +3 -3
  53. package/adapters/vue/components/FdyTable.vue +7 -7
  54. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  55. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  56. package/adapters/vue/index.d.ts +5 -2
  57. package/adapters/vue/index.js +1 -0
  58. package/adapters/vue/useFreeday.js +3 -3
  59. package/dist/asset.d.ts +3 -3
  60. package/dist/freeday-app-shell.js +9 -9
  61. package/dist/freeday-autocomplete.js +2 -2
  62. package/dist/freeday-breakpoint.js +1 -1
  63. package/dist/freeday-carousel.js +8 -7
  64. package/dist/freeday-cascade.js +10 -9
  65. package/dist/freeday-cfl.js +12 -11
  66. package/dist/freeday-chart.js +11 -11
  67. package/dist/freeday-chip.js +3 -3
  68. package/dist/freeday-datepicker.js +11 -7
  69. package/dist/freeday-datetime.js +5 -5
  70. package/dist/freeday-drawer.js +1 -1
  71. package/dist/freeday-form.js +17 -16
  72. package/dist/freeday-mask.js +9 -8
  73. package/dist/freeday-menu.js +1 -1
  74. package/dist/freeday-number.js +3 -3
  75. package/dist/freeday-popover.js +3 -3
  76. package/dist/freeday-rating.js +2 -2
  77. package/dist/freeday-select.js +5 -5
  78. package/dist/freeday-slider.js +2 -2
  79. package/dist/freeday-stepper.js +10 -9
  80. package/dist/freeday-table.js +18 -17
  81. package/dist/freeday-tabs.js +1 -1
  82. package/dist/freeday-timepicker.js +2 -2
  83. package/dist/freeday-toast.js +11 -10
  84. package/dist/freeday-tree.js +3 -3
  85. package/dist/freeday-upload.js +28 -27
  86. package/dist/freeday.bundle.css +176 -176
  87. package/dist/freeday.css +170 -170
  88. package/dist/freeday.d.ts +2 -2
  89. package/dist/freeday.js +189 -176
  90. package/dist/freeday.tokens.css +5 -5
  91. package/docs/agent-onboarding.md +49 -46
  92. package/docs/getting-started.md +49 -40
  93. package/docs/integrations.md +18 -18
  94. package/docs/reference-screen.html +14 -14
  95. package/package.json +4 -3
  96. package/src/base.css +6 -6
  97. package/src/components/accordion.css +4 -4
  98. package/src/components/alert.css +1 -1
  99. package/src/components/app-shell.css +10 -10
  100. package/src/components/appbar.css +3 -3
  101. package/src/components/autocomplete.css +1 -1
  102. package/src/components/avatar.css +3 -3
  103. package/src/components/badge.css +5 -5
  104. package/src/components/breadcrumb.css +1 -1
  105. package/src/components/breakpoints.css +4 -4
  106. package/src/components/button.css +13 -13
  107. package/src/components/card.css +8 -8
  108. package/src/components/carousel.css +2 -2
  109. package/src/components/cascade.css +2 -2
  110. package/src/components/cfl.css +4 -4
  111. package/src/components/chart.css +3 -3
  112. package/src/components/chip.css +2 -2
  113. package/src/components/combo.css +3 -3
  114. package/src/components/composition.css +7 -7
  115. package/src/components/datepicker.css +6 -6
  116. package/src/components/datetimepicker.css +1 -1
  117. package/src/components/description-list.css +1 -1
  118. package/src/components/divider.css +1 -1
  119. package/src/components/drawer.css +1 -1
  120. package/src/components/file-upload.css +5 -5
  121. package/src/components/filterbar.css +7 -7
  122. package/src/components/form-grid.css +2 -2
  123. package/src/components/input-group.css +6 -6
  124. package/src/components/input.css +6 -6
  125. package/src/components/kbd.css +1 -1
  126. package/src/components/list.css +6 -6
  127. package/src/components/menu.css +3 -3
  128. package/src/components/modal.css +1 -1
  129. package/src/components/pagination.css +1 -1
  130. package/src/components/progress.css +1 -1
  131. package/src/components/rating.css +2 -2
  132. package/src/components/selection.css +4 -4
  133. package/src/components/skeleton.css +1 -1
  134. package/src/components/slider.css +1 -1
  135. package/src/components/spinner.css +1 -1
  136. package/src/components/states.css +1 -1
  137. package/src/components/stepper.css +2 -2
  138. package/src/components/table.css +15 -15
  139. package/src/components/tabs.css +3 -3
  140. package/src/components/timeline.css +1 -1
  141. package/src/components/timepicker.css +1 -1
  142. package/src/components/toast.css +3 -3
  143. package/src/components/tooltip.css +1 -1
  144. package/src/components/tree.css +2 -2
  145. package/tokens/breakpoints.d.ts +2 -2
  146. package/tokens/breakpoints.mjs +3 -3
  147. package/tokens/tokens.json +1 -1
package/dist/freeday.js CHANGED
@@ -1,25 +1,25 @@
1
- /* Freeday enhancers — GENERATED by tokens/build.mjs. Do not edit by hand.
1
+ /* Freeday enhancers. GENERATED by tokens/build.mjs. Do not edit by hand.
2
2
  * Concatenation of every src/*.js enhancer (each a self-contained IIFE). */
3
- /* Freeday — app shell enhancer (optional, zero-dependency).
3
+ /* Freeday, app shell enhancer (optional, zero-dependency).
4
4
  * Opt in with <div class="fdy-app" data-fdy-app>; the markup is otherwise unchanged.
5
5
  *
6
6
  * The shell has always shipped the state classes and no behaviour, which left every consumer to
7
- * assemble the toggle, Escape, focus trap, focus restore and `inert` themselves — and the two
7
+ * assemble the toggle, Escape, focus trap, focus restore and `inert` themselves, and the two
8
8
  * hand-rolled copies in this repo's own docs already disagreed about which of those exist. Below
9
9
  * 721px the sidebar is an OVERLAY (app-shell.css: position:fixed + translateX(-100%)), so it needs
10
10
  * the same treatment a modal drawer gets; at or above 721px it is a column that merely collapses.
11
11
  *
12
12
  * One rule covers both a hidden nav and a hidden page:
13
13
  * - `__sidebar` is inert whenever the nav is NOT visible. Off-canvas and collapsed panels stay in
14
- * the tab order otherwise — translateX(-100%) and width:0 hide a thing from the eye, not from
14
+ * the tab order otherwise, translateX(-100%) and width:0 hide a thing from the eye, not from
15
15
  * the keyboard, which is how a nav nobody can see still swallows Tab.
16
16
  * - `__content` is inert only while the nav is an OPEN OVERLAY, so Tab cannot wander behind the
17
17
  * backdrop.
18
18
  *
19
19
  * Emits a bubbling `fdy-app-nav` CustomEvent (detail {visible}) whenever the nav's visibility
20
20
  * changes, and takes `FreedayAppShell.setVisible(root, visible)` from outside. Those two exist for
21
- * the same reason the other enhancers have them: a host that keeps its own state — the Blazor
22
- * wrapper binding @bind-NavOpen, an app persisting the collapsed preference — has to be able to
21
+ * the same reason the other enhancers have them: a host that keeps its own state, the Blazor
22
+ * wrapper binding @bind-NavOpen, an app persisting the collapsed preference, has to be able to
23
23
  * hear the change and to drive it, without owning the behaviour twice.
24
24
  * FreedayAppShell.init(root) for late-mounted markup.
25
25
  */
@@ -33,7 +33,7 @@
33
33
  + ' textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
34
34
 
35
35
  /* getClientRects(), not offsetParent: an overlay sidebar is position:fixed, and a fixed element
36
- reports no offsetParent at all — filtering on it would call every nav item invisible. */
36
+ reports no offsetParent at all, filtering on it would call every nav item invisible. */
37
37
  function focusables(root) {
38
38
  return Array.prototype.filter.call(root.querySelectorAll(FOCUSABLE), function (el) {
39
39
  return el.getClientRects().length > 0;
@@ -64,7 +64,7 @@
64
64
  function isCollapsed() { return app.classList.contains('fdy-app--nav-collapsed'); }
65
65
  function navVisible() { return mqWide.matches ? !isCollapsed() : isOverlayOpen(); }
66
66
 
67
- /* aria-expanded answers "is the nav showing?" in BOTH modes — the two state classes are the
67
+ /* aria-expanded answers "is the nav showing?" in BOTH modes, the two state classes are the
68
68
  kit's business, not the reader's. */
69
69
  function sync() {
70
70
  var visible = navVisible();
@@ -93,7 +93,7 @@
93
93
  }
94
94
  }
95
95
 
96
- /* restoreFocus is false when the viewport closed it rather than the user — a resize must not
96
+ /* restoreFocus is false when the viewport closed it rather than the user, a resize must not
97
97
  yank focus out from under whatever the reader was doing. */
98
98
  function close(restoreFocus) {
99
99
  if (!isOverlayOpen()) return;
@@ -120,7 +120,7 @@
120
120
 
121
121
  if (backdrop) backdrop.addEventListener('click', function () { close(); });
122
122
 
123
- /* Following a link inside an overlay nav means "take me there" — leaving the panel open over
123
+ /* Following a link inside an overlay nav means "take me there", leaving the panel open over
124
124
  the page you just asked for is the one thing every hand-rolled copy in this repo disagreed on. */
125
125
  sidebar.addEventListener('click', function (e) {
126
126
  if (!isOverlayOpen()) return;
@@ -208,10 +208,10 @@
208
208
  };
209
209
  })();
210
210
 
211
- /* Freeday — autocomplete enhancer (optional, zero-dependency).
211
+ /* Freeday, autocomplete enhancer (optional, zero-dependency).
212
212
  * Editable combobox (WAI-ARIA APG): a text input filters a role="listbox" of role="option"
213
213
  * items as you type; select fills the input. Auto-inits [data-fdy-autocomplete]. Options are
214
- * static markup filtered client-side — in an app, fetch/filter server-side and re-render options.
214
+ * static markup filtered client-side, in an app, fetch/filter server-side and re-render options.
215
215
  *
216
216
  * Markup: <div data-fdy-autocomplete class="fdy-autocomplete">
217
217
  * <input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
@@ -347,7 +347,7 @@
347
347
  window.FreedayAutocomplete = { init: initAutocomplete, initAll: initAll };
348
348
  })();
349
349
 
350
- /* Freeday — breakpoint provider (optional, zero-dependency).
350
+ /* Freeday, breakpoint provider (optional, zero-dependency).
351
351
  * The MudBreakpointProvider equivalent for JS consumers: stamps the current breakpoint on
352
352
  * <html data-breakpoint="xs|sm|md|lg|xl"> and emits a bubbling "fdy-breakpoint-change"
353
353
  * CustomEvent (detail {breakpoint}) whenever it changes. Also fills any element with
@@ -399,7 +399,7 @@
399
399
  };
400
400
  })();
401
401
 
402
- /* Freeday — carousel enhancer (optional, zero-dependency).
402
+ /* Freeday, carousel enhancer (optional, zero-dependency).
403
403
  * Scroll-snap slider with prev/next arrows, generated dot indicators, keyboard (←/→),
404
404
  * and optional autoplay (data-fdy-autoplay="4000", pauses on hover/focus). Auto-inits
405
405
  * [data-fdy-carousel].
@@ -419,16 +419,17 @@
419
419
  'use strict';
420
420
 
421
421
 
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. */
422
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
423
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
424
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
425
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
426
+ * hard-coded further down. */
426
427
  var TEXT = {
427
- position: '{n} dari {total}',
428
+ position: '{n} of {total}',
428
429
  slide: 'Slide {n}'
429
430
  };
430
431
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
431
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
432
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
432
433
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
433
434
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
434
435
  still resolves, for markup written against 1.39.0. */
@@ -540,7 +541,7 @@
540
541
  window.FreedayCarousel = { init: initCarousel, initAll: initAll };
541
542
  })();
542
543
 
543
- /* Freeday — cascade select enhancer (optional, zero-dependency).
544
+ /* Freeday, cascade select enhancer (optional, zero-dependency).
544
545
  * Turns a [data-fdy-cascade] wrapper into a hierarchical drill-down picker. The
545
546
  * data model is a hidden nested <ul> inside the wrapper: a <li> with a child <ul>
546
547
  * is a branch (drills in), a <li> without is a leaf (selects). Each <li> carries
@@ -596,16 +597,17 @@
596
597
  }
597
598
 
598
599
 
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. */
600
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
601
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
602
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
603
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
604
+ * hard-coded further down. */
603
605
  var TEXT = {
604
- back: 'Kembali satu tingkat',
606
+ back: 'Back one level',
605
607
  submenu: '{label}, submenu'
606
608
  };
607
609
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
608
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
610
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
609
611
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
610
612
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
611
613
  still resolves, for markup written against 1.39.0. */
@@ -630,8 +632,8 @@
630
632
  var root = sourceUl ? parse(sourceUl) : [];
631
633
  if (sourceUl) sourceUl.remove();
632
634
 
633
- var label = wrap.getAttribute('data-label') || 'Pilih';
634
- var placeholder = wrap.getAttribute('data-placeholder') || 'Pilih…';
635
+ var label = wrap.getAttribute('data-label') || 'Select';
636
+ var placeholder = wrap.getAttribute('data-placeholder') || 'Select…';
635
637
  var sep = wrap.getAttribute('data-separator') || ' / ';
636
638
 
637
639
  var trigger = document.createElement('button');
@@ -826,24 +828,24 @@
826
828
  window.FreedayCascade = { init: initCascade, initAll: initAll };
827
829
  })();
828
830
 
829
- /* Freeday — choose-from-list enhancer (optional, zero-dependency).
831
+ /* Freeday, choose-from-list enhancer (optional, zero-dependency).
830
832
  * Wires a searchable master-data picker dialog to read-only fields.
831
833
  * Progressive enhancement over a native <dialog>; in a framework app, drive the
832
834
  * dialog as a controlled component (fetchPage callback + server cache), not a store.
833
835
  *
834
836
  * Markup contract (see docs):
835
- * - Field group: [data-fdy-cfl="<dialogId>"] — usually a `.fdy-input-group` holding a
837
+ * - Field group: [data-fdy-cfl="<dialogId>"], usually a `.fdy-input-group` holding a
836
838
  * read-only `.fdy-input` and a trigger `<button class="fdy-input-group__btn">`.
837
839
  * Optional `data-fdy-cfl-display="<key>"` picks which row dataset key fills the input
838
840
  * (default "value"); optional `data-fdy-cfl-summary="{n} dipilih"` formats the multi label.
839
- * - Standalone opener: [data-fdy-cfl-open="<dialogId>"] — opens the dialog with no bound field.
841
+ * - Standalone opener: [data-fdy-cfl-open="<dialogId>"], opens the dialog with no bound field.
840
842
  * - Dialog: <dialog id="<dialogId>" class="fdy-modal fdy-modal--cfl"> containing
841
843
  * a `[data-fdy-cfl-search]` input, `.fdy-cfl__row` <tr> rows carrying `data-*` fields,
842
844
  * a `[data-fdy-cfl-empty]` element, and (multi only) `[data-fdy-cfl-multiple]` on the
843
845
  * dialog plus `[data-fdy-cfl-count]` and `[data-fdy-cfl-confirm]` in the footer.
844
846
  *
845
847
  * Emits a bubbling "fdy-cfl-select" CustomEvent on the field group (or the dialog when
846
- * opened standalone): detail {row} for single-select, {rows} for multi-select — each a plain
848
+ * opened standalone): detail {row} for single-select, {rows} for multi-select, each a plain
847
849
  * object copy of the row's dataset. Consumers fill sibling fields (code → name) from it.
848
850
  */
849
851
  (function () {
@@ -862,15 +864,16 @@
862
864
  }
863
865
 
864
866
 
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. */
867
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
868
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
869
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
870
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
871
+ * hard-coded further down. */
869
872
  var TEXT = {
870
- selected: '{n} dipilih'
873
+ selected: '{n} selected'
871
874
  };
872
875
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
873
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
876
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
874
877
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
875
878
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
876
879
  still resolves, for markup written against 1.39.0. */
@@ -931,7 +934,7 @@
931
934
  var input = activeGroup.querySelector('.fdy-input');
932
935
  if (input) {
933
936
  if (detail.rows) {
934
- var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} dipilih';
937
+ var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} selected';
935
938
  input.value = detail.rows.length ? tpl.replace('{n}', String(detail.rows.length)) : '';
936
939
  } else {
937
940
  var key = activeGroup.getAttribute('data-fdy-cfl-display') || 'value';
@@ -1070,7 +1073,7 @@
1070
1073
  window.FreedayCfl = { init: initTriggers };
1071
1074
  })();
1072
1075
 
1073
- /* Freeday — chart renderer (optional, zero-dependency, no external chart lib).
1076
+ /* Freeday, chart renderer (optional, zero-dependency, no external chart lib).
1074
1077
  * Renders sparkline / bar / line / area / donut into an element from data attributes.
1075
1078
  * Auto-inits [data-fdy-chart] once; call FreedayChart.update(el) to re-render after the
1076
1079
  * data attributes change (used by the freeday/vue <FdyChart> wrapper for reactive data).
@@ -1078,14 +1081,14 @@
1078
1081
  * Single-series colour (sparkline / simple bar / single line) via data-fdy-color references a
1079
1082
  * semantic token by name (primary, accent, success, warning, danger, info). Multi-series
1080
1083
  * defaults draw from the validated categorical --chart-1..8 palette in fixed order (never
1081
- * cycled, 8-series cap); pass data-fdy-colors to override — a name is a semantic token
1084
+ * cycled, 8-series cap); pass data-fdy-colors to override, a name is a semantic token
1082
1085
  * (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
1083
1086
  * validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
1084
1087
  * Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
1085
1088
  * --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
1086
1089
  * viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
1087
1090
  * ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
1088
- * Charts are an enhancement — put a table/text fallback inside the element and it will be
1091
+ * Charts are an enhancement, put a table/text fallback inside the element and it will be
1089
1092
  * replaced on render; give the element role="img" + aria-label.
1090
1093
  *
1091
1094
  * Sparkline: <span data-fdy-chart="sparkline" data-values="4,6,5,8,7,10" data-fdy-color="primary">
@@ -1122,7 +1125,7 @@
1122
1125
  return v ? v.split(',').map(function (s) { return s.trim(); }) : [];
1123
1126
  }
1124
1127
  // role="img" is Children Presentational per ARIA, so the docs used to say a chart's rendered
1125
- // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree —
1128
+ // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree,
1126
1129
  // measured in Chrome's AX tree, every one of those nodes is live and unignored; what saves the
1127
1130
  // advice is only that AT treats a NAMED role="img" as a leaf and does not descend. So the kit
1128
1131
  // hides them itself, and the author's aria-label really is the whole text alternative.
@@ -1297,7 +1300,7 @@
1297
1300
  // Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
1298
1301
  // pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
1299
1302
  // every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
1300
- // size by plotWidth/320 instead — a 9px axis label rendered ~39px on a 1728px viewport.)
1303
+ // size by plotWidth/320 instead, a 9px axis label rendered ~39px on a 1728px viewport.)
1301
1304
  var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
1302
1305
  var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
1303
1306
  el.classList.add('fdy-chart-xy');
@@ -1318,7 +1321,7 @@
1318
1321
  el.appendChild(plot);
1319
1322
 
1320
1323
  // Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
1321
- // so the box is already final. A detached or display:none chart measures 0 — fall back to the
1324
+ // so the box is already final. A detached or display:none chart measures 0, fall back to the
1322
1325
  // legacy box and let the ResizeObserver repaint it once it is actually laid out.
1323
1326
  var box = plot.getBoundingClientRect();
1324
1327
  var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
@@ -1362,7 +1365,7 @@
1362
1365
 
1363
1366
  // x category labels with autoskip. Labels are author-supplied, so estimate the widest one instead
1364
1367
  // of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
1365
- // would collide with the one before it — including the forced last one (a fraction-of-a-slot
1368
+ // would collide with the one before it, including the forced last one (a fraction-of-a-slot
1366
1369
  // overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
1367
1370
  var widest = 0;
1368
1371
  for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
@@ -1373,7 +1376,7 @@
1373
1376
  for (var xi = 0; xi < n; xi++) {
1374
1377
  if (xi % step === 0 || xi === n - 1) kept.push(xi);
1375
1378
  }
1376
- // The last label is worth forcing, but not on top of its neighbour — drop the penultimate one.
1379
+ // The last label is worth forcing, but not on top of its neighbour, drop the penultimate one.
1377
1380
  if (kept.length >= 2 && xPos(kept[kept.length - 1]) - xPos(kept[kept.length - 2]) < minGap) {
1378
1381
  kept.splice(kept.length - 2, 1);
1379
1382
  }
@@ -1506,17 +1509,17 @@
1506
1509
  var li = document.createElement('li');
1507
1510
  var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorAt(i);
1508
1511
  li.appendChild(sw);
1509
- li.appendChild(document.createTextNode((labels[i] || ('#' + (i + 1))) + ' — ' + Math.round((v / total) * 100) + '%'));
1512
+ li.appendChild(document.createTextNode((labels[i] || ('#' + (i + 1))) + ' · ' + Math.round((v / total) * 100) + '%'));
1510
1513
  legend.appendChild(li);
1511
1514
  });
1512
1515
  el.appendChild(ring);
1513
- // Honour data-fdy-legend for the donut too — a caller may supply its own richer legend. 'auto'
1516
+ // Honour data-fdy-legend for the donut too, a caller may supply its own richer legend. 'auto'
1514
1517
  // keeps today's behaviour (a donut is unreadable without labels, so it shows); 'none' suppresses it.
1515
1518
  if ((el.getAttribute('data-fdy-legend') || 'auto') !== 'none') el.appendChild(legend);
1516
1519
  ensureImg(el);
1517
1520
  }
1518
1521
 
1519
- // Full (re-)render from current data attributes. Idempotent — safe to call repeatedly.
1522
+ // Full (re-)render from current data attributes. Idempotent, safe to call repeatedly.
1520
1523
  function renderChart(el) {
1521
1524
  var type = el.getAttribute('data-fdy-chart');
1522
1525
  if (type === 'sparkline') renderSparkline(el);
@@ -1551,12 +1554,12 @@
1551
1554
  window.FreedayChart = { init: initChart, initAll: initAll, update: renderChart };
1552
1555
  })();
1553
1556
 
1554
- /* Freeday — chip enhancer (optional, zero-dependency).
1557
+ /* Freeday, chip enhancer (optional, zero-dependency).
1555
1558
  * Two behaviours, both progressive enhancement:
1556
1559
  *
1557
- * 1. Removable chips — any .fdy-chip__remove button removes its chip and emits a
1560
+ * 1. Removable chips, any .fdy-chip__remove button removes its chip and emits a
1558
1561
  * bubbling "fdy-chip-remove" (detail {value}).
1559
- * 2. Choice / filter groups — a [data-fdy-chips] container of interactive chips
1562
+ * 2. Choice / filter groups, a [data-fdy-chips] container of interactive chips
1560
1563
  * (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
1561
1564
  * on click. Add data-single for one-at-a-time (radio-like) selection; filter
1562
1565
  * chips get a leading check when pressed. Emits "fdy-chip-change" on the group
@@ -1638,9 +1641,9 @@
1638
1641
  window.FreedayChip = { initGroup: initGroup, initRemove: initRemove, initAll: initAll };
1639
1642
  })();
1640
1643
 
1641
- /* Freeday — date picker enhancer (optional, zero-dependency).
1644
+ /* Freeday, date picker enhancer (optional, zero-dependency).
1642
1645
  * Builds an input-styled trigger + calendar popover from an empty wrapper.
1643
- * Locale comes from <html lang> (via Intl) — month/weekday/value formatting is not hardcoded.
1646
+ * Locale comes from <html lang> (via Intl), month/weekday/value formatting is not hardcoded.
1644
1647
  *
1645
1648
  * Markup contract:
1646
1649
  * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
@@ -1659,7 +1662,11 @@
1659
1662
  (function () {
1660
1663
  'use strict';
1661
1664
 
1662
- var LOCALE = document.documentElement.getAttribute('lang') || 'id';
1665
+ /* The page's own `lang` wins, which is the better escape hatch than any attribute the kit
1666
+ could invent: an Indonesian app writes <html lang="id"> and gets Indonesian month and
1667
+ weekday names back automatically. The FALLBACK follows the kit's default language, or a
1668
+ page without `lang` would read English labels around Indonesian month names. */
1669
+ var LOCALE = document.documentElement.getAttribute('lang') || 'en';
1663
1670
  var uidSeq = 0;
1664
1671
  function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
1665
1672
  function pad(n) { return n < 10 ? '0' + n : '' + n; }
@@ -1706,10 +1713,10 @@
1706
1713
  var view = startOfDay(selected || new Date());
1707
1714
  view = new Date(view.getFullYear(), view.getMonth(), 1);
1708
1715
  var focusDate = null;
1709
- /* 'days' | 'months' — the calendar drills one level up rather than growing furniture beside the
1716
+ /* 'days' | 'months', the calendar drills one level up rather than growing furniture beside the
1710
1717
  title. Before this, the only pointer route to another month was one click per month: from
1711
1718
  August 2026 to March 2022 is fifty-three of them. Shift+PageUp already jumped a year, but a
1712
- shortcut nobody can see is not an affordance — the repo's own test helper clicked "previous
1719
+ shortcut nobody can see is not an affordance, the repo's own test helper clicked "previous
1713
1720
  month" twenty-four times to reach a date two years back. */
1714
1721
  var mode = 'days';
1715
1722
  var focusMonth = null;
@@ -1908,7 +1915,7 @@
1908
1915
  var YEARS_PER_PAGE = 12;
1909
1916
  function yearPageStart(y) { return Math.floor(y / YEARS_PER_PAGE) * YEARS_PER_PAGE; }
1910
1917
 
1911
- /* A year is only unreachable when EVERY month in it falls outside min/max — the same rule
1918
+ /* A year is only unreachable when EVERY month in it falls outside min/max, the same rule
1912
1919
  monthDisabled applies one level down, for the same reason: a bound that sits inside the year
1913
1920
  disables neither end. */
1914
1921
  function yearDisabled(y) {
@@ -2153,7 +2160,7 @@
2153
2160
  var f = fromApi.getValue();
2154
2161
  var t = toApi.getValue();
2155
2162
  toApi.setMin(f || null); // end can't precede start (one-directional, per Foundation)
2156
- fromApi.setRange(f, t); // start stays free — picking a later start clears the end below
2163
+ fromApi.setRange(f, t); // start stays free, picking a later start clears the end below
2157
2164
  toApi.setRange(f, t);
2158
2165
  }
2159
2166
  fromEl.addEventListener('fdy-datepicker-change', function () {
@@ -2187,9 +2194,9 @@
2187
2194
  window.FreedayDatepicker = { init: initPicker, initAll: initAll };
2188
2195
  })();
2189
2196
 
2190
- /* Freeday — datetime picker composer (optional, zero-dependency).
2197
+ /* Freeday, datetime picker composer (optional, zero-dependency).
2191
2198
  * Combines a child date picker and time picker into one value. It does not build
2192
- * anything itself — freeday-datepicker.js and freeday-timepicker.js enhance the
2199
+ * anything itself, freeday-datepicker.js and freeday-timepicker.js enhance the
2193
2200
  * children; this only listens for their change events and emits a single combined
2194
2201
  * "fdy-datetime-change" (detail {date, time, value}). value is "YYYY-MM-DDTHH:MM"
2195
2202
  * when both parts are set, else "". Icon variants are controlled per child picker
@@ -2199,7 +2206,7 @@
2199
2206
  * <div data-fdy-datepicker data-value="2026-07-21"></div>
2200
2207
  * <div data-fdy-timepicker data-value="14:30"></div></div>
2201
2208
  * State: add data-fdy-disabled or data-fdy-invalid on the wrapper to reflect it onto both
2202
- * child triggers as one control (read-only is adapter-only — a vanilla datetime is interactive).
2209
+ * child triggers as one control (read-only is adapter-only, a vanilla datetime is interactive).
2203
2210
  */
2204
2211
  (function () {
2205
2212
  'use strict';
@@ -2214,7 +2221,7 @@
2214
2221
  if (!dp || !tp) return;
2215
2222
 
2216
2223
  // Reflect one wrapper-level state onto BOTH child triggers so a datetime reads as a single
2217
- // disabled/invalid control. Deferred with setTimeout(0) — NOT a microtask: the timepicker
2224
+ // disabled/invalid control. Deferred with setTimeout(0). NOT a microtask: the timepicker
2218
2225
  // enhancer registers its DOMContentLoaded init AFTER this composer, and microtasks drain
2219
2226
  // *between* listeners (so a microtask would run before the timepicker trigger exists). A
2220
2227
  // macrotask waits until the whole init pass is done and both triggers are built. (readonly is
@@ -2245,7 +2252,7 @@
2245
2252
  }));
2246
2253
  }
2247
2254
 
2248
- // Listen for the children's bubbling change events (order-independent — no need
2255
+ // Listen for the children's bubbling change events (order-independent, no need
2249
2256
  // for the child enhancers to have run before this composer).
2250
2257
  wrap.addEventListener('fdy-datepicker-change', function (e) { date = e.detail.value; emit(); });
2251
2258
  wrap.addEventListener('fdy-time-select', function (e) { time = e.detail.value; emit(); });
@@ -2274,7 +2281,7 @@
2274
2281
  window.FreedayDatetime = { init: initDatetime, initAll: initAll };
2275
2282
  })();
2276
2283
 
2277
- /* Freeday — drawer enhancer (optional, zero-dependency).
2284
+ /* Freeday, drawer enhancer (optional, zero-dependency).
2278
2285
  * A trigger [data-fdy-drawer="drawerId"] opens the <dialog class="fdy-drawer" id="drawerId">
2279
2286
  * as a modal side panel (native showModal gives focus-trap, Esc, and return-focus). Clicking
2280
2287
  * the backdrop or any [data-close] inside closes it.
@@ -2323,7 +2330,7 @@
2323
2330
  window.FreedayDrawer = { init: initTriggers, initAll: initTriggers };
2324
2331
  })();
2325
2332
 
2326
- /* Freeday — form validation enhancer (optional, zero-dependency).
2333
+ /* Freeday, form validation enhancer (optional, zero-dependency).
2327
2334
  * Wires the native Constraint Validation API to accessible inline errors on any
2328
2335
  * [data-fdy-validate] <form>. Native-first: constraints come from the standard
2329
2336
  * markup attributes (required, type, pattern, min/max, minlength/maxlength, step);
@@ -2363,24 +2370,25 @@
2363
2370
  badInput: 'type',
2364
2371
  customError: 'mismatch'
2365
2372
  };
2366
- /* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
2367
- * path — and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on the field,
2368
- * `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
2369
- * a host in another language needs: it sets nine messages once instead of on every input.
2373
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
2374
+ * enhancer path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on
2375
+ * the field, `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form
2376
+ * level is what a host in another language needs: it sets nine messages once instead of on
2377
+ * every input.
2370
2378
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2371
2379
  var TEXT = {
2372
- required: 'Wajib diisi.',
2373
- type: 'Format tidak valid.',
2374
- pattern: 'Format tidak sesuai.',
2375
- minlength: 'Terlalu pendek.',
2376
- maxlength: 'Terlalu panjang.',
2377
- min: 'Nilai terlalu kecil.',
2378
- max: 'Nilai terlalu besar.',
2379
- step: 'Nilai tidak sesuai kelipatan.',
2380
- mismatch: 'Nilai tidak cocok.',
2381
- invalid: 'Tidak valid.'
2380
+ required: 'Required.',
2381
+ type: 'Invalid format.',
2382
+ pattern: 'Doesn’t match the required format.',
2383
+ minlength: 'Too short.',
2384
+ maxlength: 'Too long.',
2385
+ min: 'Too small.',
2386
+ max: 'Too large.',
2387
+ step: 'Not a valid step.',
2388
+ mismatch: 'Values don’t match.',
2389
+ invalid: 'Invalid.'
2382
2390
  };
2383
- /* Kebab-cased for the lookup — see the note in the other enhancers: HTML lowercases attribute
2391
+ /* Kebab-cased for the lookup, see the note in the other enhancers: HTML lowercases attribute
2384
2392
  names, so `data-fdy-text-filter-text` and `data-fdy-text-filtertext` are different attributes
2385
2393
  and only one of them is what an author would write. */
2386
2394
  function textOf(root, key) {
@@ -2524,7 +2532,7 @@
2524
2532
  window.FreedayForm = { init: initForm, initAll: initAll };
2525
2533
  })();
2526
2534
 
2527
- /* Freeday — input mask + password reveal enhancer (optional, zero-dependency).
2535
+ /* Freeday, input mask + password reveal enhancer (optional, zero-dependency).
2528
2536
  *
2529
2537
  * [data-fdy-mask]="pattern" formats a text input as you type. Placeholders:
2530
2538
  * # a digit (0-9) A a letter (a-z/A-Z) * a letter or digit
@@ -2577,16 +2585,17 @@
2577
2585
  }
2578
2586
 
2579
2587
 
2580
- /* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
2581
- * path — and every one overridable per element, so a host that speaks another language (the
2582
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
2583
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2588
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
2589
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
2590
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
2591
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
2592
+ * hard-coded further down. */
2584
2593
  var TEXT = {
2585
- show: 'Tampilkan kata sandi',
2586
- hide: 'Sembunyikan kata sandi'
2594
+ show: 'Show password',
2595
+ hide: 'Hide password'
2587
2596
  };
2588
2597
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
2589
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
2598
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
2590
2599
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
2591
2600
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
2592
2601
  still resolves, for markup written against 1.39.0. */
@@ -2668,7 +2677,7 @@
2668
2677
  window.FreedayMask = { initMask: initMask, initReveal: initReveal, initAll: initAll };
2669
2678
  })();
2670
2679
 
2671
- /* Freeday — menu enhancer (optional, zero-dependency).
2680
+ /* Freeday, menu enhancer (optional, zero-dependency).
2672
2681
  * Menu-button pattern (WAI-ARIA APG): a trigger with aria-haspopup="menu" toggles a
2673
2682
  * role="menu" popup of role="menuitem" buttons. Also powers the split button
2674
2683
  * (main action + attached caret toggle). Auto-inits [data-fdy-menu].
@@ -2765,10 +2774,10 @@
2765
2774
  window.FreedayMenu = { init: initMenu, initAll: initAll };
2766
2775
  })();
2767
2776
 
2768
- /* Freeday — number field enhancer (optional, zero-dependency).
2777
+ /* Freeday, number field enhancer (optional, zero-dependency).
2769
2778
  * Gives a native <input type="number"> its increment/decrement affordance back, on the kit's terms:
2770
2779
  * .fdy-input hides the user agent's own spin buttons (unthemeable OS widgets), so this puts two real
2771
- * buttons in an .fdy-input-group instead. No new block — the group already owns the shared border,
2780
+ * buttons in an .fdy-input-group instead. No new block, the group already owns the shared border,
2772
2781
  * :focus-within ring and error promotion.
2773
2782
  *
2774
2783
  * Markup contract:
@@ -2834,7 +2843,7 @@
2834
2843
  if (frozen()) return;
2835
2844
  if (Number(btn.getAttribute('data-fdy-number-step')) < 0) input.stepDown();
2836
2845
  else input.stepUp();
2837
- /* The value changed without the user typing, so say so the way the platform does — frameworks
2846
+ /* The value changed without the user typing, so say so the way the platform does, frameworks
2838
2847
  listen to these, not to a kit-specific event. */
2839
2848
  input.dispatchEvent(new Event('input', { bubbles: true }));
2840
2849
  input.dispatchEvent(new Event('change', { bubbles: true }));
@@ -2870,10 +2879,10 @@
2870
2879
  window.FreedayNumber = { init: initNumber, initAll: initAll };
2871
2880
  })();
2872
2881
 
2873
- /* Freeday — popover positioning helper (optional, zero-dependency).
2882
+ /* Freeday, popover positioning helper (optional, zero-dependency).
2874
2883
  * Lifts a dropdown panel into the top layer via the native Popover API so it escapes ANY
2875
2884
  * ancestor overflow clip (a .fdy-card with overflow:hidden, an app-shell main with
2876
- * overflow:auto, …), then positions it `fixed` against its trigger — flipping above when
2885
+ * overflow:auto, …), then positions it `fixed` against its trigger, flipping above when
2877
2886
  * there is no room below, and matching the trigger width. The panel stays a DOM child of its
2878
2887
  * component, so focus, outside-click (.closest), and ARIA relationships keep working unchanged.
2879
2888
  *
@@ -2893,7 +2902,7 @@
2893
2902
  && typeof HTMLElement.prototype.showPopover === 'function';
2894
2903
 
2895
2904
  // Position `panel` (already shown, so it has a measurable box) against `trigger`'s rect.
2896
- // matchWidth (default true) floors the panel to the trigger width — right for input-shaped
2905
+ // matchWidth (default true) floors the panel to the trigger width, right for input-shaped
2897
2906
  // dropdowns (combo/cascade/date/time), skipped for menus that keep their own min-width.
2898
2907
  function place(panel, trigger, matchWidth) {
2899
2908
  var r = trigger.getBoundingClientRect();
@@ -2940,10 +2949,10 @@
2940
2949
  window.FreedayPopover = { attach: attach, place: place, supported: supported };
2941
2950
  })();
2942
2951
 
2943
- /* Freeday — rating enhancer (optional, zero-dependency).
2952
+ /* Freeday, rating enhancer (optional, zero-dependency).
2944
2953
  * Fills stars up to the checked radio (and previews on hover) for an interactive
2945
2954
  * [data-fdy-rating] radio group; for [data-fdy-readonly] it fills from data-value.
2946
- * The radios are native — arrow-key selection and form association come for free.
2955
+ * The radios are native, arrow-key selection and form association come for free.
2947
2956
  */
2948
2957
  (function () {
2949
2958
  'use strict';
@@ -2994,11 +3003,11 @@
2994
3003
  window.FreedayRating = { init: initRating, initAll: initAll };
2995
3004
  })();
2996
3005
 
2997
- /* Freeday — combobox enhancer (optional, zero-dependency).
3006
+ /* Freeday, combobox enhancer (optional, zero-dependency).
2998
3007
  * Implements the WAI-ARIA APG "select-only combobox" pattern for every
2999
3008
  * [data-fdy-combo] on the page. Progressive enhancement: the markup is the
3000
3009
  * component, this script wires behavior. In a Vue/React/Blazor app you would
3001
- * bind your own state instead — this is the framework-agnostic reference.
3010
+ * bind your own state instead, this is the framework-agnostic reference.
3002
3011
  *
3003
3012
  * Expected markup:
3004
3013
  * <div class="fdy-combo" data-fdy-combo data-value="x">
@@ -3021,7 +3030,7 @@
3021
3030
  var seq = 0;
3022
3031
  var openClose = null; // close() of the currently-open combo, or null
3023
3032
 
3024
- /* The tick lives in CSS now, so the option's text IS its label — no sibling walk to skip a glyph. */
3033
+ /* The tick lives in CSS now, so the option's text IS its label, no sibling walk to skip a glyph. */
3025
3034
  function optionLabel(opt) {
3026
3035
  return (opt.textContent || '').trim();
3027
3036
  }
@@ -3087,7 +3096,7 @@
3087
3096
  if (openClose === close) openClose = null;
3088
3097
  }
3089
3098
  // Mark an option selected + reflect it in the button. `silent` skips the change
3090
- // event, close, and focus — used by the programmatic setValue() below so a host
3099
+ // event, close, and focus, used by the programmatic setValue() below so a host
3091
3100
  // (e.g. a Blazor/framework wrapper) can push a value in without echoing an event
3092
3101
  // back to itself or stealing focus. User-driven picks go through choose() (loud).
3093
3102
  function select(opt, silent) {
@@ -3122,7 +3131,7 @@
3122
3131
  isOpen() ? close() : open();
3123
3132
  });
3124
3133
  // Pressing an option must not blur the button: a blur fires the focusout
3125
- // handler below, which closes the listbox before the option's click lands —
3134
+ // handler below, which closes the listbox before the option's click lands,
3126
3135
  // so the pick is lost (real mouse only; synthetic .click() never blurs).
3127
3136
  // preventDefault on mousedown keeps focus on the button without blocking click.
3128
3137
  listbox.addEventListener('mousedown', function (e) { e.preventDefault(); });
@@ -3209,10 +3218,10 @@
3209
3218
  window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue };
3210
3219
  })();
3211
3220
 
3212
- /* Freeday — slider value binding (optional, zero-dependency).
3221
+ /* Freeday, slider value binding (optional, zero-dependency).
3213
3222
  * Mirrors a [data-fdy-slider] range input's value into <output for="inputId">, with an
3214
3223
  * optional prefix/suffix on the output (data-fdy-prefix / data-fdy-suffix). The range itself
3215
- * is a native input — keyboard and ARIA are built in; this only updates the visible readout.
3224
+ * is a native input, keyboard and ARIA are built in; this only updates the visible readout.
3216
3225
  */
3217
3226
  (function () {
3218
3227
  'use strict';
@@ -3247,9 +3256,9 @@
3247
3256
  window.FreedaySlider = { init: initSlider, initAll: initAll };
3248
3257
  })();
3249
3258
 
3250
- /* Freeday — stepper/wizard enhancer (optional, zero-dependency).
3259
+ /* Freeday, stepper/wizard enhancer (optional, zero-dependency).
3251
3260
  * Drives a step header + panels: prev/next navigation, completed/active state, and
3252
- * clicking a reached step to revisit it (linear — can't skip ahead). Auto-inits
3261
+ * clicking a reached step to revisit it (linear, can't skip ahead). Auto-inits
3253
3262
  * [data-fdy-stepper].
3254
3263
  *
3255
3264
  * Markup: <div data-fdy-stepper>
@@ -3268,16 +3277,17 @@
3268
3277
  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>';
3269
3278
 
3270
3279
 
3271
- /* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
3272
- * path — and every one overridable per element, so a host that speaks another language (the
3273
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3274
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3280
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
3281
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
3282
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
3283
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
3284
+ * hard-coded further down. */
3275
3285
  var TEXT = {
3276
- done: 'Selesai',
3277
- next: 'Lanjut'
3286
+ done: 'Done',
3287
+ next: 'Next'
3278
3288
  };
3279
3289
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3280
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
3290
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
3281
3291
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3282
3292
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3283
3293
  still resolves, for markup written against 1.39.0. */
@@ -3367,7 +3377,7 @@
3367
3377
  window.FreedayStepper = { init: initStepper, initAll: initAll };
3368
3378
  })();
3369
3379
 
3370
- /* Freeday — data-table enhancer (optional, zero-dependency).
3380
+ /* Freeday, data-table enhancer (optional, zero-dependency).
3371
3381
  * Adds client-side search, column sort, pagination and row selection to any
3372
3382
  * [data-fdy-table]. Progressive enhancement over a plain semantic <table>;
3373
3383
  * in a framework app, do this server- or store-side instead.
@@ -3403,26 +3413,27 @@
3403
3413
  }
3404
3414
 
3405
3415
 
3406
- /* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
3407
- * path — and every one overridable per element, so a host that speaks another language (the
3408
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3409
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3416
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
3417
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
3418
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
3419
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
3420
+ * hard-coded further down. */
3410
3421
  var TEXT = {
3411
- prev: 'Sebelumnya',
3412
- next: 'Berikutnya',
3413
- selected: '{n} dipilih',
3414
- filter: 'Filter kolom',
3415
- filterText: 'Berisi teks',
3416
- filterTextPlaceholder: 'Berisi…',
3417
- filterEnum: 'Tampilkan nilai',
3418
- filterRange: 'Rentang nilai',
3422
+ prev: 'Previous',
3423
+ next: 'Next',
3424
+ selected: '{n} selected',
3425
+ filter: 'Filter column',
3426
+ filterText: 'Contains text',
3427
+ filterTextPlaceholder: 'Contains…',
3428
+ filterEnum: 'Show values',
3429
+ filterRange: 'Value range',
3419
3430
  reset: 'Reset',
3420
- close: 'Tutup',
3421
- rows: '{n} baris',
3422
- info: 'Menampilkan {from}–{to} dari {total}'
3431
+ close: 'Close',
3432
+ rows: '{n} rows',
3433
+ info: 'Showing {from}–{to} of {total}'
3423
3434
  };
3424
3435
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3425
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
3436
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
3426
3437
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3427
3438
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3428
3439
  still resolves, for markup written against 1.39.0. */
@@ -3902,7 +3913,7 @@
3902
3913
  window.FreedayTable = { init: initTable, initAll: initAll };
3903
3914
  })();
3904
3915
 
3905
- /* Freeday — tabs enhancer (optional, zero-dependency, WAI-ARIA APG tabs).
3916
+ /* Freeday, tabs enhancer (optional, zero-dependency, WAI-ARIA APG tabs).
3906
3917
  * Auto-inits every [data-fdy-tabs]. Roving tabindex + arrow/Home/End nav.
3907
3918
  *
3908
3919
  * Markup: a [data-fdy-tabs] with a [role="tablist"] of
@@ -3971,7 +3982,7 @@
3971
3982
  window.FreedayTabs = { init: initTabs, initAll: initAll };
3972
3983
  })();
3973
3984
 
3974
- /* Freeday — time picker enhancer (optional, zero-dependency).
3985
+ /* Freeday, time picker enhancer (optional, zero-dependency).
3975
3986
  * Builds an input-styled trigger + scrollable time-list popover (WAI-ARIA listbox)
3976
3987
  * from an empty [data-fdy-timepicker] wrapper. 24h HH:MM. Options run from
3977
3988
  * data-min (default 00:00) to data-max (default 23:59) every data-step minutes
@@ -4001,7 +4012,7 @@
4001
4012
  wrap.dataset.fdyTpReady = '1';
4002
4013
  wrap.classList.add('fdy-timepicker');
4003
4014
 
4004
- var label = wrap.getAttribute('data-label') || 'Pilih waktu';
4015
+ var label = wrap.getAttribute('data-label') || 'Choose a time';
4005
4016
  var placeholder = wrap.getAttribute('data-placeholder') || '--:--';
4006
4017
  var step = Math.max(1, parseInt(wrap.getAttribute('data-step') || '30', 10));
4007
4018
  var minM = valid(wrap.getAttribute('data-min')) ? toMin(wrap.getAttribute('data-min')) : 0;
@@ -4155,12 +4166,12 @@
4155
4166
  window.FreedayTimepicker = { init: initTime, initAll: initAll };
4156
4167
  })();
4157
4168
 
4158
- /* Freeday — toast API (optional, zero-dependency).
4169
+ /* Freeday, toast API (optional, zero-dependency).
4159
4170
  * Freeday.toast({ variant, title, message, timeout, key }) shows a transient
4160
4171
  * notification in a live region (created on first use). Returns the element.
4161
4172
  * variant: 'success' | 'warning' | 'danger' | 'info' (default: neutral)
4162
4173
  * timeout: ms before auto-dismiss (default 4000; 0 = sticky)
4163
- * key: optional stable id — a new toast with the same key REPLACES the
4174
+ * key: optional stable id, a new toast with the same key REPLACES the
4164
4175
  * existing one in place instead of stacking a duplicate (e.g. a burst
4165
4176
  * of identical failures shows one toast, refreshed).
4166
4177
  * Freeday.dismiss(nodeOrKey) closes a toast early (pass the returned element or its key).
@@ -4196,14 +4207,14 @@
4196
4207
 
4197
4208
  /* Put the region in the TOP LAYER, above any open <dialog>.
4198
4209
  *
4199
- * A modal dialog lives in the top layer, where z-index does not reach — so a toast raised by an
4210
+ * A modal dialog lives in the top layer, where z-index does not reach, so a toast raised by an
4200
4211
  * action taken inside a modal was painted behind that modal's backdrop, which is where an error
4201
4212
  * message is least useful. showPopover() joins the same layer, and membership there is ordered by
4202
4213
  * when you joined: hide-then-show re-joins at the top, so a toast shown while a dialog is open
4203
4214
  * lands above it rather than under the dialog that was opened after the last toast.
4204
4215
  *
4205
4216
  * Guarded twice on purpose. `showPopover` is absent before Chrome 114 / Safari 17 / Firefox 125,
4206
- * and the call throws if the element is disconnected or already open — in every one of those
4217
+ * and the call throws if the element is disconnected or already open, in every one of those
4207
4218
  * cases the region stays an ordinary fixed block at z-index 200, which is what it has always
4208
4219
  * been. A notification must never be the thing that throws.
4209
4220
  */
@@ -4234,15 +4245,16 @@
4234
4245
  }
4235
4246
 
4236
4247
 
4237
- /* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
4238
- * path — and every one overridable per element, so a host that speaks another language (the
4239
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4240
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4248
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
4249
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
4250
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
4251
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
4252
+ * hard-coded further down. */
4241
4253
  var TEXT = {
4242
- close: 'Tutup'
4254
+ close: 'Close'
4243
4255
  };
4244
4256
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
4245
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
4257
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
4246
4258
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4247
4259
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4248
4260
  still resolves, for markup written against 1.39.0. */
@@ -4317,10 +4329,10 @@
4317
4329
  window.Freeday.dismiss = dismiss; // dismiss(nodeOrKey)
4318
4330
  })();
4319
4331
 
4320
- /* Freeday — tree checkbox cascade (optional, zero-dependency).
4332
+ /* Freeday, tree checkbox cascade (optional, zero-dependency).
4321
4333
  * Turns a [data-fdy-tree] checkbox tree into a selectable one: checking a branch checks
4322
4334
  * every descendant; a partially-selected branch shows the native :indeterminate state.
4323
- * Rows are native <input class="fdy-checkbox">, labelled via aria-label — Space toggles.
4335
+ * Rows are native <input class="fdy-checkbox">, labelled via aria-label. Space toggles.
4324
4336
  * Selection never toggles the <details> (the checkbox stops click propagation).
4325
4337
  *
4326
4338
  * Markup contract:
@@ -4344,7 +4356,7 @@
4344
4356
  return Array.prototype.filter.call(ul.children, function (n) { return n.tagName === 'LI'; });
4345
4357
  }
4346
4358
 
4347
- // The checkbox belonging to a row's own <li> — branch summary or leaf, never a child's.
4359
+ // The checkbox belonging to a row's own <li>, branch summary or leaf, never a child's.
4348
4360
  function ownBox(li) {
4349
4361
  return li.querySelector(':scope > details > summary input[type="checkbox"]')
4350
4362
  || li.querySelector(':scope > input[type="checkbox"]');
@@ -4415,16 +4427,16 @@
4415
4427
  window.FreedayTree = { init: initTree, initAll: initAll };
4416
4428
  })();
4417
4429
 
4418
- /* Freeday — file-upload enhancer (optional, zero-dependency).
4430
+ /* Freeday, file-upload enhancer (optional, zero-dependency).
4419
4431
  * A drop target that also opens the file dialog on click / Enter / Space, validates each file
4420
4432
  * (accept + max size), and renders a per-file row with explicit state. Actual upload is the
4421
- * consumer's job — this is a controlled reference: wire the emitted event's row API to your
4433
+ * consumer's job, this is a controlled reference: wire the emitted event's row API to your
4422
4434
  * upload call, or set data-fdy-upload-simulate for a demo progress animation.
4423
4435
  *
4424
4436
  * Markup contract:
4425
4437
  * <div class="fdy-dropzone" data-fdy-dropzone role="button" tabindex="0"
4426
4438
  * data-max-size="10485760" data-filelist="#dz-list" [data-fdy-upload-simulate]
4427
- * aria-label="Unggah berkas — seret ke sini atau tekan Enter">
4439
+ * aria-label="Unggah berkas, seret ke sini atau tekan Enter">
4428
4440
  * <span class="fdy-dropzone__icon">…svg…</span>
4429
4441
  * <span class="fdy-dropzone__title">…</span>
4430
4442
  * <span class="fdy-dropzone__hint">…</span>
@@ -4432,15 +4444,15 @@
4432
4444
  * </div>
4433
4445
  * <div class="fdy-filelist" id="dz-list"></div>
4434
4446
  *
4435
- * Emits bubbling CustomEvents — BOTH on the dropzone element, which is the one target a consumer
4447
+ * Emits bubbling CustomEvents. BOTH on the dropzone element, which is the one target a consumer
4436
4448
  * needs to listen on:
4437
4449
  * "fdy-upload-add" {file, rejected, reason, row}
4438
4450
  * "fdy-upload-remove" {file}
4439
4451
  * `row` is the state machine over the rendered .fdy-file:
4440
- * .ready() (rest — where a dropped file starts) / .uploading() / .setProgress(pct) /
4452
+ * .ready() (rest, where a dropped file starts) / .uploading() / .setProgress(pct) /
4441
4453
  * .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
4442
4454
  * Note the file list is a SIBLING of the dropzone above, so nothing dispatched on a row would ever
4443
- * bubble through the zone — which is why removal fires on the zone and not on the row.
4455
+ * bubble through the zone, which is why removal fires on the zone and not on the row.
4444
4456
  */
4445
4457
  (function () {
4446
4458
  'use strict';
@@ -4468,21 +4480,22 @@
4468
4480
  }
4469
4481
 
4470
4482
 
4471
- /* User-facing strings. Indonesian by default — documented and deliberate for the raw enhancer
4472
- * path — and every one overridable per element, so a host that speaks another language (the
4473
- * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4474
- * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4483
+ /* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
4484
+ * enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
4485
+ * that speaks another language (an Indonesian app on the raw path) supplies its own without
4486
+ * forking this file. Keeping them in ONE table is also what lets a guard prove none is
4487
+ * hard-coded further down. */
4475
4488
  var TEXT = {
4476
- remove: 'Hapus {name}',
4477
- progress: 'Progres unggah {name}',
4478
- uploading: 'Mengunggah…',
4479
- waiting: 'Menunggu server…',
4480
- done: 'Terunggah',
4481
- badType: 'Tipe berkas tidak didukung.',
4482
- tooBig: 'Ukuran melebihi batas ({max}).'
4489
+ remove: 'Remove {name}',
4490
+ progress: 'Upload progress for {name}',
4491
+ uploading: 'Uploading…',
4492
+ waiting: 'Waiting for the server…',
4493
+ done: 'Uploaded',
4494
+ badType: 'Unsupported file type.',
4495
+ tooBig: 'Over the {max} limit.'
4483
4496
  };
4484
4497
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
4485
- as `data-fdy-text-filtertext` — while the kebab form anybody would reach for,
4498
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
4486
4499
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4487
4500
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4488
4501
  still resolves, for markup written against 1.39.0. */
@@ -4520,12 +4533,12 @@
4520
4533
  remove.setAttribute('aria-label', textOf(zone, 'remove', { name: file.name }));
4521
4534
  remove.innerHTML = '&times;';
4522
4535
  remove.addEventListener('click', function () {
4523
- /* Dispatched on the ZONE, not on the row — the same target as fdy-upload-add, so one listener
4536
+ /* Dispatched on the ZONE, not on the row, the same target as fdy-upload-add, so one listener
4524
4537
  on the dropzone gets both. The row lives in the file list, which the kit's own markup
4525
4538
  contract puts as a SIBLING of the dropzone, so a row event never bubbles through the zone:
4526
4539
  a consumer following the docs saw `add` arrive and `remove` never fire, with no error.
4527
- Firing on both would look safer, but it makes the pair asymmetric — one `add` and, for
4528
- anyone delegating on a common ancestor, two `remove`s — which is its own silent bug. It
4540
+ Firing on both would look safer, but it makes the pair asymmetric, one `add` and, for
4541
+ anyone delegating on a common ancestor, two `remove`s, which is its own silent bug. It
4529
4542
  also fixes the listless case: a row that was never attached bubbles to nothing at all. */
4530
4543
  zone.dispatchEvent(new CustomEvent('fdy-upload-remove', { bubbles: true, detail: { file: file } }));
4531
4544
  el.remove();
@@ -4557,14 +4570,14 @@
4557
4570
  }
4558
4571
  /* Back to a measured bar. While the indeterminate modifier is on it owns the bar's width, so an
4559
4572
  explicit one has to be restored when it comes off: .fdy-progress__bar is a plain block div, and
4560
- with no width at all it fills the track — a full bar, which is the opposite of what 0% means. */
4573
+ with no width at all it fills the track, a full bar, which is the opposite of what 0% means. */
4561
4574
  function determinate() {
4562
4575
  progressEl.classList.remove('fdy-progress--indeterminate');
4563
4576
  if (!bar.style.width) bar.style.width = '0%';
4564
4577
  }
4565
4578
  return {
4566
4579
  el: el,
4567
- /* The state a row starts in: chosen, not yet sent. The size alone — it makes no claim about
4580
+ /* The state a row starts in: chosen, not yet sent. The size alone, it makes no claim about
4568
4581
  a transfer, and no progress bar is shown, because nothing is in flight. Without this the
4569
4582
  state machine had no start state: done() claims success, fail() claims an error, and
4570
4583
  uploading() is a lie until the consumer actually sends the file. */
@@ -4588,12 +4601,12 @@
4588
4601
  bar.style.width = v + '%';
4589
4602
  progressEl.setAttribute('aria-valuenow', String(Math.round(v)));
4590
4603
  },
4591
- /* The bytes are gone and the server has not answered yet — extraction, scanning, transcoding.
4604
+ /* The bytes are gone and the server has not answered yet, extraction, scanning, transcoding.
4592
4605
  "Mengunggah…" turns false the moment the last byte leaves, and a determinate bar parked at
4593
4606
  100% is the most convincing "hung" signal a UI can produce, so this state reports no
4594
4607
  percentage: the bar goes indeterminate and the label belongs to the consumer, because only
4595
4608
  they know what the server is doing ("Membaca PDF…", "Memindai…"). done()/fail()/ready() need
4596
- no counterpart here — they drop the progress element outright, modifier and all. */
4609
+ no counterpart here, they drop the progress element outright, modifier and all. */
4597
4610
  waiting: function (label) {
4598
4611
  el.classList.remove('fdy-file--error', 'fdy-file--success');
4599
4612
  icon.innerHTML = FILE_ICON;
@@ -4601,7 +4614,7 @@
4601
4614
  ensureProgress();
4602
4615
  progressEl.classList.add('fdy-progress--indeterminate');
4603
4616
  /* The modifier styles .fdy-progress__bar, so it must sit on the CONTAINER, and the inline
4604
- width setProgress wrote has to go — an inline style beats any rule the modifier brings.
4617
+ width setProgress wrote has to go, an inline style beats any rule the modifier brings.
4605
4618
  aria-valuenow goes with it: a progressbar with no value is precisely what ARIA calls
4606
4619
  indeterminate, which is the contract COMPONENTS.md already states for this component. */
4607
4620
  bar.style.width = '';
@@ -4672,7 +4685,7 @@
4672
4685
  if (input) input.addEventListener('change', function () { handleFiles(input.files); input.value = ''; });
4673
4686
 
4674
4687
  /* The list is optional: rendering a row and announcing the file are separate jobs. A consumer
4675
- that wants its own markup simply provides no list — it still gets `fdy-upload-add`, and
4688
+ that wants its own markup simply provides no list, it still gets `fdy-upload-add`, and
4676
4689
  `detail.row` still works (its element is just never attached). Gating the EVENT on the list
4677
4690
  meant "bring your own row" silently cost you the notification that a file had arrived. */
4678
4691
  function handleFiles(fileList) {