@cahyo-dimas/freeday 1.54.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +713 -545
- package/COMPONENTS.md +417 -139
- package/README.id.md +46 -45
- package/README.md +46 -43
- package/USAGE.md +34 -34
- package/adapters/blazor/FdyAppShell.razor +2 -2
- package/adapters/blazor/FdyAppShell.razor.cs +6 -6
- package/adapters/blazor/FdyCfl.razor.cs +1 -1
- package/adapters/blazor/FdyChart.razor +1 -1
- package/adapters/blazor/FdyChart.razor.cs +3 -3
- package/adapters/blazor/FdyChartSeries.cs +1 -1
- package/adapters/blazor/FdyCombo.razor.cs +2 -2
- package/adapters/blazor/FdyModal.razor +1 -1
- package/adapters/blazor/FdyTable.razor.cs +7 -7
- package/adapters/blazor/FdyTableFilter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor +1 -1
- package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
- package/adapters/blazor/Freeday.Blazor.csproj +1 -1
- package/adapters/blazor/FreedayComponentBase.cs +1 -1
- package/adapters/blazor/TableModel.cs +3 -3
- package/adapters/blazor/TableTypes.cs +3 -3
- package/adapters/blazor/freeday-blazor.js +5 -5
- package/adapters/core/app-shell.d.ts +1 -1
- package/adapters/core/app-shell.js +6 -6
- package/adapters/core/cfl-value.d.ts +11 -0
- package/adapters/core/cfl-value.js +31 -0
- package/adapters/core/table-model.d.ts +2 -2
- package/adapters/core/table-model.js +8 -8
- package/adapters/react/components/FdyAppShell.tsx +5 -5
- package/adapters/react/components/FdyAutocomplete.tsx +1 -1
- package/adapters/react/components/FdyCascade.tsx +1 -1
- package/adapters/react/components/FdyCfl.tsx +11 -11
- package/adapters/react/components/FdyDateRange.tsx +2 -2
- package/adapters/react/components/FdyDatepicker.tsx +5 -5
- package/adapters/react/components/FdyDrawer.tsx +1 -1
- package/adapters/react/components/FdyModal.tsx +2 -2
- package/adapters/react/components/FdyTable.tsx +7 -7
- package/adapters/react/components/FdyTableFilter.tsx +1 -1
- package/adapters/react/components/FdyTableFooter.tsx +5 -5
- package/adapters/react/index.d.ts +4 -1
- package/adapters/react/index.js +1 -0
- package/adapters/react/useFreeday.js +3 -3
- package/adapters/vue/components/FdyAppShell.vue +5 -5
- package/adapters/vue/components/FdyAutocomplete.vue +1 -1
- package/adapters/vue/components/FdyCascade.vue +1 -1
- package/adapters/vue/components/FdyCfl.vue +8 -8
- package/adapters/vue/components/FdyChart.vue +2 -2
- package/adapters/vue/components/FdyCombo.vue +1 -1
- package/adapters/vue/components/FdyDateRange.vue +2 -2
- package/adapters/vue/components/FdyDatepicker.vue +5 -5
- package/adapters/vue/components/FdyDrawer.vue +3 -3
- package/adapters/vue/components/FdyModal.vue +3 -3
- package/adapters/vue/components/FdyTable.vue +7 -7
- package/adapters/vue/components/FdyTableFilter.vue +1 -1
- package/adapters/vue/components/FdyTableFooter.vue +5 -5
- package/adapters/vue/index.d.ts +5 -2
- package/adapters/vue/index.js +1 -0
- package/adapters/vue/useFreeday.js +3 -3
- package/dist/asset.d.ts +3 -3
- package/dist/freeday-app-shell.js +9 -9
- package/dist/freeday-autocomplete.js +2 -2
- package/dist/freeday-breakpoint.js +1 -1
- package/dist/freeday-carousel.js +8 -7
- package/dist/freeday-cascade.js +10 -9
- package/dist/freeday-cfl.js +12 -11
- package/dist/freeday-chart.js +11 -11
- package/dist/freeday-chip.js +3 -3
- package/dist/freeday-datepicker.js +11 -7
- package/dist/freeday-datetime.js +5 -5
- package/dist/freeday-drawer.js +1 -1
- package/dist/freeday-form.js +17 -16
- package/dist/freeday-mask.js +9 -8
- package/dist/freeday-menu.js +1 -1
- package/dist/freeday-number.js +3 -3
- package/dist/freeday-popover.js +3 -3
- package/dist/freeday-rating.js +2 -2
- package/dist/freeday-select.js +5 -5
- package/dist/freeday-slider.js +2 -2
- package/dist/freeday-stepper.js +10 -9
- package/dist/freeday-table.js +18 -17
- package/dist/freeday-tabs.js +1 -1
- package/dist/freeday-timepicker.js +2 -2
- package/dist/freeday-toast.js +11 -10
- package/dist/freeday-tree.js +3 -3
- package/dist/freeday-upload.js +28 -27
- package/dist/freeday.bundle.css +176 -176
- package/dist/freeday.css +170 -170
- package/dist/freeday.d.ts +2 -2
- package/dist/freeday.js +189 -176
- package/dist/freeday.tokens.css +5 -5
- package/docs/agent-onboarding.md +49 -46
- package/docs/getting-started.md +49 -40
- package/docs/integrations.md +18 -18
- package/docs/reference-screen.html +14 -14
- package/package.json +4 -3
- package/src/base.css +6 -6
- package/src/components/accordion.css +4 -4
- package/src/components/alert.css +1 -1
- package/src/components/app-shell.css +10 -10
- package/src/components/appbar.css +3 -3
- package/src/components/autocomplete.css +1 -1
- package/src/components/avatar.css +3 -3
- package/src/components/badge.css +5 -5
- package/src/components/breadcrumb.css +1 -1
- package/src/components/breakpoints.css +4 -4
- package/src/components/button.css +13 -13
- package/src/components/card.css +8 -8
- package/src/components/carousel.css +2 -2
- package/src/components/cascade.css +2 -2
- package/src/components/cfl.css +4 -4
- package/src/components/chart.css +3 -3
- package/src/components/chip.css +2 -2
- package/src/components/combo.css +3 -3
- package/src/components/composition.css +7 -7
- package/src/components/datepicker.css +6 -6
- package/src/components/datetimepicker.css +1 -1
- package/src/components/description-list.css +1 -1
- package/src/components/divider.css +1 -1
- package/src/components/drawer.css +1 -1
- package/src/components/file-upload.css +5 -5
- package/src/components/filterbar.css +7 -7
- package/src/components/form-grid.css +2 -2
- package/src/components/input-group.css +6 -6
- package/src/components/input.css +6 -6
- package/src/components/kbd.css +1 -1
- package/src/components/list.css +6 -6
- package/src/components/menu.css +3 -3
- package/src/components/modal.css +1 -1
- package/src/components/pagination.css +1 -1
- package/src/components/progress.css +1 -1
- package/src/components/rating.css +2 -2
- package/src/components/selection.css +4 -4
- package/src/components/skeleton.css +1 -1
- package/src/components/slider.css +1 -1
- package/src/components/spinner.css +1 -1
- package/src/components/states.css +1 -1
- package/src/components/stepper.css +2 -2
- package/src/components/table.css +15 -15
- package/src/components/tabs.css +3 -3
- package/src/components/timeline.css +1 -1
- package/src/components/timepicker.css +1 -1
- package/src/components/toast.css +3 -3
- package/src/components/tooltip.css +1 -1
- package/src/components/tree.css +2 -2
- package/tokens/breakpoints.d.ts +2 -2
- package/tokens/breakpoints.mjs +3 -3
- package/tokens/tokens.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,27 +3,195 @@
|
|
|
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
|
-
|
|
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
|
+
|
|
94
|
+
## [2.0.0] - 2026-08-25
|
|
95
|
+
### Changed: BREAKING
|
|
96
|
+
- **The vanilla enhancers now write English by default** (`NEXT-UP.md` #6, owner's decision). Every
|
|
97
|
+
user-facing string they render, 39 of them across 9 enhancers, was Indonesian while the typed
|
|
98
|
+
wrappers were English, so an app that touched both paths read as two products. A Blazor app
|
|
99
|
+
touches both **by construction**: `FreedayBlazor.initAll` runs every enhancer. The report that
|
|
100
|
+
opened this was an English back-office finding `aria-label="Filter kolom"` on its own table.
|
|
101
|
+
Examples: `Menampilkan 1–5 dari 7` → `Showing 1–5 of 7`, `{n} dipilih` → `{n} selected`,
|
|
102
|
+
`Sebelumnya`/`Berikutnya` → `Previous`/`Next`, `Wajib diisi.` → `Required.`,
|
|
103
|
+
`Tampilkan kata sandi` → `Show password`, `Menunggu server…` → `Waiting for the server…`.
|
|
104
|
+
- **The datepicker's locale fallback follows.** `document.documentElement.lang || 'id'` is now
|
|
105
|
+
`|| 'en'`. This is the string nobody would have found by grepping for prose: it decides month and
|
|
106
|
+
weekday names through `Intl`, so leaving it would have produced English labels around Indonesian
|
|
107
|
+
months, which is the mixed interface this release exists to remove. The page's own `lang` still
|
|
108
|
+
wins, which makes `<html lang="id">` the whole migration for dates.
|
|
109
|
+
- **Migrating an Indonesian app** costs no fork: every string was already overridable per element
|
|
110
|
+
with `data-fdy-text-<key>` (kebab-cased), and `data-fdy-msg-*` for validation messages. That hatch
|
|
111
|
+
shipped in 1.39.0, so the half of `NEXT-UP.md` #6 written as "build an override hook" was already
|
|
112
|
+
done, and only the default was ever in question. `COMPONENTS.md` §Language documents it.
|
|
113
|
+
- **Called 2.0.0 rather than 1.55.0 on purpose.** No API changed, but every raw-path screen renders
|
|
114
|
+
different words, and a major version is the only signal npm gives a consumer that an upgrade needs
|
|
115
|
+
looking at. Hiding that behind a minor is the same class of quiet mismatch the last four releases
|
|
116
|
+
have been about.
|
|
117
|
+
### Changed
|
|
118
|
+
- **Seven messages were translated correctly and still read like a machine.** The flip to English
|
|
119
|
+
produced complete, formal sentences where a product writes short ones: `Value is too small.` /
|
|
120
|
+
`Value is too large.` are now `Too small.` / `Too large.` (matching `Too short.` / `Too long.`
|
|
121
|
+
beside them), `Values do not match.` is `Values don’t match.`, `Does not match the expected
|
|
122
|
+
format.` is `Doesn’t match the required format.`, `Not a valid increment.` drops the jargon for
|
|
123
|
+
`Not a valid step.`, and upload's `File type not supported.` / `Larger than the {max} limit.` are
|
|
124
|
+
`Unsupported file type.` / `Over the {max} limit.` Contractions carry most of it, written with a
|
|
125
|
+
typographic apostrophe so the string needs no escaping.
|
|
126
|
+
- **The donut legend separator is `·`, not an em dash.** It rendered `posted — 76%`; the kit uses a
|
|
127
|
+
middot everywhere else it separates two short things, and it is the one place the dash was in
|
|
128
|
+
front of a reader rather than in a comment. The example app's choose-from-list display line
|
|
129
|
+
(`code — name`) follows.
|
|
130
|
+
|
|
131
|
+
### Added: guards
|
|
132
|
+
- The English guard now covers `src/freeday-*.js`, not just the typed adapters, so the promise is
|
|
133
|
+
enforced on every path instead of stated on one. Verified by reverting a single string: it fails.
|
|
134
|
+
- **And a word list is not enough, which this release proved twice.** The list missed `Lanjut` and
|
|
135
|
+
`Selesai` in the stepper and `Mengunggah…` in upload, and its own comment already warned that
|
|
136
|
+
`pencarian` hides behind `\bcari\b`. So the complete vocabulary is now pinned: all 39 strings,
|
|
137
|
+
asserted whole, so changing one has to be deliberate and visible in a diff.
|
|
138
|
+
- `browser/text-override.mjs` and `browser/upload-states.mjs` assert the new defaults. The override
|
|
139
|
+
half is untouched and still passes, because it is the migration path and had to keep working in the
|
|
140
|
+
same breath as the change that makes it necessary.
|
|
141
|
+
### Added: guards
|
|
142
|
+
- **The Blazor stack had no behavioural gate at all, and now has one.** Its only check was
|
|
143
|
+
`dotnet build`, which is compilation, across twelve typed components, and `NEXT-UP.md` #5 described a
|
|
144
|
+
manual runtime verification via `drive-*.mjs` + CDP that does not exist in the repository. That
|
|
145
|
+
gap mattered most for the component added the same night: `FdyAppShell` reconciles a nullable
|
|
146
|
+
two-way binding against a JS enhancer, which is precisely the kind of logic a compiler cannot see.
|
|
147
|
+
`test/blazor/` (bUnit + xunit, 7 tests) renders the components for real: who wins on hydrate when
|
|
148
|
+
the caller binds a value versus leaves it null, what the enhancer's `fdy-app-nav` event does to
|
|
149
|
+
the binding, and that the two do not chase each other. Verified by sabotage: removing the echo
|
|
150
|
+
guard fails one test, breaking the hydrate push fails another, one each.
|
|
151
|
+
- It is wired into `npm run test:blazor` and into `ci.yml`, so it runs where releases are made
|
|
152
|
+
rather than where someone remembers. Deliberately placed OUTSIDE `adapters/`, which `package.json`
|
|
153
|
+
ships as a whole directory, and a test project has no business in a consumer's `node_modules`.
|
|
154
|
+
|
|
155
|
+
### Fixed: the kit's own docs
|
|
156
|
+
- **The docs site shipped the wrong version number for three releases.** `docs/index.html` said
|
|
157
|
+
`v1.51.0` in its eyebrow and footer through 1.52.0, 1.52.1 and 1.53.0, and
|
|
158
|
+
`docs/getting-started.md` told readers to install `^1.34.0`, eighteen releases stale. `NEXT-UP.md`
|
|
159
|
+
already carried the right instruction (don't work from a memorised list, `git grep` the old
|
|
160
|
+
version) and it was still missed every time, because a runbook step is something a person has to
|
|
161
|
+
remember. The stamps are correct now and a test asserts them against `package.json`, so the next
|
|
162
|
+
miss fails the suite instead of reaching the live site.
|
|
163
|
+
- **`HANDOFF.md` was 419 lines of per-version history under a header saying "this is not a
|
|
164
|
+
changelog"**, a duplicate of `CHANGELOG.md` that stopped being updated twenty releases before
|
|
165
|
+
2.0.0. It is a snapshot again: what the kit is now, what the guards cover, how a release goes out,
|
|
166
|
+
and what is known-unfinished. The same cleanup was done once before, in v1.20.0, for the same
|
|
167
|
+
reason: a stale snapshot is worse than none.
|
|
168
|
+
### Known
|
|
169
|
+
- `docs/index.html` is Indonesian prose with its own ID→EN toggle for authored demo chrome, and that
|
|
170
|
+
toggle does not reach enhancer-rendered strings. In Indonesian mode the demos now show English
|
|
171
|
+
widget text. Left as-is deliberately: the page's job is to show what the kit actually does, and
|
|
172
|
+
putting `data-fdy-text-*` on all 42 enhancer instances would hide the default it documents.
|
|
173
|
+
|
|
174
|
+
## [1.54.0] - 2026-08-25
|
|
7
175
|
### Added
|
|
8
|
-
- **`<FdyAppShell>`
|
|
176
|
+
- **`<FdyAppShell>` makes the shell the eleventh typed component**, in Vue, React and Blazor
|
|
9
177
|
(`NEXT-UP.md` #8, stage 2). One model: `navOpen` means *the nav is visible to the reader*, and the
|
|
10
|
-
kit owns the mapping
|
|
178
|
+
kit owns the mapping: above the nav breakpoint a hidden nav is `--nav-collapsed`, below it a
|
|
11
179
|
visible nav is `--nav-open`. An app never reasons about the viewport to answer a question about
|
|
12
180
|
its own UI. Vue `v-model:navOpen` · React `navOpen`/`onNavOpenChange` · Blazor `@bind-NavOpen`.
|
|
13
181
|
- **The prop is optional in all three, and that is the design.** Unbound, the shell starts from the
|
|
14
182
|
viewport: a column on a wide screen, hidden on a narrow one. A caller cannot express that as a
|
|
15
183
|
single initial value before it knows the viewport, so `undefined` (Vue/React) and `null` (Blazor)
|
|
16
184
|
mean "you decide". Bound, the caller wins and is pushed down on mount.
|
|
17
|
-
- **`adapters/core/app-shell.js`** (+ `.d.ts`)
|
|
185
|
+
- **`adapters/core/app-shell.js`** (+ `.d.ts`) holds the Tab trap, `inert` bookkeeping and focus restore
|
|
18
186
|
shared by the Vue and React wrappers, following `adapters/core/table-model.js`. `NAV_QUERY` is
|
|
19
187
|
built from `tokens/breakpoints.mjs` rather than repeating `721`, so the JS and `app-shell.css`
|
|
20
188
|
cannot drift. Blazor does not use it: `freeday-blazor.js` says the enhancers stay the source of
|
|
21
189
|
truth, so the Blazor shell renders `data-fdy-app` and BINDS to `freeday-app-shell.js`. Two
|
|
22
190
|
implementations, not four, and a guard holds them to one contract.
|
|
23
|
-
- **`fdy-app-nav
|
|
191
|
+
- **`fdy-app-nav`:** the enhancer now emits a bubbling CustomEvent (`detail {visible}`) on every
|
|
24
192
|
real change, and takes `FreedayAppShell.setVisible(root, visible)` / `isVisible(root)` from
|
|
25
193
|
outside. That is what the Blazor binding rides on, and what any vanilla app persisting a collapsed
|
|
26
|
-
preference needed. The event fires for viewport-driven changes too
|
|
194
|
+
preference needed. The event fires for viewport-driven changes too, since narrowing hides a nav that
|
|
27
195
|
was a visible column, and a bound value that stayed `true` would describe a panel nobody can see.
|
|
28
196
|
Setting what is already set announces nothing, so a bound host cannot loop on its own echo.
|
|
29
197
|
### Docs
|
|
@@ -33,21 +201,21 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
33
201
|
- `NEXT-UP.md` #8 is struck through rather than deleted, and its number kept: the CHANGELOG, the
|
|
34
202
|
design spec and two guard headers already say "NEXT-UP #8", so renumbering the rows under it would
|
|
35
203
|
quietly break references that are already written down.
|
|
36
|
-
### Added
|
|
37
|
-
- `browser/adapter.mjs` runs one description of the shell contract against BOTH typed stacks
|
|
204
|
+
### Added: guards
|
|
205
|
+
- `browser/adapter.mjs` runs one description of the shell contract against BOTH typed stacks:
|
|
38
206
|
viewport default, focus into the panel, the Tab cycle, Escape returning focus, `inert`, and a nav
|
|
39
207
|
link closing the overlay. Verified by sabotage: with `applyShellState` and the focus calls stubbed
|
|
40
208
|
out, React fails three of three and Vue two of three, the third being the sidebar click path the
|
|
41
209
|
sabotage did not touch.
|
|
42
210
|
- `browser/app-shell.mjs` gains the host-binding pair (`fdy-app-nav` ordering, `setVisible` doing the
|
|
43
211
|
whole job and staying silent on a no-op) and the path only Blazor takes: a shell that does not
|
|
44
|
-
exist at `DOMContentLoaded`, hydrated afterwards by handing `initAll` the component's own root
|
|
45
|
-
which is the element itself
|
|
212
|
+
exist at `DOMContentLoaded`, hydrated afterwards by handing `initAll` the component's own root,
|
|
213
|
+
which is the element itself and not a descendant of it.
|
|
46
214
|
- `dotnet build` on the Blazor RCL is clean (0 warnings, 0 errors).
|
|
47
215
|
|
|
48
|
-
## [1.53.0]
|
|
216
|
+
## [1.53.0] - 2026-08-24
|
|
49
217
|
### Added
|
|
50
|
-
- **`freeday-app-shell.js
|
|
218
|
+
- **`freeday-app-shell.js`: the shell finally ships its own behaviour** (`NEXT-UP.md` #8, reported
|
|
51
219
|
twice). `.fdy-app` shipped `__navtoggle`, `__backdrop` and the `--nav-open` / `--nav-collapsed`
|
|
52
220
|
classes with **zero JS**, and `COMPONENTS.md` told every consumer to wire it in two sentences that
|
|
53
221
|
never mentioned Escape, focus, `inert` or focus restore. Follow that exactly and you get an
|
|
@@ -62,10 +230,10 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
62
230
|
≥721px is `width:0;overflow:hidden`; off-canvas at ≤720px is `translateX(-100%)`. Both hide the
|
|
63
231
|
panel from the eye and neither hides it from the keyboard, so a nav nobody could see still
|
|
64
232
|
swallowed every Tab on the way into the page. `__sidebar` is now `inert` whenever the nav is not
|
|
65
|
-
visible
|
|
233
|
+
visible, one rule for both modes rather than two patches.
|
|
66
234
|
- **Crossing the breakpoint with the overlay open stranded the page.** Widening the window left
|
|
67
235
|
`--nav-open` set and `__content` `inert`, so the page underneath could never be clicked or read
|
|
68
|
-
again
|
|
236
|
+
again, the same failure that produced `breakpoints.nav` in v1.20.0. The enhancer clears the
|
|
69
237
|
overlay state on the media-query change, and deliberately does *not* move focus while doing it: a
|
|
70
238
|
resize is not a user asking to go somewhere.
|
|
71
239
|
### Changed
|
|
@@ -74,26 +242,26 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
74
242
|
close-on-nav-click, `reference-screen.html` handled neither, and *neither* trapped focus, marked
|
|
75
243
|
the content `inert`, or restored focus. Two versions in one repository disagreeing is the whole
|
|
76
244
|
argument for the kit owning this.
|
|
77
|
-
### Added
|
|
245
|
+
### Added: guards
|
|
78
246
|
- `browser/app-shell.mjs` (5 tests), each verified to fail with the enhancer switched off: collapse
|
|
79
247
|
and its `inert` at ≥721px, the overlay and its `inert` at ≤720px, focus entering the panel and
|
|
80
248
|
cycling inside it under **trusted** Tab presses, Escape returning focus to the toggle, backdrop
|
|
81
249
|
and nav-item dismissal, and the breakpoint crossing. One of the five originally passed against a
|
|
82
|
-
shell with no behaviour at all
|
|
83
|
-
|
|
250
|
+
shell with no behaviour at all, because the nav never opened, so "it is closed" was true and
|
|
251
|
+
meaningless. It now asserts it opened first.
|
|
84
252
|
- `setViewport()` in the browser harness (`Emulation.setDeviceMetricsOverride`). A responsive
|
|
85
253
|
contract cannot be tested at a fixed window size, and the moment a layout *crosses* a breakpoint
|
|
86
254
|
is exactly where its state gets stranded.
|
|
87
255
|
|
|
88
|
-
## [1.52.1]
|
|
256
|
+
## [1.52.1] - 2026-08-24
|
|
89
257
|
### Docs
|
|
90
258
|
- **1.51.1's close-watcher caveat named the wrong condition** (#048). It said a `showModal()` with
|
|
91
|
-
no transient activation
|
|
259
|
+
no transient activation ("from a timer, or after an `await` that outlived the click") has its
|
|
92
260
|
close watcher grouped with the dialog below, so one Escape closes both. A consumer audited three
|
|
93
261
|
call sites of exactly that shape and could not reproduce it. They were right, and the reason is
|
|
94
262
|
that the line is **sticky** activation, not transient: grouping needs a page that has received no
|
|
95
|
-
user input *at all*. Six seconds after a single click
|
|
96
|
-
`navigator.userActivation.isActive === false
|
|
263
|
+
user input *at all*. Six seconds after a single click, with transient activation long expired and
|
|
264
|
+
`navigator.userActivation.isActive === false`, two stacked overlays are still independent. So the
|
|
97
265
|
grouping is reachable in a test harness and nowhere else, which is exactly where the kit met it.
|
|
98
266
|
- **What the advice was protecting was real, but it is a different failure.** Open either overlay
|
|
99
267
|
from script and the second one's `cancel` event arrives **non-cancelable**: Escape still closes
|
|
@@ -101,55 +269,55 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
101
269
|
on that dialog silently stops working. Only when both overlays are opened by a real gesture is that
|
|
102
270
|
veto available. The Modal entry now says this instead.
|
|
103
271
|
- The note also proposed documenting that the typed wrappers' `@cancel.prevent` makes them immune to
|
|
104
|
-
the grouping. **Measured, they are not
|
|
272
|
+
the grouping. **Measured, they are not.** In a page with no activation the `cancel` event is not
|
|
105
273
|
cancelable at all, so preventing it does nothing and both dialogs close regardless. That claim is
|
|
106
274
|
not in the docs, and this is why.
|
|
107
|
-
### Added
|
|
108
|
-
- `browser/overlay-stack.mjs` gains the whole matrix as one assertion
|
|
109
|
-
overlays × what one Escape leaves open × whether it could be refused
|
|
275
|
+
### Added: guards
|
|
276
|
+
- `browser/overlay-stack.mjs` gains the whole matrix as one assertion: four ways to open two
|
|
277
|
+
overlays × what one Escape leaves open × whether it could be refused, so a row changing is
|
|
110
278
|
reported as news about the browser rather than a regression in the kit. Verified identical on
|
|
111
279
|
Chromium 133 (the suite's engine) and Chrome 151.
|
|
112
280
|
- The fixture records each `cancel` event's `cancelable` flag; nothing prevents it, so the older
|
|
113
281
|
tests measure what they always did.
|
|
114
282
|
|
|
115
|
-
### Fixed
|
|
283
|
+
### Fixed: the kit's own suite
|
|
116
284
|
- **The browser guards did not run where the releases are made.** CI ran the 59 unit tests and
|
|
117
|
-
published; the 58 browser tests across 18 specs
|
|
118
|
-
accessible subtrees, the user-activation matrix above
|
|
285
|
+
published; the 58 browser tests across 18 specs (pixel paint order for stacked overlays, chart
|
|
286
|
+
accessible subtrees, the user-activation matrix above) ran only when someone remembered to run
|
|
119
287
|
them locally. Automating the release through OIDC in 1.52.0 removed the someone. `ci.yml` now runs
|
|
120
288
|
both suites on every push and is *called* by `publish.yml` (`needs: test`), so the step list that
|
|
121
289
|
guards a release cannot drift from the one that runs during development, and a tag with red guards
|
|
122
290
|
never reaches npm.
|
|
123
291
|
- **A job that merely ran the command would have been green while running nothing.** Every browser
|
|
124
292
|
spec skips itself when no Chrome is found, and a skipped suite still exits 0. So the job checks
|
|
125
|
-
the binary before it starts and then parses its own summary, failing on any skip
|
|
293
|
+
the binary before it starts and then parses its own summary, failing on any skip. "The job was
|
|
126
294
|
green" and "the guards ran" were separate facts, and only one of them was enforceable. Both
|
|
127
295
|
demonstrated on real runs: the assertion green at 58/0, and a deliberately broken `CHROME_BIN`
|
|
128
296
|
red.
|
|
129
297
|
- **The harness can now drive any installed Chrome.** `chrome-headless-shell` is headless by
|
|
130
298
|
construction; an ordinary Chrome binary is not and, on a machine with no display, looks for one
|
|
131
299
|
and dies. It now gets `--headless=new` (and `--no-sandbox` only under `CI`), which is what lets
|
|
132
|
-
`CHROME_BIN=<some other Chrome>` compare engines
|
|
300
|
+
`CHROME_BIN=<some other Chrome>` compare engines, the manoeuvre that settled #048. CI runs the
|
|
133
301
|
runner's Chrome stable, local runs Chromium 133, so both engines `COMPONENTS.md` names are
|
|
134
302
|
genuinely exercised.
|
|
135
|
-
- Spec: `docs/superpowers/specs/2026-08-24-browser-guards-in-ci-design.md`. No shipped file changed
|
|
303
|
+
- Spec: `docs/superpowers/specs/2026-08-24-browser-guards-in-ci-design.md`. No shipped file changed:
|
|
136
304
|
`browser/` and `.github/` are not in the published tarball, so there is no release for this.
|
|
137
305
|
|
|
138
|
-
## [1.52.0]
|
|
306
|
+
## [1.52.0] - 2026-08-24
|
|
139
307
|
### Fixed
|
|
140
308
|
- **A chart's legend, bar values and donut centre were exposed to assistive tech after all** (#047).
|
|
141
|
-
`COMPONENTS.md` explained the a11y contract with the ARIA spec
|
|
309
|
+
`COMPONENTS.md` explained the a11y contract with the ARIA spec, where `role="img"` is *Children
|
|
142
310
|
Presentational*, therefore "rendered bar values, axis ticks and legends are not exposed". Measured
|
|
143
311
|
against Chrome's accessibility tree, that is not what happens: a donut kept `list` → three
|
|
144
312
|
`listitem` → `StaticText "posted — 76%"` and its centre total `1687`, the legacy `.fdy-bars` kept
|
|
145
|
-
every value and label (`12`, `Jan`, `30`, `Feb`…), and a two-series cartesian kept its legend
|
|
146
|
-
live
|
|
313
|
+
every value and label (`12`, `Jan`, `30`, `Feb`…), and a two-series cartesian kept its legend, all
|
|
314
|
+
live and none ignored. What actually protected the advice was only that AT treats a *named*
|
|
147
315
|
`role="img"` as a leaf and does not descend; the docs presented a habit as a guarantee. Axis ticks
|
|
148
316
|
were the one true claim, and for an unstated reason: the renderer already sets `aria-hidden` on the
|
|
149
317
|
`<svg>` itself.
|
|
150
318
|
`ensureImg()` now marks the rendered children `aria-hidden="true"`, so the author's `aria-label`
|
|
151
319
|
really is the entire text alternative and cannot be double-announced by an AT that does descend.
|
|
152
|
-
Nothing is removed and nothing moves
|
|
320
|
+
Nothing is removed and nothing moves: the legend and the centre are still in the DOM and still
|
|
153
321
|
painted.
|
|
154
322
|
- **The hiding requires a name.** A chart with no `aria-label`/`aria-labelledby` keeps its contents
|
|
155
323
|
exposed: pruning the subtree of an unlabelled chart would leave an image with no name *and* no
|
|
@@ -158,63 +326,63 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
158
326
|
### Docs
|
|
159
327
|
- The Charts a11y bullet now describes the kit instead of the spec: what is hidden, that `role="img"`
|
|
160
328
|
alone would not have hidden it, and what happens when the label is missing.
|
|
161
|
-
### Added
|
|
329
|
+
### Added: guards
|
|
162
330
|
- `browser/chart-a11y.mjs` (5 tests) over donut, legacy bars and cartesian: the accessible subtree
|
|
163
331
|
exposes no text of its own, the author label survives as the name, the legend is still rendered and
|
|
164
332
|
visible (hidden, not deleted), and an unlabelled chart still reads. The first three were verified to
|
|
165
333
|
fail against 1.51.1.
|
|
166
|
-
- `axSubtree()` in the browser harness
|
|
334
|
+
- `axSubtree()` in the browser harness reports every AX node under an element, ignored ones included. The
|
|
167
335
|
DOM cannot answer this class of question: `aria-hidden` changes no markup, so a `querySelector`
|
|
168
336
|
assert passes identically before and after the fix. Same reason the 1.51.0 scaling bug needed a
|
|
169
337
|
real measurement rather than a source read.
|
|
170
338
|
|
|
171
|
-
## [1.51.1]
|
|
339
|
+
## [1.51.1] - 2026-08-24
|
|
172
340
|
### Docs
|
|
173
341
|
- **`COMPONENTS.md` never said whether two overlays may be open at once** (#046). Modal and Drawer
|
|
174
342
|
were documented in isolation, so "can a confirm modal open over an open drawer?" had no answer in
|
|
175
|
-
the file
|
|
343
|
+
the file, and the built CSS does not answer it either: the only overlay `z-index` in the bundle
|
|
176
344
|
belongs to the toast region. Both entries now state the rule, because both components are
|
|
177
345
|
`<dialog>` opened with `showModal()` and therefore enter the **top layer**, where paint order is
|
|
178
346
|
the order they were opened: the later modal is above the drawer with no `z-index` from either,
|
|
179
347
|
Escape closes the topmost first (so cancelling returns to the still-open drawer), and closing them
|
|
180
348
|
out of order leaves the survivor painted and dismissible.
|
|
181
349
|
- **The one caveat is the browser's, and it is now written down**: open each overlay from the
|
|
182
|
-
gesture that asked for it. A `showModal()` with no user activation
|
|
183
|
-
`await` that outlived the click
|
|
350
|
+
gesture that asked for it. A `showModal()` with no user activation, from a timer or after an
|
|
351
|
+
`await` that outlived the click, has its close watcher GROUPED with the dialog below it, and a
|
|
184
352
|
single Escape then closes both. Measured, not assumed: two untrusted `.click()` opens, one Escape,
|
|
185
353
|
both dialogs shut.
|
|
186
|
-
### Added
|
|
187
|
-
- `browser/overlay-stack.mjs` (3 tests) proves that paragraph instead of restating it
|
|
354
|
+
### Added: guards
|
|
355
|
+
- `browser/overlay-stack.mjs` (3 tests) proves that paragraph instead of restating it, the #041
|
|
188
356
|
lesson applied to a documentation change. Paint order is asserted in **pixels** at the overlap of
|
|
189
357
|
the two, because a modal dialog makes the rest of the document inert and `elementFromPoint` will
|
|
190
358
|
name the dialog whatever is really on top; the overlays are opened with **trusted** clicks, or the
|
|
191
359
|
close-watcher grouping above would fail the Escape assertion for a reason that is not the kit's.
|
|
192
|
-
- **`pressKey` in the browser harness sent every key but Tab with virtual-key code 0
|
|
193
|
-
to the page
|
|
360
|
+
- **`pressKey` in the browser harness sent every key but Tab with virtual-key code 0**, delivered
|
|
361
|
+
to the page but not acted on by the browser: Escape reached a `keydown` listener yet never closed
|
|
194
362
|
a native `<dialog>`. A dismissal guard written with it would have passed for the wrong reason and
|
|
195
363
|
reported a broken kit as fine. It now carries the real `windowsVirtualKeyCode` /
|
|
196
364
|
`nativeVirtualKeyCode` for the keys the suite presses.
|
|
197
365
|
|
|
198
|
-
## [1.51.0]
|
|
366
|
+
## [1.51.0] - 2026-08-21
|
|
199
367
|
### Fixed
|
|
200
368
|
- **A cartesian chart's axis text and dots grew with the container** (#042). `renderCartesian` built
|
|
201
369
|
a fixed `viewBox="0 0 320 180"` and let `.fdy-chart-xy__plot` stretch it to `width:100%`, so every
|
|
202
370
|
size the renderer expressed in user units was multiplied by `plotWidth / 320`. `font-size:9px` on
|
|
203
371
|
a tick is not nine screen pixels at any width but 320px: measured on a real console it rendered
|
|
204
372
|
13px at a 350px plot and **81px at a 2232px** one, making the chart's smallest, most secondary
|
|
205
|
-
text the largest type on the page. `r="2.2"` did the same to the dots
|
|
373
|
+
text the largest type on the page. `r="2.2"` did the same to the dots: 4.8px across at 350px,
|
|
206
374
|
30.7px at 2232px. The chart now measures `__plot` and sizes the `viewBox` to it in CSS pixels, so
|
|
207
375
|
one user unit is one pixel and every declared size means what it says at every width; a
|
|
208
376
|
`ResizeObserver` repaints it when the box changes (a collapsing sidebar moves a plot ~300px at a
|
|
209
|
-
fixed viewport, so this is the common case, not an edge case). A chart that measures 0
|
|
210
|
-
or inside `display:none
|
|
377
|
+
fixed viewport, so this is the common case, not an edge case). A chart that measures 0, whether detached
|
|
378
|
+
or inside `display:none`, falls back to the old box and repaints when it is laid out.
|
|
211
379
|
- **A currency y-axis was clipped by a gutter sized for a percent one** (#042). `PL` was a constant
|
|
212
380
|
38 user units, which was ~4 characters at one scale and something else at every other. It is now
|
|
213
381
|
derived from the widest formatted tick the axis will actually draw, capped at 35% of the plot.
|
|
214
382
|
### Changed
|
|
215
383
|
- **Axis type is a real CSS size**, set by `--fdy-chart-tick-size` on `.fdy-chart-xy` (default
|
|
216
384
|
`var(--text-xs)`). `font-size` moved off `.fdy-chart-xy__tick` / `__xlabel` onto
|
|
217
|
-
`.fdy-chart-xy__plot`, which the SVG text inherits
|
|
385
|
+
`.fdy-chart-xy__plot`, which the SVG text inherits, so one computed read gives the renderer the real
|
|
218
386
|
pixel size it needs for the y-gutter and for the x-label autoskip, so an override flows to the
|
|
219
387
|
text *and* to the geometry around it. Before this, chart text could not be restyled at all: any
|
|
220
388
|
value set was multiplied by a scale the consumer could not see or compute.
|
|
@@ -224,80 +392,80 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
224
392
|
chart is `role="img"` + `aria-label` "with a `<table>` fallback inside the element"; the renderer
|
|
225
393
|
builds no table and wipes the element on every render path. Worse, `role="img"` makes descendants
|
|
226
394
|
presentational, so the bar values, ticks and legend the renderer *does* draw are not exposed
|
|
227
|
-
either
|
|
395
|
+
either, so the author's `aria-label` is the entire text alternative, and the docs now say so and ask
|
|
228
396
|
for one that carries the numbers. The four dead fallback tables in `docs/index.html` and
|
|
229
397
|
`docs/reference-screen.html`, which no reader has ever reached, are gone with it.
|
|
230
398
|
- **The kit's own chart sizing is written down** (#041): `.fdy-chart-xy__plot` is
|
|
231
399
|
`aspect-ratio:16/9` + `min-height:8rem`, `.fdy-bars` a fixed `9rem`, `.fdy-sparkline` an
|
|
232
|
-
inline-block `8rem × 2.25rem
|
|
400
|
+
inline-block `8rem × 2.25rem`, with the note to override those rather than the chart root, since
|
|
233
401
|
a root `height` fights `aspect-ratio` instead of setting it.
|
|
234
|
-
### Added
|
|
235
|
-
- **`browser/chart-scale.mjs`**
|
|
402
|
+
### Added: guards
|
|
403
|
+
- **`browser/chart-scale.mjs`** measures the axis label box and dot diameter at plot widths of
|
|
236
404
|
350 / 696 / 1400 / 2232px and must not vary by more than a pixel, must stay smaller than body
|
|
237
405
|
text, and the `viewBox` must equal the measured plot. A second spec widens a container and asserts
|
|
238
406
|
the repaint. This class of bug is invisible to a source read: `font-size:9px` and `r="2.2"` look
|
|
239
|
-
like correct sizes, and the one thing in the plot that *was* protected
|
|
240
|
-
`vector-effect:non-scaling-stroke` on the line
|
|
407
|
+
like correct sizes, and the one thing in the plot that *was* protected,
|
|
408
|
+
`vector-effect:non-scaling-stroke` on the line, is what made the other three look protected too.
|
|
241
409
|
Only a real layout tells them apart.
|
|
242
410
|
|
|
243
|
-
## [1.50.0]
|
|
411
|
+
## [1.50.0] - 2026-08-20
|
|
244
412
|
### Fixed
|
|
245
413
|
- **A toast raised from inside a modal was painted behind it** (#027). `.fdy-toast-region` carried
|
|
246
414
|
`z-index: 200`, which can never win: `.fdy-modal` is a native `<dialog>` opened with `showModal()`
|
|
247
415
|
and therefore lives in the **top layer**, above the whole z-index universe. An error raised by an
|
|
248
416
|
action taken inside a dialog was dimmed by that dialog's backdrop and mostly hidden under the
|
|
249
|
-
dialog
|
|
417
|
+
dialog, so the reader saw a confirmation still asking a question they had already answered, and
|
|
250
418
|
no reason why. The region is now `popover="manual"` and `freeday-toast.js` calls `showPopover()`
|
|
251
|
-
as each toast lands (per toast, not once
|
|
419
|
+
as each toast lands (per toast, not once, because the top layer is ordered by when you joined, so a
|
|
252
420
|
dialog opened after the last toast would otherwise sit above it). Guarded: where `showPopover` is
|
|
253
421
|
unavailable or throws, the region stays exactly what it was.
|
|
254
422
|
- **A checkbox shrank when its label was long** (#027). `.fdy-check` is an inline-flex row and its
|
|
255
|
-
box declared a width
|
|
423
|
+
box declared a width, but a flex item with a width is still shrinkable, so a label that wrapped
|
|
256
424
|
to three lines squeezed the box from 18px to 13px while leaving it 18px tall. A group of five
|
|
257
425
|
rendered three different sizes, none of them square. `flex: none` on `.fdy-check input`,
|
|
258
|
-
`.fdy-radio input`, the `.fdy-switch` track and the standalone `.fdy-checkbox
|
|
426
|
+
`.fdy-radio input`, the `.fdy-switch` track and the standalone `.fdy-checkbox`. All four are
|
|
259
427
|
flex children somewhere in the kit.
|
|
260
|
-
### Added
|
|
261
|
-
- **`pixelAt(x, y)` in `browser/harness.mjs`**
|
|
428
|
+
### Added: guards
|
|
429
|
+
- **`pixelAt(x, y)` in `browser/harness.mjs`** takes a 1×1 `Page.captureScreenshot`, decoded in-process.
|
|
262
430
|
Stacking above a modal cannot be asserted with `elementFromPoint`: a modal dialog makes the rest
|
|
263
431
|
of the document inert, so a hit test outside it returns the dialog whatever the paint order is,
|
|
264
432
|
reporting a working fix as broken. The new guard measures the colour actually composited at the
|
|
265
|
-
toast's centre. It waits for both fade-ins to finish first
|
|
433
|
+
toast's centre. It waits for both fade-ins to finish first, since a screenshot taken mid-animation
|
|
266
434
|
returns a blend of the two elements and flakes in both directions.
|
|
267
435
|
- **Selection controls are asserted square and equal** across a group whose labels run one to three
|
|
268
436
|
lines, so a box that shrank in both axes cannot pass a width-only comparison. All four controls
|
|
269
437
|
the fix touched are measured, each in the container it actually ships in: the standalone
|
|
270
|
-
`.fdy-checkbox` inside `.fdy-filter__check
|
|
271
|
-
filter popover and a CFL option list
|
|
438
|
+
`.fdy-checkbox` inside `.fdy-filter__check`, the flex row all three typed adapters render in a
|
|
439
|
+
filter popover and a CFL option list, and the switch track against a switch of its own, since it
|
|
272
440
|
is the one control that is not square. The first version of this guard held the standalone
|
|
273
441
|
checkbox in a plain block, where it cannot shrink at all, so removing `flex: none` from the one
|
|
274
442
|
box every adapter puts on screen left it passing.
|
|
275
443
|
|
|
276
|
-
## [1.49.0]
|
|
444
|
+
## [1.49.0] - 2026-08-20
|
|
277
445
|
### Added
|
|
278
|
-
- **`FdyTableColumn.labelHidden`** (#026). A column of row CONTROLS
|
|
446
|
+
- **`FdyTableColumn.labelHidden`** (#026). A column of row CONTROLS, an edit button or a row menu,
|
|
279
447
|
could be named or quiet, not both: `label` renders as bare text in the `<th>`, so a designer who
|
|
280
448
|
does not want a word above a column of icons was left with an empty header, which assistive tech
|
|
281
449
|
announces as nothing. With `labelHidden: true` the label renders inside `.fdy-visually-hidden`:
|
|
282
450
|
the cell looks empty and the column is still named. The label keeps naming the column's filter
|
|
283
451
|
popover and sort button, so it stays meaningful either way.
|
|
284
|
-
Contract and ALL THREE typed adapters
|
|
452
|
+
Contract and ALL THREE typed adapters: Vue, React and Blazor. Each renders its own header, so
|
|
285
453
|
each spends the flag itself; Blazor takes `LabelHidden` on `FdyTableColumn<TRow>`.
|
|
286
|
-
### Added
|
|
454
|
+
### Added: guards
|
|
287
455
|
- **The column contract now has to reach all three adapters** (#026). Vue and React consume
|
|
288
|
-
`table-model.d.ts` and TypeScript keeps them honest, but Blazor RE-DECLARES the column in C
|
|
289
|
-
hand copy
|
|
456
|
+
`table-model.d.ts` and TypeScript keeps them honest, but Blazor RE-DECLARES the column in C#, a
|
|
457
|
+
hand copy and the surface that can fall behind silently. A test asserts every contract property
|
|
290
458
|
exists on Blazor's `FdyTableColumn<TRow>`; coverage, not equality, so it needs no exemption list.
|
|
291
|
-
- **A hidden label is clipped, not dropped
|
|
459
|
+
- **A hidden label is clipped, not dropped**, asserted in a real browser for both JS adapters
|
|
292
460
|
(`browser/adapter.mjs`), because the claim is about computed geometry: a span carrying the class
|
|
293
461
|
with no CSS behind it passes any string match and still prints the word.
|
|
294
462
|
|
|
295
|
-
## [1.48.1]
|
|
463
|
+
## [1.48.1] - 2026-08-20
|
|
296
464
|
### Fixed
|
|
297
465
|
- **A date column sorted correctly and filtered nothing** (#025). `FdyTable`'s client-mode date
|
|
298
466
|
filter read a cell with `dateOnly`, which **sliced** the string, while its date SORT reads the same
|
|
299
|
-
cell with `toTime`, which **parses**. A date column normally renders a formatted date
|
|
300
|
-
what `value` is for
|
|
467
|
+
cell with `toTime`, which **parses**. A date column normally renders a formatted date, which is
|
|
468
|
+
what `value` is for, so `"18 Mar 2024"` sliced to `"18 Mar 202"` and compared as text against an
|
|
301
469
|
ISO bound: every row failed, silently. The working sort is what made the broken filter look
|
|
302
470
|
trustworthy.
|
|
303
471
|
- **A `Date` cell was read as its UTC day, not the reader's** (#025). `toISOString()` east of
|
|
@@ -305,49 +473,49 @@ Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
|
305
473
|
a row dated the 18th.
|
|
306
474
|
- Text that is not a date now yields `''` and is EXCLUDED by an active date filter, rather than
|
|
307
475
|
slicing to something that compares as less than every bound and matching everything.
|
|
308
|
-
### Added
|
|
476
|
+
### Added: guards
|
|
309
477
|
- Three tests in `test/table-model.test.mjs`, each verified to fail against the old body: a
|
|
310
478
|
formatted `value`, a `Date` cell at a positive UTC offset, and unparseable text. The existing date
|
|
311
479
|
test only ever filtered a column with no accessor holding an already-ISO string, which is the one
|
|
312
480
|
shape neither bug can affect.
|
|
313
481
|
|
|
314
|
-
## [1.48.0]
|
|
482
|
+
## [1.48.0] - 2026-08-20
|
|
315
483
|
### Reverted
|
|
316
484
|
- **1.47.0's readonly focus ring (#024) is withdrawn.** The report was measured wrong. The control in
|
|
317
|
-
question
|
|
485
|
+
question, a `CflField`'s readonly display input, sits inside `.fdy-input-group`, and
|
|
318
486
|
`.fdy-input-group:focus-within` already carries the border and the 3px ring for the WHOLE control;
|
|
319
487
|
`.fdy-input-group .fdy-input:focus` clears the inner input deliberately so the two do not nest. The
|
|
320
488
|
audit measured the INPUT, found no shadow on it, and concluded there was no focus indicator.
|
|
321
|
-
Measured on the group, focus shows `--color-primary` at full strength
|
|
489
|
+
Measured on the group, focus shows `--color-primary` at full strength: 6.56:1. 1.47.0 therefore
|
|
322
490
|
painted a muted ring INSIDE the blue one, which is a regression rather than a fix.
|
|
323
|
-
### Added
|
|
324
|
-
- `test/css.test.mjs` pins the arrangement
|
|
491
|
+
### Added: guards
|
|
492
|
+
- `test/css.test.mjs` pins the arrangement, the group rings and the inner input does not, so the next
|
|
325
493
|
reader finds the answer instead of repeating the conclusion.
|
|
326
494
|
|
|
327
|
-
## [1.46.0]
|
|
495
|
+
## [1.46.0] - 2026-08-19
|
|
328
496
|
The docs site catches up with the components, and writing it out as a consumer found a defect in
|
|
329
497
|
#016's own contract.
|
|
330
498
|
### Fixed
|
|
331
499
|
- **`data-fdy-text-*` could not be written the way HTML allows** (#023). The keys in each enhancer's
|
|
332
|
-
`TEXT` table are camelCase, and `textOf` looked up `'data-fdy-text-' + key
|
|
500
|
+
`TEXT` table are camelCase, and `textOf` looked up `'data-fdy-text-' + key`, but **HTML lowercases
|
|
333
501
|
attribute names**, so `data-fdy-text-filterText` becomes `...filtertext` while
|
|
334
502
|
`data-fdy-text-filter-text`, the spelling anybody would actually reach for, is a DIFFERENT
|
|
335
503
|
attribute the enhancer never read. It failed silently, which is the only way an override can fail.
|
|
336
504
|
The key is kebab-cased for the lookup now; the run-together spelling still resolves, so markup
|
|
337
505
|
written against 1.39.0 keeps working. Nine enhancers.
|
|
338
506
|
It survived #016's own guard because all seven overrides that guard asserts use **single-word**
|
|
339
|
-
keys, which have no case to lose
|
|
507
|
+
keys, which have no case to lose, so the guard was not wrong but unrepresentative. The spec now
|
|
340
508
|
overrides `filterText`, reached by clicking a column filter open. Mutation-tested.
|
|
341
509
|
### Docs
|
|
342
|
-
- **`docs/index.html` was nine releases behind
|
|
510
|
+
- **`docs/index.html` was nine releases behind.** It stated v1.34.0 and demoed none of what landed
|
|
343
511
|
after it. Brought level with the components, as live demos rather than a feature list, because
|
|
344
512
|
that is what the rest of the page is:
|
|
345
|
-
- **New section
|
|
513
|
+
- **New section, Tabel beku.** `.fdy-table--sticky` / `--sticky-head`, `.fdy-table__freeze` +
|
|
346
514
|
`--fdy-freeze-left`, `.fdy-table-scroll--frozen` + `--fdy-table-frozen-h` (1.35.0, 1.37.0), with
|
|
347
515
|
a rate matrix you can actually scroll on both axes, and the reason the two axes are separate
|
|
348
516
|
modifiers.
|
|
349
|
-
- **New section
|
|
350
|
-
same data table rendered entirely in English
|
|
517
|
+
- **New section, Teks enhancer (i18n).** The `data-fdy-text-*` contract (1.39.0), demoed as the
|
|
518
|
+
same data table rendered entirely in English, the demo that found #023.
|
|
351
519
|
- **Badge tone scale** `--tone-1`…`--tone-8` (1.44.0), with the distinction the note drew: semantic
|
|
352
520
|
variants state a judgement, tones distinguish without implying one.
|
|
353
521
|
- **Date picker** now states the days → months → years drill (1.36.0); **Choose-from-list** states
|
|
@@ -356,158 +524,158 @@ The docs site catches up with the components, and writing it out as a consumer f
|
|
|
356
524
|
and a `Previous` pager, and the frozen table computes `border-collapse:separate` with
|
|
357
525
|
`position:sticky` cells.
|
|
358
526
|
|
|
359
|
-
## [1.45.0]
|
|
527
|
+
## [1.45.0] - 2026-08-19
|
|
360
528
|
Two defects in 1.42.0's multi-select CFL, found by the app that asked for it (IDU_EMATE_APPL_WEB, #022).
|
|
361
529
|
### Fixed
|
|
362
530
|
- **The row tick rendered as the browser's checkbox, not the kit's** (#022), in all three adapters.
|
|
363
531
|
They shipped `class="fdy-check"` on a bare `<input>`; that class styles a wrapping `<label>` and
|
|
364
532
|
its `input` DESCENDANT, so on the input itself it applied `inline-flex` and a cursor and nothing
|
|
365
|
-
else
|
|
533
|
+
else, leaving a raw UA control in the middle of a styled dialog. `COMPONENTS.md` already said which class
|
|
366
534
|
a bare input in a table takes: `.fdy-checkbox`. Every behavioural assertion in `browser/cfl-multi`
|
|
367
535
|
passed while it was wrong, because none of them looked at the rendering.
|
|
368
536
|
- **`aria-selected` broke `vue-tsc` in consuming apps** (#022). `String(isPicked(row))` widens to
|
|
369
|
-
`string`, and Vue types the attribute as `Booleanish
|
|
537
|
+
`string`, and Vue types the attribute as `Booleanish`, so a strict app type-checking the kit's own
|
|
370
538
|
`.vue` source failed on a file it does not own. Now `'true' : 'false'`.
|
|
371
|
-
### Added
|
|
539
|
+
### Added: guards
|
|
372
540
|
- `browser/cfl-multi.mjs` asserts the tick's computed `appearance` is `none`, which is the tell that
|
|
373
541
|
the kit painted the box rather than the UA. Mutation-tested.
|
|
374
542
|
|
|
375
|
-
## [1.44.0]
|
|
543
|
+
## [1.44.0] - 2026-08-19
|
|
376
544
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #021).
|
|
377
545
|
### Added
|
|
378
|
-
- **`--tone-1`…`--tone-8` on `.fdy-badge`**
|
|
546
|
+
- **`--tone-1`…`--tone-8` on `.fdy-badge`** give a status vocabulary larger than the semantic palette.
|
|
379
547
|
`.fdy-avatar--tone-*` and `.fdy-chip--tone-*` already carried the categorical scale; the badge,
|
|
380
548
|
which is the component that actually renders status, did not. The reporting app's document lists
|
|
381
|
-
carry **ten distinct statuses in one column
|
|
382
|
-
Closed, Transferred, InDeclaration, Open, Rejected
|
|
549
|
+
carry **ten distinct statuses in one column**: Draft, Submitted, Approved, Completed, Settled,
|
|
550
|
+
Closed, Transferred, InDeclaration, Open, Rejected, against five modifiers, so Approved, Closed
|
|
383
551
|
and Completed came out the same green and Draft, InDeclaration and Transferred the same grey.
|
|
384
552
|
Semantics still come first: a state that IS good, bad or waiting takes `--success` / `--danger` /
|
|
385
553
|
`--warning`, and these are for the rest. Same `--tone-*` tokens and the same 18%/50% mix as the
|
|
386
554
|
other two, so the existing contrast test already measures them.
|
|
387
|
-
### Added
|
|
555
|
+
### Added: guards
|
|
388
556
|
- `test/css.test.mjs` asserts all eight bind their own token (a gap silently reuses a colour) and
|
|
389
|
-
that the mix stays identical to the avatar's and the chip's
|
|
557
|
+
that the mix stays identical to the avatar's and the chip's, so one contrast test covers the three,
|
|
390
558
|
so a badge that drifted to its own ratio would be gated by a test measuring something else.
|
|
391
559
|
`test/contrast.test.mjs` renamed from "avatar tones" to "categorical tones" to say so.
|
|
392
560
|
|
|
393
|
-
## [1.43.0]
|
|
561
|
+
## [1.43.0] - 2026-08-19
|
|
394
562
|
Three crowding reports from one settlement screen (IDU_EMATE_APPL_WEB, #020). All three measured in
|
|
395
|
-
Chromium rather than read off the CSS
|
|
563
|
+
Chromium rather than read off the CSS, since each is a layout outcome invisible in the stylesheet.
|
|
396
564
|
### Fixed
|
|
397
565
|
- **A control in a table column collapsed** (#020). `.fdy-input` is `width:100%`, which in an
|
|
398
566
|
auto-layout table contributes **no intrinsic width**: the column shrank to its header text and took
|
|
399
|
-
the control with it. A NOTE box measured **36px
|
|
567
|
+
the control with it. A NOTE box measured **36px**, too narrow to read what you typed into it.
|
|
400
568
|
Controls that are direct children of a `td` now keep a `7rem` floor (112px measured). Per-control
|
|
401
569
|
rather than per-column, because the table cannot know which columns hold controls. `width:100%` was
|
|
402
570
|
never wrong; it is only in a table that 100% of nothing is nothing.
|
|
403
571
|
- **A stat value wrapped between the currency and the number** (#020). `.fdy-stat__value` is
|
|
404
572
|
`--text-3xl`; `IDR 300,000.00` needs **224px** at that size and `.fdy-stats` tracks are
|
|
405
573
|
`minmax(11rem,1fr)`, so a three-across row at 660px gave it ~205px and it broke onto two lines.
|
|
406
|
-
It now shrinks **only when its own column cannot hold it
|
|
574
|
+
It now shrinks **only when its own column cannot hold it**: a 205px track renders 22.7px on one
|
|
407
575
|
line, a 420px track still renders exactly 31px. Shrinking the token instead would have taken 31px
|
|
408
576
|
from every consumer to fix a column that was 19px short. Scoped to a stat inside `.fdy-stats`,
|
|
409
577
|
where the width comes from the grid track: `container-type:inline-size` on a standalone
|
|
410
578
|
`.fdy-stat` would stop it sizing to its own content, and the unconditional rule stays as the
|
|
411
579
|
fallback where `@container` is missing.
|
|
412
580
|
- **`.fdy-filelist` claimed space above itself and none below** (#020), so the "Add files" button
|
|
413
|
-
every upload UI puts after it sat flush against the last row
|
|
581
|
+
every upload UI puts after it sat flush against the last row, a measured **0px**. Symmetric now,
|
|
414
582
|
with `:last-child` dropping the bottom margin so nothing pays for room it does not need.
|
|
415
|
-
### Added
|
|
416
|
-
- `browser/crowding.mjs` measures all three. The stat case asserts **both** directions
|
|
417
|
-
when narrow, still exactly `31px` when wide
|
|
583
|
+
### Added: guards
|
|
584
|
+
- `browser/crowding.mjs` measures all three. The stat case asserts **both** directions: one line
|
|
585
|
+
when narrow, still exactly `31px` when wide, because a fix that quietly shrank every dashboard
|
|
418
586
|
would pass a "does it wrap?" test.
|
|
419
587
|
|
|
420
|
-
## [1.42.0]
|
|
588
|
+
## [1.42.0] - 2026-08-19
|
|
421
589
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #019), and a string the #009 guard could not see.
|
|
422
590
|
### Added
|
|
423
591
|
- **`multiple` on the typed `FdyCfl`** (Vue · React · Blazor). The enhancer has offered
|
|
424
592
|
`data-fdy-cfl-multiple` since it shipped and `COMPONENTS.md` documented it; all three wrappers were
|
|
425
593
|
single-valued **at the type level**, so there was nothing to widen from the outside. An app that
|
|
426
|
-
started on the enhancer and later adopted the wrapper silently lost a capability
|
|
594
|
+
started on the enhancer and later adopted the wrapper silently lost a capability, which is the
|
|
427
595
|
actual defect, more than the missing feature. The settlement screen that reported it gathers six or
|
|
428
596
|
eight approved claims onto one document and was doing six open→search→click cycles.
|
|
429
597
|
Rows tick instead of committing, the footer counts them, and **Confirm** commits the set. Vue and
|
|
430
598
|
React widen their model to `Row[] | null`; Blazor takes `Values` / `ValuesChanged` instead, because
|
|
431
599
|
a nullable union is not a C# shape. New strings: `selectedText` (`{n} selected`), `confirmText`,
|
|
432
|
-
`hintText
|
|
600
|
+
`hintText`, the last one making the single-mode footer overridable too, which it never was.
|
|
433
601
|
### Notes on the shape of the fix
|
|
434
602
|
- **The ticks are component state, not the model.** Closing without Confirm has to leave the caller's
|
|
435
603
|
value exactly as it was, so the dialog seeds its ticks from the bound value on every open. Binding
|
|
436
604
|
them straight to the model would make Cancel a lie.
|
|
437
605
|
- **The field states `{n} selected`.** `display()` takes ONE row: naming one of six would be wrong and
|
|
438
606
|
naming all six does not fit a 22rem control.
|
|
439
|
-
- **Blazor gained a modal footer**, which it never had
|
|
607
|
+
- **Blazor gained a modal footer**, which it never had, while Vue and React both commit from one.
|
|
440
608
|
- The tick checkbox is `aria-hidden` and inert. `.fdy-cfl__check input` is already
|
|
441
609
|
`pointer-events:none` in the kit's CSS, so the row owns the click and a handler on the box would
|
|
442
610
|
have been unreachable code; `aria-selected` on the row is what carries the state.
|
|
443
611
|
### Fixed
|
|
444
612
|
- **`FdyCfl`'s second retry button said `Coba lagi`** (Vue · React). The error state inside the
|
|
445
613
|
results branch had a hard-coded Indonesian literal while the button two states above it correctly
|
|
446
|
-
read `retryText ?? 'Try again'
|
|
614
|
+
read `retryText ?? 'Try again'`, so the component both had the prop and ignored it. #009's guard
|
|
447
615
|
missed it because **neither `coba` nor `lagi` is in its word list**, which is #015's lesson landing
|
|
448
616
|
a third time: a word list is a floor, not a ceiling. Both now read `retryText`.
|
|
449
|
-
### Added
|
|
617
|
+
### Added: guards
|
|
450
618
|
- **The multi flow is driven by real clicks, in both adapters** (#019). `browser/cfl-multi.mjs`
|
|
451
619
|
asserts that a click ticks rather than commits, that the dialog stays open, that `aria-selected`
|
|
452
620
|
and the footer count follow, that a second click unticks, that Confirm hands back an **array** in
|
|
453
621
|
order, and that closing without Confirm discards the ticks *and* does not remember them on re-open.
|
|
454
622
|
Vue and React run the same script, so a divergence between the two fails here instead of in an app.
|
|
455
|
-
A typecheck cannot tell you whether a mode ever renders. Mutation-tested
|
|
623
|
+
A typecheck cannot tell you whether a mode ever renders. Mutation-tested: committing
|
|
456
624
|
`picked[0]` instead of the array fails it by name.
|
|
457
625
|
|
|
458
|
-
## [1.41.0]
|
|
459
|
-
One note from the account app (IDU_EMATE_ACCT_WEB, #018)
|
|
626
|
+
## [1.41.0] - 2026-08-19
|
|
627
|
+
One note from the account app (IDU_EMATE_ACCT_WEB, #018), the first raised from that repo.
|
|
460
628
|
### Fixed
|
|
461
629
|
- **`.fdy-input-group` rendered 1px shorter than every control beside it** (#018). The group
|
|
462
630
|
declares a `1.5px` border and its inner input subtracted `3px` to compensate, so the border box
|
|
463
631
|
should land on `--control-h`. **At `devicePixelRatio: 1` the engine resolves each 1.5px border to
|
|
464
632
|
1px**, the compensation over-subtracts, and a search field beside a button measured 39px against
|
|
465
|
-
40px. At dpr 2 the halves survive and the arithmetic is right
|
|
633
|
+
40px. At dpr 2 the halves survive and the arithmetic is right, which is why it shipped: it is
|
|
466
634
|
invisible on a retina display and visible on every 1× monitor.
|
|
467
635
|
The group now states `height: var(--control-h)` (it is already `border-box`) and the input
|
|
468
636
|
`height: auto; align-self: stretch`. The group is `align-items: stretch` already, so the input and
|
|
469
|
-
the addons fill whatever the border leaves
|
|
637
|
+
the addons fill whatever the border leaves, at any border width, any rounding, and under
|
|
470
638
|
`data-density="compact"` without a second `calc` to keep in step. The defect was not the number;
|
|
471
639
|
it was compensating in one rule for a value declared in another.
|
|
472
|
-
### Added
|
|
640
|
+
### Added: guards
|
|
473
641
|
- **Control heights are measured in a real browser** (#018). No stylesheet assertion could have
|
|
474
642
|
caught this: the CSS reads as correct arithmetic, and the value it is correct about is one the
|
|
475
643
|
engine rounds. `browser/control-heights.mjs` measures `.fdy-input-group` against `.fdy-btn`,
|
|
476
644
|
`.fdy-input` and `.fdy-combo` with `getBoundingClientRect`, at both densities. Written **failing
|
|
477
|
-
first** against 1.40.0
|
|
645
|
+
first** against 1.40.0, it reproduced 39 vs 40 before the fix landed.
|
|
478
646
|
- The note's own sweep is recorded: `calc(var(--control-h)` has five uses in `src/`, and the four in
|
|
479
647
|
`button.css` step a control deliberately by a spacing token. This was the last compensation of its
|
|
480
648
|
kind.
|
|
481
649
|
|
|
482
|
-
## [1.40.0]
|
|
650
|
+
## [1.40.0] - 2026-08-19
|
|
483
651
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #017).
|
|
484
652
|
### Fixed
|
|
485
653
|
- **A picker inside a `.fdy-field` now fills it.** `.fdy-field`, `.fdy-combo` and
|
|
486
654
|
`.fdy-input-group` all cap at 22rem; `.fdy-datepicker` capped at 14rem and `.fdy-timepicker`
|
|
487
|
-
at 11rem. In a two-column form grid that left a **224px** date box beside a **319px** combo
|
|
488
|
-
measured
|
|
655
|
+
at 11rem. In a two-column form grid that left a **224px** date box beside a **319px** combo,
|
|
656
|
+
measured rather than eyeballed, and the row read as ragged with no fix available to the consuming
|
|
489
657
|
app, because the cap sits on the component. `.fdy-field > .fdy-datepicker` / `> .fdy-timepicker`
|
|
490
658
|
/ `> .fdy-daterange` drop the cap; a picker standing on its own keeps its narrower width, which
|
|
491
659
|
is right for a short value.
|
|
492
660
|
|
|
493
|
-
### Added
|
|
661
|
+
### Added: guards
|
|
494
662
|
- **The enhancer string contract is asserted in a real browser** (#016). 1.39.0 moved every
|
|
495
663
|
user-facing string in **nine** enhancers into a `TEXT` table read through `textOf()`, and only
|
|
496
|
-
**one of the nine
|
|
664
|
+
**one of the nine**, upload, had any browser coverage at all. The node guard proves the
|
|
497
665
|
literals *sit* in the table; it reads the source as text and cannot prove the wiring *runs*, so a
|
|
498
666
|
`zone` out of scope or a `getAttribute` on a non-element would have thrown at init with all 51
|
|
499
667
|
node tests still green. `browser/text-override.mjs` initialises table, stepper, mask, carousel,
|
|
500
668
|
cascade, form and toast on real markup and asserts **both halves**: the documented Indonesian
|
|
501
|
-
default still renders, and `data-fdy-text-*` wins. The default is a contract too
|
|
669
|
+
default still renders, and `data-fdy-text-*` wins. The default is a contract too: an enhancer
|
|
502
670
|
that quietly turned English would be a breaking change wearing a bugfix's clothes.
|
|
503
671
|
Mutation-tested: making `textOf()` ignore the attribute fails it by name and value.
|
|
504
672
|
|
|
505
|
-
## [1.39.0]
|
|
673
|
+
## [1.39.0] - 2026-08-19
|
|
506
674
|
The sweep #013 §2 filed and #015 restated: the vanilla enhancers' strings are reachable now.
|
|
507
675
|
### Added
|
|
508
676
|
- **Every enhancer string is overridable with `data-fdy-text-<key>`** (#016). The defaults stay
|
|
509
|
-
Indonesian
|
|
510
|
-
every app that adopted it
|
|
677
|
+
Indonesian, which is documented and deliberate for the raw path, and changing it would break
|
|
678
|
+
every app that adopted it, but they were also *unreachable*, and that is the half that was
|
|
511
679
|
wrong. A Blazor app meets these enhancers through `FreedayBlazor.initAll`, which runs every one
|
|
512
680
|
of them, while COMPONENTS.md promises Blazor is English throughout; both promises can only hold
|
|
513
681
|
if the default stays and the string can be changed. Each enhancer now keeps its strings in one
|
|
@@ -515,60 +683,60 @@ The sweep #013 §2 filed and #015 restated: the vanilla enhancers' strings are r
|
|
|
515
683
|
`{label}` are substituted where the string supports them. Validation messages take the same shape
|
|
516
684
|
on the `<form>`, narrower than the per-field `data-fdy-msg-<alias>` that still wins;
|
|
517
685
|
`Freeday.toast()` takes `closeLabel`.
|
|
518
|
-
- **34 strings across 9 files**, not the 19 across 7 a word-list scan had counted
|
|
686
|
+
- **34 strings across 9 files**, not the 19 across 7 a word-list scan had counted: `carousel`
|
|
519
687
|
(`Slide {n}`), `cascade` (`Kembali satu tingkat`), `mask` (`Tampilkan kata sandi`), `stepper`
|
|
520
688
|
(`Selesai` / `Lanjut`) and four upload status strings were all invisible to it. #015 had just
|
|
521
689
|
finished making this point about `\bcari\b` and `pencarian`; the recount was found by scanning
|
|
522
|
-
by SINK
|
|
690
|
+
by SINK: what lands in a `textContent`, an `aria-label`, a `title` or a `placeholder`, which
|
|
523
691
|
cannot be fooled by an affix.
|
|
524
692
|
### Fixed
|
|
525
693
|
- **Blazor's `FdyCascade` rendered Indonesian** (#016). `Label` and `Placeholder` were `string?`
|
|
526
694
|
with no default, so an app that did not set them emitted `data-label=""` and the enhancer fell
|
|
527
|
-
back to `Pilih` / `Pilih…`. Vue and React have defaulted to `Select` / `Select…` since #015
|
|
695
|
+
back to `Pilih` / `Pilih…`. Vue and React have defaulted to `Select` / `Select…` since #015:
|
|
528
696
|
same drift as #009 and #015, third time, and the parameter existing is what made it invisible.
|
|
529
697
|
They are non-nullable with English defaults now, and `BackLabel` / `SubmenuLabel` reach the two
|
|
530
698
|
strings the enhancer builds itself, which had no route from .NET at all.
|
|
531
699
|
- **`Freeday.toast()` from Blazor labelled its close button `Tutup`.** `FreedayBlazor.toast` now
|
|
532
|
-
defaults `closeLabel` to `Close
|
|
700
|
+
defaults `closeLabel` to `Close`, in the bridge rather than the enhancer, so a hand-written page still
|
|
533
701
|
gets the documented Indonesian.
|
|
534
|
-
### Added
|
|
702
|
+
### Added: guards
|
|
535
703
|
- **An enhancer string is overridable, not hard-coded** (#016). `npm test` asserts that a literal
|
|
536
704
|
reaching a `textContent`, `aria-label`, `title` or `placeholder` lives in that file's `TEXT`
|
|
537
705
|
table. Scoped by SINK rather than by a word list, deliberately: a second word list would have the
|
|
538
|
-
same hole one affix over, and this one is also blind to no language. Mutation-tested
|
|
706
|
+
same hole one affix over, and this one is also blind to no language. Mutation-tested: putting
|
|
539
707
|
`'Tutup'` back into the toast fails it by file and line.
|
|
540
708
|
|
|
541
|
-
## [1.38.0]
|
|
709
|
+
## [1.38.0] - 2026-08-19
|
|
542
710
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #015).
|
|
543
711
|
### Fixed
|
|
544
|
-
- **`FdyCfl`'s trigger said `"Buka pencarian"`** (Vue · React)
|
|
712
|
+
- **`FdyCfl`'s trigger said `"Buka pencarian"`** (Vue · React), the accessible name of the button
|
|
545
713
|
that opens the picker, and so the first thing a screen-reader user meets on every
|
|
546
714
|
choose-from-list. #009 replaced twelve strings around it and missed this one. It takes an
|
|
547
715
|
`openLabel` prop defaulting to `'Open search'` now. Blazor was not Indonesian but was using
|
|
548
|
-
`SearchPlaceholder` as that button's `aria-label
|
|
716
|
+
`SearchPlaceholder` as that button's `aria-label`, a placeholder doing a label's job, and now
|
|
549
717
|
takes the same `OpenLabel`, so all four stacks name the control identically.
|
|
550
|
-
- **`FdyCascade`'s up-one-level button said `"Kembali satu tingkat"`** (Vue · React)
|
|
718
|
+
- **`FdyCascade`'s up-one-level button said `"Kembali satu tingkat"`** (Vue · React), new
|
|
551
719
|
`backLabel`, default `'Back one level'`.
|
|
552
720
|
### Changed
|
|
553
721
|
- **The language guard now lists derived forms, not just roots.** `\bcari\b` cannot match
|
|
554
|
-
*pen·cari·an*, and `kembali` was never listed
|
|
722
|
+
*pen·cari·an*, and `kembali` was never listed. Indonesian derives by affix, so a whole-word list
|
|
555
723
|
misses most of the UI vocabulary. Widening it is what surfaced the `FdyCascade` string nobody had
|
|
556
724
|
reported.
|
|
557
725
|
|
|
558
|
-
### Fixed
|
|
726
|
+
### Fixed: the test suite
|
|
559
727
|
- **`rules()` read only the last line of a selector**, so a rule written across several lines was
|
|
560
728
|
reported under a fragment of its own name and every guard in `test/css.test.mjs` was blind to it
|
|
561
|
-
(#014). A guard that matches nothing PASSES, which is the worst way for a test to fail
|
|
729
|
+
(#014). A guard that matches nothing PASSES, which is the worst way for a test to fail: it
|
|
562
730
|
surfaced only because the first draft of the filter-bar rule happened to be wrapped. The helper
|
|
563
731
|
now strips comments from the whole source first (a comment quoting CSS carries braces, and those
|
|
564
732
|
would split a rule in half) and reads the selector whole. Asserted directly rather than left to
|
|
565
733
|
the CSS staying one-rule-per-line: that is a convention, and this is the thing that would quietly
|
|
566
|
-
stop enforcing it. Mutation-tested
|
|
734
|
+
stop enforcing it. Mutation-tested: restoring the last-line read fails the new test by name.
|
|
567
735
|
|
|
568
|
-
## [1.37.0]
|
|
736
|
+
## [1.37.0] - 2026-08-19
|
|
569
737
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #014).
|
|
570
738
|
### Added
|
|
571
|
-
- **`.fdy-table__freeze` + `--fdy-freeze-left`**
|
|
739
|
+
- **`.fdy-table__freeze` + `--fdy-freeze-left`** freeze **any number** of columns, not just the
|
|
572
740
|
first. 1.35.0 froze `tbody th` at a hard-coded `left:0`, which suits a rate matrix's single row
|
|
573
741
|
header and cannot express a wide table with two identity columns: they all landed on 0 and
|
|
574
742
|
stacked. Mark every cell of a frozen column and give that column its offset; the offsets belong
|
|
@@ -581,17 +749,17 @@ One note from the back-office app (IDU_EMATE_APPL_WEB, #014).
|
|
|
581
749
|
### Changed
|
|
582
750
|
- **BREAKING (from 1.35.0): the header freeze moved to `.fdy-table--sticky-head`.** Freezing a row
|
|
583
751
|
needs a vertical scrollport; a table that only freezes columns scrolls with the **page**, where
|
|
584
|
-
`top:0` sticks the header under the viewport edge
|
|
752
|
+
`top:0` sticks the header under the viewport edge, over the consuming app's own top bar.
|
|
585
753
|
`.fdy-table--sticky` is now the enabler (separate borders) and each axis is asked for
|
|
586
754
|
explicitly. `tbody th` no longer freezes implicitly; use `.fdy-table__freeze`. 1.35.0 and 1.36.0
|
|
587
755
|
are unpublished, so no released version carried the old contract.
|
|
588
756
|
|
|
589
|
-
## [1.36.0]
|
|
757
|
+
## [1.36.0] - 2026-08-19
|
|
590
758
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #013).
|
|
591
759
|
### Added
|
|
592
760
|
- **A year grid in the calendar** (vanilla · Vue · React · Blazor). 1.32.0's month grid killed
|
|
593
761
|
one-click-per-month and left one-click-per-year behind it: the month grid's arrows step a year
|
|
594
|
-
each, so 2026 → 1998 was **28 clicks**. The title now drills twice
|
|
762
|
+
each, so 2026 → 1998 was **28 clicks**. The title now drills twice, days → months → years, with
|
|
595
763
|
the arrows following the level shown, and picking a year drops to that year's months. Every level
|
|
596
764
|
above the day grid stays **navigation**: nothing is committed until a day is picked. Year pages
|
|
597
765
|
are aligned (2016–2027, 2028–2039) rather than centred on the year in view, so the pages tile
|
|
@@ -604,18 +772,18 @@ One note from the back-office app (IDU_EMATE_APPL_WEB, #013).
|
|
|
604
772
|
### Fixed
|
|
605
773
|
- **`freeday-datepicker.js` spoke Indonesian to the user.** Its placeholder and every nav
|
|
606
774
|
`aria-label` were Indonesian, and because `FdyDatepicker.razor` delegates to the vanilla
|
|
607
|
-
enhancer, **every Blazor app inherited them
|
|
775
|
+
enhancer, **every Blazor app inherited them**, while #009's language guard, which scans
|
|
608
776
|
`adapters/` only, passed. The datepicker is English now. The rest of `src/` is NOT audited: see
|
|
609
777
|
improvement note #013 §2, which lists the enhancers still affected.
|
|
610
778
|
|
|
611
|
-
## [1.35.0]
|
|
779
|
+
## [1.35.0] - 2026-08-19
|
|
612
780
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #012).
|
|
613
781
|
### Added
|
|
614
|
-
- **`.fdy-table--sticky` + `.fdy-table-scroll--frozen`**
|
|
782
|
+
- **`.fdy-table--sticky` + `.fdy-table-scroll--frozen`** freeze a table on one axis or both, for
|
|
615
783
|
a grid read against two axes at once (a rate matrix, a timetable). This is a kit class rather
|
|
616
784
|
than four lines in an app because `position:sticky` on its own produces a **visibly broken**
|
|
617
785
|
table: `.fdy-table` collapses its borders, a collapsed border is painted by the table rather than
|
|
618
|
-
by either cell, and so a frozen cell scrolls out from under its own rule
|
|
786
|
+
by either cell, and so a frozen cell scrolls out from under its own rule, and the frozen column
|
|
619
787
|
arrives with nothing separating it from the data. The modifier separates the borders and puts
|
|
620
788
|
each one back on the cell that sticks. `<thead>` freezes at the top, `<th scope="row">` at the
|
|
621
789
|
left, the first header cell is the corner, and the row header paints the **body** surface so a
|
|
@@ -623,21 +791,21 @@ One note from the back-office app (IDU_EMATE_APPL_WEB, #012).
|
|
|
623
791
|
the freeze resolves against (the base `.fdy-table-scroll` scrolls one axis, so `top:0` never
|
|
624
792
|
engages); size it with `--fdy-table-frozen-h`, default `30rem`.
|
|
625
793
|
|
|
626
|
-
## [1.34.0]
|
|
794
|
+
## [1.34.0] - 2026-08-19
|
|
627
795
|
One note from the back-office app (IDU_EMATE_APPL_WEB, #008), and the second half of #005 it
|
|
628
796
|
finally makes answerable.
|
|
629
797
|
### Added
|
|
630
798
|
- **`pageSizes` on `FdyTable`** (Vue · React · Blazor) and `data-fdy-table-page-size` for the
|
|
631
|
-
enhancer. The footer stated the range and moved the page and stopped there
|
|
799
|
+
enhancer. The footer stated the range and moved the page and stopped there, with no control for **how
|
|
632
800
|
many rows a page holds**, which is the first thing anybody changes on a ninety-row list. The three
|
|
633
801
|
numbers are one object: the component already receives `index`, `size` and `total`, rendered two
|
|
634
|
-
of them, and left the third to the caller
|
|
802
|
+
of them, and left the third to the caller, so the caller withheld the whole footer with
|
|
635
803
|
`pager={false}` and rebuilt all three to add the one. Give it `pageSizes` and it grows the control
|
|
636
804
|
between the range and the pager. Server mode reports the pick through the existing
|
|
637
805
|
`update:page` / `onPageChange` / `PageChanged`, carrying a new `size`; client mode applies it
|
|
638
806
|
itself (so it works with nothing wired) and also emits `update:pageSize` / `onPageSizeChange` /
|
|
639
807
|
`PageSizeChanged` for a caller that wants to persist it.
|
|
640
|
-
- **`FdyTableFooter`** (Vue · React · Blazor)
|
|
808
|
+
- **`FdyTableFooter`** (Vue · React · Blazor) is the footer as a component of its own. This is the
|
|
641
809
|
half of #005 that `pager={false}` could only get out of the way: a **responsive** list renders one
|
|
642
810
|
page of rows twice, a `.fdy-datatable` at `lg` and a `.fdy-list` below it, and a footer that lives
|
|
643
811
|
inside the table lives inside the half a phone hides. Those screens took the footer over and
|
|
@@ -646,56 +814,56 @@ finally makes answerable.
|
|
|
646
814
|
- **`pageIndexForSize(pageIndex, oldSize, newSize)`** in the shared table model. Resizing the page
|
|
647
815
|
has two obvious answers and both are wrong: back to page 1 throws away the reader's place, and
|
|
648
816
|
keeping the same index can land past the end (page 5 of 5 at twenty rows is page 2 of 2 at fifty).
|
|
649
|
-
Anchoring on the first visible row always resolves, and it is what the reader expects
|
|
817
|
+
Anchoring on the first visible row always resolves, and it is what the reader expects: the row
|
|
650
818
|
they were looking at is still on screen.
|
|
651
819
|
### Notes on the shape of the fix
|
|
652
820
|
- **A footer with a size control stays visible on a single page.** The old rule withheld the whole
|
|
653
821
|
band whenever `totalPages === 1`, which would have made "100 rows" on a ninety-row list a one-way
|
|
654
822
|
door: the control that got you there disappears with the pager. Visibility is now "there is a
|
|
655
|
-
pager **or** there is a size control", and it lives in `FdyTableFooter
|
|
823
|
+
pager **or** there is a size control", and it lives in `FdyTableFooter`: one decision, four
|
|
656
824
|
stacks.
|
|
657
825
|
- **No wrapper element.** `.fdy-table-footer__size` takes `margin-left:auto`, so the size control and
|
|
658
826
|
the pager travel together on the right while the range stays left. The footer's DOM is byte-
|
|
659
827
|
identical for every table that does not offer one.
|
|
660
828
|
- **`.fdy-combo`, not a native `<select>`.** The first cut of this shipped a native one, reasoning
|
|
661
829
|
that three one-word options do not need a custom listbox. They do: an OS menu is unthemeable, and
|
|
662
|
-
on macOS it drops a dark grey panel into a light page
|
|
830
|
+
on macOS it drops a dark grey panel into a light page, which is the reason `FdyCombo` exists, and
|
|
663
831
|
its own source header says so. Caught by a consuming app in a screenshot within the hour, before
|
|
664
832
|
the release reached npm. The raw path still accepts either: the enhancer listens for `change` *and*
|
|
665
833
|
for the `fdy-change` a `.fdy-combo` emits.
|
|
666
|
-
### Added
|
|
834
|
+
### Added: guards
|
|
667
835
|
- `pageIndexForSize` is unit-tested at the boundaries (first page, both directions, same-size no-op,
|
|
668
836
|
a negative index, a zero size).
|
|
669
837
|
- The control is measured in a real browser, on two tables sharing one page state, **driven by real
|
|
670
838
|
clicks on the popup** rather than a synthetic `change`: the server-mode pick must reach the caller
|
|
671
|
-
**with the right index** (page 3 of five-row pages → page 2 of ten), and the client-mode table
|
|
672
|
-
deliberately wired to nothing
|
|
839
|
+
**with the right index** (page 3 of five-row pages → page 2 of ten), and the client-mode table,
|
|
840
|
+
deliberately wired to nothing, must still grow to 25 rows, because a control that only reports is
|
|
673
841
|
a control that lies. Opening the popup is the half that would have caught the native `<select>`.
|
|
674
842
|
### Fixed
|
|
675
843
|
- **The typed `FdyCfl` dialog was Indonesian end to end, in every stack but Blazor** (#009).
|
|
676
|
-
COMPONENTS.md promises the Vue/React/Blazor components are English throughout
|
|
677
|
-
English app adopts the wrappers on
|
|
844
|
+
COMPONENTS.md promises the Vue/React/Blazor components are English throughout, which is the line an
|
|
845
|
+
English app adopts the wrappers on, and the CFL broke it in **twelve strings** (`Pilih data`,
|
|
678
846
|
`Tutup`, `Cari…`, `Memuat…`, `Coba lagi`, `Tidak ada hasil.`, `Hasil pencarian`, `Muat lebih
|
|
679
847
|
banyak`, `Klik baris untuk memilih`) with **not one prop reaching any of them**. Blazor already had
|
|
680
848
|
the whole set as English-defaulted parameters, so Vue and React now take the same props with the
|
|
681
849
|
same defaults: a parity gap closing, not a translation being chosen. `FdyCascade` (`Pilih` /
|
|
682
850
|
`Pilih…`) and `FdyAutocomplete` (`Tak ada hasil.`) get English defaults too; both were already
|
|
683
|
-
overridable. The enhancers keep their Indonesian
|
|
851
|
+
overridable. The enhancers keep their Indonesian: documented, deliberate, a separate decision
|
|
684
852
|
(NEXT-UP #6).
|
|
685
853
|
|
|
686
854
|
- **A guard for each.** The paragraph margins are checked against the classes the DOCS show on a
|
|
687
|
-
`<p>`, so a new one is covered by documenting it
|
|
855
|
+
`<p>`, so a new one is covered by documenting it, the step nobody skips. The grid ordering is
|
|
688
856
|
asserted as an ORDER, because a specificity bug is invisible in either rule and only their sequence
|
|
689
857
|
shows it. Both mutation-tested.
|
|
690
858
|
- **A guard for it.** `npm test` scans every string literal in `adapters/{vue,react,blazor}` for
|
|
691
859
|
twenty unambiguous Indonesian words. One of the twelve was found by looking at a screen; the other
|
|
692
|
-
eleven by the sweep that one prompted
|
|
860
|
+
eleven by the sweep that one prompted; reading finds the instance, only a mechanical check finds
|
|
693
861
|
the class. Matched by word rather than by a list of components, so a new adapter is covered the day
|
|
694
862
|
it lands.
|
|
695
863
|
|
|
696
864
|
- **`.fdy-eyebrow` and `.fdy-cfl__empty` never cleared the UA's `<p>` margin** (#010). Every other
|
|
697
865
|
class the kit documents on a paragraph sets `margin:0`; these two did not, so a browser's 1em
|
|
698
|
-
landed as spacing nobody wrote
|
|
866
|
+
landed as spacing nobody wrote: 12px above the eyebrow and 12px below it. A consuming app reported
|
|
699
867
|
it as two complaints ("the gap from the category to the top bar is too big" and "the gap between
|
|
700
868
|
the title and the record value is too loose"); they were one missing declaration, and it made the
|
|
701
869
|
top of every page 44px against 32px at the sides.
|
|
@@ -705,7 +873,7 @@ finally makes answerable.
|
|
|
705
873
|
combobox has to agree with is the form controls, so the height goes back to `--control-h`.
|
|
706
874
|
- **`.fdy-list__title` did not set its own type** (#010). The class carries weight, colour and
|
|
707
875
|
truncation but inherited font-size and family, so on the `<span>` the docs show it is 16px body
|
|
708
|
-
text and on an `<h3
|
|
876
|
+
text and on an `<h3>`, a legitimate element for a row that names a record, where the UA's 1.17em and
|
|
709
877
|
base.css's display-font rule made it 18.7px Sora. A phone row's name then shouted over its meta,
|
|
710
878
|
and a long one wrapped to two lines instead of ellipsising. A class that names a role owns the type
|
|
711
879
|
for that role.
|
|
@@ -713,73 +881,73 @@ finally makes answerable.
|
|
|
713
881
|
for one class; asserting it as an invariant found the rest. `.fdy-alert__title`,
|
|
714
882
|
`.fdy-dropzone__title` and `.fdy-toast__title` stated no `font-size` and no `margin`;
|
|
715
883
|
`.fdy-state__title`, `.fdy-cal__title`, `.fdy-app__brand-title` and `.fdy-app__brand-subtitle`
|
|
716
|
-
stated no `margin`. On the elements the docs show, all eight rendered correctly and always had
|
|
884
|
+
stated no `margin`. On the elements the docs show, all eight rendered correctly and always had,
|
|
717
885
|
a `<span>` brings no margin and no size of its own. On a heading, which is the honest markup for
|
|
718
886
|
an empty state's title or a row that names a record, the UA supplied `1.17em`, `bold` and `1em 0`
|
|
719
887
|
and the kit's type scale simply did not apply. `font-size:inherit` is the fix where the class had
|
|
720
888
|
no size of its own: it is what the documented span already did, and on a heading it declines the
|
|
721
889
|
UA's `em` instead of inheriting it. **No rendering changes for markup that follows the docs.**
|
|
722
|
-
### Added
|
|
890
|
+
### Added: guards
|
|
723
891
|
- **A title class must render the same whatever element carries it** (#011). `npm test` asserts
|
|
724
892
|
`margin`, `font-size` and `font-weight` on every `.fdy-*title` rule that sets type and is not a
|
|
725
893
|
flex/grid box. This is #010's guard with the right invariant: that one checks the classes the docs
|
|
726
894
|
show on a `<p>`, so it could only ever see the documented element, and the kit does not own that
|
|
727
|
-
choice
|
|
895
|
+
choice, because heading level is the consuming app's semantics. Scoped to title roles deliberately:
|
|
728
896
|
asserted over every class that sets type it fails 53 times on `.fdy-btn--sm`, `.fdy-avatar--xs`
|
|
729
897
|
and friends, and a guard that loud is one somebody silences. It found the eighth class after the
|
|
730
898
|
fix list had been written by eye. Mutation-tested.
|
|
731
899
|
- **The month grid rendered seven months across** (#010). `.fdy-cal__grid--months` sets three
|
|
732
900
|
columns and `.fdy-cal__grid` sets seven; both sit on the same element and weigh the same, so
|
|
733
|
-
source order decides
|
|
901
|
+
source order decides, and the modifier was declared *before* the base it modifies, with a comment
|
|
734
902
|
directly above it describing the 3×4 layout it was failing to produce. Moved after. Neither rule is
|
|
735
903
|
wrong on its own, which is why reading them found nothing.
|
|
736
904
|
|
|
737
|
-
### Fixed
|
|
905
|
+
### Fixed: the kit's own suite
|
|
738
906
|
- `browser/fixtures/theme-subtree.html` had no explicit theme on `<html>`, so its "light app" probe
|
|
739
907
|
fell to `prefers-color-scheme`. On a machine in dark mode both probes read the dark ink and the
|
|
740
908
|
test failed claiming `data-theme` had gone back to being root-scoped. Pinned; the subtree
|
|
741
909
|
behaviour under test is unchanged.
|
|
742
910
|
|
|
743
|
-
## [1.33.0]
|
|
911
|
+
## [1.33.0] - 2026-08-18
|
|
744
912
|
Three notes from the back-office app, four findings, all four executed.
|
|
745
913
|
### Added
|
|
746
914
|
- **`pager={false}` on `FdyTable`** (Vue · React · Blazor). Server mode rendered its own footer
|
|
747
915
|
unconditionally: `hasPager` was `pageSize > 0 && totalPages > 1` with no way in, so a responsive
|
|
748
|
-
list
|
|
916
|
+
list: a table at `lg`, `.fdy-list` below it, one pager serving both so the views cannot disagree,
|
|
749
917
|
showed the kit's footer *and* the app's, stacked, on every list with more than one page. The only
|
|
750
918
|
workaround was `display:none` on `.fdy-table-footer`, an app reaching into a component's internals.
|
|
751
919
|
Client mode already had its hatch (`pageIndex`); this gives server mode one, and in server mode it
|
|
752
|
-
is the honest shape
|
|
920
|
+
is the honest shape, since the app owns the page there anyway, it was simply being handed a second
|
|
753
921
|
control.
|
|
754
922
|
- **`usePopover` is exported from the Vue adapter.** Every kit dropdown uses it to escape an
|
|
755
923
|
ancestor's overflow clip (`.fdy-card` is `overflow:hidden`, so a panel inside one is cut at the
|
|
756
924
|
card's edge), and a consuming app that had to build a control the kit does not ship had to
|
|
757
|
-
re-implement it
|
|
925
|
+
re-implement it: ~70 lines, from the kit's own description, that will not improve when the kit's
|
|
758
926
|
positioning does. *Correction to the report: React has exported it all along; only Vue was missing
|
|
759
|
-
it. Blazor has no composable layer
|
|
927
|
+
it. Blazor has no composable layer; its panels go through the JS bridge.*
|
|
760
928
|
- **Quiet destructive buttons.** `--danger` was the solid treatment and nothing else, so
|
|
761
|
-
`fdy-btn--ghost fdy-btn--danger
|
|
929
|
+
`fdy-btn--ghost fdy-btn--danger`, which every reader parses as "quiet destructive", rendered a
|
|
762
930
|
second SOLID button. That is a hierarchy defect: the base class is already the one primary per
|
|
763
931
|
screen, and a solid Delete beside Save is a second primary in all but name.
|
|
764
932
|
`.fdy-menu__item--danger` has always modelled the quiet form; it now reaches `.fdy-btn`.
|
|
765
933
|
### Fixed
|
|
766
934
|
- **`.fdy-eyebrow` and `.fdy-nav__grouplabel` spend a text ink.** Both set type in
|
|
767
935
|
`--color-text-subtle`, which is gated at 3.0 *by design* (placeholders, separators, decorative
|
|
768
|
-
glyphs) and measures 4.41:1
|
|
936
|
+
glyphs) and measures 4.41:1, so axe reports it as a serious `color-contrast` failure. Identical to
|
|
769
937
|
`.fdy-stat__label` in 1.30.0, one release earlier: same ink, same 4.41, same argument.
|
|
770
|
-
The nav group label is the one the report did not know about
|
|
938
|
+
The nav group label is the one the report did not know about; the sweep it asked for found it,
|
|
771
939
|
and that rule was *already* brightening to `--color-text-muted` on hover, which is the CSS
|
|
772
940
|
admitting its resting state was too faint.
|
|
773
941
|
### Notes on the shape of the fix
|
|
774
942
|
- The report proposed scoping the solid danger with `:not(.fdy-btn--ghost):not(.fdy-btn--text)`.
|
|
775
|
-
Shipped without it: the pairing rules are more specific and come later, so they already win
|
|
943
|
+
Shipped without it: the pairing rules are more specific and come later, so they already win,
|
|
776
944
|
the `:not()` would be a second mechanism expressing the same intent, and the mutation run proved
|
|
777
945
|
it changed nothing.
|
|
778
946
|
- **A regression written and caught inside this change:** Vue casts an omitted Boolean prop to
|
|
779
947
|
`false`, not `undefined`, so `props.pager !== false` withheld the footer from every table that
|
|
780
948
|
never mentions `pager`. The existing controlled-`pageIndex` guard failed within seconds. `pager`
|
|
781
949
|
now goes through `withDefaults`, the same trap `FdyModal`'s `dismissible` hit in v1.18.0.
|
|
782
|
-
### Added
|
|
950
|
+
### Added: guards
|
|
783
951
|
- **The prose-ink invariant** note 007 asked for: any rule that sets a `font-size` *and* spends
|
|
784
952
|
`--color-text-subtle` is typesetting prose in an ink gated for decoration, and fails. Matched by
|
|
785
953
|
shape, not by a list of names, so the third instance cannot arrive the way the second did. Two
|
|
@@ -791,7 +959,7 @@ Three notes from the back-office app, four findings, all four executed.
|
|
|
791
959
|
fills are: a gradient is a background-*image*, so the CSS reads fine either way and only the engine
|
|
792
960
|
shows which treatment won.
|
|
793
961
|
|
|
794
|
-
## [1.32.1]
|
|
962
|
+
## [1.32.1] - 2026-08-18
|
|
795
963
|
Docs only. Asked by a consumer: *"if I upgrade, will the agent in my project know what it gained?"*
|
|
796
964
|
The answer was no, and two reasons why.
|
|
797
965
|
### Fixed
|
|
@@ -799,25 +967,25 @@ The answer was no, and two reasons why.
|
|
|
799
967
|
reads first, and it listed everything in the package except the one file that says what changed
|
|
800
968
|
between the version the project had and the one it now has. It now points at
|
|
801
969
|
`node_modules/@cahyo-dimas/freeday/CHANGELOG.md` explicitly, plus a short *reach for this instead
|
|
802
|
-
of hand-rolling that* table covering the last nine releases
|
|
970
|
+
of hand-rolling that* table covering the last nine releases, the additions most likely to replace
|
|
803
971
|
something an app already worked around.
|
|
804
|
-
- **Three classes existed only in shorthand** in `COMPONENTS.md
|
|
972
|
+
- **Three classes existed only in shorthand** in `COMPONENTS.md`: `.fdy-label--required`,
|
|
805
973
|
`.fdy-cal__month` and `.fdy-cal__grid--months` were written as `(+--required)` and `__month`, so
|
|
806
974
|
the full name appeared **zero** times and a grep for the class found nothing. The drift guard
|
|
807
975
|
cannot see this either: it verifies fully-written names and skips shorthand by design, so a class
|
|
808
976
|
introduced only in shorthand is invisible to both the reader and the test.
|
|
809
977
|
|
|
810
|
-
## [1.32.0]
|
|
978
|
+
## [1.32.0] - 2026-08-18
|
|
811
979
|
Improvement note 004, both halves: a calendar you could not steer, and a check mark that talked.
|
|
812
980
|
### Added
|
|
813
|
-
- **The calendar title is a control.** It was a `<div
|
|
814
|
-
clickable
|
|
981
|
+
- **The calendar title is a control.** It was a `<div>`, the only thing naming the month, and not
|
|
982
|
+
clickable, so the only pointer route to another month was one click per month: August 2026 to
|
|
815
983
|
March 2022 is **53 clicks**. `Shift`+`PageUp` already jumped a year (APG, correct) but had no
|
|
816
984
|
affordance whatsoever; the reporting repo's own Playwright helper clicked "previous month"
|
|
817
985
|
**twenty-four times** and shipped, which is the strongest possible evidence that nobody finds a
|
|
818
986
|
shortcut nothing mentions.
|
|
819
987
|
Pressing the title now drills to a 12-cell month grid where the arrows step **years**. Same
|
|
820
|
-
journey: **7 clicks**. Picking a month is navigation, not selection
|
|
988
|
+
journey: **7 clicks**. Picking a month is navigation, not selection, so the day grid comes back with
|
|
821
989
|
the roving cell clamped into the new month and nothing is committed until a day is chosen.
|
|
822
990
|
Shipped in the vanilla enhancer, Vue and React; `FdyDateRange` composes the picker in both
|
|
823
991
|
adapters and the Blazor `FdyDatepicker` wraps the enhancer, so all of them inherit it.
|
|
@@ -827,7 +995,7 @@ Improvement note 004, both halves: a calendar you could not steer, and a check m
|
|
|
827
995
|
### Fixed
|
|
828
996
|
- **A combo option's accessible name no longer changes when it is selected.** The tick lived inside
|
|
829
997
|
`role="option"` as text, so the selected option announced `✓August` while every other announced
|
|
830
|
-
`August`: the state was read twice
|
|
998
|
+
`August`: the state was read twice, once as `aria-selected` and once as decoration, and
|
|
831
999
|
`getByRole('option', { name: 'August' })` stopped matching the one option that was selected. The
|
|
832
1000
|
glyph is now painted by CSS with alt text (`content:"✓" / ""`), the same technique
|
|
833
1001
|
`.fdy-label--required` uses. The conditional disappears from all four stacks, and the vanilla
|
|
@@ -836,13 +1004,13 @@ Improvement note 004, both halves: a calendar you could not steer, and a check m
|
|
|
836
1004
|
- **A bug written and caught inside this change:** swapping the calendar head with `v-if`/`v-else`
|
|
837
1005
|
destroys the very button the user just pressed, focus falls to `<body>`, and the panel's own
|
|
838
1006
|
`focusout` handler closes it mid-navigation. The head is now one element set whose labels and
|
|
839
|
-
handlers switch, and `focusout` ignores a **null** `relatedTarget
|
|
1007
|
+
handlers switch, and `focusout` ignores a **null** `relatedTarget`, so focus lost because an element
|
|
840
1008
|
was removed is not focus leaving the control, and a pointer that really lands outside is handled
|
|
841
1009
|
by the mousedown path.
|
|
842
1010
|
- The report's `aria-hidden` suggestion would also have worked; CSS alt text was chosen because it
|
|
843
1011
|
removes the conditional from four implementations instead of adding an attribute to each.
|
|
844
|
-
### Added
|
|
845
|
-
- `browser/harness.mjs` gains **`axName(selector)
|
|
1012
|
+
### Added: guards
|
|
1013
|
+
- `browser/harness.mjs` gains **`axName(selector)`**, the accessible name as the *engine* computes
|
|
846
1014
|
it (CDP `Accessibility.getPartialAXTree`). This mattered immediately: the first version of the
|
|
847
1015
|
name invariant read `textContent`, which never contains CSS generated content, so it passed with
|
|
848
1016
|
the tick back in the accessibility tree. With `axName`, that mutation reports `"✓ Badge"`.
|
|
@@ -850,64 +1018,64 @@ Improvement note 004, both halves: a calendar you could not steer, and a check m
|
|
|
850
1018
|
shows 12 months and moves focus into the grid, the arrows step years, and picking a month commits
|
|
851
1019
|
nothing.
|
|
852
1020
|
|
|
853
|
-
## [1.31.0]
|
|
1021
|
+
## [1.31.0] - 2026-08-18
|
|
854
1022
|
Improvement note 002: density was a one-way door.
|
|
855
1023
|
### Added
|
|
856
1024
|
- **`[data-density="comfortable"]` is now a real rule.** The kit shipped only the `compact` block,
|
|
857
|
-
and its own comment promised per-subtree density
|
|
1025
|
+
and its own comment promised per-subtree density, true in one direction only. These are
|
|
858
1026
|
inheriting custom properties, so once `<html>` is compact *every* subtree is compact and a
|
|
859
1027
|
`comfortable` wrapper matched nothing at all. An app that is dense overall (a back office where
|
|
860
1028
|
most screens are data grids) could not opt its shared chrome back out: measured against a sibling
|
|
861
|
-
product, the logo sat 12px from the edge instead of 16 and the avatar 12 instead of 20
|
|
1029
|
+
product, the logo sat 12px from the edge instead of 16 and the avatar 12 instead of 20: four
|
|
862
1030
|
pixels in two places, from `--space-4` and `--control-h`, which reads as sloppiness rather than as
|
|
863
1031
|
a token. The workaround, restating the five defaults on a local class, is exactly the copy that
|
|
864
1032
|
goes stale when the kit retunes a step.
|
|
865
1033
|
Note the kit's own README has been telling people to write `data-density="comfortable"` on `<html>`
|
|
866
|
-
since long before a rule existed for it
|
|
1034
|
+
since long before a rule existed for it, harmless there because it matched the defaults by
|
|
867
1035
|
accident, and broken the moment anyone needed it on a wrapper.
|
|
868
1036
|
### Notes on the shape of the fix
|
|
869
1037
|
- The five values are **derived from the tokens' own defaults** at build time, not written out a
|
|
870
1038
|
second time. Same key set by construction, so a token that gains a `$compact` value automatically
|
|
871
1039
|
gains its way back out, and a retuned default can never leave the two blocks disagreeing. The
|
|
872
|
-
report's hand-written block was correct in all five values
|
|
1040
|
+
report's hand-written block was correct in all five values; this only removes the chance for it
|
|
873
1041
|
to stop being correct.
|
|
874
|
-
### Added
|
|
1042
|
+
### Added: guards
|
|
875
1043
|
- `test/build.test.mjs` asserts the two density blocks cover the *same* tokens, that every
|
|
876
1044
|
comfortable value equals the `:root` default, and that none of them equals its compact counterpart
|
|
877
1045
|
(a block that resets nothing is as green as one that works).
|
|
878
1046
|
- `browser/theme.mjs` measures it in a real engine: a compact root, a `comfortable` island that
|
|
879
|
-
returns to a 40px control and `--space-4: 1rem`, and a `compact` island nested back inside that
|
|
1047
|
+
returns to a 40px control and `--space-4: 1rem`, and a `compact` island nested back inside that,
|
|
880
1048
|
because the point of the rule is the box that comes out, not the declaration going in.
|
|
881
1049
|
|
|
882
|
-
## [1.30.0]
|
|
1050
|
+
## [1.30.0] - 2026-08-18
|
|
883
1051
|
Note 008 (adopting 1.29.0) plus a direct design report: *"the input border looks too dark next to the
|
|
884
1052
|
card and button-group borders."*
|
|
885
1053
|
### Fixed
|
|
886
1054
|
- **The dark theme's contrast has not actually been tested since v1.21.0.** `contrast.test.mjs` looked
|
|
887
1055
|
for `:root[data-theme="dark"]`; un-rooting the theme selectors in 1.21.0 changed the emitted
|
|
888
1056
|
selector to `[data-theme="dark"]`, so the regex matched nothing, `dark` fell back to the **light**
|
|
889
|
-
values, and every `WCAG contrast
|
|
1057
|
+
values, and every `WCAG contrast, DARK` assertion re-tested the light theme under a dark label,
|
|
890
1058
|
54 declarations ignored, all green. Fixed, and the scope now has a guard of its own: a dark scope
|
|
891
1059
|
that parses to nothing, or that resolves to the light surface, fails loudly. *No latent dark-theme
|
|
892
|
-
failures were hiding behind it
|
|
1060
|
+
failures were hiding behind it; the theme passes on its own merits.*
|
|
893
1061
|
- **`--color-control-border` retuned** to a new ramp step `--slate-450` (`#798295`). It was
|
|
894
1062
|
`--slate-500`, the same ink as `--color-text-subtle`: a control **boundary** carrying text-level
|
|
895
1063
|
contrast (4.69:1 on surface where WCAG 1.4.11 asks 3:1), which is what makes a form look heavier
|
|
896
1064
|
than the cards around it. Now ≈3.9/3.6/3.4 across the three light surfaces. The same step
|
|
897
|
-
*strengthens* the dark theme, which was sitting at **3.02:1** on `surface-3
|
|
1065
|
+
*strengthens* the dark theme, which was sitting at **3.02:1** on `surface-3`, 0.02 above the
|
|
898
1066
|
floor, and unguarded because of the bug above; it is now 3.66:1.
|
|
899
1067
|
- **`.fdy-stat__label` uses `--color-text-muted`.** It spent `--color-text-subtle`, which is gated at
|
|
900
|
-
3.0 *on purpose* (placeholders, dividers, decorative glyphs) and measures 4.41:1 on `surface-2
|
|
1068
|
+
3.0 *on purpose* (placeholders, dividers, decorative glyphs) and measures 4.41:1 on `surface-2`,
|
|
901
1069
|
under AA for text, on the only thing naming the number above it. Reported in 006 §6 and still
|
|
902
1070
|
carried by the app at 1.29.0.
|
|
903
1071
|
### Added
|
|
904
1072
|
- **`.fdy-btn--stretch` now works in a `.fdy-list__row`** (note 008 §1). The pattern existed for
|
|
905
1073
|
"one card, one primary action, one escape hatch" and had no equivalent for the same sentence with
|
|
906
|
-
*row* in it
|
|
1074
|
+
*row* in it, so an app rendering both grid and list from one component had to hand-roll the row half.
|
|
907
1075
|
The row anchors the overlay itself, opt-in through `:has(.fdy-btn--stretch)` so a plain row is
|
|
908
1076
|
untouched and nothing an app pinned inside one starts resolving against a new containing block.
|
|
909
1077
|
Measured before the fix: **every** row's overlay covered the whole list and the last one in the DOM
|
|
910
|
-
won, so a click on row one opened row two
|
|
1078
|
+
won, so a click on row one opened row two, under row one's name.
|
|
911
1079
|
### Notes on the shape of the fix
|
|
912
1080
|
- **Note 008 §2 is not reproducible** and no code shipped for it. With a positive control to prove
|
|
913
1081
|
the measurement works (pointing at the card body *does* put the stretched target in `:hover`),
|
|
@@ -915,78 +1083,78 @@ card and button-group borders."*
|
|
|
915
1083
|
hatch`, and the stretched button is a sibling, not an ancestor. The `pointer-events:none` rule the
|
|
916
1084
|
report carries fixes something the kit's own composition does not do.
|
|
917
1085
|
- The report's contrast figures (4.41 / 4.69) were re-derived from the 1.29 ramp and are exact.
|
|
918
|
-
### Added
|
|
1086
|
+
### Added: guards
|
|
919
1087
|
- `contrast.test.mjs`: the control border is now asserted on **all three** surfaces (`surface-3` was
|
|
920
1088
|
missing, and it is the worst case that pinned the dark theme to the floor) and at **3.25**, not
|
|
921
|
-
3.0
|
|
1089
|
+
3.0, and a boundary that clears the floor by 0.02 has no headroom, and the margin is what catches a
|
|
922
1090
|
revert to the old ink.
|
|
923
1091
|
- `css.test.mjs`: a stat label must spend a *text* ink, and a list row hosting a stretched target
|
|
924
|
-
must anchor it
|
|
1092
|
+
must anchor it; both are class↔token pairings that every token-level assertion passes straight
|
|
925
1093
|
through.
|
|
926
1094
|
- `browser/card-stretch.mjs`: the list case, asserting **row one** (the point that hides the bug is
|
|
927
1095
|
row two, which passes even when the overlay belongs to another row).
|
|
928
1096
|
|
|
929
|
-
## [1.29.0]
|
|
1097
|
+
## [1.29.0] - 2026-08-18
|
|
930
1098
|
Improvement note 001 from a second consuming app (a 40-screen back office). Eight findings; six
|
|
931
1099
|
executed, one already documented, one left as an owner decision.
|
|
932
1100
|
### Added
|
|
933
1101
|
- **`clearable` on `FdyCfl`** (Vue · React · Blazor). The value type already said `Row | null`, but
|
|
934
|
-
the emit was `Row` only
|
|
1102
|
+
the emit was `Row` only, so a choose-from-list could be **set and never unset**, which breaks every
|
|
935
1103
|
*optional* foreign key (a device with no project, an expense with no workflow, a top-level record
|
|
936
1104
|
with no parent). A user who picked the wrong row had to reload the form. The clear control is a
|
|
937
1105
|
second `.fdy-input-group__btn`, emits `null`, returns focus to the trigger, and never touches the
|
|
938
1106
|
dialog.
|
|
939
|
-
- **`.fdy-label--required`**
|
|
1107
|
+
- **`.fdy-label--required`** paints the marker through `::after` with **CSS alt text**
|
|
940
1108
|
(`content:"*" / ""`), so the glyph appears and the accessibility tree gets nothing. The control
|
|
941
1109
|
already carries `required`; a `<span>` an app has to remember to mark `aria-hidden` can be
|
|
942
1110
|
forgotten, this cannot.
|
|
943
|
-
- **`.fdy-icon`**
|
|
1111
|
+
- **`.fdy-icon`** is a `1em` square, `flex:none` box for the icons the kit deliberately does not
|
|
944
1112
|
ship. Every icon slot it *does* ship (`.fdy-btn__icon`, `.fdy-nav__icon`, `.fdy-state__icon`)
|
|
945
1113
|
already assumed something sensible inside; a standalone icon had no contract.
|
|
946
|
-
- **`.fdy-text-success` · `.fdy-text-warning` · `.fdy-text-danger
|
|
1114
|
+
- **`.fdy-text-success` · `.fdy-text-warning` · `.fdy-text-danger`**: the kit had three
|
|
947
1115
|
*de-emphasis* text roles and no *state* role, so a consequential sentence ("Amount changed from X
|
|
948
1116
|
to Y", "No approver assigned") had to fall back to `.fdy-text-caption`, which de-emphasises
|
|
949
1117
|
exactly the line that should stand out. A badge is wrong (prose, not a status) and an alert is
|
|
950
1118
|
wrong (a block, not one line in a row).
|
|
951
1119
|
### Fixed
|
|
952
1120
|
- **`.fdy-menu__item:focus-visible` gets a real ring.** It marked focus with the same fill as
|
|
953
|
-
`:hover`, and `freeday-menu.js` moves real DOM focus
|
|
1121
|
+
`:hover`, and `freeday-menu.js` moves real DOM focus, so arrowing through a menu was invisible,
|
|
954
1122
|
and hover and focus were identical to everyone else. `.fdy-nav__item`, in the same kit, has always
|
|
955
1123
|
done it correctly. Reported by two different apps before it was fixed.
|
|
956
1124
|
- **A grouped `<fieldset>` now has its spacing.** `fieldset.fdy-field` already reset the UA border,
|
|
957
|
-
but the **rendered legend is laid out outside the flex flow**, so `gap` never reached it
|
|
1125
|
+
but the **rendered legend is laid out outside the flex flow**, so `gap` never reached it,
|
|
958
1126
|
measured 0px between legend and first control. One declaration on the reset the kit already
|
|
959
1127
|
ships, not a new block; `COMPONENTS.md` documents the compose (`.fdy-field` on the fieldset,
|
|
960
1128
|
`.fdy-label` on the legend), which is what was actually missing.
|
|
961
1129
|
### Notes on the shape of the fix
|
|
962
1130
|
- **§7 (an Indonesian `aria-label`) is not what it looked like.** The report assumed the rest of the
|
|
963
|
-
kit's output is English; it is not
|
|
1131
|
+
kit's output is English; it is not. *Every* user-visible string the vanilla enhancers write is
|
|
964
1132
|
Indonesian (`Sebelumnya`, `Berikutnya`, `Berisi teks`, `Reset`, `Tutup`, `Menampilkan …`,
|
|
965
1133
|
`Bulan berikutnya`, `Format tidak valid.`). Translating one would **create** the mixed interface
|
|
966
|
-
the note objects to. The real choice
|
|
967
|
-
every Indonesian consumer
|
|
1134
|
+
the note objects to. The real choice, an override hook or switching the defaults and breaking
|
|
1135
|
+
every Indonesian consumer, is recorded in `NEXT-UP.md` #6 with its trigger; `COMPONENTS.md` now
|
|
968
1136
|
states the caveat in full and points English apps at the typed wrappers.
|
|
969
1137
|
- **§8 needed no code**, only the line it asked for: the filter button and its dialog share an
|
|
970
1138
|
accessible name on purpose, so a test suite wants `getByRole`, not `getByLabel`.
|
|
971
1139
|
- Two traps found by measuring rather than reading: `.fdy-icon` as a bare inline element **ignored
|
|
972
1140
|
`width` entirely** (it does not apply to non-replaced inline boxes) and rendered 936px wide until
|
|
973
1141
|
it got a `display`; and stacking two colour roles (`.fdy-help.fdy-text-warning`) silently loses to
|
|
974
|
-
whichever rule the bundle happens to emit later
|
|
975
|
-
### Added
|
|
1142
|
+
whichever rule the bundle happens to emit later. That is documented, and the docs page no longer does it.
|
|
1143
|
+
### Added: guards
|
|
976
1144
|
- Adapter specs for `clearable` in both Vue and React (real clicks: emits `null`, empties the field,
|
|
977
1145
|
the control disappears with the value it cleared, focus lands on the trigger, dialog stays shut).
|
|
978
1146
|
- CSS gate: menu focus must render as more than the hover fill; the required marker must keep its
|
|
979
1147
|
alt text; `.fdy-icon` must keep a display that accepts a width; the state roles must spend the
|
|
980
|
-
exact inks the contrast gate proves readable
|
|
1148
|
+
exact inks the contrast gate proves readable, and that last one closes the seam between the two test
|
|
981
1149
|
files, where a class could drift to a weaker token while every token assertion stayed green.
|
|
982
1150
|
- `contrast.test.mjs` now also asserts the state inks on **plain** surfaces, not just over their own
|
|
983
1151
|
`-soft` fills.
|
|
984
1152
|
|
|
985
|
-
## [1.28.0]
|
|
1153
|
+
## [1.28.0] - 2026-08-18
|
|
986
1154
|
Improvement note 007, written while fixing a workspace picker whose cards showed a pointer cursor
|
|
987
1155
|
and swallowed every click.
|
|
988
1156
|
### Added
|
|
989
|
-
- **`.fdy-btn--stretch`**
|
|
1157
|
+
- **`.fdy-btn--stretch`** is the pattern for **one card, one primary action, one escape hatch**, which
|
|
990
1158
|
the kit had no shape for: `--button` is a card that *is* one control, `--interactive` a card that
|
|
991
1159
|
merely *has* one, and a card with two actions can be neither (interactive content nested in a
|
|
992
1160
|
`<button>` is invalid HTML). The primary control keeps its real `<button>`/`<a>` semantics and
|
|
@@ -995,14 +1163,14 @@ and swallowed every click.
|
|
|
995
1163
|
button nudges itself with a transform on `:hover`/`:active`, a transformed element becomes the
|
|
996
1164
|
containing block for its own absolutely positioned descendants, and the overlay therefore
|
|
997
1165
|
re-anchors from the card to the button's own box mid-gesture. `mousedown` lands on the button,
|
|
998
|
-
`mouseup` somewhere else, and the browser fires `click` on their common ancestor
|
|
1166
|
+
`mouseup` somewhere else, and the browser fires `click` on their common ancestor, so the button never
|
|
999
1167
|
gets one. The symptom is identical to the bug being fixed.
|
|
1000
1168
|
The report found this on `--text`/`--ghost`, which break on press. The kit's **base** rule is
|
|
1001
1169
|
`.fdy-btn:hover{transform:translateY(-1px)}`, so the default and `--danger` fills break one step
|
|
1002
|
-
earlier, on hover: neutralising only `:active
|
|
1170
|
+
earlier, on hover: neutralising only `:active`, the obvious half-fix, still leaks.
|
|
1003
1171
|
- **Escape hatches are raised automatically.** Every focusable element in a card holding a stretched
|
|
1004
1172
|
target sits above the overlay without markup or CSS from the consumer, because forgetting a
|
|
1005
|
-
`z-index` here fails silently
|
|
1173
|
+
`z-index` here fails silently: the control looks and hovers exactly as before and simply never
|
|
1006
1174
|
receives the click.
|
|
1007
1175
|
### Fixed
|
|
1008
1176
|
- `--interactive` is now documented as **presentational**: the only affordance in the kit whose
|
|
@@ -1012,13 +1180,13 @@ and swallowed every click.
|
|
|
1012
1180
|
### Notes on the shape of the fix
|
|
1013
1181
|
- **Raising the escape hatch must not outweigh the app.** The first version declared
|
|
1014
1182
|
`position:relative` at normal specificity and *measurably* dragged an absolutely positioned corner
|
|
1015
|
-
dismiss button back into the flow
|
|
1183
|
+
dismiss button back into the flow, a control the app had pinned itself, moved by the kit, with no
|
|
1016
1184
|
error. `z-index` still wins, but the `position` it needs is now a zero-specificity `:where()`
|
|
1017
1185
|
default that any app rule beats. The two rules must stay split; the gate asserts it.
|
|
1018
1186
|
- **Cards only.** The overlay anchors to the nearest *positioned* ancestor. `.fdy-list__row` is not
|
|
1019
|
-
positioned (`.fdy-list` is), so a stretched target in a list row would cover the whole list
|
|
1187
|
+
positioned (`.fdy-list` is), so a stretched target in a list row would cover the whole list,
|
|
1020
1188
|
documented, with `.fdy-list__row--button` as the answer for clickable rows.
|
|
1021
|
-
### Added
|
|
1189
|
+
### Added: guards
|
|
1022
1190
|
- `browser/card-stretch.mjs` drives real presses: the target receives clicks from anywhere on the
|
|
1023
1191
|
card, the click is dispatched **on the button** rather than on a common ancestor, the secondary
|
|
1024
1192
|
action and an inline link keep their own clicks, and an app-pinned control keeps its own
|
|
@@ -1027,43 +1195,43 @@ and swallowed every click.
|
|
|
1027
1195
|
- `test/css.test.mjs` guards all of it at the CI gate. Mutation-checked against six defects,
|
|
1028
1196
|
including both of the ones written during this change.
|
|
1029
1197
|
|
|
1030
|
-
## [1.27.0]
|
|
1198
|
+
## [1.27.0] - 2026-08-14
|
|
1031
1199
|
Improvement note #44, found while building a settings screen whose only numeric field looked like it
|
|
1032
1200
|
belonged to a different application.
|
|
1033
1201
|
### Fixed
|
|
1034
1202
|
- **`.fdy-input[type="number"]` no longer shows the user agent's spin buttons.** They are OS widgets
|
|
1035
1203
|
in OS colours that no theme reaches, so on a dark surface they read as a light-grey artefact glued
|
|
1036
|
-
to an otherwise themed field
|
|
1204
|
+
to an otherwise themed field, the one unthemed control on a page where everything else is themed
|
|
1037
1205
|
to the last pixel. Both halves ship, because they cover different engines: `appearance: textfield`
|
|
1038
1206
|
(Firefox) and `::-webkit-outer/inner-spin-button { appearance: none }` (Blink/WebKit).
|
|
1039
1207
|
### Added
|
|
1040
|
-
- **`[data-fdy-number]` + `freeday-number.js`**
|
|
1208
|
+
- **`[data-fdy-number]` + `freeday-number.js`** put the increment affordance back on the kit's terms,
|
|
1041
1209
|
since hiding the native buttons removes it. **Not a new block:** it is an `.fdy-input-group` with
|
|
1042
1210
|
two `__btn`s, so it inherits the shared border, `:focus-within` ring and `:has()` error promotion
|
|
1043
1211
|
that already existed. The reported `.fdy-number` block would have duplicated all of it.
|
|
1044
1212
|
- **No custom event.** Stepping dispatches native bubbling `input` + `change` on the input, so
|
|
1045
|
-
`v-model` / `onChange` / `@bind` work with no adapter and no new API
|
|
1213
|
+
`v-model` / `onChange` / `@bind` work with no adapter and no new API, because the input stays the source
|
|
1046
1214
|
of truth. That is also why this needs no typed wrapper in any of the four stacks.
|
|
1047
1215
|
- `min`/`max`/`step` live on the input and the buttons never redo the arithmetic (`stepUp()`/
|
|
1048
1216
|
`stepDown()` clamp for free). They go `disabled` at a bound, on a `disabled`/`readonly` field,
|
|
1049
|
-
and when `step="any"
|
|
1217
|
+
and when `step="any"`, which has no defined increment and makes `stepUp()` throw, so a stepper
|
|
1050
1218
|
cannot honestly express it.
|
|
1051
1219
|
- A `MutationObserver` watches `disabled`/`readonly`/`min`/`max`/`step`: a framework changes those
|
|
1052
1220
|
without firing an event, and a button that still looks enabled while doing nothing is the exact
|
|
1053
|
-
lie this state machine exists to prevent. (First observer in the kit
|
|
1221
|
+
lie this state machine exists to prevent. (First observer in the kit; the alternative was a
|
|
1054
1222
|
button whose appearance silently drifts from its behaviour.)
|
|
1055
1223
|
- The buttons are **not tab stops** (`tabindex="-1"`): the input is already focusable and ↑/↓
|
|
1056
1224
|
already step it, so two extra stops per field cost every keyboard user and buy nothing. They keep
|
|
1057
1225
|
an `aria-label`, and `type="button"` so they cannot submit their form.
|
|
1058
1226
|
- `.fdy-input-group__btn` gains a **leading-position** rule (divider on the correct side when the
|
|
1059
|
-
button comes first) and a **`:disabled`** state
|
|
1227
|
+
button comes first) and a **`:disabled`** state: dim + `not-allowed`, with `:hover` withdrawn,
|
|
1060
1228
|
because a disabled button still matches `:hover`.
|
|
1061
1229
|
- `COMPONENTS.md` now answers **which `type` `.fdy-input` covers**, which is the question that would
|
|
1062
1230
|
have prevented this note: every text-like type themes identically, and the two that keep a native
|
|
1063
1231
|
widget are named with what to do about each.
|
|
1064
1232
|
### Notes on the shape of the fix
|
|
1065
|
-
- **`type="search"` deliberately left alone.** Its WebKit clear (×) button is unthemed too
|
|
1066
|
-
is on the kit's own docs pages, 10 of them
|
|
1233
|
+
- **`type="search"` deliberately left alone.** Its WebKit clear (×) button is unthemed too, and it
|
|
1234
|
+
is on the kit's own docs pages, 10 of them, but it is the only way to empty the field. Stripping
|
|
1067
1235
|
it removes function, not chrome; the number arrows only removed a mouse-only increment that ↑/↓
|
|
1068
1236
|
still provides. `type="date"`/`time` likewise keep their picker indicator; the kit ships its own
|
|
1069
1237
|
datepicker/timepicker and the docs now say to use those instead.
|
|
@@ -1071,27 +1239,27 @@ belonged to a different application.
|
|
|
1071
1239
|
`docs/reference-screen.html` and `examples/` (against `search` ×10, `text` ×12, `date` ×4). The kit
|
|
1072
1240
|
never used the input it shipped. The docs page now carries a real number field, in the input-group
|
|
1073
1241
|
section where it belongs.
|
|
1074
|
-
### Added
|
|
1242
|
+
### Added: guards
|
|
1075
1243
|
- `test/css.test.mjs` asserts both engine halves survive (each is invisible in review and each brings
|
|
1076
1244
|
the artefact back on one engine only), and states in-file why `search`/`date` are excluded.
|
|
1077
1245
|
- `browser/number.mjs` drives real clicks and a real Tab: stepping fires native `input` + `change`,
|
|
1078
1246
|
bounds disable the right button, a disabled button changes nothing, `readonly` and `step="any"`
|
|
1079
1247
|
are inert without throwing, and Tab skips the buttons. Mutation-checked against five defects.
|
|
1080
|
-
- `browser/harness.mjs` gains `pressKey
|
|
1248
|
+
- `browser/harness.mjs` gains `pressKey`, since tab order only moves for a trusted key, so the claim is
|
|
1081
1249
|
measured rather than read off an attribute.
|
|
1082
1250
|
|
|
1083
|
-
## [1.26.0]
|
|
1251
|
+
## [1.26.0] - 2026-08-14
|
|
1084
1252
|
Improvement note #43, found while chasing a "the upload is stuck" report on a 626 KB PDF: the
|
|
1085
1253
|
transfer took about a second, the server then spent nearly a minute reading the document.
|
|
1086
1254
|
### Added
|
|
1087
|
-
- **`row.waiting(label)`**
|
|
1255
|
+
- **`row.waiting(label)`** is the state between `setProgress` and `done`: the bytes are gone, the
|
|
1088
1256
|
server has not answered. The row's only long-running state was named after the *transfer*, so it
|
|
1089
|
-
kept saying "Mengunggah…" for the whole minute of server-side work
|
|
1257
|
+
kept saying "Mengunggah…" for the whole minute of server-side work, and `setProgress(100)` made it
|
|
1090
1258
|
worse, because a full bar that then sits still is the most convincing "hung" signal a UI can
|
|
1091
1259
|
produce. There was no way out within the row's API: `done()` claims success, `fail()` claims an
|
|
1092
1260
|
error, `ready()` walks backwards. Consumers were rendering a second status line outside the row and
|
|
1093
1261
|
leaving the row to contradict it.
|
|
1094
|
-
The bar goes **indeterminate** and drops `aria-valuenow
|
|
1262
|
+
The bar goes **indeterminate** and drops `aria-valuenow`, and a progressbar with no value is exactly
|
|
1095
1263
|
what ARIA calls indeterminate, which is the contract `COMPONENTS.md` already stated for
|
|
1096
1264
|
`.fdy-progress`. The label is the consumer's, because only they know what the server is doing
|
|
1097
1265
|
(`Membaca PDF…`, `Memindai…`); it falls back to `Menunggu server…`.
|
|
@@ -1100,107 +1268,107 @@ transfer took about a second, the server then spent nearly a minute reading the
|
|
|
1100
1268
|
### Notes on the shape of the fix
|
|
1101
1269
|
- The report's patch would have shipped the symptom it set out to remove. It put the modifier on the
|
|
1102
1270
|
**bar** (`.fdy-progress--indeterminate` styles `.fdy-progress__bar`, so it belongs on the
|
|
1103
|
-
container
|
|
1271
|
+
container, so on the bar it matches nothing) and then set an inline `width:100%`, which beats the
|
|
1104
1272
|
modifier's own width anyway. Both mistakes render a full, frozen bar. The note also hedged that
|
|
1105
1273
|
`.fdy-progress--indeterminate` might not exist; it has all along.
|
|
1106
1274
|
- **Leaving the state needs more care than entering it.** `.fdy-progress__bar` is a plain block div:
|
|
1107
1275
|
with no width it fills its track. So `uploading()`/`setProgress()` restore an explicit width when
|
|
1108
1276
|
they clear the modifier, or a retried row paints a *full* bar while meaning 0%. `done()`, `fail()`
|
|
1109
|
-
and `ready()` need no counterpart
|
|
1277
|
+
and `ready()` need no counterpart, since they drop the progress element outright, modifier and all
|
|
1110
1278
|
(contrary to the note, which expected a line in each).
|
|
1111
1279
|
- **No `.fdy-file--waiting` class.** `uploading` has none either; only `--success`/`--error` do,
|
|
1112
1280
|
because they carry colour. A documented class with no rule is markup that looks like it does
|
|
1113
1281
|
something.
|
|
1114
1282
|
- Under `prefers-reduced-motion: reduce` the kit's indeterminate treatment is a dimmed **full** bar
|
|
1115
|
-
(no animation left to carry the meaning)
|
|
1283
|
+
(no animation left to carry the meaning), pre-existing behaviour for every indeterminate progress,
|
|
1116
1284
|
not introduced here. For those users the honest signal is the label, not the bar.
|
|
1117
|
-
### Added
|
|
1285
|
+
### Added: guards
|
|
1118
1286
|
- `browser/upload-states.mjs` gains a third spec, measuring what the **engine renders** rather than
|
|
1119
1287
|
what the source declares: both ways to get this wrong are invisible in a code read. Mutation-checked
|
|
1120
|
-
against five defects, including the report's own two
|
|
1288
|
+
against five defects, including the report's own two: modifier-on-bar, inline `width:100%`, no
|
|
1121
1289
|
width restored on return, `aria-valuenow` kept, and label ignored.
|
|
1122
1290
|
|
|
1123
|
-
## [1.25.0]
|
|
1124
|
-
Improvement note #42, found while adopting 1.24.0
|
|
1291
|
+
## [1.25.0] - 2026-08-13
|
|
1292
|
+
Improvement note #42, found while adopting 1.24.0, the other half of the same integration.
|
|
1125
1293
|
### Fixed
|
|
1126
1294
|
- **`fdy-upload-remove` now fires on the dropzone**, the same element as `fdy-upload-add`. It was
|
|
1127
|
-
dispatched on the *row*, which lives in the file list
|
|
1295
|
+
dispatched on the *row*, which lives in the file list, and the kit's own markup contract puts that
|
|
1128
1296
|
list as a **sibling** of the dropzone, so the event never bubbled through the zone. A consumer
|
|
1129
1297
|
following the documentation got `add` and never got `remove`: no error, right event name, right
|
|
1130
1298
|
element, and the other event on that element working. Their state kept a file the user had already
|
|
1131
1299
|
taken away.
|
|
1132
|
-
The kit's own header comment was the source of the mistake
|
|
1300
|
+
The kit's own header comment was the source of the mistake: it said both events are emitted "on the
|
|
1133
1301
|
dropzone" while the code dispatched one of them somewhere else. It now states the target for each,
|
|
1134
1302
|
and why (a row in a sibling list can never reach the zone). `COMPONENTS.md` says it once beside the
|
|
1135
1303
|
row state table, with the removal listener in the worked example.
|
|
1136
1304
|
### Notes on the shape of the fix
|
|
1137
1305
|
- The report proposed dispatching on **both** the row and the zone, for backward compatibility. Not
|
|
1138
|
-
taken, and the guard proves why: when the file list is **nested inside** the dropzone
|
|
1139
|
-
`data-filelist` permits
|
|
1306
|
+
taken, and the guard proves why: when the file list is **nested inside** the dropzone, which
|
|
1307
|
+
`data-filelist` permits, the row already bubbles through the zone, so a second dispatch makes a
|
|
1140
1308
|
plain zone listener fire **twice** per removal. Firing on both also leaves the pair asymmetric (one
|
|
1141
1309
|
`add`, two `remove`s for anyone delegating on a common ancestor), which is the same class of silent
|
|
1142
1310
|
bug this note is about. One canonical target is the honest fix.
|
|
1143
1311
|
- Dispatching on the zone also fixes the **listless** case from 1.24.0: a row that was never attached
|
|
1144
1312
|
to the document bubbles to nothing at all, so its × was previously unobservable.
|
|
1145
1313
|
- *Migration:* a consumer that worked around the old behaviour by listening on the file list must
|
|
1146
|
-
move that listener to the dropzone. That position was never documented
|
|
1147
|
-
### Added
|
|
1314
|
+
move that listener to the dropzone. That position was never documented; it was the bug.
|
|
1315
|
+
### Added: guards
|
|
1148
1316
|
- `browser/upload-states.mjs` gains a second spec: removal fires on the dropzone **exactly once**, in
|
|
1149
1317
|
both layouts (list as sibling, list nested inside the zone), and delegation on a shared ancestor
|
|
1150
|
-
sees no duplicates. Mutation-checked against *both* rejected designs
|
|
1318
|
+
sees no duplicates. Mutation-checked against *both* rejected designs: reverting to the row target
|
|
1151
1319
|
fails it, and so does the report's dispatch-on-both.
|
|
1152
1320
|
|
|
1153
|
-
## [1.24.0]
|
|
1321
|
+
## [1.24.0] - 2026-08-13
|
|
1154
1322
|
Improvement note #41: the upload row had no "chosen, not yet sent" state, so every consumer-driven
|
|
1155
1323
|
integration showed a transfer that had not started.
|
|
1156
1324
|
### Fixed
|
|
1157
1325
|
- **A dropped file now rests until the consumer starts the transfer.** `handleFiles` called
|
|
1158
|
-
`row.uploading()` unconditionally
|
|
1326
|
+
`row.uploading()` unconditionally, and *before* dispatching `fdy-upload-add`, so a consumer could
|
|
1159
1327
|
not pre-empt it. Between the drop and the app's own submit button (which may be a minute, while the
|
|
1160
1328
|
user fills in the rest of the form) the row claimed to be uploading, with a progress bar that never
|
|
1161
1329
|
moved. A user watching that reasonably concludes the upload has hung and reports a bug against a
|
|
1162
1330
|
transfer that was never started. The state machine was missing its start state: `done()` claims
|
|
1163
1331
|
success, `fail()` claims an error, and `uploading()` was where it already was.
|
|
1164
|
-
The demo path is unchanged
|
|
1332
|
+
The demo path is unchanged: with `data-fdy-upload-simulate` the kit *is* performing a transfer, so
|
|
1165
1333
|
showing one stays correct. That attribute already marks the only place the old default was right,
|
|
1166
1334
|
which is why this changes the default rather than adding an opt-in: making correct integrations opt
|
|
1167
1335
|
in to correctness is backwards.
|
|
1168
1336
|
- **`fdy-upload-add` no longer depends on rendering a row.** The guard was `if (!list || !fileList)
|
|
1169
|
-
return;`, so a dropzone with no file list lost the event that tells the app a file arrived
|
|
1337
|
+
return;`, so a dropzone with no file list lost the event that tells the app a file arrived,
|
|
1170
1338
|
"bring your own row" silently cost you the notification. Rendering and announcing are now separate:
|
|
1171
1339
|
no list means no row is attached, and the event still fires with a working `detail.row`.
|
|
1172
1340
|
Deliberately **no new attribute** for this. The reporter proposed `data-rows="off"`; `list` is used
|
|
1173
1341
|
in exactly two places, so decoupling covers the same case without growing the API surface, and no
|
|
1174
1342
|
page can be relying on "no event".
|
|
1175
1343
|
### Added
|
|
1176
|
-
- **`row.ready()`**
|
|
1344
|
+
- **`row.ready()`** is the rest state, and the way back to it (a failed attempt the user will retry).
|
|
1177
1345
|
It reuses the existing `dropProgress()`, so the bar is removed exactly as `done()`/`fail()` do it.
|
|
1178
1346
|
### Docs
|
|
1179
1347
|
- **`COMPONENTS.md` documents the row state machine at all.** `uploading()` / `setProgress()` /
|
|
1180
|
-
`done()` / `fail()` had **zero** mentions anywhere in the shipped docs
|
|
1348
|
+
`done()` / `fail()` had **zero** mentions anywhere in the shipped docs, so a consumer holding
|
|
1181
1349
|
`detail.row` had no supported way to know they existed, which is a fair part of why the old default
|
|
1182
1350
|
went unquestioned. The section now carries the state table, a worked example, the
|
|
1183
1351
|
bring-your-own-row position, and `data-fdy-upload-simulate` marked demo-only.
|
|
1184
1352
|
Two corrections to the report while transcribing it: the function is `handleFiles` (not `addFiles`)
|
|
1185
|
-
and the attribute is `data-fdy-upload-simulate` (not `data-simulate`)
|
|
1353
|
+
and the attribute is `data-fdy-upload-simulate` (not `data-simulate`), and the latter matters, since
|
|
1186
1354
|
documenting the wrong name would have consumers set an attribute that does nothing.
|
|
1187
|
-
### Added
|
|
1188
|
-
- **`browser/upload-states.mjs`**
|
|
1355
|
+
### Added: guards
|
|
1356
|
+
- **`browser/upload-states.mjs`** drops a real `File` and asserts the rest state shows no progress
|
|
1189
1357
|
bar, that `uploading()` → `setProgress()` → `done()` still chains, that the simulate path is
|
|
1190
1358
|
untouched, and that a listless dropzone still dispatches. Mutation-checked on both halves.
|
|
1191
1359
|
The fixture wraps the listless dropzone in its own container **on purpose**: with no
|
|
1192
1360
|
`data-filelist` the enhancer falls back to `parentNode.querySelector('.fdy-filelist')`, so a bare
|
|
1193
|
-
dropzone sharing a parent with another list adopts it
|
|
1361
|
+
dropzone sharing a parent with another list adopts it; the first version of this guard was
|
|
1194
1362
|
testing nothing, and the mutation run is what exposed that.
|
|
1195
1363
|
|
|
1196
|
-
## [1.23.0]
|
|
1364
|
+
## [1.23.0] - 2026-08-13
|
|
1197
1365
|
Consumption round 6 (`improvement-notes/006`). The reporter filed two of the three as **their own**
|
|
1198
|
-
bugs rather than the kit's
|
|
1366
|
+
bugs rather than the kit's, and they were right about the code, but in both cases the kit had a way
|
|
1199
1367
|
to make the mistake impossible and had not taken it. Those are the two most valuable findings here.
|
|
1200
1368
|
### Fixed
|
|
1201
1369
|
- **`initAll(ctx)` now enhances `ctx` itself, not just its descendants.** `querySelectorAll` never
|
|
1202
|
-
matches its own root, so a framework ref placed **on** the widget
|
|
1203
|
-
which is the ordinary shape when a component's root element *is* the widget
|
|
1370
|
+
matches its own root, so a framework ref placed **on** the widget, `<div ref="menu" data-fdy-menu>`,
|
|
1371
|
+
which is the ordinary shape when a component's root element *is* the widget, meant the one element
|
|
1204
1372
|
that needed enhancing was the only one that could not be found. It failed with **no error, no
|
|
1205
1373
|
warning, and a UI that looked finished**: the markup rendered and simply never opened. Measured
|
|
1206
1374
|
before the fix: `data-fdy-menu-ready` stayed `null` and `aria-expanded` stayed `"false"` while
|
|
@@ -1210,7 +1378,7 @@ to make the mistake impossible and had not taken it. Those are the two most valu
|
|
|
1210
1378
|
matched by the descendant query costs nothing. `useFreeday` (Vue/React) and `FreedayBlazor.initAll`
|
|
1211
1379
|
delegate to these, so they inherit the fix with no change of their own.
|
|
1212
1380
|
- **The stepper connector no longer draws through the markers when `__btn` is omitted.** The lift now
|
|
1213
|
-
lives on `.fdy-step__marker
|
|
1381
|
+
lives on `.fdy-step__marker`, the part that must always exist, instead of only on
|
|
1214
1382
|
`.fdy-step__btn`. The connector is `position:absolute; z-index:0`, and a positioned box paints
|
|
1215
1383
|
*after* in-flow inline content in the same stacking context, so a marker that was merely inline got
|
|
1216
1384
|
painted over: not for lack of a z-index, but for lack of being positioned at all. An
|
|
@@ -1218,14 +1386,14 @@ to make the mistake impossible and had not taken it. Those are the two most valu
|
|
|
1218
1386
|
a visible defect in shipped UI. Measured: hit-testing the marker's centre returned the connector's
|
|
1219
1387
|
own `.fdy-step` before, the marker after. `__btn` keeps its z-index for the navigable case.
|
|
1220
1388
|
### Added
|
|
1221
|
-
- **`.fdy-avatar--xs`** (1.5rem, `--text-xs`)
|
|
1389
|
+
- **`.fdy-avatar--xs`** (1.5rem, `--text-xs`): an avatar inside a control had nothing to reach for.
|
|
1222
1390
|
`.fdy-btn--sm` is `calc(var(--control-h) - var(--space-2))` = **2rem**, exactly `--avatar--sm`'s
|
|
1223
1391
|
size, so the monogram filled a small button edge to edge and the ghost border crossed the circle
|
|
1224
1392
|
(measured: trigger 32px tall, avatar 32×32). 1.5rem inside 2rem leaves the ~4px the kit's other
|
|
1225
|
-
controls give their icons. The report suggested `--text-2xs`; that token does not exist
|
|
1393
|
+
controls give their icons. The report suggested `--text-2xs`; that token does not exist, so `--text-xs`
|
|
1226
1394
|
is the smallest the kit has, and inventing a type step for one avatar size is not worth it.
|
|
1227
|
-
### Added
|
|
1228
|
-
- **`browser/root-init.mjs`**
|
|
1395
|
+
### Added: guards
|
|
1396
|
+
- **`browser/root-init.mjs`** mounts a widget *after* load, initialises it through its own root, and
|
|
1229
1397
|
drives a **real click** to prove it opens; repeated for a second enhancer so the fix reads as the
|
|
1230
1398
|
shared pattern rather than a one-off. Mutation-checked on both. Being marked `-ready` is explicitly
|
|
1231
1399
|
not accepted as passing: an early version of this check reported success against markup the enhancer
|
|
@@ -1236,54 +1404,54 @@ to make the mistake impossible and had not taken it. Those are the two most valu
|
|
|
1236
1404
|
never said which parts were optional); avatar gains its size table with the in-control guidance.
|
|
1237
1405
|
- Both `useFreeday` docstrings now show the ref-on-the-widget shape alongside the wrapping one.
|
|
1238
1406
|
|
|
1239
|
-
## [1.22.0]
|
|
1240
|
-
Two bodies of work. **(a)** three more findings from consumption round 5
|
|
1407
|
+
## [1.22.0] - 2026-08-12
|
|
1408
|
+
Two bodies of work. **(a)** three more findings from consumption round 5, where the report grew §5-§7
|
|
1241
1409
|
after 1.21.0 was cut; **(b)** a **routing** failure found in the same adopted project, which had been
|
|
1242
1410
|
built on raw markup + enhancers inside a framework that has typed wrappers because nothing in the
|
|
1243
1411
|
package ever told it otherwise. (b) was prepared as 1.21.1 and folded in here rather than shipped as
|
|
1244
1412
|
a separate patch minutes earlier.
|
|
1245
|
-
### Added
|
|
1246
|
-
- **`.fdy-nav--horizontal`**
|
|
1413
|
+
### Added: from consumption round 5 (§5, §6)
|
|
1414
|
+
- **`.fdy-nav--horizontal`** lays the same navigation links out as a **row**, for a top-nav
|
|
1247
1415
|
application that puts its primary nav in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar.
|
|
1248
1416
|
Deliberately a modifier, not a new block: the item, its states and `aria-current="page"` are
|
|
1249
1417
|
unchanged. On `.fdy-appbar--primary` the links go on-colour automatically (full on-colour ink in
|
|
1250
1418
|
every state; the current page is carried by the background wash plus the semibold weight the base
|
|
1251
1419
|
rule already applies, so no dimmed variant trades away the bar's guaranteed contrast).
|
|
1252
1420
|
Reported as "there is no horizontal navigation component": `.fdy-nav` was the sidebar's vertical
|
|
1253
|
-
menu and `.fdy-appbar` had `__brand`/`__spacer`/`__actions` but nothing to put *links* in
|
|
1421
|
+
menu and `.fdy-appbar` had `__brand`/`__spacer`/`__actions` but nothing to put *links* in, so a
|
|
1254
1422
|
top-nav app had a component for the bar and none for what the bar is mostly made of.
|
|
1255
1423
|
- **`.fdy-tabs__tab` also honours `aria-current="page"`**, so the tab *look* legitimately serves
|
|
1256
1424
|
**routed** sub-navigation (`/settings/profile` · `/settings/billing`) built from real links. A link
|
|
1257
|
-
cannot be `aria-selected
|
|
1425
|
+
cannot be `aria-selected`, which is invalid ARIA on an anchor, so the app that borrowed the class
|
|
1258
1426
|
had to restate the active style for a second attribute. Keep `role="tab"` + `freeday-tabs.js` for
|
|
1259
1427
|
**in-page** tabs, where the roving-tabindex / one-panel-per-tab contract is real.
|
|
1260
|
-
- **Pressed state for toggle buttons
|
|
1428
|
+
- **Pressed state for toggle buttons:** `aria-pressed="true"` now styles a soft primary fill + strong
|
|
1261
1429
|
ink on `--ghost`/`--text`, an inverted-gradient sunken look on the solid button, and the same
|
|
1262
1430
|
treatment in the danger hue. No new class or markup contract, since `aria-pressed` is already the
|
|
1263
|
-
correct attribute
|
|
1431
|
+
correct attribute, and it is what turns `.fdy-btn-group` from a joined row into a **complete**
|
|
1264
1432
|
segmented control. Previously "which one am I on" was left to each app to hand-tint, which is a
|
|
1265
1433
|
colour decision the kit should own.
|
|
1266
1434
|
*Found while building it:* one shared `.fdy-btn[aria-pressed="true"]` rule **cannot** work. A
|
|
1267
1435
|
gradient is a background-**image**, so the `background` shorthand resets `background-color` to
|
|
1268
|
-
transparent
|
|
1436
|
+
transparent, so at equal specificity the later rule won and the ghost segment lost its fill entirely.
|
|
1269
1437
|
Each fill variant now defines its own pressed look. The CSS read correctly in the file; only a real
|
|
1270
1438
|
engine showed it, which is why the new guard lives in `browser/`.
|
|
1271
|
-
### Fixed
|
|
1439
|
+
### Fixed: from consumption round 5 (§7)
|
|
1272
1440
|
- **Hard rule 1 in `COMPONENTS.md` now records its one exception.** The rule says a modifier is
|
|
1273
1441
|
always written beside its block class, but `.fdy-input-group__addon--icon` is standalone by design
|
|
1274
|
-
|
|
1442
|
+
Adding the base `__addon` gives a search glyph the grey fill and divider of a `Rp` / `%` prefix.
|
|
1275
1443
|
An agent following the rule literally produced the wrong control; one following the CSS comment
|
|
1276
1444
|
broke the stated rule. The exception is now written down, and it is the only one.
|
|
1277
|
-
### Fixed
|
|
1445
|
+
### Fixed: the package now routes by stack
|
|
1278
1446
|
- **The paste-block in `docs/agent-onboarding.md` never mentioned the adapters.** That block is the
|
|
1279
1447
|
only text that lands in a consuming project's agent instructions, so it is the only text an agent
|
|
1280
|
-
re-reads on every task
|
|
1448
|
+
re-reads on every task, and it opened with "a CSS kit + enhancers, **not a component framework**",
|
|
1281
1449
|
which actively steers a Vue/React project away from the typed components. It now opens with a
|
|
1282
1450
|
**step 0 routing table** (Vue → `/vue` · React → `/react` · Blazor → RCL · everything else → raw
|
|
1283
1451
|
markup), names the ten wrapped components, and states the failure mode: the raw path renders
|
|
1284
1452
|
correctly the first time and then silently stops hydrating.
|
|
1285
1453
|
- **`COMPONENTS.md` flags the wrapper at each of the ten sections**, so the choice is visible at the
|
|
1286
|
-
point of use rather than only on an onboarding page read once
|
|
1454
|
+
point of use rather than only on an onboarding page read once, with each component's *real*
|
|
1287
1455
|
binding (`v-model` / `value`+`onChange` / `@bind-Value` for the six value-bound ones; `open`+`close`
|
|
1288
1456
|
for modal and drawer; data props for chart; `columns`+`rows`+events for table). Hard rule 7 states
|
|
1289
1457
|
it up front.
|
|
@@ -1293,126 +1461,126 @@ a separate patch minutes earlier.
|
|
|
1293
1461
|
to the raw path, and notes that Vue/React re-implement the ten natively while Blazor wraps the
|
|
1294
1462
|
enhancer over interop.
|
|
1295
1463
|
### Notes
|
|
1296
|
-
- Root cause was structural, not editorial: at **v1.18.0** `docs/getting-started.md
|
|
1297
|
-
per-stack router the kit had
|
|
1464
|
+
- Root cause was structural, not editorial: at **v1.18.0** `docs/getting-started.md`, the only
|
|
1465
|
+
per-stack router the kit had, was **not in `files`**, so `npm i` delivered a README whose
|
|
1298
1466
|
"Starting a project? per stack" link pointed at a path that does not exist inside `node_modules`.
|
|
1299
1467
|
`adapters/` *was* shipped, so the wrappers sat in the install, unused, with nothing pointing at
|
|
1300
1468
|
them. v1.20.0 started shipping the docs; this release makes them route.
|
|
1301
1469
|
|
|
1302
|
-
## [1.21.0]
|
|
1303
|
-
Fifth round of real-app consumption feedback, written while that app adopted 1.20.0. Two findings
|
|
1470
|
+
## [1.21.0] - 2026-08-12
|
|
1471
|
+
Fifth round of real-app consumption feedback, written while that app adopted 1.20.0. Two findings,
|
|
1304
1472
|
and a **withdrawal**: the reporter retracted 1.20's rejected §A themselves after isolating the real
|
|
1305
1473
|
mechanism, which turned out to be a genuine kit bug hiding behind it. Both halves of that rejection
|
|
1306
1474
|
reproduce here, so the rejection stands and the withdrawal is recorded rather than re-litigated.
|
|
1307
1475
|
### Fixed
|
|
1308
1476
|
- **Hidden labels could scroll the whole page.** `.fdy-visually-hidden` is `position:absolute`, and
|
|
1309
1477
|
`clip` hides *painting*, not *layout*. With no positioned ancestor its containing block is the
|
|
1310
|
-
document, and `overflow` clips only what is contained inside the overflow box
|
|
1478
|
+
document, and `overflow` clips only what is contained inside the overflow box, so a hidden label in
|
|
1311
1479
|
a horizontally scrolling table (the kit's own recommended way to name an icon button) parks at its
|
|
1312
1480
|
static position and drags the document sideways. Measured in Chrome: **1351px** of phantom page
|
|
1313
1481
|
scroll from 11 spans in one table; `overflow-x:hidden` on the scroller, the shell, `body` and `html`
|
|
1314
1482
|
each changed it by **0px**; removing the spans took it to 0. Every clipping/scrolling container that
|
|
1315
|
-
holds consumer markup now declares `position:relative
|
|
1316
|
-
`.fdy-list`, `.fdy-card`, `.fdy-tabs__list`, `.fdy-carousel__viewport`, `.fdy-accordion
|
|
1483
|
+
holds consumer markup now declares `position:relative`: `.fdy-table-scroll`, `.fdy-table-wrap`,
|
|
1484
|
+
`.fdy-list`, `.fdy-card`, `.fdy-tabs__list`, `.fdy-carousel__viewport`, `.fdy-accordion`, each
|
|
1317
1485
|
measured to take its own case to 0 with the scroller still scrolling internally. The rest are
|
|
1318
1486
|
already inside something the kit positions (`<dialog>`, the sticky sidebar, a fixed popover) and are
|
|
1319
1487
|
listed with that reason in the new test.
|
|
1320
1488
|
**`.fdy-accordion` is the one to note:** it was contained only by its panel's reveal *animation*
|
|
1321
1489
|
(a transform makes a containing block), and that animation sits behind
|
|
1322
|
-
`prefers-reduced-motion: no-preference
|
|
1490
|
+
`prefers-reduced-motion: no-preference`, so the bug was reachable **only** by readers who asked for
|
|
1323
1491
|
reduced motion. Measured with the animation off: 2906px → 0.
|
|
1324
1492
|
*Possible migration:* if you absolutely positioned something inside a card, list or table scroller
|
|
1325
1493
|
and relied on it escaping, it now anchors to that container. Anchoring is the reason for the change.
|
|
1326
1494
|
- **`--button` rows and cards ignored `:disabled`.** `.fdy-list__row--button` adopts the UA button box
|
|
1327
|
-
but not its disabled state, so a row disabled mid-flight kept its hover tint and pointer cursor
|
|
1495
|
+
but not its disabled state, so a row disabled mid-flight kept its hover tint and pointer cursor, a
|
|
1328
1496
|
control answering the pointer while refusing input. `:disabled` and `[aria-disabled="true"]` now dim
|
|
1329
1497
|
it and withdraw hover, for `.fdy-list__row--button`, `.fdy-list__row--interactive`,
|
|
1330
1498
|
`.fdy-card--button` and `.fdy-card--interactive`. The report suggested `cursor:default`; the kit uses
|
|
1331
1499
|
`opacity:.5` + `cursor:not-allowed` on every other disabled control, and consistency wins.
|
|
1332
1500
|
### Changed
|
|
1333
|
-
- **`data-theme` is no longer root-scoped
|
|
1501
|
+
- **`data-theme` is no longer root-scoped**, the same move density made in 1.20.0, and for the same
|
|
1334
1502
|
reason. The two *explicit* selectors are now bare `[data-theme="dark"]` / `[data-theme="light"]`, so
|
|
1335
|
-
a `<section data-theme="dark">` inverts that region and **every component inside it follows
|
|
1503
|
+
a `<section data-theme="dark">` inverts that region and **every component inside it follows**,
|
|
1336
1504
|
card surfaces, inputs, and text roles like `.fdy-title-page` that set `color: var(--color-text)`
|
|
1337
1505
|
explicitly and therefore never saw a consumer's hand-rolled override. A dark brand panel beside a
|
|
1338
1506
|
light sign-in form is an ordinary layout; it should not cost a re-colouring pass. Setting the
|
|
1339
1507
|
attribute on `<html>` is unchanged, and a `[data-theme="light"]` island nested inside a dark region
|
|
1340
1508
|
wins in turn.
|
|
1341
|
-
**The system default stays root-scoped, deliberately
|
|
1509
|
+
**The system default stays root-scoped, deliberately.** The report asked for "the two generated
|
|
1342
1510
|
selectors", but there are three. Un-rooting `@media (prefers-color-scheme: dark) { :root:not(...) }`
|
|
1343
1511
|
would make it match every element that does not itself carry `data-theme="light"`, including the
|
|
1344
1512
|
*children* of a light island, dragging them back to dark. Measured: with the un-rooted variant that
|
|
1345
1513
|
island renders light ink on a light surface; root-scoped, it stays correct.
|
|
1346
|
-
### Added
|
|
1347
|
-
- **`test/css.test.mjs`**
|
|
1514
|
+
### Added: guards
|
|
1515
|
+
- **`test/css.test.mjs`** holds the containment invariant, CI-gated: a rule that declares `overflow` must
|
|
1348
1516
|
also be positioned, or be listed with the ancestor that already contains it. Single-line truncation
|
|
1349
1517
|
(`text-overflow:ellipsis` on a label) is excluded by shape, not by name, so new truncating labels
|
|
1350
1518
|
don't accumulate in an allowlist. A new clipping container fails the test until someone decides
|
|
1351
1519
|
which case it is.
|
|
1352
|
-
- **`browser/layout.mjs`**
|
|
1520
|
+
- **`browser/layout.mjs`** reproduces the same bug end-to-end in real Chrome (`npm run test:browser`): ten
|
|
1353
1521
|
hidden labels in a wide table, asserting the page cannot scroll horizontally *and* that the scroller
|
|
1354
1522
|
still scrolls. A static test cannot see this failure; only a layout engine can. Both guards
|
|
1355
|
-
mutation-checked
|
|
1356
|
-
- **`browser/state.mjs`**
|
|
1523
|
+
mutation-checked: reverting any single `position:relative` fails them.
|
|
1524
|
+
- **`browser/state.mjs`** covers pressed toggles and nav orientation in a real engine: the pressed ghost
|
|
1357
1525
|
segment must keep a flat background-colour (the mutation that reproduces the shorthand bug fails
|
|
1358
1526
|
it), the solid toggle must read as inset, `--horizontal` must actually be a row with the current
|
|
1359
1527
|
link marked, on-colour nav ink must stay on-colour in both states, and a routed tab must take its
|
|
1360
1528
|
underline from `aria-current`. Mutation-checked against all three fixes.
|
|
1361
|
-
- **`browser/theme.mjs`**
|
|
1529
|
+
- **`browser/theme.mjs`** covers subtree theming end-to-end: a `.fdy-title-page` and a `.fdy-card` inside
|
|
1362
1530
|
`<section data-theme="dark">` take the dark tokens, a nested light island goes back, and
|
|
1363
1531
|
`data-theme` on `<html>` still themes everything. `test/build.test.mjs` guards the selector shape
|
|
1364
1532
|
(not root-scoped, media block still is, and the block order the cascade depends on); this guards the
|
|
1365
1533
|
behaviour that shape exists for. Mutation-checked: re-rooting the selectors fails it.
|
|
1366
1534
|
### Notes
|
|
1367
1535
|
- Not adopted from the report, deliberately: `.fdy-datatable`, `.fdy-modal__body`, `.fdy-drawer__body`
|
|
1368
|
-
were listed as needing the same fix. Measured: they do not escape
|
|
1536
|
+
were listed as needing the same fix. Measured: they do not escape, because `<dialog>` is `position:fixed`
|
|
1369
1537
|
and is already their containing block, and the datatable's own scrolling child now carries it. They
|
|
1370
1538
|
are in the test's allowlist with that reason instead of carrying a declaration that does nothing.
|
|
1371
1539
|
|
|
1372
|
-
## [1.20.0]
|
|
1540
|
+
## [1.20.0] - 2026-08-12
|
|
1373
1541
|
Two bodies of work in one release (1.19.0 was prepared but never committed, tagged or published, so
|
|
1374
1542
|
it is folded in here rather than left as a phantom version):
|
|
1375
1543
|
**(a)** make the kit consumable by an **AI coding agent** in a new or migrating project, and clean the
|
|
1376
|
-
repo to production level; **(b)** act on the fourth round of real-app consumption feedback
|
|
1544
|
+
repo to production level; **(b)** act on the fourth round of real-app consumption feedback: five
|
|
1377
1545
|
confirmed gaps, one rejected premise, and one documentation bug of our own that chasing it exposed.
|
|
1378
|
-
### Added
|
|
1379
|
-
- **`.fdy-list` / `.fdy-list__row
|
|
1546
|
+
### Added: from consumption round 4
|
|
1547
|
+
- **`.fdy-list` / `.fdy-list__row`, the flat row container** (`src/components/list.css`). One bordered
|
|
1380
1548
|
surface, `--color-border-muted` hairline dividers, **no shadow**; `--interactive` for hover, and
|
|
1381
1549
|
`--button` for when the row *is* the control (UA box reset without losing the list surface), plus
|
|
1382
1550
|
`__main` / `__title` / `__meta` / `__aside` internals. `USAGE.md` §3 has always said list rows should
|
|
1383
|
-
be flat, but the only container the kit shipped was `.fdy-card
|
|
1551
|
+
be flat, but the only container the kit shipped was `.fdy-card`, which carries `--shadow-lift`, a
|
|
1384
1552
|
real 34px lift, so a responsive table that becomes a list below `md` had to choose between ten
|
|
1385
1553
|
floating cards or a hand-built box that needs a colour and escapes the token system.
|
|
1386
1554
|
- **`FdyTable`: controlled client-side page index.** `pageIndex` + `update:pageIndex` (Vue) /
|
|
1387
1555
|
`pageIndex` + `onPageIndexChange` (React) / `PageIndex` + `PageIndexChanged` (Blazor). The table
|
|
1388
1556
|
still filters/sorts/paginates; only *which page* moves to the parent. This is what makes the
|
|
1389
1557
|
`process` event from 1.18 pay off on a **responsive** screen: the pager renders inside
|
|
1390
|
-
`.fdy-datatable`, so a screen that hides the table below `md` loses it
|
|
1558
|
+
`.fdy-datatable`, so a screen that hides the table below `md` loses it, and in client mode the index
|
|
1391
1559
|
was a private ref with no prop, no event and no `goTo`. The two available options were "lose
|
|
1392
1560
|
pagination on mobile" or "own the page index in server mode", where `process` hands back exactly the
|
|
1393
1561
|
array you passed in. One index can now also span several tables, which is what a grouped list needs.
|
|
1394
1562
|
Reported after a real rebuild where `process` deleted, by measurement, zero lines.
|
|
1395
1563
|
- **`breakpoints.nav` (721)** in `tokens/breakpoints.mjs` + its type. `.fdy-app` switches the sidebar
|
|
1396
|
-
between off-canvas drawer and static column at 721px
|
|
1564
|
+
between off-canvas drawer and static column at 721px, a number that was in `app-shell.css` and
|
|
1397
1565
|
nowhere else, so every consumer of the shell hand-mirrored it. Getting it wrong is not cosmetic:
|
|
1398
1566
|
aligning to `md` (960) leaves 721–959px with a static sidebar that the script still treats as an
|
|
1399
1567
|
overlay, so opening the nav makes the page `inert` around a user with no way back out. A new test
|
|
1400
1568
|
asserts `nav` equals the CSS's `min-width` and that the `max-width` query is `nav - 1`, so the two
|
|
1401
1569
|
cannot drift.
|
|
1402
|
-
- **Blazor `FdyTable` gained `Process`** (+ the `FdyTableProcess<TRow>` record)
|
|
1570
|
+
- **Blazor `FdyTable` gained `Process`** (+ the `FdyTableProcess<TRow>` record); 1.18 added the
|
|
1403
1571
|
`process` event to Vue and React only, which left Blazor unable to drive a card list from the
|
|
1404
1572
|
processed set at all.
|
|
1405
1573
|
- **Browser guards for the controlled page index**, Vue and React, in `browser/adapter.mjs`: a table
|
|
1406
1574
|
whose pager lives *outside* the component, asserting both directions (parent → rows, and internal
|
|
1407
1575
|
pager → parent event). Mutation-checked: reverting the component to its private index fails them.
|
|
1408
|
-
### Changed
|
|
1576
|
+
### Changed: from consumption round 4
|
|
1409
1577
|
- **`data-density="compact"` is no longer root-scoped.** The generated selector was
|
|
1410
1578
|
`:root[data-density="compact"]`; it is now a bare `[data-density="compact"]`. These are inheriting
|
|
1411
|
-
custom properties, so density can be set on a route wrapper or a single section
|
|
1579
|
+
custom properties, so density can be set on a route wrapper or a single section, which is how it is
|
|
1412
1580
|
actually decided (per screen), not on `<html>` for the whole app. Setting it on the root still works
|
|
1413
1581
|
identically. A build test now asserts the selector is not root-scoped.
|
|
1414
|
-
### Fixed
|
|
1415
|
-
- **`USAGE.md` §3 misdescribed the kit's own elevation scale
|
|
1582
|
+
### Fixed: from consumption round 4
|
|
1583
|
+
- **`USAGE.md` §3 misdescribed the kit's own elevation scale.** Our bug, found while checking the
|
|
1416
1584
|
report. It prescribed `--shadow-1` for "a card" and `--shadow-4` for "modal / drawer", but `.fdy-card`
|
|
1417
1585
|
uses `--shadow-lift` (≈6× heavier than `--shadow-1`) and `.fdy-modal` uses `--shadow-lift-hover`;
|
|
1418
1586
|
`--shadow-1` is what the *data containers* use. §3 now carries the real component→token map, names
|
|
@@ -1420,15 +1588,15 @@ confirmed gaps, one rejected premise, and one documentation bug of our own that
|
|
|
1420
1588
|
warns about: docs restating a scale the components don't follow.
|
|
1421
1589
|
- **`.fdy-toolbar` vs `.fdy-filterbar` is now documented.** `.fdy-toolbar` is `align-items:center`
|
|
1422
1590
|
(right for bare controls); a `.fdy-field` with a *visible* label sits half a label-height low in it.
|
|
1423
|
-
Labelled fields belong in `.fdy-filterbar` (`align-items:flex-end
|
|
1591
|
+
Labelled fields belong in `.fdy-filterbar` (`align-items:flex-end`, which is why 1.11.2 had to
|
|
1424
1592
|
arbitrate the composed case). The difference existed but was written down nowhere, so the wrong
|
|
1425
1593
|
choice was only visible once rendered. Documented in `USAGE.md` §7 and `COMPONENTS.md`; no CSS
|
|
1426
1594
|
changed, so no existing screen shifts.
|
|
1427
|
-
### Notes
|
|
1595
|
+
### Notes: one report item rejected
|
|
1428
1596
|
- **"`.fdy-page-section` and `.fdy-table-scroll` do not compose" does not reproduce, and the proposed
|
|
1429
1597
|
`min-width:0` fix is a no-op.** Measured on `docs/reference-screen.html` (the same
|
|
1430
1598
|
section → datatable → scroller chain) with the table forced to 1400px in a 688px column: page
|
|
1431
|
-
overflow **0px**, the datatable held its column, the scroller scrolled internally
|
|
1599
|
+
overflow **0px**, the datatable held its column, the scroller scrolled internally, and adding
|
|
1432
1600
|
`min-width:0` changed nothing. Two mechanisms explain it: `.fdy-page-section` is flex **column**, so
|
|
1433
1601
|
`min-width:auto` (a main-axis rule) cannot cause horizontal growth; and `.fdy-table-scroll` has
|
|
1434
1602
|
`overflow-x:auto`, which gives it an automatic minimum size of **0**, so no ancestor flex can stretch
|
|
@@ -1436,48 +1604,48 @@ confirmed gaps, one rejected premise, and one documentation bug of our own that
|
|
|
1436
1604
|
(`.fdy-app__content`). The 129px overflow the reporter measured is real but originates in their own
|
|
1437
1605
|
wrapper chain; to locate it, walk the datatable's ancestors at the failing width and find the first
|
|
1438
1606
|
with `scrollWidth > clientWidth`. Third round in a row where a proposed fix would have changed
|
|
1439
|
-
nothing
|
|
1607
|
+
nothing: the premise gets verified before the patch, every time.
|
|
1440
1608
|
### Added
|
|
1441
|
-
- **`COMPONENTS.md
|
|
1609
|
+
- **`COMPONENTS.md`, the complete public class surface in one flat file.** Every component with its
|
|
1442
1610
|
block/element/modifier classes, a minimal markup skeleton, its enhancer hook and its a11y contract,
|
|
1443
1611
|
plus the full enhancer table (markup hook → script → global → events). The gap it closes: 425
|
|
1444
1612
|
`.fdy-*` classes exist in `src/components/`, but only ~33 appeared as literal strings anywhere in
|
|
1445
|
-
the shipped docs
|
|
1613
|
+
the shipped docs; the rest lived in a 2,552-line HTML page and in the CSS source, neither of which
|
|
1446
1614
|
an agent (or a hurried human) reads.
|
|
1447
|
-
- **`docs/agent-onboarding.md`**
|
|
1615
|
+
- **`docs/agent-onboarding.md`** is the entry point for an agent working in a *consuming* project: a
|
|
1448
1616
|
paste-ready block for that project's `CLAUDE.md`/`AGENTS.md`, what ships in the package and which
|
|
1449
1617
|
question each file answers, the order to build a new screen in, a **migration mapping table**
|
|
1450
1618
|
(Bootstrap / MudBlazor / utility-class conventions → Freeday) with a safe conversion order, and a
|
|
1451
1619
|
pre-completion verification checklist.
|
|
1452
|
-
- **`docs/reference-screen.html`**
|
|
1620
|
+
- **`docs/reference-screen.html`** is one complete business screen assembled the intended way:
|
|
1453
1621
|
`.fdy-app` → `.fdy-page` → `.fdy-page__header` → `.fdy-stats` → sections holding a chart and a
|
|
1454
1622
|
full data table, plus a native-`<dialog>` modal, theme/density toggles and the shell's nav-toggle
|
|
1455
1623
|
wiring. v1.18.0 shipped the composition primitives but nothing demonstrated them end-to-end.
|
|
1456
|
-
- **`test/docs.test.mjs
|
|
1624
|
+
- **`test/docs.test.mjs`, the drift guard for the agent-facing docs.** Every fully-written `.fdy-*`
|
|
1457
1625
|
class named in `COMPONENTS.md`, `USAGE.md`, `docs/agent-onboarding.md` and
|
|
1458
1626
|
`docs/reference-screen.html` must exist in the kit (CSS selectors ∪ classes the enhancers
|
|
1459
1627
|
query/set), and every stylesheet in `src/components/` must be represented in `COMPONENTS.md`. An
|
|
1460
1628
|
API doc that names a class the CSS never defines is worse than an omission: it invites markup that
|
|
1461
1629
|
silently does nothing.
|
|
1462
|
-
- **`reference
|
|
1630
|
+
- **`reference/`, the two source assets moved out of the repo root** (`git mv`, so history follows):
|
|
1463
1631
|
`Foundation Design System.html` → `reference/foundation-design-system.html`, and
|
|
1464
1632
|
`auth_web_ui_layout_patterns.png` → `reference/layout-patterns.png` (the `auth_` prefix was
|
|
1465
|
-
misleading
|
|
1633
|
+
misleading, since the sheet is about app layout archetypes rather than authentication). Neither was ever in the
|
|
1466
1634
|
tarball and neither is now; the folder name states that. `reference/README.md` carries their
|
|
1467
1635
|
provenance plus a **15 archetypes → Freeday primitives** table that names, per archetype, what the
|
|
1468
1636
|
kit covers and which shapes it has **no** component for (kanban columns, calendar month grid, chat
|
|
1469
|
-
bubbles, canvas)
|
|
1637
|
+
bubbles, canvas), so those get built as layout instead of as invented `fdy-` classes. The drift
|
|
1470
1638
|
guard checks this file too.
|
|
1471
1639
|
### Removed
|
|
1472
1640
|
- **`src/*.js` no longer ships.** All 24 enhancers in `src/` were **byte-identical** to their `dist/`
|
|
1473
|
-
copies, and no `exports` path points into `src
|
|
1641
|
+
copies, and no `exports` path points into `src/`, so a bundler-based consumer could never import
|
|
1474
1642
|
them. `files` now lists `src/base.css` + `src/components` (kept: per-component CSS is genuinely
|
|
1475
1643
|
easier to read than the 117 kB bundle, and `docs/agent-onboarding.md` points agents at it).
|
|
1476
|
-
**−25 files, −196 kB unpacked** per install (the compressed tarball barely moves
|
|
1477
|
-
deflate to almost nothing
|
|
1644
|
+
**−25 files, −196 kB unpacked** per install (the compressed tarball barely moves, since identical files
|
|
1645
|
+
deflate to almost nothing, but the install footprint and "what am I even looking at" do).
|
|
1478
1646
|
- **4 completed SDD implementation plans + 1 superseded spec deleted** (`docs/superpowers/plans/*`,
|
|
1479
1647
|
`specs/2026-07-22-…-polish-design.md`, **−1452 lines**). Cited by nobody, served publicly by Pages,
|
|
1480
|
-
and describing work that shipped months of releases ago
|
|
1648
|
+
and describing work that shipped months of releases ago, when `CHANGELOG.md` plus git history already
|
|
1481
1649
|
record it. Only the canonical design spec remains.
|
|
1482
1650
|
- **`NEXT-UP.md`: 421 → ~125 lines.** Held 15 `Update …` release-log blocks (v1.8.0–v1.18.0), three
|
|
1483
1651
|
2026-07-27 post-mortems of resolved items, and a frozen "current condition" snapshot still claiming
|
|
@@ -1485,15 +1653,15 @@ confirmed gaps, one rejected premise, and one documentation bug of our own that
|
|
|
1485
1653
|
purely forward-looking: the demand-driven backlog, the deliberate YAGNI list, two durable invariants,
|
|
1486
1654
|
and the release runbook.
|
|
1487
1655
|
- **`HANDOFF.md`: 134 → 85 lines.** Its "where we are" section was 90 lines re-narrating every release
|
|
1488
|
-
back to v1.3.1
|
|
1656
|
+
back to v1.3.1, a third copy of the changelog. Now: current state, then a pointer.
|
|
1489
1657
|
- `src/components/.gitkeep`, in a directory holding 47 stylesheets.
|
|
1490
1658
|
### Fixed
|
|
1491
1659
|
- **`CLAUDE.md`'s roadmap said "v0.1 (sekarang, token-first)"** at v1.19.0, and listed datepicker,
|
|
1492
|
-
data grid, filter bar, pagination, states and wizard as *future* work
|
|
1660
|
+
data grid, filter bar, pagination, states and wizard as *future* work, all shipped long ago. It is
|
|
1493
1661
|
the first file every agent session reads, so it was the most expensive stale text in the repo.
|
|
1494
1662
|
Replaced with the actual status (feature-complete, demand-driven) plus a real structure map that
|
|
1495
1663
|
distinguishes authored `src/` from generated `dist/`, and names `freeday.bundle.css` as the file to link.
|
|
1496
|
-
- **The release runbook listed version-reference locations from memory, and was wrong
|
|
1664
|
+
- **The release runbook listed version-reference locations from memory, and was wrong.** It named
|
|
1497
1665
|
`examples/*/README.md` (no version in them) and claimed `docs/getting-started.md` has 4 (it has 1).
|
|
1498
1666
|
Replaced the hand-maintained list with the command that derives it:
|
|
1499
1667
|
`git grep -n '<old-version>' -- . | grep -v CHANGELOG`.
|
|
@@ -1505,9 +1673,9 @@ confirmed gaps, one rejected premise, and one documentation bug of our own that
|
|
|
1505
1673
|
exported it, there is no `data:…;base64` font anywhere in the file, and it renders via the Google
|
|
1506
1674
|
Fonts CDN it preconnects to. Corrected, so no future session goes looking for fonts that aren't there.
|
|
1507
1675
|
- **`docs/getting-started.md` §5 pointed consumers at `Foundation Design System.html` for component
|
|
1508
|
-
markup
|
|
1676
|
+
markup**, a file that is not in the npm package, which became a dangling reference the moment this
|
|
1509
1677
|
release started shipping that doc. Now points at `COMPONENTS.md` and `reference-screen.html`.
|
|
1510
|
-
- **`docs/getting-started.md`'s app-shell snippet was structurally wrong
|
|
1678
|
+
- **`docs/getting-started.md`'s app-shell snippet was structurally wrong**, and it is the first thing
|
|
1511
1679
|
a consumer copies. It showed `__topbar` as a direct child *before* `__sidebar`, put `__brand` inside
|
|
1512
1680
|
the topbar, and nested `__content` *inside* `__main`. `.fdy-app` is a flex **row** of
|
|
1513
1681
|
`[__sidebar | __content]`, `__content` is the column holding `__topbar` + `__main` (it gives the
|
|
@@ -1518,29 +1686,29 @@ confirmed gaps, one rejected premise, and one documentation bug of our own that
|
|
|
1518
1686
|
- Same file: "compose the screen inside `__content`" → **`__main`** (which is the padded area).
|
|
1519
1687
|
### Changed
|
|
1520
1688
|
- **`files` now ships the docs an agent needs**: `COMPONENTS.md`, `docs/getting-started.md`,
|
|
1521
|
-
`docs/integrations.md`, `docs/agent-onboarding.md`, `docs/reference-screen.html
|
|
1689
|
+
`docs/integrations.md`, `docs/agent-onboarding.md`, `docs/reference-screen.html`, listed
|
|
1522
1690
|
**file-by-file, not as `"docs"`**, so the internal planning docs under `docs/superpowers/` stay out
|
|
1523
1691
|
of the tarball. (`files` does not honour `.gitignore`; whitelisting a directory ships whatever sits
|
|
1524
|
-
inside it
|
|
1692
|
+
inside it, the v1.16.0 `bin/obj` lesson.) Tarball 263.6 kB → 297.9 kB, 177 → 182 files.
|
|
1525
1693
|
The `examples/` apps are deliberately **not** shipped (127 MB of `node_modules` + 208 MB of .NET
|
|
1526
1694
|
`bin/obj` live under them); the repo link in `docs/agent-onboarding.md` covers that need.
|
|
1527
1695
|
### Notes
|
|
1528
1696
|
- Documenting the surface surfaced one inconsistency, recorded rather than patched (this release
|
|
1529
|
-
touches no CSS): **`.fdy-pagination` has no CSS rule
|
|
1697
|
+
touches no CSS): **`.fdy-pagination` has no CSS rule.** The block class on the `<nav>` is a naming
|
|
1530
1698
|
hook only, its `__list`/`__link`/`__ellipsis` elements carry all the styling, and the data table
|
|
1531
1699
|
targets `data-fdy-table-pagination`. `COMPONENTS.md` says so, and the drift guard lists it as a
|
|
1532
1700
|
known structural hook.
|
|
1533
1701
|
|
|
1534
|
-
## [1.18.0]
|
|
1702
|
+
## [1.18.0] - 2026-08-11
|
|
1535
1703
|
### Fixed
|
|
1536
|
-
- **`FdyModal` / `FdyDrawer` (Vue) were non-dismissible when `dismissible` was omitted
|
|
1704
|
+
- **`FdyModal` / `FdyDrawer` (Vue) were non-dismissible when `dismissible` was omitted, an
|
|
1537
1705
|
accessibility defect.** Vue's boolean-cast delivers an omitted Boolean prop as `false`, not
|
|
1538
1706
|
`undefined`, so the defensive `props.dismissible !== false` evaluated to `false`: no Escape, no
|
|
1539
|
-
backdrop close, and (modal) no close button rendered
|
|
1707
|
+
backdrop close, and (modal) no close button rendered, the opposite of the documented
|
|
1540
1708
|
`dismissible: true` default. Both now use `withDefaults(…, { dismissible: true })`. React was
|
|
1541
1709
|
unaffected (it leaves omitted props `undefined`); `FdyCombo`/`FdyDatepicker` use `=== true`, which
|
|
1542
1710
|
is correct for an intended-`false` default.
|
|
1543
|
-
- **`.fdy-card--interactive` hover transform now respects reduced motion
|
|
1711
|
+
- **`.fdy-card--interactive` hover transform now respects reduced motion.** The `-3px` lift is dropped
|
|
1544
1712
|
under `prefers-reduced-motion` (the shadow still signals the affordance).
|
|
1545
1713
|
### Added
|
|
1546
1714
|
- **Page-composition primitives + type roles (`src/components/composition.css`).** `.fdy-page`,
|
|
@@ -1548,21 +1716,21 @@ confirmed gaps, one rejected premise, and one documentation bug of our own that
|
|
|
1548
1716
|
deliberately *not* a card, so a metric strip doesn't become an identical-card grid), and three title
|
|
1549
1717
|
roles `.fdy-title-page` / `-section` / `-card` (+ `.fdy-eyebrow`, `.fdy-text-muted`/`-caption`). They
|
|
1550
1718
|
encode *how a page is assembled* so independently-built screens cohere.
|
|
1551
|
-
- **`USAGE.md
|
|
1719
|
+
- **`USAGE.md`, the usage doctrine.** Which token/role/shadow to use when: type roles, spacing rhythm,
|
|
1552
1720
|
elevation, one-primary-per-screen, semantic-vs-categorical colour, density, and the shell-down
|
|
1553
1721
|
composition order. The line where a component library becomes a design system.
|
|
1554
1722
|
- **General categorical palette `--tone-1`…`--tone-8`** (a chart-neutral alias of the validated,
|
|
1555
|
-
theme-aware `--chart-1`…`8`) + **`.fdy-chip--tone-N`** to match `.fdy-avatar--tone-N
|
|
1723
|
+
theme-aware `--chart-1`…`8`) + **`.fdy-chip--tone-N`** to match `.fdy-avatar--tone-N`, for non-semantic
|
|
1556
1724
|
category colours (chips, tags, legends), WCAG AA in light & dark (gated).
|
|
1557
|
-
- **`FdyTable` surfaces its processed rows**
|
|
1558
|
-
`{ rows, total }`, in **both** client and server modes
|
|
1725
|
+
- **`FdyTable` surfaces its processed rows** through a `process` event (Vue) / `onProcess` callback (React),
|
|
1726
|
+
`{ rows, total }`, in **both** client and server modes, so the same filtered/sorted/paged set can
|
|
1559
1727
|
drive a responsive card list, a selection summary, or CSV export without re-deriving the pipeline.
|
|
1560
1728
|
- **`./table-model` export.** The pure `filterRows`/`sortRows`/`paginate`/`cellValue`/… functions
|
|
1561
1729
|
(`adapters/core/table-model.js`) are now reachable as `@cahyo-dimas/freeday/table-model`, so a consumer
|
|
1562
1730
|
can pre-compute exactly what `FdyTable` does instead of re-deriving it and risking drift.
|
|
1563
1731
|
### Changed
|
|
1564
1732
|
- **`data-density="compact"` now steps the mid-range spacing scale** (`--space-3`…`--space-6`) as well as
|
|
1565
|
-
`--control-h`, so Freeday components
|
|
1733
|
+
`--control-h`, so Freeday components, and a utility theme built on `var(--space-N)`, actually densify;
|
|
1566
1734
|
previously it changed only control height.
|
|
1567
1735
|
- **Docs:** getting-started leads with the `.fdy-app` shell + a loud "load the fonts yourself" step (the
|
|
1568
1736
|
package names Sora / IBM Plex Sans / JetBrains Mono but bundles none); README links `USAGE.md`.
|
|
@@ -1571,24 +1739,24 @@ Additive except the two bug fixes (which restore documented behaviour). Gate: `n
|
|
|
1571
1739
|
`typecheck:react` 0 · `test:browser` 6/6 · new CSS browser-verified in real Chrome. From real-app
|
|
1572
1740
|
consumption feedback (three instalments).
|
|
1573
1741
|
|
|
1574
|
-
## [1.17.0]
|
|
1742
|
+
## [1.17.0] - 2026-08-11
|
|
1575
1743
|
### Added
|
|
1576
1744
|
- **Avatar identity tones (`--tone-1`…`--tone-8`).** Decorative tints (from the categorical chart
|
|
1577
|
-
palette) with a theme-aware, text-leaning foreground, so same-initial avatars
|
|
1578
|
-
names share a prefix
|
|
1745
|
+
palette) with a theme-aware, text-leaning foreground, so same-initial avatars, common where many
|
|
1746
|
+
names share a prefix, stay distinguishable. Verified WCAG **AA** (≥4.5:1) in light **and** dark,
|
|
1579
1747
|
guarded by `test/contrast.test.mjs` (which gained a `color-mix()` evaluator). Hash a stable index
|
|
1580
1748
|
off the full name, not the initials.
|
|
1581
1749
|
- **Size-matched skeletons (`.fdy-skeleton--avatar` + `--avatar-sm`/`--avatar-lg`).** Reserve the exact
|
|
1582
1750
|
`.fdy-avatar` box (2 / 2.5 / 3.5rem) so data landing causes no layout shift (a bare `--circle` has no
|
|
1583
1751
|
dimensions of its own).
|
|
1584
|
-
- **`.fdy-card--button`.** When the whole card *is* the control
|
|
1585
|
-
keyboard-reachable and announced
|
|
1752
|
+
- **`.fdy-card--button`.** When the whole card *is* the control, a real `<button>` picker row that is
|
|
1753
|
+
keyboard-reachable and announced, this resets the UA button box **without** stripping the card's own
|
|
1586
1754
|
surface/border.
|
|
1587
1755
|
- **`.fdy-list-reset`.** Opt-in list reset for consumers who pair Freeday with a utility framework run
|
|
1588
|
-
preflight-off (`base.css` is intentionally a *light* reset
|
|
1756
|
+
preflight-off (`base.css` is intentionally a *light* reset that does not strip `ul`/`ol`/`p` margins).
|
|
1589
1757
|
- **Toast `key` + `Freeday.dismiss()`.** `toast({ …, key })` replaces an existing same-key toast in
|
|
1590
1758
|
place instead of stacking a duplicate (a burst of identical failures shows one, refreshed);
|
|
1591
|
-
`Freeday.dismiss(node | key)` closes one early. `toast()` already returned the element
|
|
1759
|
+
`Freeday.dismiss(node | key)` closes one early. `toast()` already returned the element, and both are now
|
|
1592
1760
|
in the `window.Freeday` type declarations.
|
|
1593
1761
|
### Docs
|
|
1594
1762
|
- State the scope boundary (components + tokens, **not** layout; pair with a utility framework run
|
|
@@ -1600,9 +1768,9 @@ consumption feedback (three instalments).
|
|
|
1600
1768
|
Additive; no breaking changes. Gate: `node --test` 22/22 (incl. avatar-tone contrast, both themes) ·
|
|
1601
1769
|
`typecheck:react` 0 · new CSS/JS browser-verified in real Chrome. From real-app consumption feedback.
|
|
1602
1770
|
|
|
1603
|
-
## [1.16.0]
|
|
1771
|
+
## [1.16.0] - 2026-08-11
|
|
1604
1772
|
### Added
|
|
1605
|
-
- **Native Blazor component library (`adapters/blazor/`)
|
|
1773
|
+
- **Native Blazor component library (`adapters/blazor/`), first release, 10/10 parity.** A Razor
|
|
1606
1774
|
Class Library (`Freeday.Blazor`, net8.0) so Blazor consumers get typed `<FdyX>` components with
|
|
1607
1775
|
`@bind`, instead of hand-writing `fdy-*` markup + JS interop. Ten components at parity with the
|
|
1608
1776
|
Vue/React adapters: `FdyModal`, `FdyDrawer`, `FdyCombo<TValue>`, `FdyDatepicker`, `FdyAutocomplete`,
|
|
@@ -1613,39 +1781,39 @@ Additive; no breaking changes. Gate: `node --test` 22/22 (incl. avatar-tone cont
|
|
|
1613
1781
|
go through interop). Shared CRTP base `FreedayComponentBase<TSelf>` types the `DotNetObjectReference`
|
|
1614
1782
|
so `[JSInvokable]` dispatch resolves. Consumed via `<ProjectReference>`; the host loads
|
|
1615
1783
|
`dist/freeday.js` + `adapters/blazor/freeday-blazor.js` as `<script>`s.
|
|
1616
|
-
- **`FdyTable<TRow>`** is a full controlled data table
|
|
1784
|
+
- **`FdyTable<TRow>`** is a full controlled data table: client mode (sort + four column-filter types
|
|
1617
1785
|
+ pagination over `Rows`) and server mode (set `Page` → the headers/filters/pager only raise
|
|
1618
1786
|
`SortChanged`/`FiltersChanged`/`PageChanged` intent), plus row activation, expandable detail rows,
|
|
1619
|
-
cell templates, and loading/empty states. Its sort/filter/paginate logic is `TableModel
|
|
1787
|
+
cell templates, and loading/empty states. Its sort/filter/paginate logic is `TableModel`, a pure
|
|
1620
1788
|
C# port of `adapters/core/table-model.js`.
|
|
1621
1789
|
- **`freeday-blazor.js` bridge**: `chartUpdate` (repaint a chart from its data-* attributes) and a
|
|
1622
1790
|
generic `onOutside` light-dismiss primitive (outside-pointer / Esc).
|
|
1623
1791
|
- **`src/freeday-select.js`** gained an additive `setValue` (silent programmatic select) so a Blazor
|
|
1624
1792
|
`@bind-Value` can push an external combo value without the enhancer echoing an `fdy-change` back.
|
|
1625
1793
|
|
|
1626
|
-
## [1.15.0]
|
|
1794
|
+
## [1.15.0] - 2026-08-10
|
|
1627
1795
|
### Added
|
|
1628
1796
|
- **Type declarations for every export (#40).** Only `./vue` and `./react` shipped a `types`
|
|
1629
|
-
condition; the other eight exports had none. A TypeScript consumer's two side-effect imports
|
|
1630
|
-
`import '@cahyo-dimas/freeday/css'` and `import '@cahyo-dimas/freeday'
|
|
1797
|
+
condition; the other eight exports had none. A TypeScript consumer's two side-effect imports,
|
|
1798
|
+
`import '@cahyo-dimas/freeday/css'` and `import '@cahyo-dimas/freeday'`, plus `./enhancers/*` and
|
|
1631
1799
|
`./breakpoints` therefore failed to resolve (TS 5 leaves them untyped / `implicitly any`; TS 6 makes
|
|
1632
1800
|
it a hard `noUncheckedSideEffectImports` error), and `window.Freeday` was reconstructed by hand in
|
|
1633
1801
|
every consumer. Now every export names a `types` condition: `dist/asset.d.ts` (a one-line stub for the
|
|
1634
1802
|
side-effect-only CSS/enhancer imports), `tokens/breakpoints.d.ts` (the breakpoint scale), and
|
|
1635
|
-
`dist/freeday.d.ts
|
|
1803
|
+
`dist/freeday.d.ts`, which declares the `window.Freeday` global from the source: `toast(opts?)` with
|
|
1636
1804
|
all-optional `variant` / `title` / `message` / `timeout`, returning the toast `HTMLElement` (looser and
|
|
1637
1805
|
more correct than the guesses consumers had been copying). `files` now also ships
|
|
1638
1806
|
`tokens/breakpoints.d.ts`. Runtime unchanged; purely additive.
|
|
1639
1807
|
|
|
1640
|
-
## [1.14.0]
|
|
1808
|
+
## [1.14.0] - 2026-08-01
|
|
1641
1809
|
### Added
|
|
1642
|
-
- **Read-only state across the form controls (#39).** `input` / `textarea` gain a `[readonly]` rule
|
|
1810
|
+
- **Read-only state across the form controls (#39).** `input` / `textarea` gain a `[readonly]` rule,
|
|
1643
1811
|
full-contrast text on a muted surface with a softened border and no focus-ring escalation, distinct
|
|
1644
1812
|
from `:disabled` (which dims the value to placeholder-grey and drops it from tab order). A new
|
|
1645
|
-
`readonly` prop lands on all six controlled select-type adapters
|
|
1813
|
+
`readonly` prop lands on all six controlled select-type adapters: `FdyCombo`, `FdyCascade`,
|
|
1646
1814
|
`FdyDatepicker`, `FdyDateRange`, `FdyAutocomplete`, `FdyCfl` (Vue + React): the control stays focusable
|
|
1647
1815
|
and shows its value with `aria-readonly="true"` but can't be opened or changed (`FdyDatepicker`'s clear
|
|
1648
|
-
button is suppressed too). Read-only ≠ disabled
|
|
1816
|
+
button is suppressed too). Read-only ≠ disabled: it keeps tab order, full contrast, and stays
|
|
1649
1817
|
selectable / copyable / submittable, so a real value the user may not edit reads as data, not an empty
|
|
1650
1818
|
placeholder. Verified in-browser (readonly `opacity:1` vs disabled `0.5`; text at full `--color-text`).
|
|
1651
1819
|
- **Interaction-state coverage completed.** Added the missing `disabled` state to `timepicker`, `rating`
|
|
@@ -1653,28 +1821,28 @@ Additive; no breaking changes. Gate: `node --test` 22/22 (incl. avatar-tone cont
|
|
|
1653
1821
|
(`[aria-disabled]` / `.is-disabled`), and `datepicker`; and the missing `invalid` state to `rating`
|
|
1654
1822
|
(`.fdy-rating--error`). The vanilla datetime composer now reflects `data-fdy-disabled` /
|
|
1655
1823
|
`data-fdy-invalid` from its wrapper onto **both** child pickers so a datetime reads as one control
|
|
1656
|
-
(applied on a macrotask, so it lands after both child triggers are built
|
|
1824
|
+
(applied on a macrotask, so it lands after both child triggers are built; a microtask would run
|
|
1657
1825
|
before the later-registered timepicker enhancer). All additive; no breaking changes.
|
|
1658
1826
|
|
|
1659
|
-
## [1.13.1]
|
|
1827
|
+
## [1.13.1] - 2026-08-01
|
|
1660
1828
|
### Fixed
|
|
1661
|
-
- **`FdyCombo` can be selected with the mouse again (#38).** Clicking an option did nothing
|
|
1829
|
+
- **`FdyCombo` can be selected with the mouse again (#38).** Clicking an option did nothing. The
|
|
1662
1830
|
listbox closed and no `update:modelValue` fired (keyboard select still worked, which is why a
|
|
1663
1831
|
keyboard-only smoke test missed it). The option `<li>` isn't focusable, so a `mousedown` moved focus
|
|
1664
|
-
off the combobox button; the root's `focusout` handler then closed the list
|
|
1832
|
+
off the combobox button; the root's `focusout` handler then closed the list, removing the `<li>`,
|
|
1665
1833
|
*before* `mouseup`, so the browser never generated a `click` and `choose()` was never reached. Added
|
|
1666
1834
|
`@mousedown.prevent` (Vue) / `onMouseDown` `preventDefault` (React) on the option so focus stays on the
|
|
1667
|
-
button and the click lands
|
|
1835
|
+
button and the click lands, the same pattern the sibling `FdyDatepicker` / `FdyAutocomplete` (and the
|
|
1668
1836
|
vanilla `freeday-select.js`, fixed in v1.6.1) already use. Both adapters; no API change; keyboard and
|
|
1669
1837
|
hover paths untouched. Verified with trusted CDP mouse events (a synthetic `dispatchEvent` won't
|
|
1670
|
-
reproduce it
|
|
1838
|
+
reproduce it, since untrusted events run no default action and focus never moves).
|
|
1671
1839
|
|
|
1672
|
-
## [1.13.0]
|
|
1840
|
+
## [1.13.0] - 2026-07-30
|
|
1673
1841
|
### Added
|
|
1674
|
-
- **`FdyDatepicker` clear affordance
|
|
1842
|
+
- **`FdyDatepicker` clear affordance: an optional date can now be unset (#37A).** A new `clearable`
|
|
1675
1843
|
prop renders a small × button in the trigger (overlaid in the calendar-icon slot) whenever a date is
|
|
1676
1844
|
set, emitting `''` (`update:modelValue` + `change` in Vue, `onChange('')` in React) to return to
|
|
1677
|
-
empty
|
|
1845
|
+
empty, closing a functional regression against the native `<input type="date">`, which offered a clear
|
|
1678
1846
|
control. Off by default; both adapters. Verified in-browser: the × is right-aligned in the icon slot,
|
|
1679
1847
|
vertically centred, hides the calendar icon while shown, and never overlaps the value text (even a
|
|
1680
1848
|
long, ellipsised one).
|
|
@@ -1686,81 +1854,81 @@ Additive; no breaking changes. Gate: `node --test` 22/22 (incl. avatar-tone cont
|
|
|
1686
1854
|
- **`FdyDatepicker` default UI strings are now English** (`Select date` placeholder, `Previous month` /
|
|
1687
1855
|
`Next month` nav labels) instead of Indonesian, matching the kit's English-first public docs. Pass
|
|
1688
1856
|
`placeholder` / `prevMonthLabel` / `nextMonthLabel` for other languages. Scope is the Vue + React
|
|
1689
|
-
`FdyDatepicker` adapters only
|
|
1857
|
+
`FdyDatepicker` adapters only; the vanilla `freeday-datepicker.js` enhancer (used by the Indonesian
|
|
1690
1858
|
docs demos) keeps its Indonesian defaults.
|
|
1691
1859
|
|
|
1692
|
-
## [1.12.0]
|
|
1860
|
+
## [1.12.0] - 2026-07-29
|
|
1693
1861
|
### Added
|
|
1694
1862
|
- **Chart colour overrides can now name a categorical palette slot (#36).** `data-fdy-colors` (and the
|
|
1695
1863
|
`<FdyChart colors>` prop) previously resolved every name through the semantic tier
|
|
1696
|
-
(`var(--color-<name>)`), so an override could only reach `primary`/`info`/`accent
|
|
1864
|
+
(`var(--color-<name>)`), so an override could only reach `primary`/`info`/`accent`/…, never the
|
|
1697
1865
|
validated categorical palette `--chart-1..8`. An app that needs stable *per-category* colours (rather
|
|
1698
1866
|
than per-array-index) had to override, and overriding then dropped each series onto the nearest
|
|
1699
|
-
semantic hue
|
|
1867
|
+
semantic hue: a three-way split landing on three neighbouring blues, say, contrast-passing but
|
|
1700
1868
|
unreadable as a comparison. `colorVar` now maps a name of the form `chart-1`..`chart-8` to
|
|
1701
1869
|
`var(--chart-N)`, so an override can pin a series to the validated palette
|
|
1702
|
-
(`colors={['chart-1','chart-2','chart-3']}`). Backward compatible
|
|
1870
|
+
(`colors={['chart-1','chart-2','chart-3']}`). Backward compatible, since no semantic token name starts
|
|
1703
1871
|
with `chart-`, so semantic overrides are unchanged; the single-series `data-fdy-color` / `color`
|
|
1704
1872
|
accepts the `chart-N` form too. Verified in-browser: explicit `chart-1..3` now render the exact
|
|
1705
1873
|
fixed-order palette (blue/orange/green) and equal the default, while `success,warning,danger` still
|
|
1706
1874
|
resolve to their semantic tokens. (`--chart-1..8` are public tokens in `dist/freeday.tokens.css`, so
|
|
1707
|
-
matching swatches/bars elsewhere can reference `var(--chart-N)` directly
|
|
1875
|
+
matching swatches/bars elsewhere can reference `var(--chart-N)` directly, with no adapter export needed.)
|
|
1708
1876
|
|
|
1709
|
-
## [1.11.2]
|
|
1877
|
+
## [1.11.2] - 2026-07-28
|
|
1710
1878
|
### Fixed
|
|
1711
1879
|
- **A filter bar docked in a table toolbar no longer loses its alignment (#35).** `.fdy-filterbar`
|
|
1712
1880
|
(`align-items:flex-end`, for labelled fields) and `.fdy-table-toolbar` (`align-items:center`, for
|
|
1713
1881
|
bare buttons) are both single-class rules of equal specificity, so composing them on one element
|
|
1714
|
-
(`<div class="fdy-table-toolbar fdy-filterbar">`) let whichever came later in the bundle win
|
|
1715
|
-
toolbar
|
|
1882
|
+
(`<div class="fdy-table-toolbar fdy-filterbar">`) let whichever came later in the bundle win, and the
|
|
1883
|
+
toolbar won, centring every control and floating the trailing actions ~13px above the inputs beside
|
|
1716
1884
|
them. Added a `.fdy-table-toolbar.fdy-filterbar` rule (specificity 0,2,0) that settles it toward the
|
|
1717
|
-
filterbar's `flex-end`. Additive
|
|
1885
|
+
filterbar's `flex-end`. Additive, so it only affects elements carrying both classes; standalone toolbars
|
|
1718
1886
|
and filterbars are untouched. Verified in-browser: the composed bar's controls now share one
|
|
1719
1887
|
baseline (computed `align-items: flex-end`).
|
|
1720
1888
|
|
|
1721
|
-
## [1.11.1]
|
|
1889
|
+
## [1.11.1] - 2026-07-28
|
|
1722
1890
|
### Fixed
|
|
1723
1891
|
- **Tall modals now scroll their body instead of clipping the footer (#34).** `.fdy-modal` had a
|
|
1724
1892
|
`max-height` + `overflow:hidden` but never established a flex context, so its three children laid out
|
|
1725
1893
|
as blocks: the body's height was its own content height (nothing for `overflow:auto` to scroll), and
|
|
1726
|
-
the dialog's cap clipped whatever stuck out
|
|
1894
|
+
the dialog's cap clipped whatever stuck out, including `.fdy-modal__footer` and its submit button, on
|
|
1727
1895
|
a short viewport (a form you could fill but not save). Gave the modal the column-flex treatment the
|
|
1728
1896
|
sibling `.fdy-drawer` already uses: `display:flex` on `.fdy-modal[open]`, `flex-direction:column` on
|
|
1729
1897
|
the base, `flex:none` on header/footer, and `flex:1;min-height:0` on the body. Modals that already fit
|
|
1730
1898
|
are unchanged. Verified in-browser (body scrolls, footer stays inside) including the `.fdy-modal--cfl`
|
|
1731
|
-
picker
|
|
1899
|
+
picker, so no regression.
|
|
1732
1900
|
|
|
1733
|
-
## [1.11.0]
|
|
1901
|
+
## [1.11.0] - 2026-07-28
|
|
1734
1902
|
Follow-ups from the doc-ai-automation migration (improvement notes #31–#33). Additive / corrective.
|
|
1735
1903
|
|
|
1736
1904
|
### Fixed
|
|
1737
1905
|
- **Badges no longer wrap and break the pill (#31).** `.fdy-badge` had `line-height:1` but no
|
|
1738
1906
|
`white-space`, so a two-word label (e.g. "Needs review") wrapped and collided with the pill padding,
|
|
1739
|
-
turning the capsule into a cramped lozenge. Added `white-space:nowrap
|
|
1907
|
+
turning the capsule into a cramped lozenge. Added `white-space:nowrap`, matching the sibling
|
|
1740
1908
|
`.fdy-badge-ov`, which already had it. A too-narrow badge now overflows its column (which
|
|
1741
1909
|
`.fdy-table-scroll` already handles) instead of wrapping.
|
|
1742
1910
|
- **Donut chart honours `legend="none"` (#33).** `freeday-chart.js` read `data-fdy-legend` only in the
|
|
1743
1911
|
cartesian renderer; the donut appended its legend unconditionally, so `<FdyChart type="donut"
|
|
1744
1912
|
legend="none">` still printed the built-in legend even when the caller supplied its own. Gated the
|
|
1745
|
-
donut legend on the same attribute (`auto` still shows it
|
|
1913
|
+
donut legend on the same attribute (`auto` still shows it, since a donut is unreadable without labels).
|
|
1746
1914
|
|
|
1747
1915
|
### Added
|
|
1748
1916
|
- **`.fdy-filterbar--actions-inline` modifier (#32).** Opt-in: sits the trailing actions inline after
|
|
1749
1917
|
the last field instead of at the far edge, so a wrapped bar reads as attached to its filters. Also
|
|
1750
1918
|
**documented** the filterbar's wrap behaviour: a wrapped bar breaks flex lines on each item's
|
|
1751
|
-
*flex-basis* (the grow field's `flex:1 1 16rem` reserves 16rem), not its rendered width
|
|
1919
|
+
*flex-basis* (the grow field's `flex:1 1 16rem` reserves 16rem), not its rendered width, so the
|
|
1752
1920
|
grow basis decides the wrap point; to avoid stranded actions, tune the `--w-grow` basis or drop a
|
|
1753
1921
|
field. (The note's margin-swap proposal to un-strand the actions was **tested in-browser and does not
|
|
1754
|
-
work
|
|
1922
|
+
work**, because margins don't change flex line-breaking, so only the modifier + docs were taken.)
|
|
1755
1923
|
|
|
1756
|
-
## [1.10.0]
|
|
1924
|
+
## [1.10.0] - 2026-07-28
|
|
1757
1925
|
Follow-ups from the doc-ai-automation migration (improvement notes #27–#30). Additive / corrective;
|
|
1758
1926
|
no breaking changes.
|
|
1759
1927
|
|
|
1760
1928
|
### Fixed
|
|
1761
1929
|
- **`<fieldset class="fdy-field">` no longer leaks native browser chrome (#27).** The kit shipped no
|
|
1762
1930
|
`fieldset` reset, so a grouped field (a radio/checkbox group with a `<legend>`) showed the UA groove
|
|
1763
|
-
border, extra padding, and `min-inline-size:min-content
|
|
1931
|
+
border, extra padding, and `min-inline-size:min-content`, the last of which also blocked the
|
|
1764
1932
|
filterbar column widths from shrinking it. Added a scoped `fieldset.fdy-field` reset so it lays out
|
|
1765
1933
|
identically to the `<div>` form.
|
|
1766
1934
|
- **Chart x-axis: the last label no longer collides with its neighbour (#29).** `freeday-chart.js`
|
|
@@ -1779,167 +1947,167 @@ no breaking changes.
|
|
|
1779
1947
|
it need not burn the single `--w-grow` slot. The date-range pickers also gained a `min-width` (7rem)
|
|
1780
1948
|
so a squeezed range compresses and wraps instead of clipping the date (was `min-width:0`).
|
|
1781
1949
|
|
|
1782
|
-
## [1.9.0]
|
|
1783
|
-
Follow-ups to 1.8.0's `FdyTable` (improvement notes #25/#26). Additive
|
|
1950
|
+
## [1.9.0] - 2026-07-28
|
|
1951
|
+
Follow-ups to 1.8.0's `FdyTable` (improvement notes #25/#26). Additive, since the #25 change **widens** a
|
|
1784
1952
|
generic bound, so every call site that compiled before still compiles.
|
|
1785
1953
|
|
|
1786
1954
|
### Fixed
|
|
1787
1955
|
- **`FdyTable` now accepts `interface`-typed rows.** The row generic was constrained to
|
|
1788
1956
|
`Record<string, unknown>`, to which a TypeScript `interface` is never assignable (interfaces get no
|
|
1789
|
-
implicit index signature)
|
|
1957
|
+
implicit index signature), so a normally-typed DTO array failed to compile and `FdyTable` was
|
|
1790
1958
|
effectively unusable in a strict-TS app. Widened the bound to `object` in both adapters; the row type
|
|
1791
1959
|
now **infers** correctly (`cell-*` slots / `renderCell` are properly typed) and no cast is pushed
|
|
1792
|
-
onto consumers. The component never indexes rows directly
|
|
1960
|
+
onto consumers. The component never indexes rows directly; all access goes through the
|
|
1793
1961
|
already-unconstrained core, which is unchanged.
|
|
1794
1962
|
|
|
1795
1963
|
### Added
|
|
1796
1964
|
- **`FdyTable` row activation (opt-in).** `rowActivatable` makes rows focusable and emits
|
|
1797
1965
|
`row-activate` (Vue) / calls `onRowActivate` (React) on click, or Enter/Space while the row itself is
|
|
1798
|
-
focused
|
|
1966
|
+
focused, and a control inside a cell keeps its own event (the `target !== currentTarget` guard). Adds a
|
|
1799
1967
|
`rowClass(row)` hook for per-row state (e.g. a selected row) and a `.fdy-table__row--activatable`
|
|
1800
1968
|
class (pointer + `:focus-visible` ring; hover tint is already inherited from the base row rule).
|
|
1801
1969
|
Replaces the hand-rolled `<tr tabindex="0" @click @keydown>` + duplicated row CSS consumers were
|
|
1802
1970
|
each re-writing.
|
|
1803
1971
|
|
|
1804
|
-
## [1.8.0]
|
|
1805
|
-
Release **1.8
|
|
1972
|
+
## [1.8.0] - 2026-07-28
|
|
1973
|
+
Release **1.8: framework-safe data table, modal/drawer wrappers, and a monospace utility**.
|
|
1806
1974
|
Non-breaking, purely additive. No existing selector, markup, or enhancer changed.
|
|
1807
1975
|
|
|
1808
1976
|
### Added
|
|
1809
|
-
- **`FdyTable` (Vue + React)
|
|
1810
|
-
every render, so it is safe over a `v-for` / `.map()` bound to reactive data
|
|
1977
|
+
- **`FdyTable` (Vue + React), a controlled data table.** Reads `rows` as the source of truth on
|
|
1978
|
+
every render, so it is safe over a `v-for` / `.map()` bound to reactive data, unlike the
|
|
1811
1979
|
`freeday-table.js` enhancer, which snapshots the DOM and corrupts a framework list on the first
|
|
1812
1980
|
refetch. Two modes: **client** (component sorts/filters, and paginates when `pageSize` is set,
|
|
1813
1981
|
over the full `rows`) and **server** (`page` prop present → `rows` render as given and the
|
|
1814
1982
|
headers/filters/pager only emit `update:sort` / `update:filters` / `update:page`). Type-aware
|
|
1815
1983
|
column filters (**text / enum / number / date**) in a top-layer popover, sort toggles, and a
|
|
1816
|
-
page-window pager
|
|
1984
|
+
page-window pager, all over the existing `.fdy-table*` / `.fdy-filter*` / `.fdy-pagination__*`
|
|
1817
1985
|
classes. Column options: `sortable`, `filter`, `align`, `mono`, `sortType`, `value`, `options`.
|
|
1818
1986
|
Sort/filter/paginate logic lives in a shared, framework-agnostic core (`adapters/core/table-model.js`)
|
|
1819
1987
|
covered by `node --test` (`test/table-model.test.mjs`).
|
|
1820
|
-
- **`FdyModal` + `FdyDrawer` (Vue + React)
|
|
1988
|
+
- **`FdyModal` + `FdyDrawer` (Vue + React), controlled `<dialog>` wrappers.** Reconcile a reactive
|
|
1821
1989
|
`open` boolean with the native `<dialog>`'s method-driven open/close: guarded
|
|
1822
1990
|
`showModal()`/`close()`, `@cancel`/`onCancel` + `preventDefault` so Esc routes through app state,
|
|
1823
1991
|
and backdrop-click via `event.target === dialogEl`. `open` + `close` event/`onClose`, `title`,
|
|
1824
1992
|
`size`/`side`, `dismissible` (default true), and `footer`. No new modal CSS.
|
|
1825
1993
|
- **`.fdy-mono` utility.** Alignment-neutral monospace (data font + tabular figures) for identifier /
|
|
1826
|
-
code / IP / timestamp cells or inline spans
|
|
1994
|
+
code / IP / timestamp cells or inline spans, the piece that previously only existed welded into
|
|
1827
1995
|
the right-aligned `.fdy-table__num`. `FdyTable` applies it for any `mono: true` column.
|
|
1828
|
-
- **`.fdy-drawer__footer`**
|
|
1996
|
+
- **`.fdy-drawer__footer`** is a bottom action bar for drawers (mirrors `.fdy-modal__footer`).
|
|
1829
1997
|
|
|
1830
|
-
## [1.7.1]
|
|
1831
|
-
Docs & distribution patch. **No component/code changes
|
|
1998
|
+
## [1.7.1] - 2026-07-27
|
|
1999
|
+
Docs & distribution patch. **No component/code changes**: `dist/`, `src/`, `adapters/`, and
|
|
1832
2000
|
`tokens/` are byte-identical to 1.7.0.
|
|
1833
2001
|
|
|
1834
2002
|
### Changed
|
|
1835
2003
|
- **Distributed on public npm as `@cahyo-dimas/freeday`.** Install with `npm i @cahyo-dimas/freeday`
|
|
1836
|
-
|
|
2004
|
+
`npm ci` now works in CI without auth or an SSH key (the old `git+https` workaround is gone).
|
|
1837
2005
|
Releases publish via GitHub Actions **OIDC Trusted Publishing** (no tokens).
|
|
1838
|
-
- **Docs are now English-first
|
|
2006
|
+
- **Docs are now English-first**: `README.md`, `docs/getting-started.md`, `docs/integrations.md`,
|
|
1839
2007
|
and the example READMEs. `docs/index.html` stays Indonesian for now.
|
|
1840
2008
|
|
|
1841
2009
|
### Added
|
|
1842
2010
|
- **`README.id.md`** (Bahasa Indonesia) with a language-toggle row on both READMEs; now shipped in
|
|
1843
2011
|
the published package.
|
|
1844
2012
|
|
|
1845
|
-
## [1.7.0]
|
|
1846
|
-
Rilis **1.7
|
|
2013
|
+
## [1.7.0] - 2026-07-24
|
|
2014
|
+
Rilis **1.7: tree checkbox, form-grid, & tiga section docs full-width**. Non-breaking, aditif.
|
|
1847
2015
|
|
|
1848
2016
|
### Added
|
|
1849
|
-
- **Tree checkbox (cascading)
|
|
2017
|
+
- **Tree checkbox (cascading): `freeday-tree.js` + `.fdy-tree--checkbox`.** Varian tree yang
|
|
1850
2018
|
bisa dipilih: centang cabang → semua anak ikut; sebagian anak → cabang jadi native
|
|
1851
2019
|
`:indeterminate`. Checkbox = `<input class="fdy-checkbox">` native (Space toggle, nama lewat
|
|
1852
2020
|
`aria-label`); klik checkbox `stopPropagation` sehingga memilih **tak pernah** membuka/menutup
|
|
1853
2021
|
cabang, sedangkan teks summary tetap toggle `<details>`. Opt-in via `data-fdy-tree`; init
|
|
1854
2022
|
post-order menghitung state cabang dari leaf yang sudah tercentang saat load. Zero-dependency,
|
|
1855
2023
|
`window.FreedayTree`. Tree dasar tetap tanpa JS.
|
|
1856
|
-
- **`.fdy-form-grid
|
|
2024
|
+
- **`.fdy-form-grid`, layout header/dokumen.** Pelengkap dua-dimensi dari stack `.fdy-field`
|
|
1857
2025
|
satu kolom dan baris `.fdy-filterbar`: field tersusun di kolom yang rapi (`auto-fit`, reflow ke
|
|
1858
|
-
satu kolom di layar sempit); `.fdy-field--full` membentang penuh (mis. alamat). Zero JS
|
|
2026
|
+
satu kolom di layar sempit); `.fdy-field--full` membentang penuh (mis. alamat). Zero JS, murni
|
|
1859
2027
|
layout di atas `.fdy-field`.
|
|
1860
|
-
- **File upload
|
|
2028
|
+
- **File upload, layout melebar.** Modifier `.fdy-dropzone--row` (ikon di samping teks, bukan
|
|
1861
2029
|
bertumpuk) dan `.fdy-filelist--grid` (baris file berdampingan di layar lebar).
|
|
1862
2030
|
|
|
1863
2031
|
### Changed
|
|
1864
|
-
- **Docs
|
|
2032
|
+
- **Docs: tiga section jadi full-width & selaras.** Tree (kini menampilkan varian dasar +
|
|
1865
2033
|
checkbox berdampingan), Form (validasi inline + contoh **header + detail**: `.fdy-form-grid` +
|
|
1866
2034
|
tabel item), dan File upload semuanya melebar mengisi kolom konten agar lebar semua section rata.
|
|
1867
2035
|
Terverifikasi lewat gestur mouse asli (headless CDP `Input.dispatchMouseEvent`): cascade
|
|
1868
2036
|
centang/indeterminate, checkbox tak melipat cabang, teks summary tetap toggle.
|
|
1869
2037
|
|
|
1870
|
-
## [1.6.2]
|
|
1871
|
-
Rilis **patch
|
|
2038
|
+
## [1.6.2] - 2026-07-24
|
|
2039
|
+
Rilis **patch untuk lisensi**. Tak ada perubahan kode.
|
|
1872
2040
|
|
|
1873
2041
|
### Changed
|
|
1874
2042
|
- **Lisensi jadi [MIT](LICENSE)** (`Copyright (c) 2026 Cahyo D. Kurnianto`). Sebelumnya
|
|
1875
|
-
`"license": "UNLICENSED"` tanpa file `LICENSE
|
|
2043
|
+
`"license": "UNLICENSED"` tanpa file `LICENSE`, dan di repo publik itu berarti *all rights
|
|
1876
2044
|
reserved*, jadi tak ada yang boleh memakainya secara legal. Sekarang bebas dipakai/ubah/
|
|
1877
2045
|
distribusi asal menyertakan copyright. `package.json`/`package-lock.json` → `"MIT"`, tambah
|
|
1878
2046
|
file `LICENSE`, catat di README + footer docs. `"private": true` dipertahankan (pengaman
|
|
1879
2047
|
anti-`npm publish`, tak terkait lisensi).
|
|
1880
2048
|
|
|
1881
|
-
## [1.6.1]
|
|
1882
|
-
Rilis **patch
|
|
2049
|
+
## [1.6.1] - 2026-07-24
|
|
2050
|
+
Rilis **patch: perbaikan bug pemilihan combo dengan mouse**. Non-breaking.
|
|
1883
2051
|
|
|
1884
2052
|
### Fixed
|
|
1885
|
-
- **`freeday-select.js` (combo/select vanilla)
|
|
2053
|
+
- **`freeday-select.js` (combo/select vanilla): memilih opsi dengan mouse tidak berfungsi.**
|
|
1886
2054
|
Menekan (mousedown) sebuah opsi mem-blur tombol combo; handler `focusout` lalu memanggil
|
|
1887
2055
|
`close()` yang menyembunyikan listbox **sebelum** event `click` opsi sempat menjalankan
|
|
1888
|
-
`choose()
|
|
2056
|
+
`choose()`, sehingga pilihan hilang dan nilai tak berubah (hanya terlihat dengan mouse asli;
|
|
1889
2057
|
navigasi keyboard tak terpengaruh). Perbaikan: `preventDefault` pada `mousedown` listbox agar
|
|
1890
2058
|
tombol tetap fokus, jadi `click` mendarat normal. Terverifikasi lewat gestur mouse asli
|
|
1891
2059
|
(headless CDP `Input.dispatchMouseEvent`) pada playground docs dan section Select. Memengaruhi
|
|
1892
2060
|
konsumen vanilla `freeday/js`; adapter Vue/React punya implementasi sendiri dan tak terdampak.
|
|
1893
2061
|
|
|
1894
|
-
## [1.6.0]
|
|
1895
|
-
Rilis **1.6
|
|
2062
|
+
## [1.6.0] - 2026-07-24
|
|
2063
|
+
Rilis **1.6: wrapper input ekstra (Vue + React) + filter-bar**. Non-breaking, aditif.
|
|
1896
2064
|
|
|
1897
2065
|
### Added
|
|
1898
|
-
- **`FdyDateRange` (Vue + React)**
|
|
2066
|
+
- **`FdyDateRange` (Vue + React)** adalah rentang tanggal terkontrol `{start, end}` yang menyusun dua
|
|
1899
2067
|
`FdyDatepicker` di atas layout `.fdy-daterange` yang sudah ada (akhir tak bisa mendahului awal,
|
|
1900
2068
|
lewat min/max saling terkait). `v-model` di Vue, `value`/`onChange` di React. Tanpa CSS baru.
|
|
1901
|
-
- **`FdyAutocomplete` (Vue + React)**
|
|
2069
|
+
- **`FdyAutocomplete` (Vue + React)** adalah editable combobox WAI-ARIA APG terkontrol, port dari
|
|
1902
2070
|
enhancer `freeday-autocomplete.js` di atas CSS `.fdy-autocomplete` yang sama: filter saat
|
|
1903
2071
|
mengetik (substring case-insensitive pada query ter-trim), ↑/↓ dengan wrap, Enter commit,
|
|
1904
2072
|
Esc/Tab/klik-luar menutup, `aria-activedescendant` + empty state, dropdown top-layer via
|
|
1905
|
-
Popover API. `onSelect`/`select` hanya saat commit
|
|
2073
|
+
Popover API. `onSelect`/`select` hanya saat commit, terpisah dari perubahan ketikan, jadi
|
|
1906
2074
|
options yang sudah difilter server bisa dilempar masuk apa adanya.
|
|
1907
|
-
- **`FdyCascade` (Vue + React)**
|
|
2075
|
+
- **`FdyCascade` (Vue + React)** adalah picker hierarkis drill-down terkontrol, port dari enhancer
|
|
1908
2076
|
`freeday-cascade.js` di atas CSS `.fdy-cascade` yang sama: satu level sekaligus, branch masuk,
|
|
1909
2077
|
kontrol back naik, leaf memilih (value = value leaf, tampilan = path penuh), buka ulang di level
|
|
1910
2078
|
terpilih. Keyboard: Up/Down/Home/End, ArrowRight/Enter/Space aktifkan, ArrowLeft/Backspace naik,
|
|
1911
2079
|
Esc tutup; `aria-activedescendant` + crumb live. Model data = **pohon `CascadeNode` bertipe**,
|
|
1912
2080
|
menggantikan `<ul>` bersarang tersembunyi milik enhancer.
|
|
1913
|
-
- **`.fdy-filterbar`**
|
|
2081
|
+
- **`.fdy-filterbar`** adalah layout primitive untuk baris filter yang konsisten, di atas `.fdy-field`:
|
|
1914
2082
|
ritme lebar kolom (`--w-sm` / default / `--w-lg` / `--w-xl`), satu field `--w-grow` menyerap sisa
|
|
1915
2083
|
ruang, `__actions` menempel di ujung sejajar kontrol, wrap rapi di layar sempit. Nol JS.
|
|
1916
2084
|
Menutup gap "filter row ragged" dari adoption backlog #11.
|
|
1917
2085
|
|
|
1918
2086
|
### Changed
|
|
1919
|
-
- Contoh `react-faktur` kini menggerakkan **Kategori** lewat `FdyCascade
|
|
2087
|
+
- Contoh `react-faktur` kini menggerakkan **Kategori** lewat `FdyCascade`, menghapus jembatan
|
|
1920
2088
|
event `fdy-cascade-change` manual; ditambah field Periode (`FdyDateRange`) & Kota (`FdyAutocomplete`).
|
|
1921
2089
|
- Contoh `vue-faktur` mendapat demo Vue-native untuk ketiga komponen baru.
|
|
1922
2090
|
|
|
1923
|
-
Dengan rilis ini adapter **Vue dan React simetris penuh
|
|
2091
|
+
Dengan rilis ini adapter **Vue dan React simetris penuh**: tujuh komponen controlled typed yang
|
|
1924
2092
|
sama di kedua sisi: `FdyCombo` · `FdyDatepicker` · `FdyDateRange` · `FdyAutocomplete` ·
|
|
1925
2093
|
`FdyCascade` · `FdyCfl` · `FdyChart`.
|
|
1926
2094
|
|
|
1927
|
-
## [1.5.0]
|
|
1928
|
-
Rilis **1.5
|
|
2095
|
+
## [1.5.0] - 2026-07-24
|
|
2096
|
+
Rilis **1.5: React adapter parity**. Non-breaking, aditif.
|
|
1929
2097
|
### Added
|
|
1930
|
-
- **React adapter parity
|
|
2098
|
+
- **React adapter parity**: komponen controlled typed `FdyCombo` / `FdyDatepicker` / `FdyCfl` /
|
|
1931
2099
|
`FdyChart` + `usePopover`, di atas CSS kit yang sama (aksesibilitas WAI-ARIA APG, dropdown
|
|
1932
2100
|
top-layer lewat Popover API). Aplikasi React tak lagi butuh fallback `<select>`/`<input
|
|
1933
2101
|
type="date">` native. Dikonsumsi lewat `freeday/react`; Vite men-transpile source component
|
|
1934
2102
|
langsung tanpa config tambahan, konsumen Next.js mungkin butuh
|
|
1935
2103
|
`transpilePackages: ['freeday']`.
|
|
1936
2104
|
|
|
1937
|
-
## [1.4.1]
|
|
1938
|
-
Rilis **1.4.1
|
|
2105
|
+
## [1.4.1] - 2026-07-23
|
|
2106
|
+
Rilis **patch 1.4.1**. Dropdown tak lagi ter-clip di dalam card / scroll container.
|
|
1939
2107
|
### Fixed
|
|
1940
2108
|
- **Dropdown lepas dari clipping ancestor** (`.fdy-card{overflow:hidden}`, scroll container, atau
|
|
1941
|
-
ancestor ber-`transform`). Semua dropdown `position:absolute`
|
|
1942
|
-
cascade, autocomplete, timepicker, menu**
|
|
2109
|
+
ancestor ber-`transform`). Semua dropdown `position:absolute` (**combo/select, datepicker,
|
|
2110
|
+
cascade, autocomplete, timepicker, menu**) kini di-render di **top layer** lewat native
|
|
1943
2111
|
**Popover API** (`popover="manual"`) dan diposisikan `fixed` ke trigger (flip ke atas bila
|
|
1944
2112
|
sempit di bawah, lebar mengikuti trigger, reposisi saat scroll/resize). Panel tetap DOM child
|
|
1945
2113
|
komponennya, jadi focus, outside-click, dan ARIA tak berubah. Berlaku untuk enhancer vanilla
|
|
@@ -1950,32 +2118,32 @@ Rilis **1.4.1 — patch**. Dropdown tak lagi ter-clip di dalam card / scroll con
|
|
|
1950
2118
|
`freeday/vue` (internal, tak diekspor). Panel dropdown kini set `color:var(--color-text)` eksplisit
|
|
1951
2119
|
(UA `[popover]` default-nya `CanvasText`).
|
|
1952
2120
|
|
|
1953
|
-
## [1.4.0]
|
|
1954
|
-
Rilis **1.4
|
|
2121
|
+
## [1.4.0] - 2026-07-23
|
|
2122
|
+
Rilis **1.4: motion & native charts**. Dua fitur besar (gerak enter/exit lintas komponen +
|
|
1955
2123
|
chart native yang cukup untuk mem-pensiun-kan Chart.js) plus satu fix layout. Non-breaking.
|
|
1956
2124
|
|
|
1957
2125
|
### Added
|
|
1958
|
-
- **Chart native
|
|
2126
|
+
- **Chart native, parity untuk drop Chart.js.** Tipe baru **`line`** & **`area`** dan **`bar`
|
|
1959
2127
|
multi-seri + `data-fdy-stacked`**, semuanya dengan **sumbu ber-tema** (y-gridline + tick,
|
|
1960
2128
|
x-label autoskip, baseline nol) dari `--chart-grid`/`--chart-tick`. Data multi-seri lewat
|
|
1961
2129
|
**`data-series`** (JSON `[{label,values}]`); `data-values` tetap jadi shortcut seri-tunggal.
|
|
1962
2130
|
**`data-fdy-format="number|percent|currency"`** memformat tick + tooltip; **legenda otomatis**
|
|
1963
2131
|
untuk ≥2 seri (`data-fdy-legend="auto|always|none"`); hover band per-kategori. Warna dari token
|
|
1964
2132
|
→ **chart re-warna otomatis saat `data-theme` berubah** (tak perlu observer/re-paint kanvas).
|
|
1965
|
-
- **`FreedayChart.update(el)`**
|
|
1966
|
-
- **`freeday/vue` → `<FdyChart>`**
|
|
2133
|
+
- **`FreedayChart.update(el)`** me-render ulang secara idempoten saat data berubah (auto-init tetap sekali).
|
|
2134
|
+
- **`freeday/vue` → `<FdyChart>`** adalah wrapper reaktif & typed di atas renderer (re-render on prop
|
|
1967
2135
|
change): `type` · `series`/`values` · `labels` · `format` · `stacked` · `legend` · `colors`/`color`
|
|
1968
2136
|
· `center`; `aria-label` fallthrough. Tipe `FdyChartSeries` diekspor. Diverifikasi `vue-tsc` + `vite build`.
|
|
1969
2137
|
|
|
1970
2138
|
### Changed
|
|
1971
|
-
- **Motion pass
|
|
2139
|
+
- **Motion pass: gerak yang bermakna, semua hormati `prefers-reduced-motion`.**
|
|
1972
2140
|
- **Drawer & Modal** kini beranimasi **masuk _dan_ keluar** (slide / fade-scale + backdrop blur)
|
|
1973
|
-
via `@starting-style` + `allow-discrete` pada `<dialog>` native
|
|
1974
|
-
- **Sidebar app-shell** collapse/expand desktop kini **meluncur** (animasi `width`)
|
|
2141
|
+
via `@starting-style` + `allow-discrete` pada `<dialog>` native; sebelumnya hanya masuk.
|
|
2142
|
+
- **Sidebar app-shell** collapse/expand desktop kini **meluncur** (animasi `width`); sebelumnya
|
|
1975
2143
|
`display:none` (snap). Mobile (off-canvas) tak berubah.
|
|
1976
2144
|
- **Accordion** & **Tabs** me-reveal konten (fade + rise) saat dibuka/diganti.
|
|
1977
2145
|
- **Baris tabel** transisi `background` halus saat hover.
|
|
1978
|
-
- **`.fdy-daterange` responsif
|
|
2146
|
+
- **`.fdy-daterange` responsif**: dua picker kini boleh menyusut (`min-width:0`) & wrap, jadi tak
|
|
1979
2147
|
lagi overflow di row/HP < ~23rem; di lebar cukup tetap hug ~23rem seperti sebelumnya.
|
|
1980
2148
|
|
|
1981
2149
|
### Migrasi konsumen (opsional, aman)
|
|
@@ -1983,10 +2151,10 @@ chart native yang cukup untuk mem-pensiun-kan Chart.js) plus satu fix layout. No
|
|
|
1983
2151
|
2. **Motion & daterange:** otomatis, tanpa perubahan kode.
|
|
1984
2152
|
3. **Drop Chart.js (opsional):** ganti wrapper chart lokal dengan `<FdyChart type="line" :series>`
|
|
1985
2153
|
(atau bentuk `data-fdy-chart`), hapus folder wrapper chart lokal, dan buang `chart.js` dari
|
|
1986
|
-
`package.json`. Machinery tema chart (probe warna + observer) tak lagi perlu
|
|
2154
|
+
`package.json`. Machinery tema chart (probe warna + observer) tak lagi perlu, karena token yang urus.
|
|
1987
2155
|
|
|
1988
|
-
## [1.3.1]
|
|
1989
|
-
Rilis **1.3.1
|
|
2156
|
+
## [1.3.1] - 2026-07-23
|
|
2157
|
+
Rilis **patch 1.3.1**. Buang rail aksen "eyebrow" di state aktif/terpilih.
|
|
1990
2158
|
### Removed
|
|
1991
2159
|
- **Rail aksen 3px pada state aktif/terpilih** (`box-shadow:inset 3px 0 0 var(--color-primary)`)
|
|
1992
2160
|
dihapus dari nav app-shell (`[aria-current="page"]`), opsi cascade & autocomplete
|
|
@@ -1995,27 +2163,27 @@ Rilis **1.3.1 — patch**. Buang rail aksen "eyebrow" di state aktif/terpilih.
|
|
|
1995
2163
|
soft-bg satu baris penuh). Murni visual, non-breaking; kontras tetap **9/9**.
|
|
1996
2164
|
### Kept
|
|
1997
2165
|
- Indikator fungsional yang **beda motif** sengaja dipertahankan: rail fokus keyboard CFL
|
|
1998
|
-
(`:focus-visible`, wajib a11y
|
|
2166
|
+
(`:focus-visible`, wajib a11y sebagai pembeda fokus vs terpilih), underline tab aktif, checkmark
|
|
1999
2167
|
combo, ring highlight combo.
|
|
2000
2168
|
|
|
2001
|
-
## [1.3.0]
|
|
2002
|
-
Rilis **1.3
|
|
2169
|
+
## [1.3.0] - 2026-07-23
|
|
2170
|
+
Rilis **1.3: accent Azure**. Warna sekunder/aksen berubah dari **teal → biru Microsoft Azure**.
|
|
2003
2171
|
### Changed
|
|
2004
|
-
- **Accent: teal → sky / Azure blue
|
|
2172
|
+
- **Accent: teal → sky / Azure blue.** `--color-accent` kini `#0078d4` (light) / `#47a1e6` (dark),
|
|
2005
2173
|
menggantikan teal `#0d9488`. Ramp primitif `teal` diganti `sky` (biru Azure muda);
|
|
2006
2174
|
`--color-accent-hover` & `--color-on-accent` ikut disesuaikan (on-accent dark `#06233f` agar
|
|
2007
2175
|
kontras di atas accent biru-muda dark). Semua konsumen `--color-accent` ikut otomatis: varian
|
|
2008
2176
|
opt-in `.fdy-fab--accent`/`.fdy-slider--accent`/`.fdy-badge-ov--accent`, chrome docs, swatch
|
|
2009
|
-
identitas. Identitas kit kini **Azure** (primary biru-deep + accent biru-muda)
|
|
2177
|
+
identitas. Identitas kit kini **Azure** (primary biru-deep + accent biru-muda); teal dibuang.
|
|
2010
2178
|
Kontras WCAG tetap **9/9** (accent/surface + on-accent/accent lolos light & dark). Non-breaking
|
|
2011
2179
|
(nilai token berubah; nama token/kelas/API tetap).
|
|
2012
2180
|
|
|
2013
|
-
## [1.2.2]
|
|
2014
|
-
Rilis **1.2.2
|
|
2181
|
+
## [1.2.2] - 2026-07-23
|
|
2182
|
+
Rilis **patch 1.2.2**. Polish app-shell + koreksi warna indikator aktif/terpilih.
|
|
2015
2183
|
### Fixed
|
|
2016
|
-
- **Brand link-safe (#17)
|
|
2184
|
+
- **Brand link-safe (#17):** `.fdy-app__brand` kini set `text-decoration:none`; saat brand
|
|
2017
2185
|
dijadikan link "logo → home", judul/subjudul tak lagi ber-underline default browser.
|
|
2018
|
-
- **Indikator aktif/terpilih: teal → primary (#18 + extend)
|
|
2186
|
+
- **Indikator aktif/terpilih: teal → primary (#18 + extend).** Bar/underline/check pada state
|
|
2019
2187
|
aktif/terpilih sebelumnya pakai `--color-accent` (teal), yang **clash** dengan bg
|
|
2020
2188
|
`--color-primary-soft` + teks `--color-primary-strong` pada palet re-theme (accent ≠ primary).
|
|
2021
2189
|
Kini semua cue "you-are-here / terpilih" seragam di keluarga **primary**: item nav, underline
|
|
@@ -2023,33 +2191,33 @@ Rilis **1.2.2 — patch**. Polish app-shell + koreksi warna indikator aktif/terp
|
|
|
2023
2191
|
(`.fdy-fab--accent`, `.fdy-badge-ov--accent`, `.fdy-slider--accent`) tak berubah. Membalik
|
|
2024
2192
|
bagian "teal sebagai aksen fungsional" dari v1.1.0. Murni CSS, backward-compatible.
|
|
2025
2193
|
|
|
2026
|
-
## [1.2.1]
|
|
2027
|
-
Rilis **1.2.1
|
|
2194
|
+
## [1.2.1] - 2026-07-23
|
|
2195
|
+
Rilis **patch 1.2.1**. Perbaikan CSS ikon depan `.fdy-input-group`.
|
|
2028
2196
|
### Fixed
|
|
2029
|
-
- **Ikon depan input-group tak ke-center + gap
|
|
2197
|
+
- **Ikon depan input-group tak ke-center + gap.** `.fdy-input-group__addon--icon` dipakai
|
|
2030
2198
|
standalone tapi tak set `display`/`align-items`, jadi `<svg>`-nya nempel ke atas kotak flex
|
|
2031
2199
|
yang ter-stretch (ikon melayang tinggi); dan padding-kiri ikon menumpuk dengan padding-kiri
|
|
2032
2200
|
`.fdy-input` → gap lebar. Kini `--icon` self-contained (`inline-flex` + center, meniru
|
|
2033
2201
|
`.fdy-input-group__btn`) dan `.fdy-input` setelah ikon memangkas padding-kirinya. Murni CSS,
|
|
2034
2202
|
backward-compatible. Kena semua field search/find (topbar search, filter tabel, dialog CFL, dst).
|
|
2035
2203
|
|
|
2036
|
-
## [1.2.0]
|
|
2037
|
-
Rilis **1.2
|
|
2204
|
+
## [1.2.0] - 2026-07-23
|
|
2205
|
+
Rilis **1.2: Vue input wrappers (native reimpl)**. Komponen Vue idiomatik dengan `v-model`
|
|
2038
2206
|
nyata di atas class CSS kit, supaya form Vue tak perlu fallback ke `<select>`/`<input type=date>`
|
|
2039
2207
|
native. **Non-breaking**: murni tambahan (file `.vue` + export baru); enhancer, `useFreeday`,
|
|
2040
2208
|
dan semua class/token lama tak berubah. `vue` tetap peerDependency opsional.
|
|
2041
2209
|
### Added
|
|
2042
|
-
- **`FdyCombo`** (`import { FdyCombo } from 'freeday/vue'`)
|
|
2210
|
+
- **`FdyCombo`** (`import { FdyCombo } from 'freeday/vue'`) adalah combobox select-only WAI-ARIA APG
|
|
2043
2211
|
dengan `v-model` (generic `T extends string`), keyboard penuh (↑↓/Home/End/Enter/Esc/typeahead)
|
|
2044
2212
|
+ outside-click; props `options`/`placeholder`/`disabled`/`invalid`/`describedby`/`ariaLabelledby`.
|
|
2045
|
-
- **`FdyDatepicker`**
|
|
2213
|
+
- **`FdyDatepicker`** adalah kalender native di atas `datepicker.css`; `v-model` string ISO
|
|
2046
2214
|
`YYYY-MM-DD`, `min`/`max`, `locale` (Intl), keyboard grid penuh, `invalid`/`ariaLabelledby`.
|
|
2047
|
-
- **`FdyCfl`**
|
|
2215
|
+
- **`FdyCfl`** adalah choose-from-list **controlled + async**: `v-model:Row|null` +
|
|
2048
2216
|
`fetchPage(query,page) => Promise<{rows,hasMore}>`, `columns`/`display`/`rowKey`. `<dialog>`
|
|
2049
2217
|
native, search debounced, state loading/empty/error(+retry), pagination append, cache opsional,
|
|
2050
2218
|
guard respons out-of-order. Ini kontrak integrasi field lookup (map nilai → master data server).
|
|
2051
|
-
- **Error-state kit
|
|
2052
|
-
`datepicker`/`timepicker`, dan `input-group :has()` diperluas menangkap error kontrol
|
|
2219
|
+
- **Error-state kit:** varian `--error`/`[aria-invalid]` ditambah ke `combo`/`cascade`/
|
|
2220
|
+
`datepicker`/`timepicker`, dan `input-group :has()` diperluas menangkap error kontrol, jadi
|
|
2053
2221
|
validasi vanilla (`freeday-form`) **dan** prop `invalid` wrapper Vue sama-sama ber-styling.
|
|
2054
2222
|
(Field CFL = `.fdy-input`+input-group, jadi error-nya via `aria-invalid` pada input, bukan
|
|
2055
2223
|
kelas `.fdy-cfl--error`.)
|
|
@@ -2061,59 +2229,59 @@ dan semua class/token lama tak berubah. `vue` tetap peerDependency opsional.
|
|
|
2061
2229
|
- Follow-up diketahui (warisan datepicker, non-blocking): grid `role="grid"` datar tanpa `row`;
|
|
2062
2230
|
hari fokus-awal yang ter-`min`/`max`-disable bisa bikin grid tak ter-fokus keyboard di config tepi.
|
|
2063
2231
|
|
|
2064
|
-
## [1.1.0]
|
|
2065
|
-
Rilis **1.1
|
|
2066
|
-
visual** token-driven
|
|
2232
|
+
## [1.1.0] - 2026-07-23
|
|
2233
|
+
Rilis **1.1: "Precision" visual polish + pengerasan adopsi**. Dua bagian: **(1) penyegaran
|
|
2234
|
+
visual** token-driven: radius lebih tajam, elevation overlay lebih tegas, hierarki tipografi
|
|
2067
2235
|
heading lebih jelas, teal (`--color-accent`) sebagai indikator fungsional non-teks untuk state
|
|
2068
|
-
aktif/terpilih; **(2) pengerasan adopsi** dari pemakaian project nyata
|
|
2236
|
+
aktif/terpilih; **(2) pengerasan adopsi** dari pemakaian project nyata: palet chart tervalidasi,
|
|
2069
2237
|
ukuran modal, nav statis, brand shell, export breakpoint, drawer `initAll`, dan tokenisasi kontrol.
|
|
2070
2238
|
**Non-breaking**: tak ada rename kelas/token/API (`fdy-` prefix, `--color-*`,
|
|
2071
2239
|
`window.Freeday*` tetap stabil). **AA-gated**: `npm test` tetap hijau (9/9), termasuk 6
|
|
2072
2240
|
assertion kontras baru yang menjaga teal-on-surface ≥3:1 di light & dark.
|
|
2073
2241
|
### Changed
|
|
2074
|
-
- **Radius ramp lebih tajam
|
|
2242
|
+
- **Radius ramp lebih tajam.** Skala dirapatkan ke `3/4/6/10/14/999px` (dari nilai lama yang
|
|
2075
2243
|
lebih membulat), kesan lebih presisi di semua kontrol & container.
|
|
2076
|
-
- **Motion lebih responsif
|
|
2077
|
-
- **Elevation overlay dijatah ulang
|
|
2244
|
+
- **Motion lebih responsif.** Durasi dasar `--dur-base` 200ms→180ms, `--dur-slow` 320ms→280ms.
|
|
2245
|
+
- **Elevation overlay dijatah ulang.** Bayangan `--shadow-3` (menu/dropdown/popover) dan
|
|
2078
2246
|
`--shadow-4` (modal) kini lebih tajam & lebih hadir secara visual; bayangan resting
|
|
2079
2247
|
(kartu, tombol) tidak berubah.
|
|
2080
|
-
- **Hierarki heading "Precision"
|
|
2248
|
+
- **Hierarki heading "Precision".** `h1`–`h4` kini memakai `--font-display` (Sora) / 700 /
|
|
2081
2249
|
`--tracking-tighter` (token baru, -0.03em) / `--leading-snug`; ukuran font tidak berubah.
|
|
2082
|
-
- **State interaksi disatukan
|
|
2250
|
+
- **State interaksi disatukan.** Hover/active/disabled/focus-visible dirapikan lintas
|
|
2083
2251
|
input/combo/cascade/chip/pagination/selection; memperbaiki bug nyata di mana
|
|
2084
2252
|
`.fdy-chip--filter` tidak punya focus ring sama sekali.
|
|
2085
|
-
- **`.fdy-table-scroll` scroll tanpa syarat
|
|
2253
|
+
- **`.fdy-table-scroll` scroll tanpa syarat**, tak lagi hanya berlaku di dalam `.fdy-datatable`
|
|
2086
2254
|
(`.fdy-table-wrap` untuk shell berbingkai, `.fdy-table-scroll` untuk scroll polos standalone).
|
|
2087
|
-
- **`selection.css` di-tokenkan
|
|
2255
|
+
- **`selection.css` di-tokenkan.** Nilai mentah (`#fff` thumb switch, inset `rgba`, dimensi
|
|
2088
2256
|
`1.15`/`2.4`/`1.35rem`) → token (`--color-switch-thumb`, `--color-shadow-inset`, `--control-box`,
|
|
2089
2257
|
`--control-switch-w/-h`). Nol perubahan visual.
|
|
2090
|
-
- **Default warna seri donut
|
|
2258
|
+
- **Default warna seri donut:** saat `data-fdy-colors` lebih pendek dari jumlah seri, celah kini
|
|
2091
2259
|
diisi `--chart-N` fixed-order (bukan cycle palet semantik lama). Non-breaking.
|
|
2092
2260
|
### Added
|
|
2093
|
-
- **Teal sebagai aksen fungsional
|
|
2261
|
+
- **Teal sebagai aksen fungsional.** `--color-accent` dipakai sebagai indikator non-teks
|
|
2094
2262
|
untuk state aktif/terpilih: item nav aktif, underline tab aktif, baris tabel terpilih,
|
|
2095
|
-
opsi combo/cascade/autocomplete terpilih. Bukan dekorasi
|
|
2263
|
+
opsi combo/cascade/autocomplete terpilih. Bukan dekorasi, murni penanda status.
|
|
2096
2264
|
- **Token baru** `--tracking-tighter` (-0.03em) untuk heading Sora.
|
|
2097
2265
|
- **6 assertion kontras WCAG (`AA_UI`, 3:1)** baru di `test/contrast.test.mjs` yang menjaga
|
|
2098
2266
|
teal-on-surface di light & dark tetap lolos ambang kontrol non-teks.
|
|
2099
|
-
- **Palet chart kategorikal
|
|
2267
|
+
- **Palet chart kategorikal:** token `--chart-1..8` (+ `--chart-grid`/`--chart-tick`), 8 warna
|
|
2100
2268
|
colorblind-safe **tervalidasi** (validator dataviz, light & dark di surface Freeday: dark
|
|
2101
2269
|
all-pass, light lolos + relief rule). Donut menggambar seri **fixed-order** (tanpa cycle).
|
|
2102
2270
|
Token chart sengaja di luar `contrast.test.mjs` (a11y via validator palet + legend/label).
|
|
2103
|
-
- **Modal size
|
|
2104
|
-
- **`.fdy-nav--flat`**
|
|
2271
|
+
- **Modal size:** `.fdy-modal--sm/--md/--lg/--wide` (24/32/48/60rem, responsif, anti-overflow).
|
|
2272
|
+
- **`.fdy-nav--flat`** adalah varian nav grup statis (tanpa caret / garis antar-grup / affordance
|
|
2105
2273
|
collapse) + dukung markup non-`<details>` yang benar-benar tak bisa dilipat.
|
|
2106
|
-
- **Brand shell berstruktur
|
|
2274
|
+
- **Brand shell berstruktur:** `.fdy-app__brand-mark`/`-title`/`-subtitle` (ikon + 2 baris di
|
|
2107
2275
|
header shell); pemakaian teks satu-baris tetap jalan.
|
|
2108
|
-
- **`FreedayDrawer.initAll`**
|
|
2109
|
-
- **Export skala breakpoint
|
|
2276
|
+
- **`FreedayDrawer.initAll`** membuat drawer ter-hydrate `useFreeday` di subtree Vue/React.
|
|
2277
|
+
- **Export skala breakpoint:** `freeday/breakpoints` (`{ sm:600, md:960, lg:1280, xl:1920 }`)
|
|
2110
2278
|
untuk `matchMedia` / menyelaraskan `@media` app ke skala kit.
|
|
2111
|
-
- **Docs
|
|
2279
|
+
- **Docs:** contoh **product-appbar** siap-copas (breadcrumb + input-group search + menu);
|
|
2112
2280
|
install `git+https` untuk CI (repo privat tanpa SSH key); klarifikasi `.fdy-btn` = primary,
|
|
2113
2281
|
`.fdy-table-wrap`, dan density `--control-h` untuk komponen custom.
|
|
2114
2282
|
|
|
2115
|
-
## [1.0.0]
|
|
2116
|
-
Rilis **1.0
|
|
2283
|
+
## [1.0.0] - 2026-07-22
|
|
2284
|
+
Rilis **1.0, project-ready**, sekaligus **rebrand ke Freeday**. Definisi v1.0 terpenuhi:
|
|
2117
2285
|
kontras WCAG AA lolos audit otomatis · installable via git · integrasi Vue/React/Blazor
|
|
2118
2286
|
terbukti di contoh faktur yang jalan · docs adopsi lengkap.
|
|
2119
2287
|
### Changed
|
|
@@ -2127,51 +2295,51 @@ terbukti di contoh faktur yang jalan · docs adopsi lengkap.
|
|
|
2127
2295
|
### Docs
|
|
2128
2296
|
- Root redirect `index.html` → `docs/` untuk GitHub Pages (URL bersih `/freeday-ui-kit/`).
|
|
2129
2297
|
|
|
2130
|
-
## [0.9.6]
|
|
2298
|
+
## [0.9.6] - 2026-07-22
|
|
2131
2299
|
### Docs
|
|
2132
|
-
- **Whole-app language toggle (ID ⇄ EN)
|
|
2300
|
+
- **Whole-app language toggle (ID ⇄ EN):** the topbar toggle now switches the entire docs,
|
|
2133
2301
|
not just the landing: topbar, sidebar labels + group headers, every section heading, and
|
|
2134
2302
|
every description. Keyed by section id / nav href, round-trips cleanly. (In-demo sample
|
|
2135
2303
|
labels stay as illustrative content.)
|
|
2136
|
-
- **Proper state icons
|
|
2304
|
+
- **Proper state icons:** the empty / error state cards use duotone SVG icons instead of
|
|
2137
2305
|
emoji (🗂️/⚠️).
|
|
2138
2306
|
- Hero CTA spacing loosened (removed a stale `.doc-hero p` rule that zeroed the gap).
|
|
2139
2307
|
### Changed
|
|
2140
|
-
- **Icons stay single-tone
|
|
2141
|
-
(duotone is reserved for the larger illustrative icons
|
|
2308
|
+
- **Icons stay single-tone.** Reverted the date/time picker glyphs to minimalist single-tone
|
|
2309
|
+
(duotone is reserved for the larger illustrative icons, meaning states, and the theme toggle).
|
|
2142
2310
|
|
|
2143
|
-
## [0.9.5]
|
|
2311
|
+
## [0.9.5] - 2026-07-22
|
|
2144
2312
|
### Added
|
|
2145
|
-
- **Two-tone (duotone) control icons
|
|
2313
|
+
- **Two-tone (duotone) control icons:** the date/time picker triggers (and the datetime
|
|
2146
2314
|
composer that reuses them) now render with a low-opacity fill behind the stroke, matching the
|
|
2147
2315
|
sun/moon theme toggle. A coherent duotone treatment for the control-glyph family.
|
|
2148
2316
|
### Docs
|
|
2149
|
-
- **Landing language toggle (ID ⇄ EN)
|
|
2317
|
+
- **Landing language toggle (ID ⇄ EN):** a topbar button swaps the landing copy (hero, stats,
|
|
2150
2318
|
framework-integration, footer) between Indonesian and English via `[data-i18n]`; inline
|
|
2151
2319
|
markup (bold/code) is preserved. Version references synced to v0.9.5.
|
|
2152
2320
|
|
|
2153
|
-
## [0.9.4]
|
|
2321
|
+
## [0.9.4] - 2026-07-22
|
|
2154
2322
|
### Added
|
|
2155
|
-
- **Sidebar-menu component
|
|
2323
|
+
- **Sidebar-menu component:** `.fdy-nav` (App shell) now supports per-node `.fdy-nav__icon`
|
|
2156
2324
|
and `.fdy-nav__badge` (count), collapsible sections `.fdy-nav__group`/`.fdy-nav__grouplabel`,
|
|
2157
2325
|
and nested items `.fdy-nav__tree` + `.fdy-nav__sub` (native `<details>`, zero-JS). Any node
|
|
2158
2326
|
can be icon+text, text-only, or badged. New "Sidebar menu" docs section.
|
|
2159
|
-
- **Responsive App shell
|
|
2327
|
+
- **Responsive App shell:** on mobile (≤720px) the sidebar becomes an **off-canvas drawer**
|
|
2160
2328
|
opened by the topbar toggle, with a `.fdy-app__backdrop`; on desktop the toggle collapses it.
|
|
2161
2329
|
### Changed
|
|
2162
|
-
- **Brand ↔ topbar aligned
|
|
2330
|
+
- **Brand ↔ topbar aligned:** `.fdy-app__brand` is now the same height as the topbar
|
|
2163
2331
|
(`--space-16`) so the logo and page title sit on the same line. `.fdy-app__title` truncates
|
|
2164
2332
|
instead of forcing overflow.
|
|
2165
2333
|
### Fixed
|
|
2166
|
-
- **Awkward Indonesian copy
|
|
2334
|
+
- **Awkward Indonesian copy:** dropzone “Jatuhkan …” → “Seret berkas ke sini …”.
|
|
2167
2335
|
### Docs
|
|
2168
2336
|
- Theme toggle is now a two-tone **sun/moon icon** button. Clearer **notification badge** demo
|
|
2169
2337
|
(bell + count) explaining the overlay badge works on any element. Responsive fixes so nothing
|
|
2170
2338
|
overflows down to 320px.
|
|
2171
2339
|
|
|
2172
|
-
## [0.9.3]
|
|
2340
|
+
## [0.9.3] - 2026-07-22
|
|
2173
2341
|
### Added
|
|
2174
|
-
- **Chart hover (Chart.js-style)
|
|
2342
|
+
- **Chart hover (Chart.js-style):** bar & donut charts now show an interactive tooltip that
|
|
2175
2343
|
follows the cursor (`label: value`, donut adds `(%)`). Bars dim while one is hovered (the
|
|
2176
2344
|
hovered bar stays lit); donut gains a transparent SVG sector per slice that pops on hover.
|
|
2177
2345
|
### Changed
|
|
@@ -2179,29 +2347,29 @@ terbukti di contoh faktur yang jalan · docs adopsi lengkap.
|
|
|
2179
2347
|
scrolls away). New `.fdy-app__navtoggle` (hamburger) + `.fdy-app--nav-collapsed` to
|
|
2180
2348
|
collapse/hide the sidebar. Roomier `--space-8` main padding.
|
|
2181
2349
|
### Fixed
|
|
2182
|
-
- **Anchor buttons underlined
|
|
2350
|
+
- **Anchor buttons underlined:** `.fdy-btn` now sets `text-decoration:none`, so links styled
|
|
2183
2351
|
as buttons (e.g. hero CTAs) no longer show an underline.
|
|
2184
2352
|
### Docs
|
|
2185
2353
|
- Nav categories are collapsible `<details>` groups with chevrons (PrimeVue-style); more
|
|
2186
2354
|
section whitespace; topbar hamburger to collapse the sidebar.
|
|
2187
2355
|
|
|
2188
|
-
## [0.9.2]
|
|
2356
|
+
## [0.9.2] - 2026-07-22
|
|
2189
2357
|
### Fixed
|
|
2190
|
-
- **Hover kontrol form tak terlihat
|
|
2358
|
+
- **Hover kontrol form tak terlihat.** Sejak border resting jadi `--color-control-border`
|
|
2191
2359
|
(slate-500) untuk a11y, hover yang menuju `--color-text-subtle` (juga slate-500) tak berubah.
|
|
2192
2360
|
Semua kontrol (input, textarea, combo, cascade, time/date picker, input-group, dropzone) kini
|
|
2193
2361
|
hover ke `--color-text-muted` (slate-600) → menggelap terlihat.
|
|
2194
|
-
- **Split button rusak saat hover
|
|
2362
|
+
- **Split button rusak saat hover.** `translateY(-1px)` per-tombol mengangkat satu paruh saja
|
|
2195
2363
|
sehingga jahitan tak sejajar. Kini `.fdy-btn-split` menjadi satu unit: tiap paruh tak
|
|
2196
2364
|
mengangkat/berbayang sendiri; split terangkat & berbayang sebagai satu kesatuan, plus garis
|
|
2197
2365
|
pemisah tipis antar paruh.
|
|
2198
2366
|
|
|
2199
|
-
## [0.9.1]
|
|
2367
|
+
## [0.9.1] - 2026-07-22
|
|
2200
2368
|
### Added
|
|
2201
|
-
- **Adapter React** (`freeday/react`)
|
|
2369
|
+
- **Adapter React** (`freeday/react`) memberi hook `useFreeday(rootRef?)` yang meng-*hydrate* enhancer
|
|
2202
2370
|
di subtree pada mount + tiap commit (idempotent). Tipe `event.detail` di
|
|
2203
2371
|
`adapters/react/index.d.ts`. Contoh jalan `examples/react-faktur/` (Vite + React 19 + TS).
|
|
2204
|
-
- **Interop Blazor** (`freeday/blazor`, `adapters/blazor/freeday-blazor.js`)
|
|
2372
|
+
- **Interop Blazor** (`freeday/blazor`, `adapters/blazor/freeday-blazor.js`) adalah script klasik yang
|
|
2205
2373
|
mendaftarkan `window.FreedayBlazor` dengan `initAll` / `on` / `off` / `toast` / `toggleTheme`.
|
|
2206
2374
|
`on(el, event, dotNetRef, method)` meneruskan detail (JSON-safe) ke method `[JSInvokable]` C#.
|
|
2207
2375
|
Contoh jalan `examples/blazor-faktur/` (Blazor WASM, .NET 10, code-behind `.razor` + `.razor.cs`).
|
|
@@ -2211,13 +2379,13 @@ terbukti di contoh faktur yang jalan · docs adopsi lengkap.
|
|
|
2211
2379
|
- `package.json`: export `./react` + `./blazor`, `react` peerDependency opsional (`>=18`).
|
|
2212
2380
|
`version` 0.9.0 → 0.9.1.
|
|
2213
2381
|
|
|
2214
|
-
## [0.9.0]
|
|
2382
|
+
## [0.9.0] - 2026-07-22
|
|
2215
2383
|
### Added
|
|
2216
|
-
- **Adapter Vue 3** (`freeday/vue`)
|
|
2384
|
+
- **Adapter Vue 3** (`freeday/vue`) memberi composable `useFreeday(rootRef?)` yang meng-*hydrate*
|
|
2217
2385
|
semua enhancer di subtree komponen saat mount + tiap update (idempotent). Tipis: enhancer
|
|
2218
2386
|
tetap sumber kebenaran, event `fdy-*` mengalir lewat `v-on` native. Tipe `event.detail`
|
|
2219
2387
|
disertakan (`FdyCascadeChangeDetail`, dst) di `adapters/vue/index.d.ts`.
|
|
2220
|
-
- **Contoh jalan** `examples/vue-faktur/`
|
|
2388
|
+
- **Contoh jalan** `examples/vue-faktur/` adalah layar faktur nyata (Vite + Vue 3 + TS) yang memakai
|
|
2221
2389
|
form validation + mask + cascade + datepicker + combo + table via adapter. `npm install &&
|
|
2222
2390
|
npm run dev`. Membuktikan kontrak integrasi end-to-end (diverifikasi headless: hydrate,
|
|
2223
2391
|
validasi men-gate submit, event → state Vue, tema light/dark).
|
|
@@ -2225,22 +2393,22 @@ terbukti di contoh faktur yang jalan · docs adopsi lengkap.
|
|
|
2225
2393
|
- `package.json`: export `./vue`, `adapters/` masuk `files`, `vue` sebagai peerDependency
|
|
2226
2394
|
opsional (`^3.4.0`). `version` 0.8.1 → 0.9.0.
|
|
2227
2395
|
|
|
2228
|
-
## [0.8.1]
|
|
2396
|
+
## [0.8.1] - 2026-07-22
|
|
2229
2397
|
### Added
|
|
2230
|
-
- **Form validation** (`freeday-form.js`, `[data-fdy-validate]`)
|
|
2398
|
+
- **Form validation** (`freeday-form.js`, `[data-fdy-validate]`) memakai Constraint Validation
|
|
2231
2399
|
API di-wire ke error inline aksesibel: `aria-invalid` + pesan ter-`aria-describedby`,
|
|
2232
2400
|
fokus ke field invalid pertama saat submit, re-validasi live tiap blur/input. Pesan
|
|
2233
2401
|
custom `data-fdy-msg-*`, cocok antar-field `data-fdy-match`. Event `fdy-form-invalid`/
|
|
2234
2402
|
`fdy-form-valid`, `window.FreedayForm`.
|
|
2235
|
-
- **Password reveal + input mask** (`freeday-mask.js`)
|
|
2403
|
+
- **Password reveal + input mask** (`freeday-mask.js`): `[data-fdy-password]` tambah
|
|
2236
2404
|
toggle tampil/sembunyi (memakai ulang chrome tombol input-group); `[data-fdy-mask]`
|
|
2237
2405
|
format saat mengetik (`#` digit, `A` huruf, `*` alnum, sisanya literal), raw value di
|
|
2238
2406
|
`dataset.fdyRaw` + event `fdy-mask`. `window.FreedayMask`.
|
|
2239
|
-
- **Cascade select** (`cascade.css` + `freeday-cascade.js`, `[data-fdy-cascade]`)
|
|
2407
|
+
- **Cascade select** (`cascade.css` + `freeday-cascade.js`, `[data-fdy-cascade]`):
|
|
2240
2408
|
pemilih hierarki drill-down dari `<ul>` bersarang; cabang membuka level berikut, daun
|
|
2241
2409
|
memilih (nilai = jalur lengkap), back/crumb + keyboard penuh. Event `fdy-cascade-change`,
|
|
2242
2410
|
`window.FreedayCascade`.
|
|
2243
|
-
- **Chip choice/filter** (`chip.css` + `freeday-chip.js`)
|
|
2411
|
+
- **Chip choice/filter** (`chip.css` + `freeday-chip.js`): chip interaktif
|
|
2244
2412
|
(`fdy-chip--choice` / `fdy-chip--filter`, `<button aria-pressed>`) dalam grup
|
|
2245
2413
|
`[data-fdy-chips]` (`data-single` = pilih-satu), plus wiring hapus untuk
|
|
2246
2414
|
`.fdy-chip__remove`. Event `fdy-chip-change` / `fdy-chip-remove`, `window.FreedayChip`.
|
|
@@ -2249,27 +2417,27 @@ terbukti di contoh faktur yang jalan · docs adopsi lengkap.
|
|
|
2249
2417
|
`aria-invalid="true"` (bukan cuma kelas `--error`), jadi enhancer cukup toggle satu
|
|
2250
2418
|
atribut aksesibel.
|
|
2251
2419
|
|
|
2252
|
-
## [0.8.0]
|
|
2420
|
+
## [0.8.0] - 2026-07-22
|
|
2253
2421
|
### Added
|
|
2254
2422
|
- **Installable sebagai paket GitHub.** `npm i github:cahyo-dimas/freeday-ui-kit#v0.8.0`.
|
|
2255
2423
|
`package.json` kini punya `exports`, `files`, `sideEffects`. Jalur import:
|
|
2256
2424
|
`freeday/css` (token+komponen), `freeday` (semua enhancer), `freeday/tokens`,
|
|
2257
2425
|
`freeday/css/components`, `freeday/enhancers/<nama>`.
|
|
2258
|
-
- `dist/freeday.bundle.css`
|
|
2426
|
+
- `dist/freeday.bundle.css` berisi token + komponen dalam satu file (satu import, anti-footgun
|
|
2259
2427
|
"lupa token").
|
|
2260
2428
|
- Skrip rilis: lifecycle `version` build-ulang + `git add dist`; `prepack` build-ulang.
|
|
2261
2429
|
### Changed
|
|
2262
2430
|
- `version` 0.1.0 → 0.8.0 (mulai semver bersih; komponen baru berikutnya = rilis minor 0.8.x).
|
|
2263
2431
|
|
|
2264
|
-
## [0.7.0]
|
|
2432
|
+
## [0.7.0] - 2026-07-22
|
|
2265
2433
|
### Added
|
|
2266
|
-
- **Time picker** (`freeday-timepicker.js`, `[data-fdy-timepicker]`)
|
|
2434
|
+
- **Time picker** (`freeday-timepicker.js`, `[data-fdy-timepicker]`): trigger + popup daftar
|
|
2267
2435
|
waktu (listbox WAI-ARIA), 24 jam, `data-step`/`data-min`/`data-max`, keyboard penuh.
|
|
2268
|
-
- **Datetime picker** (`freeday-datetime.js`, `[data-fdy-datetimepicker]`)
|
|
2436
|
+
- **Datetime picker** (`freeday-datetime.js`, `[data-fdy-datetimepicker]`): komposisi date +
|
|
2269
2437
|
time, satu event `fdy-datetime-change` (`YYYY-MM-DDTHH:MM`).
|
|
2270
2438
|
- **Konvensi varian ikon**: `data-fdy-no-icon` / `<template data-fdy-icon>` di date/time/datetime
|
|
2271
2439
|
picker; `fdy-combo--no-icon` + `.fdy-combo__icon` di select; contoh ikon depan di input-group.
|
|
2272
|
-
- Test regresi kontras WCAG (`test/contrast.test.mjs`)
|
|
2440
|
+
- Test regresi kontras WCAG (`test/contrast.test.mjs`): audit graf token (light + dark),
|
|
2273
2441
|
composite fill `-soft`, tegakkan 4.5:1 teks / 3:1 batas kontrol.
|
|
2274
2442
|
### Fixed
|
|
2275
2443
|
- **A11y (WCAG 1.4.11 & AA):** border kontrol form 1.55:1 → token `--color-control-border`
|