@cahyo-dimas/freeday 1.53.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 (145) hide show
  1. package/CHANGELOG.md +657 -535
  2. package/COMPONENTS.md +153 -135
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +39 -35
  6. package/adapters/blazor/FdyAppShell.razor +50 -0
  7. package/adapters/blazor/FdyAppShell.razor.cs +114 -0
  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 +30 -0
  24. package/adapters/core/app-shell.js +134 -0
  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 +159 -0
  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 +2 -1
  39. package/adapters/react/index.js +1 -0
  40. package/adapters/react/useFreeday.js +3 -3
  41. package/adapters/vue/components/FdyAppShell.vue +160 -0
  42. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  43. package/adapters/vue/components/FdyCascade.vue +1 -1
  44. package/adapters/vue/components/FdyCfl.vue +8 -8
  45. package/adapters/vue/components/FdyChart.vue +2 -2
  46. package/adapters/vue/components/FdyCombo.vue +1 -1
  47. package/adapters/vue/components/FdyDateRange.vue +2 -2
  48. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  49. package/adapters/vue/components/FdyDrawer.vue +3 -3
  50. package/adapters/vue/components/FdyModal.vue +3 -3
  51. package/adapters/vue/components/FdyTable.vue +7 -7
  52. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  53. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  54. package/adapters/vue/index.d.ts +3 -2
  55. package/adapters/vue/index.js +1 -0
  56. package/adapters/vue/useFreeday.js +3 -3
  57. package/dist/asset.d.ts +3 -3
  58. package/dist/freeday-app-shell.js +49 -9
  59. package/dist/freeday-autocomplete.js +2 -2
  60. package/dist/freeday-breakpoint.js +1 -1
  61. package/dist/freeday-carousel.js +5 -5
  62. package/dist/freeday-cascade.js +7 -7
  63. package/dist/freeday-cfl.js +9 -9
  64. package/dist/freeday-chart.js +11 -11
  65. package/dist/freeday-chip.js +3 -3
  66. package/dist/freeday-datepicker.js +11 -7
  67. package/dist/freeday-datetime.js +5 -5
  68. package/dist/freeday-drawer.js +1 -1
  69. package/dist/freeday-form.js +14 -14
  70. package/dist/freeday-mask.js +6 -6
  71. package/dist/freeday-menu.js +1 -1
  72. package/dist/freeday-number.js +3 -3
  73. package/dist/freeday-popover.js +3 -3
  74. package/dist/freeday-rating.js +2 -2
  75. package/dist/freeday-select.js +5 -5
  76. package/dist/freeday-slider.js +2 -2
  77. package/dist/freeday-stepper.js +7 -7
  78. package/dist/freeday-table.js +15 -15
  79. package/dist/freeday-tabs.js +1 -1
  80. package/dist/freeday-timepicker.js +2 -2
  81. package/dist/freeday-toast.js +8 -8
  82. package/dist/freeday-tree.js +3 -3
  83. package/dist/freeday-upload.js +25 -25
  84. package/dist/freeday.bundle.css +176 -176
  85. package/dist/freeday.css +170 -170
  86. package/dist/freeday.d.ts +2 -2
  87. package/dist/freeday.js +202 -158
  88. package/dist/freeday.tokens.css +5 -5
  89. package/docs/agent-onboarding.md +42 -42
  90. package/docs/getting-started.md +35 -35
  91. package/docs/integrations.md +18 -18
  92. package/docs/reference-screen.html +14 -14
  93. package/package.json +5 -4
  94. package/src/base.css +6 -6
  95. package/src/components/accordion.css +4 -4
  96. package/src/components/alert.css +1 -1
  97. package/src/components/app-shell.css +10 -10
  98. package/src/components/appbar.css +3 -3
  99. package/src/components/autocomplete.css +1 -1
  100. package/src/components/avatar.css +3 -3
  101. package/src/components/badge.css +5 -5
  102. package/src/components/breadcrumb.css +1 -1
  103. package/src/components/breakpoints.css +4 -4
  104. package/src/components/button.css +13 -13
  105. package/src/components/card.css +8 -8
  106. package/src/components/carousel.css +2 -2
  107. package/src/components/cascade.css +2 -2
  108. package/src/components/cfl.css +4 -4
  109. package/src/components/chart.css +3 -3
  110. package/src/components/chip.css +2 -2
  111. package/src/components/combo.css +3 -3
  112. package/src/components/composition.css +7 -7
  113. package/src/components/datepicker.css +6 -6
  114. package/src/components/datetimepicker.css +1 -1
  115. package/src/components/description-list.css +1 -1
  116. package/src/components/divider.css +1 -1
  117. package/src/components/drawer.css +1 -1
  118. package/src/components/file-upload.css +5 -5
  119. package/src/components/filterbar.css +7 -7
  120. package/src/components/form-grid.css +2 -2
  121. package/src/components/input-group.css +6 -6
  122. package/src/components/input.css +6 -6
  123. package/src/components/kbd.css +1 -1
  124. package/src/components/list.css +6 -6
  125. package/src/components/menu.css +3 -3
  126. package/src/components/modal.css +1 -1
  127. package/src/components/pagination.css +1 -1
  128. package/src/components/progress.css +1 -1
  129. package/src/components/rating.css +2 -2
  130. package/src/components/selection.css +4 -4
  131. package/src/components/skeleton.css +1 -1
  132. package/src/components/slider.css +1 -1
  133. package/src/components/spinner.css +1 -1
  134. package/src/components/states.css +1 -1
  135. package/src/components/stepper.css +2 -2
  136. package/src/components/table.css +15 -15
  137. package/src/components/tabs.css +3 -3
  138. package/src/components/timeline.css +1 -1
  139. package/src/components/timepicker.css +1 -1
  140. package/src/components/toast.css +3 -3
  141. package/src/components/tooltip.css +1 -1
  142. package/src/components/tree.css +2 -2
  143. package/tokens/breakpoints.d.ts +2 -2
  144. package/tokens/breakpoints.mjs +3 -3
  145. package/tokens/tokens.json +1 -1
package/dist/freeday.js CHANGED
@@ -1,22 +1,27 @@
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
- * Emits nothing; the classes are the state. FreedayAppShell.init(root) for late-mounted markup.
19
+ * Emits a bubbling `fdy-app-nav` CustomEvent (detail {visible}) whenever the nav's visibility
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
23
+ * hear the change and to drive it, without owning the behaviour twice.
24
+ * FreedayAppShell.init(root) for late-mounted markup.
20
25
  */
