@kerfjs/ui 5.0.0-beta.81 → 5.0.0-beta.83

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.
Files changed (142) hide show
  1. package/README.md +1 -0
  2. package/ai/catalog-authoring.json +1 -3
  3. package/ai/catalog-authoring.schema.json +3 -7
  4. package/ai/component-catalog.json +73 -2
  5. package/ai/component-composition.json +64 -1
  6. package/ai/components/README.md +1 -0
  7. package/ai/components/anchored-surface.md +86 -0
  8. package/ai/components/tab-navigator.md +3 -2
  9. package/ai/public-api-signatures-v1.md +74 -30
  10. package/ai/skill.md +2 -2
  11. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  12. package/dist/anchored-surface.d.ts +30 -0
  13. package/dist/anchored-surface.js +3 -0
  14. package/dist/anchored-surface.js.map +1 -0
  15. package/dist/app-tab.js +1 -1
  16. package/dist/browser/anchored-surface.js +3 -0
  17. package/dist/browser/catalog.js +4 -2
  18. package/dist/catalog-resources.d.ts +1 -1
  19. package/dist/catalog.d.ts +3 -3
  20. package/dist/catalog.js +51 -42
  21. package/dist/catalog.js.map +1 -1
  22. package/dist/{chunk-I4SDIMFA.js → chunk-2YPPNFYT.js} +2 -2
  23. package/dist/{chunk-I4SDIMFA.js.map → chunk-2YPPNFYT.js.map} +1 -1
  24. package/dist/{chunk-CVKIADIS.js → chunk-3BXDWEGB.js} +5 -5
  25. package/dist/{chunk-CVKIADIS.js.map → chunk-3BXDWEGB.js.map} +1 -1
  26. package/dist/{chunk-VHXHLZ6R.js → chunk-3DXHNLME.js} +3 -3
  27. package/dist/{chunk-VHXHLZ6R.js.map → chunk-3DXHNLME.js.map} +1 -1
  28. package/dist/{chunk-HFQDSSED.js → chunk-3PO7JEK7.js} +4 -4
  29. package/dist/{chunk-HFQDSSED.js.map → chunk-3PO7JEK7.js.map} +1 -1
  30. package/dist/chunk-6IVNMC6R.js +19 -0
  31. package/dist/chunk-6IVNMC6R.js.map +1 -0
  32. package/dist/{chunk-VR7MWD2X.js → chunk-CD5TOO22.js} +4 -13
  33. package/dist/chunk-CD5TOO22.js.map +1 -0
  34. package/dist/{chunk-H7ZJEI3H.js → chunk-DL55BSQX.js} +3 -3
  35. package/dist/{chunk-H7ZJEI3H.js.map → chunk-DL55BSQX.js.map} +1 -1
  36. package/dist/{chunk-LQKY5S4F.js → chunk-EA5R75B5.js} +4 -4
  37. package/dist/{chunk-LQKY5S4F.js.map → chunk-EA5R75B5.js.map} +1 -1
  38. package/dist/{chunk-VIGVNDXZ.js → chunk-GPCAXYMT.js} +4 -4
  39. package/dist/{chunk-VIGVNDXZ.js.map → chunk-GPCAXYMT.js.map} +1 -1
  40. package/dist/chunk-IOS2NXW7.js +53 -0
  41. package/dist/chunk-IOS2NXW7.js.map +1 -0
  42. package/dist/{chunk-HFLGWMG3.js → chunk-KAJVVN7Q.js} +3 -3
  43. package/dist/{chunk-HFLGWMG3.js.map → chunk-KAJVVN7Q.js.map} +1 -1
  44. package/dist/{chunk-NVKRZ5DI.js → chunk-LFPW4YC6.js} +4 -4
  45. package/dist/{chunk-NVKRZ5DI.js.map → chunk-LFPW4YC6.js.map} +1 -1
  46. package/dist/{chunk-75I3SNUJ.js → chunk-MN3POJFN.js} +2 -2
  47. package/dist/{chunk-75I3SNUJ.js.map → chunk-MN3POJFN.js.map} +1 -1
  48. package/dist/{chunk-5D3WDMIK.js → chunk-MORVLMDG.js} +4 -16
  49. package/dist/chunk-MORVLMDG.js.map +1 -0
  50. package/dist/{chunk-RRCD3A6G.js → chunk-MTCTX25G.js} +2 -2
  51. package/dist/{chunk-RRCD3A6G.js.map → chunk-MTCTX25G.js.map} +1 -1
  52. package/dist/{chunk-PQQQXJGV.js → chunk-NP3FSLNX.js} +2 -2
  53. package/dist/{chunk-PQQQXJGV.js.map → chunk-NP3FSLNX.js.map} +1 -1
  54. package/dist/{chunk-ARZ4RSQO.js → chunk-PGL6TXFW.js} +3 -3
  55. package/dist/{chunk-ARZ4RSQO.js.map → chunk-PGL6TXFW.js.map} +1 -1
  56. package/dist/{chunk-NVRJ7C3W.js → chunk-QQICWWNX.js} +4 -16
  57. package/dist/chunk-QQICWWNX.js.map +1 -0
  58. package/dist/{chunk-JZM3G6RT.js → chunk-TTGIHMDR.js} +4 -4
  59. package/dist/{chunk-JZM3G6RT.js.map → chunk-TTGIHMDR.js.map} +1 -1
  60. package/dist/{chunk-LRAIS3YB.js → chunk-U657IHFB.js} +1 -1
  61. package/dist/chunk-U657IHFB.js.map +1 -0
  62. package/dist/{chunk-3SXWV2IR.js → chunk-VLGQIYZ5.js} +2 -2
  63. package/dist/{chunk-3SXWV2IR.js.map → chunk-VLGQIYZ5.js.map} +1 -1
  64. package/dist/{chunk-YLJ6X3AC.js → chunk-XK4E225T.js} +2 -2
  65. package/dist/{chunk-YLJ6X3AC.js.map → chunk-XK4E225T.js.map} +1 -1
  66. package/dist/{chunk-BJLXE56E.js → chunk-XKZPBRU7.js} +4 -4
  67. package/dist/{chunk-BJLXE56E.js.map → chunk-XKZPBRU7.js.map} +1 -1
  68. package/dist/{chunk-62VMNBV2.js → chunk-XNXKIPU5.js} +2 -2
  69. package/dist/{chunk-62VMNBV2.js.map → chunk-XNXKIPU5.js.map} +1 -1
  70. package/dist/{chunk-EFDCALR3.js → chunk-Y43B6DS3.js} +2 -2
  71. package/dist/{chunk-EFDCALR3.js.map → chunk-Y43B6DS3.js.map} +1 -1
  72. package/dist/{chunk-3RE2JMZU.js → chunk-YOKEL75M.js} +2 -2
  73. package/dist/{chunk-3RE2JMZU.js.map → chunk-YOKEL75M.js.map} +1 -1
  74. package/dist/collapsible-panel.js +4 -3
  75. package/dist/collapsible-panel.js.map +1 -1
  76. package/dist/css-values.d.ts +8 -1
  77. package/dist/css-values.js +1 -1
  78. package/dist/disclosure-arrow.js +1 -1
  79. package/dist/empty-state.js +1 -1
  80. package/dist/grid.d.ts +5 -4
  81. package/dist/grid.js +2 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +27 -25
  84. package/dist/list-action-row.js +1 -1
  85. package/dist/list-header.js +1 -1
  86. package/dist/list-item.js +1 -1
  87. package/dist/list.d.ts +5 -4
  88. package/dist/list.js +2 -1
  89. package/dist/loading-spinner.js +1 -1
  90. package/dist/lucide-icon.js +1 -1
  91. package/dist/nav-stack.js +2 -1
  92. package/dist/row.d.ts +5 -4
  93. package/dist/row.js +2 -1
  94. package/dist/select.js +1 -1
  95. package/dist/skeleton.js +1 -1
  96. package/dist/spacer.js +1 -1
  97. package/dist/split-view.js +2 -1
  98. package/dist/split-view.js.map +1 -1
  99. package/dist/state-banner.js +1 -1
  100. package/dist/styles/anchored-surface.css +15 -0
  101. package/dist/styles/catalog/components/catalog-example.css +1 -1
  102. package/dist/styles/catalog/components/catalog-stage.css +26 -42
  103. package/dist/styles/catalog/components/catalog.css +0 -24
  104. package/dist/styles/components/collections/list/list.css +4 -1
  105. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +15 -0
  106. package/dist/styles/components/layout/grid/grid.css +4 -1
  107. package/dist/styles/components/layout/row/row.css +4 -1
  108. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +25 -9
  109. package/dist/styles/grid.css +4 -1
  110. package/dist/styles/list.css +4 -1
  111. package/dist/styles/row.css +4 -1
  112. package/dist/styles/styles.css +1 -0
  113. package/dist/styles/tab-navigator.css +25 -9
  114. package/dist/styles/webawesome.css +4 -1
  115. package/dist/tab-navigator.d.ts +10 -3
  116. package/dist/tab-navigator.js +2 -0
  117. package/dist/tab-navigator.js.map +1 -1
  118. package/dist/token-search-field.js +1 -1
  119. package/dist/toolbar-control-group.js +1 -1
  120. package/dist/toolbar-text.js +1 -1
  121. package/dist/{types-C9BubXfz.d.ts → types-CXrh9Wyx.d.ts} +6 -2
  122. package/dist/value-table.js +1 -1
  123. package/dist/wire-catalog.d.ts +1 -9
  124. package/dist/wire-catalog.js +37 -205
  125. package/dist/wire-catalog.js.map +1 -1
  126. package/dist/wire-token-search-fields.js +1 -1
  127. package/dist/workbench.js +2 -1
  128. package/docs/anchored-surface.md +51 -0
  129. package/docs/catalog.md +41 -85
  130. package/docs/component-contract.md +3 -1
  131. package/docs/component-selection.md +19 -1
  132. package/docs/layout.md +13 -2
  133. package/docs/tab-navigator.md +12 -4
  134. package/docs/type-contracts.md +2 -0
  135. package/docs/ux-demo.md +4 -4
  136. package/docs/webawesome-theme.md +4 -1
  137. package/llms.txt +8 -3
  138. package/package.json +7 -1
  139. package/dist/chunk-5D3WDMIK.js.map +0 -1
  140. package/dist/chunk-LRAIS3YB.js.map +0 -1
  141. package/dist/chunk-NVRJ7C3W.js.map +0 -1
  142. package/dist/chunk-VR7MWD2X.js.map +0 -1
