@cahyo-dimas/freeday 1.54.0 → 2.0.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 (143) hide show
  1. package/CHANGELOG.md +625 -545
  2. package/COMPONENTS.md +141 -138
  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/table-model.d.ts +2 -2
  26. package/adapters/core/table-model.js +8 -8
  27. package/adapters/react/components/FdyAppShell.tsx +5 -5
  28. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  29. package/adapters/react/components/FdyCascade.tsx +1 -1
  30. package/adapters/react/components/FdyCfl.tsx +11 -11
  31. package/adapters/react/components/FdyDateRange.tsx +2 -2
  32. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  33. package/adapters/react/components/FdyDrawer.tsx +1 -1
  34. package/adapters/react/components/FdyModal.tsx +2 -2
  35. package/adapters/react/components/FdyTable.tsx +7 -7
  36. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  37. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  38. package/adapters/react/index.d.ts +1 -1
  39. package/adapters/react/useFreeday.js +3 -3
  40. package/adapters/vue/components/FdyAppShell.vue +5 -5
  41. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  42. package/adapters/vue/components/FdyCascade.vue +1 -1
  43. package/adapters/vue/components/FdyCfl.vue +8 -8
  44. package/adapters/vue/components/FdyChart.vue +2 -2
  45. package/adapters/vue/components/FdyCombo.vue +1 -1
  46. package/adapters/vue/components/FdyDateRange.vue +2 -2
  47. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  48. package/adapters/vue/components/FdyDrawer.vue +3 -3
  49. package/adapters/vue/components/FdyModal.vue +3 -3
  50. package/adapters/vue/components/FdyTable.vue +7 -7
  51. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  52. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  53. package/adapters/vue/index.d.ts +2 -2
  54. package/adapters/vue/useFreeday.js +3 -3
  55. package/dist/asset.d.ts +3 -3
  56. package/dist/freeday-app-shell.js +9 -9
  57. package/dist/freeday-autocomplete.js +2 -2
  58. package/dist/freeday-breakpoint.js +1 -1
  59. package/dist/freeday-carousel.js +5 -5
  60. package/dist/freeday-cascade.js +7 -7
  61. package/dist/freeday-cfl.js +9 -9
  62. package/dist/freeday-chart.js +11 -11
  63. package/dist/freeday-chip.js +3 -3
  64. package/dist/freeday-datepicker.js +11 -7
  65. package/dist/freeday-datetime.js +5 -5
  66. package/dist/freeday-drawer.js +1 -1
  67. package/dist/freeday-form.js +14 -14
  68. package/dist/freeday-mask.js +6 -6
  69. package/dist/freeday-menu.js +1 -1
  70. package/dist/freeday-number.js +3 -3
  71. package/dist/freeday-popover.js +3 -3
  72. package/dist/freeday-rating.js +2 -2
  73. package/dist/freeday-select.js +5 -5
  74. package/dist/freeday-slider.js +2 -2
  75. package/dist/freeday-stepper.js +7 -7
  76. package/dist/freeday-table.js +15 -15
  77. package/dist/freeday-tabs.js +1 -1
  78. package/dist/freeday-timepicker.js +2 -2
  79. package/dist/freeday-toast.js +8 -8
  80. package/dist/freeday-tree.js +3 -3
  81. package/dist/freeday-upload.js +25 -25
  82. package/dist/freeday.bundle.css +176 -176
  83. package/dist/freeday.css +170 -170
  84. package/dist/freeday.d.ts +2 -2
  85. package/dist/freeday.js +162 -158
  86. package/dist/freeday.tokens.css +5 -5
  87. package/docs/agent-onboarding.md +41 -41
  88. package/docs/getting-started.md +35 -35
  89. package/docs/integrations.md +18 -18
  90. package/docs/reference-screen.html +14 -14
  91. package/package.json +4 -3
  92. package/src/base.css +6 -6
  93. package/src/components/accordion.css +4 -4
  94. package/src/components/alert.css +1 -1
  95. package/src/components/app-shell.css +10 -10
  96. package/src/components/appbar.css +3 -3
  97. package/src/components/autocomplete.css +1 -1
  98. package/src/components/avatar.css +3 -3
  99. package/src/components/badge.css +5 -5
  100. package/src/components/breadcrumb.css +1 -1
  101. package/src/components/breakpoints.css +4 -4
  102. package/src/components/button.css +13 -13
  103. package/src/components/card.css +8 -8
  104. package/src/components/carousel.css +2 -2
  105. package/src/components/cascade.css +2 -2
  106. package/src/components/cfl.css +4 -4
  107. package/src/components/chart.css +3 -3
  108. package/src/components/chip.css +2 -2
  109. package/src/components/combo.css +3 -3
  110. package/src/components/composition.css +7 -7
  111. package/src/components/datepicker.css +6 -6
  112. package/src/components/datetimepicker.css +1 -1
  113. package/src/components/description-list.css +1 -1
  114. package/src/components/divider.css +1 -1
  115. package/src/components/drawer.css +1 -1
  116. package/src/components/file-upload.css +5 -5
  117. package/src/components/filterbar.css +7 -7
  118. package/src/components/form-grid.css +2 -2
  119. package/src/components/input-group.css +6 -6
  120. package/src/components/input.css +6 -6
  121. package/src/components/kbd.css +1 -1
  122. package/src/components/list.css +6 -6
  123. package/src/components/menu.css +3 -3
  124. package/src/components/modal.css +1 -1
  125. package/src/components/pagination.css +1 -1
  126. package/src/components/progress.css +1 -1
  127. package/src/components/rating.css +2 -2
  128. package/src/components/selection.css +4 -4
  129. package/src/components/skeleton.css +1 -1
  130. package/src/components/slider.css +1 -1
  131. package/src/components/spinner.css +1 -1
  132. package/src/components/states.css +1 -1
  133. package/src/components/stepper.css +2 -2
  134. package/src/components/table.css +15 -15
  135. package/src/components/tabs.css +3 -3
  136. package/src/components/timeline.css +1 -1
  137. package/src/components/timepicker.css +1 -1
  138. package/src/components/toast.css +3 -3
  139. package/src/components/tooltip.css +1 -1
  140. package/src/components/tree.css +2 -2
  141. package/tokens/breakpoints.d.ts +2 -2
  142. package/tokens/breakpoints.mjs +3 -3
  143. 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,16 @@
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
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
424
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
425
425
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
426
426
  var TEXT = {
427
- position: '{n} dari {total}',
427
+ position: '{n} of {total}',
428
428
  slide: 'Slide {n}'
429
429
  };