21
26
  (function () {
22
27
  'use strict';
@@ -28,7 +33,7 @@
28
33
  + ' textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
29
34
 
30
35
  /* getClientRects(), not offsetParent: an overlay sidebar is position:fixed, and a fixed element
31
- 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. */
32
37
  function focusables(root) {
33
38
  return Array.prototype.filter.call(root.querySelectorAll(FOCUSABLE), function (el) {
34
39
  return el.getClientRects().length > 0;
@@ -53,18 +58,25 @@
53
58
 
54
59
  var mqWide = window.matchMedia(WIDE);
55
60
  var restoreTo = null;
61
+ var lastVisible = null;
56
62
 
57
63
  function isOverlayOpen() { return app.classList.contains('fdy-app--nav-open'); }
58
64
  function isCollapsed() { return app.classList.contains('fdy-app--nav-collapsed'); }
59
65
  function navVisible() { return mqWide.matches ? !isCollapsed() : isOverlayOpen(); }
60
66
 
61
- /* 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
62
68
  kit's business, not the reader's. */
63
69
  function sync() {
64
70
  var visible = navVisible();
65
71
  toggle.setAttribute('aria-expanded', String(visible));
66
72
  setInert(sidebar, !visible);
67
73
  setInert(content, !mqWide.matches && visible);
74
+ /* Announce only real changes. The first sync() runs at init to describe the state the markup
75
+ arrived in, which is not something a host asked for and must not look like one. */
76
+ if (lastVisible !== null && visible !== lastVisible) {
77
+ app.dispatchEvent(new CustomEvent('fdy-app-nav', { bubbles: true, detail: { visible: visible } }));
78
+ }
79
+ lastVisible = visible;
68
80
  }
69
81
 
70
82
  function open() {
@@ -81,7 +93,7 @@
81
93
  }
82
94
  }
83
95
 
84
- /* 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
85
97
  yank focus out from under whatever the reader was doing. */
86
98
  function close(restoreFocus) {
87
99
  if (!isOverlayOpen()) return;
@@ -108,7 +120,7 @@
108
120
 
109
121
  if (backdrop) backdrop.addEventListener('click', function () { close(); });
110
122
 
111
- /* 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
112
124
  the page you just asked for is the one thing every hand-rolled copy in this repo disagreed on. */
113
125
  sidebar.addEventListener('click', function (e) {
114
126
  if (!isOverlayOpen()) return;
@@ -149,6 +161,23 @@
149
161
  });
150
162
 
151
163
  sync();
164
+
165
+ /* The same handle the other enhancers expose (`_fdyCombo` and friends): a host that binds its
166
+ own state needs to drive this without reaching for the class names the kit reserves. */
167
+ app._fdyAppShell = {
168
+ isVisible: navVisible,
169
+ setVisible: function (visible) {
170
+ if (visible === navVisible()) return;
171
+ if (mqWide.matches) {
172
+ app.classList.toggle('fdy-app--nav-collapsed', !visible);
173
+ sync();
174
+ } else if (visible) {
175
+ open();
176
+ } else {
177
+ close();
178
+ }
179
+ },
180
+ };
152
181
  }
153
182
 
154
183
  function initShells(context) {
@@ -165,13 +194,24 @@
165
194
  initShells();
166
195
  }
167
196
 
168
- window.FreedayAppShell = { init: initShells, initAll: initShells };
197
+ window.FreedayAppShell = {
198
+ init: initShells,
199
+ initAll: initShells,
200
+ /* Both take the shell root. A missing or un-initialised root is a no-op rather than a throw:
201
+ a host may race the enhancer on first render, and a crash there is worse than a late sync. */
202
+ setVisible: function (root, visible) {
203
+ if (root && root._fdyAppShell) root._fdyAppShell.setVisible(visible === true);
204
+ },
205
+ isVisible: function (root) {
206
+ return !!(root && root._fdyAppShell && root._fdyAppShell.isVisible());
207
+ },
208
+ };
169
209
  })();
170
210
 
171
- /* Freeday autocomplete enhancer (optional, zero-dependency).
211
+ /* Freeday, autocomplete enhancer (optional, zero-dependency).
172
212
  * Editable combobox (WAI-ARIA APG): a text input filters a role="listbox" of role="option"
173
213
  * items as you type; select fills the input. Auto-inits [data-fdy-autocomplete]. Options are
174
- * 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.
175
215
  *
176
216
  * Markup: <div data-fdy-autocomplete class="fdy-autocomplete">
177
217
  * <input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
@@ -307,7 +347,7 @@
307
347
  window.FreedayAutocomplete = { init: initAutocomplete, initAll: initAll };
308
348
  })();
309
349
 
310
- /* Freeday breakpoint provider (optional, zero-dependency).
350
+ /* Freeday, breakpoint provider (optional, zero-dependency).
311
351
  * The MudBreakpointProvider equivalent for JS consumers: stamps the current breakpoint on
312
352
  * <html data-breakpoint="xs|sm|md|lg|xl"> and emits a bubbling "fdy-breakpoint-change"
313
353
  * CustomEvent (detail {breakpoint}) whenever it changes. Also fills any element with
@@ -359,7 +399,7 @@
359
399
  };
360
400
  })();
361
401
 
362
- /* Freeday carousel enhancer (optional, zero-dependency).
402
+ /* Freeday, carousel enhancer (optional, zero-dependency).
363
403
  * Scroll-snap slider with prev/next arrows, generated dot indicators, keyboard (←/→),
364
404
  * and optional autoplay (data-fdy-autoplay="4000", pauses on hover/focus). Auto-inits
365
405
  * [data-fdy-carousel].
@@ -379,16 +419,16 @@
379
419
  'use strict';
380
420
 
381
421
 
382
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
383
- * 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
384
424
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
385
425
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
386
426
  var TEXT = {
387
- position: '{n} dari {total}',
427
+ position: '{n} of {total}',
388
428
  slide: 'Slide {n}'
389
429
  };
390
430
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
391
- 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,
392
432
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
393
433
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
394
434
  still resolves, for markup written against 1.39.0. */
@@ -500,7 +540,7 @@
500
540
  window.FreedayCarousel = { init: initCarousel, initAll: initAll };
501
541
  })();
