kerfjs 5.0.0-beta.52 → 5.0.0-beta.54
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 +738 -0
- package/README.md +4 -3
- package/ai/cursorrules +7 -6
- package/ai/manifest.json +11 -7
- package/ai/skill.md +7 -6
- package/dist/actions.js +3 -3
- package/dist/actions.js.map +1 -1
- package/dist/array-signal.js +1 -1
- package/dist/async.js +1 -1
- package/dist/attach.js +1 -68
- package/dist/attach.js.map +1 -1
- package/dist/{chunk-CEQMZYLR.js → chunk-3C6F6NM4.js} +1 -1
- package/dist/{chunk-CEQMZYLR.js.map → chunk-3C6F6NM4.js.map} +1 -1
- package/dist/{chunk-BDX3R4OM.js → chunk-4TX6YJDM.js} +8 -2
- package/dist/chunk-4TX6YJDM.js.map +1 -0
- package/dist/{chunk-ZOIERTUW.js → chunk-BI4HGICV.js} +3 -3
- package/dist/{chunk-ZOIERTUW.js.map → chunk-BI4HGICV.js.map} +1 -1
- package/dist/chunk-FM6N2B4N.js +70 -0
- package/dist/chunk-FM6N2B4N.js.map +1 -0
- package/dist/{chunk-ELXVRKY2.js → chunk-H7WTRM6F.js} +2 -2
- package/dist/{chunk-ELXVRKY2.js.map → chunk-H7WTRM6F.js.map} +1 -1
- package/dist/{chunk-Y2FOYPBV.js → chunk-IHMDBBUJ.js} +2 -2
- package/dist/chunk-IHMDBBUJ.js.map +1 -0
- package/dist/{chunk-3GSWKMBZ.js → chunk-UGNPDLNI.js} +7 -5
- package/dist/chunk-UGNPDLNI.js.map +1 -0
- package/dist/{chunk-H3TTM7PQ.js → chunk-WMRFXSLG.js} +112 -108
- package/dist/chunk-WMRFXSLG.js.map +1 -0
- package/dist/{chunk-E5R5GNKE.js → chunk-X6OQ2HTR.js} +2 -2
- package/dist/{chunk-E5R5GNKE.js.map → chunk-X6OQ2HTR.js.map} +1 -1
- package/dist/dev.d.ts +31 -13
- package/dist/dev.js +42 -23
- package/dist/dev.js.map +1 -1
- package/dist/html.js +3 -3
- package/dist/index.d.ts +12 -7
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/list.d.ts +3 -2
- package/dist/list.js +27 -13
- package/dist/list.js.map +1 -1
- package/dist/overlay.d.ts +12 -25
- package/dist/overlay.js +177 -35
- package/dist/overlay.js.map +1 -1
- package/dist/remount.js +2 -2
- package/dist/router.d.ts +13 -4
- package/dist/router.js +8 -4
- package/dist/router.js.map +1 -1
- package/dist/scope.d.ts +10 -2
- package/dist/scope.js +22 -28
- package/dist/scope.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/timing.d.ts +3 -2
- package/dist/timing.js +1 -1
- package/dist/timing.js.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/chunk-3GSWKMBZ.js.map +0 -1
- package/dist/chunk-BDX3R4OM.js.map +0 -1
- package/dist/chunk-H3TTM7PQ.js.map +0 -1
- package/dist/chunk-Y2FOYPBV.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,744 @@ 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`'s `wireTabBars` reveals a tab the application selects.** A
|
|
10
|
+
tab added and selected by the app in an overflowing `TabBar` stayed
|
|
11
|
+
off-screen, because the wiring revealed a selection only at wire-up, on
|
|
12
|
+
focus, and after its own keyboard/drag changes. A change of a bar's selected
|
|
13
|
+
tab now scrolls that bar's strip (never the page) to show it; re-renders that
|
|
14
|
+
keep the selection leave a user-scrolled strip alone.
|
|
15
|
+
|
|
16
|
+
- **`eslint-plugin-kerfjs`'s `ui-composition` rule resolves application
|
|
17
|
+
wrappers imported through a barrel.** A cataloged wrapper registered only by
|
|
18
|
+
its defining source file, so `import { DemandSegmentsControl } from
|
|
19
|
+
'./components/index.js'` (where `index.ts` re-exports it) was an unknown
|
|
20
|
+
element and reported `KUI-L202` in a toolbar zone despite its `rendersAs`.
|
|
21
|
+
Relative re-exports — `export { X } from`, `export { X as Y } from`,
|
|
22
|
+
`export * from`, and chains of them — are now followed to the cataloged
|
|
23
|
+
source file. Type-only and namespace re-exports and non-cataloged components
|
|
24
|
+
stay unresolved.
|
|
25
|
+
|
|
26
|
+
- **A collapsed `@kerfjs/ui` `Workbench` panel or `ResizableRegion` leaves
|
|
27
|
+
the accessibility tree.** Only its content was `inert`, so the labeled
|
|
28
|
+
`aside` / `section` stayed behind as an empty complementary or region
|
|
29
|
+
landmark that a screen-reader user could still land on. The collapsed
|
|
30
|
+
region itself now renders `inert` and `aria-hidden="true"`, like a
|
|
31
|
+
collapsed `CollapsiblePanel`; its separator was already hidden, and each
|
|
32
|
+
`restoreControl` renders outside it and stays reachable.
|
|
33
|
+
|
|
34
|
+
- **An expanded `@kerfjs/ui` `CollapsiblePanel` holds its size in a flex
|
|
35
|
+
layout.** The panel shrank like any flex item beside a work area with wide
|
|
36
|
+
content, while its content kept the panel's fixed size for the slide-out, so
|
|
37
|
+
the panel's edge clipped the content — in the collapsible-sidebar recipe at
|
|
38
|
+
1280px, the rail showed 211 of its 232px and cut off the selected Inbox
|
|
39
|
+
row's count. The panel is now `flex-shrink: 0`; the sibling work area gives
|
|
40
|
+
way instead.
|
|
41
|
+
|
|
42
|
+
- **`@kerfjs/ui` overlay panels span their host on the fixed axis.** The
|
|
43
|
+
85vw / 85vh overlay maximums (`--kui-resizable-region-overlay-max-*`,
|
|
44
|
+
`--kui-workbench-overlay-max-*`) applied to both axes, so an overlay bottom
|
|
45
|
+
drawer stopped about 15% short of its host's width and an overlay side rail
|
|
46
|
+
in a full-height layout stopped short of the bottom. Each maximum now caps
|
|
47
|
+
only the panel's resizable axis: side overlays cap their width, drawers
|
|
48
|
+
their height.
|
|
49
|
+
|
|
50
|
+
- **An overlay `@kerfjs/ui` `ResizableRegion` paints the surface.** With
|
|
51
|
+
`presentation="overlay"` the region floated over the work area with a
|
|
52
|
+
shadow but no background, so the content beneath — and a collapsed rail's
|
|
53
|
+
restore control stacked under it — showed through. It now paints
|
|
54
|
+
`--kui-color-surface`, like an overlay `CollapsiblePanel`; an inline region
|
|
55
|
+
stays transparent over its host.
|
|
56
|
+
|
|
57
|
+
- **A pinned `@kerfjs/ui` resize separator no longer overwrites the
|
|
58
|
+
remembered size.** When a container squeezes a `Workbench` rail, drawer, or
|
|
59
|
+
`ResizableRegion` below its `min` (or its reachable range collapses to one
|
|
60
|
+
size), the separator cannot move it, yet an arrow/Home/End key or a drag
|
|
61
|
+
still committed the configured `min` to the app's size signal and storage:
|
|
62
|
+
a rail remembered at 280px silently became 180px and came back at 180px
|
|
63
|
+
when the window widened. A pinned separator now commits nothing, so the
|
|
64
|
+
track returns at its remembered size once there is room.
|
|
65
|
+
|
|
66
|
+
- **A `pending` `@kerfjs/ui` `AppTab` is selectable and no longer dimmed.**
|
|
67
|
+
A known tab that is still opening now keeps its select button enabled and
|
|
68
|
+
in the roving tab order, so a user can switch to it and the application can
|
|
69
|
+
show placeholder content in its panel until loading completes. With
|
|
70
|
+
`webawesome.css` loaded, Web Awesome's native `button:disabled` no longer
|
|
71
|
+
dims the pending pill to 50%; it shows only its quiet name color and
|
|
72
|
+
trailing spinner, as documented. Close and dragging stay disabled while
|
|
73
|
+
pending, and it stays `aria-busy`. An unknown `placeholder` tab is
|
|
74
|
+
unchanged: disabled and out of the tab order.
|
|
75
|
+
|
|
76
|
+
- **Closing a `@kerfjs/ui` `Workbench` panel from its own control by mouse
|
|
77
|
+
returns focus in Safari.** Safari never focuses a clicked button, so
|
|
78
|
+
pressing an open panel's Hide control blurred the focused control to the
|
|
79
|
+
page before the click collapsed the panel, and `wireWorkbench` — which
|
|
80
|
+
returns focus only when it is inside the closing panel — left focus on the
|
|
81
|
+
page body instead of the toggle that opened it. A press that starts inside
|
|
82
|
+
the panel while focus is there now counts as focus inside, so the close
|
|
83
|
+
returns it; focus the user has moved to another element since is left
|
|
84
|
+
alone.
|
|
85
|
+
- **A collapsed `@kerfjs/ui` `CollapsiblePanel` or `ResizableRegion` leaves
|
|
86
|
+
the Tab order.** Both kept collapsed content in the DOM, only slid out and
|
|
87
|
+
clipped, so Tab still reached its controls; focusing one scrolled the clipped
|
|
88
|
+
panel, and a `CollapsiblePanel`'s `aria-hidden` put those focusable controls
|
|
89
|
+
inside a hidden subtree. A collapsed `CollapsiblePanel` now renders `inert`
|
|
90
|
+
beside its `aria-hidden`, and a collapsed `ResizableRegion` renders its
|
|
91
|
+
content wrapper `inert`, with no wiring needed. The slide-out still animates,
|
|
92
|
+
each `restoreControl` stays outside the inert subtree and reachable, and
|
|
93
|
+
`wireSidebar` still hands focus to the panel's toggle when it collapses from
|
|
94
|
+
inside.
|
|
95
|
+
|
|
96
|
+
- **A collapsed `@kerfjs/ui` `Workbench` rail's restore control no longer
|
|
97
|
+
covers an expanded bottom drawer.** A `leftRail` / `rightRail`
|
|
98
|
+
`restoreControl` floats in the Workbench's bottom corner, which the bottom
|
|
99
|
+
drawer's column reaches once the rail collapses, so with the drawer expanded
|
|
100
|
+
the control sat on the drawer's content. An expanded inline drawer now
|
|
101
|
+
publishes its top edge as a CSS anchor scoped to its Workbench, and the
|
|
102
|
+
rail's control floats the restore inset above it, returning to the corner
|
|
103
|
+
when the drawer collapses. An overlay drawer (static or `responsiveOverlayAt`)
|
|
104
|
+
still covers the control as every open overlay does, a nested Workbench's
|
|
105
|
+
drawer moves only its own rails' controls, and engines without anchor
|
|
106
|
+
positioning keep the corner.
|
|
107
|
+
|
|
108
|
+
- **A collapsed `@kerfjs/ui` `Workbench` panel's content leaves the Tab order.**
|
|
109
|
+
A collapsed rail or bottom drawer kept its content in the DOM, only slid out
|
|
110
|
+
and clipped, so Tab still reached its controls; focusing one (at a narrow
|
|
111
|
+
width, the collapsed inspector's or output drawer's own Hide control)
|
|
112
|
+
scrolled the clipped panel and slid its hidden content back over the work
|
|
113
|
+
area. The panel's content wrapper now renders `inert` while `collapsed`, with
|
|
114
|
+
no wiring needed, so neither Tab nor assistive technology reaches it; the
|
|
115
|
+
slide-out still animates and the `restoreControl`, which lives outside the
|
|
116
|
+
content, stays reachable.
|
|
117
|
+
|
|
118
|
+
- **A `kerfjs/overlay` popover or tooltip in a transformed or filtered modal
|
|
119
|
+
`<dialog>` now lands against its anchor.** A surface rendered into a
|
|
120
|
+
dialog's `[data-kerf-overlay-host][data-morph-skip]` slot is
|
|
121
|
+
`position: fixed`, and a dialog styled with `transform`, `filter`,
|
|
122
|
+
`perspective`, `contain`, or `will-change` becomes the containing block for
|
|
123
|
+
fixed descendants, so the surface used to land offset by the dialog's
|
|
124
|
+
position (and scale). `positionAnchored` (and so `autoReposition`,
|
|
125
|
+
`popover()`, and `tooltip()`) now measures the element's fixed-position
|
|
126
|
+
containing block with a transient 1px probe and maps its viewport
|
|
127
|
+
coordinates into it, compensating translation and scale. Rotated or skewed
|
|
128
|
+
containing blocks are not compensated.
|
|
129
|
+
- **The `@kerfjs/ui` UX catalog demonstrates a standalone restore control.**
|
|
130
|
+
The CollapsiblePanel route gains a "Restore control" example: a bottom
|
|
131
|
+
drawer that collapses to a `FloatingToolbar` `restoreControl` in its frame's
|
|
132
|
+
corner, wired with `wireSidebar` so the control restores the drawer and
|
|
133
|
+
focus moves between it and the drawer's own hide toggle.
|
|
134
|
+
|
|
135
|
+
- **A `@kerfjs/ui` collapsed rail's restore control no longer covers an open
|
|
136
|
+
bottom drawer beside it.** When a `CollapsiblePanel` or `ResizableRegion`
|
|
137
|
+
with a `restoreControl` shared its container with an expanded bottom
|
|
138
|
+
drawer — a bottom `CollapsiblePanel` or bottom `ResizableRegion` — as a
|
|
139
|
+
sibling or inside the app's work-area column, the control floated in the
|
|
140
|
+
container's bottom corner, on top of the drawer. It now floats the same
|
|
141
|
+
inset above the drawer's top edge, follows the drawer as it resizes, and
|
|
142
|
+
returns to the corner when the drawer collapses. A drawer nested deeper,
|
|
143
|
+
inside the work area's own content, does not move it. This uses CSS anchor
|
|
144
|
+
positioning; engines without it keep the container corner.
|
|
145
|
+
|
|
146
|
+
- **An open `@kerfjs/ui` `CollapsiblePanel` or `ResizableRegion` overlay now
|
|
147
|
+
covers other panels' restore controls, as a `Workbench` overlay does.** A
|
|
148
|
+
collapsed panel's `restoreControl` stacked at z-index 42, above
|
|
149
|
+
`wireSidebar`'s compact overlay and its backdrop and above an overlay
|
|
150
|
+
`ResizableRegion`, so it floated over an open overlay and stayed clickable
|
|
151
|
+
behind the overlay's focus trap. `--kui-collapsible-panel-restore-z` now
|
|
152
|
+
defaults to two less than `--kui-collapsible-panel-overlay-z` (38),
|
|
153
|
+
`--kui-resizable-region-restore-z` to two less than
|
|
154
|
+
`--kui-resizable-region-overlay-z` (39), and the compact backdrop's
|
|
155
|
+
`--kui-collapsible-panel-backdrop-z` to one less than the overlay (still
|
|
156
|
+
39), so an app that moves the overlay z-index keeps the order. When the
|
|
157
|
+
overlay closes, the control shows and works again.
|
|
158
|
+
- **An open `@kerfjs/ui` `Workbench` overlay now covers other panels'
|
|
159
|
+
restore controls.** Restore controls stacked above every overlay, so a
|
|
160
|
+
collapsed drawer's corner restore control floated over an open right-rail
|
|
161
|
+
overlay's content. An open overlay is now the top layer, like a sheet over
|
|
162
|
+
the view beneath it: `--kui-workbench-restore-z` defaults to two less than
|
|
163
|
+
`--kui-workbench-overlay-z` (39 by default, previously 42), below the
|
|
164
|
+
drawer and rail overlays. When the overlay closes, the control shows and
|
|
165
|
+
works again; a collapsed overlay passes pointer events through to it.
|
|
166
|
+
- **`@kerfjs/ui` `wireWorkbench` overlay panels now take focus and keep Tab
|
|
167
|
+
inside them,** like `wireSidebar`'s compact overlay (the ARIA dialog
|
|
168
|
+
pattern). An open overlay covers the work area, yet focus stayed on the
|
|
169
|
+
control that opened it and Tab walked through the covered editor. A wired
|
|
170
|
+
panel that opens while it presents as an overlay now focuses its first
|
|
171
|
+
focusable control (usually the close control in its header); while it is
|
|
172
|
+
open, Tab and Shift+Tab cycle through its controls, and Escape, its own
|
|
173
|
+
close control, or an outside press leaves it, returning focus to the
|
|
174
|
+
opener. Panels opening inline never move focus. Focus returned on close
|
|
175
|
+
also skips an opener inside another panel that has closed since.
|
|
176
|
+
`dismissOverlays: false` leaves all of this to the app.
|
|
177
|
+
- **`@kerfjs/ui` `Workbench` keeps its work-area minimum beside fixed rails
|
|
178
|
+
and above the bottom drawer.** `mainMinSize` used to apply only beside a
|
|
179
|
+
resizable rail, so two fixed 280px rails on a ~730px Workbench left the
|
|
180
|
+
editor 174px; now every inline rail, fixed or resizable, shrinks in
|
|
181
|
+
proportion (its content following the shown width) to keep the work area
|
|
182
|
+
`mainMinSize` px wide. A new `mainMinHeight` prop (default 120, `0` turns it
|
|
183
|
+
off) is the drawer's counterpart: an inline drawer, fixed or resizable,
|
|
184
|
+
shrinks when the Workbench gets shorter, and `wireWorkbench` stops a
|
|
185
|
+
resizable drawer where the work area would drop below that height (reported
|
|
186
|
+
in `aria-valuemax`) instead of only at the drawer's own `max`.
|
|
187
|
+
- **A `@kerfjs/ui` Workbench rail squeezed below its minimum now reports the
|
|
188
|
+
width it shows.** When the Workbench was too narrow for a rail's `min`
|
|
189
|
+
beside the work-area minimum, the rail showed about 172px while its
|
|
190
|
+
separator still announced `aria-valuenow="180"`. The separator now reports
|
|
191
|
+
the shown width, with `aria-valuemin` and `aria-valuemax` pinned to it: the
|
|
192
|
+
WAI-ARIA window splitter's value is the separator's actual position and must
|
|
193
|
+
sit inside its range, and the separator cannot move a rail the container
|
|
194
|
+
holds there. Resizing still commits no less than `min`, and the configured
|
|
195
|
+
range returns once there is room. A `ResizableRegion` a parent clamps below
|
|
196
|
+
its `min` reports the same way.
|
|
197
|
+
- **`KUI-L018` (`kerf-ui-analyze` / `kerf-ui-doctor`) now measures contrast and
|
|
198
|
+
follows the cascade.** When a `--wa-color-{tone}-fill-loud` override and its
|
|
199
|
+
`on-loud` are both literal colors (including `light-dark()`), a pair below
|
|
200
|
+
4.5:1 in either color scheme is reported with its ratio, and an unpaired
|
|
201
|
+
literal fill that still clears 4.5:1 against the theme's default `on-loud`
|
|
202
|
+
is no longer reported. An `on-loud` set in an ancestor scope (`:root`, or the
|
|
203
|
+
leading selector of a descendant chain) or in another stylesheet the same
|
|
204
|
+
entry loads now counts as the pair.
|
|
205
|
+
- **`kerf-component-catalog` lets a private application declare components
|
|
206
|
+
without a fake `exports` map.** In a private package
|
|
207
|
+
(`"private": true`), a component's `publicExports` items may omit `subpath`; the generator
|
|
208
|
+
verifies each name against the component's `source` file instead of
|
|
209
|
+
requiring a `./dist/<path>.js` export the bundled app never emits. Publishable
|
|
210
|
+
packages still need every subpath in `exports`. The v2 catalog schema makes
|
|
211
|
+
`subpath` optional to match, and the `kerfjs/ui-composition` rule resolves a
|
|
212
|
+
subpath-less export only by its source file.
|
|
213
|
+
- **`kerf-ui-doctor` and the `kerfjs/ui-public-boundaries` ESLint rule no
|
|
214
|
+
longer flag the documented `class="kui-app-root"` as KUI-L101 private
|
|
215
|
+
anatomy.** The `@kerfjs/ui` component catalog now has a `document-baseline`
|
|
216
|
+
composition entry for `@kerfjs/ui/document.css` that lists `kui-app-root`
|
|
217
|
+
as its public class, with a catalog demo route showing a `SplitView` filling
|
|
218
|
+
the root's definite height.
|
|
219
|
+
- The `@kerfjs/ui` heading guidance (`ai/skill.md`, `llms.txt`,
|
|
220
|
+
`docs/component-selection.md`, `docs/layout.md`) now sizes a panel title to
|
|
221
|
+
its track: extra-large `ToolbarText` stays the title size for a page, view,
|
|
222
|
+
dialog, or pane that holds it, while a narrow peripheral rail or drawer
|
|
223
|
+
names itself with the default size, since extra-large truncated "Inspector"
|
|
224
|
+
to "Ins…" in a 160px Workbench rail.
|
|
225
|
+
- **A `@kerfjs/ui` `Workbench` panel open when `wireWorkbench` wired up no
|
|
226
|
+
longer drops focus to the page when it closes from inside.** Such a panel
|
|
227
|
+
had no recorded opener, so without a restore control closing it from its
|
|
228
|
+
own header left focus on `<body>`. Focus now falls back to a control
|
|
229
|
+
outside the panel whose `aria-controls` names it (or an element inside it).
|
|
230
|
+
Each panel renders a stable `id` for that — `<id>-left-rail`,
|
|
231
|
+
`<id>-right-rail`, and `<id>-bottom-drawer` — so a toggle can name the
|
|
232
|
+
panel it controls.
|
|
233
|
+
- **`@kerfjs/ui` `wireWorkbench` keeps overlay panels exclusive.** At a narrow
|
|
234
|
+
width two overlay rails could both be open and, together wider than the
|
|
235
|
+
Workbench, the right rail covered the left rail's own close control. Opening
|
|
236
|
+
a panel while it presents as an overlay now closes every other open overlay
|
|
237
|
+
panel, the bottom drawer included, like `wireSidebar`'s compact overlays;
|
|
238
|
+
panels presenting inline are never closed by it. New option
|
|
239
|
+
`exclusiveOverlays` (default `true`); pass `false` to let overlays stay open
|
|
240
|
+
together.
|
|
241
|
+
- **A `@kerfjs/ui` `CollapsiblePanel` or `ResizableRegion` restore control
|
|
242
|
+
stays inside its container.** `restoreControl` was `position: fixed` to the
|
|
243
|
+
viewport, so a panel or region embedded in a page floated it over unrelated
|
|
244
|
+
page content. It now floats in the `restorePosition` corner of the element
|
|
245
|
+
that holds the panel (or region), scrolls with it, and is inset by the
|
|
246
|
+
component's restore inset plus the unsafe area of the edges that container
|
|
247
|
+
reaches, so a full-viewport shell still places it clear of them. While the
|
|
248
|
+
control is shown, that container isolates its stacking and becomes its
|
|
249
|
+
containing block; an app that positions the container itself keeps its own
|
|
250
|
+
`position`. As in `Workbench`, the corner owns the inset, so a
|
|
251
|
+
`FloatingToolbar` hosting the control no longer doubles it.
|
|
252
|
+
- The `@kerfjs/ui` UX catalog's Workbench page gains a responsive overlay
|
|
253
|
+
drawer example: an output drawer with `responsiveOverlayAt: "narrow"` that
|
|
254
|
+
takes its own track beside a wide editor and becomes a transient overlay
|
|
255
|
+
over the editor's bottom once the Workbench is 704px or narrower, starting
|
|
256
|
+
hidden there and closing from its own header, on Escape, or on a press
|
|
257
|
+
outside it. The Workbench guide shows the pattern.
|
|
258
|
+
- A `@kerfjs/ui` `Workbench` now stacks overlay rails above an overlay bottom
|
|
259
|
+
drawer. With the drawer and a rail both presented as overlays, their order
|
|
260
|
+
followed the markup, so the drawer covered the left rail while the right
|
|
261
|
+
rail covered the drawer. Both rails now stack above the drawer, mirroring
|
|
262
|
+
the inline layout where rails span the full height beside the drawer's
|
|
263
|
+
column, and the right rail stacks above the left; restore controls stay
|
|
264
|
+
above every overlay. The drawer's overlay z-index is now one less than
|
|
265
|
+
`--kui-workbench-overlay-z`.
|
|
266
|
+
- **A `@kerfjs/ui` `Workbench` overlay rail can always be closed from inside
|
|
267
|
+
itself.** At a narrow width, the UX catalog's inspector overlay covered the
|
|
268
|
+
editor toolbar's "Hide inspector" toggle, leaving only Escape or an outside
|
|
269
|
+
press to close it. The catalog's resizable rails now carry their own close
|
|
270
|
+
control in their `Pane` header, and the Workbench guide recommends the same
|
|
271
|
+
for any panel that may present as an overlay. `wireWorkbench` now returns
|
|
272
|
+
focus stranded in a panel however the panel closed — including through the
|
|
273
|
+
app's own control inside it, and for inline panels — to the control that
|
|
274
|
+
opened it, else to its restore control; before, only an Escape, an outside
|
|
275
|
+
press, or a breakpoint collapse returned it.
|
|
276
|
+
- **A `@kerfjs/ui` `Workbench` restore control stays inside its Workbench.**
|
|
277
|
+
`restoreControl` was `position: fixed` to the viewport, so an embedded
|
|
278
|
+
Workbench (the UX catalog's, for one) floated it over the page footer. It is
|
|
279
|
+
now anchored to a corner of the Workbench itself — the drawer's in the
|
|
280
|
+
corner of the work-area column, so it never covers an expanded rail — inset
|
|
281
|
+
by `--kui-workbench-restore-inset` plus the unsafe area of the edges that
|
|
282
|
+
corner reaches, and it scrolls with the Workbench. The Workbench now
|
|
283
|
+
isolates its own stacking, so its overlays and restore controls never stack
|
|
284
|
+
against the page around it. A `FloatingToolbar` can host the control
|
|
285
|
+
without doubling the inset: the corner owns it, and `FloatingToolbar` now
|
|
286
|
+
honors `--kui-floating-toolbar-inset` set on an ancestor as well as on
|
|
287
|
+
itself. The catalog's collapsed console restores from such a toolbar.
|
|
288
|
+
- **`@kerfjs/ui` `wireWorkbench` makes overlay panels transient.** A rail
|
|
289
|
+
whose `responsiveOverlayAt` breakpoint applied stayed expanded over the
|
|
290
|
+
editor until the user found its toggle. Pass a panel's app-owned
|
|
291
|
+
`collapsed` signal (`panels: { leftRail: { collapsed } }`; `size` is now
|
|
292
|
+
optional, so a panel need not be resizable) and, like `wireSidebar`'s
|
|
293
|
+
compact overlay, the wiring collapses it when the breakpoint begins to
|
|
294
|
+
apply (without the collapse motion) and gives back its inline state when
|
|
295
|
+
the breakpoint stops applying or the wiring is disposed. An open overlay
|
|
296
|
+
panel, responsive or `presentation: "overlay"`, closes on Escape or on a
|
|
297
|
+
press that starts and ends outside it, and focus stranded inside returns to
|
|
298
|
+
the control that opened it, else to its restore control. It is on by
|
|
299
|
+
default for panels given `collapsed`; `dismissOverlays: false` turns it
|
|
300
|
+
off. The UX catalog's resizable example wires its rails this way.
|
|
301
|
+
- A `@kerfjs/ui` `Workbench` rail with `presentation: "overlay"` now renders
|
|
302
|
+
exactly its size. Out of flow, it sized to its content plus its 1px
|
|
303
|
+
separator border, so a 280px rail measured 281px; it now takes its size
|
|
304
|
+
explicitly, as the responsive overlays do. Rails and the drawer are also
|
|
305
|
+
`border-box` now, so a panel's `size` is its whole track, border included,
|
|
306
|
+
even in an app without a global `box-sizing` reset, and overlay content
|
|
307
|
+
fills the panel inside its separator instead of painting over it.
|
|
308
|
+
- **A `@kerfjs/ui` `Workbench` bottom drawer with `presentation: "overlay"`
|
|
309
|
+
is visible again.** Its content is positioned against the drawer's bottom
|
|
310
|
+
edge, so the out-of-flow overlay box had no height of its own and collapsed
|
|
311
|
+
to its 1px border. An overlay drawer now takes its configured height (220px
|
|
312
|
+
by default, or its `size`), covers the bottom of the work-area column, and
|
|
313
|
+
clamps its content to the overlay maximum height. The drawer also accepts
|
|
314
|
+
`responsiveOverlayAt: "narrow" | "compact"` like the rails: below that
|
|
315
|
+
Workbench width it presents as the same overlay, without a separator and
|
|
316
|
+
never resizable, while the work area takes the full height and keeps its
|
|
317
|
+
bottom safe-area inset beneath it.
|
|
318
|
+
- The bundled AI assistant configs (`ai/skill.md`, `ai/cursorrules`) now
|
|
319
|
+
explain the `data-kerf-overlay-host` opt-in next to the one-`mount()`-per-root
|
|
320
|
+
rule: a modal `<dialog>` of your own that anchors kerf popovers or tooltips
|
|
321
|
+
needs `<div data-kerf-overlay-host data-morph-skip></div>`, the one allowed
|
|
322
|
+
nested-mount boundary. The `kerfjs/ai-assistant-configs` lint rule reports
|
|
323
|
+
installed copies as stale until they are refreshed.
|
|
324
|
+
- The opt-in `rebuiltListeners` dev warning no longer reports a popover or
|
|
325
|
+
tooltip closing inside a modal dialog's host slot. Removals inside a
|
|
326
|
+
`[data-kerf-overlay-host][data-morph-skip]` slot belong to the surface's own
|
|
327
|
+
mount, so the enclosing mount now ignores them, as its re-renders already
|
|
328
|
+
did. A listener lost to a re-render inside the surface is still reported,
|
|
329
|
+
once.
|
|
330
|
+
- **A popover or tooltip in a modal `<dialog>` no longer lingers after the
|
|
331
|
+
dialog is removed when its anchor was moved out first.** A surface rendered
|
|
332
|
+
into a dialog's host slot left with the dialog, but because its anchor was
|
|
333
|
+
still in the document, kerf kept it registered as open, and a tooltip on
|
|
334
|
+
that anchor never showed again. A slot-hosted surface now closes itself
|
|
335
|
+
(without calling `onDismiss`) when it leaves the document, and the tooltip
|
|
336
|
+
shows again on the next hover or focus.
|
|
337
|
+
- **A popover inside a modal `<dialog>` is reachable by Tab in Safari on
|
|
338
|
+
macOS.** A popover or tooltip rendered into a dialog's host slot follows the
|
|
339
|
+
dialog's own Tab order, and WebKit on macOS skips buttons and links there
|
|
340
|
+
unless the system keyboard-navigation setting is on. kerf now gives each
|
|
341
|
+
implicitly tabbable control in a slot-hosted surface an explicit
|
|
342
|
+
`tabindex="0"` when it opens and again before each Tab keypress, the same
|
|
343
|
+
way its focus trap already did. Authored tabindexes, disabled controls, and
|
|
344
|
+
surfaces outside a slot are left alone.
|
|
345
|
+
- `@kerfjs/ui` `Workbench` rails gain `responsiveOverlayAt: "narrow" |
|
|
346
|
+
"compact"`, mirroring `ResizableRegion`'s `responsiveFillAt`: below the
|
|
347
|
+
Workbench's own 704px (`narrow`) or 448px (`compact`) width the rail
|
|
348
|
+
presents as an overlay over the work area, without a separator and never
|
|
349
|
+
resizable, and inline above it — the CSS decides, so apps no longer derive
|
|
350
|
+
`presentation` from a `deviceClass()` check. The bottom drawer ignores it.
|
|
351
|
+
A collapsed overlay rail (with `presentation: "overlay"` too) now drops its
|
|
352
|
+
surface and shadow instead of leaving an empty panel-sized box over the work
|
|
353
|
+
area. The UX catalog's resizable example uses it and now renders at phone
|
|
354
|
+
widths, and its collapsed console's restore control is a `ToolbarControlGroup`
|
|
355
|
+
toggle that actually restores the console.
|
|
356
|
+
- `@kerfjs/ui` `Workbench` keeps a minimum work area beside resizable rails.
|
|
357
|
+
Two rails dragged near their 480px maximum on a ~1024px Workbench used to
|
|
358
|
+
leave the editor a sliver; now the work area keeps at least `mainMinSize`
|
|
359
|
+
px (a new `Workbench` prop, default 320, `0` turns it off). `wireWorkbench`
|
|
360
|
+
stops drag and keyboard resizing where the work area would drop below it
|
|
361
|
+
(and reports that reachable maximum in `aria-valuemax`), and when the
|
|
362
|
+
Workbench narrows, resizable rails shrink in proportion to their sizes, with
|
|
363
|
+
their content following the shown width, instead of squeezing the work
|
|
364
|
+
area. Workbenches without a resizable rail render exactly as before. The UX
|
|
365
|
+
catalog's resizable example adds a resizable inspector to show it.
|
|
366
|
+
- `@kerfjs/ui`'s static analyzer (`kerf-ui-analyze`, and the analyzer stage of
|
|
367
|
+
`kerf-ui-doctor`) reports a new review finding, `KUI-L018`, when a
|
|
368
|
+
stylesheet overrides a loud tone fill (`--wa-color-{tone}-fill-loud` for
|
|
369
|
+
neutral, brand, success, warning, danger, or pop) without setting the
|
|
370
|
+
matching `--wa-color-{tone}-on-loud` in the same scope. The theme pairs each
|
|
371
|
+
loud fill with a foreground that clears WCAG AA, and an unpaired override
|
|
372
|
+
keeps that foreground on a fill it was never checked against. The pair must
|
|
373
|
+
share a selector (after resolving nesting and splitting selector lists) and
|
|
374
|
+
at-rule scope in the same stylesheet, in one rule or separate ones; another
|
|
375
|
+
tone's `on-loud` does not count. It is a review finding because it checks
|
|
376
|
+
the pairing, not the contrast; pass `--fail-on-review` to gate it. The
|
|
377
|
+
`webawesome-theme.md` customization example now sets both halves of each
|
|
378
|
+
pair.
|
|
379
|
+
- **Tooltips and popovers also stay visible when your own code opens a modal
|
|
380
|
+
`<dialog>`.** `kerfjs/overlay` already moved every open tooltip and popover
|
|
381
|
+
above a modal dialog it opened itself, but a dialog opened with a bare
|
|
382
|
+
`dialog.showModal()` went unnoticed and left them inert beneath it. While
|
|
383
|
+
any kerf surface is open, one capture-phase listener on the document now
|
|
384
|
+
catches the dialog's `toggle` event and re-hosts them the same way, one task
|
|
385
|
+
after the `showModal()` call. The listener is removed once nothing is open.
|
|
386
|
+
An engine that fires no `toggle` event for `<dialog>` still misses such a
|
|
387
|
+
dialog; open it through `overlay()` there.
|
|
388
|
+
- **A popover inside a modal `<dialog>` is clickable.** Browsers make
|
|
389
|
+
everything outside an open modal dialog inert, so a popover lifted above the
|
|
390
|
+
dialog was visible but its buttons could not be clicked or focused, and
|
|
391
|
+
kerf's one-mount-per-tree rule kept it from rendering inside a kerf-mounted
|
|
392
|
+
dialog. `popover()` and `tooltip()` whose anchor sits inside an open modal
|
|
393
|
+
`<dialog>` now render into the dialog's host slot, an element marked
|
|
394
|
+
`data-kerf-overlay-host data-morph-skip`, so they are part of the modal and
|
|
395
|
+
fully interactive: Tab reaches them, and Escape closes the popover before the
|
|
396
|
+
dialog. A dialog `kerfjs/overlay` opened gets the slot automatically (an
|
|
397
|
+
empty `display: contents` element added on first use). For your own
|
|
398
|
+
`<dialog>`, add `<div data-kerf-overlay-host data-morph-skip></div>` to its
|
|
399
|
+
markup, or pass `container`. The slot is the one place a second `mount()`
|
|
400
|
+
may live inside a mounted tree: the enclosing mount's morph, nesting guard,
|
|
401
|
+
and binding and `each()` marker scans all stop there. The `kerfjs/dev`
|
|
402
|
+
`inert` warning now fires only when no slot was used.
|
|
403
|
+
- `@kerfjs/ui` `Workbench` rails and the bottom drawer can be drag-resized,
|
|
404
|
+
as an opt-in per panel. `WorkbenchPanel` gains `resizable: true | { min, max }`
|
|
405
|
+
(off by default, so existing Workbenches render unchanged); a resizable panel
|
|
406
|
+
gets a separator on its inner edge with the same focusable
|
|
407
|
+
`role="separator"` contract, grip, and 20px hit target as a
|
|
408
|
+
`ResizableRegion` handle. The new `@kerfjs/ui/wire-workbench` subpath's
|
|
409
|
+
`wireWorkbench(root, { id, panels, deviceClass?, storage?, onResize? })`
|
|
410
|
+
drives it: live pointer drags, arrow / Shift+arrow / Home / End keys,
|
|
411
|
+
clamping to the limits, and commits to the app-owned size signals, with
|
|
412
|
+
optional `storageKey` persistence. Collapsing keeps a panel's size, so it
|
|
413
|
+
expands back to the size it had, and resizing is suspended on compact device
|
|
414
|
+
classes and for overlay or hidden panels. The UX catalog's Workbench page
|
|
415
|
+
adds a resizable example. `wireResizableRegions` now drives only handles that
|
|
416
|
+
are direct children of a `ResizableRegion`, so it never double-drives a
|
|
417
|
+
Workbench separator under the same root.
|
|
418
|
+
- `@kerfjs/ui` elevation shadows follow the color scheme, with or without the
|
|
419
|
+
optional `@kerfjs/ui/webawesome.css` theme. Kerf's theme layer set
|
|
420
|
+
`--wa-shadow-s` / `-m` / `-l` to fixed light-mode shadows (14%, 11%, and 9%
|
|
421
|
+
black), which all but vanished against the dark page, and the
|
|
422
|
+
`--kui-shadow-s` / `--kui-shadow-l` foundation fallbacks copied those fixed
|
|
423
|
+
values. The theme now pairs each light alpha with a stronger dark one (50%,
|
|
424
|
+
40%, 32%) through `light-dark()`, and the foundation fallbacks mirror them, so
|
|
425
|
+
selected segments and app tabs, compact overlay panels, and Web Awesome
|
|
426
|
+
surfaces that read `--wa-shadow-*` keep their elevation in dark mode. Light mode is unchanged. The browser parity test
|
|
427
|
+
now compares every `--kui-shadow-*` token with and without the theme in both
|
|
428
|
+
schemes.
|
|
429
|
+
- `@kerfjs/ui` text on loud tone fills clears WCAG AA in both color schemes,
|
|
430
|
+
with or without the optional `@kerfjs/ui/webawesome.css` theme. White
|
|
431
|
+
`on-loud` text reached about 2.2:1 on the success fill, 3.5:1 on the light
|
|
432
|
+
brand fill (`#0088ff`), and 3.6:1 on the danger fill (`#ff383c`) in both
|
|
433
|
+
schemes. Kerf's theme layer and the foundation fallbacks now pair each
|
|
434
|
+
fill with a foreground chosen to keep the tone's look: success keeps its
|
|
435
|
+
bright green with dark text (`#1d1d1f`, 7.6:1), as warning already did; light
|
|
436
|
+
brand and danger keep white text on a slightly deeper fill of the same hue
|
|
437
|
+
(`#0075db`, 4.6:1; `#eb0005`, 4.6:1); dark danger keeps its fill with dark
|
|
438
|
+
text (`#111113`, 5.3:1), as dark brand already did. Web Awesome accent
|
|
439
|
+
buttons, badges, and checked controls pick this up, and accent `wa-badge`s now
|
|
440
|
+
use their variant's `on-loud` foreground by default instead of a fixed dark
|
|
441
|
+
color (`--kui-wa-badge-accent-foreground` still overrides it). The browser
|
|
442
|
+
contrast test asserts every tone's `on-loud` over `fill-loud` pair, including
|
|
443
|
+
pop and neutral, in both schemes with and without the theme.
|
|
444
|
+
- `@kerfjs/ui` brand, success, and warning text clears WCAG AA on the lowered
|
|
445
|
+
page background in light mode, with or without the optional
|
|
446
|
+
`@kerfjs/ui/webawesome.css` theme. Their light `on-quiet` values (`#1e6ef4`,
|
|
447
|
+
`#008932`, `#a16a00`) passed on the white surface but reached only about
|
|
448
|
+
4.1:1 on `#f2f2f7`. Kerf's theme layer and the foundation fallbacks now use
|
|
449
|
+
the same hues a step darker: `#0c62f3` (4.64:1), `#007f2e` (4.62:1), and
|
|
450
|
+
`#966200` (4.65:1) on the page, about 5.2:1 on white. Links, selected tabs,
|
|
451
|
+
segmented and toolbar selections, and token-search accents that read
|
|
452
|
+
`brand-on-quiet` darken with it. Dark mode is unchanged. The browser contrast
|
|
453
|
+
test no longer skips light text over the page background.
|
|
454
|
+
- `@kerfjs/ui` surfaces, links, and focus rings match the optional
|
|
455
|
+
`@kerfjs/ui/webawesome.css` theme without it. Three foundation fallbacks
|
|
456
|
+
pointed at a different token than the theme: `--kui-color-surface-raised`
|
|
457
|
+
fell back to the surface color (the theme's dark raised surface is
|
|
458
|
+
`#2c2c2e`), `--kui-color-text-link` fell back to the brand fill (`#0088ff`,
|
|
459
|
+
about 3.5:1 on white) instead of the brand on-quiet text color, and the focus
|
|
460
|
+
ring used the brand fill instead of the theme's focus color (`#007aff`). Each
|
|
461
|
+
now falls back to the theme's value. The browser parity test now covers every
|
|
462
|
+
`--kui-color-*` token the foundation declares, plus the focus ring, instead of
|
|
463
|
+
only the tone tokens.
|
|
464
|
+
- The `@kerfjs/ui` UX catalog's `StateBanner` route adds a placeholder with a
|
|
465
|
+
badge for every tone, next to the existing badge-less placeholder, so visual
|
|
466
|
+
review and captures show the solid badge's loading skeleton on each tone's
|
|
467
|
+
fill. A browser test checks that each tone's badged placeholder is present.
|
|
468
|
+
- `@kerfjs/ui` toned text stays readable in dark mode without the optional
|
|
469
|
+
`@kerfjs/ui/webawesome.css` theme. The foundation's fallbacks for the
|
|
470
|
+
success, warning, and danger `on-quiet` and `on-normal` tokens were single
|
|
471
|
+
light-mode colors, so dark danger `Text` rendered dark red on a dark surface
|
|
472
|
+
(under 3:1). Every tone token now falls back to the same light and dark
|
|
473
|
+
pair the theme assigns, including `brand-on-loud` (dark text on the pale dark
|
|
474
|
+
brand fill instead of white) and `neutral-border-quiet`. Light-mode colors
|
|
475
|
+
are unchanged. A browser test bundles the package with and without the theme
|
|
476
|
+
and asserts that every tone token resolves identically in both color schemes
|
|
477
|
+
and that toned text clears WCAG AA in dark mode.
|
|
478
|
+
- `@kerfjs/ui` danger text and badges are readable in dark mode, and every
|
|
479
|
+
tone's `StateBanner` detail and quiet or outline `Badge` text now clears
|
|
480
|
+
WCAG AA. The danger tone was the only one without an on-fill foreground: its
|
|
481
|
+
solid badge fill and `StateBanner` text reused `danger-on-quiet`, a mid red
|
|
482
|
+
in dark mode (banner detail 4.0:1, badge placeholder skeleton 2.25:1). A new
|
|
483
|
+
`--kui-color-danger-on-fill` token (also accepted by `uiColor()`) matches the
|
|
484
|
+
other tones' pale dark values, lifting the dark danger banner text to 8.2:1.
|
|
485
|
+
The banner detail no longer dims to 78% opacity, which left the light info,
|
|
486
|
+
success, warning, and danger details at 3.4 to 4.3:1; it reads as secondary
|
|
487
|
+
through its smaller type, and the neutral detail uses the quiet text color.
|
|
488
|
+
Quiet and outline badges use their tone's on-fill text, lifting light brand,
|
|
489
|
+
success, and warning from about 4.1:1 to at least 5:1.
|
|
490
|
+
|
|
491
|
+
- A `@kerfjs/ui` placeholder skeleton inside a solid `Badge`, such as the
|
|
492
|
+
badge of a `StateBanner` placeholder, is now clearly visible. The generic
|
|
493
|
+
skeleton tint, 12% of the text color, almost disappeared on the badge's
|
|
494
|
+
saturated fill in every tone. A solid badge now tints its skeleton from its
|
|
495
|
+
own foreground color, which contrasts with the fill in every tone in light
|
|
496
|
+
and dark mode. Quiet and outline badges keep the generic tint, which already
|
|
497
|
+
reads on their pale fills. Geometry is unchanged.
|
|
498
|
+
- The `@kerfjs/ui` SVG design templates render badges as filled pills: the
|
|
499
|
+
`StateBanner` badge and the `ListHeader` and `List` count pills used to show
|
|
500
|
+
as plain text, because the template build loaded only a hand-written list of
|
|
501
|
+
stylesheets that omitted `badge.css`. The build now loads each component's
|
|
502
|
+
full stylesheet set from its generated browser entry, the same set a
|
|
503
|
+
consumer's browser import loads, so a nested component's CSS can no longer be
|
|
504
|
+
missed. The `ListHeader` and `List` templates also pick up the label inset
|
|
505
|
+
from `text.css`, so a header label now lines up with the list items below it,
|
|
506
|
+
as it does in the live component.
|
|
507
|
+
- A `@kerfjs/ui` `StateBanner` placeholder's badge sits at the live badge's
|
|
508
|
+
height on every platform. With a skeleton instead of text, the badge took a
|
|
509
|
+
baseline from the skeleton's bottom edge and sat about a pixel higher, by an
|
|
510
|
+
amount that depended on the platform's font metrics.
|
|
511
|
+
- A `@kerfjs/ui` `StateBanner` with a long, wrapping title keeps its badge
|
|
512
|
+
one item gap after the title's last word, and its detail uses the full copy
|
|
513
|
+
width. The wide flex layout shrank the title and detail together, which left
|
|
514
|
+
a large gap before the badge and truncated the detail early. The title,
|
|
515
|
+
badge, and detail now flow as inline text: the detail stays beside the title
|
|
516
|
+
while it fits whole, and otherwise moves to its own line, truncating only at
|
|
517
|
+
the full width. A wrapped title's lines stay evenly spaced.
|
|
518
|
+
- A `@kerfjs/ui` `ListHeader` disclosure toggle disabled with `actionDisabled`
|
|
519
|
+
is dimmed by the component itself, at the same tone as a disabled action
|
|
520
|
+
button, and shows the `not-allowed` cursor. It used to rely on Web Awesome's
|
|
521
|
+
native `button:disabled` dimming, so without `webawesome.css` it looked
|
|
522
|
+
enabled. Disabled toggles and action buttons also no longer show hover
|
|
523
|
+
feedback without `webawesome.css`.
|
|
524
|
+
- A `@kerfjs/ui` `ListActionRow` placeholder keeps its `busy` spinner. The
|
|
525
|
+
whole status line used to collapse into one skeleton, dropping the spinner
|
|
526
|
+
the component draws from a known prop. Now only the `status` text becomes a
|
|
527
|
+
skeleton, matching `ListItem`.
|
|
528
|
+
- **A tooltip or popover already showing stays visible when a modal opens.**
|
|
529
|
+
Lifting a surface above an open modal `<dialog>` was decided only when the
|
|
530
|
+
surface opened, so a tooltip or popover already on screen when a modal
|
|
531
|
+
dialog opened was left inert beneath it. Whenever `kerfjs/overlay` opens a
|
|
532
|
+
modal `<dialog>` (a `native: true` modal, or one it lifted), it now moves
|
|
533
|
+
every open tooltip and popover into the top layer above the new dialog. One
|
|
534
|
+
that is already a `[popover]` is re-shown so it stacks on top. The existing
|
|
535
|
+
`kerfjs/dev` warnings still report an engine without the Popover API, and a
|
|
536
|
+
surface with controls the modal keeps inert. A modal `<dialog>` the app
|
|
537
|
+
opens itself, outside kerf, is not observed.
|
|
538
|
+
- Moved `mount()`'s list-binding lifecycle (turning list markers into
|
|
539
|
+
bindings, first-render row-contract validation, orphan pruning) into its own
|
|
540
|
+
internal module, so `mount.ts` holds only the render orchestration. No
|
|
541
|
+
behavior or public API change.
|
|
542
|
+
- A narrow (576px and below) `@kerfjs/ui` `StateBanner` keeps its badge
|
|
543
|
+
sized to its content, one item gap after the title and on the title's
|
|
544
|
+
baseline, as in the wide layout. The narrow grid stretched the badge across
|
|
545
|
+
the free column into a wide pill that sat less than 1px from the title.
|
|
546
|
+
- Hovering a `@kerfjs/ui` `ListActionRow` placeholder with
|
|
547
|
+
`trailingActionVisibility="interaction"` no longer reveals its disabled
|
|
548
|
+
trailing button. A placeholder is inert, so it keeps the live row's resting
|
|
549
|
+
state, with the trailing action hidden until an interaction it cannot take.
|
|
550
|
+
- A `@kerfjs/ui` `ListItem` placeholder keeps its `busy` spinner. The whole
|
|
551
|
+
trailing slot used to collapse into one skeleton, dropping the spinner the
|
|
552
|
+
component draws from a known prop and shifting the label's width. Now only
|
|
553
|
+
the values in that slot become skeletons: `status` text in its status line
|
|
554
|
+
and author-supplied `trailing` content (always a value, since the component
|
|
555
|
+
cannot tell a chevron from a count) each get their own.
|
|
556
|
+
- `@kerfjs/ui` placeholders keep the live disabled tone of a control the
|
|
557
|
+
author disabled. A disabled `ListItem` or `ListActionRow`, a
|
|
558
|
+
`trailingActionDisabled` button, a disabled `SegmentedControl` choice, or a
|
|
559
|
+
`ListHeader` with `actionDisabled` rendered at full opacity as a
|
|
560
|
+
placeholder, because the placeholder reset could not tell its own
|
|
561
|
+
`disabled` attribute from the author's. Those controls now carry a
|
|
562
|
+
component-owned `data-kui-disabled="true"` marker whenever their disabled
|
|
563
|
+
prop is set; the disabled tone keys on it and the placeholder reset skips
|
|
564
|
+
it. The marker is reserved: the extension-attribute props cannot set it.
|
|
565
|
+
- `@kerfjs/ui` adds a browser test that renders every placeholder-capable
|
|
566
|
+
component live and as a placeholder from identical props and diffs each
|
|
567
|
+
element's geometry and computed chrome, so any future drift outside a value
|
|
568
|
+
slot fails. It caught one more: a `ListActionRow` placeholder's `status`
|
|
569
|
+
line was 3.5px shorter than the live line; the status line now always
|
|
570
|
+
keeps a full line height.
|
|
571
|
+
- `@kerfjs/ui`'s `Select` placeholder hint now matches the live hint: 14px
|
|
572
|
+
text at a 1.6 line height, 7px below the box. It was 12px / 1.4 with a 4px
|
|
573
|
+
gap, so a placeholder with a `hint` was 8.6px shorter than the loaded
|
|
574
|
+
control and the content below it jumped when loading finished.
|
|
575
|
+
- `@kerfjs/ui`'s `StateBanner` placeholder adds a detail skeleton only when
|
|
576
|
+
`detail` is set, matching the live banner. Previously every placeholder
|
|
577
|
+
showed a detail line, so a banner without one changed shape when loading
|
|
578
|
+
finished. Pass the banner's real `title` and `detail` with `placeholder`;
|
|
579
|
+
an empty `detail` now means no detail line.
|
|
580
|
+
- `@kerfjs/ui`'s `AppTab` placeholder pill is no longer dimmed to 50% with a
|
|
581
|
+
`not-allowed` cursor when `webawesome.css` is loaded; it keeps the live
|
|
582
|
+
tab's tone, and only its name is a skeleton.
|
|
583
|
+
- `@kerfjs/ui`'s `ListHeader` placeholder no longer dims its action button
|
|
584
|
+
(48%) or, in `toggle` mode, the whole title and label (Web Awesome's native
|
|
585
|
+
50%), and its inert action and toggle give no hover feedback. A live
|
|
586
|
+
`actionDisabled` header still dims its action.
|
|
587
|
+
- `@kerfjs/ui`'s `SegmentedControl` placeholder now keeps its choices. The
|
|
588
|
+
choices are known; only the selection is unknown, so a placeholder renders
|
|
589
|
+
every choice's live label or icon at its live size, selects none, and stays
|
|
590
|
+
inert. Previously every segment became a 4em text skeleton (widening icon
|
|
591
|
+
segments from 40px to 60px) and was dimmed to 45%.
|
|
592
|
+
- `@kerfjs/ui`'s `ListItem` and `ListActionRow` placeholders now match the
|
|
593
|
+
live row. They are no longer dimmed (the rows' own `:disabled` tone and
|
|
594
|
+
hover highlight skip placeholders), and a row with a `description` (or a
|
|
595
|
+
`ListActionRow` `status`) keeps that line as a second, smaller skeleton, so
|
|
596
|
+
the row keeps its live height instead of collapsing to one line. The
|
|
597
|
+
`ListActionRow` icon placeholder is an 18px block centered in its icon slot,
|
|
598
|
+
like `ListItem`'s.
|
|
599
|
+
- `@kerfjs/ui` placeholders no longer look disabled. A component's
|
|
600
|
+
`placeholder` keeps its controls `disabled` (inert and out of the keyboard
|
|
601
|
+
order), but `skeleton.css` now neutralizes generic disabled chrome under
|
|
602
|
+
`[data-placeholder="true"]`: full opacity and the default cursor instead of
|
|
603
|
+
Web Awesome's native `button:disabled` dimming to 50% with a `not-allowed`
|
|
604
|
+
cursor. A component's own opacity (a control hidden until interaction) still
|
|
605
|
+
applies.
|
|
606
|
+
- `toElement('<image href="…" />')` now returns an SVG `<image>`. The HTML
|
|
607
|
+
parser rewrites an orphan `<image>` to an HTML `<img>` before kerf sees the
|
|
608
|
+
tag, so it previously came back as an XHTML `img` that no `<svg>` would
|
|
609
|
+
render. kerf now recognizes a leading `<image` tag in the raw input
|
|
610
|
+
(case-insensitive, after leading whitespace) and parses it through the SVG
|
|
611
|
+
wrapper. A genuine `<img>` is unchanged.
|
|
612
|
+
- `delegateActions()` (`kerfjs/actions`) dispatches only the table's own
|
|
613
|
+
entries. It used to look the action up with a plain property read, so a
|
|
614
|
+
`data-action` naming an inherited `Object.prototype` member resolved:
|
|
615
|
+
`"__proto__"` or `"hasOwnProperty"` threw a `TypeError` from the listener,
|
|
616
|
+
and `"toString"` or `"constructor"` silently ran the inherited function.
|
|
617
|
+
Those values are now ignored like any other action absent from the table.
|
|
618
|
+
An own entry with such a name (`{ toString: … }`) still dispatches.
|
|
619
|
+
- `toElement()` now puts orphan camelCase SVG fragments in the SVG namespace.
|
|
620
|
+
`toElement('<linearGradient>…</linearGradient>')`, and likewise `clipPath`,
|
|
621
|
+
`radialGradient`, and `foreignObject`, previously returned an XHTML
|
|
622
|
+
`LINEARGRADIENT` element that no `<svg>` would render, because the lookup
|
|
623
|
+
compared the HTML parser's lowercased tag against camelCase names. The
|
|
624
|
+
fragment is now wrapped in `<svg>` and XML-parsed like `<path>` or `<g>`,
|
|
625
|
+
and the returned element keeps its camelCase `localName`.
|
|
626
|
+
- **A tooltip or popover no longer outlives its anchor.** When the anchor left
|
|
627
|
+
the document, most commonly because the modal containing it closed, the
|
|
628
|
+
`tooltip()` or `popover()` stayed on screen: a removed element fires no
|
|
629
|
+
`pointerleave` or `blur`. Both now watch their anchor with the same removal
|
|
630
|
+
tracking as `kerfjs/attach`. The tooltip hides, and the popover closes
|
|
631
|
+
without calling `onDismiss`, as soon as the anchor leaves the document.
|
|
632
|
+
Moving the anchor within the document keeps them open. A tooltip show still
|
|
633
|
+
pending when its anchor is removed never appears.
|
|
634
|
+
- **Overlays opened over a modal `<dialog>` are visible and usable.** A browser
|
|
635
|
+
makes everything outside an open modal dialog inert and paints a plain
|
|
636
|
+
element beneath it, so a `tooltip()`, `popover()`, or `confirm()` opened
|
|
637
|
+
from inside a `native: true` dialog without `native` itself was invisible and
|
|
638
|
+
could not be used. While a modal `<dialog>` is open, `kerfjs/overlay` now
|
|
639
|
+
hosts such a surface in the top layer anyway: a tooltip or popover becomes a
|
|
640
|
+
`[popover]`, and a nested modal becomes its own `<dialog>`. The browser's
|
|
641
|
+
default `[popover]` / `<dialog>` styles then apply, as they do with `native:
|
|
642
|
+
true`. Browsers keep a popover outside the modal dialog inert even in the top
|
|
643
|
+
layer, so a lifted popover with buttons is visible but not clickable. A new
|
|
644
|
+
always-on `kerfjs/dev` warning reports that case, and an engine without the
|
|
645
|
+
needed API.
|
|
646
|
+
- **A tooltip shown inside a modal no longer disables the modal.** `kerfjs/overlay`
|
|
647
|
+
used to treat the most recently opened overlay as the only one that could
|
|
648
|
+
react to input, so a `tooltip()` (which handles no input) inside an open
|
|
649
|
+
modal left Escape doing nothing and let Tab leave the dialog. Every overlay,
|
|
650
|
+
native or fallback, now joins one open-order stack, and each Escape, backdrop
|
|
651
|
+
click, outside click, or trapped Tab goes to the topmost surface that handles
|
|
652
|
+
it. A modal still blocks everything beneath it, and a surface that handles
|
|
653
|
+
nothing never intercepts. A popover that dismisses on Escape gets it before
|
|
654
|
+
the modal beneath. One click that is both outside a popover and on a modal's
|
|
655
|
+
backdrop now closes only the popover. A native `<dialog>` is arbitrated with
|
|
656
|
+
the fallback surfaces: one Escape closes one surface, and a fallback modal
|
|
657
|
+
beneath an open dialog no longer takes the dialog's Tab. Closing a lower
|
|
658
|
+
overlay first leaves focus in the overlay that holds it. An overlay opened
|
|
659
|
+
from inside it then restores focus to the closed overlay's opener, not a
|
|
660
|
+
detached node.
|
|
661
|
+
- `kerfjs` now exports the `EachOptions<T>` type (`{ cacheKey?, key? }`), the
|
|
662
|
+
options-object form of `each()`'s third argument, so a helper that builds or
|
|
663
|
+
forwards those options can import the shape instead of restating it:
|
|
664
|
+
`import { type EachOptions } from "kerfjs"`. Type-only; no runtime or bundle
|
|
665
|
+
change.
|
|
666
|
+
- Documentation: the companion-utility reference now matches the code. The
|
|
667
|
+
router's `match()` / `activeClass()` compare a literal path prefix, so
|
|
668
|
+
`match('/users/:id')` is never true. Link interception still applies to
|
|
669
|
+
`target="_self"`, the no-match `outlet()` is `null` with `params` `{}`,
|
|
670
|
+
components receive `(params, route)`, and hash mode keeps `route.hash` at
|
|
671
|
+
`''`. Invalid `bindList` configuration throws only `RangeError`. `popover()`
|
|
672
|
+
defaults `className` to `'kerf-popover'`, and the toast region is a reused
|
|
673
|
+
`.kerf-toasts` element when one exists. A `resource()` `cacheKey` applies
|
|
674
|
+
only to `run(input, fetcher)`, and throttle's `flush()` leaves the cooldown
|
|
675
|
+
running. `delegate()` / `delegateCapture()` throw on an invalid selector at
|
|
676
|
+
registration. The reference also now lists the `kerfjs/jsx-dev-runtime`
|
|
677
|
+
alias, the `DEV_HOOKS` export of `kerfjs/dev`, and `Signal#peek()`.
|
|
678
|
+
- Documentation: `morph(liveRoot, template)` is now described as it behaves.
|
|
679
|
+
It reconciles only the root's children and never touches the root's own
|
|
680
|
+
attributes, so `template` describes the content inside the root (an
|
|
681
|
+
`Element` template contributes its child nodes). The examples no longer
|
|
682
|
+
nest a duplicate `<article>` inside the live one. An `each()` list parent is
|
|
683
|
+
not a children-only short-circuit: the diff still walks it and skips only
|
|
684
|
+
the owned rows. The user-agent-owned `open` exception covers custom elements
|
|
685
|
+
as well as `<details>` / `<dialog>`, and a content-only row change inside an
|
|
686
|
+
`each()` list keeps its DOM node (and focus) unless the row's top-level tag
|
|
687
|
+
changes or the same render also inserts, removes, or moves rows.
|
|
688
|
+
- Every opt-in `kerfjs/dev` warning now ends with a silence hint that leads
|
|
689
|
+
with the browser-reachable switch, for example "Silence it with
|
|
690
|
+
enableWarnings({ narrowSet: false }) from kerfjs/dev, or set
|
|
691
|
+
KERF_DEV_WARN_NARROW_SET=0 (or unset it) under Node." Previously the
|
|
692
|
+
messages named only the environment variable, which a browser cannot reach.
|
|
693
|
+
The docs now teach `enableWarnings()` wherever they showed an env-only
|
|
694
|
+
switch, and name all three creation-time boundaries: `signal()`
|
|
695
|
+
(`untrackedSignals`), `effect()` (`delegateInEffect`), and `mount()`
|
|
696
|
+
(`rebuiltListeners`) decide once when created, so anything created before
|
|
697
|
+
the install or the opt-in stays uncovered.
|
|
698
|
+
- The `kerfjs/dev` rebuilt-listeners warning (`rebuiltListeners` /
|
|
699
|
+
`KERF_DEV_WARN_REBUILT_LISTENERS`) now dedups once per `mount()`, as
|
|
700
|
+
documented. Previously the first warning silenced every other mount for
|
|
701
|
+
the life of the process, so a second mount with the same bug stayed quiet.
|
|
702
|
+
- `kerfjs/router`: a second router on the same document no longer diverges
|
|
703
|
+
from the URL after a link click. The first router's interceptor navigates
|
|
704
|
+
with `pushState`, which fires no `popstate`, so the second router ignored
|
|
705
|
+
the already-handled click and kept the old route. An already-handled click
|
|
706
|
+
now re-syncs the route from the URL; when nothing navigated, it is a no-op.
|
|
707
|
+
One router per document remains the supported shape: a second router does
|
|
708
|
+
not follow another router's programmatic `navigate()` (no `popstate` fires
|
|
709
|
+
and routers do not broadcast to each other) until the next `popstate` or
|
|
710
|
+
link click. This is now documented and pinned by a test.
|
|
711
|
+
- `kerfjs/scope`: a disposed `Scope` handle no longer leaks registrations.
|
|
712
|
+
Previously `add` / `mount` / `effect` / `delegate` on a handle after its
|
|
713
|
+
`dispose()` (or from a disposer while `dispose()` ran) pushed into a list
|
|
714
|
+
nothing would ever run, leaving the effect subscribed or the listener
|
|
715
|
+
attached. Such a registration now tears down at once: `add(fn)` calls `fn`
|
|
716
|
+
immediately, and the wrappers create and immediately dispose.
|
|
717
|
+
- `bindList` measured-height virtualization no longer applies a stale
|
|
718
|
+
scroll-anchor correction. A `setHeight()` report for a row above the viewport
|
|
719
|
+
is now resolved when the animation frame runs: if the source removed the key,
|
|
720
|
+
moved it below the fold, or the user scrolled the row into view first,
|
|
721
|
+
`scrollTop` is left alone, and several reports for one key in a frame net
|
|
722
|
+
out. Fixed-height windows also clamp to the source length, so a list that
|
|
723
|
+
shrinks while scrolled deep no longer pads far past its content.
|
|
724
|
+
- `create-kerf-component` accepts an explicit `boundaries.rootClass: null` as
|
|
725
|
+
an author decision, as its schema always allowed. A wrapper whose root
|
|
726
|
+
element belongs to another catalog (a `rendersAs` wrapper) no longer has to
|
|
727
|
+
invent a class to satisfy the generator. An absent `rootClass` still fails
|
|
728
|
+
as undecided.
|
|
729
|
+
- `@kerfjs/ui`'s `Select` placeholder now matches the live control. The label
|
|
730
|
+
uses the field-label style (uppercase, extra-small, quiet, inset to line up
|
|
731
|
+
with the value), and the chevron is the same glyph at the same size.
|
|
732
|
+
Previously the placeholder drew a larger, regular-weight label at a
|
|
733
|
+
different inset and a different, smaller chevron. Only the value is a
|
|
734
|
+
skeleton; the chrome no longer changes when loading finishes.
|
|
735
|
+
- **Wrapper components can declare the cataloged root they render.** A
|
|
736
|
+
`kerf.components.json` component may set `composition.rendersAs`, for
|
|
737
|
+
example `["@kerfjs/ui:toolbar-control-group"]`: a list of the roots it
|
|
738
|
+
renders, any one of them, or nothing. `kerf-component-catalog` copies it
|
|
739
|
+
into the generated v2 entry and fails unless every key resolves to a
|
|
740
|
+
cataloged entry. `eslint-plugin-kerfjs`'s `ui-composition` rule, and so
|
|
741
|
+
`kerf-ui-doctor`, now loads the application and third-party catalogs a
|
|
742
|
+
`.kerf-ui-profile.json` declares. It resolves their components by package
|
|
743
|
+
subpath or source file and checks a wrapper as each declared root: a zone
|
|
744
|
+
must accept every root, each root's parent contract applies where the
|
|
745
|
+
wrapper is placed, and a wrapper counts as zero or one child. Undeclared
|
|
746
|
+
wrappers keep reporting `KUI-L202`.
|
|
9
747
|
- `@kerfjs/ui`'s `AppTab` adds `pending` for a known tab that is still
|
|
10
748
|
opening. It keeps the name visible in the quiet text color and uses it as
|
|
11
749
|
the tab's accessible name, so a trailing spinner's label is not folded in.
|