430
430
  /* 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,
431
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
432
432
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
433
433
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
434
434
  still resolves, for markup written against 1.39.0. */
@@ -540,7 +540,7 @@
540
540
  window.FreedayCarousel = { init: initCarousel, initAll: initAll };
541
541
  })();
542
542
 
543
- /* Freeday — cascade select enhancer (optional, zero-dependency).
543
+ /* Freeday, cascade select enhancer (optional, zero-dependency).
544
544
  * Turns a [data-fdy-cascade] wrapper into a hierarchical drill-down picker. The
545
545
  * data model is a hidden nested <ul> inside the wrapper: a <li> with a child <ul>
546
546
  * is a branch (drills in), a <li> without is a leaf (selects). Each <li> carries
@@ -596,16 +596,16 @@
596
596
  }
597
597
 
598
598
 
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
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
601
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
602
602
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
603
603
  var TEXT = {
604
- back: 'Kembali satu tingkat',
604
+ back: 'Back one level',
605
605
  submenu: '{label}, submenu'
606
606
  };
607
607
  /* 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,
608
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
609
609
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
610
610
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
611
611
  still resolves, for markup written against 1.39.0. */
@@ -630,8 +630,8 @@
630
630
  var root = sourceUl ? parse(sourceUl) : [];
631
631
  if (sourceUl) sourceUl.remove();
632
632
 
633
- var label = wrap.getAttribute('data-label') || 'Pilih';
634
- var placeholder = wrap.getAttribute('data-placeholder') || 'Pilih…';
633
+ var label = wrap.getAttribute('data-label') || 'Select';
634
+ var placeholder = wrap.getAttribute('data-placeholder') || 'Select…';
635
635
  var sep = wrap.getAttribute('data-separator') || ' / ';
636
636
 
637
637
  var trigger = document.createElement('button');
@@ -826,24 +826,24 @@
826
826
  window.FreedayCascade = { init: initCascade, initAll: initAll };
827
827
  })();
828
828
 
829
- /* Freeday — choose-from-list enhancer (optional, zero-dependency).
829
+ /* Freeday, choose-from-list enhancer (optional, zero-dependency).
830
830
  * Wires a searchable master-data picker dialog to read-only fields.
831
831
  * Progressive enhancement over a native <dialog>; in a framework app, drive the
832
832
  * dialog as a controlled component (fetchPage callback + server cache), not a store.
833
833
  *
834
834
  * Markup contract (see docs):
835
- * - Field group: [data-fdy-cfl="<dialogId>"] — usually a `.fdy-input-group` holding a
835
+ * - Field group: [data-fdy-cfl="<dialogId>"], usually a `.fdy-input-group` holding a
836
836
  * read-only `.fdy-input` and a trigger `<button class="fdy-input-group__btn">`.
837
837
  * Optional `data-fdy-cfl-display="<key>"` picks which row dataset key fills the input
838
838
  * (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.
839
+ * - Standalone opener: [data-fdy-cfl-open="<dialogId>"], opens the dialog with no bound field.
840
840
  * - Dialog: <dialog id="<dialogId>" class="fdy-modal fdy-modal--cfl"> containing
841
841
  * a `[data-fdy-cfl-search]` input, `.fdy-cfl__row` <tr> rows carrying `data-*` fields,
842
842
  * a `[data-fdy-cfl-empty]` element, and (multi only) `[data-fdy-cfl-multiple]` on the
843
843
  * dialog plus `[data-fdy-cfl-count]` and `[data-fdy-cfl-confirm]` in the footer.
844
844
  *
845
845
  * 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
846
+ * opened standalone): detail {row} for single-select, {rows} for multi-select, each a plain
847
847
  * object copy of the row's dataset. Consumers fill sibling fields (code → name) from it.
848
848
  */
849
849
  (function () {
@@ -862,15 +862,15 @@
862
862
  }
863
863
 
864
864
 
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
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
867
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
868
868
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
869
869
  var TEXT = {
870
- selected: '{n} dipilih'
870
+ selected: '{n} selected'
871
871
  };
872
872
  /* 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,
873
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
874
874
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
875
875
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
876
876
  still resolves, for markup written against 1.39.0. */
@@ -931,7 +931,7 @@
931
931
  var input = activeGroup.querySelector('.fdy-input');
932
932
  if (input) {
933
933
  if (detail.rows) {
934
- var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} dipilih';
934
+ var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} selected';
935
935
  input.value = detail.rows.length ? tpl.replace('{n}', String(detail.rows.length)) : '';
936
936
  } else {
937
937
  var key = activeGroup.getAttribute('data-fdy-cfl-display') || 'value';
@@ -1070,7 +1070,7 @@
1070
1070
  window.FreedayCfl = { init: initTriggers };
1071
1071
  })();
1072
1072
 
