@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
@@ -8,19 +8,19 @@ import FdyCombo from './FdyCombo.vue';
8
8
  * The band under a table: what you are looking at, how much of it you see, where you are.
9
9
  *
10
10
  * `FdyTable` renders this itself, so most screens never import it. It is exported for the one shape
11
- * that cannot use the table's own a RESPONSIVE list, where a `.fdy-datatable` at `lg` and a
11
+ * that cannot use the table's own, a RESPONSIVE list, where a `.fdy-datatable` at `lg` and a
12
12
  * `.fdy-list` below it are two renderings of ONE page of rows. A footer inside the table is inside
13
13
  * the half that is hidden on a phone, so those screens need to render it once, outside both
14
14
  * (improvement notes #005 and #008, from IDU_EMATE_APPL_WEB).
15
15
  *
16
- * It owns nothing. `page` in, `update:page` out the same contract as the table's server mode, so a
16
+ * It owns nothing. `page` in, `update:page` out, the same contract as the table's server mode, so a
17
17
  * screen already wired for that switches by moving the prop.
18
18
  */
19
19
  const props = defineProps<{
20
20
  /** The page being shown. `size` drives the range AND the rows-per-page control's value. */
21
21
  page: FdyPageState;
22
22
  /**
23
- * Offer a rows-per-page control. Omit for none the footer is then range + pager, exactly as
23
+ * Offer a rows-per-page control. Omit for none; the footer is then range + pager, exactly as
24
24
  * before. Picking a size emits `update:page` carrying the new `size` and the index that still
25
25
  * holds the row you were looking at.
26
26
  */
@@ -29,7 +29,7 @@ const props = defineProps<{
29
29
 
30
30
  const emit = defineEmits<{ 'update:page': [page: FdyPageState] }>();
31
31
 
32
- /* The combo is labelled by the visible word beside it a `<label for>` cannot reach inside a
32
+ /* The combo is labelled by the visible word beside it, a `<label for>` cannot reach inside a
33
33
  * component, and an aria-label would leave that word unattached to anything. */
34
34
  const sizeLabelId: string = `fdy-rows-${useId()}`;
35
35
 
@@ -54,7 +54,7 @@ const sizes: ComputedRef<readonly number[]> = computed((): readonly number[] =>
54
54
  const sizeOptions: ComputedRef<Array<{ value: string; label: string }>> = computed(() =>
55
55
  sizes.value.map((size: number) => ({ value: String(size), label: String(size) })),
56
56
  );
57
- /* One page and no size control means there is nothing here to say the table has always withheld
57
+ /* One page and no size control means there is nothing here to say, the table has always withheld
58
58
  * the whole band in that case, and this is where that decision now lives. */
59
59
  const visible: ComputedRef<boolean> = computed((): boolean => hasPager.value || sizes.value.length > 0);
60
60
 
@@ -14,7 +14,7 @@ export declare function useFreeday(rootRef?: Ref<HTMLElement | null>): UseFreeda
14
14
 
15
15
  /**
16
16
  * Anchor a dropdown panel to its trigger in the top layer, so it escapes any ancestor overflow clip
17
- * `.fdy-card` is `overflow:hidden`, so a panel positioned inside one is otherwise cut at the card's
17
+ *, `.fdy-card` is `overflow:hidden`, so a panel positioned inside one is otherwise cut at the card's
18
18
  * edge. The panel element must carry `popover="manual"`. This is the plumbing every kit dropdown
19
19
  * uses; it is exported so a control the kit does not ship can behave like one that it does, instead
20
20
  * of each app re-implementing it from the description.
@@ -35,11 +35,12 @@ export type { CascadeNode } from './components/FdyCascade.vue';
35
35
  export { default as FdyCfl } from './components/FdyCfl.vue';
36
36
  export { default as FdyChart } from './components/FdyChart.vue';
37
37
  export { default as FdyTable } from './components/FdyTable.vue';
38
- /** The table's own footer, standalone for a responsive list whose table is hidden at some
38
+ /** The table's own footer, standalone, for a responsive list whose table is hidden at some
39
39
  * breakpoints and so cannot host it. */
40
40
  export { default as FdyTableFooter } from './components/FdyTableFooter.vue';
41
41
  export { default as FdyModal } from './components/FdyModal.vue';
42
42
  export { default as FdyDrawer } from './components/FdyDrawer.vue';
43
+ export { default as FdyAppShell } from './components/FdyAppShell.vue';
43
44
 
44
45
  /** Controlled data-table types (shared, framework-agnostic core). */
45
46
  export type {
@@ -11,3 +11,4 @@ export { default as FdyTable } from './components/FdyTable.vue';
11
11
  export { default as FdyTableFooter } from './components/FdyTableFooter.vue';
12
12
  export { default as FdyModal } from './components/FdyModal.vue';
13
13
  export { default as FdyDrawer } from './components/FdyDrawer.vue';
14
+ export { default as FdyAppShell } from './components/FdyAppShell.vue';
@@ -1,4 +1,4 @@
1
- /* Freeday Vue 3 adapter (thin). The enhancers stay the source of truth; this
1
+ /* Freeday. Vue 3 adapter (thin). The enhancers stay the source of truth; this
2
2
  * composable just re-runs every registered window.Freeday*.initAll() over a
3
3
  * subtree after Vue renders it, so [data-fdy-*] markup rendered by Vue gets
4
4
  * enhanced (and re-enhanced after updates). Enhancers are idempotent, so the
@@ -10,12 +10,12 @@
10
10
  * useFreeday(root); // scopes init to this component's subtree
11
11
  * // <div ref="root"> ...[data-fdy-*] markup... </div>
12
12
  *
13
- * The ref may also sit ON the widget itself <div ref="root" data-fdy-menu> which is what you
13
+ * The ref may also sit ON the widget itself, <div ref="root" data-fdy-menu>, which is what you
14
14
  * write when the component's root element IS the widget. initAll() matches its own root as
15
15
  * well as descendants, so both shapes work. (Before 1.23.0 only the wrapping shape did, and
16
16
  * the other failed silently: querySelectorAll never matches its root.)
17
17
  *
18
- * Events come straight through as native bubbling CustomEvents bind them with
18
+ * Events come straight through as native bubbling CustomEvents, bind them with
19
19
  * plain v-on (e.g. @fdy-cascade-change) and read event.detail. See index.d.ts
20
20
  * for the detail types.
21
21
  */
package/dist/asset.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- // Shared type stub for every export imported purely for its side effects the CSS
1
+ // Shared type stub for every export imported purely for its side effects: the CSS
2
2
  // bundles (`./css`, `./css/components`, `./tokens`) and the individual enhancer scripts
3
3
  // (`./enhancers/*`). TypeScript only needs the `types` condition to *resolve* the module;
4
- // there is no value surface to describe. (The root `.` import is different it augments
5
- // window.Freeday so it points at ./freeday.d.ts instead.)
4
+ // there is no value surface to describe. (The root `.` import is different, since it augments
5
+ // window.Freeday, so it points at ./freeday.d.ts instead.)
6
6
  export {};
@@ -1,20 +1,25 @@
1
- /* Freeday app shell enhancer (optional, zero-dependency).
1
+ /* Freeday, app shell enhancer (optional, zero-dependency).
2
2
  * Opt in with <div class="fdy-app" data-fdy-app>; the markup is otherwise unchanged.
3
3
  *
4
4
  * The shell has always shipped the state classes and no behaviour, which left every consumer to
5
- * assemble the toggle, Escape, focus trap, focus restore and `inert` themselves and the two
5
+ * assemble the toggle, Escape, focus trap, focus restore and `inert` themselves, and the two
6
6
  * hand-rolled copies in this repo's own docs already disagreed about which of those exist. Below
7
7
  * 721px the sidebar is an OVERLAY (app-shell.css: position:fixed + translateX(-100%)), so it needs
8
8
  * the same treatment a modal drawer gets; at or above 721px it is a column that merely collapses.
9
9
  *
10
10
  * One rule covers both a hidden nav and a hidden page:
11
11
  * - `__sidebar` is inert whenever the nav is NOT visible. Off-canvas and collapsed panels stay in
12
- * the tab order otherwise translateX(-100%) and width:0 hide a thing from the eye, not from
12
+ * the tab order otherwise, translateX(-100%) and width:0 hide a thing from the eye, not from
13
13
  * the keyboard, which is how a nav nobody can see still swallows Tab.
14
14
  * - `__content` is inert only while the nav is an OPEN OVERLAY, so Tab cannot wander behind the
15
15
  * backdrop.
16
16
  *
17
- * Emits nothing; the classes are the state. FreedayAppShell.init(root) for late-mounted markup.
17
+ * Emits a bubbling `fdy-app-nav` CustomEvent (detail {visible}) whenever the nav's visibility
18
+ * changes, and takes `FreedayAppShell.setVisible(root, visible)` from outside. Those two exist for
19
+ * the same reason the other enhancers have them: a host that keeps its own state, the Blazor
20
+ * wrapper binding @bind-NavOpen, an app persisting the collapsed preference, has to be able to
21
+ * hear the change and to drive it, without owning the behaviour twice.
22
+ * FreedayAppShell.init(root) for late-mounted markup.
18
23
  */
19
24
  (function () {
20
25
  'use strict';
@@ -26,7 +31,7 @@
26
31
  + ' textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
27
32
 
28
33
  /* getClientRects(), not offsetParent: an overlay sidebar is position:fixed, and a fixed element
29
- reports no offsetParent at all filtering on it would call every nav item invisible. */
34
+ reports no offsetParent at all, filtering on it would call every nav item invisible. */
30
35
  function focusables(root) {
31
36
  return Array.prototype.filter.call(root.querySelectorAll(FOCUSABLE), function (el) {
32
37
  return el.getClientRects().length > 0;
@@ -51,18 +56,25 @@
51
56
 
52
57
  var mqWide = window.matchMedia(WIDE);
53
58
  var restoreTo = null;
59
+ var lastVisible = null;
54
60
 
55
61
  function isOverlayOpen() { return app.classList.contains('fdy-app--nav-open'); }
56
62
  function isCollapsed() { return app.classList.contains('fdy-app--nav-collapsed'); }
57
63
  function navVisible() { return mqWide.matches ? !isCollapsed() : isOverlayOpen(); }
58
64
 
59
- /* aria-expanded answers "is the nav showing?" in BOTH modes the two state classes are the
65
+ /* aria-expanded answers "is the nav showing?" in BOTH modes, the two state classes are the
60
66
  kit's business, not the reader's. */
61
67
  function sync() {
62
68
  var visible = navVisible();
63
69
  toggle.setAttribute('aria-expanded', String(visible));
64
70
  setInert(sidebar, !visible);
65
71
  setInert(content, !mqWide.matches && visible);
72
+ /* Announce only real changes. The first sync() runs at init to describe the state the markup
73
+ arrived in, which is not something a host asked for and must not look like one. */
74
+ if (lastVisible !== null && visible !== lastVisible) {
75
+ app.dispatchEvent(new CustomEvent('fdy-app-nav', { bubbles: true, detail: { visible: visible } }));
76
+ }
77
+ lastVisible = visible;
66
78
  }
67
79
 
68
80
  function open() {
@@ -79,7 +91,7 @@
79
91
  }
80
92
  }
81
93
 
82
- /* restoreFocus is false when the viewport closed it rather than the user a resize must not
94
+ /* restoreFocus is false when the viewport closed it rather than the user, a resize must not
83
95
  yank focus out from under whatever the reader was doing. */
84
96
  function close(restoreFocus) {
85
97
  if (!isOverlayOpen()) return;
@@ -106,7 +118,7 @@
106
118
 
107
119
  if (backdrop) backdrop.addEventListener('click', function () { close(); });
108
120
 
109
- /* Following a link inside an overlay nav means "take me there" leaving the panel open over
121
+ /* Following a link inside an overlay nav means "take me there", leaving the panel open over
110
122
  the page you just asked for is the one thing every hand-rolled copy in this repo disagreed on. */
111
123
  sidebar.addEventListener('click', function (e) {
112
124
  if (!isOverlayOpen()) return;
@@ -147,6 +159,23 @@
147
159
  });
148
160
 
149
161
  sync();
162
+
163
+ /* The same handle the other enhancers expose (`_fdyCombo` and friends): a host that binds its
164
+ own state needs to drive this without reaching for the class names the kit reserves. */
165
+ app._fdyAppShell = {
166
+ isVisible: navVisible,
167
+ setVisible: function (visible) {
168
+ if (visible === navVisible()) return;
169
+ if (mqWide.matches) {
170
+ app.classList.toggle('fdy-app--nav-collapsed', !visible);
171
+ sync();
172
+ } else if (visible) {
173
+ open();
174
+ } else {
175
+ close();
176
+ }
177
+ },
178
+ };
150
179
  }
151
180
 
152
181
  function initShells(context) {
@@ -163,5 +192,16 @@
163
192
  initShells();
164
193
  }
165
194
 
166
- window.FreedayAppShell = { init: initShells, initAll: initShells };
195
+ window.FreedayAppShell = {
196
+ init: initShells,
197
+ initAll: initShells,
198
+ /* Both take the shell root. A missing or un-initialised root is a no-op rather than a throw:
199
+ a host may race the enhancer on first render, and a crash there is worse than a late sync. */
200
+ setVisible: function (root, visible) {
201
+ if (root && root._fdyAppShell) root._fdyAppShell.setVisible(visible === true);
202
+ },
203
+ isVisible: function (root) {
204
+ return !!(root && root._fdyAppShell && root._fdyAppShell.isVisible());
205
+ },
206
+ };
167
207
  })();
@@ -1,7 +1,7 @@
1
- /* Freeday autocomplete enhancer (optional, zero-dependency).
1
+ /* Freeday, autocomplete enhancer (optional, zero-dependency).
2
2
  * Editable combobox (WAI-ARIA APG): a text input filters a role="listbox" of role="option"
3
3
  * items as you type; select fills the input. Auto-inits [data-fdy-autocomplete]. Options are
4
- * static markup filtered client-side in an app, fetch/filter server-side and re-render options.
4
+ * static markup filtered client-side, in an app, fetch/filter server-side and re-render options.
5
5
  *
6
6
  * Markup: <div data-fdy-autocomplete class="fdy-autocomplete">
7
7
  * <input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
@@ -1,4 +1,4 @@
1
- /* Freeday breakpoint provider (optional, zero-dependency).
1
+ /* Freeday, breakpoint provider (optional, zero-dependency).
2
2
  * The MudBreakpointProvider equivalent for JS consumers: stamps the current breakpoint on
3
3
  * <html data-breakpoint="xs|sm|md|lg|xl"> and emits a bubbling "fdy-breakpoint-change"
4
4
  * CustomEvent (detail {breakpoint}) whenever it changes. Also fills any element with
@@ -1,4 +1,4 @@
1
- /* Freeday carousel enhancer (optional, zero-dependency).
1
+ /* Freeday, carousel enhancer (optional, zero-dependency).
2
2
  * Scroll-snap slider with prev/next arrows, generated dot indicators, keyboard (←/→),
3
3
  * and optional autoplay (data-fdy-autoplay="4000", pauses on hover/focus). Auto-inits
4
4
  * [data-fdy-carousel].
@@ -18,16 +18,16 @@
18
18
  'use strict';
19
19
 
20
20
 
21
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
22
- * path and every one overridable per element, so a host that speaks another language (the
21
+ /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
22
+ * path, and every one overridable per element, so a host that speaks another language (the
23
23
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
24
24
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
25
25
  var TEXT = {
26
- position: '{n} dari {total}',
26
+ position: '{n} of {total}',
27
27
  slide: 'Slide {n}'
28
28
  };
29
29
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
30
- as `data-fdy-text-filtertext` while the kebab form anybody would reach for,
30
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
31
31
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
32
32
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
33
33
  still resolves, for markup written against 1.39.0. */
@@ -1,4 +1,4 @@
1
- /* Freeday cascade select enhancer (optional, zero-dependency).
1
+ /* Freeday, cascade select enhancer (optional, zero-dependency).
2
2
  * Turns a [data-fdy-cascade] wrapper into a hierarchical drill-down picker. The
3
3
  * data model is a hidden nested <ul> inside the wrapper: a <li> with a child <ul>
4
4
  * is a branch (drills in), a <li> without is a leaf (selects). Each <li> carries
@@ -54,16 +54,16 @@
54
54
  }
55
55
 
56
56
 
57
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
58
- * path and every one overridable per element, so a host that speaks another language (the
57
+ /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
58
+ * path, and every one overridable per element, so a host that speaks another language (the
59
59
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
60
60
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
61
61
  var TEXT = {
62
- back: 'Kembali satu tingkat',
62
+ back: 'Back one level',
63
63
  submenu: '{label}, submenu'
64
64
  };
65
65
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
66
- as `data-fdy-text-filtertext` while the kebab form anybody would reach for,
66
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
67
67
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
68
68
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
69
69
  still resolves, for markup written against 1.39.0. */
@@ -88,8 +88,8 @@
88
88
  var root = sourceUl ? parse(sourceUl) : [];
89
89
  if (sourceUl) sourceUl.remove();
90
90
 
91
- var label = wrap.getAttribute('data-label') || 'Pilih';
92
- var placeholder = wrap.getAttribute('data-placeholder') || 'Pilih…';
91
+ var label = wrap.getAttribute('data-label') || 'Select';
92
+ var placeholder = wrap.getAttribute('data-placeholder') || 'Select…';
93
93
  var sep = wrap.getAttribute('data-separator') || ' / ';
94
94
 
95
95
  var trigger = document.createElement('button');
@@ -1,21 +1,21 @@
1
- /* Freeday choose-from-list enhancer (optional, zero-dependency).
1
+ /* Freeday, choose-from-list enhancer (optional, zero-dependency).
2
2
  * Wires a searchable master-data picker dialog to read-only fields.
3
3
  * Progressive enhancement over a native <dialog>; in a framework app, drive the
4
4
  * dialog as a controlled component (fetchPage callback + server cache), not a store.
5
5
  *
6
6
  * Markup contract (see docs):
7
- * - Field group: [data-fdy-cfl="<dialogId>"] usually a `.fdy-input-group` holding a
7
+ * - Field group: [data-fdy-cfl="<dialogId>"], usually a `.fdy-input-group` holding a
8
8
  * read-only `.fdy-input` and a trigger `<button class="fdy-input-group__btn">`.
9
9
  * Optional `data-fdy-cfl-display="<key>"` picks which row dataset key fills the input
10
10
  * (default "value"); optional `data-fdy-cfl-summary="{n} dipilih"` formats the multi label.
11
- * - Standalone opener: [data-fdy-cfl-open="<dialogId>"] opens the dialog with no bound field.
11
+ * - Standalone opener: [data-fdy-cfl-open="<dialogId>"], opens the dialog with no bound field.
12
12
  * - Dialog: <dialog id="<dialogId>" class="fdy-modal fdy-modal--cfl"> containing
13
13
  * a `[data-fdy-cfl-search]` input, `.fdy-cfl__row` <tr> rows carrying `data-*` fields,
14
14
  * a `[data-fdy-cfl-empty]` element, and (multi only) `[data-fdy-cfl-multiple]` on the
15
15
  * dialog plus `[data-fdy-cfl-count]` and `[data-fdy-cfl-confirm]` in the footer.
16
16
  *
17
17
  * Emits a bubbling "fdy-cfl-select" CustomEvent on the field group (or the dialog when
18
- * opened standalone): detail {row} for single-select, {rows} for multi-select each a plain
18
+ * opened standalone): detail {row} for single-select, {rows} for multi-select, each a plain
19
19
  * object copy of the row's dataset. Consumers fill sibling fields (code → name) from it.
20
20
  */
21
21
  (function () {
@@ -34,15 +34,15 @@
34
34
  }
35
35
 
36
36
 
37
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
38
- * path and every one overridable per element, so a host that speaks another language (the
37
+ /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
38
+ * path, and every one overridable per element, so a host that speaks another language (the
39
39
  * Blazor adapters, an English app on the raw path) supplies its own without forking this file.
40
40
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
41
41
  var TEXT = {
42
- selected: '{n} dipilih'
42
+ selected: '{n} selected'
43
43
  };
44
44
  /* HTML lowercases attribute names, so a camelCase key like `filterText` can only ever be written
45
- as `data-fdy-text-filtertext` while the kebab form anybody would reach for,
45
+ as `data-fdy-text-filtertext`, while the kebab form anybody would reach for,
46
46
  `data-fdy-text-filter-text`, becomes a DIFFERENT attribute the enhancer never reads, and the
47
47
  override fails silently. So the key is kebab-cased for the lookup; the run-together spelling
48
48
  still resolves, for markup written against 1.39.0. */
@@ -103,7 +103,7 @@
103
103
  var input = activeGroup.querySelector('.fdy-input');
104
104
  if (input) {
105
105
  if (detail.rows) {
106
- var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} dipilih';
106
+ var tpl = activeGroup.getAttribute('data-fdy-cfl-summary') || '{n} selected';
107
107
  input.value = detail.rows.length ? tpl.replace('{n}', String(detail.rows.length)) : '';
108
108
  } else {
109
109
  var key = activeGroup.getAttribute('data-fdy-cfl-display') || 'value';
@@ -1,4 +1,4 @@
1
- /* Freeday chart renderer (optional, zero-dependency, no external chart lib).
1
+ /* Freeday, chart renderer (optional, zero-dependency, no external chart lib).
2
2
  * Renders sparkline / bar / line / area / donut into an element from data attributes.
3
3
  * Auto-inits [data-fdy-chart] once; call FreedayChart.update(el) to re-render after the
4
4
  * data attributes change (used by the freeday/vue <FdyChart> wrapper for reactive data).
@@ -6,14 +6,14 @@
6
6
  * Single-series colour (sparkline / simple bar / single line) via data-fdy-color references a
7
7
  * semantic token by name (primary, accent, success, warning, danger, info). Multi-series
8
8
  * defaults draw from the validated categorical --chart-1..8 palette in fixed order (never
9
- * cycled, 8-series cap); pass data-fdy-colors to override a name is a semantic token
9
+ * cycled, 8-series cap); pass data-fdy-colors to override, a name is a semantic token
10
10
  * (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
11
11
  * validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
12
12
  * Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
13
13
  * --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
14
14
  * viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
15
15
  * ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
16
- * Charts are an enhancement put a table/text fallback inside the element and it will be
16
+ * Charts are an enhancement, put a table/text fallback inside the element and it will be
17
17
  * replaced on render; give the element role="img" + aria-label.
18
18
  *
19
19
  * Sparkline: <span data-fdy-chart="sparkline" data-values="4,6,5,8,7,10" data-fdy-color="primary">
@@ -50,7 +50,7 @@
50
50
  return v ? v.split(',').map(function (s) { return s.trim(); }) : [];
51
51
  }
52
52
  // role="img" is Children Presentational per ARIA, so the docs used to say a chart's rendered
53
- // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree
53
+ // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree,
54
54
  // measured in Chrome's AX tree, every one of those nodes is live and unignored; what saves the
55
55
  // advice is only that AT treats a NAMED role="img" as a leaf and does not descend. So the kit
56
56
  // hides them itself, and the author's aria-label really is the whole text alternative.
@@ -225,7 +225,7 @@
225
225
  // Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
226
226
  // pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
227
227
  // every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
228
- // size by plotWidth/320 instead a 9px axis label rendered ~39px on a 1728px viewport.)
228
+ // size by plotWidth/320 instead, a 9px axis label rendered ~39px on a 1728px viewport.)
229
229
  var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
230
230
  var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
231
231
  el.classList.add('fdy-chart-xy');
@@ -246,7 +246,7 @@
246
246
  el.appendChild(plot);
247
247
 
248
248
  // Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
249
- // so the box is already final. A detached or display:none chart measures 0 fall back to the
249
+ // so the box is already final. A detached or display:none chart measures 0, fall back to the
250
250
  // legacy box and let the ResizeObserver repaint it once it is actually laid out.
251
251
  var box = plot.getBoundingClientRect();
252
252
  var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
@@ -290,7 +290,7 @@
290
290
 
291
291
  // x category labels with autoskip. Labels are author-supplied, so estimate the widest one instead
292
292
  // of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
293
- // would collide with the one before it including the forced last one (a fraction-of-a-slot
293
+ // would collide with the one before it, including the forced last one (a fraction-of-a-slot
294
294
  // overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
295
295
  var widest = 0;
296
296
  for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
@@ -301,7 +301,7 @@
301
301
  for (var xi = 0; xi < n; xi++) {
302
302
  if (xi % step === 0 || xi === n - 1) kept.push(xi);
303
303
  }
304
- // The last label is worth forcing, but not on top of its neighbour drop the penultimate one.
304
+ // The last label is worth forcing, but not on top of its neighbour, drop the penultimate one.
305
305
  if (kept.length >= 2 && xPos(kept[kept.length - 1]) - xPos(kept[kept.length - 2]) < minGap) {
306
306
  kept.splice(kept.length - 2, 1);
307
307
  }
@@ -434,17 +434,17 @@
434
434
  var li = document.createElement('li');
435
435
  var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorAt(i);
436
436
  li.appendChild(sw);
437
- li.appendChild(document.createTextNode((labels[i] || ('#' + (i + 1))) + ' ' + Math.round((v / total) * 100) + '%'));
437
+ li.appendChild(document.createTextNode((labels[i] || ('#' + (i + 1))) + ' · ' + Math.round((v / total) * 100) + '%'));
438
438
  legend.appendChild(li);
439
439
  });
440
440
  el.appendChild(ring);
441
- // Honour data-fdy-legend for the donut too a caller may supply its own richer legend. 'auto'
441
+ // Honour data-fdy-legend for the donut too, a caller may supply its own richer legend. 'auto'
442
442
  // keeps today's behaviour (a donut is unreadable without labels, so it shows); 'none' suppresses it.
443
443
  if ((el.getAttribute('data-fdy-legend') || 'auto') !== 'none') el.appendChild(legend);
444
444
  ensureImg(el);
445
445
  }
446
446
 
447
- // Full (re-)render from current data attributes. Idempotent safe to call repeatedly.
447
+ // Full (re-)render from current data attributes. Idempotent, safe to call repeatedly.
448
448
  function renderChart(el) {
449
449
  var type = el.getAttribute('data-fdy-chart');
450
450
  if (type === 'sparkline') renderSparkline(el);
@@ -1,9 +1,9 @@
1
- /* Freeday chip enhancer (optional, zero-dependency).
1
+ /* Freeday, chip enhancer (optional, zero-dependency).
2
2
  * Two behaviours, both progressive enhancement:
3
3
  *
4
- * 1. Removable chips any .fdy-chip__remove button removes its chip and emits a
4
+ * 1. Removable chips, any .fdy-chip__remove button removes its chip and emits a
5
5
  * bubbling "fdy-chip-remove" (detail {value}).
6
- * 2. Choice / filter groups a [data-fdy-chips] container of interactive chips
6
+ * 2. Choice / filter groups, a [data-fdy-chips] container of interactive chips
7
7
  * (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
8
8
  * on click. Add data-single for one-at-a-time (radio-like) selection; filter
9
9
  * chips get a leading check when pressed. Emits "fdy-chip-change" on the group
@@ -1,6 +1,6 @@
1
- /* Freeday date picker enhancer (optional, zero-dependency).
1
+ /* Freeday, date picker enhancer (optional, zero-dependency).
2
2
  * Builds an input-styled trigger + calendar popover from an empty wrapper.
3
- * Locale comes from <html lang> (via Intl) month/weekday/value formatting is not hardcoded.
3
+ * Locale comes from <html lang> (via Intl), month/weekday/value formatting is not hardcoded.
4
4
  *
5
5
  * Markup contract:
6
6
  * - Single: <div data-fdy-datepicker data-value="2026-07-21" data-label="Tanggal unggah"
@@ -19,7 +19,11 @@
19
19
  (function () {
20
20
  'use strict';
21
21
 
22
- var LOCALE = document.documentElement.getAttribute('lang') || 'id';
22
+ /* The page's own `lang` wins, which is the better escape hatch than any attribute the kit
23
+ could invent: an Indonesian app writes <html lang="id"> and gets Indonesian month and
24
+ weekday names back automatically. The FALLBACK follows the kit's default language, or a
25
+ page without `lang` would read English labels around Indonesian month names. */
26
+ var LOCALE = document.documentElement.getAttribute('lang') || 'en';
23
27
  var uidSeq = 0;
24
28
  function uid(p) { uidSeq += 1; return p + '-' + uidSeq; }
25
29
  function pad(n) { return n < 10 ? '0' + n : '' + n; }
@@ -66,10 +70,10 @@
66
70
  var view = startOfDay(selected || new Date());
67
71
  view = new Date(view.getFullYear(), view.getMonth(), 1);
68
72
  var focusDate = null;
69
- /* 'days' | 'months' the calendar drills one level up rather than growing furniture beside the
73
+ /* 'days' | 'months', the calendar drills one level up rather than growing furniture beside the
70
74
  title. Before this, the only pointer route to another month was one click per month: from
71
75
  August 2026 to March 2022 is fifty-three of them. Shift+PageUp already jumped a year, but a
72
- shortcut nobody can see is not an affordance the repo's own test helper clicked "previous
76
+ shortcut nobody can see is not an affordance, the repo's own test helper clicked "previous
73
77
  month" twenty-four times to reach a date two years back. */
74
78
  var mode = 'days';
75
79
  var focusMonth = null;
@@ -268,7 +272,7 @@
268
272
  var YEARS_PER_PAGE = 12;
269
273
  function yearPageStart(y) { return Math.floor(y / YEARS_PER_PAGE) * YEARS_PER_PAGE; }
270
274
 
271
- /* A year is only unreachable when EVERY month in it falls outside min/max the same rule
275
+ /* A year is only unreachable when EVERY month in it falls outside min/max, the same rule
272
276
  monthDisabled applies one level down, for the same reason: a bound that sits inside the year
273
277
  disables neither end. */
274
278
  function yearDisabled(y) {
@@ -513,7 +517,7 @@
513
517
  var f = fromApi.getValue();
514
518
  var t = toApi.getValue();
515
519
  toApi.setMin(f || null); // end can't precede start (one-directional, per Foundation)
516
- fromApi.setRange(f, t); // start stays free picking a later start clears the end below
520
+ fromApi.setRange(f, t); // start stays free, picking a later start clears the end below
517
521
  toApi.setRange(f, t);
518
522
  }
519
523
  fromEl.addEventListener('fdy-datepicker-change', function () {
@@ -1,6 +1,6 @@
1
- /* Freeday datetime picker composer (optional, zero-dependency).
1
+ /* Freeday, datetime picker composer (optional, zero-dependency).
2
2
  * Combines a child date picker and time picker into one value. It does not build
3
- * anything itself freeday-datepicker.js and freeday-timepicker.js enhance the
3
+ * anything itself, freeday-datepicker.js and freeday-timepicker.js enhance the
4
4
  * children; this only listens for their change events and emits a single combined
5
5
  * "fdy-datetime-change" (detail {date, time, value}). value is "YYYY-MM-DDTHH:MM"
6
6
  * when both parts are set, else "". Icon variants are controlled per child picker
@@ -10,7 +10,7 @@
10
10
  * <div data-fdy-datepicker data-value="2026-07-21"></div>
11
11
  * <div data-fdy-timepicker data-value="14:30"></div></div>
12
12
  * State: add data-fdy-disabled or data-fdy-invalid on the wrapper to reflect it onto both
13
- * child triggers as one control (read-only is adapter-only a vanilla datetime is interactive).
13
+ * child triggers as one control (read-only is adapter-only, a vanilla datetime is interactive).
14
14
  */
15
15
  (function () {
16
16
  'use strict';
@@ -25,7 +25,7 @@
25
25
  if (!dp || !tp) return;
26
26
 
27
27
  // Reflect one wrapper-level state onto BOTH child triggers so a datetime reads as a single
28
- // disabled/invalid control. Deferred with setTimeout(0) NOT a microtask: the timepicker
28
+ // disabled/invalid control. Deferred with setTimeout(0). NOT a microtask: the timepicker
29
29
  // enhancer registers its DOMContentLoaded init AFTER this composer, and microtasks drain
30
30
  // *between* listeners (so a microtask would run before the timepicker trigger exists). A
31
31
  // macrotask waits until the whole init pass is done and both triggers are built. (readonly is
@@ -56,7 +56,7 @@
56
56
  }));
57
57
  }
58
58
 
59
- // Listen for the children's bubbling change events (order-independent no need
59
+ // Listen for the children's bubbling change events (order-independent, no need
60
60
  // for the child enhancers to have run before this composer).
61
61
  wrap.addEventListener('fdy-datepicker-change', function (e) { date = e.detail.value; emit(); });
62
62
  wrap.addEventListener('fdy-time-select', function (e) { time = e.detail.value; emit(); });
@@ -1,4 +1,4 @@
1
- /* Freeday drawer enhancer (optional, zero-dependency).
1
+ /* Freeday, drawer enhancer (optional, zero-dependency).
2
2
  * A trigger [data-fdy-drawer="drawerId"] opens the <dialog class="fdy-drawer" id="drawerId">
3
3
  * as a modal side panel (native showModal gives focus-trap, Esc, and return-focus). Clicking
4
4
  * the backdrop or any [data-close] inside closes it.
@@ -1,4 +1,4 @@
1
- /* Freeday form validation enhancer (optional, zero-dependency).
1
+ /* Freeday, form validation enhancer (optional, zero-dependency).
2
2
  * Wires the native Constraint Validation API to accessible inline errors on any
3
3
  * [data-fdy-validate] <form>. Native-first: constraints come from the standard
4
4
  * markup attributes (required, type, pattern, min/max, minlength/maxlength, step);
@@ -38,24 +38,24 @@
38
38
  badInput: 'type',
39
39
  customError: 'mismatch'
40
40
  };
41
- /* User-facing strings. Indonesian by default documented and deliberate for the raw enhancer
42
- * path and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on the field,
41
+ /* User-facing strings. Indonesian by default, documented and deliberate for the raw enhancer
42
+ * path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on the field,
43
43
  * `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form level is what
44
44
  * a host in another language needs: it sets nine messages once instead of on every input.
45
45
  * Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
46
46
  var TEXT = {
47
- required: 'Wajib diisi.',
48
- type: 'Format tidak valid.',
49
- pattern: 'Format tidak sesuai.',
50
- minlength: 'Terlalu pendek.',
51
- maxlength: 'Terlalu panjang.',
52
- min: 'Nilai terlalu kecil.',
53
- max: 'Nilai terlalu besar.',
54
- step: 'Nilai tidak sesuai kelipatan.',
55
- mismatch: 'Nilai tidak cocok.',
56
- invalid: 'Tidak valid.'
47
+ required: 'Required.',
48
+ type: 'Invalid format.',
49
+ pattern: 'Doesn’t match the required format.',
50
+ minlength: 'Too short.',
51
+ maxlength: 'Too long.',
52
+ min: 'Too small.',
53
+ max: 'Too large.',
54
+ step: 'Not a valid step.',
55
+ mismatch: 'Values don’t match.',
56
+ invalid: 'Invalid.'
57
57
  };
58
- /* Kebab-cased for the lookup see the note in the other enhancers: HTML lowercases attribute
58
+ /* Kebab-cased for the lookup, see the note in the other enhancers: HTML lowercases attribute
59
59
  names, so `data-fdy-text-filter-text` and `data-fdy-text-filtertext` are different attributes
60
60
  and only one of them is what an author would write. */
61
61
  function textOf(root, key) {