@cahyo-dimas/freeday 1.54.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +713 -545
  2. package/COMPONENTS.md +417 -139
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +34 -34
  6. package/adapters/blazor/FdyAppShell.razor +2 -2
  7. package/adapters/blazor/FdyAppShell.razor.cs +6 -6
  8. package/adapters/blazor/FdyCfl.razor.cs +1 -1
  9. package/adapters/blazor/FdyChart.razor +1 -1
  10. package/adapters/blazor/FdyChart.razor.cs +3 -3
  11. package/adapters/blazor/FdyChartSeries.cs +1 -1
  12. package/adapters/blazor/FdyCombo.razor.cs +2 -2
  13. package/adapters/blazor/FdyModal.razor +1 -1
  14. package/adapters/blazor/FdyTable.razor.cs +7 -7
  15. package/adapters/blazor/FdyTableFilter.razor +1 -1
  16. package/adapters/blazor/FdyTableFooter.razor +1 -1
  17. package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
  18. package/adapters/blazor/Freeday.Blazor.csproj +1 -1
  19. package/adapters/blazor/FreedayComponentBase.cs +1 -1
  20. package/adapters/blazor/TableModel.cs +3 -3
  21. package/adapters/blazor/TableTypes.cs +3 -3
  22. package/adapters/blazor/freeday-blazor.js +5 -5
  23. package/adapters/core/app-shell.d.ts +1 -1
  24. package/adapters/core/app-shell.js +6 -6
  25. package/adapters/core/cfl-value.d.ts +11 -0
  26. package/adapters/core/cfl-value.js +31 -0
  27. package/adapters/core/table-model.d.ts +2 -2
  28. package/adapters/core/table-model.js +8 -8
  29. package/adapters/react/components/FdyAppShell.tsx +5 -5
  30. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  31. package/adapters/react/components/FdyCascade.tsx +1 -1
  32. package/adapters/react/components/FdyCfl.tsx +11 -11
  33. package/adapters/react/components/FdyDateRange.tsx +2 -2
  34. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  35. package/adapters/react/components/FdyDrawer.tsx +1 -1
  36. package/adapters/react/components/FdyModal.tsx +2 -2
  37. package/adapters/react/components/FdyTable.tsx +7 -7
  38. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  39. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  40. package/adapters/react/index.d.ts +4 -1
  41. package/adapters/react/index.js +1 -0
  42. package/adapters/react/useFreeday.js +3 -3
  43. package/adapters/vue/components/FdyAppShell.vue +5 -5
  44. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  45. package/adapters/vue/components/FdyCascade.vue +1 -1
  46. package/adapters/vue/components/FdyCfl.vue +8 -8
  47. package/adapters/vue/components/FdyChart.vue +2 -2
  48. package/adapters/vue/components/FdyCombo.vue +1 -1
  49. package/adapters/vue/components/FdyDateRange.vue +2 -2
  50. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  51. package/adapters/vue/components/FdyDrawer.vue +3 -3
  52. package/adapters/vue/components/FdyModal.vue +3 -3
  53. package/adapters/vue/components/FdyTable.vue +7 -7
  54. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  55. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  56. package/adapters/vue/index.d.ts +5 -2
  57. package/adapters/vue/index.js +1 -0
  58. package/adapters/vue/useFreeday.js +3 -3
  59. package/dist/asset.d.ts +3 -3
  60. package/dist/freeday-app-shell.js +9 -9
  61. package/dist/freeday-autocomplete.js +2 -2
  62. package/dist/freeday-breakpoint.js +1 -1
  63. package/dist/freeday-carousel.js +8 -7
  64. package/dist/freeday-cascade.js +10 -9
  65. package/dist/freeday-cfl.js +12 -11
  66. package/dist/freeday-chart.js +11 -11
  67. package/dist/freeday-chip.js +3 -3
  68. package/dist/freeday-datepicker.js +11 -7
  69. package/dist/freeday-datetime.js +5 -5
  70. package/dist/freeday-drawer.js +1 -1
  71. package/dist/freeday-form.js +17 -16
  72. package/dist/freeday-mask.js +9 -8
  73. package/dist/freeday-menu.js +1 -1
  74. package/dist/freeday-number.js +3 -3
  75. package/dist/freeday-popover.js +3 -3
  76. package/dist/freeday-rating.js +2 -2
  77. package/dist/freeday-select.js +5 -5
  78. package/dist/freeday-slider.js +2 -2
  79. package/dist/freeday-stepper.js +10 -9
  80. package/dist/freeday-table.js +18 -17
  81. package/dist/freeday-tabs.js +1 -1
  82. package/dist/freeday-timepicker.js +2 -2
  83. package/dist/freeday-toast.js +11 -10
  84. package/dist/freeday-tree.js +3 -3
  85. package/dist/freeday-upload.js +28 -27
  86. package/dist/freeday.bundle.css +176 -176
  87. package/dist/freeday.css +170 -170
  88. package/dist/freeday.d.ts +2 -2
  89. package/dist/freeday.js +189 -176
  90. package/dist/freeday.tokens.css +5 -5
  91. package/docs/agent-onboarding.md +49 -46
  92. package/docs/getting-started.md +49 -40
  93. package/docs/integrations.md +18 -18
  94. package/docs/reference-screen.html +14 -14
  95. package/package.json +4 -3
  96. package/src/base.css +6 -6
  97. package/src/components/accordion.css +4 -4
  98. package/src/components/alert.css +1 -1
  99. package/src/components/app-shell.css +10 -10
  100. package/src/components/appbar.css +3 -3
  101. package/src/components/autocomplete.css +1 -1
  102. package/src/components/avatar.css +3 -3
  103. package/src/components/badge.css +5 -5
  104. package/src/components/breadcrumb.css +1 -1
  105. package/src/components/breakpoints.css +4 -4
  106. package/src/components/button.css +13 -13
  107. package/src/components/card.css +8 -8
  108. package/src/components/carousel.css +2 -2
  109. package/src/components/cascade.css +2 -2
  110. package/src/components/cfl.css +4 -4
  111. package/src/components/chart.css +3 -3
  112. package/src/components/chip.css +2 -2
  113. package/src/components/combo.css +3 -3
  114. package/src/components/composition.css +7 -7
  115. package/src/components/datepicker.css +6 -6
  116. package/src/components/datetimepicker.css +1 -1
  117. package/src/components/description-list.css +1 -1
  118. package/src/components/divider.css +1 -1
  119. package/src/components/drawer.css +1 -1
  120. package/src/components/file-upload.css +5 -5
  121. package/src/components/filterbar.css +7 -7
  122. package/src/components/form-grid.css +2 -2
  123. package/src/components/input-group.css +6 -6
  124. package/src/components/input.css +6 -6
  125. package/src/components/kbd.css +1 -1
  126. package/src/components/list.css +6 -6
  127. package/src/components/menu.css +3 -3
  128. package/src/components/modal.css +1 -1
  129. package/src/components/pagination.css +1 -1
  130. package/src/components/progress.css +1 -1
  131. package/src/components/rating.css +2 -2
  132. package/src/components/selection.css +4 -4
  133. package/src/components/skeleton.css +1 -1
  134. package/src/components/slider.css +1 -1
  135. package/src/components/spinner.css +1 -1
  136. package/src/components/states.css +1 -1
  137. package/src/components/stepper.css +2 -2
  138. package/src/components/table.css +15 -15
  139. package/src/components/tabs.css +3 -3
  140. package/src/components/timeline.css +1 -1
  141. package/src/components/timepicker.css +1 -1
  142. package/src/components/toast.css +3 -3
  143. package/src/components/tooltip.css +1 -1
  144. package/src/components/tree.css +2 -2
  145. package/tokens/breakpoints.d.ts +2 -2
  146. package/tokens/breakpoints.mjs +3 -3
  147. package/tokens/tokens.json +1 -1