1073
- /* Freeday — chart renderer (optional, zero-dependency, no external chart lib).
1073
+ /* Freeday, chart renderer (optional, zero-dependency, no external chart lib).
1074
1074
  * Renders sparkline / bar / line / area / donut into an element from data attributes.
1075
1075
  * Auto-inits [data-fdy-chart] once; call FreedayChart.update(el) to re-render after the
1076
1076
  * data attributes change (used by the freeday/vue <FdyChart> wrapper for reactive data).
@@ -1078,14 +1078,14 @@
1078
1078
  * Single-series colour (sparkline / simple bar / single line) via data-fdy-color references a
1079
1079
  * semantic token by name (primary, accent, success, warning, danger, info). Multi-series
1080
1080
  * 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
1081
+ * cycled, 8-series cap); pass data-fdy-colors to override, a name is a semantic token
1082
1082
  * (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
1083
1083
  * validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
1084
1084
  * Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
1085
1085
  * --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
1086
1086
  * viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
1087
1087
  * 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
1088
+ * Charts are an enhancement, put a table/text fallback inside the element and it will be
1089
1089
  * replaced on render; give the element role="img" + aria-label.
1090
1090
  *
1091
1091
  * Sparkline: <span data-fdy-chart="sparkline" data-values="4,6,5,8,7,10" data-fdy-color="primary">
@@ -1122,7 +1122,7 @@
1122
1122
  return v ? v.split(',').map(function (s) { return s.trim(); }) : [];
1123
1123
  }
1124
1124
  // 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 —
1125
+ // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree,
1126
1126
  // measured in Chrome's AX tree, every one of those nodes is live and unignored; what saves the
1127
1127
  // advice is only that AT treats a NAMED role="img" as a leaf and does not descend. So the kit
1128
1128
  // hides them itself, and the author's aria-label really is the whole text alternative.
@@ -1297,7 +1297,7 @@
1297
1297
  // Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
1298
1298
  // pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
1299
1299
  // 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.)
1300
+ // size by plotWidth/320 instead, a 9px axis label rendered ~39px on a 1728px viewport.)
1301
1301
  var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
1302
1302
  var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
1303
1303
  el.classList.add('fdy-chart-xy');
@@ -1318,7 +1318,7 @@
1318
1318
  el.appendChild(plot);
1319
1319
 
1320
1320
  // 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
1321
+ // so the box is already final. A detached or display:none chart measures 0, fall back to the
1322
1322
  // legacy box and let the ResizeObserver repaint it once it is actually laid out.
1323
1323
  var box = plot.getBoundingClientRect();
1324
1324
  var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
@@ -1362,7 +1362,7 @@
1362
1362
 
1363
1363
  // x category labels with autoskip. Labels are author-supplied, so estimate the widest one instead
1364
1364
  // 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
1365
+ // would collide with the one before it, including the forced last one (a fraction-of-a-slot
1366
1366
  // overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
1367
1367
  var widest = 0;
1368
1368
  for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
@@ -1373,7 +1373,7 @@
1373
1373
  for (var xi = 0; xi < n; xi++) {
1374
1374
  if (xi % step === 0 || xi === n - 1) kept.push(xi);
1375
1375
  }
1376
- // The last label is worth forcing, but not on top of its neighbour — drop the penultimate one.
1376
+ // The last label is worth forcing, but not on top of its neighbour, drop the penultimate one.
1377
1377
  if (kept.length >= 2 && xPos(kept[kept.length - 1]) - xPos(kept[kept.length - 2]) < minGap) {
1378
1378
  kept.splice(kept.length - 2, 1);
1379
1379
  }
@@ -1506,17 +1506,17 @@
1506
1506
  var li = document.createElement('li');
1507
1507
  var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorAt(i);
1508
1508
  li.appendChild(sw);
1509
- li.appendChild(document.createTextNode((labels[i] || ('#' + (i + 1))) + ' — ' + Math.round((v / total) * 100) + '%'));
1509
+ li.appendChild(document.createTextNode((labels[i] || ('#' + (i + 1))) + ' · ' + Math.round((v / total) * 100) + '%'));
1510
1510
  legend.appendChild(li);
1511
1511
  });
1512
1512
  el.appendChild(ring);
1513
- // Honour data-fdy-legend for the donut too — a caller may supply its own richer legend. 'auto'
1513
+ // Honour data-fdy-legend for the donut too, a caller may supply its own richer legend. 'auto'
1514
1514
  // keeps today's behaviour (a donut is unreadable without labels, so it shows); 'none' suppresses it.
1515
1515
  if ((el.getAttribute('data-fdy-legend') || 'auto') !== 'none') el.appendChild(legend);
1516
1516
  ensureImg(el);
1517
1517
  }
1518
1518
 
1519
- // Full (re-)render from current data attributes. Idempotent — safe to call repeatedly.
1519
+ // Full (re-)render from current data attributes. Idempotent, safe to call repeatedly.
1520
1520
  function renderChart(el) {
1521
1521
  var type = el.getAttribute('data-fdy-chart');
1522
1522
  if (type === 'sparkline') renderSparkline(el);
@@ -1551,12 +1551,12 @@
1551
1551
  window.FreedayChart = { init: initChart, initAll: initAll, update: renderChart };
1552
1552
  })();
1553
1553
 
1554
- /* Freeday — chip enhancer (optional, zero-dependency).
1554
+ /* Freeday, chip enhancer (optional, zero-dependency).
1555
1555
  * Two behaviours, both progressive enhancement:
1556
1556
  *
1557
- * 1. Removable chips — any .fdy-chip__remove button removes its chip and emits a
1557
+ * 1. Removable chips, any .fdy-chip__remove button removes its chip and emits a
1558
1558
  * bubbling "fdy-chip-remove" (detail {value}).
1559
- * 2. Choice / filter groups — a [data-fdy-chips] container of interactive chips
1559
+ * 2. Choice / filter groups, a [data-fdy-chips] container of interactive chips
1560
1560
  * (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
1561
1561
  * on click. Add data-single for one-at-a-time (radio-like) selection; filter
1562
1562
  * chips get a leading check when pressed. Emits "fdy-chip-change" on the group
@@ -1638,9 +1638,9 @@
1638
1638
  window.FreedayChip = { initGroup: initGroup, initRemove: initRemove, initAll: initAll };
1639
1639
  })();
1640
1640
 
1641
- /* Freeday — date picker enhancer (optional, zero-dependency).
1641
+ /* Freeday, date picker enhancer (optional, zero-dependency).
1642
1642
  * 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.
1643
+ * Locale comes from <html lang> (via Intl), month/weekday/value formatting is not hardcoded.
1644
1644
  *
1645
1645
  * Markup contract:
1646
1646
  * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
@@ -1659,7 +1659,11 @@
1659
1659
  (function () {
1660
1660
  'use strict';
1661
1661
 
1662
- var LOCALE = document.documentElement.getAttribute('lang') || 'id';
1662
+ /* The page's own `lang` wins, which is the better escape hatch than any attribute the kit
1663
+ could invent: an Indonesian app writes <html lang="id"> and gets Indonesian month and
1664
+ weekday names back automatically. The FALLBACK follows the kit's default language, or a
1665
+ page without `lang` would read English labels around Indonesian month names. */
1666
+ var LOCALE = document.documentElement.getAttribute('lang') || 'en';
1663
1667
  var uidSeq = 0;