502
542
 
503
- /* Freeday cascade select enhancer (optional, zero-dependency).
543
+ /* Freeday, cascade select enhancer (optional, zero-dependency).
504
544
  * Turns a [data-fdy-cascade] wrapper into a hierarchical drill-down picker. The
505
545
  * data model is a hidden nested <ul> inside the wrapper: a <li> with a child <ul>
506
546
  * is a branch (drills in), a <li> without is a leaf (selects). Each <li> carries
@@ -556,16 +596,16 @@
556
596
  }
557
597
 
558
598
 
559
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
560
- * 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
561
601
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
562
602
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
563
603
  var TEXT = {
564
- back: 'Kembali satu tingkat',
604
+ back: 'Back one level',
565
605
  submenu: '{label}, submenu'
566
606
  };
567
607
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
568
- 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,
569
609
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
570
610
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
571
611
  still resolves, for markup written against 1.39.0. */
@@ -590,8 +630,8 @@
590
630
  var root = sourceUl ? parse(sourceUl) : [];
591
631
  if (sourceUl) sourceUl.remove();
592
632
 
593
- var label = wrap.getAttribute('data-label') || 'Pilih';
594
- var placeholder = wrap.getAttribute('data-placeholder') || 'Pilih…';
633
+ var label = wrap.getAttribute('data-label') || 'Select';
634
+ var placeholder = wrap.getAttribute('data-placeholder') || 'Select…';
595
635
  var sep = wrap.getAttribute('data-separator') || ' / ';
596
636
 
597
637
  var trigger = document.createElement('button');
@@ -786,24 +826,24 @@
786
826
  window.FreedayCascade = { init: initCascade, initAll: initAll };
787
827
  })();
788
828
 
789
- /* Freeday choose-from-list enhancer (optional, zero-dependency).
829
+ /* Freeday, choose-from-list enhancer (optional, zero-dependency).
790
830
  * Wires a searchable master-data picker dialog to read-only fields.
791
831
  * Progressive enhancement over a native <dialog>; in a framework app, drive the
792
832
  * dialog as a controlled component (fetchPage callback + server cache), not a store.
793
833
  *
794
834
  * Markup contract (see docs):
795
- * - 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
796
836
  * read-only `.fdy-input` and a trigger `<button class="fdy-input-group__btn">`.
797
837
  * Optional `data-fdy-cfl-display="<key>"` picks which row dataset key fills the input
798
838
  * (default "value"); optional `data-fdy-cfl-summary="{n} dipilih"` formats the multi label.
799
- * - 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.
800
840
  * - Dialog: <dialog id="<dialogId>" class="fdy-modal fdy-modal--cfl"> containing
801
841
  * a `[data-fdy-cfl-search]` input, `.fdy-cfl__row` <tr> rows carrying `data-*` fields,
802
842
  * a `[data-fdy-cfl-empty]` element, and (multi only) `[data-fdy-cfl-multiple]` on the
803
843
  * dialog plus `[data-fdy-cfl-count]` and `[data-fdy-cfl-confirm]` in the footer.
804
844
  *
805
845
  * Emits a bubbling "fdy-cfl-select" CustomEvent on the field group (or the dialog when
806
- * 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
807
847
  * object copy of the row's dataset. Consumers fill sibling fields (code → name) from it.
808
848
  */
809
849
  (function () {
@@ -822,15 +862,15 @@
822
862
  }
823
863
 
824
864
 
825
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
826
- * 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
827
867
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
828
868
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
829
869
  var TEXT = {
830
- selected: '{n} dipilih'
870
+ selected: '{n} selected'
831
871
  };
832
872
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
833
- 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,
834
874
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
835
875
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
836
876
  still resolves, for markup written against 1.39.0. */
@@ -891,7 +931,7 @@
891
931
  var input = activeGroup.querySelector('.fdy-input');
892
932
  if (input) {
893
933
  if (detail.rows) {
894
- var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} dipilih';
934
+ var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} selected';
895
935
  input.value = detail.rows.length ? tpl.replace('{n}', String(detail.rows.length)) : '';
896
936
  } else {
897
937
  var key = activeGroup.getAttribute('data-fdy-cfl-display') || 'value';
@@ -1030,7 +1070,7 @@
1030
1070
  window.FreedayCfl = { init: initTriggers };
1031
1071
  })();
1032
1072
 
