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