1664
1668
  function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
1665
1669
  function pad(n) { return n < 10 ? '0' + n : '' + n; }
@@ -1706,10 +1710,10 @@
1706
1710
  var view = startOfDay(selected || new Date());
1707
1711
  view = new Date(view.getFullYear(), view.getMonth(), 1);
1708
1712
  var focusDate = null;
1709
- /* 'days' | 'months' — the calendar drills one level up rather than growing furniture beside the
1713
+ /* 'days' | 'months', the calendar drills one level up rather than growing furniture beside the
1710
1714
  title. Before this, the only pointer route to another month was one click per month: from
1711
1715
  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
1716
+ shortcut nobody can see is not an affordance, the repo's own test helper clicked "previous
1713
1717
  month" twenty-four times to reach a date two years back. */
1714
1718
  var mode = 'days';
1715
1719
  var focusMonth = null;
@@ -1908,7 +1912,7 @@
1908
1912
  var YEARS_PER_PAGE = 12;
1909
1913
  function yearPageStart(y) { return Math.floor(y / YEARS_PER_PAGE) * YEARS_PER_PAGE; }
1910
1914
 
1911
- /* A year is only unreachable when EVERY month in it falls outside min/max — the same rule
1915
+ /* A year is only unreachable when EVERY month in it falls outside min/max, the same rule
1912
1916
  monthDisabled applies one level down, for the same reason: a bound that sits inside the year
1913
1917
  disables neither end. */
1914
1918
  function yearDisabled(y) {
@@ -2153,7 +2157,7 @@
2153
2157
  var f = fromApi.getValue();
2154
2158
  var t = toApi.getValue();
2155
2159
  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
2160
+ fromApi.setRange(f, t); // start stays free, picking a later start clears the end below
2157
2161
  toApi.setRange(f, t);
2158
2162
  }
2159
2163
  fromEl.addEventListener('fdy-datepicker-change', function () {
@@ -2187,9 +2191,9 @@
2187
2191
  window.FreedayDatepicker = { init: initPicker, initAll: initAll };
2188
2192
  })();
2189
2193
 
2190
- /* Freeday — datetime picker composer (optional, zero-dependency).
2194
+ /* Freeday, datetime picker composer (optional, zero-dependency).
2191
2195
  * 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
2196
+ * anything itself, freeday-datepicker.js and freeday-timepicker.js enhance the
2193
2197
  * children; this only listens for their change events and emits a single combined
2194
2198
  * "fdy-datetime-change" (detail {date, time, value}). value is "YYYY-MM-DDTHH:MM"
2195
2199
  * when both parts are set, else "". Icon variants are controlled per child picker
@@ -2199,7 +2203,7 @@
2199
2203
  * <div data-fdy-datepicker data-value="2026-07-21"></div>
2200
2204
  * <div data-fdy-timepicker data-value="14:30"></div></div>
2201
2205
  * 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).
2206
+ * child triggers as one control (read-only is adapter-only, a vanilla datetime is interactive).
2203
2207
  */
2204
2208
  (function () {
2205
2209
  'use strict';
@@ -2214,7 +2218,7 @@
2214
2218
  if (!dp || !tp) return;
2215
2219
 
2216
2220
  // 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
2221
+ // disabled/invalid control. Deferred with setTimeout(0). NOT a microtask: the timepicker
2218
2222
  // enhancer registers its DOMContentLoaded init AFTER this composer, and microtasks drain
2219
2223
  // *between* listeners (so a microtask would run before the timepicker trigger exists). A
2220
2224
  // macrotask waits until the whole init pass is done and both triggers are built. (readonly is
@@ -2245,7 +2249,7 @@
2245
2249
  }));
2246
2250
  }
2247
2251
 
2248
- // Listen for the children's bubbling change events (order-independent — no need
2252
+ // Listen for the children's bubbling change events (order-independent, no need
2249
2253
  // for the child enhancers to have run before this composer).
2250
2254
  wrap.addEventListener('fdy-datepicker-change', function (e) { date = e.detail.value; emit(); });
2251
2255
  wrap.addEventListener('fdy-time-select', function (e) { time = e.detail.value; emit(); });
@@ -2274,7 +2278,7 @@
2274
2278
  window.FreedayDatetime = { init: initDatetime, initAll: initAll };
2275
2279
  })();
2276
2280
 
2277
- /* Freeday — drawer enhancer (optional, zero-dependency).
2281
+ /* Freeday, drawer enhancer (optional, zero-dependency).
2278
2282
  * A trigger [data-fdy-drawer="drawerId"] opens the <dialog class="fdy-drawer" id="drawerId">
2279
2283
  * as a modal side panel (native showModal gives focus-trap, Esc, and return-focus). Clicking
2280
2284
  * the backdrop or any [data-close] inside closes it.
@@ -2323,7 +2327,7 @@
2323
2327
  window.FreedayDrawer = { init: initTriggers, initAll: initTriggers };
2324
2328
  })();
2325
2329
 
2326
- /* Freeday — form validation enhancer (optional, zero-dependency).
2330
+ /* Freeday, form validation enhancer (optional, zero-dependency).
2327
2331
  * Wires the native Constraint Validation API to accessible inline errors on any
2328
2332
  * [data-fdy-validate] <form>. Native-first: constraints come from the standard
2329
2333
  * markup attributes (required, type, pattern, min/max, minlength/maxlength, step);
@@ -2363,24 +2367,24 @@
2363
2367
  badInput: 'type',
2364
2368
  customError: 'mismatch'
2365
2369
  };
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,
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,
2368
2372
  * `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
2369
2373
  * a host in another language needs: it sets nine messages once instead of on every input.
2370
2374
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2371
2375
  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.'
2376
+ required: 'Required.',
2377
+ type: 'Invalid format.',
2378
+ pattern: 'Doesn’t match the required format.',
2379
+ minlength: 'Too short.',
2380
+ maxlength: 'Too long.',
2381
+ min: 'Too small.',
2382
+ max: 'Too large.',
2383
+ step: 'Not a valid step.',
2384
+ mismatch: 'Values don’t match.',
2385
+ invalid: 'Invalid.'
2382
2386
  };
2383
- /* Kebab-cased for the lookup — see the note in the other enhancers: HTML lowercases attribute
2387
+ /* Kebab-cased for the lookup, see the note in the other enhancers: HTML lowercases attribute
2384
2388
  names, so `data-fdy-text-filter-text` and `data-fdy-text-filtertext` are different attributes
2385
2389
  and only one of them is what an author would write. */