1033
- /* Freeday chart renderer (optional, zero-dependency, no external chart lib).
1073
+ /* Freeday, chart renderer (optional, zero-dependency, no external chart lib).
1034
1074
  * Renders sparkline / bar / line / area / donut into an element from data attributes.
1035
1075
  * Auto-inits [data-fdy-chart] once; call FreedayChart.update(el) to re-render after the
1036
1076
  * data attributes change (used by the freeday/vue <FdyChart> wrapper for reactive data).
@@ -1038,14 +1078,14 @@
1038
1078
  * Single-series colour (sparkline / simple bar / single line) via data-fdy-color references a
1039
1079
  * semantic token by name (primary, accent, success, warning, danger, info). Multi-series
1040
1080
  * defaults draw from the validated categorical --chart-1..8 palette in fixed order (never
1041
- * 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
1042
1082
  * (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
1043
1083
  * validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
1044
1084
  * Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
1045
1085
  * --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
1046
1086
  * viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
1047
1087
  * ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
1048
- * 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
1049
1089
  * replaced on render; give the element role="img" + aria-label.
1050
1090
  *
1051
1091
  * Sparkline: <span data-fdy-chart="sparkline" data-values="4,6,5,8,7,10" data-fdy-color="primary">
@@ -1082,7 +1122,7 @@
1082
1122
  return v ? v.split(',').map(function (s) { return s.trim(); }) : [];
1083
1123
  }
1084
1124
  // role="img" is Children Presentational per ARIA, so the docs used to say a chart's rendered
1085
- // 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,
1086
1126
  // measured in Chrome's AX tree, every one of those nodes is live and unignored; what saves the
1087
1127
  // advice is only that AT treats a NAMED role="img" as a leaf and does not descend. So the kit
1088
1128
  // hides them itself, and the author's aria-label really is the whole text alternative.
@@ -1257,7 +1297,7 @@
1257
1297
  // Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
1258
1298
  // pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
1259
1299
  // every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
1260
- // 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.)
1261
1301
  var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
1262
1302
  var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
1263
1303
  el.classList.add('fdy-chart-xy');
@@ -1278,7 +1318,7 @@
1278
1318
  el.appendChild(plot);
1279
1319
 
1280
1320
  // Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
1281
- // 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
1282
1322
  // legacy box and let the ResizeObserver repaint it once it is actually laid out.
1283
1323
  var box = plot.getBoundingClientRect();
1284
1324
  var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
@@ -1322,7 +1362,7 @@
1322
1362
 
1323
1363
  // x category labels with autoskip. Labels are author-supplied, so estimate the widest one instead
1324
1364
  // of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
1325
- // 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
1326
1366
  // overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
1327
1367
  var widest = 0;
1328
1368
  for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
@@ -1333,7 +1373,7 @@
1333
1373
  for (var xi = 0; xi < n; xi++) {
1334
1374
  if (xi % step === 0 || xi === n - 1) kept.push(xi);
1335
1375
  }
1336
- // 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.
1337
1377
  if (kept.length >= 2 && xPos(kept[kept.length - 1]) - xPos(kept[kept.length - 2]) < minGap) {
1338
1378
  kept.splice(kept.length - 2, 1);
1339
1379
  }
@@ -1466,17 +1506,17 @@
1466
1506
  var li = document.createElement('li');
1467
1507
  var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorAt(i);
1468
1508
  li.appendChild(sw);
1469
- 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) + '%'));
1470
1510
  legend.appendChild(li);
1471
1511
  });
1472
1512
  el.appendChild(ring);
1473
- // 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'
1474
1514
  // keeps today's behaviour (a donut is unreadable without labels, so it shows); 'none' suppresses it.
1475
1515
  if ((el.getAttribute('data-fdy-legend') || 'auto') !== 'none') el.appendChild(legend);
1476
1516
  ensureImg(el);
1477
1517
  }
1478
1518
 
1479
- // 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.
1480
1520
  function renderChart(el) {
1481
1521
  var type = el.getAttribute('data-fdy-chart');
1482
1522
  if (type === 'sparkline') renderSparkline(el);
@@ -1511,12 +1551,12 @@
1511
1551
  window.FreedayChart = { init: initChart, initAll: initAll, update: renderChart };
1512
1552
  })();
1513
1553
 
1514
- /* Freeday chip enhancer (optional, zero-dependency).
1554
+ /* Freeday, chip enhancer (optional, zero-dependency).
1515
1555
  * Two behaviours, both progressive enhancement:
1516
1556
  *
1517
- * 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
1518
1558
  * bubbling "fdy-chip-remove" (detail {value}).
1519
- * 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
1520
1560
  * (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
1521
1561
  * on click. Add data-single for one-at-a-time (radio-like) selection; filter
1522
1562
  * chips get a leading check when pressed. Emits "fdy-chip-change" on the group
@@ -1598,9 +1638,9 @@
1598
1638
  window.FreedayChip = { initGroup: initGroup, initRemove: initRemove, initAll: initAll };
1599
1639
  })();
1600
1640
 
1601
- /* Freeday date picker enhancer (optional, zero-dependency).
1641
+ /* Freeday, date picker enhancer (optional, zero-dependency).
1602
1642
  * Builds an input-styled trigger + calendar popover from an empty wrapper.
1603
- * 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.
1604
1644
  *
1605
1645
  * Markup contract:
1606
1646
  * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
@@ -1619,7 +1659,11 @@
1619
1659
  (function () {
1620
1660
  'use strict';
1621
1661
 
1622
- 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';
1623
1667
  var uidSeq = 0;
1624
1668
  function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
1625
1669
  function pad(n) { return n < 10 ? '0' + n : '' + n; }
@@ -1666,10 +1710,10 @@
1666
1710
  var view = startOfDay(selected || new Date());
1667
1711
  view = new Date(view.getFullYear(), view.getMonth(), 1);
1668
1712
  var focusDate = null;
1669
- /* '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
1670
1714
  title. Before this, the only pointer route to another month was one click per month: from
1671
1715
  August 2026 to March 2022 is fifty-three of them. Shift+PageUp already jumped a year, but a
1672
- 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
1673
1717
  month" twenty-four times to reach a date two years back. */
1674
1718
  var mode = 'days';
1675
1719
  var focusMonth = null;
@@ -1868,7 +1912,7 @@
1868
1912
  var YEARS_PER_PAGE = 12;
1869
1913
  function yearPageStart(y) { return Math.floor(y / YEARS_PER_PAGE) * YEARS_PER_PAGE; }
1870
1914
 
1871
- /* 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
1872
1916
  monthDisabled applies one level down, for the same reason: a bound that sits inside the year
1873
1917
  disables neither end. */
1874
1918
  function yearDisabled(y) {
@@ -2113,7 +2157,7 @@
2113
2157
  var f = fromApi.getValue();
2114
2158
  var t = toApi.getValue();
2115
2159
  toApi.setMin(f || null); // end can't precede start (one-directional, per Foundation)
2116
- 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
2117
2161
  toApi.setRange(f, t);
2118
2162
  }
2119
2163
  fromEl.addEventListener('fdy-datepicker-change', function () {
@@ -2147,9 +2191,9 @@
2147
2191
  window.FreedayDatepicker = { init: initPicker, initAll: initAll };
2148
2192
  })();
2149
2193
 
2150
- /* Freeday datetime picker composer (optional, zero-dependency).
2194
+ /* Freeday, datetime picker composer (optional, zero-dependency).
2151
2195
  * Combines a child date picker and time picker into one value. It does not build
2152
- * anything itself freeday-datepicker.js and freeday-timepicker.js enhance the
2196
+ * anything itself, freeday-datepicker.js and freeday-timepicker.js enhance the
2153
2197
  * children; this only listens for their change events and emits a single combined
2154
2198
  * "fdy-datetime-change" (detail {date, time, value}). value is "YYYY-MM-DDTHH:MM"
2155
2199
  * when both parts are set, else "". Icon variants are controlled per child picker
@@ -2159,7 +2203,7 @@
2159
2203
  * <div data-fdy-datepicker data-value="2026-07-21"></div>
2160
2204
  * <div data-fdy-timepicker data-value="14:30"></div></div>
2161
2205
  * State: add data-fdy-disabled or data-fdy-invalid on the wrapper to reflect it onto both
2162
- * 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).
2163
2207
  */