package/docs/catalog.md CHANGED
@@ -37,7 +37,16 @@ description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
37
37
  the row's quiet status treatment for decision metadata such as `Discouraged`.
38
38
  - **`content`** — the rendered preview for the active entry. Keep a map of `id →
39
39
  () => SafeHtml` in your app and call `renderers[active]()` in your render.
40
- - **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
40
+ - **`backgroundStyle`** — the preview stage backdrop: `checkerboard` (default,
41
+ 8px squares),
42
+ `vertical-stripes` (alternating 8px bands), `surface`, or `sunken`. The two
43
+ patterned choices share the main Pane's sunken surface and reveal transparent
44
+ specimen edges; `surface` paints an
45
+ opaque backdrop. `sunken` leaves the stage
46
+ transparent so the main Pane's semitransparent sunken surface paints only once.
47
+ A controlled app may pass its current choice on each render.
48
+ - **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header. The
49
+ optional logo paints through `ToolbarControlGroup.avatarImage`.
41
50
  - **`secondarySections`** — an optional secondary "ecosystem" group shown below the
42
51
  primary sections with a quieter treatment: `{ label, sections, collapsible?,
43
52
  expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
@@ -45,13 +54,14 @@ expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
45
54
  - Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
46
55
  sidebar content), and `status` (a footer status line).
47
56
 
48
- The sidebar includes a search field automatically for every Catalog consumer.
49
- `wireCatalog` filters entry names and section headings as text is typed,
57
+ The sidebar includes a `TokenSearchField` in its fixed header for every Catalog
58
+ consumer, so the filter remains visible while the entries scroll. `wireCatalog`
59
+ filters entry names and section headings as text is typed,
50
60
  ignoring case. A heading match shows all its entries; a matching secondary
51
61
  group heading searches its sections too, even while that group is collapsed.
52
62
  Empty sections disappear and an empty-result message appears when nothing
53
63
  matches. The query survives controlled Catalog rerenders and clears through the
54
- search field's native clear control. Filtering does not change the selected
64
+ field's clear action. Filtering does not change the selected
55
65
  entry or its preview.
56
66
 
57
67
  Per-entry `resources` render as "open in new tab" links in the footer, and
@@ -73,11 +83,11 @@ chosen component.
73
83
 
74
84
  Classify every entry before rendering it:
75
85
 
76
- | Entry kind | Preview purpose | Geometry overlay |
77
- | --------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
78
- | **Component** | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
79
- | **Composition** | Show components commonly used together in one focused container or surface. | `false`; child geometry remains unmarked so the composition can be read as a whole. |
80
- | **Recipe** | Teach the recommended production approach to a specific task, including ownership and application glue. | `false`; child geometry remains unmarked so the complete approach can be read. |
86
+ | Entry kind | Preview purpose |
87
+ | --------------- | ------------------------------------------------------------------------------------------------------- |
88
+ | **Component** | Show one public component, its meaningful variants, and adverse states. |
89
+ | **Composition** | Show components commonly used together in one focused container or surface. |
90
+ | **Recipe** | Teach the recommended production approach to a specific task, including ownership and application glue. |
81
91
 
82
92
  These three kinds are exhaustive. Theme/foundation showcases use a functional
83
93
  category, third-party specimens use source metadata, and state galleries retain
@@ -93,10 +103,6 @@ field exists. Show a `Composition` tag rather than repeating the word in a
93
103
  composition's display name; the generic `Catalog` accepts that through the
94
104
  entry's `tags` array.
95
105
 
96
- Keep `geometryOverlay` present and compute it from the active entry. Do not make
97
- it a permanent catalog-wide `true`, and do not give individual specimens their
98
- own overlay implementation.
99
-
100
106
  ### Required structure
101
107
 
102
108
  - `Catalog` is the one shell. The app owns active-entry state and passes one
@@ -148,29 +154,17 @@ const buttonPreview = (
148
154
  <CatalogExample label="Control" align="inline-control">
149
155
  <SegmentedControl id="view" label="View" value="list" choices={choices} />
150
156
  </CatalogExample>
151
- <CatalogExample
152
- label="Authoring note"
153
- note="Explanatory chrome is not a specimen."
154
- rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
155
- >
157
+ <CatalogExample label="Authoring note" note="Explain the specimen below.">
156
158
  <p>Use the public helper contract.</p>
157
159
  </CatalogExample>
158
160
  </CatalogExampleStack>
159
161
  );
160
162
  ```
161
163
 
162
- The overlay selects every immediate child of a `CatalogExample`; the helper
163
- marks its generated label and note so they are excluded automatically. It does
164
- not recursively promote a nested child to be the specimen. Outside an example
165
- row, it selects only top-level
166
- `[data-component]` roots in the canvas and ignores nested component descendants.
167
- These rules keep a row's label/group scaffolding out of the measurement and make
168
- the authored nesting determine exactly what is inspected.
169
-
170
164
  ### Metadata ownership
171
165
 
172
- Use `rootAttributes` on either helper for authoring metadata such as `data-demo`
173
- or `data-catalog-geometry-overlay-skip`; the metadata lands on that helper's
166
+ Use `rootAttributes` on either helper for authoring metadata such as `data-demo`;
167
+ the metadata lands on that helper's
174
168
  rendered root. The slot accepts only `data-*` strings. Structural
175
169
  `data-catalog-example`, `data-catalog-example-stack`, and `data-align` semantics
176
170
  remain helper-owned and are rejected case-insensitively at runtime, including
@@ -181,50 +175,21 @@ The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
181
175
  `Catalog.stageRootAttributes` places app-owned `data-*` state on the
182
176
  catalog-owned preview stage without an extra wrapper; `data-catalog-stage`
183
177
  remains protected.
178
+ The stage is the main Pane's direct scrolling content, owns an 8px inset, and
179
+ grows with tall specimens. The main Pane keeps its sunken appearance without a
180
+ deep inset.
184
181
  The helpers own their structural markers, label/note anatomy, alignment marker,
185
182
  and private classes. Component metadata such as margin/border/padding ownership
186
- lives in `component-catalog.json`; do not infer or overwrite it from overlay
187
- pixels.
188
-
189
- ### Geometry overlay and legend
183
+ lives in `component-catalog.json`.
190
184
 
191
- Pass the conditional `geometryOverlay` boolean to `Catalog`, then call
192
- `wireCatalogGeometryOverlay(root)` once after the first render and retain its
193
- disposer alongside `wireCatalog`'s.
185
+ ### Component metadata
194
186
 
195
- | Overlay mark | Meaning | It is not |
196
- | ----------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
197
- | Orange translucent band | A positive computed margin on the selected specimen, after subtracting the helper's alignment inset. | Padding, gap, or empty content. Zero and negative margins are not drawn. |
198
- | Accent solid edge | The selected specimen's computed border widths and radius, except `none`/`hidden` styles. | A focus ring, outline, child border, or metadata ownership claim. |
199
- | Quiet dashed outline | The border-box bound of a transparent selected specimen with no visible border. | A real CSS border, padding edge, or proof that the specimen owns size. |
200
-
201
- The overlay recomputes from live rendered styles when specimens resize, preview
202
- markup changes, theme/root attributes change, or loaded/inline stylesheets
203
- change. Component CSS is therefore the source of truth; do not duplicate margin
204
- or border values in demo data. The overlay does not visualize padding, gaps,
205
- negative/zero margins, scroll overflow, hit targets, or nested descendants.
206
- Inspect computed styles and the machine-readable `geometry` metadata for those
207
- facts. Opaque borderless specimens do not receive the dashed bound.
208
-
209
- ```tsx
210
- <Catalog
211
- {...props}
212
- geometryOverlay={activeEntry.kind === "component"}
213
- content={renderers[active.value]()}
214
- />
215
- ```
187
+ Machine-readable geometry ownership metadata identifies whether the component,
188
+ its parent, or its children are responsible for margin, border, and padding.
189
+ Keep that metadata in `component-catalog.json` and use the rendered specimen to
190
+ review the actual appearance.
216
191
 