2386
2390
  function textOf(root, key) {
@@ -2524,7 +2528,7 @@
2524
2528
  window.FreedayForm = { init: initForm, initAll: initAll };
2525
2529
  })();
2526
2530
 
2527
- /* Freeday — input mask + password reveal enhancer (optional, zero-dependency).
2531
+ /* Freeday, input mask + password reveal enhancer (optional, zero-dependency).
2528
2532
  *
2529
2533
  * [data-fdy-mask]="pattern" formats a text input as you type. Placeholders:
2530
2534
  * # a digit (0-9) A a letter (a-z/A-Z) * a letter or digit
@@ -2577,16 +2581,16 @@
2577
2581
  }
2578
2582
 
2579
2583
 
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
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
2582
2586
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
2583
2587
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2584
2588
  var TEXT = {
2585
- show: 'Tampilkan kata sandi',
2586
- hide: 'Sembunyikan kata sandi'
2589
+ show: 'Show password',
2590
+ hide: 'Hide password'
2587
2591
  };
2588
2592
  /* 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,
2593
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
2590
2594
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
2591
2595
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
2592
2596
  still resolves, for markup written against 1.39.0. */
@@ -2668,7 +2672,7 @@
2668
2672
  window.FreedayMask = { initMask: initMask, initReveal: initReveal, initAll: initAll };
2669
2673
  })();
2670
2674
 
2671
- /* Freeday — menu enhancer (optional, zero-dependency).
2675
+ /* Freeday, menu enhancer (optional, zero-dependency).
2672
2676
  * Menu-button pattern (WAI-ARIA APG): a trigger with aria-haspopup="menu" toggles a
2673
2677
  * role="menu" popup of role="menuitem" buttons. Also powers the split button
2674
2678
  * (main action + attached caret toggle). Auto-inits [data-fdy-menu].
@@ -2765,10 +2769,10 @@
2765
2769
  window.FreedayMenu = { init: initMenu, initAll: initAll };
2766
2770
  })();
2767
2771
 
2768
- /* Freeday — number field enhancer (optional, zero-dependency).
2772
+ /* Freeday, number field enhancer (optional, zero-dependency).
2769
2773
  * Gives a native <input type="number"> its increment/decrement affordance back, on the kit's terms:
2770
2774
  * .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,
2775
+ * buttons in an .fdy-input-group instead. No new block, the group already owns the shared border,
2772
2776
  * :focus-within ring and error promotion.
2773
2777
  *
2774
2778
  * Markup contract:
@@ -2834,7 +2838,7 @@
2834
2838
  if (frozen()) return;
2835
2839
  if (Number(btn.getAttribute('data-fdy-number-step')) < 0) input.stepDown();
2836
2840
  else input.stepUp();
2837
- /* The value changed without the user typing, so say so the way the platform does — frameworks
2841
+ /* The value changed without the user typing, so say so the way the platform does, frameworks
2838
2842
  listen to these, not to a kit-specific event. */
2839
2843
  input.dispatchEvent(new Event('input', { bubbles: true }));
2840
2844
  input.dispatchEvent(new Event('change', { bubbles: true }));
@@ -2870,10 +2874,10 @@
2870
2874
  window.FreedayNumber = { init: initNumber, initAll: initAll };
2871
2875
  })();
2872
2876
 
2873
- /* Freeday — popover positioning helper (optional, zero-dependency).
2877
+ /* Freeday, popover positioning helper (optional, zero-dependency).
2874
2878
  * Lifts a dropdown panel into the top layer via the native Popover API so it escapes ANY
2875
2879
  * 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
2880
+ * overflow:auto, …), then positions it `fixed` against its trigger, flipping above when
2877
2881
  * there is no room below, and matching the trigger width. The panel stays a DOM child of its
2878
2882
  * component, so focus, outside-click (.closest), and ARIA relationships keep working unchanged.
2879
2883
  *
@@ -2893,7 +2897,7 @@
2893
2897
  && typeof HTMLElement.prototype.showPopover === 'function';
2894
2898
 
2895
2899
  // 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
2900
+ // matchWidth (default true) floors the panel to the trigger width, right for input-shaped
2897
2901
  // dropdowns (combo/cascade/date/time), skipped for menus that keep their own min-width.
2898
2902
  function place(panel, trigger, matchWidth) {
2899
2903
  var r = trigger.getBoundingClientRect();
@@ -2940,10 +2944,10 @@
2940
2944
  window.FreedayPopover = { attach: attach, place: place, supported: supported };
2941
2945
  })();
2942
2946
 
2943
- /* Freeday — rating enhancer (optional, zero-dependency).
2947
+ /* Freeday, rating enhancer (optional, zero-dependency).
2944
2948
  * Fills stars up to the checked radio (and previews on hover) for an interactive
2945
2949
  * [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.
2950
+ * The radios are native, arrow-key selection and form association come for free.
2947
2951
  */
2948
2952
  (function () {
2949
2953
  'use strict';
@@ -2994,11 +2998,11 @@
2994
2998
  window.FreedayRating = { init: initRating, initAll: initAll };
2995
2999
  })();