2164
2208
  (function () {
2165
2209
  'use strict';
@@ -2174,7 +2218,7 @@
2174
2218
  if (!dp || !tp) return;
2175
2219
 
2176
2220
  // Reflect one wrapper-level state onto BOTH child triggers so a datetime reads as a single
2177
- // 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
2178
2222
  // enhancer registers its DOMContentLoaded init AFTER this composer, and microtasks drain
2179
2223
  // *between* listeners (so a microtask would run before the timepicker trigger exists). A
2180
2224
  // macrotask waits until the whole init pass is done and both triggers are built. (readonly is
@@ -2205,7 +2249,7 @@
2205
2249
  }));
2206
2250
  }
2207
2251
 
2208
- // 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
2209
2253
  // for the child enhancers to have run before this composer).
2210
2254
  wrap.addEventListener('fdy-datepicker-change', function (e) { date = e.detail.value; emit(); });
2211
2255
  wrap.addEventListener('fdy-time-select', function (e) { time = e.detail.value; emit(); });
@@ -2234,7 +2278,7 @@
2234
2278
  window.FreedayDatetime = { init: initDatetime, initAll: initAll };
2235
2279
  })();
2236
2280
 
2237
- /* Freeday drawer enhancer (optional, zero-dependency).
2281
+ /* Freeday, drawer enhancer (optional, zero-dependency).
2238
2282
  * A trigger [data-fdy-drawer="drawerId"] opens the <dialog class="fdy-drawer" id="drawerId">
2239
2283
  * as a modal side panel (native showModal gives focus-trap, Esc, and return-focus). Clicking
2240
2284
  * the backdrop or any [data-close] inside closes it.
@@ -2283,7 +2327,7 @@
2283
2327
  window.FreedayDrawer = { init: initTriggers, initAll: initTriggers };
2284
2328
  })();
2285
2329
 
2286
- /* Freeday form validation enhancer (optional, zero-dependency).
2330
+ /* Freeday, form validation enhancer (optional, zero-dependency).
2287
2331
  * Wires the native Constraint Validation API to accessible inline errors on any
2288
2332
  * [data-fdy-validate] <form>. Native-first: constraints come from the standard
2289
2333
  * markup attributes (required, type, pattern, min/max, minlength/maxlength, step);
@@ -2323,24 +2367,24 @@
2323
2367
  badInput: 'type',
2324
2368
  customError: 'mismatch'
2325
2369
  };
2326
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
2327
- * 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,
2328
2372
  * `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
2329
2373
  * a host in another language needs: it sets nine messages once instead of on every input.
2330
2374
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2331
2375
  var TEXT = {
2332
- required: 'Wajib diisi.',
2333
- type: 'Format tidak valid.',
2334
- pattern: 'Format tidak sesuai.',
2335
- minlength: 'Terlalu pendek.',
2336
- maxlength: 'Terlalu panjang.',
2337
- min: 'Nilai terlalu kecil.',
2338
- max: 'Nilai terlalu besar.',
2339
- step: 'Nilai tidak sesuai kelipatan.',
2340
- mismatch: 'Nilai tidak cocok.',
2341
- 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.'
2342
2386
  };
2343
- /* 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
2344
2388
  names, so `data-fdy-text-filter-text` and `data-fdy-text-filtertext` are different attributes
2345
2389
  and only one of them is what an author would write. */
2346
2390
  function textOf(root, key) {
@@ -2484,7 +2528,7 @@
2484
2528
  window.FreedayForm = { init: initForm, initAll: initAll };
2485
2529
  })();
2486
2530
 
2487
- /* Freeday input mask + password reveal enhancer (optional, zero-dependency).
2531
+ /* Freeday, input mask + password reveal enhancer (optional, zero-dependency).
2488
2532
  *
2489
2533
  * [data-fdy-mask]="pattern" formats a text input as you type. Placeholders:
2490
2534
  * # a digit (0-9) A a letter (a-z/A-Z) * a letter or digit
@@ -2537,16 +2581,16 @@
2537
2581
  }
2538
2582
 
2539
2583
 
2540
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
2541
- * 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
2542
2586
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
2543
2587
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
2544
2588
  var TEXT = {
2545
- show: 'Tampilkan kata sandi',
2546
- hide: 'Sembunyikan kata sandi'
2589
+ show: 'Show password',
2590
+ hide: 'Hide password'
2547
2591
  };
2548
2592
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
2549
- 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,
2550
2594
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
2551
2595
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
2552
2596
  still resolves, for markup written against 1.39.0. */
@@ -2628,7 +2672,7 @@
2628
2672
  window.FreedayMask = { initMask: initMask, initReveal: initReveal, initAll: initAll };
2629
2673
  })();
2630
2674
 
2631
- /* Freeday menu enhancer (optional, zero-dependency).
2675
+ /* Freeday, menu enhancer (optional, zero-dependency).
2632
2676
  * Menu-button pattern (WAI-ARIA APG): a trigger with aria-haspopup="menu" toggles a
2633
2677
  * role="menu" popup of role="menuitem" buttons. Also powers the split button
2634
2678
  * (main action + attached caret toggle). Auto-inits [data-fdy-menu].
@@ -2725,10 +2769,10 @@
2725
2769
  window.FreedayMenu = { init: initMenu, initAll: initAll };
2726
2770
  })();
2727
2771
 
2728
- /* Freeday number field enhancer (optional, zero-dependency).
2772
+ /* Freeday, number field enhancer (optional, zero-dependency).
2729
2773
  * Gives a native <input type="number"> its increment/decrement affordance back, on the kit's terms:
2730
2774
  * .fdy-input hides the user agent's own spin buttons (unthemeable OS widgets), so this puts two real
2731
- * 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,
2732
2776
  * :focus-within ring and error promotion.
2733
2777
  *
2734
2778
  * Markup contract:
@@ -2794,7 +2838,7 @@
2794
2838
  if (frozen()) return;
2795
2839
  if (Number(btn.getAttribute('data-fdy-number-step')) < 0) input.stepDown();
2796
2840
  else input.stepUp();
2797
- /* 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
2798
2842
  listen to these, not to a kit-specific event. */
2799
2843
  input.dispatchEvent(new Event('input', { bubbles: true }));
2800
2844
  input.dispatchEvent(new Event('change', { bubbles: true }));
@@ -2830,10 +2874,10 @@
2830
2874
  window.FreedayNumber = { init: initNumber, initAll: initAll };
