@cahyo-dimas/freeday 2.0.0 → 2.2.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.
- package/CHANGELOG.md +155 -0
- package/COMPONENTS.md +298 -2
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/blazor/FdyAutocomplete.razor +4 -1
- package/adapters/blazor/FdyAutocomplete.razor.cs +26 -0
- package/adapters/blazor/FdyCascade.razor +6 -1
- package/adapters/blazor/FdyCascade.razor.cs +24 -0
- package/adapters/blazor/FdyCombo.razor +1 -0
- package/adapters/blazor/FdyCombo.razor.cs +13 -0
- package/adapters/blazor/FdyDatepicker.razor +15 -1
- package/adapters/blazor/FdyDatepicker.razor.cs +46 -0
- package/adapters/core/cfl-value.d.ts +11 -0
- package/adapters/core/cfl-value.js +31 -0
- package/adapters/react/components/FdyDrawer.tsx +3 -1
- package/adapters/react/components/FdyModal.tsx +3 -1
- package/adapters/react/index.d.ts +3 -0
- package/adapters/react/index.js +1 -0
- package/adapters/vue/components/FdyDrawer.vue +4 -1
- package/adapters/vue/components/FdyModal.vue +4 -1
- package/adapters/vue/index.d.ts +3 -0
- package/adapters/vue/index.js +1 -0
- package/dist/freeday-autocomplete.js +17 -1
- package/dist/freeday-carousel.js +5 -4
- package/dist/freeday-cascade.js +58 -7
- package/dist/freeday-cfl.js +5 -4
- package/dist/freeday-chart.js +33 -3
- package/dist/freeday-datepicker.js +109 -17
- package/dist/freeday-form.js +5 -4
- package/dist/freeday-mask.js +5 -4
- package/dist/freeday-select.js +18 -1
- package/dist/freeday-stepper.js +5 -4
- package/dist/freeday-table.js +9 -6
- package/dist/freeday-timepicker.js +19 -1
- package/dist/freeday-toast.js +5 -4
- package/dist/freeday-upload.js +5 -4
- package/dist/freeday.js +298 -64
- package/docs/agent-onboarding.md +9 -6
- package/docs/getting-started.md +15 -6
- package/docs/integrations.md +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,161 @@
|
|
|
3
3
|
Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); tiap versi = git tag.
|
|
5
5
|
|
|
6
|
+
**Kebijakan tipe (sejak 2.1.0).** Tipe publik yang **melebar** dihitung breaking meski runtime tak
|
|
7
|
+
berubah, karena yang gagal adalah build konsumen: `vue-tsc`/`tsc` menolak handler yang kemarin
|
|
8
|
+
benar. Perubahan seperti itu ditulis di bawah `### Changed: BREAKING (types)` — nama prop, tipe
|
|
9
|
+
lama → tipe baru, dan cara menyempitkannya — bukan di bawah `### Added`, betapapun aditifnya dari
|
|
10
|
+
sisi kit.
|
|
11
|
+
|
|
12
|
+
## [2.2.0] - 2026-08-26
|
|
13
|
+
### Added
|
|
14
|
+
- **The raw path can disable, lock and invalidate a picker** (`NEXT-UP.md` #12). `datepicker.css`
|
|
15
|
+
and `cascade.css` have styled `:disabled`, `[aria-readonly="true"]` and `[aria-invalid="true"]`
|
|
16
|
+
since they were written, and the enhancers never set any of them — so the states existed only in
|
|
17
|
+
the stacks that re-implement the control natively (Vue, React), and a **Blazor** app, whose
|
|
18
|
+
picker *is* the enhancer, could not disable a field at all. Now `data-disabled` /
|
|
19
|
+
`data-readonly` / `data-invalid` on the seed, plus `data-id` and `data-describedby` so the
|
|
20
|
+
trigger the enhancer builds can be labelled and described by a form's own markup.
|
|
21
|
+
- **`setState` on four enhancers** — `FreedayDatepicker`, `FreedayCascade`, `FreedayCombo`,
|
|
22
|
+
`FreedayAutocomplete`: `setState(root, { disabled, readonly, invalid })`. Not decoration: every
|
|
23
|
+
Blazor wrapper stops rendering once hydrated (`ShouldRender => !Hydrated`), so a parameter that
|
|
24
|
+
is only *rendered* goes quiet the moment a real form toggles it. The wrappers push through this
|
|
25
|
+
instead.
|
|
26
|
+
- **The Blazor pickers reach parity** with their Vue and React twins. `FdyDatepicker` goes from 6
|
|
27
|
+
parameters to 20 (the three states, `Id`, `Describedby`, and the nine calendar navigation
|
|
28
|
+
labels), `FdyAutocomplete` from 6 to 12, `FdyCascade` from 8 to 13, and `FdyCombo` finally has
|
|
29
|
+
`Describedby`. Four exemptions remain, each about the platform rather than effort, and each
|
|
30
|
+
named in `COMPONENTS.md`: `locale` (the enhancer formats through `Intl` from `<html lang>`),
|
|
31
|
+
`clearable`/`clearLabel` (this path builds no clear button), `ariaLabelledby` (the built trigger
|
|
32
|
+
is named by `Label`).
|
|
33
|
+
- **`closeLabel` on `FdyModal` and `FdyDrawer`** in Vue and React. Blazor has had `CloseLabel`
|
|
34
|
+
since it shipped; the other two hard-coded `aria-label="Close"`, so the gap ran in both
|
|
35
|
+
directions and this is the half nobody had reported yet.
|
|
36
|
+
- **The datepicker's ten labels are overridable**, as `data-fdy-text-prev-month`,
|
|
37
|
+
`-next-month`, `-prev-year`, `-next-year`, `-prev-years`, `-next-years`, `-choose-month`,
|
|
38
|
+
`-choose-year`, `-back-to-months`. Month and weekday NAMES still come from `Intl` via the page's
|
|
39
|
+
`lang`, which is the better hatch; these are the buttons around them.
|
|
40
|
+
### Fixed
|
|
41
|
+
- **A chart series with no label read `Seri 1`.** Indonesian, in a release train whose 2.0.0
|
|
42
|
+
exists precisely to stop the enhancers mixing languages. It is `Series {n}` now, and
|
|
43
|
+
overridable. The donut's centre caption (`Total`) was hard-coded in the same file and is
|
|
44
|
+
overridable too; it is also built as an element rather than assigned through `innerHTML`, since
|
|
45
|
+
an author-supplied string is not markup.
|
|
46
|
+
- **The table's number-range filter offered `Min` and `Maks`.** Same leftover, same fix:
|
|
47
|
+
`filterMin` / `filterMax` in the strings table, English by default.
|
|
48
|
+
- **The cascade's own `Select` / `Select…` defaults and the timepicker's `Choose a time`** were
|
|
49
|
+
literals no host could reach. Both tables now carry them.
|
|
50
|
+
- **A `readonly` autocomplete opened its suggestion list.** The input carries `readonly` natively
|
|
51
|
+
and the enhancer never read it, so the list dropped over a field nobody could edit.
|
|
52
|
+
### Changed
|
|
53
|
+
- **The hard-coded-string guard (#016) is inverted.** It used to find the line that writes to the
|
|
54
|
+
DOM and read the literals on it, which cannot see a string passed to a helper —
|
|
55
|
+
`navButton('‹', 'Previous month', fn)` writes its label inside `navButton`, where the value is a
|
|
56
|
+
variable. Ten datepicker labels, the timepicker's, the cascade's two, the table's two and the
|
|
57
|
+
chart's `Seri 1` all hid there, some of them for the whole life of the file. It now reads every
|
|
58
|
+
literal in every enhancer and asks the opposite question — is this prose, and is it in the
|
|
59
|
+
`TEXT` table — dropping non-prose by shape (selectors, CSS custom properties, kebab keys,
|
|
60
|
+
camelCase identifiers, URLs, locale tags) and one short vocabulary of DOM tokens. Verified by
|
|
61
|
+
reintroducing both blind spots and watching it fail.
|
|
62
|
+
### Added: guards
|
|
63
|
+
- `test/blazor/FdyPickerStateTests.cs`: 7 bUnit tests covering what `dotnet build` cannot see —
|
|
64
|
+
each parameter reaching the markup the enhancer reads, an absent one leaving no empty attribute
|
|
65
|
+
behind, and a state change **after** the first render arriving through interop.
|
|
66
|
+
- `browser/picker-states.mjs`: 7 tests in real Chrome with real mouse gestures — a disabled picker
|
|
67
|
+
that will not open, a readonly one that keeps focus and still refuses, both `aria-invalid` and
|
|
68
|
+
the `--error` class, `setState` unlocking a picker, and a nav label overridden per element.
|
|
69
|
+
- `test/docs.test.mjs`: the Blazor picker parity guard, Vue's prop surface against the four
|
|
70
|
+
wrappers, with the four exemptions written out with their reasons rather than a bare list.
|
|
71
|
+
### Notes on the shape of the fix
|
|
72
|
+
- The regression this work caused, and what caught it: naming the new flag `isDisabled` shadowed
|
|
73
|
+
an **existing** `isDisabled(date)` in the datepicker that decides whether a day falls outside
|
|
74
|
+
`min`/`max`, so the day grid threw on every render — while the panel still opened and only its
|
|
75
|
+
cells went missing. Nothing in the node suite noticed; `browser/vanilla.mjs`, which clicks
|
|
76
|
+
through the calendar in a real browser, failed immediately. The flags are named `state*` now,
|
|
77
|
+
with the collision written above them.
|
|
78
|
+
|
|
79
|
+
## [2.1.0] - 2026-08-25
|
|
80
|
+
### Added
|
|
81
|
+
- **`singleRow()`, exported from `@cahyo-dimas/freeday/vue` and `/react`** (#045). `FdyCfl` emits
|
|
82
|
+
`Row | Row[] | null` whatever its props are: the array is reachable only under `multiple`, the
|
|
83
|
+
null only under `clearable`, and the type cannot say which. So every single-select screen wrote
|
|
84
|
+
its own narrowing, and each one differently — a cast (which hides the day somebody adds
|
|
85
|
+
`clearable`), a silent ignore, or a throw. One implementation now lives in
|
|
86
|
+
`adapters/core/cfl-value.js` and both adapters re-export it: it returns `Row | null`, and
|
|
87
|
+
**throws** a `TypeError` on an array instead of keeping its first row, because quietly dropping
|
|
88
|
+
the rest is how a `multiple` added later goes unnoticed. Blazor never needed it — multi-select
|
|
89
|
+
there is a separate `Values` / `ValuesChanged` pair.
|
|
90
|
+
```ts
|
|
91
|
+
import { singleRow } from '@cahyo-dimas/freeday/vue'; // or '@cahyo-dimas/freeday/react'
|
|
92
|
+
// (value: CflRow | CflRow[] | null) => CflRow | null
|
|
93
|
+
const onUpdate = (value: CflRow | CflRow[] | null): void => { picked.value = singleRow(value); };
|
|
94
|
+
```
|
|
95
|
+
### Changed: BREAKING (types)
|
|
96
|
+
- **A retroactive entry for 1.29.0 and 1.42.0** (#045) — nothing changed in this release; this is
|
|
97
|
+
the record those two are missing. Both widened `FdyCfl`'s emitted type — `Row` → `Row | null`
|
|
98
|
+
(1.29.0, with `clearable`) → `Row | Row[] | null` (1.42.0, with `multiple`) — and both shipped
|
|
99
|
+
under **Added**, which is honest from the kit's side and useless from the consumer's: the
|
|
100
|
+
combined effect is `vue-tsc` failing in a handler nobody touched. An app upgrading from before
|
|
101
|
+
1.29.0 meets both at once, which is exactly how it was reported. Migration is `singleRow()`
|
|
102
|
+
above. The policy that should stop a third instance is at the top of this file.
|
|
103
|
+
### Added: guards
|
|
104
|
+
- **The typed column contract is now pinned in both directions** (#040): `test/docs.test.mjs`
|
|
105
|
+
asserts the field list documented in `COMPONENTS.md` matches `FdyTableColumn` in
|
|
106
|
+
`adapters/core/table-model.d.ts` — a field in the type but not the docs fails, and so does a
|
|
107
|
+
field in the docs that the type does not have. Verified by mutation (drop `labelHidden` from the
|
|
108
|
+
table → red; invent a `sticky` row → red), since a guard nobody has watched fail is only a
|
|
109
|
+
green tick.
|
|
110
|
+
- `test/cfl-value.test.mjs`: three tests for `singleRow` (row passes through, `null`/`undefined`
|
|
111
|
+
become null, an array throws).
|
|
112
|
+
- **Every typed wrapper's props are pinned to their documentation** (`NEXT-UP.md` #11). One guard
|
|
113
|
+
reads all twelve `### Props — <FdyX>` tables and compares them with the Vue and React
|
|
114
|
+
declarations: a prop missing from a table fails, a documented prop neither adapter declares
|
|
115
|
+
fails, and a wrapper exported with no table at all fails. Mutation-verified in all three
|
|
116
|
+
directions, including the case that found a bug in the guard itself — a hyphenated prop name
|
|
117
|
+
(`aria-label`) was invisible to the first version of its parser.
|
|
118
|
+
### Docs
|
|
119
|
+
- **`COMPONENTS.md` finally states the typed column contract** (#040). §Data table gained
|
|
120
|
+
"Columns — the typed `FdyTableColumn<T>`": all ten fields with their types, the Blazor spelling
|
|
121
|
+
(`PascalCase`, `required` Key/Label, the extra `Cell` slot), and the row-controls example.
|
|
122
|
+
`agent-onboarding.md` tells every consuming agent this file is closed — "if a class is not in
|
|
123
|
+
that file, it does not exist" — while three of the four stacks build a table only through
|
|
124
|
+
`columns`, whose shape appeared nowhere in it. One reporting app shipped `label: ''` on its
|
|
125
|
+
actions column as a result: a `<th>` announced as nothing.
|
|
126
|
+
- **Nine enhancers still claimed to be Indonesian.** The comment above every `TEXT` table read
|
|
127
|
+
"User-facing strings. Indonesian by default" — written before 2.0.0 flipped them all to English,
|
|
128
|
+
and shipped in `dist/`, where it is the first thing a consumer reading the source sees. It now
|
|
129
|
+
says English, names `data-fdy-text-<key>` as the override, and gives an Indonesian app on the
|
|
130
|
+
raw path as the example of who overrides.
|
|
131
|
+
- **The two docs a consumer actually reads on upgrade now mention the language.** 2.0.0's breaking
|
|
132
|
+
change was documented in `COMPONENTS.md` and this file, but neither `docs/getting-started.md` nor
|
|
133
|
+
the block projects paste into their agent instructions
|
|
134
|
+
(`docs/agent-onboarding.md`) said a word about it. Both now do, with `data-fdy-text-<key>` as the
|
|
135
|
+
whole migration and `<html lang>` for date names.
|
|
136
|
+
- **`HANDOFF.md` described a release that had already happened.** It stated npm was on 1.53.0 with
|
|
137
|
+
1.54.0 and 2.0.0 sitting unpushed; npm had been on 2.0.0 since the morning, with the tag, the
|
|
138
|
+
branch and `origin/main` all on the same commit. The file that warns about stale snapshots was
|
|
139
|
+
the stale snapshot. Corrected, with the three commands that check it.
|
|
140
|
+
- **`CLAUDE.md` claimed 44 components**, a number matching nothing countable. Now 48 stylesheets in
|
|
141
|
+
`src/components/` and 26 enhancers in `src/freeday-*.js`, both of which anyone can re-count.
|
|
142
|
+
- **The typed props of all twelve wrappers are now in `COMPONENTS.md`** (`NEXT-UP.md` #11, the
|
|
143
|
+
general form of #040). A `### Props` table per wrapper: Vue and React names together, what each
|
|
144
|
+
does, and the Blazor differences stated rather than implied. Writing them measured something
|
|
145
|
+
nobody had: **four Blazor pickers are far thinner than their JS twins** — `FdyDatepicker` takes 6
|
|
146
|
+
parameters against 21 Vue props, `FdyAutocomplete` 6 against 11, `FdyCascade` 8 against 12, and
|
|
147
|
+
`FdyCombo` has no `Describedby`. Those wrappers render a seed element and do not splat unmatched
|
|
148
|
+
attributes, so `disabled`, `readonly`, `invalid`, the a11y ids, `clearable` and the datepicker's
|
|
149
|
+
ten navigation labels cannot be reached from a Blazor page at all. It runs the other way too:
|
|
150
|
+
`CloseLabel` exists only in Blazor, so the modal and drawer × is fixed at `Close` in Vue and
|
|
151
|
+
React. Filed as `NEXT-UP.md` #12 with its trigger; documented here as fact in the meantime,
|
|
152
|
+
because a consumer meeting it should find it written down rather than discover it.
|
|
153
|
+
- **`FdyAppShell` was missing from every list of the typed wrappers but the two READMEs.** It
|
|
154
|
+
shipped as the eleventh in 1.54.0 and `COMPONENTS.md`, `getting-started.md`, `agent-onboarding.md`
|
|
155
|
+
and `integrations.md` went on naming ten for a whole major version — `agent-onboarding.md`
|
|
156
|
+
saying both "ten" and "eleven" about the same set, in the same file, and `getting-started.md`
|
|
157
|
+
carrying a section heading that still listed the seven of some earlier release. All corrected,
|
|
158
|
+
and a guard now reads any run of six or more wrapper names as an enumeration and fails if it is
|
|
159
|
+
incomplete. It found two of these itself, one of them a list this release had already touched.
|
|
160
|
+
|
|
6
161
|
## [2.0.0] - 2026-08-25
|
|
7
162
|
### Changed: BREAKING
|
|
8
163
|
- **The vanilla enhancers now write English by default** (`NEXT-UP.md` #6, owner's decision). Every
|
package/COMPONENTS.md
CHANGED
|
@@ -36,7 +36,7 @@ live docs.
|
|
|
36
36
|
alert, breadcrumb, timeline, accordion, tree-without-cascade…) are CSS-only.
|
|
37
37
|
7. **On Vue, React or Blazor, eleven components have a typed wrapper. Use it.** `FdyCombo` ·
|
|
38
38
|
`FdyDatepicker` · `FdyDateRange` · `FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart` ·
|
|
39
|
-
`FdyTable` · `FdyModal` · `FdyDrawer`, from `@cahyo-dimas/freeday/vue`, `/react`, or the
|
|
39
|
+
`FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`, from `@cahyo-dimas/freeday/vue`, `/react`, or the
|
|
40
40
|
`Freeday.Blazor` RCL. Each is flagged at its own section below. Hand-writing their raw markup in
|
|
41
41
|
those stacks *looks* right at first, because the enhancer initialises once and the first render is
|
|
42
42
|
correct. Then
|
|
@@ -117,6 +117,25 @@ is a no-op.
|
|
|
117
117
|
## App shell — `.fdy-app`
|
|
118
118
|
> **Typed wrapper: `<FdyAppShell>`**. Vue (`v-model:navOpen`) · React (`navOpen`/`onNavOpenChange`) · Blazor (`@bind-NavOpen`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
119
119
|
|
|
120
|
+
### Props — `<FdyAppShell>`
|
|
121
|
+
|
|
122
|
+
Vue and React names, `?` marking optional. Vue takes the regions as **slots** of the same names,
|
|
123
|
+
which is why React's list is longer while offering the same thing.
|
|
124
|
+
|
|
125
|
+
| Prop | Type | What it does |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `navOpen?` | `boolean` | Whether the nav is visible. Optional because the default is the **viewport's**, and a caller cannot state that in one initial value. Vue binds `v-model:navOpen`. |
|
|
128
|
+
| `onNavOpenChange?` | `(open: boolean) => void` | React. Visibility changed: the toggle, Escape, the backdrop, or a followed nav item. Vue emits `update:navOpen`. |
|
|
129
|
+
| `title?` | `string` (Vue) · `ReactNode` | Topbar title. |
|
|
130
|
+
| `toggleLabel?` | `string` | Accessible name for the nav toggle button. |
|
|
131
|
+
| `toggleIcon?` | `ReactNode` | React. Replaces the default hamburger; Vue uses the slot of that name. |
|
|
132
|
+
| `skip?` | `ReactNode` | React. Skip link, rendered first in the tab order. |
|
|
133
|
+
| `brand?` · `nav?` · `topbar?` | `ReactNode` | React. Sidebar brand, sidebar nav, topbar contents. |
|
|
134
|
+
| `children?` | `ReactNode` | React. The page itself; Vue's default slot. |
|
|
135
|
+
|
|
136
|
+
Blazor takes the regions as `RenderFragment`s — `BrandContent`, `NavContent`, `TopbarContent`,
|
|
137
|
+
`SkipContent`, `TitleContent`, `ChildContent` — and binds with `@bind-NavOpen`.
|
|
138
|
+
|
|
120
139
|
The frame every application lives in. Do not hand-roll one from flexbox: the responsive sidebar,
|
|
121
140
|
off-canvas drawer and backdrop are built in.
|
|
122
141
|
|
|
@@ -422,6 +441,25 @@ Native inputs, styled. `.fdy-check` · `.fdy-radio` · `.fdy-switch` on the wrap
|
|
|
422
441
|
## Select / combobox — `.fdy-combo`
|
|
423
442
|
> **Typed wrapper: `<FdyCombo>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
424
443
|
|
|
444
|
+
### Props — `<FdyCombo>`
|
|
445
|
+
|
|
446
|
+
| Prop | Type | What it does |
|
|
447
|
+
|---|---|---|
|
|
448
|
+
| `modelValue` · `value` | `T extends string` | The selected value: `v-model` in Vue, `value` in React. |
|
|
449
|
+
| `onChange` | `(value: T) => void` | React. A selection was committed. Vue emits `update:modelValue` and `change`. |
|
|
450
|
+
| `options` | `ReadonlyArray<{ value: T; label: string }>` | The list. React exports the row type as `FdyComboOption<T>`. |
|
|
451
|
+
| `id?` | `string` | Explicit id for the combobox button; otherwise one is generated. |
|
|
452
|
+
| `ariaLabelledby?` | `string` | Id of the element that labels the combobox. |
|
|
453
|
+
| `placeholder?` | `string` | Shown when no option matches the current value. |
|
|
454
|
+
| `disabled?` | `boolean` | Greyed and out of the tab order, the native `disabled` semantics. |
|
|
455
|
+
| `readonly?` | `boolean` | Locked/view mode: keeps focus and tab order and shows its value, but cannot be opened or changed. Unlike `disabled` it is not greyed. |
|
|
456
|
+
| `invalid?` | `boolean` | Sets `aria-invalid`; pair it with `describedby` pointing at the error text. |
|
|
457
|
+
| `describedby?` | `string` | Id of the help or error text (`aria-describedby`). |
|
|
458
|
+
|
|
459
|
+
Blazor: the same names in `PascalCase`, bound with `@bind-Value` (`Value` / `ValueChanged`).
|
|
460
|
+
`FreedayCombo.setState(root, { disabled, readonly, invalid })` pushes a state change onto a combo
|
|
461
|
+
whose host has stopped re-rendering.
|
|
462
|
+
|
|
425
463
|
Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
426
464
|
|
|
427
465
|
- `.fdy-combo` (+`--error`, `--no-icon`) · `__button` `__value` (+`--placeholder`) `__listbox`
|
|
@@ -452,6 +490,23 @@ keeps matching after selection. Do not put a glyph in that span.
|
|
|
452
490
|
## Autocomplete — `.fdy-autocomplete`
|
|
453
491
|
> **Typed wrapper: `<FdyAutocomplete>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
454
492
|
|
|
493
|
+
### Props — `<FdyAutocomplete>`
|
|
494
|
+
|
|
495
|
+
| Prop | Type | What it does |
|
|
496
|
+
|---|---|---|
|
|
497
|
+
| `modelValue` · `value` | `string` | The typed text. Free text: the list suggests, it does not constrain. |
|
|
498
|
+
| `onChange` · `onSelect` | `(value: string) => void` | React. `onChange` on every keystroke, `onSelect` only when a suggestion is taken. Vue emits `update:modelValue` and `select`. |
|
|
499
|
+
| `options` | `ReadonlyArray<string>` | The suggestions to filter. |
|
|
500
|
+
| `emptyText?` | `string` | Shown when nothing matches. |
|
|
501
|
+
| `placeholder?` | `string` | |
|
|
502
|
+
| `id?` · `ariaLabel?` · `ariaLabelledby?` · `describedby?` | `string` | Input id; its accessible name as text or as a reference; the help/error text it points at. |
|
|
503
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps focus and tab order and shows its value, but the input is not editable and the list will not open. Unlike `disabled` it is not greyed. |
|
|
504
|
+
|
|
505
|
+
Blazor carries all of these. The states live on the input natively, and
|
|
506
|
+
`FreedayAutocomplete.setState(root, { disabled, readonly, invalid })` changes them after the first
|
|
507
|
+
render. A `readonly` input no longer opens its suggestion list, which it used to do over a field
|
|
508
|
+
nobody could edit.
|
|
509
|
+
|
|
455
510
|
Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
456
511
|
|
|
457
512
|
- `.fdy-autocomplete` · `__listbox` `__option` `__empty`
|
|
@@ -461,6 +516,25 @@ Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
|
461
516
|
## Cascade select — `.fdy-cascade`
|
|
462
517
|
> **Typed wrapper: `<FdyCascade>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
463
518
|
|
|
519
|
+
### Props — `<FdyCascade>`
|
|
520
|
+
|
|
521
|
+
| Prop | Type | What it does |
|
|
522
|
+
|---|---|---|
|
|
523
|
+
| `modelValue` · `value` | `string` | The selected **leaf** value; `''` means nothing selected. |
|
|
524
|
+
| `onChange` | `(value: string, labels: string[]) => void` | React. Vue emits `update:modelValue` and `change`. `labels` is the whole path, root → leaf. |
|
|
525
|
+
| `options` | `ReadonlyArray<CascadeNode>` | The tree. `CascadeNode` = `{ label, value, children? }`; a node **with** children is a branch, one without is selectable. |
|
|
526
|
+
| `separator?` | `string` | What joins the path in the display. Default `" / "`, matching the enhancer. |
|
|
527
|
+
| `backLabel?` | `string` | Accessible name for the up-one-level button. Default `Back one level`. |
|
|
528
|
+
| `label?` | `string` | Accessible name for the trigger and its listbox. |
|
|
529
|
+
| `placeholder?` | `string` | |
|
|
530
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
531
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | As on `<FdyCombo>`. |
|
|
532
|
+
|
|
533
|
+
Raw path: `data-disabled` / `data-readonly` / `data-invalid`, `data-id`, `data-describedby`, and
|
|
534
|
+
`FreedayCascade.setState(root, { disabled, readonly, invalid })` for a host that rendered once.
|
|
535
|
+
Blazor calls the tree `Nodes`, adds `SubmenuLabel`, and carries everything else except
|
|
536
|
+
`ariaLabelledby` — the trigger it builds is named by `Label`.
|
|
537
|
+
|
|
464
538
|
Hierarchical drill-down. The data model is a **nested `<ul>`** inside the wrapper: an `<li>` with a
|
|
465
539
|
child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
466
540
|
|
|
@@ -482,6 +556,37 @@ child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
|
482
556
|
## Choose-from-list (CFL) — `data-fdy-cfl`
|
|
483
557
|
> **Typed wrapper: `<FdyCfl>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
484
558
|
|
|
559
|
+
### Props — `<FdyCfl>`
|
|
560
|
+
|
|
561
|
+
Four of these are required and carry the whole component: `fetchPage`, `columns`, `display`,
|
|
562
|
+
`rowKey`. The rest are copy and state.
|
|
563
|
+
|
|
564
|
+
| Prop | Type | What it does |
|
|
565
|
+
|---|---|---|
|
|
566
|
+
| `modelValue` · `value` | `Row \| Row[] \| null` | The picked row. An array is reachable only under `multiple`, `null` only under `clearable`; narrow it with `singleRow()` (see the bullet below the raw-path hooks). |
|
|
567
|
+
| `onChange` | `(value: Row \| Row[] \| null) => void` | React. Vue emits `update:modelValue` and `change`. |
|
|
568
|
+
| `fetchPage` | `(query: string, page: number) => Promise<CflPage<Row>>` | The only data source — `{ rows, hasMore }`. Called on open, on search, and on Load more. Server state, never a global store. |
|
|
569
|
+
| `columns` | `ReadonlyArray<CflColumn<Row>>` | `{ key, label }` per dialog column. |
|
|
570
|
+
| `display` | `(row: Row) => string` | What the closed field shows once a row is picked. |
|
|
571
|
+
| `rowKey` | `(row: Row) => string` | Row identity: ticks in multi mode, and seeding them when the dialog re-opens. |
|
|
572
|
+
| `pageSize?` | `number` | Advisory only. Your `fetchPage` owns paging; this documents the intent. |
|
|
573
|
+
| `title?` | `string` | The dialog's heading. Default `Choose data`. |
|
|
574
|
+
| `searchPlaceholder?` | `string` | Placeholder **and** accessible name of the search box. Default `Search…`. |
|
|
575
|
+
| `loadingText?` · `emptyText?` · `retryText?` · `moreText?` | `string` | Dialog states. Defaults `Loading…`, `No results.`, `Try again`, `Load more`. |
|
|
576
|
+
| `closeLabel?` · `openLabel?` | `string` | Accessible names for the dialog's × and the field's open button. Defaults `Close`, `Open search`. |
|
|
577
|
+
| `multiple?` | `boolean` | Tick rows and commit them together on Confirm instead of committing the clicked row, and widen the value to an array. |
|
|
578
|
+
| `selectedText?` · `confirmText?` · `hintText?` | `string` | Footer copy. Defaults `{n} selected` (with `{n}` substituted), `Confirm`, `Click a row to choose it`. |
|
|
579
|
+
| `placeholder?` | `string` | Field placeholder while nothing is picked. |
|
|
580
|
+
| `clearable?` | `boolean` | A clear button, so an **optional** foreign key can be unset. Without it the value type admits a `null` the component can never produce. |
|
|
581
|
+
| `clearLabel?` | `string` | Accessible name for that button. Default `Clear selection`. |
|
|
582
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps the picked value visible, focusable and copyable, but the dialog cannot be opened. |
|
|
583
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Field id; the element that labels it; the help/error text it points at. |
|
|
584
|
+
|
|
585
|
+
Blazor carries the same surface in `PascalCase` with three differences: multi-select is a second
|
|
586
|
+
pair (`Values` / `ValuesChanged`) rather than a widened single binding, the fetch delegate is
|
|
587
|
+
called `LoadPage`, and it has two parameters the JS adapters do not — `ErrorText` and
|
|
588
|
+
`SearchDebounceMs`.
|
|
589
|
+
|
|
485
590
|
A read-only field backed by master data: the button opens a searchable dialog, the picked row fills
|
|
486
591
|
the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
487
592
|
|
|
@@ -498,6 +603,13 @@ the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
|
498
603
|
`ValuesChanged`, because a nullable union is not a C# shape. Closing without Confirm leaves the
|
|
499
604
|
bound value untouched, and re-opening seeds the ticks from it. The field states `{n} selected`
|
|
500
605
|
rather than one row's `display()`. Strings: `selectedText`, `confirmText`, `hintText`.
|
|
606
|
+
- **A single-select in Vue or React needs one narrowing, and the kit ships it.** The emitted value
|
|
607
|
+
is typed `Row | Row[] | null` whatever the props are, so a single-select handler cannot take it
|
|
608
|
+
as `Row`: the array is reachable only under `multiple`, the null only under `clearable`, and the
|
|
609
|
+
type cannot say which. Import the guard instead of writing one per app:
|
|
610
|
+
`import { singleRow } from '@cahyo-dimas/freeday/vue'` (or `/react`). It returns `Row | null`,
|
|
611
|
+
and throws if a `multiple` field's array ever reaches it, rather than silently keeping the first
|
|
612
|
+
row. Blazor is unaffected: multi-select there is a separate `Values` / `ValuesChanged` pair.
|
|
501
613
|
|
|
502
614
|
```html
|
|
503
615
|
<div class="fdy-field">
|
|
@@ -547,6 +659,52 @@ a new class.
|
|
|
547
659
|
## Date picker — `data-fdy-datepicker`
|
|
548
660
|
> **Typed wrapper: `<FdyDatepicker>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
549
661
|
|
|
662
|
+
### Props — `<FdyDatepicker>`
|
|
663
|
+
|
|
664
|
+
| Prop | Type | What it does |
|
|
665
|
+
|---|---|---|
|
|
666
|
+
| `modelValue` · `value` | `string \| null` | ISO `YYYY-MM-DD`, or `''`/`null` for empty. |
|
|
667
|
+
| `onChange` | `(value: string) => void` | React. Vue emits `update:modelValue` and `change`. Clearing sends `''`. |
|
|
668
|
+
| `min?` · `max?` | `string` | ISO bounds; days outside them cannot be picked. |
|
|
669
|
+
| `locale?` | `string` | BCP-47 tag deciding month and weekday names. Defaults to the page's `<html lang>`, then `en`. |
|
|
670
|
+
| `placeholder?` | `string` | |
|
|
671
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
672
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps focus and tab order and shows its date, but it cannot be opened, cleared or changed. |
|
|
673
|
+
| `clearable?` | `boolean` | Show a × in the trigger once a date is set, so an optional date can be unset. Sends `''`. Off by default. |
|
|
674
|
+
| `clearLabel?` | `string` | Accessible name for that ×. Default `Clear date`. |
|
|
675
|
+
| `prevMonthLabel?` · `nextMonthLabel?` | `string` | The month arrows. Defaults `Previous month` / `Next month`. |
|
|
676
|
+
| `chooseMonthLabel?` · `chooseYearLabel?` | `string` | The title buttons that drill into the month and year grids. Defaults `Choose month` / `Choose year`. |
|
|
677
|
+
| `prevYearLabel?` · `nextYearLabel?` | `string` | Year arrows, shown in the month grid. Defaults `Previous year` / `Next year`. |
|
|
678
|
+
| `prevYearsLabel?` · `nextYearsLabel?` | `string` | Page arrows, shown in the year grid. Defaults `Previous years` / `Next years`. |
|
|
679
|
+
|
|
680
|
+
The ten label props exist because month and weekday names follow `locale` while the buttons around
|
|
681
|
+
them do not: without these, a Spanish calendar would be navigated by English arrows.
|
|
682
|
+
|
|
683
|
+
On the **raw path** these all have hooks now: `data-disabled` / `data-readonly` / `data-invalid`,
|
|
684
|
+
`data-id`, `data-describedby`, and one `data-fdy-text-<key>` per label above
|
|
685
|
+
(`data-fdy-text-prev-month`, …). A host that renders its markup once and cannot re-render it can
|
|
686
|
+
change the three states afterwards with `FreedayDatepicker.setState(root, { disabled, readonly,
|
|
687
|
+
invalid })`.
|
|
688
|
+
|
|
689
|
+
Blazor carries the same surface bar four: `locale` (the enhancer formats through `Intl` from the
|
|
690
|
+
page's `<html lang>`), `clearable` and `clearLabel` (this path builds no clear button), and
|
|
691
|
+
`ariaLabelledby` (the trigger it builds is named by `Label`).
|
|
692
|
+
|
|
693
|
+
### Props — `<FdyDateRange>`
|
|
694
|
+
|
|
695
|
+
| Prop | Type | What it does |
|
|
696
|
+
|---|---|---|
|
|
697
|
+
| `modelValue` · `value` | `DateRangeValue` = `{ start, end }`, each `string \| null` | Both ends at once, so the pair can never be half-applied. |
|
|
698
|
+
| `onChange` | `(value: DateRangeValue) => void` | React. Vue emits `update:modelValue` and `change`. |
|
|
699
|
+
| `min?` · `max?` | `string` | ISO bounds for both ends. The end can never precede the start. |
|
|
700
|
+
| `locale?` | `string` | As on the single picker. |
|
|
701
|
+
| `startPlaceholder?` · `endPlaceholder?` | `string` | One per field. |
|
|
702
|
+
| `ariaLabel?` · `ariaLabelledby?` · `describedby?` | `string` | Names the group (`role="group"`), or points at the text that does. |
|
|
703
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | Applied to both pickers together. |
|
|
704
|
+
|
|
705
|
+
Blazor splits the value into two scalars, `From` / `FromChanged` and `To` / `ToChanged`, because a
|
|
706
|
+
C# binding cannot express one object bound in two places, and it adds `Separator`.
|
|
707
|
+
|
|
550
708
|
Input-styled trigger + calendar popover. Needs `freeday-datepicker.js`. **Author an empty `<div>`**
|
|
551
709
|
The enhancer builds everything.
|
|
552
710
|
|
|
@@ -772,6 +930,75 @@ sum, so measure them once after render (and on resize) and set the variable.
|
|
|
772
930
|
## Data table — `.fdy-datatable`
|
|
773
931
|
> **Typed wrapper: `<FdyTable>`**. controlled: `columns` + `rows`, with sort/filter/page events (`update:pageIndex` · `onPageIndexChange` · `PageIndexChanged`) and `process` for driving a card list off the same processed set. The markup below is the raw enhancer path.
|
|
774
932
|
|
|
933
|
+
### Columns — the typed `FdyTableColumn<T>`
|
|
934
|
+
|
|
935
|
+
On Vue, React and Blazor, `columns` **is** the table: it is what those stacks write instead of the
|
|
936
|
+
markup hooks further down. Ten fields, `key` and `label` required. The authored doc comments live on
|
|
937
|
+
the type itself (`adapters/core/table-model.d.ts`, re-declared for Blazor in
|
|
938
|
+
`adapters/blazor/TableTypes.cs`); the list below is the discoverable copy, and `npm test` fails if
|
|
939
|
+
the two ever disagree.
|
|
940
|
+
|
|
941
|
+
| Field | Type | What it does |
|
|
942
|
+
|---|---|---|
|
|
943
|
+
| `key` | `string` | Row property key; the sort/filter identity and, unless `value` is set, the cell accessor. |
|
|
944
|
+
| `label` | `string` | Header label. |
|
|
945
|
+
| `labelHidden` | `boolean` | Render the label for assistive tech only; the header cell looks empty. |
|
|
946
|
+
| `sortable` | `boolean` | Show a sort toggle in the header. |
|
|
947
|
+
| `filter` | `'text' \| 'enum' \| 'number' \| 'date'` | Offer a column filter of this type (the funnel popover). |
|
|
948
|
+
| `align` | `'left' \| 'right' \| 'center'` | Cell alignment. Default `left`. |
|
|
949
|
+
| `mono` | `boolean` | Render cells in the monospace data font (`.fdy-mono`). |
|
|
950
|
+
| `sortType` | `'text' \| 'number' \| 'date'` | Override the comparator; defaults to one derived from `filter`, else `text`. |
|
|
951
|
+
| `value` | `(row: T) => unknown` | Custom accessor; defaults to `row[key]`. Feeds sort, filter and the default cell text. |
|
|
952
|
+
| `options` | `readonly string[]` | Explicit enum-filter options; they default to the distinct values in the rows on screen, so pass them in server-paged mode, where those are one page. |
|
|
953
|
+
|
|
954
|
+
Blazor spells the same fields `PascalCase` (`LabelHidden`, `SortType`), makes `Key`/`Label`
|
|
955
|
+
`required`, and adds one field with no TS twin: `Cell`, a `RenderFragment<TRow>` where Vue and React
|
|
956
|
+
use a slot.
|
|
957
|
+
|
|
958
|
+
A column of row controls is worth stating outright, because the obvious move is the wrong one:
|
|
959
|
+
|
|
960
|
+
```ts
|
|
961
|
+
const columns: FdyTableColumn<User>[] = [
|
|
962
|
+
{ key: 'email', label: 'Email', sortable: true, filter: 'text' },
|
|
963
|
+
{ key: 'total', label: 'Total', align: 'right', mono: true, sortType: 'number' },
|
|
964
|
+
// Not `label: ''`. A <th> with no text announces as nothing, and the label also names this
|
|
965
|
+
// column's sort button and filter popover, so it has to stay meaningful.
|
|
966
|
+
{ key: 'actions', label: 'Actions', labelHidden: true },
|
|
967
|
+
];
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
### Props — `<FdyTable>`
|
|
971
|
+
|
|
972
|
+
Controlled by default: hand it `columns` + `rows` and it filters, sorts and paginates internally;
|
|
973
|
+
provide the state prop for a concern and you own that concern instead. React names the callbacks
|
|
974
|
+
`onXChange`, Vue emits `update:x`, Blazor exposes `XChanged`.
|
|
975
|
+
|
|
976
|
+
| Prop | Type | What it does |
|
|
977
|
+
|---|---|---|
|
|
978
|
+
| `columns` | `ReadonlyArray<FdyTableColumn<Row>>` | The column contract documented above. |
|
|
979
|
+
| `rows` | `ReadonlyArray<Row>` | The current page's rows in server mode, the whole set in client mode. |
|
|
980
|
+
| `rowKey` | `(row: Row) => string \| number` | Row identity: keys, expansion, activation. |
|
|
981
|
+
| `sort?` · `onSortChange?` | `FdySortState \| null` · `(sort) => void` | Pass `sort`, **even as `null`**, to own sorting; omit it for the internal sort. |
|
|
982
|
+
| `filters?` · `onFiltersChange?` | `FdyFilterMap` · `(filters) => void` | The same, for filtering. |
|
|
983
|
+
| `page?` · `onPageChange?` | `FdyPageState` · `(page) => void` | Its **presence switches the table into server mode**: you own paging and get `{ index, size, total }` back. |
|
|
984
|
+
| `pageSize?` | `number` | Client mode: rows per page. Absent or 0 renders every row with no pager. |
|
|
985
|
+
| `pageIndex?` · `onPageIndexChange?` | `number` · `(index) => void` | Client mode: own the 0-based index while the table keeps filtering, sorting and paginating — this is what lets one external pager drive both a table and a card list. |
|
|
986
|
+
| `pageSizes?` · `onPageSizeChange?` | `readonly number[]` · `(size) => void` | Offer a rows-per-page control. Server mode reports the pick through the page event (read its `size`); client mode applies it itself and reports it too, so it works with nothing wired. |
|
|
987
|
+
| `pager?` | `boolean` | Default true. Turn it off to withhold the table's own footer when the screen renders one. |
|
|
988
|
+
| `loading?` | `boolean` | Show the in-table loading state. |
|
|
989
|
+
| `emptyText?` · `empty?` | `string` · `ReactNode` | The empty state as text, or as markup (React `empty`, Vue's `empty` slot). |
|
|
990
|
+
| `ariaLabel?` | `string` | Names the table for assistive tech. |
|
|
991
|
+
| `rowActivatable?` · `onRowActivate?` | `boolean` · `(row) => void` | Rows become focusable and activate on click, Enter or Space. |
|
|
992
|
+
| `rowClass?` | `(row: Row) => string \| undefined` | Per-row class hook, e.g. marking the selected row. |
|
|
993
|
+
| `expandedKeys?` · `renderRowDetail?` | `ReadonlyArray<string \| number>` · `(row) => ReactNode` | Controlled expansion: these keys get a full-width detail row. Vue uses the `row-detail` slot. |
|
|
994
|
+
| `renderCell?` | `(column, row, value) => ReactNode` | React. Custom cell rendering; Vue uses the `cell` slot. |
|
|
995
|
+
| `toolbar?` | `ReactNode` | React. Content above the table; Vue uses the `toolbar` slot. |
|
|
996
|
+
| `onProcess?` | `(result: { rows, total }) => void` | React. The processed page after filter/sort/paginate, in **both** modes. Vue emits `process`, Blazor calls it `Process`. Render the same set elsewhere — a card list, a summary, a CSV export — without re-deriving the pipeline. |
|
|
997
|
+
|
|
998
|
+
Blazor matches this surface (`LoadingText` and `EmptyContent` in place of `emptyText`/`empty`,
|
|
999
|
+
`Toolbar` and `RowDetail` as `RenderFragment`s) and is the one adapter that also exposes
|
|
1000
|
+
`FiltersChanged`.
|
|
1001
|
+
|
|
775
1002
|
The interactive table: global search, sort, per-column filters, row selection + bulk bar,
|
|
776
1003
|
pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` + `data-fdy-table`
|
|
777
1004
|
(`data-page-size="N"`).
|
|
@@ -822,7 +1049,11 @@ pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` +
|
|
|
822
1049
|
(`data-fdy-text-required`, `data-fdy-text-type`, …), narrower than the per-field
|
|
823
1050
|
`data-fdy-msg-<alias>` that still wins. `Freeday.toast()` takes `closeLabel` in its options
|
|
824
1051
|
object. `npm test` asserts no enhancer string is written outside its `TEXT` table, so a new one
|
|
825
|
-
arrives overridable or not at all.
|
|
1052
|
+
arrives overridable or not at all — a claim that was only half true until **2.2.0**, when the
|
|
1053
|
+
guard stopped looking for the line that writes to the DOM and started reading every literal in
|
|
1054
|
+
the file. The old shape could not see a string handed to a helper, which is how ten datepicker
|
|
1055
|
+
labels, the timepicker's, the cascade's two defaults, the table's `Min`/`Maks` filter
|
|
1056
|
+
placeholders and the chart's `Seri 1` all sat outside it.
|
|
826
1057
|
|
|
827
1058
|
**Testing note:** a column's filter button and the dialog it opens deliberately share one
|
|
828
1059
|
accessible name (`Filter <column>`), since a dialog named after its trigger is the normal pattern. In a
|
|
@@ -849,6 +1080,14 @@ ninety-row list would remove the only way back to twenty.
|
|
|
849
1080
|
of one page of rows. A footer inside the table is inside the half a phone hides, so those screens
|
|
850
1081
|
render it once, outside both, with `pager={false}` on the table.
|
|
851
1082
|
|
|
1083
|
+
### Props — `<FdyTableFooter>`
|
|
1084
|
+
|
|
1085
|
+
| Prop | Type | What it does |
|
|
1086
|
+
|---|---|---|
|
|
1087
|
+
| `page` | `FdyPageState` = `{ index, size, total }` | The page being shown. `size` drives both the range text and the rows-per-page control's value. |
|
|
1088
|
+
| `onPageChange?` | `(page: FdyPageState) => void` | React. A page click or a size change. Vue emits `update:page`; Blazor `PageChanged`. |
|
|
1089
|
+
| `pageSizes?` | `readonly number[]` | Offer a rows-per-page control. Omit it and the footer is range + pager only. Picking a size reports the new `size` together with the index that still holds the row the reader was looking at. |
|
|
1090
|
+
|
|
852
1091
|
**Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
|
|
853
1092
|
than one page. Two ways to take it over:
|
|
854
1093
|
|
|
@@ -879,6 +1118,25 @@ replaces.
|
|
|
879
1118
|
## Charts — `data-fdy-chart`
|
|
880
1119
|
> **Typed wrapper: `<FdyChart>`**. data props in all three (`type` + `values` / `series`); it repaints on data change, so `FreedayChart.update(el)` is only for the raw path below.
|
|
881
1120
|
|
|
1121
|
+
### Props — `<FdyChart>`
|
|
1122
|
+
|
|
1123
|
+
| Prop | Type | What it does |
|
|
1124
|
+
|---|---|---|
|
|
1125
|
+
| `type` | `'line' \| 'area' \| 'bar' \| 'sparkline' \| 'donut'` | Which chart to draw. |
|
|
1126
|
+
| `values?` | `ReadonlyArray<number>` | One series. |
|
|
1127
|
+
| `series?` | `ReadonlyArray<FdyChartSeries>` | Several: each `{ label, values }`. Use one or the other, not both. |
|
|
1128
|
+
| `labels?` | `ReadonlyArray<string>` | Category labels: the x axis, or the donut's slices. |
|
|
1129
|
+
| `format?` | `'number' \| 'percent' \| 'currency'` | How values are written in labels and the donut centre. |
|
|
1130
|
+
| `stacked?` | `boolean` | Bar charts: stack the series instead of grouping them side by side. |
|
|
1131
|
+
| `legend?` | `'auto' \| 'always' \| 'none'` | `auto` shows one when there is more than one series. |
|
|
1132
|
+
| `colors?` | `ReadonlyArray<string>` | Per-series override. Each entry is a semantic token name (`primary`, `accent`, `success`, `warning`, `danger`, `info`) or a categorical slot `chart-1`…`chart-8`. Omit for the default fixed-order palette. |
|
|
1133
|
+
| `color?` | `string` | The same, for a single-series chart (sparkline, simple bar or line). |
|
|
1134
|
+
| `center?` | `string \| number` | Donut only: what to print in the middle. |
|
|
1135
|
+
| `aria-label?` | `string` | React. The chart's accessible name, and in practice the **whole** text alternative: a named chart's painted subtree is `aria-hidden`, see the a11y note in this section. Vue and Blazor set it as a plain attribute (Blazor's parameter is `AriaLabel`). |
|
|
1136
|
+
| `children?` | `ReactNode` | React. Content inside the element before the chart paints. The renderer replaces it, so it is a pre-paint placeholder, not a text alternative. |
|
|
1137
|
+
|
|
1138
|
+
Blazor adds `ChildContent` for the same placeholder role.
|
|
1139
|
+
|
|
882
1140
|
Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`; call
|
|
883
1141
|
`FreedayChart.update(el)` after changing data (or use `FdyChart` in Vue/React/Blazor).
|
|
884
1142
|
|
|
@@ -889,6 +1147,10 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
889
1147
|
- Colour: `data-fdy-color="primary"` or `data-fdy-colors="success,warning,danger"` for semantic token
|
|
890
1148
|
names **or** `chart-1`…`chart-8` slots to pin a category's colour. Multi-series defaults to the
|
|
891
1149
|
validated categorical palette `--chart-1`…`--chart-8`.
|
|
1150
|
+
- **Its two strings are overridable**, and until 2.2.0 neither was: a series with no `label` fell
|
|
1151
|
+
back to `Seri 1` — Indonesian, three months after 2.0.0 turned the enhancers English — and the
|
|
1152
|
+
donut's centre caption was hard-coded. Both take the usual hook now:
|
|
1153
|
+
`data-fdy-text-series="Series {n}"` and `data-fdy-text-total="Total"`.
|
|
892
1154
|
- Format: `data-fdy-format="number|percent|currency"`; legend `data-fdy-legend` (`none` to drop);
|
|
893
1155
|
axes `data-fdy-axes`
|
|
894
1156
|
- Sizing is already set by the kit. Override **these**, never a `height` on the chart root (a
|
|
@@ -1076,6 +1338,23 @@ so the same look serves routed sub-navigation built from plain links. See the no
|
|
|
1076
1338
|
## Modal — `.fdy-modal`
|
|
1077
1339
|
> **Typed wrapper: `<FdyModal>`**. Vue (`:open` + `@close`) · React (`open` + `onClose`) · Blazor (`@bind-Open`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
1078
1340
|
|
|
1341
|
+
### Props — `<FdyModal>`
|
|
1342
|
+
|
|
1343
|
+
| Prop | Type | What it does |
|
|
1344
|
+
|---|---|---|
|
|
1345
|
+
| `open` | `boolean` | Controlled: your state decides. The wrapper cancels the native `cancel` event so the DOM can never disagree with it. |
|
|
1346
|
+
| `title` | `string` (Vue) · `ReactNode` | Names the dialog through `aria-labelledby`. Vue also offers a `title` slot when the heading needs markup. |
|
|
1347
|
+
| `onClose` | `() => void` | React. Vue emits `close`. Fires from Escape, a backdrop click and the ×, all three of which exist only under `dismissible`. |
|
|
1348
|
+
| `size?` | `'sm' \| 'md' \| 'lg' \| 'wide'` | Dialog width. |
|
|
1349
|
+
| `dismissible?` | `boolean` | Whether Escape, the backdrop and the × can close it. Off means the reader has to take a footer action, which is the point of a blocking confirm. |
|
|
1350
|
+
| `closeLabel?` | `string` | Accessible name for the × button. Default `Close`. |
|
|
1351
|
+
| `footer?` | `ReactNode` | React. Vue: the `footer` slot. |
|
|
1352
|
+
| `children?` | `ReactNode` | React. The body. Vue: the default slot. |
|
|
1353
|
+
|
|
1354
|
+
Blazor binds `@bind-Open` and takes `TitleContent` / `ChildContent` / `FooterContent`, plus
|
|
1355
|
+
`OnClose`. `CloseLabel` used to be Blazor-only, which left the × in Vue and React fixed at `Close`;
|
|
1356
|
+
all three stacks carry it now.
|
|
1357
|
+
|
|
1079
1358
|
Native `<dialog>`: focus trap, Esc and backdrop come from the browser. Sizes `--sm` `--md` `--lg`
|
|
1080
1359
|
`--wide`; `--cfl` for the choose-from-list dialog.
|
|
1081
1360
|
|
|
@@ -1111,6 +1390,23 @@ that is not the kit. Both rules measured on Chromium 133 and 151, and pinned as
|
|
|
1111
1390
|
## Drawer — `.fdy-drawer`
|
|
1112
1391
|
> **Typed wrapper: `<FdyDrawer>`**. Vue (`:open` + `@close`) · React (`open` + `onClose`) · Blazor (`@bind-Open`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
1113
1392
|
|
|
1393
|
+
### Props — `<FdyDrawer>`
|
|
1394
|
+
|
|
1395
|
+
The modal's props with `side` in place of `size`; the behaviour notes there apply here too.
|
|
1396
|
+
|
|
1397
|
+
| Prop | Type | What it does |
|
|
1398
|
+
|---|---|---|
|
|
1399
|
+
| `open` | `boolean` | Controlled, as with the modal. |
|
|
1400
|
+
| `title` | `string` (Vue) · `ReactNode` | Names the drawer (`aria-labelledby`). |
|
|
1401
|
+
| `onClose` | `() => void` | React. Vue emits `close`. |
|
|
1402
|
+
| `side?` | `'left' \| 'right'` | Which edge it slides from. |
|
|
1403
|
+
| `dismissible?` | `boolean` | Escape, backdrop and × on or off. |
|
|
1404
|
+
| `closeLabel?` | `string` | Accessible name for the × button. Default `Close`. |
|
|
1405
|
+
| `footer?` | `ReactNode` | React. Vue: the `footer` slot. |
|
|
1406
|
+
| `children?` | `ReactNode` | React. The body. Vue: the default slot. |
|
|
1407
|
+
|
|
1408
|
+
Blazor: `@bind-Open`, `TitleContent` / `ChildContent` / `FooterContent`, `OnClose`, `CloseLabel`.
|
|
1409
|
+
|
|
1114
1410
|
Temporary side panel on native `<dialog>`, left by default, `--right` to flip. Parts `__header`
|
|
1115
1411
|
`__title` `__body` `__footer` `__close`. Open it from any
|
|
1116
1412
|
`<button data-fdy-drawer="<dialog id>">`. Needs `freeday-drawer.js`.
|
package/README.id.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Lebih banyak _free day_ buat dev. UI kit-nya sudah siap pakai.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v2.2.0)
|
|
9
9
|
|
|
10
10
|
UI KIT yang token-driven & framework-agnostic. Satu sumber kebenaran untuk warna, tipografi,
|
|
11
11
|
spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **More free days for devs. The UI kit is ready to use.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v2.2.0)
|
|
9
9
|
|
|
10
10
|
A token-driven, framework-agnostic UI kit. One source of truth for color, typography,
|
|
11
11
|
spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
<div @ref="Root" data-fdy-autocomplete class="fdy-autocomplete">
|
|
9
9
|
<input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
|
|
10
10
|
aria-controls="@_listId" autocomplete="off" value="@Value"
|
|
11
|
-
placeholder="@Placeholder" aria-label="@AriaLabel"
|
|
11
|
+
placeholder="@Placeholder" aria-label="@AriaLabel"
|
|
12
|
+
id="@Id" aria-labelledby="@AriaLabelledby" aria-describedby="@Describedby"
|
|
13
|
+
disabled="@Disabled" readonly="@Readonly"
|
|
14
|
+
aria-invalid="@(Invalid ? "true" : null)" />
|
|
12
15
|
<ul class="fdy-autocomplete__listbox" id="@_listId" role="listbox" hidden>
|
|
13
16
|
@foreach (string item in Options)
|
|
14
17
|
{
|
|
@@ -25,8 +25,34 @@ public partial class FdyAutocomplete
|
|
|
25
25
|
protected override async ValueTask OnHydratedAsync()
|
|
26
26
|
=> await SubscribeAsync("fdy-autocomplete-select", nameof(OnSelect));
|
|
27
27
|
|
|
28
|
+
/// <summary>Explicit id for the input.</summary>
|
|
29
|
+
[Parameter] public string? Id { get; set; }
|
|
30
|
+
|
|
31
|
+
/// <summary>Id of the element that labels the input, when a visible label does the naming.</summary>
|
|
32
|
+
[Parameter] public string? AriaLabelledby { get; set; }
|
|
33
|
+
|
|
34
|
+
/// <summary>Id of the help or error text the input describes itself with.</summary>
|
|
35
|
+
[Parameter] public string? Describedby { get; set; }
|
|
36
|
+
|
|
37
|
+
/// <summary>Greyed and out of the tab order.</summary>
|
|
38
|
+
[Parameter] public bool Disabled { get; set; }
|
|
39
|
+
|
|
40
|
+
/// <summary>Locked/view mode: focusable and showing its value, not editable, and the
|
|
41
|
+
/// suggestion list will not open.</summary>
|
|
42
|
+
[Parameter] public bool Readonly { get; set; }
|
|
43
|
+
|
|
44
|
+
/// <summary>Marks the field invalid (<c>aria-invalid</c>).</summary>
|
|
45
|
+
[Parameter] public bool Invalid { get; set; }
|
|
46
|
+
|
|
28
47
|
protected override bool ShouldRender() => !Hydrated;
|
|
29
48
|
|
|
49
|
+
protected override async Task OnParametersSetAsync()
|
|
50
|
+
{
|
|
51
|
+
if (!Hydrated) return;
|
|
52
|
+
await JS.InvokeVoidAsync("FreedayAutocomplete.setState", Root,
|
|
53
|
+
new { disabled = Disabled, @readonly = Readonly, invalid = Invalid });
|
|
54
|
+
}
|
|
55
|
+
|
|
30
56
|
/// <summary>Invoked by the bridge when the user picks a suggestion.</summary>
|
|
31
57
|
[JSInvokable]
|
|
32
58
|
public async Task OnSelect(SelectDetail detail)
|