217
- Put `rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}` on a
218
- `CatalogExample` or `CatalogExampleStack` only when that whole subtree is
219
- explanatory chrome rather than a specimen. The marker excludes the marked root
220
- and every descendant from selection; it does not merely hide one band. It is
221
- normally unnecessary in a composition because the active entry already sets the
222
- global overlay to `false`.
223
-
224
- Use the overlay together with machine-readable geometry ownership metadata; the
225
- overlay verifies what is rendered, while metadata tells people and AI tools
226
- whether the component, its parent, or its children are responsible for margin,
227
- border, and padding. Catalogs for downstream components should conform to the
192
+ Catalogs for downstream components should conform to the
228
193
  [`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
229
194
  contract and can start from the checked
230
195
  [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
@@ -274,8 +239,8 @@ facts that source can prove without guessing at rendered intent:
274
239
 
275
240
  - focused component routes import and use `CatalogExampleStack` and
276
241
  `CatalogExample` from the public package;
277
- - focused route metadata uses the helpers' `rootAttributes` slot, example rows
278
- are not empty, and composition routes do not add redundant skip markers;
242
+ - focused route metadata uses the helpers' `rootAttributes` slot and example rows
243
+ are not empty;
279
244
  - every `@kerfjs/ui` import is a published package export and relative imports
280
245
  do not reach into `ui/src`;
281
246
  - focused demos have no relative stylesheet imports, inline style attributes,
@@ -285,20 +250,19 @@ facts that source can prove without guessing at rendered intent:
285
250
  analyzer) import no stylesheet other than public `@kerfjs/ui` CSS, set no
286
251
  inline style, and name only published layout and Web Awesome classes; each
287
252
  recipe declares its catalog frame through an exported `presentation`; and
288
- - the shell derives both geometry-overlay enablement and documented demo mode
289
- from the active entry's source and kind.
253
+ - the shell derives the documented demo mode from the active entry's kind.
290
254
 
291
255
  The gate deliberately does not infer component ownership from arbitrary class
292
- names, margins, borders, or nested descendants. Runtime selection and geometry
293
- remain the browser suite's job.
256
+ names, margins, borders, or nested descendants. Runtime appearance remains the
257
+ browser suite's job.
294
258
 
295
259
  A focused route may bypass the two public layout helpers only when the route's
296
260
  stage geometry is itself the reviewed specimen. Add the narrow waiver to
297
261
  [`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
298
262
  with the exact route, source file, stable diagnostic ids, a substantive reason,
299
263
  and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
300
- private imports, local styling, private markup, empty examples, composition overlay drift, and
301
- shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
264
+ private imports, local styling, private markup, empty examples, and shell-mode
265
+ drift always fail. The gate rejects duplicate, malformed, unused,
302
266
  and stale exceptions, so delete a waiver when its route adopts the standard
303
267
  helpers.
304
268
 
@@ -329,7 +293,6 @@ import {
329
293
  import {
330
294
  revealCatalogEntry,
331
295
  wireCatalog,
332
- wireCatalogGeometryOverlay,
333
296
  } from "@kerfjs/ui/wire-catalog";
334
297
  // A CSS-aware (browser-condition) bundler loads the Catalog's CSS with its import.
335
298
 
@@ -374,11 +337,7 @@ const renderers: Record<string, () => SafeHtml> = {
374
337
  <CatalogExample label="Default" align="inline-control">
375
338
  <Button label="Save" />
376
339
  </CatalogExample>
377
- <CatalogExample
378
- label="Authoring note"
379
- note="This explanatory row is deliberately excluded from inspection."
380
- rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
381
- >
340
+ <CatalogExample label="Authoring note" note="Explain the specimen below.">
382
341
  <p>The application owns product copy and actions.</p>
383
342
  </CatalogExample>
384
343
  </CatalogExampleStack>
@@ -401,7 +360,6 @@ const initial =
401
360
  const active = signal(initial);
402
361
  const collapsed = signal(false);
403
362
  const theme = signal<"light" | "dark">("light");
404
- const activeEntry = () => entries.find(({ id }) => id === active.value) ?? entries[0];
405
363
 
406
364
  const app = document.getElementById("app")!;
407
365
  mount(app, () => (
@@ -412,7 +370,6 @@ mount(app, () => (
412
370
  content={renderers[active.value]?.() ?? <></>}
413
371
  collapsed={collapsed.value}
414
372
  theme={theme.value}
415
- geometryOverlay={activeEntry().kind === "component"}
416
373
  />
417
374
  ));
418
375
 
@@ -430,7 +387,6 @@ wireCatalog(app, {
430
387
  urlParam: "c", // mirror the active id into ?c=<id>
431
388
  revealSelection: true, // reveal long desktop sidebars without moving focus
432
389
  });
433
- wireCatalogGeometryOverlay(app);
434
390
 
435
391
  // Optional for an initial deep link whose row may start outside the viewport.
436
392
  revealCatalogEntry(app, initial, { block: "center" });
@@ -483,7 +439,7 @@ matching options on `wireCatalog`) if they collide with your own action table.
483
439
 
484
440
  `Catalog` composes public components (`Workbench`, `Pane`, `Toolbar`, `List`,
485
441
  `ListHeader`, `ListItem`, `ListInsetText`, `Text`, …) and owns only the preview stage
486
- (the checkerboard and centered canvas), the geometry overlay, the example viewport
442
+ (the checkerboard and left-aligned canvas), the example viewport
487
443
  options, and the brand mark's size. A bundler that honors the `browser` export
488
444
  condition loads the Catalog's stylesheets and those of every component it renders
489
445
  internally with the `@kerfjs/ui/catalog` import alone — no dependence on which other
@@ -502,7 +502,9 @@ spacing shorthand such as `gap="2xs"`, or pass a `CssLength` created by the
502
502
  CSS-free `@kerfjs/ui/css-values` helpers: `space('m')`, `rem(0.25)`,
503
503
  `lengthVar('--app-gap')`, or `calc(plus(rem(0.25), pct(10)))`. `plus` returns a
504
504
  non-standalone `CssLengthExpression`; only `calc` turns it into a complete
505
- length. `remify(18)` is the runtime twin of source CSS's `remify(18px)`: it
505
+ length. `gap={{ column: 'none', row: 'm' }}` sets the two CSS gap axes
506
+ independently; each axis keeps the same typed spacing contract. `remify(18)` is
507
+ the runtime twin of source CSS's `remify(18px)`: it
506
508
  takes a pixel count and returns a `CssLength` in rem (`1.125rem`) against the
507
509
  same fixed 16px baseline. Raw CSS strings, and the source-only `remify(4px)`
508
510
  string form, are not valid runtime props. The `CssLength` name intentionally includes percentages for UI dimension
@@ -61,6 +61,8 @@ Use `Row`, `Grid`, or `List` spacing shorthands for repeated sibling spacing: `g
61
61
  `gap="m"`, and the other `UiSpaceName` values resolve to their matching
62
62
  `--kui-space-*` token. Use `Spacer` for one deliberate empty width or height, or
63
63
  pass `flex` to consume the remaining space along a flex parent's main axis.
64
+ Pass `gap={{ column: 'none', row: 'm' }}` when the axes need different values;
65
+ each axis accepts the same token or complete typed length as the scalar form.
64
66
  The canonical rhythm is `none`, `2xs`, `xs`, `m`, and `l`; use `s` and `xl`
65
67
  only as deliberate exceptions.
66
68
 
@@ -78,6 +80,7 @@ import { Row } from "@kerfjs/ui/row";
78
80
 
79
81
  <List gap="xs">...</List>;
80
82
  <Grid columns={3} gap="m">...</Grid>;
83
+ <Grid columns={3} gap={{ column: 'none', row: 'm' }}>...</Grid>;
81
84
  <Row hAlign="full" vAlign="middle" gap="m">...</Row>;
82
85
  <List gap={space("m")}>...</List>;
83
86
  <List gap={calc(plus(rem(0.25), pct(10)))}>...</List>;
@@ -130,11 +133,17 @@ copyable reference compositions, not new monolithic components.
130
133
 
131
134
  When presenting a component or recipe in a Catalog, use the authoritative
132
135
  [Catalog demo authoring contract](./catalog.md#catalog-demo-authoring-contract)
133
- rather than deriving preview structure or geometry-overlay behavior from these
136
+ rather than deriving preview structure from these
134
137
  selection entries.
135
138
 
136
139
  ## Missing recurring concepts
137
140
 
141
+ For arbitrary interactive help or popup content beside a control, use
142
+ [`openAnchoredSurface`](anchored-surface.md) with an accessible label; use
143
+ `openAnchoredSurfaceAt` for pointer coordinates. It owns the Kerf UI surface,
144
+ placement, focus, dismissal, and modal-dialog hosting while the app owns its
145
+ content and action policy. Use `PopupMenu` for a typed command list.
146
+
138
147
  Kerf UI does not export a command-palette component. Do not invent a package
139
148
  command-palette import.
140
149
  The typed [application adapter example](./examples/command-palette-adapter.tsx)
@@ -558,6 +567,15 @@ menu. Call `closePopupMenu` for programmatic dismissal. The pointer anchor
558
567
  retains Web Awesome's collision placement, focus management, submenu keyboard
559
568
  navigation, and outside/Escape dismissal.
560
569
 
570
+ The dropdown's popup flips and shifts at viewport edges, limits its height to
571
+ the available space, and uses the browser top layer where supported. The app
572
+ supplies pointer coordinates and command policy; it does not need to calculate
573
+ menu offsets. For other anchored content, use Web Awesome `wa-tooltip` or
574
+ `wa-popover` when its interaction fits, and `wa-popup` when only placement is
575
+ needed. `kerfjs/overlay` provides a structural popover or tooltip with its own
576
+ lifecycle for applications that do not use Web Awesome. These are distinct
577
+ interaction contracts: a command menu still uses `PopupMenu`.
578
+
561
579
  ## Configuring recurring list rows
562
580
 
563
581
  Configure the List family instead of selecting its descendant classes. Use
package/docs/layout.md CHANGED
@@ -376,8 +376,12 @@ The two relationships that get confused most are 8px vs 24px: 8px is _inside_ a
376
376
  group, 24px is _between_ major, differing regions. 16px sits between them for
377
377
  same-kind groups.
378
378
 
379
- For `Row.gap`, `Grid.gap`, and `List.gap`, pass these token names directly (`gap="xs"`, `gap="m"`) or use
380
- `space('xs')` from `@kerfjs/ui/css-values` when composing a value in JavaScript.
379
+ For `Row.gap`, `Grid.gap`, and `List.gap`, pass one token name (`gap="xs"`,
380
+ `gap="m"`) to use it on both axes, or pass
381
+ `gap={{ column: 'none', row: 'm' }}` to set each axis independently. Each axis
382
+ also accepts a complete typed `CssLength`, including `space('xs')` from
383
+ `@kerfjs/ui/css-values`. A List is a single vertical column, so its row gap
384
+ separates items; its column gap is exposed for the same CSS axis contract.
381
385
  Use `rem`, `em`, `px`, `pct`, `lengthVar`, and `calc(plus(...))` only when a
382
386
  named spacing relationship does not express the requirement; do not pass raw
383
387
  CSS strings.
@@ -523,6 +527,13 @@ import { px } from "@kerfjs/ui/css-values";
523
527
  <label>Unit <input /></label>
524
528
  </Grid>;
525
529
 
530
+ <Grid columns={2} gap={{ column: 'none', row: 'm' }}>
531
+ <DocumentTile />
532
+ <DocumentTile />
533
+ <DocumentTile />
534
+ <DocumentTile />
535
+ </Grid>;
536
+
526
537
  <Grid minColumnWidth={px(376)} gap="m">
527
538
  <label>Provider <input /></label>
528
539
  <label>Endpoint <input /></label>
@@ -144,9 +144,16 @@ paint the lowered surface behind short or long content. For a scene whose
144
144
  only child is a `Pane` or `NavStack`, set appearance on that child's scroll
145
145
  owner. See [Lowered work surfaces](layout.md#lowered-work-surfaces).
146
146
 
147
- ## Scroll divider
147
+ ## Bar separator and scroll divider
148
148
 
149
- The bar has no permanent top border. With `wireScrollDividers(appRoot)`
149
+ `separator="auto"` (the default) draws the bar's top edge when the active scene
150
+ has `appearance: "sunken"`. It also follows the sunken appearance of a sole
151
+ `Pane`, or the active `Pane` view of a sole `NavStack`, inside that scene. The
152
+ edge stays visible when the content fits and as the active tab or nested view
153
+ changes. `separator="hidden"` suppresses the top line, including any scroll
154
+ divider or `chromeDividers="always"` line.
155
+
156
+ For a flat active scene, the bar has no permanent top line. With `wireScrollDividers(appRoot)`
150
157
  installed (see [Layout › Scroll dividers](layout.md#scroll-dividers)), it shows
151
158
  a line along its top edge only while the active scene's content continues below
152
159
  it — not at the scroll end and not when the content fits. The line keys on
@@ -156,10 +163,11 @@ own bottom toolbar sits between them, so that toolbar's line applies instead.
156
163
  The line is the bar's always-present 1px top border, colored only while shown,
157
164
  so it never moves the bar or a tab.
158
165
 
159
- `chromeDividers` configures it, with the same values as a `Pane`'s: `scroll`
166
+ `chromeDividers` configures the scroll divider, with the same values as a `Pane`'s: `scroll`
160
167
  (the default) follows the scroll state and draws nothing until the wiring runs;
161
168
  `always` keeps the line, with or without the wiring; `none` drops it even when
162
- the wiring reports content below.
169
+ the wiring reports content below. It does not suppress the sunken-surface
170
+ separator; use `separator="hidden"` for that.
163
171
 
164
172
  ## Tab badges
165
173
 
@@ -121,6 +121,8 @@ move errors into casts without making the integration safer.
121
121
  - Replace raw `List.gap` strings with a direct spacing shorthand (`"xs"`,
122
122
  `"m"`) or a complete value from `@kerfjs/ui/css-values`. Replace
123
123
  `gap="0.25rem"` with `gap={rem(0.25)}` and wrap sums with `calc(plus(...))`.
124
+ `Row`, `Grid`, and `List` also accept `gap={{ column: 'none', row: 'm' }}`;
125
+ each axis uses the same typed value contract.
124
126
  - Replace raw `List.flex` shorthands with `flex(grow, shrink, basis)` or a finite
125
127
  keyword; replace raw Skeleton dimensions with `px`/`rem`/`em`/`pct` builders;
126
128
  and replace raw choice colors with a foreground token such as
package/docs/ux-demo.md CHANGED
@@ -22,20 +22,20 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
22
22
 
23
23
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
24
24
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
25
- - The `PopupMenu` route's context example opens one app-owned menu either at the row's right-click point or below its adjacent, keyboard-operable Demand actions button. Its interactive row/button composition opts out of the catalog geometry overlay so the overlay's border and margin highlights do not masquerade as a row inset during activation. The Nested decisions example shows the automatic phone-width submenu placement above or below its parent list, with native ArrowLeft return, Escape dismissal, and child selection.
25
+ - The `PopupMenu` route's context example opens one app-owned menu either at the row's right-click point or below its adjacent, keyboard-operable Demand actions button. The Nested decisions example shows the automatic phone-width submenu placement above or below its parent list, with native ArrowLeft return, Escape dismissal, and child selection.
26
26
  - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The pinned leading-tab specimen uses native strip scrolling so momentum and the final tab's end position remain natural; `snapTabs` stays an opt-in for applications that want whole-tab settling. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding (a new tab opens `pending` with a spinner and stays selectable while its panel shows a `Skeleton` heading until a simulated load completes), closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
27
27
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
28
28
  - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
29
29
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
30
30
  - The master/detail shell groups first-party routes by category with production `ListHeader` and `ListItem` components. Catalog entries can supply short reusable trailing tags; the Web Awesome projection uses them to label exactly the 15 discouraged choices. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts keep the expanded list in document flow rather than introducing a competing nested scroller.
31
- - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. The shared Catalog sidebar includes a filter for entry and heading names that works across first-party and ecosystem sections, including collapsed sections. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the preview scroll owner rather than a viewport-height stage or nested card, and one explicitly repeated SVG tile keeps the pattern continuous through below-fold content. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
31
+ - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. The shared Catalog sidebar includes a filter for entry and heading names that works across first-party and ecosystem sections, including collapsed sections. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The preview stage sits directly in the sunken main Pane with its own 8px inset. The checkerboard belongs to that stage, rather than a viewport-height wrapper or nested card, and one explicitly repeated SVG tile keeps the pattern continuous through below-fold content. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
32
32
  - The composer-form recipe demonstrates one coherent visible form surface with toolbar title/supporting-copy hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
33
33
  - The list-detail dialog recipe is laid out like a `Workbench` on roomy devices: a headerless `DialogSurface` modal whose `SplitView` holds a full-height list sidebar `Pane` with its own toolbar beside a full-height detail `Pane` that carries the primary xl title, the `data-dialog="close"` control, and the record actions in its footer. Compact devices get a full-screen sheet with its own labeled header, footer actions, and a list-to-detail drill-down. Browser geometry coverage protects those relationships at wide and narrow widths.
34
34
  - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
35
35
  - The menu composition route demonstrates toolbar/content/footer anatomy, 24px major gaps, 8/1/8 child geometry, the full-width `ListHeader` count-or-badge/action split, logical-end 44px action targets with 18px visuals, and 44px row/header/toolbar targets. Its one chevron is a production 18px `DisclosureArrow` inside a controlled Tools header that actually reveals and hides content; ordinary Projects navigation has no false disclosure affordance. Browser assertions cover stable naming, expanded/content transitions, visual rotation, node identity, reduced motion, RTL alignment and containment, and wide, narrow, dark, and 200% layouts. The focused ListHeader route separately shows the shared neutral count pill for Attachments, Notes (including zero), and Duplicates plus a non-count legacy badge, full-width logical-end actions, and no disclosure without content.
36
36
  - The `DisclosureArrow` route renders the production 18px root-scaled default without a catalog-only size override. Its two native buttons independently control the default right-to-down chevron and a recognizable right-facing replacement arrow with left-to-up custom directions; both use shortest-path rotation and their names stay stable while `aria-expanded` communicates state. Browser coverage verifies the custom clockwise path, pointer and native keyboard activation, focus, narrow and 200%-zoom stacking with contained labels, consumer sizing, and the independent `.5` Web Awesome glyph scale retained by Kerf `Select`.
37
37
  - A detail with relationships renders one content-fitted `Components` footer selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Its popup owns the standard surface inset so headings and even long component names stay clear of the menu border. Choosing either navigates to that component's demo. The relationship group is absent when both sets are empty; no duplicate summary is rendered.
38
- - Focused demos ship no local stylesheets, inline styles, or styling-only class names. They compose Kerf components first, Web Awesome surfaces where Kerf has no equivalent, and ordinary semantic HTML for the remainder. The catalog-owned `CatalogExample.viewport` contract supplies the few specimen constraints needed to demonstrate layout behavior. Recipes follow the same rule: they ship no stylesheets, inline styles, or styling-only classes, and the catalog frames each one in the `CatalogExample` viewport its exported `presentation` declares (full-width application arrangements span the canvas measure; narrower recipes sit in the centered example column). Demo-only CSS is limited to the document-shell `style.css`, which carries no recipe rules. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
38
+ - Focused demos ship no local stylesheets, inline styles, or styling-only class names. They compose Kerf components first, Web Awesome surfaces where Kerf has no equivalent, and ordinary semantic HTML for the remainder. The catalog-owned `CatalogExample.viewport` contract supplies the few specimen constraints needed to demonstrate layout behavior. Recipes follow the same rule: they ship no stylesheets, inline styles, or styling-only classes, and the catalog frames each one in the `CatalogExample` viewport its exported `presentation` declares (full-width application arrangements span the canvas measure; narrower recipes sit in the left-aligned example column). Demo-only CSS is limited to the document-shell `style.css`, which carries no recipe rules. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
39
39
  - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. Individual Web Awesome action, form, layout, navigation, feedback, media, and helper routes provide focused theme specimens without a redundant aggregate route; ListItem and ListHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; the Headers composition demonstrates direct toolbar headings with an extra-large title, optional supporting copy, and grouped icon/actions; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone with tone-tinted badges, a scoped override, and a placeholder with and without a badge, the badged placeholder in every tone so captures cover the solid badge skeleton; the Application tabs composition uses a Web Awesome card with semantic header/footer slots and Kerf `Text` while demonstrating overflow, edge autoscroll, and controlled pointer/keyboard reordering.
40
40
  - The ToolbarControlGroup route also demonstrates the public compact/tight,
41
41
  mixed-content, filled-selection, nested-dropdown, and avatar/scrim contracts
@@ -48,7 +48,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
48
48
  collapsible-search specimen follows the selected group shape with a
49
49
  concentric inner radius in both collapsed and expanded states.
50
50
  - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals each recipe's note as its `CatalogExample` note above the framed specimen, so instructional copy never covers the composition.
51
- - Settings exercise light/dark, increased contrast, and reduced motion without changing component code. The theme action starts from the operating system's effective color scheme, always names the appearance it will switch to, and installs an explicit light or dark override when activated; contrast and reduced motion expose their independent preferences through `aria-pressed`.
51
+ - Settings exercise light/dark, increased contrast, and reduced motion without changing component code. The theme action starts from the operating system's effective color scheme, always names the appearance it will switch to, and installs an explicit light or dark override when activated; contrast and reduced motion expose their independent preferences through `aria-pressed`. The display-control group wraps whole independent controls at narrow widths so all stay reachable. The adjacent Background menu chooses an 8px-square checkerboard (default), 8px vertical stripes, surface, or sunken for the preview stage; the current choice stays checked while moving between entries.
52
52
  - The footer action log is an `aria-live` region, making interactions visible to both people and browser tests.
53
53
  - The focused Toast route calls Web Awesome's documented programmatic stack API and notes that Hot Sheet 2 still uses its own app-level toast implementation; the catalog demonstrates ecosystem support without implying current product adoption.
54
54
  - Browser coverage walks every Kerf, composition, and Web Awesome route, checks every listed Web Awesome custom element is registered in its focused specimen, exercises the ecosystem disclosure and both relationship directions, validates offline source/guidance hrefs and accessible wide/narrow/200%-zoom wrapping, and covers tab selection/reordering/overflow, semantic palettes and scoped overrides, light/dark rendering, toolbar popup and responsive find behavior, single/multiline search alignment, menu alignment, banner urgency, keyboard resizing, live custom-element values, and representative wide/narrow captures.
@@ -67,7 +67,10 @@ Form fields carry the same content-item inset as the Kerf primitives: a 1px
67
67
  border with 8px inside it (`--wa-form-control-border-width`,
68
68
  `--wa-form-control-padding-block`, and `--wa-form-control-padding-inline` are set
69
69
  to the `--kui-layout-item-border-width` / `--kui-layout-item-padding` values), so
70
- the value sits 9px in from the field edge. Default-size single-line controls
70
+ the value sits 9px in from the field edge. Native buttons and fields, plus Web
71
+ Awesome buttons and fields, use the 12px `--kui-layout-rounded-radius` through
72
+ `--wa-form-control-border-radius`; explicit pill and circular shapes retain
73
+ their own radius. Default-size single-line controls
71
74
  share Kerf's 44px control height — the height of a Kerf `Select`, a toolbar
72
75
  group, and a list row — so a `wa-input` or `wa-button` sits flush beside a Kerf
73
76
  `Select`. The theme sets `--wa-form-control-height` to `2.75em` (44px at the
package/llms.txt CHANGED
@@ -12,6 +12,9 @@ npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional pee
12
12
  Import each component from its own subpath (`@kerfjs/ui/list-item`, `@kerfjs/ui/toolbar`,
13
13
  …); the browser build pulls that component's reachable CSS automatically, so do not
14
14
  maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
15
+ For arbitrary interactive help or popup content, use `openAnchoredSurface` or
16
+ `openAnchoredSurfaceAt` from `@kerfjs/ui/anchored-surface`; see
17
+ [AnchoredSurface](./docs/anchored-surface.md). The app owns content and actions.
15
18
  For a public subpath containing several visual components, use a folder with one
16
19
  component per source file and one owned stylesheet per visual component. Keep its
17
20
  compatibility entrypoints implementation-free: JavaScript/TypeScript only re-exports
@@ -395,6 +398,9 @@ headingLevel`, `TokenSearchField clearIcon`, `ContentItem frame="framed"` for a
395
398
  or explicit `scroll`, `always`, `none`), and a `NavStack` or `TabNavigator`
396
399
  with its own `chromeDividers` (same values; a `SplitView` forwards it through
397
400
  `compactStack`); name app-owned chrome in `targets`.
401
+ A `TabNavigator` also takes `separator="auto" | "hidden"`: auto draws the
402
+ bar's top edge beside a sunken active scene or nested Pane even when content
403
+ fits; hidden suppresses that edge, including scroll dividers.
398
404
  Never add a border, or a permanent `dividerSides`, to fake one.
399
405
  While a side overlay is open (a Workbench rail overlay, a `wireSidebar` compact
400
406
  overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`), the
@@ -524,9 +530,8 @@ shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelTogg
524
530
  - `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence; a compact overlay starts closed and opens only on a user action, and `inlineCollapsed` declares a panel's inline default).
525
531
  `@kerfjs/ui/catalog` is another opt-in shell — a
526
532
  `Catalog` component + `wireCatalog` helper for building a component-gallery catalog
527
- (collapsible category sidebar + preview stage + resources footer, with an opt-in
528
- transparent-bound and margin geometry overlay plus focus-preserving active-row
529
- reveal for long desktop sidebars) from your own
533
+ (collapsible category sidebar + preview stage + resources footer, with
534
+ focus-preserving active-row reveal for long desktop sidebars) from your own
530
535
  entries; controlled/stateless like the layouts. For any Catalog demo, follow
531
536
  the [authoritative authoring contract](./docs/catalog.md#catalog-demo-authoring-contract)
532
537
  rather than inferring structure from the shell or private CSS.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.81",
3
+ "version": "5.0.0-beta.83",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -317,6 +317,11 @@
317
317
  "browser": "./dist/browser/popup-menu.js",
318
318
  "import": "./dist/popup-menu.js"
319
319
  },
320
+ "./anchored-surface": {
321
+ "types": "./dist/anchored-surface.d.ts",
322
+ "browser": "./dist/browser/anchored-surface.js",
323
+ "import": "./dist/anchored-surface.js"
324
+ },
320
325
  "./popup-menu/register": {
321
326
  "types": "./dist/popup-menu-register.d.ts",
322
327
  "import": "./dist/popup-menu-register.js"
@@ -390,6 +395,7 @@
390
395
  "./loading-spinner.css": "./dist/styles/loading-spinner.css",
391
396
  "./select.css": "./dist/styles/select.css",
392
397
  "./popup-menu.css": "./dist/styles/popup-menu.css",
398
+ "./anchored-surface.css": "./dist/styles/anchored-surface.css",
393
399
  "./skeleton.css": "./dist/styles/skeleton.css",
394
400
  "./sunken-panel.css": "./dist/styles/sunken-panel.css",
395
401
  "./segmented-control.css": "./dist/styles/segmented-control.css",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/layout/row/row.tsx"],"names":[],"mappings":";;;;;AAiBA,IAAM,sBAAA,uBAA6B,GAAA,CAAI;AAAA,EACrC,gBAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAeD,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAgCO,SAAS,GAAA,CAAI;AAAA,EAClB,QAAA;AAAA,EACA,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,MAAA;AAAA,EACT,GAAA,GAAM,IAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY,EAAA;AAAA,EACZ,iBAAiB,EAAC;AAAA,EAClB;AACF,CAAA,EAAa;AACX,EAAA,MAAM,kBAAA,GAAqB,oBAAA;AAAA,IACzB,cAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM,WAAW,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACnD,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA;AACJ,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,kBAAkB,QAAQ,CAAA,CAAA;AAAA,IAC1B,SAAA,GAAY,CAAA,gBAAA,EAAmB,SAAS,CAAA,CAAA,GAAK;AAAA,GAC/C,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACE,GAAG,kBAAA;AAAA,MACJ,KAAA,EAAO,CAAA,QAAA,EAAW,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACnC,gBAAA,EAAe,KAAA;AAAA,MACf,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,WAAA,EAAW,OAAO,IAAI,CAAA;AAAA,MACtB,oBAAkB,UAAA,IAAc,MAAA;AAAA,MAChC,uBAAqB,aAAA,IAAiB,MAAA;AAAA,MACtC,KAAA;AAAA,MACA,IAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-5D3WDMIK.js","sourcesContent":["import type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { filterDataAttributes } from '../../../shared/dom/extension-attributes.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type VerticalAlignment,\n verticalAlignment,\n} from '../../../shared/layout/flex-alignment.js';\nimport type { Sides } from '../../../shared/layout/sides.js';\nimport {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from '../../../shared/styles/css-values.js';\n\nconst rowProtectedAttributes = new Set([\n 'data-component',\n 'data-h-align',\n 'data-v-align',\n 'data-flex',\n 'data-fill',\n 'data-wrap',\n 'data-text-insets',\n 'data-control-insets',\n]);\n\ntype RowRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-h-align'?: never;\n 'data-v-align'?: never;\n 'data-flex'?: never;\n 'data-fill'?: never;\n 'data-wrap'?: never;\n 'data-text-insets'?: never;\n 'data-control-insets'?: never;\n }\n>;\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface RowProps {\n children?: KerfUiContent;\n /** Horizontal distribution. Defaults to left. */\n hAlign?: HorizontalAlignment;\n /** Vertical alignment and wrapped-line distribution. Defaults to full. */\n vAlign?: VerticalAlignment;\n /** A named UI spacing token or typed CSS length. Defaults to xs. */\n gap?: UiSpaceName | CssLength;\n /** Allow this row to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /**\n * Fill the height of a parent with a definite height, such as an app root or\n * a fixed-height frame, when this row is that parent's layout root. Inside a\n * flex layout use `flex` instead. Defaults to false.\n */\n fill?: boolean;\n /** Allow children to wrap onto additional lines. */\n wrap?: boolean;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n /** Safe `data-*` metadata; Row-owned structural attributes remain protected. */\n rootAttributes?: RowRootAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/** A horizontal flex row with explicit physical-axis alignment and spacing. */\nexport function Row({\n children,\n hAlign = 'left',\n vAlign = 'full',\n gap = 'xs',\n flex = false,\n fill = false,\n wrap = false,\n textInsets = '',\n controlInsets = '',\n className = '',\n rootAttributes = {},\n slot,\n}: RowProps) {\n const safeRootAttributes = filterDataAttributes(\n rootAttributes,\n rowProtectedAttributes,\n );\n const gapValue = spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n `--_kui-row-gap:${gapValue}`,\n flexValue ? `--_kui-row-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n {...safeRootAttributes}\n class={`kui-row ${className}`.trim()}\n data-component=\"row\"\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-flex={String(Boolean(flex))}\n data-fill={fill ? 'true' : undefined}\n data-wrap={String(wrap)}\n data-text-insets={textInsets || undefined}\n data-control-insets={controlInsets || undefined}\n style={style}\n slot={slot}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n HorizontalAlignment,\n VerticalAlignment,\n} from '../../../shared/layout/flex-alignment.js';\nexport type { Sides } from '../../../shared/layout/sides.js';\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from '../../../shared/styles/css-values.js';\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/styles/css-values.ts"],"names":[],"mappings":";AAqEA,IAAM,YAAA,GAAe;AAAA,EACnB,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA,qBAAA;AAAA,EACA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,mBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,kBAAA;AAAA,EACA,iBAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EACA,cAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,SAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA;AACF,CAAA;AAcA,IAAM,cAAA,GAAiB;AAAA,EACrB,IAAA,EAAM,kBAAA;AAAA,EACN,KAAA,EAAO,iBAAA;AAAA,EACP,EAAA,EAAI,gBAAA;AAAA,EACJ,CAAA,EAAG,eAAA;AAAA,EACH,CAAA,EAAG,eAAA;AAAA,EACH,CAAA,EAAG,eAAA;AAAA,EACH,EAAA,EAAI;AACN,CAAA;AAEA,SAAS,eAAA,CAAgB,OAAe,MAAA,EAAwB;AAC9D,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA;AACxB,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,4BAAA,CAA8B,CAAA;AAC9D,EAAA,OAAO,OAAO,MAAA,CAAO,EAAA,CAAG,OAAO,EAAE,CAAA,GAAI,IAAI,KAAK,CAAA;AAChD;AAEA,SAAS,0BAAA,CAA2B,OAAe,MAAA,EAAwB;AACzE,EAAA,MAAM,UAAA,GAAa,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AAChD,EAAA,IAAI,KAAA,GAAQ,CAAA;AACV,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,qCAAA,CAAuC,CAAA;AACvE,EAAA,OAAO,UAAA;AACT;AAEA,SAAS,0BAAA,CACP,IAAA,EACA,MAAA,EACA,OAAA,EACM;AACN,EAAA,IAAI,CAAC,6BAAA,CAA8B,IAAA,CAAK,IAAI,CAAA;AAC1C,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA,EAAG,MAAM,CAAA,kDAAA,EAAqD,OAAO,CAAA,CAAA;AAAA,KACvE;AACJ;AAEA,SAAS,SAAA,CAAU,KAAA,EAAe,IAAA,EAAc,MAAA,EAA2B;AACzE,EAAA,OAAO,GAAG,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAC,GAAG,IAAI,CAAA,CAAA;AACjD;AAGO,SAAS,GAAG,KAAA,EAA0B;AAC3C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,IAAA,EAAM,IAAI,CAAA;AACpC;AAGO,SAAS,IAAI,KAAA,EAA0B;AAC5C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAK,CAAA;AACtC;AAGO,SAAS,OAAO,KAAA,EAA0B;AAC/C,EAAA,OAAO,GAAA,CAAI,QAAQ,EAAE,CAAA;AACvB;AAGO,SAAS,GAAG,KAAA,EAA0B;AAC3C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,IAAA,EAAM,IAAI,CAAA;AACpC;AAGO,SAAS,IAAI,KAAA,EAA0B;AAC5C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,GAAA,EAAK,KAAK,CAAA;AACpC;AAGO,SAAS,MAAM,IAAA,EAA8B;AAClD,EAAA,MAAM,QAAA,GAAW,eAAe,IAAI,CAAA;AACpC,EAAA,IAAI,QAAA,KAAa,MAAA;AACf,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,KACvD;AACF,EAAA,OAAO,OAAO,QAAQ,CAAA,CAAA,CAAA;AACxB;AAOO,SAAS,SAAA,CACd,MACA,QAAA,EACW;AACX,EAAA,0BAAA,CAA2B,IAAA,EAAM,aAAa,WAAW,CAAA;AACzD,EAAA,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,EAAG,QAAA,KAAa,SAAY,EAAA,GAAK,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,CAAA,CAAA,CAAA;AACpE;AAGO,SAAS,IAAA,CACd,KAAA,EACA,MAAA,EAAA,GACG,IAAA,EACkB;AACrB,EAAA,OAAO,CAAC,KAAA,EAAO,MAAA,EAAQ,GAAG,IAAI,CAAA,CAAE,KAAK,KAAK,CAAA;AAC5C;AAGO,SAAS,KAAK,UAAA,EAA4C;AAC/D,EAAA,OAAO,QAAQ,UAAU,CAAA,CAAA,CAAA;AAC3B;AAGO,SAAS,IAAA,CACd,IAAA,EACA,MAAA,GAAS,CAAA,EACT,QAAsB,MAAA,EACb;AACT,EAAA,OAAO,CAAA,EAAG,0BAAA,CAA2B,IAAA,EAAM,MAAM,CAAC,CAAA,CAAA,EAAI,0BAAA,CAA2B,MAAA,EAAQ,MAAM,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AAC3G;AAeO,SAAS,QAAkC,IAAA,EAA2B;AAC3E,EAAA,IAAI,CAAE,YAAA,CAAmC,QAAA,CAAS,IAAI,CAAA;AACpD,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,KACvD;AACF,EAAA,OAAO,mBAAmB,IAAI,CAAA,CAAA,CAAA;AAChC;AAGO,SAAS,QAAA,CAAS,MAAqB,QAAA,EAA+B;AAC3E,EAAA,0BAAA,CAA2B,IAAA,EAAM,YAAY,aAAa,CAAA;AAC1D,EAAA,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,EAAG,QAAA,KAAa,SAAY,EAAA,GAAK,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,CAAA,CAAA,CAAA;AACpE;AAOO,SAAS,kBAAA,CACd,MACA,QAAA,EACoB;AACpB,EAAA,0BAAA,CAA2B,IAAA,EAAM,sBAAsB,kBAAkB,CAAA;AACzE,EAAA,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,EAAG,QAAA,KAAa,SAAY,EAAA,GAAK,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,CAAA,CAAA,CAAA;AACpE;AAMO,SAAS,gBAAgB,KAAA,EAAmC;AACjE,EAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,EAAK;AACzB,EAAA,IAAI,CAAC,KAAA,IAAS,iBAAA,CAAkB,IAAA,CAAK,KAAK,CAAA;AACxC,IAAA,MAAM,IAAI,UAAU,iDAAiD,CAAA;AACvE,EAAA,OAAO,KAAA;AACT","file":"chunk-LRAIS3YB.js","sourcesContent":["declare const cssValueBrand: unique symbol;\ndeclare const cssLengthBrand: unique symbol;\ndeclare const cssLengthExpressionBrand: unique symbol;\ndeclare const cssFlexBrand: unique symbol;\ndeclare const cssColorBrand: unique symbol;\ndeclare const cssForegroundColorBrand: unique symbol;\n\n/**\n * A complete typed CSS value minted by a property-specific Kerf UI builder.\n *\n * This brand is an authoring correctness aid, not a sanitizer or security\n * boundary. Prefer the narrower property grammar, such as {@link CssLength},\n * whenever one is available.\n */\nexport type CssValue = string & {\n readonly [cssValueBrand]: 'CssValue';\n};\n\n/**\n * A complete CSS length-percentage value suitable for dimension-valued UI\n * props. Despite the concise name, percentages are intentionally included.\n */\nexport type CssLength = CssValue & {\n readonly [cssLengthBrand]: 'CssLength';\n};\n\n/**\n * An incomplete arithmetic expression. Wrap it with {@link calc} before\n * passing it to a prop that accepts {@link CssLength}.\n */\nexport type CssLengthExpression = string & {\n readonly [cssLengthExpressionBrand]: 'CssLengthExpression';\n};\n\n/** A complete CSS `flex` shorthand. It is not interchangeable with a length. */\nexport type CssFlex = CssValue & {\n readonly [cssFlexBrand]: 'CssFlex';\n};\n\n/** A complete CSS color value. It is not interchangeable with a length. */\nexport type CssColor = CssValue & {\n readonly [cssColorBrand]: 'CssColor';\n};\n\n/**\n * A complete CSS color meant to paint a foreground: text or an icon drawn over\n * a surface. Mint it with {@link uiColor} and a foreground token name\n * ({@link UiForegroundColorName}) or with {@link foregroundColorVar}. It is a\n * {@link CssColor}, but a plain `CssColor` is not a foreground color: fill,\n * border, and surface tokens are pale backgrounds that leave a foreground\n * nearly invisible.\n */\nexport type CssForegroundColor = CssColor & {\n readonly [cssForegroundColorBrand]: 'CssForegroundColor';\n};\n\nexport type CssFlexKeyword = 'none' | 'auto' | 'initial';\nexport type CssFlexBasis =\n | CssLength\n | 'auto'\n | 'content'\n | 'min-content'\n | 'max-content'\n | 'fit-content';\nexport type CssSizeKeyword =\n 'auto' | 'min-content' | 'max-content' | 'fit-content';\n/** A complete width/height value accepted by dimension-valued UI props. */\nexport type CssSize = CssLength | CssSizeKeyword;\n\nconst uiColorNames = [\n 'brand-border-loud',\n 'brand-border-normal',\n 'brand-border-quiet',\n 'brand-fill-loud',\n 'brand-fill-normal',\n 'brand-fill-quiet',\n 'brand-on-fill',\n 'brand-on-loud',\n 'brand-on-normal',\n 'brand-on-quiet',\n 'danger-border-loud',\n 'danger-border-normal',\n 'danger-border-quiet',\n 'danger-fill-loud',\n 'danger-fill-normal',\n 'danger-fill-quiet',\n 'danger-on-fill',\n 'danger-on-loud',\n 'danger-on-normal',\n 'danger-on-quiet',\n 'neutral-border-loud',\n 'neutral-border-normal',\n 'neutral-border-quiet',\n 'neutral-fill-loud',\n 'neutral-fill-normal',\n 'neutral-fill-quiet',\n 'neutral-on-loud',\n 'neutral-on-normal',\n 'neutral-on-quiet',\n 'pop-border-loud',\n 'pop-border-normal',\n 'pop-border-quiet',\n 'pop-fill-loud',\n 'pop-fill-normal',\n 'pop-fill-quiet',\n 'pop-on-fill',\n 'pop-on-loud',\n 'pop-on-normal',\n 'pop-on-quiet',\n 'success-border-loud',\n 'success-border-normal',\n 'success-border-quiet',\n 'success-fill-loud',\n 'success-fill-normal',\n 'success-fill-quiet',\n 'success-on-fill',\n 'success-on-loud',\n 'success-on-normal',\n 'success-on-quiet',\n 'surface',\n 'surface-lowered',\n 'surface-raised',\n 'text',\n 'text-link',\n 'text-quiet',\n 'warning-border-loud',\n 'warning-border-normal',\n 'warning-border-quiet',\n 'warning-fill-loud',\n 'warning-fill-normal',\n 'warning-fill-quiet',\n 'warning-on-fill',\n 'warning-on-loud',\n 'warning-on-normal',\n 'warning-on-quiet',\n] as const;\n\n/** Names of the public `--kui-color-*` semantic tokens. */\nexport type UiColorName = (typeof uiColorNames)[number];\n\n/** The semantic tokens that paint a foreground: the `*-on-*` and text roles. */\nexport type UiForegroundColorName = Extract<\n UiColorName,\n `${string}-on-${string}` | 'text' | 'text-quiet' | 'text-link'\n>;\n\n/** Kerf UI's complete spacing-token vocabulary. `s` and `xl` are exceptions. */\nexport type UiSpaceName = 'none' | '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl';\n\nconst spaceVariables = {\n none: '--kui-space-none',\n '2xs': '--kui-space-2xs',\n xs: '--kui-space-xs',\n s: '--kui-space-s',\n m: '--kui-space-m',\n l: '--kui-space-l',\n xl: '--kui-space-xl',\n} as const satisfies Record<UiSpaceName, string>;\n\nfunction serializeNumber(value: number, helper: string): string {\n if (!Number.isFinite(value))\n throw new RangeError(`${helper}() requires a finite number.`);\n return String(Object.is(value, -0) ? 0 : value);\n}\n\nfunction serializeNonnegativeNumber(value: number, helper: string): string {\n const serialized = serializeNumber(value, helper);\n if (value < 0)\n throw new RangeError(`${helper}() requires nonnegative flex factors.`);\n return serialized;\n}\n\nfunction validateCustomPropertyName(\n name: string,\n helper: string,\n example: string,\n): void {\n if (!/^--[A-Za-z_][A-Za-z0-9_-]*$/.test(name))\n throw new TypeError(\n `${helper}() requires an ASCII custom property name such as ${example}.`,\n );\n}\n\nfunction dimension(value: number, unit: string, helper: string): CssLength {\n return `${serializeNumber(value, helper)}${unit}` as CssLength;\n}\n\n/** Create a complete pixel length. */\nexport function px(value: number): CssLength {\n return dimension(value, 'px', 'px');\n}\n\n/** Create a complete root-font-relative length. */\nexport function rem(value: number): CssLength {\n return dimension(value, 'rem', 'rem');\n}\n\n/** Converts a pixel value into rem units by dividing by 16. */\nexport function remify(value: number): CssLength {\n return rem(value / 16);\n}\n\n/** Create a complete current-font-relative length. */\nexport function em(value: number): CssLength {\n return dimension(value, 'em', 'em');\n}\n\n/** Create a complete percentage length. */\nexport function pct(value: number): CssLength {\n return dimension(value, '%', 'pct');\n}\n\n/** Resolve a Kerf UI spacing step to its public custom property. */\nexport function space(name: UiSpaceName): CssLength {\n const variable = spaceVariables[name];\n if (variable === undefined)\n throw new RangeError(\n `space() received unknown spacing name ${String(name)}.`,\n );\n return `var(${variable})` as CssLength;\n}\n\n/**\n * Reference an application-owned custom property whose contract is a CSS\n * length-percentage. The deliberately narrow name grammar keeps this helper\n * from becoming an arbitrary CSS-string constructor.\n */\nexport function lengthVar(\n name: `--${string}`,\n fallback?: CssLength,\n): CssLength {\n validateCustomPropertyName(name, 'lengthVar', '--app-gap');\n return `var(${name}${fallback === undefined ? '' : `, ${fallback}`})` as CssLength;\n}\n\n/** Combine two or more complete lengths into a non-standalone sum. */\nexport function plus(\n first: CssLength,\n second: CssLength,\n ...rest: readonly CssLength[]\n): CssLengthExpression {\n return [first, second, ...rest].join(' + ') as CssLengthExpression;\n}\n\n/** Turn a typed length expression into a complete CSS `calc()` value. */\nexport function calc(expression: CssLengthExpression): CssLength {\n return `calc(${expression})` as CssLength;\n}\n\n/** Build a complete, structured CSS flex shorthand. */\nexport function flex(\n grow: number,\n shrink = 1,\n basis: CssFlexBasis = 'auto',\n): CssFlex {\n return `${serializeNonnegativeNumber(grow, 'flex')} ${serializeNonnegativeNumber(shrink, 'flex')} ${basis}` as CssFlex;\n}\n\n/**\n * The brand {@link uiColor} returns for a token name: a\n * {@link CssForegroundColor} for a foreground token, otherwise a plain\n * {@link CssColor}.\n */\nexport type UiColor<Name extends UiColorName> =\n Name extends UiForegroundColorName ? CssForegroundColor : CssColor;\n\n/**\n * Resolve a public Kerf UI semantic color token. A foreground token name\n * (`success-on-quiet`, `text-quiet`, …) returns a {@link CssForegroundColor};\n * any other token returns a plain {@link CssColor}.\n */\nexport function uiColor<Name extends UiColorName>(name: Name): UiColor<Name> {\n if (!(uiColorNames as readonly string[]).includes(name))\n throw new RangeError(\n `uiColor() received unknown color name ${String(name)}.`,\n );\n return `var(--kui-color-${name})` as UiColor<Name>;\n}\n\n/** Reference an application-owned custom property whose contract is a color. */\nexport function colorVar(name: `--${string}`, fallback?: CssColor): CssColor {\n validateCustomPropertyName(name, 'colorVar', '--app-color');\n return `var(${name}${fallback === undefined ? '' : `, ${fallback}`})` as CssColor;\n}\n\n/**\n * Reference an application-owned custom property whose contract is a\n * foreground color (text or an icon over a surface). Use it where a prop\n * accepts only {@link CssForegroundColor}, such as `SelectChoice.color`.\n */\nexport function foregroundColorVar(\n name: `--${string}`,\n fallback?: CssForegroundColor,\n): CssForegroundColor {\n validateCustomPropertyName(name, 'foregroundColorVar', '--app-icon-color');\n return `var(${name}${fallback === undefined ? '' : `, ${fallback}`})` as CssForegroundColor;\n}\n\n/**\n * Use an application-chosen CSS color for foreground content. The browser\n * resolves the color syntax; this checks only that it is one declaration value.\n */\nexport function foregroundColor(value: string): CssForegroundColor {\n const color = value.trim();\n if (!color || /[;{}<>]|\\p{Cc}/u.test(color))\n throw new TypeError('foregroundColor() requires one CSS color value.');\n return color as CssForegroundColor;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/collections/list/list.tsx"],"names":[],"mappings":";;;;;AAiBA,IAAM,uBAAA,uBAA8B,GAAA,CAAI;AAAA,EACtC,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,iBAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAgBD,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAiDO,SAAS,IAAA,CAAK;AAAA,EACnB,QAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAA,GAAM,KAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,KAAA;AAAA,EACT,UAAA,GAAa,KAAA;AAAA,EACb,YAAA,GAAe,EAAA;AAAA,EACf,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY,EAAA;AAAA,EACZ,iBAAiB,EAAC;AAAA,EAClB;AACF,CAAA,EAAc;AACZ,EAAA,IAAI,aAAA,KAAkB,UAAA,IAAc,CAAC,SAAA,EAAW;AAC9C,IAAA,MAAM,IAAI,MAAM,yCAAyC,CAAA;AAAA,EAC3D;AACA,EAAA,MAAM,kBAAA,GAAqB,oBAAA;AAAA,IACzB,cAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,IAAA,GACJ,qBAAA,GACA,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACpC,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA,IAAO,MAAA;AACf,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,QAAA,GAAW,CAAA,gBAAA,EAAmB,QAAQ,CAAA,CAAA,GAAK,EAAA;AAAA,IAC3C,SAAA,GAAY,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,GAAK;AAAA,GAChD,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACE,GAAG,kBAAA;AAAA,MACJ,KAAA,EAAO,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACpC,gBAAA,EAAe,MAAA;AAAA,MACf,IAAA,EAAM,aAAA,KAAkB,UAAA,GAAa,MAAA,GAAS,MAAA;AAAA,MAC9C,YAAA,EAAY,aAAA,KAAkB,UAAA,GAAa,SAAA,GAAY,MAAA;AAAA,MACvD,sBAAA,EAAsB,aAAA,KAAkB,UAAA,GAAa,MAAA,GAAS,MAAA;AAAA,MAC9D,UAAA,EAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAC,CAAA;AAAA,MAC7B,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,iBAAA,EAAiB,OAAO,UAAU,CAAA;AAAA,MAClC,iBAAe,YAAA,IAAgB,MAAA;AAAA,MAC/B,oBAAkB,UAAA,IAAc,MAAA;AAAA,MAChC,uBAAqB,aAAA,IAAiB,MAAA;AAAA,MACtC,OAAO,KAAA,IAAS,MAAA;AAAA,MAChB,IAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-NVRJ7C3W.js","sourcesContent":["import type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { filterDataAttributes } from '../../../shared/dom/extension-attributes.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type ListVerticalAlignment,\n verticalAlignment,\n} from '../../../shared/layout/flex-alignment.js';\nimport type { Sides } from '../../../shared/layout/sides.js';\nimport {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from '../../../shared/styles/css-values.js';\n\nconst listProtectedAttributes = new Set([\n 'data-component',\n 'data-gap',\n 'data-flex',\n 'data-fill',\n 'data-h-align',\n 'data-v-align',\n 'data-scrollable',\n 'data-text-insets',\n 'data-control-insets',\n]);\n\ntype ListRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-gap'?: never;\n 'data-flex'?: never;\n 'data-fill'?: never;\n 'data-h-align'?: never;\n 'data-v-align'?: never;\n 'data-scrollable'?: never;\n 'data-text-insets'?: never;\n 'data-control-insets'?: never;\n }\n>;\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface ListProps {\n children?: KerfUiContent;\n /** Make this list a labeled multi-select grid for `ContentItem selectionMode=\"multiple\"` rows. */\n selectionMode?: 'multiple';\n /** Accessible name for the multi-select grid. */\n ariaLabel?: string;\n /** Use the standard item gap, a named UI spacing token, or a typed CSS length. Defaults to no gap. */\n gap?: boolean | UiSpaceName | CssLength;\n /** Allow this list to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /**\n * Fill the height of a parent with a definite height, such as an app root or\n * a fixed-height frame, when this list is that parent's layout root. Inside a\n * flex layout use `flex` instead. Defaults to false.\n */\n fill?: boolean;\n /** Horizontal alignment. Defaults to full to preserve stretch-aligned list children. */\n hAlign?: HorizontalAlignment;\n /** Vertical distribution. Defaults to top. */\n vAlign?: ListVerticalAlignment;\n /** Own vertical scrolling and overscroll containment. */\n scrollable?: boolean;\n /** Physical divider edges in canonical top/right/bottom/left order. */\n dividerSides?: Sides;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n /** Safe `data-*` metadata; List-owned structural attributes remain protected. */\n rootAttributes?: ListRootAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A list's configuration, apart from its content and styling hooks: the props a\n * composite that renders a `List` for the app (the Workbench's `mainHeader` /\n * `mainFooter` chrome) forwards, so the app configures that list instead of\n * styling it. An omitted or `undefined` field keeps the composite's default.\n */\nexport type ListConfig = Pick<\n ListProps,\n 'gap' | 'hAlign' | 'vAlign' | 'dividerSides' | 'textInsets' | 'controlInsets'\n>;\n\n/** A stretch-aligned vertical stack with optional gap, flex, fill, scroll, and dividers. */\nexport function List({\n children,\n selectionMode,\n ariaLabel,\n gap = false,\n flex = false,\n fill = false,\n hAlign = 'full',\n vAlign = 'top',\n scrollable = false,\n dividerSides = '',\n textInsets = '',\n controlInsets = '',\n className = '',\n rootAttributes = {},\n slot,\n}: ListProps) {\n if (selectionMode === 'multiple' && !ariaLabel) {\n throw new Error('Multi-select List requires an ariaLabel');\n }\n const safeRootAttributes = filterDataAttributes(\n rootAttributes,\n listProtectedAttributes,\n );\n const gapValue =\n gap === true\n ? 'var(--kui-list-gap)'\n : spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap || undefined;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n gapValue ? `--_kui-list-gap:${gapValue}` : '',\n flexValue ? `--_kui-list-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n {...safeRootAttributes}\n class={`kui-list ${className}`.trim()}\n data-component=\"list\"\n role={selectionMode === 'multiple' ? 'grid' : undefined}\n aria-label={selectionMode === 'multiple' ? ariaLabel : undefined}\n aria-multiselectable={selectionMode === 'multiple' ? 'true' : undefined}\n data-gap={String(Boolean(gap))}\n data-flex={String(Boolean(flex))}\n data-fill={fill ? 'true' : undefined}\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-scrollable={String(scrollable)}\n divider-sides={dividerSides || undefined}\n data-text-insets={textInsets || undefined}\n data-control-insets={controlInsets || undefined}\n style={style || undefined}\n slot={slot}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n HorizontalAlignment,\n ListVerticalAlignment,\n VerticalAlignment,\n} from '../../../shared/layout/flex-alignment.js';\nexport type { Sides } from '../../../shared/layout/sides.js';\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from '../../../shared/styles/css-values.js';\n"]}