2831
2875
  })();
2832
2876
 
2833
- /* Freeday popover positioning helper (optional, zero-dependency).
2877
+ /* Freeday, popover positioning helper (optional, zero-dependency).
2834
2878
  * Lifts a dropdown panel into the top layer via the native Popover API so it escapes ANY
2835
2879
  * ancestor overflow clip (a .fdy-card with overflow:hidden, an app-shell main with
2836
- * 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
2837
2881
  * there is no room below, and matching the trigger width. The panel stays a DOM child of its
2838
2882
  * component, so focus, outside-click (.closest), and ARIA relationships keep working unchanged.
2839
2883
  *
@@ -2853,7 +2897,7 @@
2853
2897
  && typeof HTMLElement.prototype.showPopover === 'function';
2854
2898
 
2855
2899
  // Position `panel` (already shown, so it has a measurable box) against `trigger`'s rect.
2856
- // 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
2857
2901
  // dropdowns (combo/cascade/date/time), skipped for menus that keep their own min-width.
2858
2902
  function place(panel, trigger, matchWidth) {
2859
2903
  var r = trigger.getBoundingClientRect();
@@ -2900,10 +2944,10 @@
2900
2944
  window.FreedayPopover = { attach: attach, place: place, supported: supported };
2901
2945
  })();
2902
2946
 
2903
- /* Freeday rating enhancer (optional, zero-dependency).
2947
+ /* Freeday, rating enhancer (optional, zero-dependency).
2904
2948
  * Fills stars up to the checked radio (and previews on hover) for an interactive
2905
2949
  * [data-fdy-rating] radio group; for [data-fdy-readonly] it fills from data-value.
2906
- * 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.
2907
2951
  */
2908
2952
  (function () {
2909
2953
  'use strict';
@@ -2954,11 +2998,11 @@
2954
2998
  window.FreedayRating = { init: initRating, initAll: initAll };
2955
2999
  })();
2956
3000
 
2957
- /* Freeday combobox enhancer (optional, zero-dependency).
3001
+ /* Freeday, combobox enhancer (optional, zero-dependency).
2958
3002
  * Implements the WAI-ARIA APG "select-only combobox" pattern for every
2959
3003
  * [data-fdy-combo] on the page. Progressive enhancement: the markup is the
2960
3004
  * component, this script wires behavior. In a Vue/React/Blazor app you would
2961
- * bind your own state instead this is the framework-agnostic reference.
3005
+ * bind your own state instead, this is the framework-agnostic reference.
2962
3006
  *
2963
3007
  * Expected markup:
2964
3008
  * <div class="fdy-combo" data-fdy-combo data-value="x">
@@ -2981,7 +3025,7 @@
2981
3025
  var seq = 0;
2982
3026
  var openClose = null; // close() of the currently-open combo, or null
2983
3027
 
2984
- /* 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. */
2985
3029
  function optionLabel(opt) {
2986
3030
  return (opt.textContent || '').trim();
2987
3031
  }
@@ -3047,7 +3091,7 @@
3047
3091
  if (openClose === close) openClose = null;
3048
3092
  }
3049
3093
  // Mark an option selected + reflect it in the button. `silent` skips the change
3050
- // 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
3051
3095
  // (e.g. a Blazor/framework wrapper) can push a value in without echoing an event
3052
3096
  // back to itself or stealing focus. User-driven picks go through choose() (loud).
3053
3097
  function select(opt, silent) {
@@ -3082,7 +3126,7 @@
3082
3126
  isOpen() ? close() : open();
3083
3127
  });
3084
3128
  // Pressing an option must not blur the button: a blur fires the focusout
3085
- // 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,
3086
3130
  // so the pick is lost (real mouse only; synthetic .click() never blurs).
3087
3131
  // preventDefault on mousedown keeps focus on the button without blocking click.
3088
3132
  listbox.addEventListener('mousedown', function (e) { e.preventDefault(); });
@@ -3169,10 +3213,10 @@
3169
3213
  window.FreedayCombo = { init: initCombo, initAll: initAll, setValue: setValue };
3170
3214
  })();
3171
3215
 
3172
- /* Freeday slider value binding (optional, zero-dependency).
3216
+ /* Freeday, slider value binding (optional, zero-dependency).
3173
3217
  * Mirrors a [data-fdy-slider] range input's value into <output for="inputId">, with an
3174
3218
  * optional prefix/suffix on the output (data-fdy-prefix / data-fdy-suffix). The range itself
3175
- * 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.
3176
3220
  */
3177
3221
  (function () {
3178
3222
  'use strict';
@@ -3207,9 +3251,9 @@
3207
3251
  window.FreedaySlider = { init: initSlider, initAll: initAll };
3208
3252
  })();
3209
3253
 
3210
- /* Freeday stepper/wizard enhancer (optional, zero-dependency).
3254
+ /* Freeday, stepper/wizard enhancer (optional, zero-dependency).
3211
3255
  * Drives a step header + panels: prev/next navigation, completed/active state, and
3212
- * 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
3213
3257
  * [data-fdy-stepper].
3214
3258
  *
3215
3259
  * Markup: <div data-fdy-stepper>
@@ -3228,16 +3272,16 @@
3228
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>';
3229
3273
 
3230
3274
 
3231
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
3232
- * 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
3233
3277
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3234
3278
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3235
3279
  var TEXT = {
3236
- done: 'Selesai',
3237
- next: 'Lanjut'
3280
+ done: 'Done',
3281
+ next: 'Next'
3238
3282
  };
3239
3283
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3240
- 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,
3241
3285
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3242
3286
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3243
3287
  still resolves, for markup written against 1.39.0. */
@@ -3327,7 +3371,7 @@
3327
3371
  window.FreedayStepper = { init: initStepper, initAll: initAll };
3328
3372
  })();
3329
3373
 