2996
3000
 
2997
- /* Freeday — combobox enhancer (optional, zero-dependency).
3001
+ /* Freeday, combobox enhancer (optional, zero-dependency).
2998
3002
  * Implements the WAI-ARIA APG "select-only combobox" pattern for every
2999
3003
  * [data-fdy-combo] on the page. Progressive enhancement: the markup is the
3000
3004
  * 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.
3005
+ * bind your own state instead, this is the framework-agnostic reference.
3002
3006
  *
3003
3007
  * Expected markup:
3004
3008
  * <div class="fdy-combo" data-fdy-combo data-value="x">
@@ -3021,7 +3025,7 @@
3021
3025
  var seq = 0;
3022
3026
  var openClose = null; // close() of the currently-open combo, or null
3023
3027
 
3024
- /* The tick lives in CSS now, so the option's text IS its label — no sibling walk to skip a glyph. */
3028
+ /* The tick lives in CSS now, so the option's text IS its label, no sibling walk to skip a glyph. */
3025
3029
  function optionLabel(opt) {
3026
3030
  return (opt.textContent || '').trim();
3027
3031
  }
@@ -3087,7 +3091,7 @@
3087
3091
  if (openClose === close) openClose = null;
3088
3092
  }
3089
3093
  // 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
3094
+ // event, close, and focus, used by the programmatic setValue() below so a host
3091
3095
  // (e.g. a Blazor/framework wrapper) can push a value in without echoing an event
3092
3096
  // back to itself or stealing focus. User-driven picks go through choose() (loud).
3093
3097
  function select(opt, silent) {
@@ -3122,7 +3126,7 @@
3122
3126
  isOpen() ? close() : open();
3123
3127
  });
3124
3128
  // 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 —
3129
+ // handler below, which closes the listbox before the option's click lands,
3126
3130
  // so the pick is lost (real mouse only; synthetic .click() never blurs).
3127
3131
  // preventDefault on mousedown keeps focus on the button without blocking click.
3128
3132
  listbox.addEventListener('mousedown', function (e) { e.preventDefault(); });
@@ -3209,10 +3213,10 @@
3209
3213
  window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue };
3210
3214
  })();
3211
3215
 
3212
- /* Freeday — slider value binding (optional, zero-dependency).
3216
+ /* Freeday, slider value binding (optional, zero-dependency).
3213
3217
  * Mirrors a [data-fdy-slider] range input's value into <output for="inputId">, with an
3214
3218
  * 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.
3219
+ * is a native input, keyboard and ARIA are built in; this only updates the visible readout.
3216
3220
  */
3217
3221
  (function () {
3218
3222
  'use strict';
@@ -3247,9 +3251,9 @@
3247
3251
  window.FreedaySlider = { init: initSlider, initAll: initAll };
3248
3252
  })();
3249
3253
 
3250
- /* Freeday — stepper/wizard enhancer (optional, zero-dependency).
3254
+ /* Freeday, stepper/wizard enhancer (optional, zero-dependency).
3251
3255
  * 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
3256
+ * clicking a reached step to revisit it (linear, can't skip ahead). Auto-inits
3253
3257
  * [data-fdy-stepper].
3254
3258
  *
3255
3259
  * Markup: <div data-fdy-stepper>
@@ -3268,16 +3272,16 @@
3268
3272
  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
3273
 
3270
3274
 
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
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
3273
3277
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3274
3278
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3275
3279
  var TEXT = {
3276
- done: 'Selesai',
3277
- next: 'Lanjut'
3280
+ done: 'Done',
3281
+ next: 'Next'
3278
3282
  };
3279
3283
  /* 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,
3284
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
3281
3285
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3282
3286
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3283
3287
  still resolves, for markup written against 1.39.0. */
@@ -3367,7 +3371,7 @@
3367
3371
  window.FreedayStepper = { init: initStepper, initAll: initAll };
3368
3372
  })();
3369
3373
 
3370
- /* Freeday — data-table enhancer (optional, zero-dependency).
3374
+ /* Freeday, data-table enhancer (optional, zero-dependency).
3371
3375
  * Adds client-side search, column sort, pagination and row selection to any
3372
3376
  * [data-fdy-table]. Progressive enhancement over a plain semantic <table>;
3373
3377
  * in a framework app, do this server- or store-side instead.
@@ -3403,26 +3407,26 @@
3403
3407
  }
3404
3408
 
3405
3409
 
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
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
3408
3412
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3409
3413
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3410
3414
  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',
3415
+ prev: 'Previous',
3416
+ next: 'Next',
3417
+ selected: '{n} selected',
3418
+ filter: 'Filter column',
3419
+ filterText: 'Contains text',
3420
+ filterTextPlaceholder: 'Contains…',
3421
+ filterEnum: 'Show values',
3422
+ filterRange: 'Value range',
3419
3423
  reset: 'Reset',
3420
- close: 'Tutup',
3421
- rows: '{n} baris',
3422
- info: 'Menampilkan {from}–{to} dari {total}'
3424
+ close: 'Close',
3425
+ rows: '{n} rows',
3426
+ info: 'Showing {from}–{to} of {total}'
3423
3427
  };
3424
3428
  /* 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,
3429
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
3426
3430
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3427
3431
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3428
3432
  still resolves, for markup written against 1.39.0. */
@@ -3902,7 +3906,7 @@
3902
3906
  window.FreedayTable = { init: initTable, initAll: initAll };
3903
3907
  })();
3904
3908
 
