@cahyo-dimas/freeday 2.0.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.
- package/CHANGELOG.md +88 -0
- package/COMPONENTS.md +276 -1
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/core/cfl-value.d.ts +11 -0
- package/adapters/core/cfl-value.js +31 -0
- package/adapters/react/index.d.ts +3 -0
- package/adapters/react/index.js +1 -0
- package/adapters/vue/index.d.ts +3 -0
- package/adapters/vue/index.js +1 -0
- package/dist/freeday-carousel.js +5 -4
- package/dist/freeday-cascade.js +5 -4
- package/dist/freeday-cfl.js +5 -4
- package/dist/freeday-form.js +5 -4
- package/dist/freeday-mask.js +5 -4
- package/dist/freeday-stepper.js +5 -4
- package/dist/freeday-table.js +5 -4
- package/dist/freeday-toast.js +5 -4
- package/dist/freeday-upload.js +5 -4
- package/dist/freeday.js +45 -36
- package/docs/agent-onboarding.md +9 -6
- package/docs/getting-started.md +15 -6
- package/docs/integrations.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,94 @@
|
|
|
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.1.0] - 2026-08-25
|
|
13
|
+
### Added
|
|
14
|
+
- **`singleRow()`, exported from `@cahyo-dimas/freeday/vue` and `/react`** (#045). `FdyCfl` emits
|
|
15
|
+
`Row | Row[] | null` whatever its props are: the array is reachable only under `multiple`, the
|
|
16
|
+
null only under `clearable`, and the type cannot say which. So every single-select screen wrote
|
|
17
|
+
its own narrowing, and each one differently — a cast (which hides the day somebody adds
|
|
18
|
+
`clearable`), a silent ignore, or a throw. One implementation now lives in
|
|
19
|
+
`adapters/core/cfl-value.js` and both adapters re-export it: it returns `Row | null`, and
|
|
20
|
+
**throws** a `TypeError` on an array instead of keeping its first row, because quietly dropping
|
|
21
|
+
the rest is how a `multiple` added later goes unnoticed. Blazor never needed it — multi-select
|
|
22
|
+
there is a separate `Values` / `ValuesChanged` pair.
|
|
23
|
+
```ts
|
|
24
|
+
import { singleRow } from '@cahyo-dimas/freeday/vue'; // or '@cahyo-dimas/freeday/react'
|
|
25
|
+
// (value: CflRow | CflRow[] | null) => CflRow | null
|
|
26
|
+
const onUpdate = (value: CflRow | CflRow[] | null): void => { picked.value = singleRow(value); };
|
|
27
|
+
```
|
|
28
|
+
### Changed: BREAKING (types)
|
|
29
|
+
- **A retroactive entry for 1.29.0 and 1.42.0** (#045) — nothing changed in this release; this is
|
|
30
|
+
the record those two are missing. Both widened `FdyCfl`'s emitted type — `Row` → `Row | null`
|
|
31
|
+
(1.29.0, with `clearable`) → `Row | Row[] | null` (1.42.0, with `multiple`) — and both shipped
|
|
32
|
+
under **Added**, which is honest from the kit's side and useless from the consumer's: the
|
|
33
|
+
combined effect is `vue-tsc` failing in a handler nobody touched. An app upgrading from before
|
|
34
|
+
1.29.0 meets both at once, which is exactly how it was reported. Migration is `singleRow()`
|
|
35
|
+
above. The policy that should stop a third instance is at the top of this file.
|
|
36
|
+
### Added: guards
|
|
37
|
+
- **The typed column contract is now pinned in both directions** (#040): `test/docs.test.mjs`
|
|
38
|
+
asserts the field list documented in `COMPONENTS.md` matches `FdyTableColumn` in
|
|
39
|
+
`adapters/core/table-model.d.ts` — a field in the type but not the docs fails, and so does a
|
|
40
|
+
field in the docs that the type does not have. Verified by mutation (drop `labelHidden` from the
|
|
41
|
+
table → red; invent a `sticky` row → red), since a guard nobody has watched fail is only a
|
|
42
|
+
green tick.
|
|
43
|
+
- `test/cfl-value.test.mjs`: three tests for `singleRow` (row passes through, `null`/`undefined`
|
|
44
|
+
become null, an array throws).
|
|
45
|
+
- **Every typed wrapper's props are pinned to their documentation** (`NEXT-UP.md` #11). One guard
|
|
46
|
+
reads all twelve `### Props — <FdyX>` tables and compares them with the Vue and React
|
|
47
|
+
declarations: a prop missing from a table fails, a documented prop neither adapter declares
|
|
48
|
+
fails, and a wrapper exported with no table at all fails. Mutation-verified in all three
|
|
49
|
+
directions, including the case that found a bug in the guard itself — a hyphenated prop name
|
|
50
|
+
(`aria-label`) was invisible to the first version of its parser.
|
|
51
|
+
### Docs
|
|
52
|
+
- **`COMPONENTS.md` finally states the typed column contract** (#040). §Data table gained
|
|
53
|
+
"Columns — the typed `FdyTableColumn<T>`": all ten fields with their types, the Blazor spelling
|
|
54
|
+
(`PascalCase`, `required` Key/Label, the extra `Cell` slot), and the row-controls example.
|
|
55
|
+
`agent-onboarding.md` tells every consuming agent this file is closed — "if a class is not in
|
|
56
|
+
that file, it does not exist" — while three of the four stacks build a table only through
|
|
57
|
+
`columns`, whose shape appeared nowhere in it. One reporting app shipped `label: ''` on its
|
|
58
|
+
actions column as a result: a `<th>` announced as nothing.
|
|
59
|
+
- **Nine enhancers still claimed to be Indonesian.** The comment above every `TEXT` table read
|
|
60
|
+
"User-facing strings. Indonesian by default" — written before 2.0.0 flipped them all to English,
|
|
61
|
+
and shipped in `dist/`, where it is the first thing a consumer reading the source sees. It now
|
|
62
|
+
says English, names `data-fdy-text-<key>` as the override, and gives an Indonesian app on the
|
|
63
|
+
raw path as the example of who overrides.
|
|
64
|
+
- **The two docs a consumer actually reads on upgrade now mention the language.** 2.0.0's breaking
|
|
65
|
+
change was documented in `COMPONENTS.md` and this file, but neither `docs/getting-started.md` nor
|
|
66
|
+
the block projects paste into their agent instructions
|
|
67
|
+
(`docs/agent-onboarding.md`) said a word about it. Both now do, with `data-fdy-text-<key>` as the
|
|
68
|
+
whole migration and `<html lang>` for date names.
|
|
69
|
+
- **`HANDOFF.md` described a release that had already happened.** It stated npm was on 1.53.0 with
|
|
70
|
+
1.54.0 and 2.0.0 sitting unpushed; npm had been on 2.0.0 since the morning, with the tag, the
|
|
71
|
+
branch and `origin/main` all on the same commit. The file that warns about stale snapshots was
|
|
72
|
+
the stale snapshot. Corrected, with the three commands that check it.
|
|
73
|
+
- **`CLAUDE.md` claimed 44 components**, a number matching nothing countable. Now 48 stylesheets in
|
|
74
|
+
`src/components/` and 26 enhancers in `src/freeday-*.js`, both of which anyone can re-count.
|
|
75
|
+
- **The typed props of all twelve wrappers are now in `COMPONENTS.md`** (`NEXT-UP.md` #11, the
|
|
76
|
+
general form of #040). A `### Props` table per wrapper: Vue and React names together, what each
|
|
77
|
+
does, and the Blazor differences stated rather than implied. Writing them measured something
|
|
78
|
+
nobody had: **four Blazor pickers are far thinner than their JS twins** — `FdyDatepicker` takes 6
|
|
79
|
+
parameters against 21 Vue props, `FdyAutocomplete` 6 against 11, `FdyCascade` 8 against 12, and
|
|
80
|
+
`FdyCombo` has no `Describedby`. Those wrappers render a seed element and do not splat unmatched
|
|
81
|
+
attributes, so `disabled`, `readonly`, `invalid`, the a11y ids, `clearable` and the datepicker's
|
|
82
|
+
ten navigation labels cannot be reached from a Blazor page at all. It runs the other way too:
|
|
83
|
+
`CloseLabel` exists only in Blazor, so the modal and drawer × is fixed at `Close` in Vue and
|
|
84
|
+
React. Filed as `NEXT-UP.md` #12 with its trigger; documented here as fact in the meantime,
|
|
85
|
+
because a consumer meeting it should find it written down rather than discover it.
|
|
86
|
+
- **`FdyAppShell` was missing from every list of the typed wrappers but the two READMEs.** It
|
|
87
|
+
shipped as the eleventh in 1.54.0 and `COMPONENTS.md`, `getting-started.md`, `agent-onboarding.md`
|
|
88
|
+
and `integrations.md` went on naming ten for a whole major version — `agent-onboarding.md`
|
|
89
|
+
saying both "ten" and "eleven" about the same set, in the same file, and `getting-started.md`
|
|
90
|
+
carrying a section heading that still listed the seven of some earlier release. All corrected,
|
|
91
|
+
and a guard now reads any run of six or more wrapper names as an enumeration and fails if it is
|
|
92
|
+
incomplete. It found two of these itself, one of them a list this release had already touched.
|
|
93
|
+
|
|
6
94
|
## [2.0.0] - 2026-08-25
|
|
7
95
|
### Changed: BREAKING
|
|
8
96
|
- **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,24 @@ 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
|
+
with one absence — there is no `Describedby`, so a Blazor combo cannot point at its own error text.
|
|
461
|
+
|
|
425
462
|
Fully styleable dropdown, APG combobox+listbox. Needs `freeday-select.js`.
|
|
426
463
|
|
|
427
464
|
- `.fdy-combo` (+`--error`, `--no-icon`) · `__button` `__value` (+`--placeholder`) `__listbox`
|
|
@@ -452,6 +489,22 @@ keeps matching after selection. Do not put a glyph in that span.
|
|
|
452
489
|
## Autocomplete — `.fdy-autocomplete`
|
|
453
490
|
> **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
491
|
|
|
492
|
+
### Props — `<FdyAutocomplete>`
|
|
493
|
+
|
|
494
|
+
| Prop | Type | What it does |
|
|
495
|
+
|---|---|---|
|
|
496
|
+
| `modelValue` · `value` | `string` | The typed text. Free text: the list suggests, it does not constrain. |
|
|
497
|
+
| `onChange` · `onSelect` | `(value: string) => void` | React. `onChange` on every keystroke, `onSelect` only when a suggestion is taken. Vue emits `update:modelValue` and `select`. |
|
|
498
|
+
| `options` | `ReadonlyArray<string>` | The suggestions to filter. |
|
|
499
|
+
| `emptyText?` | `string` | Shown when nothing matches. |
|
|
500
|
+
| `placeholder?` | `string` | |
|
|
501
|
+
| `id?` · `ariaLabel?` · `ariaLabelledby?` · `describedby?` | `string` | Input id; its accessible name as text or as a reference; the help/error text it points at. |
|
|
502
|
+
| `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. |
|
|
503
|
+
|
|
504
|
+
Blazor takes six of these — `Value` / `ValueChanged`, `Options`, `Placeholder`, `AriaLabel`,
|
|
505
|
+
`EmptyText` — and no state flags or ids. Its wrapper renders a fixed seed element and does not
|
|
506
|
+
splat unmatched attributes, so those are not reachable from a Blazor page at all.
|
|
507
|
+
|
|
455
508
|
Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
456
509
|
|
|
457
510
|
- `.fdy-autocomplete` · `__listbox` `__option` `__empty`
|
|
@@ -461,6 +514,22 @@ Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
|
461
514
|
## Cascade select — `.fdy-cascade`
|
|
462
515
|
> **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
516
|
|
|
517
|
+
### Props — `<FdyCascade>`
|
|
518
|
+
|
|
519
|
+
| Prop | Type | What it does |
|
|
520
|
+
|---|---|---|
|
|
521
|
+
| `modelValue` · `value` | `string` | The selected **leaf** value; `''` means nothing selected. |
|
|
522
|
+
| `onChange` | `(value: string, labels: string[]) => void` | React. Vue emits `update:modelValue` and `change`. `labels` is the whole path, root → leaf. |
|
|
523
|
+
| `options` | `ReadonlyArray<CascadeNode>` | The tree. `CascadeNode` = `{ label, value, children? }`; a node **with** children is a branch, one without is selectable. |
|
|
524
|
+
| `separator?` | `string` | What joins the path in the display. Default `" / "`, matching the enhancer. |
|
|
525
|
+
| `backLabel?` | `string` | Accessible name for the up-one-level button. Default `Back one level`. |
|
|
526
|
+
| `label?` | `string` | Accessible name for the trigger and its listbox. |
|
|
527
|
+
| `placeholder?` | `string` | |
|
|
528
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
529
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | As on `<FdyCombo>`. |
|
|
530
|
+
|
|
531
|
+
Blazor calls the tree `Nodes`, adds `SubmenuLabel`, and takes neither the state flags nor the ids.
|
|
532
|
+
|
|
464
533
|
Hierarchical drill-down. The data model is a **nested `<ul>`** inside the wrapper: an `<li>` with a
|
|
465
534
|
child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
466
535
|
|
|
@@ -482,6 +551,37 @@ child `<ul>` is a branch, one without is a leaf. Needs `freeday-cascade.js`.
|
|
|
482
551
|
## Choose-from-list (CFL) — `data-fdy-cfl`
|
|
483
552
|
> **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
553
|
|
|
554
|
+
### Props — `<FdyCfl>`
|
|
555
|
+
|
|
556
|
+
Four of these are required and carry the whole component: `fetchPage`, `columns`, `display`,
|
|
557
|
+
`rowKey`. The rest are copy and state.
|
|
558
|
+
|
|
559
|
+
| Prop | Type | What it does |
|
|
560
|
+
|---|---|---|
|
|
561
|
+
| `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). |
|
|
562
|
+
| `onChange` | `(value: Row \| Row[] \| null) => void` | React. Vue emits `update:modelValue` and `change`. |
|
|
563
|
+
| `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. |
|
|
564
|
+
| `columns` | `ReadonlyArray<CflColumn<Row>>` | `{ key, label }` per dialog column. |
|
|
565
|
+
| `display` | `(row: Row) => string` | What the closed field shows once a row is picked. |
|
|
566
|
+
| `rowKey` | `(row: Row) => string` | Row identity: ticks in multi mode, and seeding them when the dialog re-opens. |
|
|
567
|
+
| `pageSize?` | `number` | Advisory only. Your `fetchPage` owns paging; this documents the intent. |
|
|
568
|
+
| `title?` | `string` | The dialog's heading. Default `Choose data`. |
|
|
569
|
+
| `searchPlaceholder?` | `string` | Placeholder **and** accessible name of the search box. Default `Search…`. |
|
|
570
|
+
| `loadingText?` · `emptyText?` · `retryText?` · `moreText?` | `string` | Dialog states. Defaults `Loading…`, `No results.`, `Try again`, `Load more`. |
|
|
571
|
+
| `closeLabel?` · `openLabel?` | `string` | Accessible names for the dialog's × and the field's open button. Defaults `Close`, `Open search`. |
|
|
572
|
+
| `multiple?` | `boolean` | Tick rows and commit them together on Confirm instead of committing the clicked row, and widen the value to an array. |
|
|
573
|
+
| `selectedText?` · `confirmText?` · `hintText?` | `string` | Footer copy. Defaults `{n} selected` (with `{n}` substituted), `Confirm`, `Click a row to choose it`. |
|
|
574
|
+
| `placeholder?` | `string` | Field placeholder while nothing is picked. |
|
|
575
|
+
| `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. |
|
|
576
|
+
| `clearLabel?` | `string` | Accessible name for that button. Default `Clear selection`. |
|
|
577
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps the picked value visible, focusable and copyable, but the dialog cannot be opened. |
|
|
578
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Field id; the element that labels it; the help/error text it points at. |
|
|
579
|
+
|
|
580
|
+
Blazor carries the same surface in `PascalCase` with three differences: multi-select is a second
|
|
581
|
+
pair (`Values` / `ValuesChanged`) rather than a widened single binding, the fetch delegate is
|
|
582
|
+
called `LoadPage`, and it has two parameters the JS adapters do not — `ErrorText` and
|
|
583
|
+
`SearchDebounceMs`.
|
|
584
|
+
|
|
485
585
|
A read-only field backed by master data: the button opens a searchable dialog, the picked row fills
|
|
486
586
|
the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
487
587
|
|
|
@@ -498,6 +598,13 @@ the field. The value is always **chosen, never typed**. Needs `freeday-cfl.js`.
|
|
|
498
598
|
`ValuesChanged`, because a nullable union is not a C# shape. Closing without Confirm leaves the
|
|
499
599
|
bound value untouched, and re-opening seeds the ticks from it. The field states `{n} selected`
|
|
500
600
|
rather than one row's `display()`. Strings: `selectedText`, `confirmText`, `hintText`.
|
|
601
|
+
- **A single-select in Vue or React needs one narrowing, and the kit ships it.** The emitted value
|
|
602
|
+
is typed `Row | Row[] | null` whatever the props are, so a single-select handler cannot take it
|
|
603
|
+
as `Row`: the array is reachable only under `multiple`, the null only under `clearable`, and the
|
|
604
|
+
type cannot say which. Import the guard instead of writing one per app:
|
|
605
|
+
`import { singleRow } from '@cahyo-dimas/freeday/vue'` (or `/react`). It returns `Row | null`,
|
|
606
|
+
and throws if a `multiple` field's array ever reaches it, rather than silently keeping the first
|
|
607
|
+
row. Blazor is unaffected: multi-select there is a separate `Values` / `ValuesChanged` pair.
|
|
501
608
|
|
|
502
609
|
```html
|
|
503
610
|
<div class="fdy-field">
|
|
@@ -547,6 +654,46 @@ a new class.
|
|
|
547
654
|
## Date picker — `data-fdy-datepicker`
|
|
548
655
|
> **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
656
|
|
|
657
|
+
### Props — `<FdyDatepicker>`
|
|
658
|
+
|
|
659
|
+
| Prop | Type | What it does |
|
|
660
|
+
|---|---|---|
|
|
661
|
+
| `modelValue` · `value` | `string \| null` | ISO `YYYY-MM-DD`, or `''`/`null` for empty. |
|
|
662
|
+
| `onChange` | `(value: string) => void` | React. Vue emits `update:modelValue` and `change`. Clearing sends `''`. |
|
|
663
|
+
| `min?` · `max?` | `string` | ISO bounds; days outside them cannot be picked. |
|
|
664
|
+
| `locale?` | `string` | BCP-47 tag deciding month and weekday names. Defaults to the page's `<html lang>`, then `en`. |
|
|
665
|
+
| `placeholder?` | `string` | |
|
|
666
|
+
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
667
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps focus and tab order and shows its date, but it cannot be opened, cleared or changed. |
|
|
668
|
+
| `clearable?` | `boolean` | Show a × in the trigger once a date is set, so an optional date can be unset. Sends `''`. Off by default. |
|
|
669
|
+
| `clearLabel?` | `string` | Accessible name for that ×. Default `Clear date`. |
|
|
670
|
+
| `prevMonthLabel?` · `nextMonthLabel?` | `string` | The month arrows. Defaults `Previous month` / `Next month`. |
|
|
671
|
+
| `chooseMonthLabel?` · `chooseYearLabel?` | `string` | The title buttons that drill into the month and year grids. Defaults `Choose month` / `Choose year`. |
|
|
672
|
+
| `prevYearLabel?` · `nextYearLabel?` | `string` | Year arrows, shown in the month grid. Defaults `Previous year` / `Next year`. |
|
|
673
|
+
| `prevYearsLabel?` · `nextYearsLabel?` | `string` | Page arrows, shown in the year grid. Defaults `Previous years` / `Next years`. |
|
|
674
|
+
|
|
675
|
+
The ten label props exist because month and weekday names follow `locale` while the buttons around
|
|
676
|
+
them do not: without these, a Spanish calendar would be navigated by English arrows.
|
|
677
|
+
|
|
678
|
+
Blazor's picker is much thinner — `Value` / `ValueChanged`, `Label`, `Placeholder`, `Min`, `Max`
|
|
679
|
+
— and its seed element takes no unmatched attributes, so state flags, ids, `clearable` and every
|
|
680
|
+
label above are unreachable there.
|
|
681
|
+
|
|
682
|
+
### Props — `<FdyDateRange>`
|
|
683
|
+
|
|
684
|
+
| Prop | Type | What it does |
|
|
685
|
+
|---|---|---|
|
|
686
|
+
| `modelValue` · `value` | `DateRangeValue` = `{ start, end }`, each `string \| null` | Both ends at once, so the pair can never be half-applied. |
|
|
687
|
+
| `onChange` | `(value: DateRangeValue) => void` | React. Vue emits `update:modelValue` and `change`. |
|
|
688
|
+
| `min?` · `max?` | `string` | ISO bounds for both ends. The end can never precede the start. |
|
|
689
|
+
| `locale?` | `string` | As on the single picker. |
|
|
690
|
+
| `startPlaceholder?` · `endPlaceholder?` | `string` | One per field. |
|
|
691
|
+
| `ariaLabel?` · `ariaLabelledby?` · `describedby?` | `string` | Names the group (`role="group"`), or points at the text that does. |
|
|
692
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | Applied to both pickers together. |
|
|
693
|
+
|
|
694
|
+
Blazor splits the value into two scalars, `From` / `FromChanged` and `To` / `ToChanged`, because a
|
|
695
|
+
C# binding cannot express one object bound in two places, and it adds `Separator`.
|
|
696
|
+
|
|
550
697
|
Input-styled trigger + calendar popover. Needs `freeday-datepicker.js`. **Author an empty `<div>`**
|
|
551
698
|
The enhancer builds everything.
|
|
552
699
|
|
|
@@ -772,6 +919,75 @@ sum, so measure them once after render (and on resize) and set the variable.
|
|
|
772
919
|
## Data table — `.fdy-datatable`
|
|
773
920
|
> **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
921
|
|
|
922
|
+
### Columns — the typed `FdyTableColumn<T>`
|
|
923
|
+
|
|
924
|
+
On Vue, React and Blazor, `columns` **is** the table: it is what those stacks write instead of the
|
|
925
|
+
markup hooks further down. Ten fields, `key` and `label` required. The authored doc comments live on
|
|
926
|
+
the type itself (`adapters/core/table-model.d.ts`, re-declared for Blazor in
|
|
927
|
+
`adapters/blazor/TableTypes.cs`); the list below is the discoverable copy, and `npm test` fails if
|
|
928
|
+
the two ever disagree.
|
|
929
|
+
|
|
930
|
+
| Field | Type | What it does |
|
|
931
|
+
|---|---|---|
|
|
932
|
+
| `key` | `string` | Row property key; the sort/filter identity and, unless `value` is set, the cell accessor. |
|
|
933
|
+
| `label` | `string` | Header label. |
|
|
934
|
+
| `labelHidden` | `boolean` | Render the label for assistive tech only; the header cell looks empty. |
|
|
935
|
+
| `sortable` | `boolean` | Show a sort toggle in the header. |
|
|
936
|
+
| `filter` | `'text' \| 'enum' \| 'number' \| 'date'` | Offer a column filter of this type (the funnel popover). |
|
|
937
|
+
| `align` | `'left' \| 'right' \| 'center'` | Cell alignment. Default `left`. |
|
|
938
|
+
| `mono` | `boolean` | Render cells in the monospace data font (`.fdy-mono`). |
|
|
939
|
+
| `sortType` | `'text' \| 'number' \| 'date'` | Override the comparator; defaults to one derived from `filter`, else `text`. |
|
|
940
|
+
| `value` | `(row: T) => unknown` | Custom accessor; defaults to `row[key]`. Feeds sort, filter and the default cell text. |
|
|
941
|
+
| `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. |
|
|
942
|
+
|
|
943
|
+
Blazor spells the same fields `PascalCase` (`LabelHidden`, `SortType`), makes `Key`/`Label`
|
|
944
|
+
`required`, and adds one field with no TS twin: `Cell`, a `RenderFragment<TRow>` where Vue and React
|
|
945
|
+
use a slot.
|
|
946
|
+
|
|
947
|
+
A column of row controls is worth stating outright, because the obvious move is the wrong one:
|
|
948
|
+
|
|
949
|
+
```ts
|
|
950
|
+
const columns: FdyTableColumn<User>[] = [
|
|
951
|
+
{ key: 'email', label: 'Email', sortable: true, filter: 'text' },
|
|
952
|
+
{ key: 'total', label: 'Total', align: 'right', mono: true, sortType: 'number' },
|
|
953
|
+
// Not `label: ''`. A <th> with no text announces as nothing, and the label also names this
|
|
954
|
+
// column's sort button and filter popover, so it has to stay meaningful.
|
|
955
|
+
{ key: 'actions', label: 'Actions', labelHidden: true },
|
|
956
|
+
];
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### Props — `<FdyTable>`
|
|
960
|
+
|
|
961
|
+
Controlled by default: hand it `columns` + `rows` and it filters, sorts and paginates internally;
|
|
962
|
+
provide the state prop for a concern and you own that concern instead. React names the callbacks
|
|
963
|
+
`onXChange`, Vue emits `update:x`, Blazor exposes `XChanged`.
|
|
964
|
+
|
|
965
|
+
| Prop | Type | What it does |
|
|
966
|
+
|---|---|---|
|
|
967
|
+
| `columns` | `ReadonlyArray<FdyTableColumn<Row>>` | The column contract documented above. |
|
|
968
|
+
| `rows` | `ReadonlyArray<Row>` | The current page's rows in server mode, the whole set in client mode. |
|
|
969
|
+
| `rowKey` | `(row: Row) => string \| number` | Row identity: keys, expansion, activation. |
|
|
970
|
+
| `sort?` · `onSortChange?` | `FdySortState \| null` · `(sort) => void` | Pass `sort`, **even as `null`**, to own sorting; omit it for the internal sort. |
|
|
971
|
+
| `filters?` · `onFiltersChange?` | `FdyFilterMap` · `(filters) => void` | The same, for filtering. |
|
|
972
|
+
| `page?` · `onPageChange?` | `FdyPageState` · `(page) => void` | Its **presence switches the table into server mode**: you own paging and get `{ index, size, total }` back. |
|
|
973
|
+
| `pageSize?` | `number` | Client mode: rows per page. Absent or 0 renders every row with no pager. |
|
|
974
|
+
| `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. |
|
|
975
|
+
| `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. |
|
|
976
|
+
| `pager?` | `boolean` | Default true. Turn it off to withhold the table's own footer when the screen renders one. |
|
|
977
|
+
| `loading?` | `boolean` | Show the in-table loading state. |
|
|
978
|
+
| `emptyText?` · `empty?` | `string` · `ReactNode` | The empty state as text, or as markup (React `empty`, Vue's `empty` slot). |
|
|
979
|
+
| `ariaLabel?` | `string` | Names the table for assistive tech. |
|
|
980
|
+
| `rowActivatable?` · `onRowActivate?` | `boolean` · `(row) => void` | Rows become focusable and activate on click, Enter or Space. |
|
|
981
|
+
| `rowClass?` | `(row: Row) => string \| undefined` | Per-row class hook, e.g. marking the selected row. |
|
|
982
|
+
| `expandedKeys?` · `renderRowDetail?` | `ReadonlyArray<string \| number>` · `(row) => ReactNode` | Controlled expansion: these keys get a full-width detail row. Vue uses the `row-detail` slot. |
|
|
983
|
+
| `renderCell?` | `(column, row, value) => ReactNode` | React. Custom cell rendering; Vue uses the `cell` slot. |
|
|
984
|
+
| `toolbar?` | `ReactNode` | React. Content above the table; Vue uses the `toolbar` slot. |
|
|
985
|
+
| `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. |
|
|
986
|
+
|
|
987
|
+
Blazor matches this surface (`LoadingText` and `EmptyContent` in place of `emptyText`/`empty`,
|
|
988
|
+
`Toolbar` and `RowDetail` as `RenderFragment`s) and is the one adapter that also exposes
|
|
989
|
+
`FiltersChanged`.
|
|
990
|
+
|
|
775
991
|
The interactive table: global search, sort, per-column filters, row selection + bulk bar,
|
|
776
992
|
pagination. Needs `freeday-table.js`. Wrap the whole thing in `.fdy-datatable` + `data-fdy-table`
|
|
777
993
|
(`data-page-size="N"`).
|
|
@@ -849,6 +1065,14 @@ ninety-row list would remove the only way back to twenty.
|
|
|
849
1065
|
of one page of rows. A footer inside the table is inside the half a phone hides, so those screens
|
|
850
1066
|
render it once, outside both, with `pager={false}` on the table.
|
|
851
1067
|
|
|
1068
|
+
### Props — `<FdyTableFooter>`
|
|
1069
|
+
|
|
1070
|
+
| Prop | Type | What it does |
|
|
1071
|
+
|---|---|---|
|
|
1072
|
+
| `page` | `FdyPageState` = `{ index, size, total }` | The page being shown. `size` drives both the range text and the rows-per-page control's value. |
|
|
1073
|
+
| `onPageChange?` | `(page: FdyPageState) => void` | React. A page click or a size change. Vue emits `update:page`; Blazor `PageChanged`. |
|
|
1074
|
+
| `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. |
|
|
1075
|
+
|
|
852
1076
|
**Who draws the pager.** The table renders its own footer (range + pager) whenever there is more
|
|
853
1077
|
than one page. Two ways to take it over:
|
|
854
1078
|
|
|
@@ -879,6 +1103,25 @@ replaces.
|
|
|
879
1103
|
## Charts — `data-fdy-chart`
|
|
880
1104
|
> **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
1105
|
|
|
1106
|
+
### Props — `<FdyChart>`
|
|
1107
|
+
|
|
1108
|
+
| Prop | Type | What it does |
|
|
1109
|
+
|---|---|---|
|
|
1110
|
+
| `type` | `'line' \| 'area' \| 'bar' \| 'sparkline' \| 'donut'` | Which chart to draw. |
|
|
1111
|
+
| `values?` | `ReadonlyArray<number>` | One series. |
|
|
1112
|
+
| `series?` | `ReadonlyArray<FdyChartSeries>` | Several: each `{ label, values }`. Use one or the other, not both. |
|
|
1113
|
+
| `labels?` | `ReadonlyArray<string>` | Category labels: the x axis, or the donut's slices. |
|
|
1114
|
+
| `format?` | `'number' \| 'percent' \| 'currency'` | How values are written in labels and the donut centre. |
|
|
1115
|
+
| `stacked?` | `boolean` | Bar charts: stack the series instead of grouping them side by side. |
|
|
1116
|
+
| `legend?` | `'auto' \| 'always' \| 'none'` | `auto` shows one when there is more than one series. |
|
|
1117
|
+
| `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. |
|
|
1118
|
+
| `color?` | `string` | The same, for a single-series chart (sparkline, simple bar or line). |
|
|
1119
|
+
| `center?` | `string \| number` | Donut only: what to print in the middle. |
|
|
1120
|
+
| `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`). |
|
|
1121
|
+
| `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. |
|
|
1122
|
+
|
|
1123
|
+
Blazor adds `ChildContent` for the same placeholder role.
|
|
1124
|
+
|
|
882
1125
|
Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`; call
|
|
883
1126
|
`FreedayChart.update(el)` after changing data (or use `FdyChart` in Vue/React/Blazor).
|
|
884
1127
|
|
|
@@ -1076,6 +1319,22 @@ so the same look serves routed sub-navigation built from plain links. See the no
|
|
|
1076
1319
|
## Modal — `.fdy-modal`
|
|
1077
1320
|
> **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
1321
|
|
|
1322
|
+
### Props — `<FdyModal>`
|
|
1323
|
+
|
|
1324
|
+
| Prop | Type | What it does |
|
|
1325
|
+
|---|---|---|
|
|
1326
|
+
| `open` | `boolean` | Controlled: your state decides. The wrapper cancels the native `cancel` event so the DOM can never disagree with it. |
|
|
1327
|
+
| `title` | `string` (Vue) · `ReactNode` | Names the dialog through `aria-labelledby`. Vue also offers a `title` slot when the heading needs markup. |
|
|
1328
|
+
| `onClose` | `() => void` | React. Vue emits `close`. Fires from Escape, a backdrop click and the ×, all three of which exist only under `dismissible`. |
|
|
1329
|
+
| `size?` | `'sm' \| 'md' \| 'lg' \| 'wide'` | Dialog width. |
|
|
1330
|
+
| `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. |
|
|
1331
|
+
| `footer?` | `ReactNode` | React. Vue: the `footer` slot. |
|
|
1332
|
+
| `children?` | `ReactNode` | React. The body. Vue: the default slot. |
|
|
1333
|
+
|
|
1334
|
+
Blazor binds `@bind-Open` and takes `TitleContent` / `ChildContent` / `FooterContent`, plus two
|
|
1335
|
+
things Vue and React do not have: `OnClose` and **`CloseLabel`**. The × in Vue and React is labelled
|
|
1336
|
+
`Close` and cannot be renamed.
|
|
1337
|
+
|
|
1079
1338
|
Native `<dialog>`: focus trap, Esc and backdrop come from the browser. Sizes `--sm` `--md` `--lg`
|
|
1080
1339
|
`--wide`; `--cfl` for the choose-from-list dialog.
|
|
1081
1340
|
|
|
@@ -1111,6 +1370,22 @@ that is not the kit. Both rules measured on Chromium 133 and 151, and pinned as
|
|
|
1111
1370
|
## Drawer — `.fdy-drawer`
|
|
1112
1371
|
> **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
1372
|
|
|
1373
|
+
### Props — `<FdyDrawer>`
|
|
1374
|
+
|
|
1375
|
+
The modal's props with `side` in place of `size`; the behaviour notes there apply here too.
|
|
1376
|
+
|
|
1377
|
+
| Prop | Type | What it does |
|
|
1378
|
+
|---|---|---|
|
|
1379
|
+
| `open` | `boolean` | Controlled, as with the modal. |
|
|
1380
|
+
| `title` | `string` (Vue) · `ReactNode` | Names the drawer (`aria-labelledby`). |
|
|
1381
|
+
| `onClose` | `() => void` | React. Vue emits `close`. |
|
|
1382
|
+
| `side?` | `'left' \| 'right'` | Which edge it slides from. |
|
|
1383
|
+
| `dismissible?` | `boolean` | Escape, backdrop and × on or off. |
|
|
1384
|
+
| `footer?` | `ReactNode` | React. Vue: the `footer` slot. |
|
|
1385
|
+
| `children?` | `ReactNode` | React. The body. Vue: the default slot. |
|
|
1386
|
+
|
|
1387
|
+
Blazor: `@bind-Open`, `TitleContent` / `ChildContent` / `FooterContent`, `OnClose`, `CloseLabel`.
|
|
1388
|
+
|
|
1114
1389
|
Temporary side panel on native `<dialog>`, left by default, `--right` to flip. Parts `__header`
|
|
1115
1390
|
`__title` `__body` `__footer` `__close`. Open it from any
|
|
1116
1391
|
`<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.1.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.1.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`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Types for the choose-from-list value helper (adapters/core/cfl-value.js).
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Narrow an `FdyCfl` value to the row a single-select can actually produce.
|
|
5
|
+
*
|
|
6
|
+
* `Row[]` is reachable only under `multiple` and `null` only under `clearable`, but the emitted
|
|
7
|
+
* type cannot say which of those props a given field sets, so every single-select handler has to
|
|
8
|
+
* prove it. Throws on an array rather than taking its first row: quietly picking one is how a
|
|
9
|
+
* `multiple` added later goes unnoticed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function singleRow<Row>(value: Row | Row[] | null): Row | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Freeday, choose-from-list value narrowing (pure, zero dependencies).
|
|
2
|
+
//
|
|
3
|
+
// `FdyCfl` in Vue and React declares one model type, `Row | Row[] | null`, covering three prop
|
|
4
|
+
// combinations at once: the array is reachable only under `multiple`, the null only under
|
|
5
|
+
// `clearable`. A single-select screen therefore has to narrow a union that its own props make
|
|
6
|
+
// impossible, and the widening arrived across two releases that were additive from the kit's side
|
|
7
|
+
// (1.29.0 `clearable`, 1.42.0 `multiple`) and a broken build from the consumer's. Apps were each
|
|
8
|
+
// inventing the guard differently: a cast that hides the day `clearable` is added, a silent
|
|
9
|
+
// ignore, or a throw. The kit is what knows the invariant, so the guard is written once, here.
|
|
10
|
+
//
|
|
11
|
+
// Blazor needs none of this: multi-select there is a separate `Values` / `ValuesChanged` pair,
|
|
12
|
+
// because a nullable union is not a C# binding shape.
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Narrow an `FdyCfl` value to the row a single-select can actually produce.
|
|
16
|
+
*
|
|
17
|
+
* @template Row
|
|
18
|
+
* @param {Row | Row[] | null} value the value the component emitted
|
|
19
|
+
* @returns {Row | null} the picked row, or null when nothing is picked
|
|
20
|
+
* @throws {TypeError} when handed an array, which only a `multiple` CFL emits
|
|
21
|
+
*/
|
|
22
|
+
export function singleRow(value) {
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
throw new TypeError(
|
|
25
|
+
`singleRow() got an array of ${value.length} row(s). Only an FdyCfl with \`multiple\` emits ` +
|
|
26
|
+
'one, and a set of rows is not a single selection: read the array directly on that field, ' +
|
|
27
|
+
'or drop `multiple`.',
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return value == null ? null : value;
|
|
31
|
+
}
|
|
@@ -34,6 +34,9 @@ export { FdyDateRange, type FdyDateRangeProps, type DateRangeValue } from './com
|
|
|
34
34
|
export { FdyAutocomplete, type FdyAutocompleteProps } from './components/FdyAutocomplete';
|
|
35
35
|
export { FdyCascade, type FdyCascadeProps, type CascadeNode } from './components/FdyCascade';
|
|
36
36
|
export { FdyCfl, type FdyCflProps, type CflColumn, type CflPage } from './components/FdyCfl';
|
|
37
|
+
/** Narrow `FdyCfl`'s `Row | Row[] | null` to the `Row | null` a single-select can produce; throws
|
|
38
|
+
* on the array only a `multiple` field emits. */
|
|
39
|
+
export { singleRow } from '../core/cfl-value';
|
|
37
40
|
export { FdyChart, type FdyChartProps, type FdyChartSeries } from './components/FdyChart';
|
|
38
41
|
export { FdyTable, type FdyTableProps } from './components/FdyTable';
|
|
39
42
|
/** The table's own footer, standalone, for a responsive list whose table is hidden at some
|
package/adapters/react/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { FdyDateRange } from './components/FdyDateRange.tsx';
|
|
|
6
6
|
export { FdyAutocomplete } from './components/FdyAutocomplete.tsx';
|
|
7
7
|
export { FdyCascade } from './components/FdyCascade.tsx';
|
|
8
8
|
export { FdyCfl } from './components/FdyCfl.tsx';
|
|
9
|
+
export { singleRow } from '../core/cfl-value.js';
|
|
9
10
|
export { FdyChart } from './components/FdyChart.tsx';
|
|
10
11
|
export { FdyTable } from './components/FdyTable.tsx';
|
|
11
12
|
export { FdyTableFooter } from './components/FdyTableFooter.tsx';
|
package/adapters/vue/index.d.ts
CHANGED
|
@@ -33,6 +33,9 @@ export { default as FdyAutocomplete } from './components/FdyAutocomplete.vue';
|
|
|
33
33
|
export { default as FdyCascade } from './components/FdyCascade.vue';
|
|
34
34
|
export type { CascadeNode } from './components/FdyCascade.vue';
|
|
35
35
|
export { default as FdyCfl } from './components/FdyCfl.vue';
|
|
36
|
+
/** Narrow `FdyCfl`'s `Row | Row[] | null` to the `Row | null` a single-select can produce; throws
|
|
37
|
+
* on the array only a `multiple` field emits. */
|
|
38
|
+
export { singleRow } from '../core/cfl-value';
|
|
36
39
|
export { default as FdyChart } from './components/FdyChart.vue';
|
|
37
40
|
export { default as FdyTable } from './components/FdyTable.vue';
|
|
38
41
|
/** The table's own footer, standalone, for a responsive list whose table is hidden at some
|
package/adapters/vue/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { default as FdyDateRange } from './components/FdyDateRange.vue';
|
|
|
6
6
|
export { default as FdyAutocomplete } from './components/FdyAutocomplete.vue';
|
|
7
7
|
export { default as FdyCascade } from './components/FdyCascade.vue';
|
|
8
8
|
export { default as FdyCfl } from './components/FdyCfl.vue';
|
|
9
|
+
export { singleRow } from '../core/cfl-value.js';
|
|
9
10
|
export { default as FdyChart } from './components/FdyChart.vue';
|
|
10
11
|
export { default as FdyTable } from './components/FdyTable.vue';
|
|
11
12
|
export { default as FdyTableFooter } from './components/FdyTableFooter.vue';
|
package/dist/freeday-carousel.js
CHANGED
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
'use strict';
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
/* User-facing strings.
|
|
22
|
-
* path, and every one overridable per element
|
|
23
|
-
*
|
|
24
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
21
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
22
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
23
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
24
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
25
|
+
* hard-coded further down. */
|
|
25
26
|
var TEXT = {
|
|
26
27
|
position: '{n} of {total}',
|
|
27
28
|
slide: 'Slide {n}'
|
package/dist/freeday-cascade.js
CHANGED
|
@@ -54,10 +54,11 @@
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
/* User-facing strings.
|
|
58
|
-
* path, and every one overridable per element
|
|
59
|
-
*
|
|
60
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
57
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
58
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
59
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
60
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
61
|
+
* hard-coded further down. */
|
|
61
62
|
var TEXT = {
|
|
62
63
|
back: 'Back one level',
|
|
63
64
|
submenu: '{label}, submenu'
|
package/dist/freeday-cfl.js
CHANGED
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
/* User-facing strings.
|
|
38
|
-
* path, and every one overridable per element
|
|
39
|
-
*
|
|
40
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
37
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
38
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
39
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
40
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
41
|
+
* hard-coded further down. */
|
|
41
42
|
var TEXT = {
|
|
42
43
|
selected: '{n} selected'
|
|
43
44
|
};
|
package/dist/freeday-form.js
CHANGED
|
@@ -38,10 +38,11 @@
|
|
|
38
38
|
badInput: 'type',
|
|
39
39
|
customError: 'mismatch'
|
|
40
40
|
};
|
|
41
|
-
/* User-facing strings.
|
|
42
|
-
* path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on
|
|
43
|
-
* `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form
|
|
44
|
-
* a host in another language needs: it sets nine messages once instead of on
|
|
41
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
42
|
+
* enhancer path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on
|
|
43
|
+
* the field, `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form
|
|
44
|
+
* level is what a host in another language needs: it sets nine messages once instead of on
|
|
45
|
+
* every input.
|
|
45
46
|
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
46
47
|
var TEXT = {
|
|
47
48
|
required: 'Required.',
|
package/dist/freeday-mask.js
CHANGED
|
@@ -51,10 +51,11 @@
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
/* User-facing strings.
|
|
55
|
-
* path, and every one overridable per element
|
|
56
|
-
*
|
|
57
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
54
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
55
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
56
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
57
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
58
|
+
* hard-coded further down. */
|
|
58
59
|
var TEXT = {
|
|
59
60
|
show: 'Show password',
|
|
60
61
|
hide: 'Hide password'
|
package/dist/freeday-stepper.js
CHANGED
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
var CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>';
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
/* User-facing strings.
|
|
23
|
-
* path, and every one overridable per element
|
|
24
|
-
*
|
|
25
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
22
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
23
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
24
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
25
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
26
|
+
* hard-coded further down. */
|
|
26
27
|
var TEXT = {
|
|
27
28
|
done: 'Done',
|
|
28
29
|
next: 'Next'
|
package/dist/freeday-table.js
CHANGED
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
/* User-facing strings.
|
|
38
|
-
* path, and every one overridable per element
|
|
39
|
-
*
|
|
40
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
37
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
38
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
39
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
40
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
41
|
+
* hard-coded further down. */
|
|
41
42
|
var TEXT = {
|
|
42
43
|
prev: 'Previous',
|
|
43
44
|
next: 'Next',
|
package/dist/freeday-toast.js
CHANGED
|
@@ -77,10 +77,11 @@
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
/* User-facing strings.
|
|
81
|
-
* path, and every one overridable per element
|
|
82
|
-
*
|
|
83
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
80
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
81
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
82
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
83
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
84
|
+
* hard-coded further down. */
|
|
84
85
|
var TEXT = {
|
|
85
86
|
close: 'Close'
|
|
86
87
|
};
|
package/dist/freeday-upload.js
CHANGED
|
@@ -51,10 +51,11 @@
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
/* User-facing strings.
|
|
55
|
-
* path, and every one overridable per element
|
|
56
|
-
*
|
|
57
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
54
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
55
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
56
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
57
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
58
|
+
* hard-coded further down. */
|
|
58
59
|
var TEXT = {
|
|
59
60
|
remove: 'Remove {name}',
|
|
60
61
|
progress: 'Upload progress for {name}',
|
package/dist/freeday.js
CHANGED
|
@@ -419,10 +419,11 @@
|
|
|
419
419
|
'use strict';
|
|
420
420
|
|
|
421
421
|
|
|
422
|
-
/* User-facing strings.
|
|
423
|
-
* path, and every one overridable per element
|
|
424
|
-
*
|
|
425
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
422
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
423
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
424
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
425
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
426
|
+
* hard-coded further down. */
|
|
426
427
|
var TEXT = {
|
|
427
428
|
position: '{n} of {total}',
|
|
428
429
|
slide: 'Slide {n}'
|
|
@@ -596,10 +597,11 @@
|
|
|
596
597
|
}
|
|
597
598
|
|
|
598
599
|
|
|
599
|
-
/* User-facing strings.
|
|
600
|
-
* path, and every one overridable per element
|
|
601
|
-
*
|
|
602
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
600
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
601
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
602
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
603
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
604
|
+
* hard-coded further down. */
|
|
603
605
|
var TEXT = {
|
|
604
606
|
back: 'Back one level',
|
|
605
607
|
submenu: '{label}, submenu'
|
|
@@ -862,10 +864,11 @@
|
|
|
862
864
|
}
|
|
863
865
|
|
|
864
866
|
|
|
865
|
-
/* User-facing strings.
|
|
866
|
-
* path, and every one overridable per element
|
|
867
|
-
*
|
|
868
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
867
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
868
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
869
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
870
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
871
|
+
* hard-coded further down. */
|
|
869
872
|
var TEXT = {
|
|
870
873
|
selected: '{n} selected'
|
|
871
874
|
};
|
|
@@ -2367,10 +2370,11 @@
|
|
|
2367
2370
|
badInput: 'type',
|
|
2368
2371
|
customError: 'mismatch'
|
|
2369
2372
|
};
|
|
2370
|
-
/* User-facing strings.
|
|
2371
|
-
* path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on
|
|
2372
|
-
* `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form
|
|
2373
|
-
* a host in another language needs: it sets nine messages once instead of on
|
|
2373
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
2374
|
+
* enhancer path, and overridable at three levels, narrowest first: `data-fdy-msg-<alias>` on
|
|
2375
|
+
* the field, `data-fdy-msg` on the field, then `data-fdy-text-<alias>` on the FORM. The form
|
|
2376
|
+
* level is what a host in another language needs: it sets nine messages once instead of on
|
|
2377
|
+
* every input.
|
|
2374
2378
|
* Keeping them in ONE table is also what lets a guard prove none is hard-coded further down. */
|
|
2375
2379
|
var TEXT = {
|
|
2376
2380
|
required: 'Required.',
|
|
@@ -2581,10 +2585,11 @@
|
|
|
2581
2585
|
}
|
|
2582
2586
|
|
|
2583
2587
|
|
|
2584
|
-
/* User-facing strings.
|
|
2585
|
-
* path, and every one overridable per element
|
|
2586
|
-
*
|
|
2587
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
2588
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
2589
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
2590
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
2591
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
2592
|
+
* hard-coded further down. */
|
|
2588
2593
|
var TEXT = {
|
|
2589
2594
|
show: 'Show password',
|
|
2590
2595
|
hide: 'Hide password'
|
|
@@ -3272,10 +3277,11 @@
|
|
|
3272
3277
|
var CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>';
|
|
3273
3278
|
|
|
3274
3279
|
|
|
3275
|
-
/* User-facing strings.
|
|
3276
|
-
* path, and every one overridable per element
|
|
3277
|
-
*
|
|
3278
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
3280
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
3281
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
3282
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
3283
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
3284
|
+
* hard-coded further down. */
|
|
3279
3285
|
var TEXT = {
|
|
3280
3286
|
done: 'Done',
|
|
3281
3287
|
next: 'Next'
|
|
@@ -3407,10 +3413,11 @@
|
|
|
3407
3413
|
}
|
|
3408
3414
|
|
|
3409
3415
|
|
|
3410
|
-
/* User-facing strings.
|
|
3411
|
-
* path, and every one overridable per element
|
|
3412
|
-
*
|
|
3413
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
3416
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
3417
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
3418
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
3419
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
3420
|
+
* hard-coded further down. */
|
|
3414
3421
|
var TEXT = {
|
|
3415
3422
|
prev: 'Previous',
|
|
3416
3423
|
next: 'Next',
|
|
@@ -4238,10 +4245,11 @@
|
|
|
4238
4245
|
}
|
|
4239
4246
|
|
|
4240
4247
|
|
|
4241
|
-
/* User-facing strings.
|
|
4242
|
-
* path, and every one overridable per element
|
|
4243
|
-
*
|
|
4244
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
4248
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
4249
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
4250
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
4251
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
4252
|
+
* hard-coded further down. */
|
|
4245
4253
|
var TEXT = {
|
|
4246
4254
|
close: 'Close'
|
|
4247
4255
|
};
|
|
@@ -4472,10 +4480,11 @@
|
|
|
4472
4480
|
}
|
|
4473
4481
|
|
|
4474
4482
|
|
|
4475
|
-
/* User-facing strings.
|
|
4476
|
-
* path, and every one overridable per element
|
|
4477
|
-
*
|
|
4478
|
-
* Keeping them in ONE table is also what lets a guard prove none is
|
|
4483
|
+
/* User-facing strings. English by default since 2.0.0, documented and deliberate for the raw
|
|
4484
|
+
* enhancer path, and every one overridable per element with `data-fdy-text-<key>`, so a host
|
|
4485
|
+
* that speaks another language (an Indonesian app on the raw path) supplies its own without
|
|
4486
|
+
* forking this file. Keeping them in ONE table is also what lets a guard prove none is
|
|
4487
|
+
* hard-coded further down. */
|
|
4479
4488
|
var TEXT = {
|
|
4480
4489
|
remove: 'Remove {name}',
|
|
4481
4490
|
progress: 'Upload progress for {name}',
|
package/docs/agent-onboarding.md
CHANGED
|
@@ -20,21 +20,21 @@ 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;
|
|
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
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
|
|
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
34
|
| Static HTML, Svelte, server-rendered templates… | no wrapper: raw markup + the enhancer script | `fdy-*` DOM events |
|
|
35
35
|
|
|
36
|
-
The
|
|
37
|
-
FdyChart · FdyTable · FdyModal · FdyDrawer**. In Vue/React/Blazor, **never hand-write the raw
|
|
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
38
|
markup + enhancer for these eleven.** The raw path *appears* to work: the enhancer auto-initialises
|
|
39
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
|
|
@@ -75,6 +75,9 @@ wrapper, use the raw markup and hydrate it:
|
|
|
75
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
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
|
|
@@ -85,7 +88,7 @@ Blazor). Then verify the agent can actually read those files. An agent that can'
|
|
|
85
88
|
|
|
86
89
|
| File | What it answers |
|
|
87
90
|
|---|---|
|
|
88
|
-
| `COMPONENTS.md` | The complete class surface: what exists, its modifiers, minimal markup, a11y. |
|
|
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. |
|
|
89
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…). |
|
|
@@ -95,7 +98,7 @@ Blazor). Then verify the agent can actually read those files. An agent that can'
|
|
|
95
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
100
|
| `tokens/tokens.json` | Every token in W3C DTCG format, machine-readable. |
|
|
98
|
-
| `adapters/vue` · `adapters/react` · `adapters/blazor` | Typed wrappers,
|
|
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
104
|
<https://cahyo-dimas.github.io/freeday-ui-kit/>, and the repo (including three complete example
|
package/docs/getting-started.md
CHANGED
|
@@ -23,7 +23,7 @@ Freeday = **CSS** (semantic tokens + `fdy-*` classes) + **zero-dependency JS enh
|
|
|
23
23
|
table: [`integrations.md` §Event & API contract](integrations.md).
|
|
24
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
|
|
@@ -65,6 +65,13 @@ Freeday = **CSS** (semantic tokens + `fdy-*` classes) + **zero-dependency JS enh
|
|
|
65
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
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
|
|
|
@@ -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": "^2.
|
|
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`)
|
|
@@ -300,10 +307,11 @@ export function Panel() {
|
|
|
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
|
|
310
|
+
### 5. Alternative: typed controlled components
|
|
304
311
|
For fields you'd normally write as a native `<select>`/`<input type="date">`,
|
|
305
312
|
`@cahyo-dimas/freeday/react` also exports typed **controlled** components with plain `value`/`onChange`,
|
|
306
|
-
no manual event bubbling
|
|
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';
|
|
@@ -436,11 +444,12 @@ 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
|
-
|
|
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`)
|
|
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
|
|
package/docs/integrations.md
CHANGED
|
@@ -154,7 +154,7 @@ function Panel(): JSX.Element {
|
|
|
154
154
|
```
|
|
155
155
|
> **Controlled alternative (parity with Vue's `v-model`):** `@cahyo-dimas/freeday/react` also exports
|
|
156
156
|
> the typed components `FdyCombo` / `FdyDatepicker` / `FdyDateRange` / `FdyAutocomplete` /
|
|
157
|
-
> `FdyCascade` / `FdyCfl` / `FdyChart` / `FdyTable` / `FdyModal` / `FdyDrawer`, with plain
|
|
157
|
+
> `FdyCascade` / `FdyCfl` / `FdyChart` / `FdyTable` / `FdyModal` / `FdyDrawer` / `FdyAppShell`, with plain
|
|
158
158
|
> `value`/`onChange` (props in, events out), no `data-fdy-*` + manual event listener:
|
|
159
159
|
> ```tsx
|
|
160
160
|
> import { FdyCombo } from '@cahyo-dimas/freeday/react';
|
|
@@ -200,7 +200,7 @@ document.addEventListener('fdy-form-invalid', (e) =>
|
|
|
200
200
|
> **Native components (parity with Vue's `v-model` / React's `value`/`onChange`):** the
|
|
201
201
|
> **`Freeday.Blazor`** RCL (net8.0, `adapters/blazor/`) ships the typed components `FdyCombo<TValue>` /
|
|
202
202
|
> `FdyDatepicker` / `FdyDateRange` / `FdyAutocomplete` / `FdyCascade` / `FdyCfl<TRow>` / `FdyChart` /
|
|
203
|
-
> `FdyTable<TRow>` / `FdyModal` / `FdyDrawer` with `@bind`, no `@ref` / manual `initAll` / `[JSInvokable]`:
|
|
203
|
+
> `FdyTable<TRow>` / `FdyModal` / `FdyDrawer` / `FdyAppShell` with `@bind`, no `@ref` / manual `initAll` / `[JSInvokable]`:
|
|
204
204
|
> ```razor
|
|
205
205
|
> @using Freeday.Blazor
|
|
206
206
|
> <FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl-status" />
|