3330
- /* Freeday data-table enhancer (optional, zero-dependency).
3374
+ /* Freeday, data-table enhancer (optional, zero-dependency).
3331
3375
  * Adds client-side search, column sort, pagination and row selection to any
3332
3376
  * [data-fdy-table]. Progressive enhancement over a plain semantic <table>;
3333
3377
  * in a framework app, do this server- or store-side instead.
@@ -3363,26 +3407,26 @@
3363
3407
  }
3364
3408
 
3365
3409
 
3366
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
3367
- * 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
3368
3412
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
3369
3413
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
3370
3414
  var TEXT = {
3371
- prev: 'Sebelumnya',
3372
- next: 'Berikutnya',
3373
- selected: '{n} dipilih',
3374
- filter: 'Filter kolom',
3375
- filterText: 'Berisi teks',
3376
- filterTextPlaceholder: 'Berisi…',
3377
- filterEnum: 'Tampilkan nilai',
3378
- 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',
3379
3423
  reset: 'Reset',
3380
- close: 'Tutup',
3381
- rows: '{n} baris',
3382
- info: 'Menampilkan {from}–{to} dari {total}'
3424
+ close: 'Close',
3425
+ rows: '{n} rows',
3426
+ info: 'Showing {from}–{to} of {total}'
3383
3427
  };
3384
3428
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
3385
- 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,
3386
3430
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
3387
3431
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
3388
3432
  still resolves, for markup written against 1.39.0. */
@@ -3862,7 +3906,7 @@
3862
3906
  window.FreedayTable = { init: initTable, initAll: initAll };
3863
3907
  })();
3864
3908
 
3865
- /* Freeday tabs enhancer (optional, zero-dependency, WAI-ARIA APG tabs).
3909
+ /* Freeday, tabs enhancer (optional, zero-dependency, WAI-ARIA APG tabs).
3866
3910
  * Auto-inits every [data-fdy-tabs]. Roving tabindex + arrow/Home/End nav.
3867
3911
  *
3868
3912
  * Markup: a [data-fdy-tabs] with a [role="tablist"] of
@@ -3931,7 +3975,7 @@
3931
3975
  window.FreedayTabs = { init: initTabs, initAll: initAll };
3932
3976
  })();
3933
3977
 
3934
- /* Freeday time picker enhancer (optional, zero-dependency).
3978
+ /* Freeday, time picker enhancer (optional, zero-dependency).
3935
3979
  * Builds an input-styled trigger + scrollable time-list popover (WAI-ARIA listbox)
3936
3980
  * from an empty [data-fdy-timepicker] wrapper. 24h HH:MM. Options run from
3937
3981
  * data-min (default 00:00) to data-max (default 23:59) every data-step minutes
@@ -3961,7 +4005,7 @@
3961
4005
  wrap.dataset.fdyTpReady = '1';
3962
4006
  wrap.classList.add('fdy-timepicker');
3963
4007
 
3964
- var label = wrap.getAttribute('data-label') || 'Pilih waktu';
4008
+ var label = wrap.getAttribute('data-label') || 'Choose a time';
3965
4009
  var placeholder = wrap.getAttribute('data-placeholder') || '--:--';
3966
4010
  var step = Math.max(1, parseInt(wrap.getAttribute('data-step') || '30', 10));
3967
4011
  var minM = valid(wrap.getAttribute('data-min')) ? toMin(wrap.getAttribute('data-min')) : 0;
@@ -4115,12 +4159,12 @@
4115
4159
  window.FreedayTimepicker = { init: initTime, initAll: initAll };
4116
4160
  })();
4117
4161
 
4118
- /* Freeday toast API (optional, zero-dependency).
4162
+ /* Freeday, toast API (optional, zero-dependency).
4119
4163
  * Freeday.toast({ variant, title, message, timeout, key }) shows a transient
4120
4164
  * notification in a live region (created on first use). Returns the element.
4121
4165
  * variant: 'success' | 'warning' | 'danger' | 'info' (default: neutral)
4122
4166
  * timeout: ms before auto-dismiss (default 4000; 0 = sticky)
4123
- * 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
4124
4168
  * existing one in place instead of stacking a duplicate (e.g. a burst
4125
4169
  * of identical failures shows one toast, refreshed).
4126
4170
  * Freeday.dismiss(nodeOrKey) closes a toast early (pass the returned element or its key).
@@ -4156,14 +4200,14 @@
4156
4200
 
4157
4201
  /* Put the region in the TOP LAYER, above any open <dialog>.
4158
4202
  *
4159
- * 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
4160
4204
  * action taken inside a modal was painted behind that modal's backdrop, which is where an error
4161
4205
  * message is least useful. showPopover() joins the same layer, and membership there is ordered by
4162
4206
  * when you joined: hide-then-show re-joins at the top, so a toast shown while a dialog is open
4163
4207
  * lands above it rather than under the dialog that was opened after the last toast.
4164
4208
  *
4165
4209
  * Guarded twice on purpose. `showPopover` is absent before Chrome 114 / Safari 17 / Firefox 125,
4166
- * 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
4167
4211
  * cases the region stays an ordinary fixed block at z-index 200, which is what it has always
4168
4212
  * been. A notification must never be the thing that throws.
4169
4213
  */
@@ -4194,15 +4238,15 @@
4194
4238
  }
4195
4239
 
4196
4240
 
4197
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
4198
- * 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
4199
4243
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4200
4244
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4201
4245
  var TEXT = {
4202
- close: 'Tutup'
4246
+ close: 'Close'
4203
4247
  };
4204
4248
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
4205
- 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,
4206
4250
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4207
4251
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4208
4252
  still resolves, for markup written against 1.39.0. */
@@ -4277,10 +4321,10 @@
4277
4321
  window.Freeday.dismiss = dismiss; // dismiss(nodeOrKey)
4278
4322
  })();
4279
4323
 
