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