fold-ng 0.3.0 → 0.5.1
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 +376 -1
- package/README.md +136 -50
- package/fesm2022/fold-ng.mjs +3968 -1251
- package/fesm2022/fold-ng.mjs.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/tokens/primitives.css +19 -0
- package/tokens/scales.css +11 -0
- package/tokens/semantic.css +238 -0
- package/types/fold-ng.d.ts +1443 -72
- package/types/fold-ng.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,378 @@ All notable changes to **fold-ng** are documented here. The format follows
|
|
|
8
8
|
|
|
9
9
|
_Nothing yet._
|
|
10
10
|
|
|
11
|
+
## [0.5.1] - 2026-07-27
|
|
12
|
+
|
|
13
|
+
First published 0.5.x — same tree as the (unpublished, e2e-gated) v0.5.0 tag with the release e2e locator scoped to the first control.
|
|
14
|
+
|
|
15
|
+
## [0.5.0] - 2026-07-27
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`fold-view-toggle` — a segmented single-select.** A compact Cards / Table
|
|
20
|
+
(or density, chart-mode…) switch, generic and zero-domain: pass `options`
|
|
21
|
+
(`{ value, icon?, label?, ariaLabel?, disabled? }`) and bind `[(value)]`. It's a
|
|
22
|
+
real `role="radiogroup"` of `role="radio"` segments — roving tabindex, arrow-key
|
|
23
|
+
selection, `Home`/`End`, disabled-skip — not two independent toggles. `size`
|
|
24
|
+
(`sm`/`md`), `iconOnly`, `activeStyle` (`raised` chip or `accent` brand tint),
|
|
25
|
+
`forced-colors`-aware. New gallery `/view-toggle` page.
|
|
26
|
+
|
|
27
|
+
- **`fold-password-field` + `revealable` on `fold-input`.** A password input with
|
|
28
|
+
a **live requirements checklist** — a dot per rule that turns on as the value
|
|
29
|
+
satisfies it — built in two layers. `fold-input` gains a reusable `revealable`
|
|
30
|
+
(a show/hide eye toggle on a `type="password"` input); `fold-password-field`
|
|
31
|
+
composes it with the checklist. Rules are **injected**, not hard-coded: a
|
|
32
|
+
`FoldPasswordRule` is `{ label, test }`, so a `RegExp` (`foldRegexRule`), a
|
|
33
|
+
`zod` `safeParse`, a length check or anything drops in — `rules` defaults to a
|
|
34
|
+
sensible policy (`foldDefaultPasswordRules`). `validChange` emits when every
|
|
35
|
+
rule passes; Signal-Forms-native; the checklist is an `aria-live` list that
|
|
36
|
+
labels each row met / not met. The checklist is **redesignable**: project into
|
|
37
|
+
the `[rules]` slot and drive your own markup off the exported live state
|
|
38
|
+
(`#pw="foldPasswordField"` → `pw.checklist()`), with the default list as the
|
|
39
|
+
fallback. The built-in row marker is a `dot` (filling in) or a `check` tick
|
|
40
|
+
(`marker="check"`). New gallery `/password` page.
|
|
41
|
+
|
|
42
|
+
- **`fold-popover` + `fold-dropdown` — anchored floating layer & actions menu.**
|
|
43
|
+
fold's first floating primitive. `fold-popover` renders projected content in
|
|
44
|
+
the native **top layer** (the `popover` attribute — escapes `overflow: hidden`
|
|
45
|
+
and every `z-index`), anchored to a projected `[foldPopoverTrigger]` by a
|
|
46
|
+
**dependency-free flip/shift engine** (`computePlacement`, exported and
|
|
47
|
+
unit-tested in isolation — no Floating UI). Controlled via `[(open)]`;
|
|
48
|
+
dismissal (outside-click + `Escape`) and focus-return are built in, and the
|
|
49
|
+
trigger gets `aria-haspopup`/`aria-expanded`/`aria-controls` wired
|
|
50
|
+
automatically. `fold-dropdown` (+ `fold-dropdown-item`) is the actions menu on
|
|
51
|
+
top: `role="menu"`, ↑/↓ roving tabindex, `Home`/`End`, type-ahead, opens onto
|
|
52
|
+
its first enabled item, closes returning focus to the trigger. New gallery
|
|
53
|
+
`/popover` page.
|
|
54
|
+
|
|
55
|
+
Hardened to a competitor benchmark (Floating UI / Radix): the placement engine
|
|
56
|
+
now does **flip → size → shift** — it picks the best-fitting side (preferred →
|
|
57
|
+
opposite → roomiest) and reports the available space so a tall panel gets a
|
|
58
|
+
`max-height` and **scrolls inside the viewport** instead of overflowing;
|
|
59
|
+
**autoUpdate** tracks the trigger + panel via `ResizeObserver` (not just
|
|
60
|
+
scroll/resize) so the anchor never drifts; an optional **`arrow`** points at
|
|
61
|
+
the trigger; **enter + exit transitions** are native CSS (`@starting-style` +
|
|
62
|
+
`transition-behavior: allow-discrete`, no JS timers); and the dropdown's
|
|
63
|
+
type-ahead is **multi-letter** (buffered); `fallbackPlacements` makes the flip
|
|
64
|
+
chain configurable; the `autoUpdate` helper is exported. Nested/sub-menus and
|
|
65
|
+
cursor-anchored context menus are tracked for a later pass.
|
|
66
|
+
|
|
67
|
+
Added a **Playwright interaction suite** (`pnpm test:e2e`, real Chromium) for
|
|
68
|
+
what jsdom can't reach — native top-layer open, box sizing, keyboard/focus.
|
|
69
|
+
It caught two focus bugs now fixed: the dropdown focused its first item
|
|
70
|
+
_before_ the popover had shown the panel (so keydowns missed the menu), and
|
|
71
|
+
focus-return targeted a non-focusable wrapper trigger (e.g. `fold-button-icon`)
|
|
72
|
+
instead of its inner control — the popover now resolves the focusable element
|
|
73
|
+
for both focus and the aria wiring.
|
|
74
|
+
|
|
75
|
+
- **`fold-listbox` (+ `fold-option`) — a styleable single-select.** The richer
|
|
76
|
+
sibling of `fold-select` (which wraps a native `<select>`): reach for it when
|
|
77
|
+
options need custom rendering the OS popup can't give — an icon, a second line,
|
|
78
|
+
a status dot. Built on `fold-popover`, so it inherits the native top layer,
|
|
79
|
+
flip/shift positioning, outside-click + `Escape` dismissal and focus return.
|
|
80
|
+
On top it implements the ARIA select pattern — a `role="listbox"` that holds
|
|
81
|
+
focus and drives `aria-activedescendant` — with full keyboard (↑/↓, `Home`/
|
|
82
|
+
`End`, multi-letter type-ahead, `Enter`), a disabled-row skip, and a pure-CSS
|
|
83
|
+
selected check. Signal-Forms-native (`FormValueControl<string>`, so `[formField]`
|
|
84
|
+
and `[(value)]` both work) and shares `fold-input`'s box chrome (sizes, `panel`
|
|
85
|
+
variant). Options are dumb + presentational — each derives its own selected /
|
|
86
|
+
active state from the parent by `computed`, so nothing is pushed in during
|
|
87
|
+
change detection. New gallery `/listbox` page and a Playwright suite. Option
|
|
88
|
+
groups and a filter/combobox variant are tracked for later.
|
|
89
|
+
|
|
90
|
+
- **`fold-multiselect` — the multi-select sibling.** Same styleable popover +
|
|
91
|
+
`fold-option` rows, but the value is a set (`readonly string[]`): activating a
|
|
92
|
+
row **toggles** its membership and the panel **stays open**. It's a separate
|
|
93
|
+
component, not a `multiple` flag, because the Signal-Forms value type genuinely
|
|
94
|
+
differs from single-select's `string` — keeping `[formField]` / `[(value)]`
|
|
95
|
+
honestly typed (no `any`). `role="listbox"` + `aria-multiselectable`, the same
|
|
96
|
+
keyboard core (`Enter`/`Space` toggle), each selected row keeps its check, and
|
|
97
|
+
the trigger summarises the picks. The keyboard/roving/type-ahead core and the
|
|
98
|
+
option↔owner contract are now **shared** between the two components (a
|
|
99
|
+
`FOLD_LISTBOX_OWNER` token instead of a concrete injection, which also removed
|
|
100
|
+
a circular import). New `/listbox` demo section + a Playwright suite.
|
|
101
|
+
|
|
102
|
+
- **`fold-inline-confirm` — in-place destructive-action guard.** Extracted from
|
|
103
|
+
SH3PHERD's shared inline-confirm (which replaced four ad-hoc patterns) and
|
|
104
|
+
rebuilt to fold conventions. The host projects a real focusable trigger
|
|
105
|
+
(`foldButton` / `fold-button-icon`); on activation it is swapped, in the same
|
|
106
|
+
slot, for a confirm/cancel row — no modal, no layout jump. Three families:
|
|
107
|
+
**simple** (`confirmed` emits `""`), **type-to-confirm** (`[match]` — the
|
|
108
|
+
button unlocks once the text matches, case-insensitive + trimmed), and
|
|
109
|
+
**secret** (`password` — a masked field that confirms when non-empty and emits
|
|
110
|
+
the typed value, since a password can only be verified server-side). `Escape`
|
|
111
|
+
cancels; `Enter` confirms. Fully i18n via `provideFoldInlineConfirmLabels()`
|
|
112
|
+
(English default) or a per-instance `labels` input. Composed of `fold-button`,
|
|
113
|
+
`fold-button-icon` and `fold-input`. New gallery `/inline-confirm` page.
|
|
114
|
+
|
|
115
|
+
Hardened against a competitor benchmark (Radix `AlertDialog` / React-Aria):
|
|
116
|
+
the warning `message` is wired to the confirm button via `aria-describedby`
|
|
117
|
+
(so it is announced on focus, not stranded on the group); focus reliably
|
|
118
|
+
returns to the trigger on close (the previous attempt read the trigger before
|
|
119
|
+
it re-rendered and no-op'd); the trigger no longer double-fires on `Enter`.
|
|
120
|
+
New API: `confirmIcon` (leading icon on the confirm button) and `cancelIcon`
|
|
121
|
+
now takes an **icon name of your choice** (was a fixed `×`); a two-way
|
|
122
|
+
`[(open)]` model plus `keepOpenOnConfirm` give a controlled async story —
|
|
123
|
+
keep the affordance open, show `loading`, close it when the request settles.
|
|
124
|
+
16 spec blocks.
|
|
125
|
+
|
|
126
|
+
- **Slider hardcore pass — `fold-slider` + `fold-range-slider`.** `fold-slider`
|
|
127
|
+
now implements `FormValueControl<number>` (bind `[formField]`, or `[(value)]`);
|
|
128
|
+
the visible label is a real `<label for>` (else `ariaLabel`); a `valueText`
|
|
129
|
+
override is announced via `aria-valuetext`; `hint` + touched-gated `errors`.
|
|
130
|
+
`fold-range-slider` gets a `model()` value (`[(value)]` parity), `disabled`,
|
|
131
|
+
and i18n thumb labels (`minLabel` / `maxLabel`, English default) resolving the
|
|
132
|
+
hardcoded aria suffixes; it's a labelled `role="group"` with formatted
|
|
133
|
+
`aria-valuetext` per thumb (duration reads `mm:ss`). Both share a hardened
|
|
134
|
+
thumb — a focus-visible ring (was invisible on keyboard focus),
|
|
135
|
+
`prefers-reduced-motion`, `forced-colors`, and tokenised motion. New gallery
|
|
136
|
+
`/slider` page; README rows; specs 12 → 20.
|
|
137
|
+
|
|
138
|
+
- **`fold-paginator` hardcore pass.** Fully i18n — every string
|
|
139
|
+
(`Pagination`/prev/next/page/size/range/empty) is now overridable via
|
|
140
|
+
`provideFoldPaginatorLabels()` (English default) or a per-instance `labels`
|
|
141
|
+
input, resolving the last hardcoded-French portability blocker. Plus:
|
|
142
|
+
keyboard focus is preserved after a page change (moves to the active page, or
|
|
143
|
+
the prev/next arrow while it stays enabled — never dropped to `<body>`); the
|
|
144
|
+
visible range + active button clamp an out-of-range `currentPage` (a lagging
|
|
145
|
+
parent can't render a garbage range); the current `pageSize` is always in the
|
|
146
|
+
selector's options (the `<select>` can't show a phantom value); `disabled` is a
|
|
147
|
+
`booleanAttribute`; `siblingCount` is floored + zero-bounded. `@selector` + a
|
|
148
|
+
gallery `/paginator` page; specs cover DOM clicks, i18n, focus and the edges.
|
|
149
|
+
|
|
150
|
+
- **`fold-checkbox` — the boolean form control.** A native
|
|
151
|
+
`<input type="checkbox">` (keyboard, focus, the `checkbox` role, form
|
|
152
|
+
submission and `indeterminate` all native) visually replaced by a tokenised
|
|
153
|
+
box + check/dash mark. Signal-forms native via `FormCheckboxControl` (bind
|
|
154
|
+
`[formField]`), or standalone `[(checked)]`; plus `indeterminate`, `label` /
|
|
155
|
+
`ariaLabel`, `hint` + touched-gated `errors`, `required`, `size` (`sm`/`md`),
|
|
156
|
+
`disabled`. Accessible by construction (visible label wraps the control, or a
|
|
157
|
+
required `ariaLabel` — dev-warns when neither is set), with a focus-visible
|
|
158
|
+
ring, `prefers-reduced-motion` and `forced-colors` handled. `fold-data-table`'s
|
|
159
|
+
selection column now uses it.
|
|
160
|
+
|
|
161
|
+
- **`fold-data-table` — controlled row selection.** `selectable` renders a
|
|
162
|
+
checkbox column plus a header select-all with an indeterminate state over the
|
|
163
|
+
current rows; the parent owns the set via `selected` (a `Set` of row keys) and
|
|
164
|
+
`selectionChange` (emits the next set — the table never mutates). Selected rows
|
|
165
|
+
carry an accent tint + `aria-selected`, and a `selectionLabel` names each
|
|
166
|
+
checkbox. Toggling a checkbox never triggers `rowClick`.
|
|
167
|
+
- **`fold-data-table` — `mobileLayout` (parent owns the small-screen shape).**
|
|
168
|
+
`scroll` (default — stay tabular, scroll horizontally; the table imposes no
|
|
169
|
+
card), `auto-cards` (each row stacks into a label/value card), or `custom` —
|
|
170
|
+
the parent supplies `<ng-template foldRowCard let-row>` (new
|
|
171
|
+
`FoldDataTableRowCardDirective`) and the table renders _that_ per row on mobile
|
|
172
|
+
instead of an imposed card. The table owns the chrome, not the content.
|
|
173
|
+
- **`fold-data-table` — a `loading` state.** A fetching table now shows a
|
|
174
|
+
centred `fold-spinner` instead of the empty state, so an in-flight roster
|
|
175
|
+
reads as "loading", never as "no data".
|
|
176
|
+
- **`fold-data-table` — an optional toolbar/title bar.** Project content with
|
|
177
|
+
`[foldToolbar]` (a title, a live count, a bulk-action bar that appears once
|
|
178
|
+
rows are selected) and it renders as a visible band above the column header —
|
|
179
|
+
the same content-projection idiom as `fold-card`'s `[cardHeader]`, collapsing
|
|
180
|
+
to nothing when the parent projects nothing. It stays put while the body
|
|
181
|
+
scrolls (the table now has an inner scroll region). `toolbarSurface` lends the
|
|
182
|
+
table a level —
|
|
183
|
+
`default` / `sunken` / `raised` / `accent` — mapped only to fold surface
|
|
184
|
+
tokens (no hard-coded colour); `accent` reuses the shared
|
|
185
|
+
`[data-surface="accent"]` machinery, so the bar's content auto-inverts to the
|
|
186
|
+
on-accent palette per theme.
|
|
187
|
+
- **`fold-data-table` — an accessible `caption`.** A new `caption` input renders
|
|
188
|
+
a visually-hidden `<caption>` that names the table for assistive tech
|
|
189
|
+
(distinct from the visible `foldToolbar` title).
|
|
190
|
+
- **`fold-data-table` — `stickyFirst` + `density`.** `stickyFirst` pins the
|
|
191
|
+
checkbox + identity columns while the body scrolls horizontally (opaque-backed
|
|
192
|
+
so tints don't bleed); `density="compact"` tightens the row padding.
|
|
193
|
+
- **`fold-data-table` — column `align: "center"` and `truncate`.** `center`
|
|
194
|
+
joins `right`; `truncate` clips a column to one ellipsised line (pair with
|
|
195
|
+
`width`).
|
|
196
|
+
- **`data-table` gallery page.** Added `/data-table` to the demo (live sort, row
|
|
197
|
+
select, keyboard nav, the loading + empty states, a custom mobile card, and a
|
|
198
|
+
playground for every flag).
|
|
199
|
+
- **Select family — generic value + a data-driven `[options]` API.**
|
|
200
|
+
`fold-listbox` / `fold-multiselect` / `fold-option` are now generic over the
|
|
201
|
+
option value `T` (was `string`-only): `value` is `T | null` (single) /
|
|
202
|
+
`readonly T[]` (multi), and a `compareWith` input (default `Object.is`) matches
|
|
203
|
+
**object** values by identity — string/number/enum need nothing. On top of
|
|
204
|
+
projected `<fold-option>`, a `[options]="FoldSelectOption<T>[]"` array API links
|
|
205
|
+
the value type to the options at compile time (rich rows via a projected
|
|
206
|
+
`<ng-template #option let-o>`). The type stays honest end-to-end (`T` public,
|
|
207
|
+
the owner token erased to `unknown`, no `any`/`as`).
|
|
208
|
+
- **`fold-data-table` — i18n label token.** `provideFoldDataTableLabels` +
|
|
209
|
+
`FoldDataTableLabels` + a `labels` input (same shape as the paginator), so the
|
|
210
|
+
select-all/select-row/sort/loading accessible strings are overridable per
|
|
211
|
+
locale instead of hardcoded English.
|
|
212
|
+
- **`/changelog` — the CHANGELOG as a designed timeline.** A new gallery page
|
|
213
|
+
renders `CHANGELOG.md` as a vertical `fold-timeline` (one card per release,
|
|
214
|
+
category-count badges, breaking flagged) — parsed at build into a typed,
|
|
215
|
+
SSR-safe data file (runs pre-tokenised, no runtime markdown, no `innerHTML`).
|
|
216
|
+
- **`/lab` — an "in dev" index.** A dedicated menu of exactly the components not
|
|
217
|
+
yet on npm, each linking to its page with the version it ships in. Both the
|
|
218
|
+
list and the `dev` rail badges are derived from each nav item's `since` vs the
|
|
219
|
+
published version, so they clear themselves the moment a release is cut.
|
|
220
|
+
- **`pnpm eta` — a read-only release preview.** Prints the next version, the
|
|
221
|
+
derived bump level, and the reasons, straight from the CHANGELOG's
|
|
222
|
+
`[Unreleased]` section — no side effects.
|
|
223
|
+
|
|
224
|
+
### Fixed
|
|
225
|
+
|
|
226
|
+
- **0.5 review-hardening pass** (multi-agent review of the release). `fold-data-table`
|
|
227
|
+
accessible strings are now i18n-overridable (were hardcoded); `fold-password-field`'s
|
|
228
|
+
requirements checklist actually **announces** rule flips (the `aria-live` region
|
|
229
|
+
now carries the met/not-met word as text, not just an attribute) and forwards
|
|
230
|
+
the reveal labels; a `forced-colors` + `prefers-reduced-motion` sweep across
|
|
231
|
+
`fold-data-table`, `fold-paginator`, the `fold-input` reveal button, `fold-view-toggle`
|
|
232
|
+
and the select-family trigger; `fold-view-toggle`'s roving tab stop never lands
|
|
233
|
+
on a disabled segment (+ a dev warning for a missing `ariaLabel`);
|
|
234
|
+
`fold-inline-confirm`'s Escape is guarded while `loading`; `fold-dropdown`
|
|
235
|
+
excludes Space from type-ahead; slider spacing tokenised.
|
|
236
|
+
|
|
237
|
+
- **Select family — a review-driven hardening pass** (`fold-listbox` /
|
|
238
|
+
`fold-multiselect` / `fold-popover`). Closing focus no longer traps: the
|
|
239
|
+
popover only pulls focus back to the trigger when it's still inside the closing
|
|
240
|
+
panel (Escape / pick) or nowhere — a `Tab` out now **advances** to the next
|
|
241
|
+
field like a native `<select>`. Dismissing the popup (Escape, outside-click,
|
|
242
|
+
Tab, or a pick) now marks the field **touched**, so a `required` select that's
|
|
243
|
+
opened and abandoned surfaces its error (blur parity). The trigger's
|
|
244
|
+
`aria-controls` points at the real `role="listbox"` (new `ariaControls` on
|
|
245
|
+
`fold-popover`), the active-row highlight and selected check get a
|
|
246
|
+
`forced-colors` treatment, and `fold-multiselect` membership is a `Set`
|
|
247
|
+
(O(1), not O(n²) across a long list) with the trigger summary collapsing to
|
|
248
|
+
"…, +N". Dev-mode now warns when a control holds a value with no matching
|
|
249
|
+
`<fold-option>`. New: `allowClear` on `fold-listbox` (a clear × once a value is
|
|
250
|
+
picked) and closed-trigger type-ahead (type to pick without opening).
|
|
251
|
+
|
|
252
|
+
- **`fold-popover` panels are now opaque.** The panel used `surface-raised` — a
|
|
253
|
+
~5% tint meant to _sit on_ an opaque surface — so on the top layer the page
|
|
254
|
+
bled through (visible while scrolling a long `fold-listbox`). It now composites
|
|
255
|
+
that tint over an opaque `surface-card` base. The popover also publishes its
|
|
256
|
+
trigger width as `--fold-popover-anchor-width`, and `fold-listbox` reads it for
|
|
257
|
+
a `min-width` — so the panel is never narrower than the trigger (a coherent
|
|
258
|
+
select look).
|
|
259
|
+
|
|
260
|
+
- **Heading inputs no longer leak a native `title` tooltip.** A static
|
|
261
|
+
`title="…"` on a component that has a `title` input both seeds the input _and_
|
|
262
|
+
stays on the host as a real HTML attribute — so `fold-page-layout`,
|
|
263
|
+
`fold-page-section`, `fold-element-title`, `fold-context-card`,
|
|
264
|
+
`fold-empty-state` and `fold-panel-header` rendered their heading a second time
|
|
265
|
+
as a browser tooltip on hover. The reflected attribute is now stripped
|
|
266
|
+
(`host: { '[attr.title]': 'null' }`); `fold-icon` keeps its `title` on purpose
|
|
267
|
+
(it maps to `aria-label`).
|
|
268
|
+
|
|
269
|
+
### Changed
|
|
270
|
+
|
|
271
|
+
- **Form-control box metrics are single-sourced** (`_field-box.scss`). The size
|
|
272
|
+
and `panel` dimensions shared by the native inputs (`input-shell`) and the
|
|
273
|
+
select-family triggers (`_listbox-shell`) now live in one Sass mixin, so a
|
|
274
|
+
metric redesign lands in both instead of drifting.
|
|
275
|
+
|
|
276
|
+
- **CI + release gates now run the Playwright browser tier.** The `test:e2e`
|
|
277
|
+
suite (native popover top layer, focus, positioning — behaviour jsdom can't
|
|
278
|
+
reach) is wired into both `ci.yml` (inside the single required `ci` job) and
|
|
279
|
+
`release.yml`, so a PR or a tag can't go green on a broken overlay.
|
|
280
|
+
|
|
281
|
+
- **The release bump is derived from the CHANGELOG.** `pnpm release` with no
|
|
282
|
+
argument now reads `[Unreleased]` and derives patch/minor/major (0.x-aware:
|
|
283
|
+
breaking → minor, features → minor, else patch) — the changelog you curate
|
|
284
|
+
defines the version. An explicit level still overrides. Parser + derivation
|
|
285
|
+
live in a shared `scripts/lib/changelog.mjs`, reused by the release flow, the
|
|
286
|
+
`pnpm eta` preview, and the `/changelog` page.
|
|
287
|
+
|
|
288
|
+
- **`fold-data-table` — clickable rows are a roving-tabindex group.** They now
|
|
289
|
+
answer Space as well as Enter (page-scroll suppressed) and Arrow Up/Down +
|
|
290
|
+
Home/End move focus between rows with a single tab stop — the ARIA grid
|
|
291
|
+
keyboard pattern, not a wall of tab stops.
|
|
292
|
+
- **`fold-data-table` — sort indicators are now `fold-icon`s** (`expand-all` when
|
|
293
|
+
idle, `chevron-up` / `chevron-down` when active) rather than text glyphs, so
|
|
294
|
+
the direction cue is theme-aware, pixel-aligned, and consistent with the rest
|
|
295
|
+
of the system. The icon is decorative — `aria-sort` on the `<th>` stays the
|
|
296
|
+
accessible carrier.
|
|
297
|
+
- Transitions now respect `prefers-reduced-motion`.
|
|
298
|
+
- **`fold-data-table` hardening (hardcore-review follow-ups).** Row keydown now
|
|
299
|
+
fires only when the row itself is focused — keys bubbling from an inner control
|
|
300
|
+
(a link, a button, the selection checkbox) are no longer stolen or
|
|
301
|
+
double-handled; the roving tab stop survives the focused row being removed
|
|
302
|
+
(never strands the group with zero tab stops); the `custom` mobile layout no
|
|
303
|
+
longer instantiates its card list on desktop; `truncate` warns in dev when its
|
|
304
|
+
column lacks a `width` to clip against; `aria-colcount` + a "Sort by …" label
|
|
305
|
+
on the sort control; the checkbox-column width is a single source of truth.
|
|
306
|
+
|
|
307
|
+
- **BREAKING (`fold-data-table`): the primary column renders as
|
|
308
|
+
`<th scope="row">`** (was a `<td>`) so screen readers announce each row by its
|
|
309
|
+
identity cell. Consumers that target the first cell with a `td`-specific
|
|
310
|
+
selector should switch to the `.folddt-cell` class (present on both the row
|
|
311
|
+
header and the data cells).
|
|
312
|
+
- **BREAKING (`fold-data-table`): `mobileCards` (boolean) is replaced by
|
|
313
|
+
`mobileLayout`, and the default flips from cards to scroll.** A narrow-screen
|
|
314
|
+
table now stays tabular (scrolls) unless asked to stack. `[mobileCards]="true"`
|
|
315
|
+
(or unset) → `mobileLayout="auto-cards"` to keep the stacked cards;
|
|
316
|
+
`[mobileCards]="false"` → drop it (`scroll` is the default).
|
|
317
|
+
|
|
318
|
+
## [0.4.0] - 2026-07-25
|
|
319
|
+
|
|
320
|
+
### Added
|
|
321
|
+
|
|
322
|
+
- **Auto-inverting accent surface.** A new `[data-surface="accent"]` region
|
|
323
|
+
(stamped by `fold-card surface="accent"`, `fold-hero-card surface="accent"`,
|
|
324
|
+
or the `foldSurface` directive) fills with the brand accent and re-points its
|
|
325
|
+
**whole content sub-tree** to an on-accent palette — text, borders, band
|
|
326
|
+
gradation, and even nested buttons / links / icon-tiles read on the accent
|
|
327
|
+
with no per-component code. It is theme-agnostic and derived (every value a
|
|
328
|
+
`color-mix` of the captured accent pair), and swaps the brand pair
|
|
329
|
+
(`primary` ↔ `on-primary`) without a CSS custom-property cycle by capturing on
|
|
330
|
+
the surface and inverting on descendants. A theme can override any role by
|
|
331
|
+
nesting a rule for its own `[data-theme=…]` under `[data-surface="accent"]`.
|
|
332
|
+
`FoldSurfaceName` gains `'accent'`. See `docs/surfaces.md`.
|
|
333
|
+
- **On-accent contrast contract.** A contract test derives the on-accent text
|
|
334
|
+
ramp per theme and asserts it clears a documented WCAG floor (the accent is an
|
|
335
|
+
emphasis surface — AA-large 3:1 — or the theme must override the ramp). No
|
|
336
|
+
eyeballing.
|
|
337
|
+
- **`fold-card` — an accessible interactive contract.** `interactive` cards are
|
|
338
|
+
now real controls: `role="button"`, `tabindex`, `Enter`/`Space` activation, an
|
|
339
|
+
`(activated)` output, an `ariaLabel` input, a visible focus ring, and
|
|
340
|
+
`prefers-reduced-motion` respected. The projected bands are neutral `<div>`s
|
|
341
|
+
(not `<header>`/`<footer>`), so a card is a single control with no nested
|
|
342
|
+
landmarks.
|
|
343
|
+
- **`fold-link` — `target` + `rel` for external links.** A linked `fold-link`
|
|
344
|
+
takes `target` (e.g. `_blank`) and `rel`; `rel` defaults to a safe
|
|
345
|
+
`noopener noreferrer` whenever `target="_blank"`, and `(clicked)` now emits the
|
|
346
|
+
`MouseEvent` (so cmd/middle-click and modifier state are observable).
|
|
347
|
+
- **`titan` theme — brushed titanium.** A fifth `[data-theme]`: a light, warm
|
|
348
|
+
brushed-steel read. A cool `steel` ground with the header + rails at the page's
|
|
349
|
+
own tint (a frameless top), bright polished cards floating off it on the shared
|
|
350
|
+
elevation shadow, and a heat-anodized `titanium` **copper-orange** brand.
|
|
351
|
+
Borders re-point to a **solid** steel primitive (a palpable machined seam, not
|
|
352
|
+
the alpha hairline the light themes share); corners soften a step in
|
|
353
|
+
`scales.css`. In the gallery both rails float as steel plates while the header
|
|
354
|
+
stays flat. Uniform-polarity, so no chrome override. The token contract (theme
|
|
355
|
+
parity, no-hex, no dead primitives) stays green.
|
|
356
|
+
|
|
357
|
+
### Changed
|
|
358
|
+
|
|
359
|
+
- **BREAKING — `fold-card` band chrome is per-band.** `separators` and
|
|
360
|
+
`raisedBands` change from booleans to a `FoldCardBandChrome`
|
|
361
|
+
(`'none' | 'header' | 'footer' | 'both'`), so a header and a footer are dressed
|
|
362
|
+
independently. Migration: `<fold-card separators>` → `separators="both"`;
|
|
363
|
+
drop the attribute for `'none'`.
|
|
364
|
+
|
|
365
|
+
### Fixed
|
|
366
|
+
|
|
367
|
+
- **`fold-avatar` — a broken image falls back to the initials** instead of the
|
|
368
|
+
browser's broken-image glyph, and retries when `imageUrl` changes. `ghost`
|
|
369
|
+
combined with `imageUrl` is no longer a silent no-op — a guest keeps the
|
|
370
|
+
dashed edge even with a photo.
|
|
371
|
+
- **`fold-avatar` — initials stay legible on every palette fill.** The initials
|
|
372
|
+
ink is now the higher-contrast of the dark/light pair (was a magic-threshold
|
|
373
|
+
guess), and a contrast contract asserts every built-in palette fill clears AA
|
|
374
|
+
(≥ 4.5:1). A status-ring perceivability contract locks the `ring` colours
|
|
375
|
+
against a WCAG 1.4.11 regression.
|
|
376
|
+
|
|
377
|
+
### Docs
|
|
378
|
+
|
|
379
|
+
- **`docs/surfaces.md`** documents the auto-inversion principle and the
|
|
380
|
+
per-theme override seam; a `/surfaces` gallery page shows the live plain-vs-
|
|
381
|
+
accent proof. `docs/STRENGTHS.md` captures the top-tier design arguments.
|
|
382
|
+
|
|
11
383
|
## [0.3.0] - 2026-07-25
|
|
12
384
|
|
|
13
385
|
### Added
|
|
@@ -133,7 +505,10 @@ design-token stylesheet.
|
|
|
133
505
|
`currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
|
|
134
506
|
strings localise via inputs / providers (`provideFoldPanelLabels`).
|
|
135
507
|
|
|
136
|
-
[unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.
|
|
508
|
+
[unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.5.1...HEAD
|
|
509
|
+
[0.5.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.5.1
|
|
510
|
+
[0.5.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.5.0
|
|
511
|
+
[0.4.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.4.0
|
|
137
512
|
[0.3.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.3.0
|
|
138
513
|
[0.2.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.2.1
|
|
139
514
|
[0.2.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.2.0
|