3905
- /* Freeday — tabs enhancer (optional, zero-dependency, WAI-ARIA APG tabs).
3909
+ /* Freeday, tabs enhancer (optional, zero-dependency, WAI-ARIA APG tabs).
3906
3910
  * Auto-inits every [data-fdy-tabs]. Roving tabindex + arrow/Home/End nav.
3907
3911
  *
3908
3912
  * Markup: a [data-fdy-tabs] with a [role="tablist"] of
@@ -3971,7 +3975,7 @@
3971
3975
  window.FreedayTabs = { init: initTabs, initAll: initAll };
3972
3976
  })();
3973
3977
 
3974
- /* Freeday — time picker enhancer (optional, zero-dependency).
3978
+ /* Freeday, time picker enhancer (optional, zero-dependency).
3975
3979
  * Builds an input-styled trigger + scrollable time-list popover (WAI-ARIA listbox)
3976
3980
  * from an empty [data-fdy-timepicker] wrapper. 24h HH:MM. Options run from
3977
3981
  * data-min (default 00:00) to data-max (default 23:59) every data-step minutes
@@ -4001,7 +4005,7 @@
4001
4005
  wrap.dataset.fdyTpReady = '1';
4002
4006
  wrap.classList.add('fdy-timepicker');
4003
4007
 
4004
- var label = wrap.getAttribute('data-label') || 'Pilih waktu';
4008
+ var label = wrap.getAttribute('data-label') || 'Choose a time';
4005
4009
  var placeholder = wrap.getAttribute('data-placeholder') || '--:--';
4006
4010
  var step = Math.max(1, parseInt(wrap.getAttribute('data-step') || '30', 10));
4007
4011
  var minM = valid(wrap.getAttribute('data-min')) ? toMin(wrap.getAttribute('data-min')) : 0;
@@ -4155,12 +4159,12 @@
4155
4159
  window.FreedayTimepicker = { init: initTime, initAll: initAll };
4156
4160
  })();
4157
4161
 
4158
- /* Freeday — toast API (optional, zero-dependency).
4162
+ /* Freeday, toast API (optional, zero-dependency).
4159
4163
  * Freeday.toast({ variant, title, message, timeout, key }) shows a transient
4160
4164
  * notification in a live region (created on first use). Returns the element.
4161
4165
  * variant: 'success' | 'warning' | 'danger' | 'info' (default: neutral)
4162
4166
  * timeout: ms before auto-dismiss (default 4000; 0 = sticky)
4163
- * key: optional stable id — a new toast with the same key REPLACES the
4167
+ * key: optional stable id, a new toast with the same key REPLACES the
4164
4168
  * existing one in place instead of stacking a duplicate (e.g. a burst
4165
4169
  * of identical failures shows one toast, refreshed).
4166
4170
  * Freeday.dismiss(nodeOrKey) closes a toast early (pass the returned element or its key).
@@ -4196,14 +4200,14 @@
4196
4200
 
4197
4201
  /* Put the region in the TOP LAYER, above any open <dialog>.
4198
4202
  *
4199
- * A modal dialog lives in the top layer, where z-index does not reach — so a toast raised by an
4203
+ * A modal dialog lives in the top layer, where z-index does not reach, so a toast raised by an
4200
4204
  * action taken inside a modal was painted behind that modal's backdrop, which is where an error
4201
4205
  * message is least useful. showPopover() joins the same layer, and membership there is ordered by
4202
4206
  * when you joined: hide-then-show re-joins at the top, so a toast shown while a dialog is open
4203
4207
  * lands above it rather than under the dialog that was opened after the last toast.
4204
4208
  *
4205
4209
  * 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
4210
+ * and the call throws if the element is disconnected or already open, in every one of those
4207
4211
  * cases the region stays an ordinary fixed block at z-index 200, which is what it has always
4208
4212
  * been. A notification must never be the thing that throws.
4209
4213
  */
@@ -4234,15 +4238,15 @@
4234
4238
  }
4235
4239
 
4236
4240
 
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
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
4239
4243
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4240
4244
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4241
4245
  var TEXT = {
4242
- close: 'Tutup'
4246
+ close: 'Close'
4243
4247
  };
4244
4248
  /* 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,
4249
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
4246
4250
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4247
4251
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4248
4252
  still resolves, for markup written against 1.39.0. */
@@ -4317,10 +4321,10 @@
4317
4321
  window.Freeday.dismiss = dismiss; // dismiss(nodeOrKey)
4318
4322
  })();
4319
4323
 
4320
- /* Freeday — tree checkbox cascade (optional, zero-dependency).
4324
+ /* Freeday, tree checkbox cascade (optional, zero-dependency).
4321
4325
  * Turns a [data-fdy-tree] checkbox tree into a selectable one: checking a branch checks
4322
4326
  * 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.
4327
+ * Rows are native <input class="fdy-checkbox">, labelled via aria-label. Space toggles.
4324
4328
  * Selection never toggles the <details> (the checkbox stops click propagation).
4325
4329
  *
4326
4330
  * Markup contract:
@@ -4344,7 +4348,7 @@
4344
4348
  return Array.prototype.filter.call(ul.children, function (n) { return n.tagName === 'LI'; });
4345
4349
  }
4346
4350
 
4347
- // The checkbox belonging to a row's own <li> — branch summary or leaf, never a child's.
4351
+ // The checkbox belonging to a row's own <li>, branch summary or leaf, never a child's.
4348
4352
  function ownBox(li) {
4349
4353
  return li.querySelector(':scope > details > summary input[type="checkbox"]')
4350
4354
  || li.querySelector(':scope > input[type="checkbox"]');
@@ -4415,16 +4419,16 @@
4415
4419
  window.FreedayTree = { init: initTree, initAll: initAll };
4416
4420
  })();
4417
4421
 
4418
- /* Freeday — file-upload enhancer (optional, zero-dependency).
4422
+ /* Freeday, file-upload enhancer (optional, zero-dependency).
4419
4423
  * A drop target that also opens the file dialog on click / Enter / Space, validates each file
4420
4424
  * (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
4425
+ * consumer's job, this is a controlled reference: wire the emitted event's row API to your
4422
4426
  * upload call, or set data-fdy-upload-simulate for a demo progress animation.
4423
4427
  *
4424
4428
  * Markup contract:
4425
4429
  * <div class="fdy-dropzone" data-fdy-dropzone role="button" tabindex="0"
4426
4430
  * data-max-size="10485760" data-filelist="#dz-list" [data-fdy-upload-simulate]
4427
- * aria-label="Unggah berkas — seret ke sini atau tekan Enter">
4431
+ * aria-label="Unggah berkas, seret ke sini atau tekan Enter">
4428
4432
  * <span class="fdy-dropzone__icon">…svg…</span>
4429
4433
  * <span class="fdy-dropzone__title">…</span>
4430
4434
  * <span class="fdy-dropzone__hint">…</span>
@@ -4432,15 +4436,15 @@
4432
4436
  * </div>
4433
4437
  * <div class="fdy-filelist" id="dz-list"></div>
4434
4438
  *
4435
- * Emits bubbling CustomEvents — BOTH on the dropzone element, which is the one target a consumer
4439
+ * Emits bubbling CustomEvents. BOTH on the dropzone element, which is the one target a consumer
4436
4440
  * needs to listen on:
4437
4441
  * "fdy-upload-add" {file, rejected, reason, row}
4438
4442
  * "fdy-upload-remove" {file}
4439
4443
  * `row` is the state machine over the rendered .fdy-file:
4440
- * .ready() (rest — where a dropped file starts) / .uploading() / .setProgress(pct) /
4444
+ * .ready() (rest, where a dropped file starts) / .uploading() / .setProgress(pct) /
4441
4445
  * .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
4442
4446
  * 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.
4447
+ * bubble through the zone, which is why removal fires on the zone and not on the row.
4444
4448
  */
