kerfjs 5.0.0-beta.100 → 5.0.0-beta.101

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,8 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Added
10
+
11
+ - **`@kerfjs/ui/overlay-sheet.css`.** An opt-in stylesheet for core `kerfjs/overlay` dialogs presented with `presentation: 'bottom-sheet'`, matching `DialogSurface`'s sheet:
12
+ - the bottom edge, full width up to 560px and centered beyond;
13
+ - square bottom corners under the dialog radius;
14
+ - the 8px surface inset, padded by the larger of the safe area and the core's `--kerf-keyboard-inset`;
15
+ - a cap below the top safe area;
16
+ - a slide up as it opens.
17
+
18
+ The `kui-overlay-sheet-exit` class (passed as `exitClass`) slides it back down as it closes. It covers the plain and `native: true` hosts and honors reduced motion, the static-effects switch, and forced colors.
19
+
20
+ - **Resizable core bottom sheets in `@kerfjs/ui`.** `wireOverlaySheets()` (from `@kerfjs/ui/wire-overlay-sheets`) gives a core `kerfjs/overlay` bottom sheet with the `kui-overlay-sheet-resizable` class the grabber of a resizable `DialogSurface` sheet. It resizes the visible height by drag or keyboard (arrows, Page Up/Down, Home, End), between `--kui-overlay-sheet-min-height` (160px) and the cap, and reports the height in its ARIA values. The grabber sticks to the top edge while the content scrolls and stays first across re-renders. Call it once for each container sheets open in. Covered in Chromium, Firefox, and WebKit.
21
+
22
+ - **Tint accents.** A `@kerfjs/ui` `tint` takes an optional `accent`: `{ color, scheme, accent }`. Inside the scope, the brand ramp re-derives from it: loud fills and their on-loud text, brand text and links, text on brand fills, and the focus ring. Lightness is clamped so every pair keeps WCAG AA for any accent; an accent seed sweep checks this in Chromium, Firefox, and WebKit.
23
+ - **`npm run dev:lan` for device checks.** From the repo root or `ui/`, it serves the `@kerfjs/ui` development catalog on every network interface. It prints the LAN and Tailscale URLs and accepts Tailscale MagicDNS (`*.ts.net`) and mDNS (`*.local`) host names, so a phone can test what a desktop browser cannot: the virtual keyboard, safe areas, and touch. Plain `npm run dev` stays on `127.0.0.1`. The catalog page now sets `viewport-fit=cover`, so a notched phone reports its real safe-area insets. The device checks are listed in `ui/docs/manual-test-plan.md`.
24
+ - **Exit motion for `kerfjs/overlay` dialogs.** `overlay()`, `confirm()`, `prompt()`, `form()`, and `choice()` take `exitClass` / `exitDuration`, named and defaulted like `toast()`'s, so CSS can animate an overlay out (for example, slide a bottom sheet down). On `close()` everything else still happens immediately: the result settles, the overlay leaves the stack (an overlay opened during the exit is topmost), a `native` dialog leaves the top layer, the wrapper turns `inert`, and focus returns. Only the node's removal waits, until the exit's CSS animations finish or `exitDuration` passes, whichever is first. `prefers-reduced-motion: reduce` skips the exit. `OverlayHandle.close(result, { instant: true })` (new `OverlayCloseOptions` type) skips the exit or ends one in progress. A second `close()` is still a no-op. Without the new options, behavior is unchanged.
25
+ - **Exit motion for `popover()` and `tooltip()`.** Both take `exitClass` / `exitDuration` and use the same exit as `overlay()`. A popover plays it on `close()` and on an outside-click dismissal, and a tooltip plays it when it hides after `hideDelay`. Teardown when the anchor leaves the document stays instant, and so does the tooltip disposer, which also ends an exit in progress. A tooltip shown again while the previous one is still exiting replaces the leaving node at once, so an anchor never has two tooltips. A popover or tooltip anchored inside an overlay now closes at that overlay's `close()`, instead of lingering until an exiting overlay's node is removed.
26
+ - **Bottom-sheet presentation for `kerfjs/overlay` dialogs.** `overlay()`, `confirm()`, `prompt()`, `form()`, and `choice()` take `presentation: 'bottom-sheet'` (the new `OverlayPresentation` type, named after `DialogSurface`'s prop). The wrapper gets `data-presentation="bottom-sheet"` on both the plain and `native` hosts. While the sheet is open, kerf follows `visualViewport` and publishes the virtual keyboard's overlap as `--kerf-keyboard-inset` on the wrapper. The value is cleared when the keyboard hides, while pinch-zoomed, and on close. kerf still ships no CSS: rest the sheet on the bottom edge and add `max(env(safe-area-inset-bottom), var(--kerf-keyboard-inset, 0px))` to its bottom padding (the API reference has the recipe). The default `'modal'` presentation is unchanged. Covered in Chromium, Firefox, and WebKit.
27
+ - **Media Detail recipe for `@kerfjs/ui`.** `?component=recipe-media-detail` composes an Apple-Music-style album page from components only: `NavStack` clear chrome with the scroll-edge material, a scoped `tint` from the artwork's bottom color, an `underChrome` `BackdropImage` with parallax and progressive blur fading into the tinted surface, and glass control groups. Under clear chrome, a `NavStack`'s own back control now sits in a glass control group, so it stays legible over imagery. The recipe has end-to-end coverage in Chromium, Firefox, and WebKit.
28
+ - **`BackdropImage` runs beneath clear chrome.** `underChrome` pulls the image up beneath the Pane's block-start inset: an overlay or clear header's clearance, NavStack chrome handed to a view's Pane, and the top safe area. Artwork fills the screen behind transparent chrome, as on a media detail page. The visible image keeps its `height` below the chrome and its `overlay` stays clear of it. `Pane` now publishes that inset as `--kui-pane-content-inset-block-start`, beside the inline insets.
29
+ - **Clear chrome for `@kerfjs/ui` layouts.** `Pane` `headerPresentation`, `NavStack` `chromePresentation` and `bottomToolbarPresentation`, and `TabNavigator` `barPresentation` take `clear`: an overlay bar with the same measuring and scroll clearance as `overlay`, but fully transparent, for chrome over imagery whose controls sit in glass `ToolbarControlGroup`s. A clear bar stays clear while content scrolls beneath it. `scrollEdge: 'material' | 'surface'` (on `Pane`, also in `PaneConfig`, and on the two layouts) fades the chrome material or the opaque surface in gradually instead, the iOS scroll-edge effect: fully clear at rest, fully in once content has scrolled `scrollEdgeDistance` pixels (default 60) beneath the bar, and only the backing's opacity changes. `wireScrollDividers` publishes the progress from the scroll it already tracks, so it works in every engine. Clear bars draw no scroll divider, turn opaque under reduced transparency or increased contrast, paint `Canvas` in forced colors, and snap on the scroll state instead of tracking it under the static-effects switch or on a slow-updating display. A `Toolbar`'s leading zone no longer clips the shadow of a glass control group.
30
+ - **Scoped tints for `@kerfjs/ui` layout surfaces.** `Pane` (and `PaneConfig`), `NavStack`, `TabNavigator`, and `TabNavigatorTab` take `tint: { color, scheme }`: an app color, typed with `@kerfjs/ui/css-values`, and the `'light'` or `'dark'` scheme it belongs to. Kerf does no color detection. The region paints the tinted surface, and its surface, raised surface, text, quiet text, link and brand text, quiet fills, chrome material, and popup rows re-derive from the color through `color-mix()` and relative color syntax. Lightness is clamped so text keeps WCAG AA over any color: a seed sweep across hue and lightness holds every text token at 4.5:1 or better in Chromium, Firefox, and WebKit, with and without the Web Awesome theme. The scope sets its `color-scheme` and Web Awesome scheme class, so Web Awesome components inside re-derive too. Under `prefers-contrast: more`, or without relative color syntax, it keeps the plain palette in the declared scheme.
31
+ - `@kerfjs/ui` `DialogSurface` gains `presentation="bottom-sheet"`: a dialog attached to the bottom edge of the screen, with square bottom corners, the top corners on the dialog radius, and a panel that runs behind the home indicator and the virtual keyboard while its content stays above them (the larger of the bottom safe area, the keyboard overlap measured from `visualViewport`, and `env(keyboard-inset-height)`). `height`, `maxHeight`, and the new `minHeight` size its visible height. The new `resizable` prop adds a top grabber, a focusable "Resize sheet" separator you drag or move with the arrow, Page Up/Down, Home, and End keys between `minHeight` and the viewport cap. The sheet slides up when it opens and down when it is dismissed; `enterMotion` / `exitMotion` (`"slide"` or `"standard"`) choose per transition, and reduced motion keeps Web Awesome's own animation. The centered `modal` presentation remains the default. The grabber, keyboard clearance, and slide need `@kerfjs/ui/surface-scaffold/register`.
32
+
9
33
  ### Fixed
10
34
 
35
+ - A `kerfjs/overlay` `initialFocus` that targets a custom element that cannot take focus yet no longer aborts the open. Before, a Web Awesome `wa-button` whose `focus()` throws before it has rendered made `overlay()` (and `confirm()`, `prompt()`, `form()`, `choice()`) roll back and rethrow. Now focus holds on the overlay itself, then moves to the element once it is defined and rendered, unless the overlay has closed or the user has moved focus in the meantime. The same applies to `initialFocus: true` when the first tab stop is such an element. Covered in Chromium, Firefox, and WebKit.
36
+ - `wireNavStack` keeps fallback focus on a pushed view with nothing focusable (an inert loading placeholder, say) across re-renders. The temporary `tabindex="-1"` it adds to the view was morphed away by the next render, which made the view unfocusable and dropped focus to the page body. It is now restored while the view holds that focus, and focus moves to the view's first focus target once content with one arrives under the same key. Focus the person moved elsewhere is left alone.
37
+ - Clearing a collapsible `TokenSearchField` with its ✕ button while focus is outside the field now collapses the emptied field and leaves focus where it was. Previously the clear re-expanded the field and claimed the caret, so it stayed open until it was focused and blurred again. A clear started from inside the field still returns the caret to the editor; that is now decided by where focus was when ✕ was pressed, which also covers WebKit, where the press drops focus to the page before the click.
38
+ - A `DialogSurface` bottom sheet with no `height` now opens at its content height, as documented. A modal dialog sits between both block insets, so the sheet's auto height stretched to its cap. The catalog's resizable sheet now relies on content height: its fixed height sat a few pixels short of the form and clipped the textarea's bottom border on a phone.
39
+ - A title over a `BackdropImage` `scrim` is now light in both color schemes. The scrim is dark in both schemes, so the overlay now takes the dark color scheme and default-tone titles and controls resolve their light foregrounds. `ToolbarText tone="dark"` is unchanged: it matches dark control groups on a loud-filled toolbar and inverts with the scheme, so the guidance no longer recommends it for photo-backed toolbars. Under reduced transparency or increased contrast, where the overlay sits on a solid `fadeTo` band, the overlay keeps the page's scheme.
11
40
  - `kerf-ui-doctor --full` and `kerf-ui-analyze` no longer report findings in gitignored, generated output. A local Playwright run's `playwright-report/` (its trace viewer ships CSS) produced about a hundred `KUI-L006` review findings in one app, so running the tests changed the doctor result. Inside a Git work tree, full traversal now skips every path the project's ignore rules ignore, across TypeScript, the isolated ESLint pass, analyzer discovery, and cache inputs; Playwright's `playwright-report`, `test-results`, and `blob-report` directories are also skipped by name, which covers projects outside Git.
12
41
  - A dark-tone `AnchoredSurface` (`tone: 'dark'`) now follows a live color-scheme switch while it is open. It picked its inverse scheme once, on open, so an OS preference change or an app theme toggled on an ancestor left the popup on the old side until it was reopened. It now flips with the page. `openAnchoredSurfaceAt` also reads the dark tone's scheme from `point.context`, the element under the pointer, instead of from the body or dialog that holds its temporary anchor, so a region with its own forced `color-scheme` is honored.
13
42
  - A keyboard move made as a long `PopupMenu` submenu (or menu) opens now leaves the chosen item in view, not just focused. When Web Awesome's late first-item focus landed after the move (say `End` pressed right after `ArrowRight`), it scrolled the menu back to the top, and kerf's restore refocused the chosen item without scrolling, so the focused last choice sat out of view below the clip. The restore now scrolls the item into view within its own menu, leaving the page alone.
@@ -102,6 +131,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
102
131
 
103
132
  ### Added
104
133
 
134
+ - **Typed background colors on `@kerfjs/ui` layout surfaces.** `Pane` takes `background`, a `CssColor` from `@kerfjs/ui/css-values` (`uiColor(...)`, `fillColor(...)`, or `colorVar(...)`). It paints the whole pane and replaces the default or sunken fill, and it is in `PaneConfig`, so Workbench panels, `CollapsiblePanel`, and `NavStack` view panes forward it. `NavStack` and `TabNavigator` take their own `background` for the stack or navigator surface and their docked bars, and each `TabNavigatorTab` takes one for its scene. Overlay chrome keeps the translucent material, and forced colors paint `Canvas`. Omitted, every surface keeps its current color.
135
+ - **Static-effects switch for `@kerfjs/ui`.** Set `data-kui-effects="static"` on the root element (`document.documentElement.dataset.kuiEffects = 'static'`) to turn every backdrop effect opaque and stop every scroll-driven animation. Use it when the app detects a low-power device itself, since CSS has no reliable signal for one. A slow-updating display (`@media (update: slow)`, such as e-ink) gets the same result automatically. The translucent chrome material (overlay `Pane` headers, `NavStack` and `TabNavigator` overlay bars, floating `DialogSurface` headers) resolves to the opaque surface, as it already does under reduced transparency.
136
+ - `ToolbarControlGroup` takes `material: 'solid' | 'glass'` (default `solid`). A `glass` group paints the translucent chrome material (`--kui-chrome-material`, blurred and saturated by `--kui-chrome-material-filter`), replaces its gray border with a specular edge highlight, and floats on a soft shadow. Use it for per-control glass pills over imagery or a clear (transparent) toolbar, for example inside a `FloatingToolbar`. Its controls take the normal text color, and a raised or outline selection paints an opaque surface pill. Rest, hover, and selected controls keep WCAG AA over worst-case content in light and dark: the bare material leaves the brand selected color at about 3.2:1 over black. The fill turns opaque under reduced transparency, increased contrast, static effects, or without `backdrop-filter`, and the edge gives way to the normal border there. A dark-toned glass group's loud fill turns opaque too. Forced colors paints `Canvas`/`CanvasText` with no filter or shadow. A borderless group ignores the material.
137
+ - **`BackdropImage`.** `@kerfjs/ui/backdrop-image` opens a scrolling Pane or NavStack view with a hero or tiled image (`src`/`srcset`/`alt`, `fit`, `position`, `repeat`, typed `height`). `parallax` (`subtle` or a 0 to 1 factor) lags the image on a compositor-only scroll-driven timeline with no scroll listener or `background-attachment: fixed`; a typed `fadeTo` color dissolves it through a mask and keeps painting under its `children`, so the page reads as one surface; `overlay` content sits over its block end with an optional `scrim`. It is static without scroll-driven animation support, under reduced motion, `data-kui-effects="static"`, or on slow-update displays, and ends on a hard edge over solid `fadeTo` under reduced transparency or increased contrast.
138
+ - **`BackdropImage` progressive blur.** `blur={{ radius?, blurredSrc?, dim? }}` stacks a copy of the image blurred once (a CSS `filter` of `radius`, default 24px, overhanging the block so its fringe is clipped) or an app-supplied pre-blurred `blurredSrc`, and fades only its opacity (plus an optional black `dim` layer) in over the first half of the image's scroll exit, so the blur is at full strength while most of the image is still in view; the filter never animates. A Chromium trace across 200 scroll frames recorded no `Paint` events and no per-frame raster. Static and sharp wherever the parallax is static.
105
139
  - `TabNavigator` takes `barPresentation: 'docked' | 'overlay'` (default `docked`). With `overlay`, the tab bar lies over the active scene on the translucent chrome material, so content scrolls visibly beneath it, as under an iOS tab bar. `wireTabNavigator` measures the live bar (bottom safe area included), and the scenes hand its height to their content as the block-end edge inset: a plain scene, a sole `Pane`, or a `NavStack` (whose own overlay bottom toolbar then sits just above the bar) rests its last item clear of it. Unselected and selected labels lift to keep AA over the material. The bar is opaque under reduced transparency, increased contrast, or without `backdrop-filter`, and paints `Canvas` with a `CanvasText` edge in forced colors. Unwired, the bar stays docked. Like the NavStack overlay bars, the tab bar ends where the active scene's classic (space-taking) scrollbar begins, so the thumb and buttons stay visible.
106
140
  - `@kerfjs/ui/wire-list-focus-scroll`'s `wireListFocusScroll(root)` brings a keyboard-focused row of a `scrollable` List fully into view, focus ring included, in every engine. Firefox's `focus()` leaves a partly hidden row alone until about half of it is hidden and then centers it, and Safari's never scrolls it, so arrow-key or Tab navigation could rest on a row that was mostly clipped. The wiring calls `scrollIntoView({ block: 'nearest' })` only when a `:focus-visible` element's ring crosses the List's clip, honoring its ring-room scroll padding; pointer focus is left alone, so a click never scrolls its row from under the pointer.
107
141
  - **`AnchoredSurface` dark tone for popups anchored to media chrome.** `openAnchoredSurface` and `openAnchoredSurfaceAt` take `tone: 'dark'`, the same loud, inverse tone as a `ToolbarControlGroup` or `ToolbarText` `tone="dark"`. A volume popup anchored to a dark media control group over a video preview is dark beside a light page and light beside a dark one, matching the group that opened it. The surface takes the opposite of its anchor's color scheme when it opens and is marked `data-tone="dark"`. Every semantic token is a `light-dark()` pair, so the content inherits the inverse text, border, and focus colors without tone props or overrides: a `ListItem` mute row and a `wa-slider` included. Measured on the catalog demo in Chromium, Firefox, and WebKit, the row label reads 12.8:1 and its focus ring 8.1:1 on a light page, and 16.8:1 and 4.0:1 on a dark one. The default tone is unchanged.
@@ -237,6 +271,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
237
271
 
238
272
  ### Changed
239
273
 
274
+ - **SVG data URLs are allowed where they load as images.** The URL screen now keeps `data:image/svg+xml` in `<img src>` and in SVG `<image href>` / `<image xlink:href>`, where the browser renders the SVG as an image in secure static mode and runs no script, on the JSX, `html` tagged template, and live-binding paths. Every other sink (`<iframe src>`, `<embed src>`, `<object data>`, `<a href>`, …) still drops it, and no other scheme or `data:` rule changes. Generated SVG artwork no longer needs a `blob:` URL or `raw()`.
275
+ - `@kerfjs/ui` dialog corners are now concentric with a pill `ToolbarControlGroup` (22px) resting at the body inset, in place of Web Awesome's 12px panel radius: 30px by default (a bare `wa-dialog`, `PopoverDialogSurface`, and `DialogSurface` with a `compact` or `none` body inset) and 38px for `bodyInset="comfortable"`. A side sheet's leading corners follow the same radius (`KF-1FNWQG`).
276
+
240
277
  - A resizable `@kerfjs/ui` `Workbench` panel without a `label` now names its resize handle in title style, as `ui/docs/writing.md` asks for region names: "Resize Left Rail", "Resize Right Rail", and "Resize Bottom Drawer" (previously "Resize left rail" and so on). A test or locator that matched the old accessible name needs the new one (`KF-NWS9GB`).
241
278
 
242
279
  - `kerf-ui-doctor`'s `KUI-D040` now also checks template literals passed to title-style props, judging only their static words: ``label={`Edit ${name} shortcut`}`` is reported with the suggestion `` `Edit ${name} Shortcut` ``. Interpolated values and words joined to an interpolation are never judged. The UX demos' dynamic control names follow it ("Edit … Shortcut", "Open Ticket …", "New Tab …"), and the NavStack demo's detail item is named by its visible title alone (`KF-8B6JES`).
package/ai/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "kerfjsVersion": "5.0.0-beta.100",
2
+ "kerfjsVersion": "5.0.0-beta.101",
3
3
  "files": [
4
4
  {
5
5
  "name": "skill",
@@ -1,6 +1,6 @@
1
1
  import { itemVersion } from './chunk-5WRGJZV6.js';
2
2
  import { seedRenderedAttrs, RENDERED_ATTRS, isUserAgentOwnedAttr, hasCustomTag, parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-6LXUC4ZY.js';
3
- import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-2MNLFTTW.js';
3
+ import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, wireRowBindings, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-VX3RCRGG.js';
4
4
  import { effect } from './chunk-IHMDBBUJ.js';
5
5
  import { LIST_MARKER_PREFIX, flattenWithoutListItems, collectLists, isOverlayHost, flatten } from './chunk-4TX6YJDM.js';
6
6
  import { devHooks } from './chunk-3C6F6NM4.js';
@@ -1809,4 +1809,4 @@ function resultToSegment(result) {
1809
1809
 
1810
1810
  export { captureFocus, each, morph, mount, moveNode, restoreFocus };
1811
1811
 
1812
- //# sourceMappingURL=chunk-ZNQSWFYU.js.map
1812
+ //# sourceMappingURL=chunk-NVNTUHAA.js.map