@@ -1,4 +1,4 @@
1
- /* Freeday tokens — GENERATED by tokens/build.mjs. Do not edit by hand. */
1
+ /* Freeday tokens. GENERATED by tokens/build.mjs. Do not edit by hand. */
2
2
  :root {
3
3
  --azure-50: #eff4ff;
4
4
  --azure-100: #dbe6fe;
@@ -178,7 +178,7 @@
178
178
  --focus-ring-width: 2px;
179
179
  }
180
180
  /* The SYSTEM default stays root-scoped: it is a statement about the document, and dropping :root
181
- * here would match every element that is not itself [data-theme="light"] — which would re-darken
181
+ * here would match every element that is not itself [data-theme="light"], which would re-darken
182
182
  * the children of a light panel, since they carry no attribute of their own. */
183
183
  @media (prefers-color-scheme: dark) {
184
184
  :root:not([data-theme="light"]) {
@@ -240,7 +240,7 @@
240
240
  }
241
241
  /* The two EXPLICIT opt-ins are deliberately NOT scoped to :root, for the same reason as density
242
242
  * below: these are inheriting custom properties, so data-theme on any ancestor re-themes just that
243
- * subtree — a dark brand panel beside a light form is an ordinary layout, and it should not require
243
+ * subtree, a dark brand panel beside a light form is an ordinary layout, and it should not require
244
244
  * re-colouring each component by hand. The root still matches, so data-theme on <html> is unchanged.
245
245
  * Both keep the same specificity (0,1,0) as :root and come after it, so they still win there; and
246
246
  * --light after --dark means a light island inside a dark region wins in turn. */
@@ -356,7 +356,7 @@
356
356
  --chart-tick: var(--slate-400);
357
357
  --focus-ring: var(--azure-600);
358
358
  }
359
- /* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit —
359
+ /* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit,
360
360
  * putting the attribute on any ancestor (a route wrapper, one section) densifies just that
361
361
  * subtree, which is how density is actually decided: per screen, not per app. The root still
362
362
  * matches, so setting data-density on the html element keeps working exactly as before. */
@@ -368,7 +368,7 @@
368
368
  --control-h: 2rem;
369
369
  }
370
370
  /* ...and the way back out. Inheritance only runs downhill: once the root is compact every subtree is
371
- * compact, and before this rule existed a data-density="comfortable" wrapper matched nothing — so the
371
+ * compact, and before this rule existed a data-density="comfortable" wrapper matched nothing, so the
372
372
  * comment above was true in one direction only. An app that is dense overall and carries one region
373
373
  * of shared chrome (a header, a toolbar, a footer shared with a sibling product) had to restate the
374
374
  * defaults by hand, which is exactly the local copy that goes stale when the kit retunes them. */
@@ -5,7 +5,7 @@ Copilot…). No model has Freeday in its training data, so an agent that is mere
5
5
  will invent class names or silently fall back to Bootstrap/Tailwind conventions. This file is the
6
6
  fix: paste the block below into the consuming project's agent instruction file, once.
7
7
 
8
- > Working on **the kit itself**, not a consuming app? That's [`../CLAUDE.md`](../CLAUDE.md) — this
8
+ > Working on **the kit itself**, not a consuming app? That's [`../CLAUDE.md`](../CLAUDE.md). This
9
9
  > file is about *using* the published package.
10
10
 
11
11
  ---
@@ -20,49 +20,49 @@ the **root of the consuming project**:
20
20
 
21
21
  All UI in this project is built from Freeday: a **token-driven CSS kit** (`fdy-*` classes on plain
22
22
  markup) with **typed components for Vue, React and Blazor** layered on top. Most of the kit is
23
- markup + classes; ten interactive components also ship a typed wrapper, and in those three stacks
23
+ markup + classes; eleven interactive components also ship a typed wrapper, and in those three stacks
24
24
  the wrapper is the correct way to use them.
25
25
 
26
- **0. First decide which entry point this project uses. This is not an optimisation — get it wrong
26
+ **0. First decide which entry point this project uses. This is not an optimisation. Get it wrong
27
27
  and the code looks correct and fails later.**
28
28
 
29
- | This project's stack | Import the ten components from | Binding |
29
+ | This project's stack | Import the eleven components from | Binding |
30
30
  |---|---|---|
31
31
  | Vue 3 | `@cahyo-dimas/freeday/vue` | `v-model` |
32
32
  | React 18/19 | `@cahyo-dimas/freeday/react` | `value` + `onChange` |
33
33
  | Blazor (net8.0) | `@using Freeday.Blazor` (RCL) | `@bind-Value` |
