kerfjs 5.0.0-beta.55 → 5.0.0-beta.56

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 CHANGED
@@ -6,6 +6,335 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ - **`@kerfjs/ui` `Badge` has a text-free dot, and `TabScaffold` tabs can show
10
+ it.** `Badge` takes `size="dot"`: an 8px solid circle in the badge's tone,
11
+ with no children, appearance, or shape. Because a dot has no text, its props
12
+ require either a `label` (announced as an image, for example
13
+ `label="New activity"`) or `ariaHidden` when the owning component already
14
+ announces it. `TabScaffoldTab` accepts `badge: true` for the iOS tab-bar dot
15
+ (new content without a count), centered on the icon's top-trailing corner;
16
+ its `badgeLabel` is required and joins the tab's accessible name
17
+ ("Settings, Update available"). `BadgeTextProps` and `BadgeDotProps` are
18
+ exported from the root barrel.
19
+
20
+ - **`@kerfjs/ui` `Toolbar` truncates the leading title instead of letting the
21
+ center zone cover it.** When a leading identity, a `center` group, and
22
+ trailing actions no longer fit one row, the center zone keeps its whole
23
+ content width and the leading title ends in an ellipsis. Previously the
24
+ center track could shrink below its group, which then painted over the
25
+ title — a pushed `NavStack` view with a center group did this at 390px. The
26
+ fix holds under every `responsive` policy and both `centerAlign` values, and
27
+ a toolbar that fits lays out exactly as before.
28
+
29
+ - **`@kerfjs/ui` `ValueTableRow` keeps its value readable beside a long
30
+ label.** A long single-word label (such as `ConnectionStateBanner` in a
31
+ 390px-wide table) no longer squeezes the value to 0px wide, where it wrapped
32
+ one character per line while the label ran underneath it. The label and value
33
+ now share the row as two content-sized columns: a short value stays whole on
34
+ one line, the label wraps at its spaces and ends an over-long word in an
35
+ ellipsis before the value, and when both are long they split the row.
36
+
37
+ - **`@kerfjs/ui` pinned chrome can give way in a short pane.** `Pane` takes
38
+ `chromePlacement="auto"`: its header and footer stay pinned while the pane is
39
+ at least 480px (30rem) tall, and below that the whole pane scrolls as one
40
+ column so tall chrome never squeezes the content to nothing.
41
+ `WorkbenchChromePlacement` gains `"auto"` for `mainHeaderPlacement` /
42
+ `mainFooterPlacement`, and `Catalog` now defaults `headerPlacement` /
43
+ `footerPlacement` to `"auto"`. Its entry description is pinned under the
44
+ toolbar, which at 200% text size on a short window had left the preview 0px
45
+ tall.
46
+
47
+ - **`@kerfjs/ui` `TabScaffold` tabs can show a badge.** Give a
48
+ `TabScaffoldTab` a `badge` (a count or short string) to show the iOS tab-bar
49
+ badge — a compact, solid danger `Badge` at the top-trailing corner of the
50
+ tab's icon. The badge is hidden from assistive technology; the tab's
51
+ accessible name becomes `"<label>, <badgeLabel>"` (for example
52
+ "Inbox, 3 unread"), with `badgeLabel` defaulting to the badge text. Tabs
53
+ without a badge are unchanged.
54
+
55
+ - **`@kerfjs/ui` `ListItem` and `ListActionRow` labels stay out of the
56
+ trailing slot.** A long single-word primary label (such as a Catalog sidebar
57
+ entry named `ConnectionStateBanner` in a narrow rail) no longer runs
58
+ underneath trailing status text. Single-line rows now end the primary label
59
+ and description with a real ellipsis (previously the text was cut mid-glyph
60
+ with no "…"), and `multiline` rows break a word wider than the label column
61
+ onto the next line.
62
+
63
+ - **Custom elements keep the `role` and `aria-*` they set on themselves across
64
+ re-renders.** Web components conventionally own their host's ARIA (Web
65
+ Awesome's `wa-option` sets `role="option"` / `aria-selected`, `wa-divider`
66
+ sets `role="separator"`; Shoelace and FAST do the same), but a template never
67
+ mentions them, so the morph and the keyed-list attribute fast path stripped
68
+ them on the next re-render. On any custom element (hyphenated tag), `role`
69
+ and `aria-*` are now element-owned like `open`: never removed just because
70
+ the template omits them. The template can still set or change them, a signal
71
+ binding set to `null` / `false` still removes them, and plain elements are
72
+ unchanged. Trade-off: omitting one from a static template no longer removes
73
+ it from a custom element — render an explicit value (`aria-pressed="false"`)
74
+ instead.
75
+
76
+ - **`@kerfjs/ui` layout regions no longer slow edits to long lists inside
77
+ them.** The rule that clears the safe-area context for the children of a
78
+ Workbench main area or panel, SplitView list or detail, NavStack view,
79
+ TabScaffold scene, or CollapsiblePanel content made each child re-check the
80
+ region in Chromium on every DOM change. A 1000-row list placed directly in
81
+ one of these regions took about 46ms per style recalculation (2ms with the
82
+ list one level down), and now takes about 0.2ms. Layout and insets are
83
+ unchanged.
84
+
85
+ - **`@kerfjs/ui` `CollapsiblePanel` no longer slows insertions into long
86
+ lists.** An expanded inline panel clears its screen edge for its siblings;
87
+ the rules that did so selected the siblings themselves
88
+ (`:has(~ panel)` and `panel ~ *`), which in Chromium made each row inserted
89
+ into or removed from a long list restyle the list's other rows — about
90
+ 14.6ms instead of 0.2ms per insertion beside a 1000-row list. The panel's
91
+ container now carries a flag that a CSS style query reads, so a list pays
92
+ nothing. The edge still clears for the panel's siblings and returns when it
93
+ collapses; the one difference is that a sibling placed before a left panel
94
+ or after a right or bottom panel now loses the edge too.
95
+
96
+ - **`@kerfjs/ui` restore corners and covered floating controls no longer slow
97
+ every style recalculation.** The `CollapsiblePanel` / `ResizableRegion`
98
+ restore-corner anchor scope (`:has(> restore) > * > * > *`) and the static
99
+ side-overlay covered flag (`:has(> open overlay) > :not(overlay)`) put a
100
+ universal selector after a `:has()` container, which in Chromium restyled
101
+ the children of every ancestor of each DOM change — about 85ms instead of
102
+ 1ms per edit beside a 1000-row list. The drawer now publishes its anchor
103
+ only beside a restore control's container (or its work-area column), and
104
+ the covered flag is set on the overlay's container and cleared on its
105
+ overlay children. Restore-corner placement and covered-control hiding are
106
+ unchanged; a new CSS unit test rejects the costly `:has()` shapes.
107
+
108
+ - **`@kerfjs/ui` adds `ContentItem`.** Import it from
109
+ `@kerfjs/ui/content-item` to render one self-contained pane child with the
110
+ shared 8px margin, 1px border, and 8px padding. Framing and shape are typed
111
+ props instead of hand-written modifier classes:
112
+ - `frame="framed"` paints the neutral border in the 1px the item always
113
+ reserves, so its geometry does not change (replacing
114
+ `class="kui-content-item kui-content-item--framed"`);
115
+ - `shape="pill"` selects the 22px radius.
116
+ - `ariaLabel` names a distinct region, and `focusTarget` makes the item a
117
+ programmatic focus target (`tabindex="-1"`), such as a NavStack view's
118
+ `data-nav-focus` target.
119
+
120
+ The `.kui-content-item` classes stay public for elements that must carry the
121
+ geometry themselves; their CSS now lives in `content-item.css`, which
122
+ `layout.css` still imports. There is no filled variant — use `SunkenPanel`
123
+ for a lowered surface. The catalog, demos, and recipes use the component.
124
+
125
+ - **`@kerfjs/ui` icon-only toolbar buttons show the same help tag.** An
126
+ icon-only `<button>` or link inside a `ToolbarControlGroup`, named by
127
+ `aria-label` with no visible text, now shows the help tag icon-only Selects
128
+ and PopupMenus show, with the same 500ms hover delay, keyboard-focus-only
129
+ display, press/Escape dismissal, and quick scanning along a toolbar. The tag
130
+ is `aria-hidden` and never renames the button. A button with a native
131
+ `title`, or with its own Web Awesome tooltip, keeps that and gets no second
132
+ tag. The `select/register` and `popup-menu/register` imports install it; a
133
+ toolbar with icon-only buttons but neither control imports the new
134
+ `@kerfjs/ui/help-tags/register` subpath, which registers only the tooltip.
135
+
136
+ - **`@kerfjs/ui` icon-only Select and PopupMenu triggers show a help tag.**
137
+ An icon-only `Select` (single or `multiple` with `triggerIcon`) and an
138
+ icon-only `PopupMenu` now show a Web Awesome tooltip naming the control on
139
+ hover (after 500ms) and on keyboard focus: a filter reads "Filter by label:
140
+ Bug, Docs", a single Select its name and current choice, a menu its `label`.
141
+ It hides while the popup is open, on a press, and on Escape. The tag repeats
142
+ the accessible name, so it is `aria-hidden` and never joins
143
+ `aria-labelledby`; screen readers still hear the name once. It is installed
144
+ by the existing `select/register` and `popup-menu/register` imports, needs no
145
+ props, and never appears for labeled Selects or text menu triggers.
146
+
147
+ - **`@kerfjs/ui` toolbar groups drop their focus ring while a popup is open.**
148
+ A `ToolbarControlGroup` that owns the ring (`focusRing="outline"` or
149
+ `"halo"`) no longer paints it while a Select listbox or `PopupMenu` inside it
150
+ is open; the open popup shows focus, and the ring returns when the popup
151
+ closes with focus still in the group. This matches the per-control Select
152
+ and PopupMenu triggers.
153
+
154
+ - **`@kerfjs/ui` multiple `Select` can be an icon-only toolbar filter menu.**
155
+ Set `selectedPresentation: 'icon-only'` on a `multiple` Select together with
156
+ a fixed `triggerIcon` (for example a funnel `LucideIcon`); the types require
157
+ `triggerIcon` there and reject it everywhere else. The trigger keeps the
158
+ icon-only caret pill used in a `ToolbarControlGroup`, and:
159
+ - shows the fixed icon whatever is chosen;
160
+ - adds a compact count `Badge` while any choice is chosen;
161
+ - ends the combobox's accessible name with the chosen labels in choice order
162
+ ("Filter by label: Bug, Docs"), leaving the hidden value text empty so the
163
+ list is not announced twice.
164
+
165
+ The popup still stays open while choices toggle.
166
+
167
+ - **`@kerfjs/ui` `Select` options keep their accessibility semantics across
168
+ re-renders.** Re-rendering a Select (for example after its `change` handler
169
+ updates the controlling signal) removed the `role="option"` and
170
+ `aria-selected` Web Awesome sets on each option, so screen readers lost every
171
+ option's role and selected state. They now survive in single and `multiple`
172
+ Selects through the core rule below (custom elements own their `role` /
173
+ `aria-*`), with no Select-specific restore.
174
+
175
+ - **`@kerfjs/ui` `Select` dividers keep their separator role across
176
+ re-renders.** The `wa-divider` a `separatorBefore` choice adds lost the
177
+ `role="separator"` and `aria-orientation` Web Awesome sets on it the first
178
+ time the enclosing mount re-rendered, and never got them back, so assistive
179
+ technology stopped announcing the break between option runs. The Select now
180
+ renders both attributes itself.
181
+
182
+ - **`@kerfjs/ui` `Select` and `PopupMenu` re-renders no longer force Web
183
+ Awesome updates.** Web Awesome reflects its default `size`, `appearance`,
184
+ `placement`, and `orientation` onto `wa-select`, `wa-dropdown`, and
185
+ `wa-divider`; the templates omitted them, so every re-render of the enclosing
186
+ mount stripped them and each host ran a full Lit update to put them back. The
187
+ templates now render those defaults (`wa-select size="m"
188
+ appearance="outlined" placement="bottom"`, `wa-dropdown size="m"`, divider
189
+ `orientation="horizontal"`), so an unchanged re-render leaves the hosts
190
+ alone. No visual change. The `@kerfjs/ui/webawesome` JSX declarations type
191
+ these attributes.
192
+
193
+ - **`@kerfjs/ui` Workbench and CollapsiblePanel panes and chrome lists are
194
+ configurable.** The `Pane`s these layouts compose around app content now
195
+ take the new `PaneConfig` type (`contentElement`, `contentLabel`,
196
+ `separators`, `safeAreaEdges`): a Workbench panel's or `CollapsiblePanel`'s
197
+ `pane`, and the Workbench's `mainPane`. The `List`s holding `mainHeader` /
198
+ `mainFooter` take the new `ListConfig` type (`gap`, `hAlign`, `vAlign`,
199
+ `dividerSides`, `textInsets`, `controlInsets`) through `mainHeaderList` /
200
+ `mainFooterList`. Previously this configuration was hard-coded, so, for
201
+ example, a navigator rail could not render its content as a `nav` landmark
202
+ without restyling. Omitted or `undefined` fields keep today's defaults, and
203
+ the default markup is unchanged.
204
+
205
+ - **`@kerfjs/ui` `SplitView` forwards its `ResizableRegion` and compact
206
+ `NavStack` configuration.** `resizable` now also takes the list region's
207
+ `separator`, `handleIcon`, `contentOverflow`, and collapse options
208
+ (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`,
209
+ `restorePosition`), so an app can hide the list and float a restore control
210
+ without leaving `SplitView`. The new `compactStack` prop forwards the compact
211
+ stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, and
212
+ persistent `bottomToolbar`, and gives the list and detail views their own
213
+ toolbar groups (`list` / `detail`: `leading`, `center`, `toolbar`,
214
+ `bottomToolbar`). Omitted options keep today's defaults.
215
+
216
+ - **`@kerfjs/ui` `NavStack` renders its top chrome as a real `Toolbar`.** The
217
+ back control is a borderless `ToolbarControlGroup` and the title a
218
+ `ToolbarText` in the leading zone, and the stack now forwards:
219
+ - `toolbarConfig` — the `Toolbar` configuration (`dividerSides`,
220
+ `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`) plus `label`,
221
+ `titleSize` (default `large`), and `headingLevel`;
222
+ - per-view `leading` and `center` groups beside the existing trailing
223
+ `toolbar`;
224
+ - `backIcon` and visible `backText` for the back control.
225
+
226
+ The defaults keep today's bar: no divider, one 44px band, and the top and
227
+ side safe-area edges claimed. The hand-rolled `kui-nav-stack__lead` and
228
+ `kui-nav-stack__actions` wrappers are gone (their content now sits in the
229
+ toolbar's zones), and `kui-nav-stack__back` / `kui-nav-stack__back-icon`
230
+ are no longer styled by the stack.
231
+
232
+ - **`@kerfjs/ui` configures with props instead of token overrides.** Five
233
+ settings apps previously reached through raw `--kui-*` overrides now have a
234
+ typed prop or modifier; each defaults to today's rendering, and the
235
+ underlying tokens keep working:
236
+ - `FloatingToolbar` `inset` (a `CssLength`, default `--kui-space-m`) moves
237
+ it from the container edges. Omit it inside a restore corner, which owns
238
+ the inset.
239
+ - `DisclosureArrow` `size` (a `CssLength`, default 18px root-scaled).
240
+ - `ListHeader` `headingLevel` (1–6, default 2) sets the label's native
241
+ heading element for passive and trailing-action headers; a toggle header
242
+ has no heading and rejects it at compile time.
243
+ - `TokenSearchField` `clearIcon` replaces the clear action's glyph.
244
+ - `.kui-content-item--framed` paints the standard neutral border on a
245
+ content item that marks a real distinction, with no geometry change.
246
+
247
+ The docs and AI guidance also stop recommending `--kui-edge-inset-*`
248
+ overrides: an app-owned arrangement passes `safeAreaEdges` to its panes and
249
+ edge-claiming toolbars (including the layout toolbar configurations, which
250
+ forward it).
251
+
252
+ - **`@kerfjs/ui` hides a work area's floating controls under an open side
253
+ overlay.** While a side overlay is open — a Workbench rail (static
254
+ `presentation: "overlay"`, or a responsive overlay whose breakpoint applies),
255
+ a `wireSidebar` compact overlay or `presentation="overlay"`
256
+ `CollapsiblePanel`, or a horizontal `presentation="overlay"`
257
+ `ResizableRegion` — the covered work area's `FloatingToolbar`s and the
258
+ layouts' restore corners are now hidden (`visibility: hidden`, so they also
259
+ leave the tab order and the accessibility tree) instead of relying on
260
+ stacking order alone. A phone-width app could otherwise paint the main
261
+ column's floating drawer toggle over its open inspector. They come back as
262
+ the overlay closes; the overlay's own floating controls and a bottom
263
+ drawer's overlay are unaffected.
264
+
265
+ - **`@kerfjs/ui` `Select` supports multiple selection.** Set `multiple: true`
266
+ and pass the chosen values as an array `value`; the props are typed so an
267
+ array value requires `multiple`. Behavior:
268
+ - The popup stays open while the person toggles choices with the pointer,
269
+ Enter, or Space, and closes only on an outside press, Escape, or focus
270
+ leaving.
271
+ - Each chosen option shows a check.
272
+ - The closed control summarizes the chosen labels in choice order as a short
273
+ localized list, or shows `placeholderText` when none are chosen.
274
+ - `change` / `input` report the value array, and a form submits every chosen
275
+ value under `name`.
276
+ - Re-rendering with a different `value` updates the selection even after the
277
+ person has interacted.
278
+
279
+ `renderSelected` and `selectedPresentation="icon-only"` stay single-value
280
+ only.
281
+
282
+ - **`@kerfjs/ui` `PopupMenu` now looks exactly like `Select`.** Both read one
283
+ shared popup contract, so a command menu beside a value list in a toolbar
284
+ draws the same popup. `PopupMenu` changes:
285
+ - its caret turns to face the open menu;
286
+ - its menu fades in without scaling;
287
+ - it takes the Select listbox's 4px inset (also under the default
288
+ `menuInset="standard"`);
289
+ - its rows use the Select option's height, padding, and reserved check
290
+ column, and its icons use the same 16px size and 8px gap;
291
+ - a keyboard-focused item takes the Select's current-option fill instead of
292
+ a focus ring;
293
+ - headings render as the Select's uppercase group titles rather than slotted
294
+ `h3` elements.
295
+
296
+ An open `Select` also no longer paints its trigger focus ring. The open
297
+ listbox shows focus, and the ring returns when it closes.
298
+
299
+ - **`@kerfjs/ui` `Catalog` exposes its sidebar and toolbar configuration.** New
300
+ `sidebar` prop (size, `resizable`, `separator`, `collapseMotion`,
301
+ `presentation`, `responsiveOverlayAt`, `compactOverlay`, and the header
302
+ `toolbar`'s `ToolbarConfig`) forwarded to its Workbench rail, plus
303
+ `mainToolbar` / `footerToolbar` `ToolbarConfig` props. `wireCatalog` takes
304
+ `sidebarSize` / `sidebarStorageKey` to wire a resizable sidebar. Today's
305
+ 288px fixed sidebar and toolbar policies remain the defaults.
306
+
307
+ - **`@kerfjs/ui` composed toolbars are configurable.** `WorkbenchMainToolbar`,
308
+ `WorkbenchMainBottomToolbar`, and the panel `toolbar` of a Workbench panel or
309
+ `CollapsiblePanel` now take the `Toolbar`'s configuration: the new
310
+ `ToolbarConfig` type, covering `dividerSides`, `centerAlign`, `responsive`,
311
+ `responsiveAt`, and `safeAreaEdges`. Previously their dividers were
312
+ hard-coded, so for example dropping the line between a toolbar and its
313
+ content took CSS. Today's dividers remain the defaults. Also:
314
+ - `mainBottomToolbar` and panel toolbars take a `center`;
315
+ - panel toggles take localized `showLabel` / `hideLabel`;
316
+ - a Workbench panel's floating restore controls follow its
317
+ `restorePosition`.
318
+
319
+ - **Breaking (`@kerfjs/ui`): removed the pre-release compatibility surface.**
320
+ - The `DividerSides` type is gone; use `Sides`, from the renamed `sides`
321
+ module.
322
+ - `ListInsetText.horizontalOnly` and its `kui-list-inset-text--horizontal`
323
+ class are gone; pass `sides="rl"` instead.
324
+ - The legacy color tokens are gone, both as `--kui-color-*` properties and
325
+ as `uiColor()` names. Replace them with their canonical tokens:
326
+
327
+ | Removed | Use instead |
328
+ | --------------------------------------------- | ----------------------- |
329
+ | `border` | `neutral-border-normal` |
330
+ | `border-quiet` | `neutral-border-quiet` |
331
+ | `accent` | `brand-fill-normal` |
332
+ | `accent-text` | `brand-on-quiet` |
333
+ | `pop` | `pop-fill-quiet` |
334
+ | `pop-text` | `pop-on-fill` |
335
+ | `success`, `warning`, `danger` | `*-fill-quiet` |
336
+ | `success-text`, `warning-text`, `danger-text` | `*-on-quiet` |
337
+
9
338
  - **New `@kerfjs/ui` `PopupMenu` (`@kerfjs/ui/popup-menu`).** It renders a
10
339
  trigger and typed commands, headings, and dividers over the Web Awesome
11
340
  dropdown. Put it in a `single` `ToolbarControlGroup` with `nestedDropdown`, or
package/ai/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "kerfjsVersion": "5.0.0-beta.55",
2
+ "kerfjsVersion": "5.0.0-beta.56",
3
3
  "files": [
4
4
  {
5
5
  "name": "skill",
@@ -294,7 +294,10 @@ function restoreFocus(snap) {
294
294
 
295
295
  // src/utils/isUserAgentOwnedAttr.ts
296
296
  function isUserAgentOwnedAttr(tagNameUpper, name) {
297
- return name === "open" && (tagNameUpper === "DETAILS" || tagNameUpper === "DIALOG" || tagNameUpper.includes("-"));
297
+ if (tagNameUpper.includes("-")) {
298
+ return name === "open" || name === "role" || name.startsWith("aria-");
299
+ }
300
+ return name === "open" && (tagNameUpper === "DETAILS" || tagNameUpper === "DIALOG");
298
301
  }
299
302
 
300
303
  // src/utils/moveNode.ts
@@ -1511,4 +1514,4 @@ function resultToSegment(result) {
1511
1514
 
1512
1515
  export { captureFocus, each, morph, mount, moveNode, restoreFocus };
1513
1516
 
1514
- //# sourceMappingURL=chunk-WMRFXSLG.js.map
1517
+ //# sourceMappingURL=chunk-TLL3KXT6.js.map