4280
- /* Freeday tree checkbox cascade (optional, zero-dependency).
4324
+ /* Freeday, tree checkbox cascade (optional, zero-dependency).
4281
4325
  * Turns a [data-fdy-tree] checkbox tree into a selectable one: checking a branch checks
4282
4326
  * every descendant; a partially-selected branch shows the native :indeterminate state.
4283
- * 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.
4284
4328
  * Selection never toggles the <details> (the checkbox stops click propagation).
4285
4329
  *
4286
4330
  * Markup contract:
@@ -4304,7 +4348,7 @@
4304
4348
  return Array.prototype.filter.call(ul.children, function (n) { return n.tagName === 'LI'; });
4305
4349
  }
4306
4350
 
4307
- // 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.
4308
4352
  function ownBox(li) {
4309
4353
  return li.querySelector(':scope > details > summary input[type="checkbox"]')
4310
4354
  || li.querySelector(':scope > input[type="checkbox"]');
@@ -4375,16 +4419,16 @@
4375
4419
  window.FreedayTree = { init: initTree, initAll: initAll };
4376
4420
  })();
4377
4421
 
4378
- /* Freeday file-upload enhancer (optional, zero-dependency).
4422
+ /* Freeday, file-upload enhancer (optional, zero-dependency).
4379
4423
  * A drop target that also opens the file dialog on click / Enter / Space, validates each file
4380
4424
  * (accept + max size), and renders a per-file row with explicit state. Actual upload is the
4381
- * 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
4382
4426
  * upload call, or set data-fdy-upload-simulate for a demo progress animation.
4383
4427
  *
4384
4428
  * Markup contract:
4385
4429
  * <div class="fdy-dropzone" data-fdy-dropzone role="button" tabindex="0"
4386
4430
  * data-max-size="10485760" data-filelist="#dz-list" [data-fdy-upload-simulate]
4387
- * aria-label="Unggah berkas seret ke sini atau tekan Enter">
4431
+ * aria-label="Unggah berkas, seret ke sini atau tekan Enter">
4388
4432
  * <span class="fdy-dropzone__icon">…svg…</span>
4389
4433
  * <span class="fdy-dropzone__title">…</span>
4390
4434
  * <span class="fdy-dropzone__hint">…</span>
@@ -4392,15 +4436,15 @@
4392
4436
  * </div>
4393
4437
  * <div class="fdy-filelist" id="dz-list"></div>
4394
4438
  *
4395
- * 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
4396
4440
  * needs to listen on:
4397
4441
  * "fdy-upload-add" {file, rejected, reason, row}
4398
4442
  * "fdy-upload-remove" {file}
4399
4443
  * `row` is the state machine over the rendered .fdy-file:
4400
- * .ready() (rest where a dropped file starts) / .uploading() / .setProgress(pct) /
4444
+ * .ready() (rest, where a dropped file starts) / .uploading() / .setProgress(pct) /
4401
4445
  * .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
4402
4446
  * Note the file list is a SIBLING of the dropzone above, so nothing dispatched on a row would ever
4403
- * 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.
4404
4448
  */
4405
4449
  (function () {
4406
4450
  'use strict';
@@ -4428,21 +4472,21 @@
4428
4472
  }
4429
4473
 
4430
4474
 
4431
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
4432
- * 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
4433
4477
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
4434
4478
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
4435
4479
  var TEXT = {
4436
- remove: 'Hapus {name}',
4437
- progress: 'Progres unggah {name}',
4438
- uploading: 'Mengunggah…',
4439
- waiting: 'Menunggu server…',
4440
- done: 'Terunggah',
4441
- badType: 'Tipe berkas tidak didukung.',
4442
- 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.'
4443
4487
  };
4444
4488
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
4445
- 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,
4446
4490
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
4447
4491
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
4448
4492
  still resolves, for markup written against 1.39.0. */
@@ -4480,12 +4524,12 @@
4480
4524
  remove.setAttribute('aria-label', textOf(zone, 'remove', { name: file.name }));
4481
4525
  remove.innerHTML = '&times;';
4482
4526
  remove.addEventListener('click', function () {
4483
- /* 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
4484
4528
  on the dropzone gets both. The row lives in the file list, which the kit's own markup
4485
4529
  contract puts as a SIBLING of the dropzone, so a row event never bubbles through the zone:
4486
4530
  a consumer following the docs saw `add` arrive and `remove` never fire, with no error.
4487
- Firing on both would look safer, but it makes the pair asymmetric one `add` and, for
4488
- 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
4489
4533
  also fixes the listless case: a row that was never attached bubbles to nothing at all. */
4490
4534
  zone.dispatchEvent(new CustomEvent('fdy-upload-remove', { bubbles: true, detail: { file: file } }));
4491
4535
  el.remove();
@@ -4517,14 +4561,14 @@
4517
4561
  }
4518
4562
  /* Back to a measured bar. While the indeterminate modifier is on it owns the bar's width, so an
4519
4563
  explicit one has to be restored when it comes off: .fdy-progress__bar is a plain block div, and
4520
- 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. */
4521
4565
  function determinate() {
4522
4566
  progressEl.classList.remove('fdy-progress--indeterminate');
4523
4567
  if (!bar.style.width) bar.style.width = '0%';
4524
4568
  }
4525
4569
  return {
4526
4570
  el: el,
4527
- /* 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
4528
4572
  a transfer, and no progress bar is shown, because nothing is in flight. Without this the
4529
4573
  state machine had no start state: done() claims success, fail() claims an error, and
4530
4574
  uploading() is a lie until the consumer actually sends the file. */
@@ -4548,12 +4592,12 @@
4548
4592
  bar.style.width = v + '%';
4549
4593
  progressEl.setAttribute('aria-valuenow', String(Math.round(v)));
4550
4594
  },
4551
- /* 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.
4552
4596
  "Mengunggah…" turns false the moment the last byte leaves, and a determinate bar parked at
4553
4597
  100% is the most convincing "hung" signal a UI can produce, so this state reports no
4554
4598
  percentage: the bar goes indeterminate and the label belongs to the consumer, because only
4555
4599
  they know what the server is doing ("Membaca PDF…", "Memindai…"). done()/fail()/ready() need
4556
- 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. */
4557
4601
  waiting: function (label) {
4558
4602
  el.classList.remove('fdy-file--error', 'fdy-file--success');
4559
4603
  icon.innerHTML = FILE_ICON;
@@ -4561,7 +4605,7 @@
4561
4605
  ensureProgress();
4562
4606
  progressEl.classList.add('fdy-progress--indeterminate');
4563
4607
  /* The modifier styles .fdy-progress__bar, so it must sit on the CONTAINER, and the inline
4564
- 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.
4565
4609
  aria-valuenow goes with it: a progressbar with no value is precisely what ARIA calls
4566
4610
  indeterminate, which is the contract COMPONENTS.md already states for this component. */
4567
4611
  bar.style.width = '';
@@ -4632,7 +4676,7 @@
4632
4676
  if (input) input.addEventListener('change', function () { handleFiles(input.files); input.value = ''; });
4633
4677
 
4634
4678
  /* The list is optional: rendering a row and announcing the file are separate jobs. A consumer
4635
- 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
4636
4680
  `detail.row` still works (its element is just never attached). Gating the EVENT on the list
4637
4681
  meant "bring your own row" silently cost you the notification that a file had arrived. */
4638
4682
  function handleFiles(fileList) {