34
- | Static HTML, Svelte, server-rendered templates… | no wrapper — raw markup + the enhancer script | `fdy-*` DOM events |
34
+ | Static HTML, Svelte, server-rendered templates… | no wrapper: raw markup + the enhancer script | `fdy-*` DOM events |
35
35
 
36
- The ten: **FdyCombo · FdyDatepicker · FdyDateRange · FdyAutocomplete · FdyCascade · FdyCfl ·
37
- FdyChart · FdyTable · FdyModal · FdyDrawer**. In Vue/React/Blazor, **never hand-write the raw
38
- markup + enhancer for these ten.** The raw path *appears* to work — the enhancer auto-initialises
39
- once on `DOMContentLoaded` and the first render is correct — then fails quietly: DOM your framework
36
+ The eleven: **FdyCombo · FdyDatepicker · FdyDateRange · FdyAutocomplete · FdyCascade · FdyCfl ·
37
+ FdyChart · FdyTable · FdyModal · FdyDrawer · FdyAppShell**. In Vue/React/Blazor, **never hand-write the raw
38
+ markup + enhancer for these eleven.** The raw path *appears* to work: the enhancer auto-initialises
39
+ once on `DOMContentLoaded` and the first render is correct. Then it fails quietly: DOM your framework
40
40
  renders later is never hydrated, and the widget's state lives in the DOM instead of in your
41
41
  framework's state.
42
42
 
43
43
  Everything else is the same in every stack: plain `fdy-*` markup (button, card, badge, alert,
44
44
  table markup, layout…). For the *other* interactive components (chips, stepper, input mask, file
45
45
  upload, tree, tabs, menu, rating, slider, form validation, carousel, timepicker) there is no
46
- wrapper — use the raw markup and hydrate it:
46
+ wrapper, use the raw markup and hydrate it:
47
47
 
48
- - **Vue / React** — `useFreeday(rootRef)` from the same import path, plus `import '@cahyo-dimas/freeday'`
48
+ - **Vue / React:** `useFreeday(rootRef)` from the same import path, plus `import '@cahyo-dimas/freeday'`
49
49
  once at app entry to register the enhancers. (The eleven typed components do **not** need this: they
50
50
  are native Vue/React implementations of the same markup, not wrappers over the enhancer.)
51
- - **Blazor** — `FreedayBlazor.initAll` interop. (Here the typed components *are* thin wrappers over
51
+ - **Blazor:** `FreedayBlazor.initAll` interop. (Here the typed components *are* thin wrappers over
52
52
  the enhancers, so the enhancer script is always required.)
53
53
 
54
54
  **Before writing or editing any markup/CSS, read these (they ship inside the package):**
55
- - `node_modules/@cahyo-dimas/freeday/COMPONENTS.md` — every class that exists, with minimal markup
55
+ - `node_modules/@cahyo-dimas/freeday/COMPONENTS.md` lists every class that exists, with minimal markup
56
56
  skeletons, enhancer hooks and the a11y contract per component. **The class list is closed:
57
- if a class is not in that file, it does not exist — do not invent one.**
58
- - `node_modules/@cahyo-dimas/freeday/USAGE.md` — the doctrine: which token/role/shadow to use when.
59
- - `node_modules/@cahyo-dimas/freeday/docs/reference-screen.html` — one complete screen, assembled
57
+ if a class is not in that file, it does not exist. Do not invent one.**
58
+ - `node_modules/@cahyo-dimas/freeday/USAGE.md` is the doctrine: which token/role/shadow to use when.
59
+ - `node_modules/@cahyo-dimas/freeday/docs/reference-screen.html` is one complete screen, assembled
60
60
  the intended way. Copy this structure for a new screen.
61
61
 
62
62
  **Non-negotiables:**
63
63
  1. No raw hex or px in app CSS. Use tokens: `var(--color-primary)`, `var(--space-4)` (4px scale),
64
64
  `var(--radius-md)`, `var(--shadow-1)`, `var(--dur-2)`.
65
- 2. Components only touch semantic tokens (`--color-*`) — never the primitive ramp (`--azure-600`).
65
+ 2. Components only touch semantic tokens (`--color-*`), never the primitive ramp (`--azure-600`).
66
66
  3. `.fdy-btn` is already the primary action (there is no `--primary`). One per screen; everything
67
67
  else is `--ghost` or `--text`.
68
68
  4. Three title roles only: `.fdy-title-page` (one `<h1>`) / `.fdy-title-section` / `.fdy-title-card`.
@@ -72,40 +72,43 @@ wrapper — use the raw markup and hydrate it:
72
72
  Icon-only buttons need `aria-label`. Status is never colour-only.
73
73
  7. Interactive components need their enhancer script loaded (see the table in COMPONENTS.md); in an
74
74
  SPA, re-hydrate dynamic DOM with `useFreeday` / `FreedayBlazor.initAll`. This applies to the
75
- components **without** a typed wrapper — for the ten in step 0, use the wrapper instead.
75
+ components **without** a typed wrapper. For the eleven in step 0, use the wrapper instead.
76
76
  8. Freeday owns components + tokens, **not layout**. Grids/stacks/one-off gaps come from our own
77
- layout layer — build its theme on `var(--space-N)` so both systems stay in step.
77
+ layout layer. Build its theme on `var(--space-N)` so both systems stay in step.
78
+ 9. The enhancers render their own English UI strings (pager, filter dialog, validation). Never
79
+ hand-translate one by rewriting the enhancer's nodes — set `data-fdy-text-<key>` on the
80
+ component root, and `<html lang>` for date names. Keys are in COMPONENTS.md.
78
81
  ```
79
82
 
80
83
  Adjust the paths if the package lives somewhere else (a workspace, a vendored copy, `wwwroot/` for
81
- Blazor). Then verify the agent can actually read those files — an agent that can't open
84
+ Blazor). Then verify the agent can actually read those files. An agent that can't open
82
85
  `node_modules` will keep guessing.
83
86
 
84
87
  ## 2. What ships in the package
85
88
 
86
89
  | File | What it answers |
87
90
  |---|---|
88
- | `COMPONENTS.md` | The complete class surface — what exists, its modifiers, minimal markup, a11y. |
89
- | `USAGE.md` | The doctrine — which token/role/shadow/emphasis to use when. |
91
+ | `COMPONENTS.md` | The complete class surface: what exists, its modifiers, minimal markup, a11y. Also the typed wrappers' props — one `### Props — <FdyX>` table each, and the `FdyTableColumn` fields. |
92
+ | `USAGE.md` | The doctrine: which token/role/shadow/emphasis to use when. |
90
93
  | `docs/getting-started.md` | Install + import + theme, per stack (Static HTML · Vue · React · Blazor). |
91
94
  | `docs/integrations.md` | How to bridge third-party libraries (validation, charts, dates, i18n…). |
92
95
  | `docs/reference-screen.html` | A full screen assembled from the shell down. Open it in a browser. |
93
96
  | `docs/agent-onboarding.md` | This file. |
94
- | `CHANGELOG.md` | **What changed between the version this project had and the one it has now** — read it after every upgrade; each entry says what broke, what is new, and why. |
95
- | `dist/` | Built CSS + enhancers. **`freeday.bundle.css` = tokens + components** (what `@cahyo-dimas/freeday/css` resolves to); `freeday.css` is components **only**, `freeday.tokens.css` tokens only — linking `freeday.css` alone leaves every `var(--…)` unresolved. Plus `freeday-*.js` and the `.d.ts` files. |
97
+ | `CHANGELOG.md` | **What changed between the version this project had and the one it has now.** Read it after every upgrade; each entry says what broke, what is new, and why. |
98
+ | `dist/` | Built CSS + enhancers. **`freeday.bundle.css` = tokens + components** (what `@cahyo-dimas/freeday/css` resolves to); `freeday.css` is components **only**, `freeday.tokens.css` tokens only, so linking `freeday.css` alone leaves every `var(--…)` unresolved. Plus `freeday-*.js` and the `.d.ts` files. |
96
99
  | `src/components/*.css` | The authoritative source for every class, when a doc is ambiguous. |
97
- | `tokens/tokens.json` | Every token in W3C DTCG format — machine-readable. |
98
- | `adapters/vue` · `adapters/react` · `adapters/blazor` | Typed wrappers, 10 components each. |
100
+ | `tokens/tokens.json` | Every token in W3C DTCG format, machine-readable. |
101
+ | `adapters/vue` · `adapters/react` · `adapters/blazor` | Typed wrappers, 11 components each (plus `FdyTableFooter`). |
99
102
 
100
103
  The live docs (with an interactive playground) are at
101
- <https://cahyo-dimas.github.io/freeday-ui-kit/>, and the repo — including three complete example
102
- apps under `examples/` (Vue, React, Blazor) that are **not** in the npm tarball — is at
104
+ <https://cahyo-dimas.github.io/freeday-ui-kit/>, and the repo (including three complete example
105
+ apps under `examples/` (Vue, React, Blazor) that are **not** in the npm tarball) is at
103
106
  <https://github.com/cahyo-dimas/freeday-ui-kit>.