4445
4449
  (function () {
4446
4450
  'use strict';
@@ -4468,21 +4472,21 @@
4468
4472
  }
4469
4473
 
4470
4474
 
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
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
4473
4477
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4474
4478
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4475
4479
  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}).'
4480
+ remove: 'Remove {name}',
4481
+ progress: 'Upload progress for {name}',
4482
+ uploading: 'Uploading…',
4483
+ waiting: 'Waiting for the server…',
4484
+ done: 'Uploaded',
4485
+ badType: 'Unsupported file type.',
4486
+ tooBig: 'Over the {max} limit.'
4483
4487
  };
4484
4488
  /* 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,
4489
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
4486
4490
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4487
4491
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4488
4492
  still resolves, for markup written against 1.39.0. */
@@ -4520,12 +4524,12 @@
4520
4524
  remove.setAttribute('aria-label', textOf(zone, 'remove', { name: file.name }));
4521
4525
  remove.innerHTML = '&times;';
4522
4526
  remove.addEventListener('click', function () {
4523
- /* Dispatched on the ZONE, not on the row — the same target as fdy-upload-add, so one listener
4527
+ /* Dispatched on the ZONE, not on the row, the same target as fdy-upload-add, so one listener
4524
4528
  on the dropzone gets both. The row lives in the file list, which the kit's own markup
4525
4529
  contract puts as a SIBLING of the dropzone, so a row event never bubbles through the zone:
4526
4530
  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
4531
+ Firing on both would look safer, but it makes the pair asymmetric, one `add` and, for
4532
+ anyone delegating on a common ancestor, two `remove`s, which is its own silent bug. It
4529
4533
  also fixes the listless case: a row that was never attached bubbles to nothing at all. */
4530
4534
  zone.dispatchEvent(new CustomEvent('fdy-upload-remove', { bubbles: true, detail: { file: file } }));
4531
4535
  el.remove();
@@ -4557,14 +4561,14 @@
4557
4561
  }
4558
4562
  /* Back to a measured bar. While the indeterminate modifier is on it owns the bar's width, so an
4559
4563
  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. */
4564
+ with no width at all it fills the track, a full bar, which is the opposite of what 0% means. */
4561
4565
  function determinate() {
4562
4566
  progressEl.classList.remove('fdy-progress--indeterminate');
4563
4567
  if (!bar.style.width) bar.style.width = '0%';
4564
4568
  }
4565
4569
  return {
4566
4570
  el: el,
4567
- /* The state a row starts in: chosen, not yet sent. The size alone — it makes no claim about
4571
+ /* The state a row starts in: chosen, not yet sent. The size alone, it makes no claim about
4568
4572
  a transfer, and no progress bar is shown, because nothing is in flight. Without this the
4569
4573
  state machine had no start state: done() claims success, fail() claims an error, and
4570
4574
  uploading() is a lie until the consumer actually sends the file. */
@@ -4588,12 +4592,12 @@
4588
4592
  bar.style.width = v + '%';
4589
4593
  progressEl.setAttribute('aria-valuenow', String(Math.round(v)));
4590
4594
  },
4591
- /* The bytes are gone and the server has not answered yet — extraction, scanning, transcoding.
4595
+ /* The bytes are gone and the server has not answered yet, extraction, scanning, transcoding.
4592
4596
  "Mengunggah…" turns false the moment the last byte leaves, and a determinate bar parked at
4593
4597
  100% is the most convincing "hung" signal a UI can produce, so this state reports no
4594
4598
  percentage: the bar goes indeterminate and the label belongs to the consumer, because only
4595
4599
  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. */
4600
+ no counterpart here, they drop the progress element outright, modifier and all. */
4597
4601
  waiting: function (label) {
4598
4602
  el.classList.remove('fdy-file--error', 'fdy-file--success');
4599
4603
  icon.innerHTML = FILE_ICON;
@@ -4601,7 +4605,7 @@
4601
4605
  ensureProgress();
4602
4606
  progressEl.classList.add('fdy-progress--indeterminate');
4603
4607
  /* 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.
4608
+ width setProgress wrote has to go, an inline style beats any rule the modifier brings.
4605
4609
  aria-valuenow goes with it: a progressbar with no value is precisely what ARIA calls
4606
4610
  indeterminate, which is the contract COMPONENTS.md already states for this component. */
4607
4611
  bar.style.width = '';
@@ -4672,7 +4676,7 @@
4672
4676
  if (input) input.addEventListener('change', function () { handleFiles(input.files); input.value = ''; });
4673
4677
 
4674
4678
  /* 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
4679
+ that wants its own markup simply provides no list, it still gets `fdy-upload-add`, and
4676
4680
  `detail.row` still works (its element is just never attached). Gating the EVENT on the list
4677
4681
  meant "bring your own row" silently cost you the notification that a file had arrived. */
4678
4682
  function handleFiles(fileList) {