104
107
 
105
108
  ### After an upgrade, read the changelog first
106
109
 
107
110
  `npm i @cahyo-dimas/freeday@latest` does not tell you what you gained. Read
108
- `node_modules/@cahyo-dimas/freeday/CHANGELOG.md` down to the version this project was on before —
111
+ `node_modules/@cahyo-dimas/freeday/CHANGELOG.md` down to the version this project was on before,
109
112
  it is written for exactly this moment, and it is the difference between adopting a new affordance
110
113
  and re-implementing it locally.
111
114
 
@@ -130,31 +133,31 @@ Recent additions most likely to replace something an app hand-rolled (all detail
130
133
 
131
134
  The order matters; skipping to components is what produces flat, identical-card screens.
132
135
 
133
- 0. **Pick the screen shape first.** Which archetype is this — dashboard, master-detail, kanban,
136
+ 0. **Pick the screen shape first.** Which archetype is this: dashboard, master-detail, kanban,
134
137
  wizard, POS…? The repo's
135
138
  [`reference/README.md`](https://github.com/cahyo-dimas/freeday-ui-kit/blob/main/reference/README.md)
136
139
  maps 15 archetypes to the exact primitives that compose each one, and says plainly which shapes
137
140
  the kit has **no** component for (kanban columns, calendar month grid, chat bubbles, canvas) so
138
- you build the frame instead of inventing a class. Not in the npm package — read it on GitHub.
139
- 1. **Shell** — is `.fdy-app` already in place (usually once, in the app layout)? If not, copy it
141
+ you build the frame instead of inventing a class. Not in the npm package, so read it on GitHub.
142
+ 1. **Shell:** is `.fdy-app` already in place (usually once, in the app layout)? If not, copy it
140
143
  from `docs/getting-started.md` §The app shell.
141
- 2. **Theme** — `data-theme="light|dark"` + `data-density="comfortable|compact"`, normally on
144
+ 2. **Theme:** `data-theme="light|dark"` + `data-density="comfortable|compact"`, normally on
142
145
  `<html>`, set once at the root. Use `compact` for table-heavy back-office screens. Both attributes
143
146
  also work on **any ancestor**: `<section data-theme="dark">` inverts that region and every
144
147
  component inside it follows, so never hand-colour an inverted panel.
145
- 3. **Fonts** — the package ships **no** `@font-face`. Load Sora / IBM Plex Sans / JetBrains Mono
148
+ 3. **Fonts:** the package ships **no** `@font-face`. Load Sora / IBM Plex Sans / JetBrains Mono
146
149
  yourself, or override `--font-display`/`--font-body`/`--font-mono`. Skipping this reads as
147
150
  "unfinished design", not "missing dependency".
148
- 4. **Page frame** — `.fdy-page` + `.fdy-page__header` (eyebrow + `.fdy-title-page` + desc on the
151
+ 4. **Page frame:** `.fdy-page` + `.fdy-page__header` (eyebrow + `.fdy-title-page` + desc on the
149
152
  left, **one** primary action on the right).
150
- 5. **Sections** — one `.fdy-page-section` per region, each with a `.fdy-title-section`.
151
- 6. **Components** — from `COMPONENTS.md`, inside the sections.
152
- 7. **Verify** — the checklist in §5.
153
+ 5. **Sections:** one `.fdy-page-section` per region, each with a `.fdy-title-section`.
154
+ 6. **Components:** from `COMPONENTS.md`, inside the sections.
155
+ 7. **Verify:** the checklist in §5.
153
156
 
154
157
  ## 4. Migrating an existing UI to Freeday
155
158
 
156
159
  Migration is a **class-and-structure swap**, not a rewrite. Keep the app's DOM semantics; replace
157
- the styling layer. Rough equivalents — always confirm the target class in `COMPONENTS.md`, and note
160
+ the styling layer. Rough equivalents, but always confirm the target class in `COMPONENTS.md`, and note
158
161
  that Freeday deliberately has **no** layout/spacing utilities, so grid/flex/margin classes stay with
159
162
  your own layout layer:
160
163
 
@@ -174,19 +177,19 @@ your own layout layer:
174
177
  | `table table-striped` / `MudTable` | `fdy-table` in `fdy-table-wrap`; interactive → `fdy-datatable` |
175
178
  | `badge bg-success` / `MudChip` (status) | `fdy-badge fdy-badge--success` |
176
179
  | `alert alert-danger` | `fdy-alert fdy-alert--danger` + `role="alert"` |
177
- | `modal` / `MudDialog` | `<dialog class="fdy-modal">` (native — drop the JS backdrop plumbing) |
180
+ | `modal` / `MudDialog` | `<dialog class="fdy-modal">` (native, so drop the JS backdrop plumbing) |
178
181
  | `offcanvas` / `MudDrawer` | `<dialog class="fdy-drawer">` + `data-fdy-drawer` |
179
182
  | `nav nav-tabs` | `fdy-tabs` + `data-fdy-tabs` |
180
183
  | `breadcrumb` / `pagination` | `fdy-breadcrumb` / `fdy-pagination` (same `<nav><ol>` structure) |
181
184
  | `spinner-border` / `progress` | `fdy-spinner` / `fdy-progress` + `__bar` |
182
- | `toast` container + JS | `Freeday.toast({…})` — imperative, no markup to author |
185
+ | `toast` container + JS | `Freeday.toast({…})`, imperative, no markup to author |
183
186
  | `text-muted` | `fdy-text-muted` |
184
187
  | `d-none` / `visually-hidden` | `fdy-hidden` / `fdy-visually-hidden` |
185
- | `container` / `row` / `col-*` / `mb-3` / `gap-2` | **stays yours** — Freeday ships no layout utilities |
188
+ | `container` / `row` / `col-*` / `mb-3` / `gap-2` | **stays yours**: Freeday ships no layout utilities |
186
189
 
187
190
  Order of work that avoids a half-migrated mess:
188
191
 
189
- 1. Load Freeday's CSS and **turn off the old framework's reset/preflight** — `base.css` is the
192
+ 1. Load Freeday's CSS and **turn off the old framework's reset/preflight**, because `base.css` is the
190
193
  reset now. Two resets fighting is the usual source of "everything looks slightly off".
191
194
  2. Shell + theme attributes first, so tokens resolve everywhere.
192
195
  3. Then screen by screen: page frame → sections → controls. Convert a whole screen at a time;
@@ -194,15 +197,15 @@ Order of work that avoids a half-migrated mess:
194
197
  4. Delete the old framework's CSS only when no screen references it, then grep for leftover class
195
198
  prefixes.
196
199
  5. Replace hand-rolled modal/drawer/dropdown JS with the native-`<dialog>` components and the
197
- enhancers — that is usually where the most code disappears.
200
+ enhancers, which is usually where the most code disappears.
198
201
 
199
202
  ## 5. Verification checklist (before claiming a screen is done)
200
203
 
201
204
  - Every `fdy-*` class used appears in `COMPONENTS.md`. Grep the diff for `fdy-` and check.
202
205
  - No raw hex/rgb/px in the diff's CSS. Grep for `#` and `px`.
203
206
  - Exactly one `.fdy-btn` without a variant modifier on the screen; one `.fdy-title-page`.
204
- - Toggle `data-theme="dark"` on `<html>` — nothing becomes unreadable, no hard-coded white/black.
205
- - Toggle `data-density="compact"` — the layout still holds.
207
+ - Toggle `data-theme="dark"` on `<html>`: nothing becomes unreadable, no hard-coded white/black.
208
+ - Toggle `data-density="compact"`: the layout still holds.
206
209
  - Keyboard: Tab reaches every control, focus is always visible, Esc closes overlays.
207
210
  - Form errors carry `aria-invalid` + a linked message; icon-only buttons have `aria-label`.
208
211
  - The interactive components on the screen have their enhancer loaded, and SPA-rendered DOM is
@@ -15,43 +15,43 @@ A step-by-step guide to adopting Freeday in **your new project**. Pick your stac
15
15
  Freeday = **CSS** (semantic tokens + `fdy-*` classes) + **zero-dependency JS enhancers** (optional).
16
16
 
17
17
  1. **Static vs interactive.** Static components (button, card, badge, plain input, layout) need
18
- only the **`fdy-*` classes** — no JS. Interactive components (select/combo, cascade, date/time
18
+ only the **`fdy-*` classes**, no JS. Interactive components (select/combo, cascade, date/time
19
19
  picker, table, dropzone, form validation, input mask, chip) need the **JS enhancers**.
20
- 2. **The enhancer is the source of truth — *on the raw path*.** You don't re-implement components;
20
+ 2. **The enhancer is the source of truth, *on the raw path*.** You don't re-implement components;
21
21
  the enhancer owns the widget's DOM. You **listen for `fdy-*` events** (all bubbling
22
22
  `CustomEvent`s, data in `event.detail`) → store them in your framework state. Event/API contract
23
23
  table: [`integrations.md` §Event & API contract](integrations.md).
24
- **On Vue, React or Blazor this is not the path to take for ten of the components** — `FdyCombo`,
24
+ **On Vue, React or Blazor this is not the path to take for eleven of the components.** `FdyCombo`,
25
25
  `FdyDatepicker`, `FdyDateRange`, `FdyAutocomplete`, `FdyCascade`, `FdyCfl`, `FdyChart`,
26
- `FdyTable`, `FdyModal`, `FdyDrawer` ship typed wrappers that own the state properly (Vue and
26
+ `FdyTable`, `FdyModal`, `FdyDrawer`, `FdyAppShell` ship typed wrappers that own the state properly (Vue and
27
27
  React re-implement the interaction natively; Blazor wraps the enhancer over interop). Use them;
28
28
  the raw path is for the components without a wrapper, and for stacks without an adapter.
29
29
  3. **Hydrate dynamic DOM.** Enhancers auto-init once on `DOMContentLoaded`. DOM an SPA renders
30
30
  **after** that must be re-hydrated: `window.Freeday<X>.initAll(el)` (idempotent, safe to repeat).
31
- Each framework's adapter wraps this — you don't call it manually.
31
+ Each framework's adapter wraps this, so you don't call it manually.
32
32
  4. **Theme via `data-*`.** `data-theme="light|dark"` (all semantic tokens switch) +
33
33
  `data-density="comfortable|compact"` (control height, for data-dense screens). Normally on
34
34
  `<html>`; change at runtime with `document.documentElement.dataset.theme = 'dark'`. Both also
35
- work on **any ancestor** — these are inheriting custom properties, so `<section data-theme="dark">`
35
+ work on **any ancestor**, because these are inheriting custom properties, so `<section data-theme="dark">`
36
36
  inverts just that region and every component inside it follows. See [`USAGE.md`](../USAGE.md) §5b.
37
37
  5. **3-tier token rule.** Components only touch **Tier 2/3** (`var(--color-primary)`,
38
38
  `var(--space-4)`, `var(--radius-md)`…). **Never** write raw hex/px.
39
39
  6. **Scope: components + tokens, deliberately *not* layout.** Freeday ships components and tokens;
40
40
  the only layout helpers are `.fdy-hidden` / `.fdy-visually-hidden`. Stacks, grids, gaps and sizing
41
- come from **your** layout layer — pair Freeday with a utility framework (Tailwind, UnoCSS…) run
41
+ come from **your** layout layer. Pair Freeday with a utility framework (Tailwind, UnoCSS…) run
42
42
  **utilities-only, preflight OFF** (Freeday's `base.css` is your reset). Two consequences worth
43
43
  knowing up front:
44
- - **base.css is a *light* reset** — it does not strip `ul`/`ol`/`p` margins. With preflight off, a
44
+ - **base.css is a *light* reset.** It does not strip `ul`/`ol`/`p` margins. With preflight off, a
45
45
  semantic `<ul>` keeps native bullets + a 40px indent; add **`.fdy-list-reset`** (or use a Freeday
46
46
  list component) on such lists.
47
47
  - **The spacing scale is public.** `--space-0`…`--space-24`, `--radius-*`, `--dur-*` etc. are real
48
- custom properties in `dist/freeday.tokens.css` — **define your utility theme in terms of them**
48
+ custom properties in `dist/freeday.tokens.css`, so **define your utility theme in terms of them**
49
49
  (`spacing: { 4: 'var(--space-4)' }`) so both systems stay in step. `data-density="compact"` steps
50
50
  `--control-h` **and** the mid-range spacing scale (`--space-3`…`--space-6`), so Freeday components
51
- densify — and if your utility theme is built on `var(--space-N)`, density reaches your utilities too.
52
- 7. **Load the fonts — the package does not.** The type tokens *name* **Sora** (display), **IBM Plex
51
+ densify, and if your utility theme is built on `var(--space-N)`, density reaches your utilities too.
52
+ 7. **Load the fonts yourself. The package does not.** The type tokens *name* **Sora** (display), **IBM Plex
53
53
  Sans** (body) and **JetBrains Mono** (data), but Freeday bundles no `@font-face` and no font files.
54
- Load them yourself, or the kit renders in the system fallback — which reads as "unfinished design",
54
+ Load them yourself, or the kit renders in the system fallback, which reads as "unfinished design",
55
55
  not "missing dependency". One line with [Fontsource](https://fontsource.org):
56
56
  ```css
57
57
  @import '@fontsource/sora/600.css'; @import '@fontsource/sora/700.css';
@@ -59,23 +59,30 @@ Freeday = **CSS** (semantic tokens + `fdy-*` classes) + **zero-dependency JS enh
59
59
  ```
60
60
  (Or a `<link>` to your own self-hosted copies, or override `--font-display`/`--font-body`/`--font-mono`
61
61
  to faces you already ship. If you keep a system-sans fallback, consider softening
62
- `--tracking-tighter` on headings — it's tuned for Sora's proportions.)
62
+ `--tracking-tighter` on headings, since it's tuned for Sora's proportions.)
63
63
  8. **Start from the shell, then compose.** Every application goes inside **`.fdy-app`** (see below);
64
- inside it, assemble screens from the composition primitives — `.fdy-page`, `.fdy-page__header`,
65
- `.fdy-page-section`, `.fdy-toolbar`, `.fdy-stats`/`.fdy-stat` — and the type roles (`.fdy-title-page`
64
+ inside it, assemble screens from the composition primitives (`.fdy-page`, `.fdy-page__header`,
65
+ `.fdy-page-section`, `.fdy-toolbar`, `.fdy-stats`/`.fdy-stat`) and the type roles (`.fdy-title-page`
66
66
  / `-section` / `-card`), not by re-using `.fdy-card__title` for everything. **Which token/role/shadow
67
- to use when lives in [`USAGE.md`](../USAGE.md)** — read it once; it's what makes screens cohere.
67
+ to use when lives in [`USAGE.md`](../USAGE.md)**. Read it once; it's what makes screens cohere.
68
+ 9. **The enhancers' UI strings are English, and replaceable per element.** Pager labels, column
69
+ filter dialogs, upload and validation messages come from the enhancer, not from your markup.
70
+ Override any of them with **`data-fdy-text-<key>`** on the component's root — key list and the
71
+ `{n}`/`{from}`/`{total}` placeholders are in [`COMPONENTS.md`](../COMPONENTS.md) §Data table.
72
+ Dates need no attribute: the pickers format through `Intl` from the page's `<html lang>`, so
73
+ `lang="id"` gives Indonesian month and weekday names. **Coming from 1.x**, where those defaults
74
+ were Indonesian: 2.0.0 flipped them to English, and this attribute is the whole migration.
68
75
 
69
76
  ---
70
77
 
71
78
  ## The app shell (start here)
72
79
 
73
- Every Freeday application goes inside **`.fdy-app`** — the frame that holds a top bar, a sidebar, and
80
+ Every Freeday application goes inside **`.fdy-app`**, the frame that holds a top bar, a sidebar, and
74
81
  the scrolling content. Don't hand-roll one from flexbox; the responsive sidebar + backdrop are built in.
75
82
  The nav toggle is one line: toggle `.fdy-app--nav-open` (mobile drawer) / `.fdy-app--nav-collapsed`
76
83
  (desktop) on the `.fdy-app` element from the `__navtoggle` button's click.
77
84
 
78
- The nesting is not free-form — `.fdy-app` is a flex **row** of `[sidebar | content]`, and `__content`
85
+ The nesting is not free-form: `.fdy-app` is a flex **row** of `[sidebar | content]`, and `__content`
79
86
  is the column that holds the topbar and the main area (it gives the sticky topbar a tall containing
80
87
  block to travel in). The brand belongs in the **sidebar**, sized to match the topbar's height:
81
88
 
@@ -109,11 +116,11 @@ block to travel in). The brand belongs in the **sidebar**, sized to match the to
109
116
  </div>
110
117
  ```
111
118
 
112
- `.fdy-app__main` already carries the page padding (`--space-8`, `--space-5` on mobile) — don't wrap
119
+ `.fdy-app__main` already carries the page padding (`--space-8`, `--space-5` on mobile), so don't wrap
113
120
  your screen in another padded box. The toggle's two states split at **720px**: above it,
114
121
  `.fdy-app--nav-collapsed` collapses the sidebar to zero width; at or below it,
115
122
  `.fdy-app--nav-open` slides the sidebar in as an off-canvas drawer over the backdrop. A complete,
116
- working version of all of this — including the toggle script — is
123
+ working version of all of this, including the toggle script, is
117
124
  [`reference-screen.html`](reference-screen.html).
118
125
 
119
126
  Then compose the screen inside `__main` with `.fdy-page` / `.fdy-page__header` / `.fdy-page-section`
@@ -125,10 +132,10 @@ token to use where.
125
132
 
126
133
  ## Static HTML (no build)
127
134
 
128
- Good for plain `.html` pages / templates — no bundler, no npm.
135
+ Good for plain `.html` pages / templates, with no bundler and no npm.
129
136
 
130
137
  ### 1. Get the dist files into your project
131
- `dist/` is committed, so there's no build step. Easiest way — use npm once just to download, then
138
+ `dist/` is committed, so there's no build step. Easiest way: use npm once just to download, then
132
139
  copy the files (vendor them):
133
140
  ```bash
134
141
  npm i @cahyo-dimas/freeday
@@ -184,7 +191,7 @@ live docs also have a copy button per component.
184
191
  ```bash
185
192
  npm i @cahyo-dimas/freeday
186
193
  ```
187
- Lands in `package.json` as `"@cahyo-dimas/freeday": "^1.34.0"` (public npm package). `dist/` is
194
+ Lands in `package.json` as `"@cahyo-dimas/freeday": "^2.1.0"` (public npm package). `dist/` is
188
195
  committed and published → no build step; `npm ci` runs without auth.
189
196
 
190
197
  ### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
@@ -229,7 +236,7 @@ const onDate = (e: Event) => { form.dueDate = (e as CustomEvent<FdyDatepicke
229
236
  ```
230
237
 
231
238
  **Gotcha:** if TypeScript complains about `import '@cahyo-dimas/freeday/css'`, make sure `env.d.ts`
232
- has `/// <reference types="vite/client" />`. For **Nuxt/SSR**, enhancers are client-only — wrap them
239
+ has `/// <reference types="vite/client" />`. For **Nuxt/SSR**, enhancers are client-only, so wrap them
233
240
  in `onMounted`/`<ClientOnly>`.
234
241
 
235
242
  Full working example: [`examples/vue-faktur/`](../examples/vue-faktur/).
@@ -296,14 +303,15 @@ export function Panel() {
296
303
  }
297
304
  ```
298
305
 
299
- **Gotcha:** because the enhancer owns the widget DOM, don't double-control it from React — store the
306
+ **Gotcha:** because the enhancer owns the widget DOM, don't double-control it from React. Store the
300
307
  value from `event.detail` in state/ref; don't set the DOM `value` back. `StrictMode` mounts twice in
301
308
  dev; `useFreeday` is idempotent, so it's safe.
302
309
 
303
- ### 5. Alternative: typed controlled components (`FdyCombo` · `FdyDatepicker` · `FdyDateRange` · `FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart`)
310
+ ### 5. Alternative: typed controlled components
304
311
  For fields you'd normally write as a native `<select>`/`<input type="date">`,
305
- `@cahyo-dimas/freeday/react` also exports typed **controlled** components — plain `value`/`onChange`,
306
- no manual event bubbling (parity with the Vue `v-model` components above):
312
+ `@cahyo-dimas/freeday/react` also exports typed **controlled** components with plain `value`/`onChange`,
313
+ no manual event bubbling — the same eleven listed in §Core concepts #2, at parity with the Vue
314
+ `v-model` components above:
307
315
  ```tsx
308
316
  import { FdyCombo } from '@cahyo-dimas/freeday/react';
309
317
  import type { FdyComboOption } from '@cahyo-dimas/freeday/react';
@@ -320,7 +328,7 @@ function StatusField({ value, onChange }: { value: Status; onChange: (v: Status)
320
328
  }
321
329
  ```
322
330
  `FdyDatepicker`, `FdyCfl` (async choose-from-list), and `FdyChart` share the same shape (typed
323
- `value`/`onChange`, or `series`/`values` for `FdyChart`) — see [`integrations.md`](integrations.md)
331
+ `value`/`onChange`, or `series`/`values` for `FdyChart`). See [`integrations.md`](integrations.md)
324
332
  and `examples/react-faktur/src/App.tsx` for the full patterns. **Vite works with no extra config**
325
333
  (esbuild transpiles the `.tsx` source directly); **Next.js** consumers may need
326
334
  `transpilePackages: ['@cahyo-dimas/freeday']` in `next.config.js`.
@@ -331,9 +339,9 @@ Full working example: [`examples/react-faktur/`](../examples/react-faktur/).
331
339
 
332
340
  ## Blazor (WASM)
333
341
 
334
- Blazor doesn't use npm — Freeday is served as **static files** in `wwwroot/`.
342
+ Blazor doesn't use npm. Freeday is served as **static files** in `wwwroot/`.
335
343
 
336
- > **Prefer the native components?** Jump to [§4 — the `Freeday.Blazor` RCL](#4-recommended-native-typed-components-freedayblazor-rcl):
344
+ > **Prefer the native components?** Jump to [§4, the `Freeday.Blazor` RCL](#4-recommended-native-typed-components-freedayblazor-rcl):
337
345
  > typed `<FdyX>` with `@bind`, no manual JS interop. Steps 1–2 (assets + scripts) still apply; step 3
338
346
  > below (the raw enhancer + event bridge) is the underlying mechanism and the fallback for markup the
339
347
  > RCL doesn't cover.
@@ -365,7 +373,7 @@ Load `freeday.js` then `freeday-blazor.js` **before** `blazor.webassembly.js`:
365
373
  <script src="_framework/blazor.webassembly.js"></script>
366
374
  </body>
367
375
  ```
368
- > Use the global IIFE (`window.FreedayBlazor`), **not** an ES module — so it passes strict-MIME on static hosts.
376
+ > Use the global IIFE (`window.FreedayBlazor`), **not** an ES module, so it passes strict-MIME on static hosts.
369
377
 
370
378
  ### 3. Hydrate + bridge events in code-behind (`.razor.cs`)
371
379
  In `OnAfterRenderAsync(firstRender)`: `initAll`, then `on(...)` per event → `[JSInvokable]` methods.
@@ -402,7 +410,7 @@ public partial class Panel : ComponentBase, IAsyncDisposable
402
410
  }
403
411
  ```
404
412
  ```razor
405
- @* Panel.razor — @ref on the subtree container, fdy-* classes + data-fdy-* hooks in the markup *@
413
+ @* Panel.razor: @ref on the subtree container, fdy-* classes + data-fdy-* hooks in the markup *@
406
414
  <div @ref="_root">
407
415
  <button class="fdy-btn fdy-btn--primary" type="button">Save</button>
408
416
  <div data-fdy-cascade></div>
@@ -415,7 +423,7 @@ to flip the theme. Event DTOs are deserialized case-insensitively by Blazor.
415
423
  ### 4. Recommended: native typed components (`Freeday.Blazor` RCL)
416
424
 
417
425
  Instead of hand-writing `fdy-*` markup + the interop above, reference the **Razor Class Library** and
418
- use typed `<FdyX>` components with `@bind` — the Blazor equivalent of the Vue `v-model` / React
426
+ use typed `<FdyX>` components with `@bind`, the Blazor equivalent of the Vue `v-model` / React
419
427
  `value`/`onChange` adapters. Place the Freeday repo near your solution and add a project reference:
420
428
  ```xml
421
429
  <!-- YourApp.csproj -->
@@ -428,7 +436,7 @@ use typed `<FdyX>` components with `@bind` — the Blazor equivalent of the Vue
428
436
  Load `freeday.js` + `freeday-blazor.js` exactly as in step 2 (the components still hydrate over the
429
437
  kit's CSS/enhancers), then bind:
430
438
  ```razor
431
- @* Invoice.razor — no @ref, no manual JS interop, no [JSInvokable] *@
439
+ @* Invoice.razor: no @ref, no manual JS interop, no [JSInvokable] *@
432
440
  <FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl-status" />
433
441
  <FdyDatepicker @bind-Value="_dueDate" Label="Due date" />
434
442
  <FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)"
@@ -436,15 +444,16 @@ kit's CSS/enhancers), then bind:
436
444
  <FdyChart Type="donut" Values="_byCity" Labels="_cityLabels" AriaLabel="Revenue by city" />
437
445
  <FdyDrawer @bind-Open="_drawerOpen" Title="Detail" Side="right">…</FdyDrawer>
438
446
  ```
439
- Ten components at parity with the Vue/React adapters: **`FdyModal`** · **`FdyDrawer`** (`@bind-Open`,
447
+ Eleven components at parity with the Vue/React adapters: **`FdyModal`** · **`FdyDrawer`** (`@bind-Open`,
440
448
  `Title`, `Size`/`Side`, `Dismissible`) · **`FdyCombo<TValue>`** · **`FdyDatepicker`** ·
441
449
  **`FdyAutocomplete`** · **`FdyCascade`** · **`FdyDateRange`** (`@bind-From`/`@bind-To`) ·
442
450
  **`FdyCfl<TRow>`** (async `LoadPage`) · **`FdyChart`** · **`FdyTable<TRow>`** (client sort/filter/page,
443
- or controlled `Sort`/`Filters`/`Page` for a server-paged table; `RowActivatable`, `RowDetail`). Each
451
+ or controlled `Sort`/`Filters`/`Page` for a server-paged table; `RowActivatable`, `RowDetail`) ·
452
+ **`FdyAppShell`** (`@bind-NavOpen`). Each
444
453
  `select`-type control also takes `Disabled`/`Readonly`/`Invalid`. The RCL targets **net8.0** and is
445
454
  consumed as source (`<ProjectReference>`); `.NET bin/obj` never ships in the npm tarball.
446
455
 
447
- Full working example (all ten): [`examples/blazor-faktur/`](../examples/blazor-faktur/) —
456
+ Full working example: [`examples/blazor-faktur/`](../examples/blazor-faktur/),
448
457
  `Pages/ComponentsDemo.razor`.
449
458
 
450
459
  ---
@@ -452,8 +461,8 @@ Full working example (all ten): [`examples/blazor-faktur/`](../examples/blazor-f
452
461
  ## Verify (every stack)
453
462
 
454
463
  Run the project → check two things:
455
- 1. **CSS connected** — buttons/cards are styled (not plain HTML).
456
- 2. **Enhancers connected** — interactive components come alive (e.g. datepicker/combo open on
464
+ 1. **CSS connected:** buttons/cards are styled (not plain HTML).
465
+ 2. **Enhancers connected:** interactive components come alive (e.g. datepicker/combo open on
457
466
  click), and `event.detail` reaches your state.
458
467
 
459
468
  If the visuals are plain → the CSS didn't load. If visuals are fine but widgets are dead → the