@kerfjs/ui 5.0.0-beta.76 → 5.0.0-beta.78

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 (147) hide show
  1. package/README.md +15 -0
  2. package/ai/application-ui-diagnostic-ids-v1.json +3 -0
  3. package/ai/component-catalog.json +333 -28
  4. package/ai/component-composition.json +224 -17
  5. package/ai/components/nav-stack.md +4 -3
  6. package/ai/components/select.md +1 -0
  7. package/ai/components/split-view.md +1 -0
  8. package/ai/components/surface-scaffold.md +11 -2
  9. package/ai/components/text.md +4 -2
  10. package/ai/components/toolbar-control-group.md +22 -3
  11. package/ai/components/toolbar-text.md +21 -4
  12. package/ai/components/toolbar.md +12 -4
  13. package/ai/public-api-signatures-v1.md +99 -127
  14. package/ai/skill.md +37 -3
  15. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  16. package/dist/browser/nav-stack.js +2 -0
  17. package/dist/browser/split-view.js +2 -0
  18. package/dist/catalog-resources.d.ts +3 -3
  19. package/dist/catalog.d.ts +3 -3
  20. package/dist/catalog.js +6 -6
  21. package/dist/chunk-2GOCOLQL.js +270 -0
  22. package/dist/chunk-2GOCOLQL.js.map +1 -0
  23. package/dist/{chunk-ZVYA6TLR.js → chunk-4Y4L5YEI.js} +3 -1
  24. package/dist/chunk-4Y4L5YEI.js.map +1 -0
  25. package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
  26. package/dist/chunk-A7DYEERU.js.map +1 -0
  27. package/dist/{chunk-7QWIJEK5.js → chunk-DSLRNR43.js} +9 -8
  28. package/dist/chunk-DSLRNR43.js.map +1 -0
  29. package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
  30. package/dist/chunk-DXQZ3BEO.js.map +1 -0
  31. package/dist/chunk-IKCUPTR2.js +170 -0
  32. package/dist/chunk-IKCUPTR2.js.map +1 -0
  33. package/dist/{chunk-HGSTMZZ6.js → chunk-JL4BBZP4.js} +5 -4
  34. package/dist/chunk-JL4BBZP4.js.map +1 -0
  35. package/dist/{chunk-IVLTEBBT.js → chunk-M6BWQMQY.js} +2 -2
  36. package/dist/{chunk-IVLTEBBT.js.map → chunk-M6BWQMQY.js.map} +1 -1
  37. package/dist/{chunk-72LLWYP2.js → chunk-NVKRZ5DI.js} +1 -1
  38. package/dist/chunk-NVKRZ5DI.js.map +1 -0
  39. package/dist/{chunk-GO2CZX4M.js → chunk-RRCD3A6G.js} +13 -5
  40. package/dist/chunk-RRCD3A6G.js.map +1 -0
  41. package/dist/{chunk-5PAVAR4B.js → chunk-VHXHLZ6R.js} +10 -1
  42. package/dist/chunk-VHXHLZ6R.js.map +1 -0
  43. package/dist/collapsible-panel.js +2 -2
  44. package/dist/index.d.ts +4 -3
  45. package/dist/index.js +9 -8
  46. package/dist/list-header.d.ts +1 -0
  47. package/dist/list-header.js +1 -1
  48. package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
  49. package/dist/nav-stack.d.ts +12 -93
  50. package/dist/nav-stack.js +5 -1
  51. package/dist/pane.d.ts +5 -2
  52. package/dist/pane.js +1 -1
  53. package/dist/select-register.js +3 -0
  54. package/dist/select-register.js.map +1 -1
  55. package/dist/select.d.ts +1 -0
  56. package/dist/select.js +1 -1
  57. package/dist/split-view.d.ts +9 -4
  58. package/dist/split-view.js +7 -3
  59. package/dist/split-view.js.map +1 -1
  60. package/dist/styles/components/actions/toolbar/toolbar.css +32 -0
  61. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +105 -7
  62. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +17 -0
  63. package/dist/styles/components/layout/pane/pane.css +7 -0
  64. package/dist/styles/components/navigation/nav-stack/nav-stack.css +17 -12
  65. package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +17 -0
  66. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
  67. package/dist/styles/components/typography/text/text.css +19 -0
  68. package/dist/styles/nav-stack.css +17 -12
  69. package/dist/styles/pane.css +7 -0
  70. package/dist/styles/surface-scaffold.css +55 -0
  71. package/dist/styles/tab-scaffold.css +17 -0
  72. package/dist/styles/text.css +19 -0
  73. package/dist/styles/toolbar-control-group.css +105 -7
  74. package/dist/styles/toolbar-text.css +17 -0
  75. package/dist/styles/toolbar.css +32 -0
  76. package/dist/surface-scaffold-register.d.ts +4 -0
  77. package/dist/surface-scaffold-register.js +59 -0
  78. package/dist/surface-scaffold-register.js.map +1 -0
  79. package/dist/surface-scaffold.d.ts +10 -2
  80. package/dist/surface-scaffold.js +1 -1
  81. package/dist/tab-scaffold.d.ts +2 -0
  82. package/dist/tab-scaffold.js +1 -0
  83. package/dist/tab-scaffold.js.map +1 -1
  84. package/dist/text.d.ts +5 -2
  85. package/dist/text.js +1 -1
  86. package/dist/toolbar-control-group.d.ts +14 -2
  87. package/dist/toolbar-control-group.js +1 -1
  88. package/dist/toolbar-text.d.ts +10 -2
  89. package/dist/toolbar-text.js +1 -1
  90. package/dist/wire-nav-stack.js +58 -22
  91. package/dist/wire-nav-stack.js.map +1 -1
  92. package/dist/wire-toolbar-visibility.d.ts +13 -0
  93. package/dist/wire-toolbar-visibility.js +3 -0
  94. package/dist/wire-toolbar-visibility.js.map +1 -0
  95. package/dist/workbench.d.ts +5 -13
  96. package/dist/workbench.js +1 -1
  97. package/docs/accessibility.md +15 -0
  98. package/docs/collapsible-panel.md +4 -0
  99. package/docs/component-contract.md +38 -0
  100. package/docs/component-selection.md +56 -2
  101. package/docs/design/templates/toolbar/width-visibility-320-dark.svg +1 -0
  102. package/docs/design/templates/toolbar/width-visibility-320.svg +1 -0
  103. package/docs/design/templates/toolbar/width-visibility-600-dark.svg +1 -0
  104. package/docs/design/templates/toolbar/width-visibility-600.svg +1 -0
  105. package/docs/design/templates/toolbar-control-group/icon-tile-brand-dark.svg +1 -0
  106. package/docs/design/templates/toolbar-control-group/icon-tile-brand.svg +1 -0
  107. package/docs/design/templates/toolbar-control-group/icon-tile-danger-dark.svg +1 -0
  108. package/docs/design/templates/toolbar-control-group/icon-tile-danger.svg +1 -0
  109. package/docs/design/templates/toolbar-control-group/icon-tile-neutral-dark.svg +1 -0
  110. package/docs/design/templates/toolbar-control-group/icon-tile-neutral.svg +1 -0
  111. package/docs/design/templates/toolbar-control-group/icon-tile-success-dark.svg +1 -0
  112. package/docs/design/templates/toolbar-control-group/icon-tile-success.svg +1 -0
  113. package/docs/design/templates/toolbar-control-group/icon-tile-warning-dark.svg +1 -0
  114. package/docs/design/templates/toolbar-control-group/icon-tile-warning.svg +1 -0
  115. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  116. package/docs/design/templates/toolbar-control-group.svg +1 -1
  117. package/docs/design/templates/toolbar-dark.svg +1 -1
  118. package/docs/design/templates/toolbar-text/dark-tone-dark.svg +1 -0
  119. package/docs/design/templates/toolbar-text/dark-tone.svg +1 -0
  120. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  121. package/docs/design/templates/toolbar-text.svg +1 -1
  122. package/docs/design/templates/toolbar.svg +1 -1
  123. package/docs/design/templates.md +2 -0
  124. package/docs/layout.md +32 -1
  125. package/docs/nav-stack.md +35 -19
  126. package/docs/recipes.md +5 -1
  127. package/docs/split-view.md +7 -4
  128. package/docs/surface-scaffold.md +46 -0
  129. package/docs/tab-scaffold.md +57 -0
  130. package/docs/ui-doctor.md +23 -0
  131. package/docs/workbench.md +3 -2
  132. package/doctor/component-style-checks.mjs +224 -0
  133. package/doctor/index.mjs +42 -13
  134. package/llms.txt +34 -0
  135. package/package.json +11 -2
  136. package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
  137. package/ux-demo/recipes/navigation-stack.tsx +2 -2
  138. package/dist/chunk-5PAVAR4B.js.map +0 -1
  139. package/dist/chunk-72LLWYP2.js.map +0 -1
  140. package/dist/chunk-7QWIJEK5.js.map +0 -1
  141. package/dist/chunk-AOAW55JN.js.map +0 -1
  142. package/dist/chunk-GO2CZX4M.js.map +0 -1
  143. package/dist/chunk-HGSTMZZ6.js.map +0 -1
  144. package/dist/chunk-SJY7KC45.js +0 -123
  145. package/dist/chunk-SJY7KC45.js.map +0 -1
  146. package/dist/chunk-XIZRXI7N.js.map +0 -1
  147. package/dist/chunk-ZVYA6TLR.js.map +0 -1
package/ai/skill.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: kerf-ui
3
3
  description: Build interfaces with kerfjs and the @kerfjs/ui production component package. Use whenever code imports @kerfjs/ui or a task asks for Kerf UI components.
4
- kerf-ui-skill-version: 1.51.0
4
+ kerf-ui-skill-version: 1.55.0
5
5
  ---
6
6
 
7
7
  # Building with @kerfjs/ui
@@ -250,7 +250,8 @@ Hard rules:
250
250
  13. The Web Awesome theme makes Tooltip and Popover arrowless by default. Keep that default unless a pointer materially clarifies the anchor; opt back in with `--wa-tooltip-arrow-size`, `--kui-wa-popover-arrow-size`, or a popover's public `--arrow-size`, and use `without-arrow` when local no-arrow intent should survive theme changes. Its non-field chrome uses `--kui-wa-control-inset` (8px controls/items), `--kui-wa-surface-margin` / `--kui-wa-surface-inset` (8px around/inside Accordion, Card, Details, Callout, and Include), and `--kui-wa-container-inset` (16px Tab Panel content and framed disclosure regions). Plain Details use the 8px surface inset on the trigger and keep the body flush; plain Accordion items use it on both trigger and body. Outlined and sunken Details use the 16px container inset on the trigger and 8px on the body; framed Accordion items use 16px on both. Dialog maps its body to the 8px surface tier and its footer to the 16px container tier independently of `--spacing`. Override those tiers instead of restyling individual parts. Filled badges use each variant's quiet fill with normal text so their compact copy stays WCAG-AA readable; override the pair with `--kui-wa-badge-filled-background` / `--kui-wa-badge-filled-foreground`, not host selectors. When a dialog provides another dismissal affordance, put `hide-actions` on the `wa-dialog` host to hide its directly exported `header-actions` part; never chain `::part()` selectors through internal parts. Checkbox and Radio Group option regions, the Color Picker trigger, and Slider's complete interactive region receive the shared 8px logical inline outer inset because those controls have no bordered field shell. Known Date field captions and bordered text-like field hints align with values at the 9px border-plus-padding inset. OTP Input's label uses the same uppercase xs/650 treatment as other field labels, and both its label and hint use that 9px inset.
251
251
  14. Treat the complete Web Awesome catalog as support coverage, not a recommendation list. Its UX sidebar marks superseded and exceptional entries `Discouraged`. Consider Popup when it replaces custom anchored positioning. Prefer Kerf `Select` over direct Dropdown/Dropdown Item/Select/Option composition, `SegmentedControl` over Button Group, `TabBar` or `SegmentedControl` over Web Awesome Tabs, `LucideIcon` over Web Awesome Icon, and `ResizableRegion` over Split Panel. Use Tree/Tree Item, Animated Image, and Comparison only for a specific required behavior; avoid Zoomable Frame. Never render a `Discouraged` element directly in application, demo, or recipe markup; reach it only through the Kerf component that wraps it (`PopupMenu` renders `wa-dropdown`, `Select` renders `wa-select` / `wa-option`), and treat a Kerf component gap as a request to file, not a reason to drop to the raw element.
252
252
  15. Build sidebars, main areas, inspectors, and dialogs by rendering `Pane` (`@kerfjs/ui/pane`): an unpadded `.kui-pane` root, an optional `header`, one scrolling `.kui-pane__content`, and an optional `footer`; pick landmarks with `element` / `contentElement`. Never write a component's anatomy classes (`kui-pane`, `kui-toolbar`, `kui-list-item`, …) onto your own element to recreate it — render the component (`KUI-L103`). Only the catalog's `placeableClasses` (the `.kui-content` / `.kui-scroll-owner` / `.kui-control-cluster` / `.kui-inline-metadata` layout utilities, `.kui-app-root`, and `.kui-content-item` geometry on a non-div carrier) belong on elements you write. Use `Row` instead of a hand-written flex-row wrapper, `List` instead of a hand-written flex-column wrapper, and `Grid` instead of repeated equal-track CSS when children need a fixed positive count of evenly distributed columns. `Row` defaults to left/full with an `xs` gap and no wrapping; Grid defaults to an `xs` gap and keeps each track at `minmax(0, 1fr)`; List retains full/top and zero-gap defaults. The app owns Grid's responsive column-count changes. When a Row, List, or Grid is the layout root of a definite-height parent (an app root or a fixed-height frame), pass `fill` so it takes that full height, and put route/test/recipe `data-*` markers in its `rootAttributes`; never wrap a layout in a frame `Pane` just for height or a root attribute, because its content slot becomes a scroll owner that never scrolls. Inside a flex layout use `flex`, not `fill`. Use `List` when rows or sections also need flex growth, one vertical scroll owner, or physical-edge dividers. `dividerSides`, `textInsets`, and `controlInsets` use canonical physical `t`/`r`/`b`/`l` order; text geometry wins where text and control selections overlap. `ListInsetText` and `ListInsetControl` inset all four sides by default and accept the same `sides` grammar for selective edges. A dialog body is generally a `List`: set `DialogSurface bodyInset="none"` when its list children own geometry, and wrap bare dialog prose in `ListInsetText` so it aligns with bordered siblings without a second inset. Add `.kui-content` for 24px major vertical separation and render each ordinary surface-like child as `ContentItem` (`@kerfjs/ui/content-item`) for a child-owned 8px inline margin, 1px border, 8px padding, and 12px radius. Choose `frame="framed"` (neutral border in the always-reserved 1px, same geometry) only for a real distinction and `shape="pill"` for 22px; do not hand-write `<div class="kui-content-item">` or its modifier classes. There is no filled variant: a lowered filled surface is `SunkenPanel`. Do not pad pane shells, duplicate item geometry in wrappers, or create nested scroll owners. Align text to text and icons to icons across a pane — a title, a row label, and bare prose share the same text column (`ListInsetText` puts bare text on it); a border is decoration and never the alignment anchor, so a bordered surface extends to the pane gutter instead of being indented to line its border up with a heading's text.
253
- 15a. Use `Text` from `@kerfjs/ui/text` for ordinary headings, paragraphs, and inline secondary text outside toolbar title zones. Omit `variant` for the default `p`, pass `h1`–`h6` according to the document outline, or use `variant="span"` inside a row, label, or table cell; ordinary native attributes pass through. Express supporting, error, compact, or code text with the independent finite `tone="quiet|danger"`, `size="compact"`, and `font="monospace"` props instead of global utility classes or appearance-only heading choices. Block Text variants own a transparent 1px border and the standard item padding; `span` owns no box geometry. Keep toolbar identity/title content on `ToolbarText` and list-aligned bare prose on `ListInsetText`. `ToolbarText` below `xlarge` is intentionally quiet identity text with no tone option; a normal-color subsection title is `Text` with an `h2`–`h6` variant (or `ListHeader` for a list or menu section), never a recolored `ToolbarText`.
253
+ 15a. Use `Text` from `@kerfjs/ui/text` for ordinary headings, paragraphs, and inline secondary text outside toolbar title zones. Omit `variant` for the default `p`, pass `h1`–`h6` according to the document outline, or use `variant="span"` inside a row, label, or table cell; ordinary native attributes pass through. Express supporting, error, compact, or code text with the independent finite `tone="quiet|danger"`, `size="compact"`, and `font="monospace"` props instead of global utility classes or appearance-only heading choices. Block Text variants own a transparent 1px border and the standard item padding; `span` owns no box geometry. Keep toolbar identity/title content on `ToolbarText` and list-aligned bare prose on `ListInsetText`. `ToolbarText` below `xlarge` is intentionally quiet identity text in default tone; a normal-color subsection title is `Text` with an `h2`–`h6` variant (or `ListHeader` for a list or menu section), never a recolored `ToolbarText`. Use `controlMargins` with canonical physical Sides for an exterior 8px control gutter without a Row wrapper; avoid stacking parent insets, and remember spans retain native inline margin behavior.
254
+
254
255
  16. Keep a visible collapsible pane's collapse control in its own toolbar. When hidden, move its restore control into the adjacent main toolbar on the corresponding logical edge: leading for an inline-start sidebar and trailing for an inline-end inspector. Collapse the pane completely; do not preserve an empty icon-only rail.
255
256
  16a. Dividers between pinned chrome and scrolling content are component chrome: a `Toolbar` draws none by default (`dividerSides` is for a permanent edge only). Call `wireScrollDividers(appRoot)` from `@kerfjs/ui/wire-scroll-dividers` once and retain its disposer; a plain `Pane` with `chromeDividers="scroll"` draws the line under its header only while content is scrolled beneath it and over its footer only while more content lies below (never when the content fits), every `NavStack`'s top chrome and bottom toolbar and every `TabScaffold` bar do the same around the active view or scene (keyed on whatever actually scrolls there, including a sole `Pane`'s content), and every `TabBar` marks each side of its strip with tabs scrolled out of view. Configure a pane with `chromeDividers` (`scroll` by default for plain panes, `always` by default for sunken panes, or explicit `scroll`, `always`, `none`, also via `PaneConfig`); `always` draws both lines even when content fits. A `NavStack` or `TabScaffold` has its own `chromeDividers` (same values: `always` draws the chrome lines without the wiring, `none` never, even when wired; a `SplitView` forwards it through `compactStack`); a `toolbarConfig.dividerSides` edge stays the Toolbar's own. Name app-owned chrome by id in `targets`. The wiring writes only `data-scroll-overflow` / `data-scroll-divider` state; never write a border onto a component to show a divider.
256
257
  17. The direct children of a `Toolbar` zone are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; `trailing` also accepts one standalone primary `wa-button` that keeps its own Web Awesome chrome. Never drop other bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and other Web Awesome controls live inside a group. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button. By default a group stays on one row: each icon sits beside its label, and the toolbar's `responsive` policy relocates whole groups when needed. Set `overflow="wrap"` only for independent links that should move as whole actions to another row within the group; use `overflow="scroll"` for a bounded one-row action strip. A `ToolbarText` is one line ending in an ellipsis by default; opt into `wrap` (capped by `maxLines`) or `ellipsis={false}` deliberately. Inside a group, a `Select` or `PopupMenu` trigger drops its focus ring while its popup is open (the open popup and its current row show where focus is) and the ring returns when the popup closes; a ring follows the control's shape (a pill for an icon-and-caret trigger, a circle for a compact icon button) — set `focusRingOwner="group"` on a Select whose group paints the composed ring, and never add CSS to show, hide, or reshape a ring. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A non-interactive heading icon is an icon tile: put the `LucideIcon` directly in its `ToolbarControlGroup` (it takes an icon button's 40px slot and centers), never inside a `<button>` that does nothing. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Each zone item sits in the toolbar's top 44px control band, centered by its own size; a taller item (a wrapped title, a second trailing row) starts at the band's top and grows down without moving the others, so a wrapped `ToolbarText` keeps trailing controls beside its first line — do not add alignment CSS for this. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `width="content"` to shrink-wrap while retaining its normal geometry, or `inline` when it belongs in surrounding text without outer margin, border, or padding. It keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square whose transparent hit layer keeps the pointer target at least 44px (44px wide by the 36px row in `density="compact"`); never shrink the target to match the visual, and never force the button to 44px. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets. Dividers are chrome and earn their place: a `Toolbar` draws none by default, and the line between pinned chrome and scrolling content comes from scroll state (rule 16a), so never add a border, a permanent `dividerSides`, or a margin to fake or adjust one.
@@ -266,7 +267,7 @@ For a rail view heading, use `presentation="title"` for the 36px bold,
266
267
  flush-start trigger and adjacent caret. Set `focusRingInset` when an ancestor
267
268
  clips focus painting; never restyle Select shadow parts from application CSS.
268
269
 
269
- 19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset. 20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling. 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap`, `Grid.gap`, or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale". 22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. The stack's top chrome is a real `Toolbar`: put a view's groups on `NavStackView.leading` / `center` / `toolbar` (trailing), and configure the bar through `toolbarConfig` (`ToolbarConfig` plus `label`, `titleSize`, `headingLevel`) and `backIcon` / `backText`, never with CSS. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (`leading`, `center`, `toolbar`, `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A list-detail dialog is a Workbench-shaped layout on roomy devices: a full-height list sidebar and a full-height main column, each a `Pane` with its own top `Toolbar`; the main column holds the primary xl title, the close control (`data-dialog="close"` in a `without-header` Web Awesome dialog), and the record actions, never a header spanning both columns. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
270
+ 19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset. 20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling. 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap`, `Grid.gap`, or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale". 22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. The stack's top chrome is a real `Toolbar`: put a view's title and groups on `NavStackView.toolbar` (`title`, `leading`, `center`, `trailing`), where its Toolbar configuration overrides stack-wide `toolbarConfig` defaults; configure `backIcon` / `backText` through the stack, never with CSS. Each view and the stack fallback use a structured `bottomToolbar` with an accessible `label`; a view can also provide `header` / `footer`, `headerList` / `footerList`, and `pane` configuration, which render a Pane around that view's scrolling content. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (structured `toolbar` and `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A list-detail dialog is a Workbench-shaped layout on roomy devices: a full-height list sidebar and a full-height main column, each a `Pane` with its own top `Toolbar`; the main column holds the primary xl title, the close control (`data-dialog="close"` in a `without-header` Web Awesome dialog), and the record actions, never a header spanning both columns. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
270
271
 
271
272
  For application tabs, `AppTab presentation="icon-only"` retains its accessible `name`, `labelMaxWidth` truncates the visible name, and `attention` colors it through `--kui-app-tab-attention-color`. Configure TabBar strip geometry through public `--kui-tab-bar-strip-*` tokens.
272
273
 
@@ -291,6 +292,8 @@ When importing the opt-in `@kerfjs/ui/document.css` baseline, native `th`, `td`,
291
292
 
292
293
  For a read-only field preview that replaces a Web Awesome input, use `FieldLabel` from `@kerfjs/ui/text` above the preview. Put it in `ListInsetControl sides="trl"` beside a `ContentItem` value; give it an id and use `aria-labelledby` on the preview group. The app owns preview/edit state and hint copy.
293
294
 
295
+ `ToolbarControlGroup.tileTone` selects `neutral`, `brand`, `success`, `warning`, or `danger` quiet fill, matching quiet border, and on-quiet foreground for a non-interactive icon tile. Compose a contained `single` group whose only child is a direct decorative LucideIcon (`aria-hidden="true"`). Omission preserves the normal group palette. The prop does not recolor interactive, mixed, multi-control, or borderless groups; keep real actions in controls with their existing focus/hover/pressed treatments.
296
+
294
297
  Common mistakes:
295
298
 
296
299
  | Mistake | Fix |
@@ -351,3 +354,34 @@ remain with Web Awesome. Do not add popup delays, position repairs, or animation
351
354
  completion handlers in consumers. Raw `wa-select` elements remain native.
352
355
 
353
356
  Neutral lowered surfaces and decorative neutral fills/borders are translucent theme colors; compose nested public components directly. Default/raised canvases, foregrounds, focus indicators, and loud contrast-bearing fills remain opaque. Do not assign depth-specific component tokens in demo wrappers.
357
+
358
+ Use `ToolbarText tone="dark"` on a loud or photo-backed toolbar surface to
359
+ match dark control groups. It applies to read-only headings and actionable
360
+ titles at every size; default tone retains existing identity and heading colors.
361
+
362
+ Navigation transition continuity: `wireNavStack` slides the incoming view over the preserved previous view on push and the outgoing view off on pop, cross-fades both live chrome surfaces with inert snapshots, and settles/cancels an interrupted transition before starting the next. Reduced motion and duration zero settle immediately.
363
+
364
+ For a tab scene with drill-down, place a `NavStack` directly in `TabScaffoldTab.content`. Give its views structured `toolbar` and `pane` configuration and omit both stack and view `bottomToolbar`; the scaffold bar remains the only bottom chrome. Wire the scaffold and stack on their respective roots. See `docs/tab-scaffold.md` and the catalog's **NavStack inside a tab** example.
365
+
366
+ Use `Pane.deepInset` (also in `PaneConfig`) or `TabScaffoldTab.deepInset` for a plain scene when its scrolling content needs a 16px outer gutter. The extra 8px is applied to the scroll surface, while nested Row/List/Grid item margins stay 8px. When a tab scene is a sole Pane or NavStack, configure deep inset on that child's Pane; the tab bar and top toolbar stay full width.
367
+
368
+ Expanded search groups accept `expandedOverflow="visible"` to anchor app-owned suggestion/date/help surfaces below their relative root; collapsed/default groups retain existing clipping. Apps own surface placement/dismissal and use `data-token-search-keep-open` or `collapsible.keepOpenOn` for focus.
369
+
370
+ `ToolbarText` and `ToolbarControlGroup` accept typed `hideBelow`/`showBelow`
371
+ lengths (for example `px(416)`). Wire `wireToolbarVisibility(root)` from
372
+ `@kerfjs/ui/wire-toolbar-visibility` and dispose on unmount. Thresholds use the
373
+ nearest Toolbar content-box width: hideBelow hides strictly below, showBelow
374
+ shows strictly below, with complementary behavior at equality. Percentages use
375
+ that content width; em and custom properties use the item's own context. Invalid,
376
+ negative, unresolved, or contradictory values leave the item visible under this
377
+ width policy. Keep primary actions visible and offer a complementary overflow
378
+ group. See `docs/component-contract.md` "Toolbar width visibility" for lifecycle
379
+ details.
380
+
381
+ Select menus use native viewport shifting with 10px gutters: default menus grow to content (up to 20rem), while `fitMenu` matches trigger width and long labels wrap. Keep popup sizing and position repairs out of consumer CSS.
382
+
383
+ Modal `DialogSurface` accepts a typed `preferredWidth` (`CssLength`) that overrides `size` while retaining the viewport width cap. Typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`) bound the modal; explicit height caps stay within the dynamic viewport minus both gutters. Omitted props preserve Web Awesome defaults. Side-sheet/fullscreen geometry, application responsive choices, and preview layout remain separately owned.
384
+
385
+ Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
386
+
387
+ Toolbar visibility measurement probes and invisible state children are helper-owned and survive Kerf rerenders through `data-morph-preserve`. Actual CSS visibility stays stable while rendering removes transient parent markers, preventing geometry-driven render feedback and repeated probe recreation. Toolbar replacement, explicit internal-node removal and disposal retain their existing behavior.
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.76` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.78` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';
@@ -4,6 +4,8 @@ import '../styles/loading-spinner.css';
4
4
  import '../styles/toolbar-control-group.css';
5
5
  import '../styles/skeleton.css';
6
6
  import '../styles/toolbar-text.css';
7
+ import '../styles/list.css';
8
+ import '../styles/pane.css';
7
9
  import '../styles/lucide-icon.css';
8
10
  import '../styles/nav-stack.css';
9
11
  export * from '../nav-stack.js';
@@ -4,6 +4,8 @@ import '../styles/loading-spinner.css';
4
4
  import '../styles/toolbar-control-group.css';
5
5
  import '../styles/skeleton.css';
6
6
  import '../styles/toolbar-text.css';
7
+ import '../styles/list.css';
8
+ import '../styles/pane.css';
7
9
  import '../styles/lucide-icon.css';
8
10
  import '../styles/nav-stack.css';
9
11
  import '../styles/resizable-region.css';
@@ -5,15 +5,15 @@ import './semantic-content-BbzjvSu9.js';
5
5
  import './sides-BPSWde0A.js';
6
6
  import './pane.js';
7
7
  import './workbench.js';
8
+ import './nav-stack-B1Qc2e5q.js';
8
9
  import './list.js';
9
10
  import './flex-alignment-4ms8ZbV8.js';
10
11
  import './css-values.js';
11
- import './nav-stack.js';
12
- import './toolbar-text.js';
13
- import './resizable-region.js';
14
12
  import './panel-toolbar-CL9tYOkh.js';
15
13
  import './lucide-icon.js';
16
14
  import 'lucide';
15
+ import './toolbar-text.js';
16
+ import './resizable-region.js';
17
17
 
18
18
  /**
19
19
  * Standard resource labels for a Kerf catalog detail footer. Keep these labels
package/dist/catalog.d.ts CHANGED
@@ -7,15 +7,15 @@ import './toolbar.js';
7
7
  import './sides-BPSWde0A.js';
8
8
  import './pane.js';
9
9
  import './workbench.js';
10
+ import './nav-stack-B1Qc2e5q.js';
10
11
  import './list.js';
11
12
  import './flex-alignment-4ms8ZbV8.js';
12
13
  import './css-values.js';
13
- import './nav-stack.js';
14
- import './toolbar-text.js';
15
- import './resizable-region.js';
16
14
  import './panel-toolbar-CL9tYOkh.js';
17
15
  import './lucide-icon.js';
18
16
  import 'lucide';
17
+ import './toolbar-text.js';
18
+ import './resizable-region.js';
19
19
 
20
20
  /**
21
21
  * Controlled, stateless component-catalog shell: a `Workbench` whose left rail
package/dist/catalog.js CHANGED
@@ -1,25 +1,25 @@
1
- import { Workbench } from './chunk-7QWIJEK5.js';
1
+ import { Workbench } from './chunk-DSLRNR43.js';
2
2
 
3
3
 
4
4
  import { PopupMenu } from './chunk-NJZMR7HM.js';
5
5
 
6
6
  import { Row } from './chunk-5D3WDMIK.js';
7
- import { ListHeader } from './chunk-IVLTEBBT.js';
8
- import { Text } from './chunk-ZVYA6TLR.js';
7
+ import { ListHeader } from './chunk-M6BWQMQY.js';
8
+ import { Text } from './chunk-4Y4L5YEI.js';
9
9
  import { ListInsetText } from './chunk-NMWDHZVM.js';
10
10
  import { ListItem } from './chunk-HFQDSSED.js';
11
- import { ToolbarText } from './chunk-5PAVAR4B.js';
11
+ import { ToolbarText } from './chunk-VHXHLZ6R.js';
12
12
 
13
13
 
14
14
 
15
15
 
16
16
 
17
17
 
18
+ import { ToolbarControlGroup, ToolbarActionLink } from './chunk-RRCD3A6G.js';
19
+ import { Toolbar } from './chunk-BYHGRKC3.js';
18
20
 
19
21
  import { List } from './chunk-NVRJ7C3W.js';
20
22
 
21
- import { ToolbarControlGroup, ToolbarActionLink } from './chunk-GO2CZX4M.js';
22
- import { Toolbar } from './chunk-BYHGRKC3.js';
23
23
  import { LucideIcon } from './chunk-PQQQXJGV.js';
24
24
 
25
25
  import { filterDataAttributes } from './chunk-LAZLBC2Q.js';
@@ -0,0 +1,270 @@
1
+ // src/wiring/wire-toolbar-visibility.ts
2
+ var HIDE_BELOW = "data-hide-below";
3
+ var SHOW_BELOW = "data-show-below";
4
+ var HIDDEN = "data-toolbar-width-hidden";
5
+ var PROBE = "data-toolbar-visibility-probe";
6
+ var STATE = "data-toolbar-visibility-state";
7
+ var LENGTH = "--kui-toolbar-visibility-length";
8
+ var ITEM = `[data-component="toolbar-control-group"][${HIDE_BELOW}], [data-component="toolbar-control-group"][${SHOW_BELOW}], [data-component="toolbar-text"][${HIDE_BELOW}], [data-component="toolbar-text"][${SHOW_BELOW}]`;
9
+ var controllers = /* @__PURE__ */ new WeakMap();
10
+ function wireToolbarVisibility(root) {
11
+ const document = root.nodeType === 9 ? root : root.ownerDocument;
12
+ let register = controllers.get(document);
13
+ if (!register) {
14
+ register = createController(document);
15
+ controllers.set(document, register);
16
+ }
17
+ return register(root);
18
+ }
19
+ function createController(document) {
20
+ const view = document.defaultView;
21
+ const registrations = /* @__PURE__ */ new Set();
22
+ const measurements = /* @__PURE__ */ new Map();
23
+ const written = /* @__PURE__ */ new Map();
24
+ const observed = /* @__PURE__ */ new Set();
25
+ let disposed = false;
26
+ let refreshing = false;
27
+ const normalizedStyle = document.createElement("div").style;
28
+ function setStyle(element, property, value) {
29
+ normalizedStyle.removeProperty(property);
30
+ normalizedStyle.setProperty(property, value);
31
+ const normalized = normalizedStyle.getPropertyValue(property);
32
+ if (element.style.getPropertyValue(property) === normalized && element.style.getPropertyPriority(property) === "")
33
+ return;
34
+ if (normalized === "") element.style.removeProperty(property);
35
+ else element.style.setProperty(property, normalized);
36
+ }
37
+ function write(element, hidden) {
38
+ let previous = written.get(element);
39
+ if (!previous) {
40
+ const state = document.createElement("span");
41
+ state.setAttribute(STATE, "");
42
+ state.setAttribute("data-morph-preserve", "");
43
+ state.setAttribute("aria-hidden", "true");
44
+ state.hidden = true;
45
+ previous = { original: element.getAttribute(HIDDEN), value: null, state };
46
+ written.set(element, previous);
47
+ }
48
+ if (previous.state.parentElement !== element)
49
+ element.append(previous.state);
50
+ const value = hidden ? "true" : null;
51
+ if (previous.state.getAttribute(HIDDEN) !== value) {
52
+ if (value === null) previous.state.removeAttribute(HIDDEN);
53
+ else previous.state.setAttribute(HIDDEN, value);
54
+ }
55
+ if (element.getAttribute(HIDDEN) !== value) {
56
+ if (value === null) element.removeAttribute(HIDDEN);
57
+ else element.setAttribute(HIDDEN, value);
58
+ }
59
+ previous.value = value;
60
+ }
61
+ function restore(element) {
62
+ const previous = written.get(element);
63
+ previous.state.remove();
64
+ if (element.getAttribute(HIDDEN) === previous.value) {
65
+ if (previous.original === null) element.removeAttribute(HIDDEN);
66
+ else element.setAttribute(HIDDEN, previous.original);
67
+ }
68
+ written.delete(element);
69
+ }
70
+ function contentWidth(toolbar) {
71
+ const style = view.getComputedStyle(toolbar);
72
+ const inset = (property) => parseFloat(style.getPropertyValue(property)) || 0;
73
+ const padding = inset("padding-left") + inset("padding-right");
74
+ const border = inset("border-left-width") + inset("border-right-width");
75
+ const width = parseFloat(style.width);
76
+ return Math.max(
77
+ 0,
78
+ Number.isFinite(width) ? width - (style.boxSizing === "border-box" ? padding + border : 0) : toolbar.getBoundingClientRect().width - padding - border
79
+ );
80
+ }
81
+ function createMeasurement(toolbar) {
82
+ const box = document.createElement("div");
83
+ box.setAttribute(PROBE, "");
84
+ box.setAttribute("data-morph-preserve", "");
85
+ box.setAttribute("aria-hidden", "true");
86
+ const hide = document.createElement("div");
87
+ const show = document.createElement("div");
88
+ box.append(hide, show);
89
+ toolbar.append(box);
90
+ return { toolbar, box, hide, show };
91
+ }
92
+ function resolveLength(probe, value) {
93
+ if (value === null) return null;
94
+ setStyle(probe, LENGTH, value);
95
+ const left = view.getComputedStyle(probe).left;
96
+ if (!/^-?(?:\d+(?:\.\d+)?|\.\d+)px$/.test(left)) return null;
97
+ const pixels = parseFloat(left);
98
+ return Number.isFinite(pixels) && pixels >= 0 ? pixels : null;
99
+ }
100
+ function copyContext(item, measurement, width) {
101
+ const style = view.getComputedStyle(item);
102
+ const { box } = measurement;
103
+ setStyle(box, "width", `${width}px`);
104
+ for (const property of [
105
+ "font-size",
106
+ "font-family",
107
+ "font-weight",
108
+ "font-style",
109
+ "font-stretch",
110
+ "font-variant",
111
+ "font-feature-settings",
112
+ "font-variation-settings",
113
+ "font-size-adjust",
114
+ "font-kerning",
115
+ "line-height"
116
+ ])
117
+ setStyle(box, property, style.getPropertyValue(property));
118
+ const properties = /* @__PURE__ */ new Set();
119
+ for (let index = 0; index < style.length; index++) {
120
+ const name = style.item(index);
121
+ if (!name.startsWith("--")) continue;
122
+ properties.add(name);
123
+ setStyle(box, name, style.getPropertyValue(name));
124
+ }
125
+ for (let index = box.style.length - 1; index >= 0; index--) {
126
+ const name = box.style.item(index);
127
+ if (name.startsWith("--") && !properties.has(name))
128
+ box.style.removeProperty(name);
129
+ }
130
+ }
131
+ function refresh() {
132
+ if (disposed || refreshing || !view) return;
133
+ refreshing = true;
134
+ try {
135
+ const items = /* @__PURE__ */ new Set();
136
+ for (const { root } of registrations) {
137
+ root.querySelectorAll(ITEM).forEach((item) => items.add(item));
138
+ if (root.nodeType === 1 && root.matches(ITEM))
139
+ items.add(root);
140
+ }
141
+ const wanted = /* @__PURE__ */ new Set();
142
+ const marked = /* @__PURE__ */ new Set();
143
+ for (const item of items) {
144
+ const toolbar = item.closest('[data-component="toolbar"]');
145
+ if (!toolbar) continue;
146
+ let measurement = measurements.get(item);
147
+ if (measurement?.toolbar !== toolbar || measurement.box.parentElement !== toolbar) {
148
+ measurement?.box.remove();
149
+ measurement = createMeasurement(toolbar);
150
+ measurements.set(item, measurement);
151
+ }
152
+ const width = contentWidth(toolbar);
153
+ copyContext(item, measurement, width);
154
+ const hideValue = item.getAttribute(HIDE_BELOW);
155
+ const showValue = item.getAttribute(SHOW_BELOW);
156
+ const hide = resolveLength(measurement.hide, hideValue);
157
+ const show = resolveLength(measurement.show, showValue);
158
+ const valid = (hideValue === null || hide !== null) && (showValue === null || show !== null) && !(hide !== null && show !== null && hide >= show);
159
+ const hidden = valid && (hide !== null && width < hide || show !== null && width >= show);
160
+ write(item, hidden);
161
+ marked.add(item);
162
+ const status = item.nextElementSibling;
163
+ if (status instanceof view.HTMLElement && item.matches(
164
+ '[data-component="toolbar-control-group"][data-busy="true"]'
165
+ ) && status.matches(
166
+ '.kui-toolbar-control-group__busy-status[role="status"]'
167
+ )) {
168
+ write(
169
+ status,
170
+ hidden || view.getComputedStyle(item).display === "none"
171
+ );
172
+ marked.add(status);
173
+ }
174
+ wanted.add(toolbar);
175
+ wanted.add(measurement.hide);
176
+ wanted.add(measurement.show);
177
+ }
178
+ for (const [item, measurement] of measurements)
179
+ if (!items.has(item) || item.closest('[data-component="toolbar"]') !== measurement.toolbar) {
180
+ measurement.box.remove();
181
+ measurements.delete(item);
182
+ }
183
+ for (const element of written.keys())
184
+ if (!marked.has(element)) restore(element);
185
+ for (const element of observed)
186
+ if (!wanted.has(element)) {
187
+ resizeObserver?.unobserve(element);
188
+ observed.delete(element);
189
+ }
190
+ for (const element of wanted)
191
+ if (!observed.has(element)) {
192
+ resizeObserver?.observe(element);
193
+ observed.add(element);
194
+ }
195
+ } finally {
196
+ refreshing = false;
197
+ }
198
+ }
199
+ const resizeObserver = view?.ResizeObserver ? new view.ResizeObserver(refresh) : null;
200
+ const mutationObserver = view?.MutationObserver ? new view.MutationObserver((records) => {
201
+ const internal = (node) => node.nodeType === 1 && (node.hasAttribute(PROBE) || node.hasAttribute(STATE) || Boolean(node.closest(`[${PROBE}], [${STATE}]`)));
202
+ if (records.some(
203
+ (record) => record.type === "childList" && [...record.removedNodes].some(
204
+ (node) => node.nodeType === 1 && (node.hasAttribute(PROBE) || node.hasAttribute(STATE))
205
+ ) || !internal(record.target) && (record.type !== "childList" || [...record.addedNodes, ...record.removedNodes].some(
206
+ (node) => !internal(node)
207
+ ))
208
+ ))
209
+ refresh();
210
+ }) : null;
211
+ mutationObserver?.observe(document.documentElement, {
212
+ subtree: true,
213
+ childList: true,
214
+ attributes: true,
215
+ attributeFilter: [
216
+ HIDE_BELOW,
217
+ SHOW_BELOW,
218
+ HIDDEN,
219
+ "class",
220
+ "style",
221
+ "data-component",
222
+ "data-busy",
223
+ "data-expanded",
224
+ "data-visibility",
225
+ "data-size",
226
+ "data-tone"
227
+ ]
228
+ });
229
+ const media = [
230
+ "(prefers-color-scheme: dark)",
231
+ "(prefers-contrast: more)",
232
+ "(prefers-contrast: less)",
233
+ "(forced-colors: active)",
234
+ "(prefers-reduced-motion: reduce)",
235
+ "(pointer: coarse)",
236
+ "(hover: hover)"
237
+ ].flatMap((query) => view?.matchMedia ? [view.matchMedia(query)] : []);
238
+ for (const preference of media)
239
+ preference.addEventListener("change", refresh);
240
+ document.fonts?.addEventListener("loadingdone", refresh);
241
+ view?.addEventListener("resize", refresh);
242
+ return (root) => {
243
+ const registration = { root };
244
+ registrations.add(registration);
245
+ refresh();
246
+ return () => {
247
+ if (!registrations.delete(registration)) return;
248
+ if (registrations.size) refresh();
249
+ else {
250
+ disposed = true;
251
+ mutationObserver?.disconnect();
252
+ resizeObserver?.disconnect();
253
+ for (const preference of media)
254
+ preference.removeEventListener("change", refresh);
255
+ document.fonts?.removeEventListener("loadingdone", refresh);
256
+ view?.removeEventListener("resize", refresh);
257
+ for (const measurement of measurements.values())
258
+ measurement.box.remove();
259
+ for (const element of written.keys()) restore(element);
260
+ measurements.clear();
261
+ observed.clear();
262
+ controllers.delete(document);
263
+ }
264
+ };
265
+ };
266
+ }
267
+
268
+ export { wireToolbarVisibility };
269
+
270
+ //# sourceMappingURL=chunk-2GOCOLQL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wiring/wire-toolbar-visibility.ts"],"names":[],"mappings":"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","file":"chunk-2GOCOLQL.js","sourcesContent":["type Root = HTMLElement | Document;\n\nconst HIDE_BELOW = 'data-hide-below';\nconst SHOW_BELOW = 'data-show-below';\nconst HIDDEN = 'data-toolbar-width-hidden';\nconst PROBE = 'data-toolbar-visibility-probe';\nconst STATE = 'data-toolbar-visibility-state';\nconst LENGTH = '--kui-toolbar-visibility-length';\nconst ITEM = `[data-component=\"toolbar-control-group\"][${HIDE_BELOW}], [data-component=\"toolbar-control-group\"][${SHOW_BELOW}], [data-component=\"toolbar-text\"][${HIDE_BELOW}], [data-component=\"toolbar-text\"][${SHOW_BELOW}]`;\n\ninterface Measurement {\n toolbar: HTMLElement;\n box: HTMLDivElement;\n hide: HTMLDivElement;\n show: HTMLDivElement;\n}\n\ninterface WrittenAttribute {\n original: string | null;\n value: string | null;\n state: HTMLSpanElement;\n}\n\nconst controllers = new WeakMap<Document, (root: Root) => () => void>();\n\n/**\n * Wire ToolbarText and ToolbarControlGroup width thresholds below root.\n * Below is strict: hideBelow hides at width < threshold; showBelow hides at\n * width >= threshold. Percentages use the closest Toolbar's content box;\n * em and custom properties use the item's own inherited context. Invalid,\n * negative, unresolved, or contradictory thresholds leave the item visible.\n * This helper changes only a component-owned visibility marker. Existing\n * CSS visibility policies still apply. Dispose with the owning application.\n */\nexport function wireToolbarVisibility(root: Root): () => void {\n const document =\n root.nodeType === 9 ? (root as Document) : root.ownerDocument!;\n let register = controllers.get(document);\n if (!register) {\n register = createController(document);\n controllers.set(document, register);\n }\n return register(root);\n}\n\nfunction createController(document: Document) {\n const view = document.defaultView;\n const registrations = new Set<{ root: Root }>();\n const measurements = new Map<HTMLElement, Measurement>();\n const written = new Map<HTMLElement, WrittenAttribute>();\n const observed = new Set<Element>();\n let disposed = false;\n let refreshing = false;\n const normalizedStyle = document.createElement('div').style;\n\n function setStyle(element: HTMLElement, property: string, value: string) {\n // CSSOM can normalize computed font/token values on assignment. Compare\n // that serialization, preserving meaningful blank custom-property tokens.\n normalizedStyle.removeProperty(property);\n normalizedStyle.setProperty(property, value);\n const normalized = normalizedStyle.getPropertyValue(property);\n if (\n element.style.getPropertyValue(property) === normalized &&\n element.style.getPropertyPriority(property) === ''\n )\n return;\n if (normalized === '') element.style.removeProperty(property);\n else element.style.setProperty(property, normalized);\n }\n\n function write(element: HTMLElement, hidden: boolean) {\n let previous = written.get(element);\n if (!previous) {\n const state = document.createElement('span');\n state.setAttribute(STATE, '');\n state.setAttribute('data-morph-preserve', '');\n state.setAttribute('aria-hidden', 'true');\n state.hidden = true;\n previous = { original: element.getAttribute(HIDDEN), value: null, state };\n written.set(element, previous);\n }\n if (previous.state.parentElement !== element)\n element.append(previous.state);\n const value = hidden ? 'true' : null;\n if (previous.state.getAttribute(HIDDEN) !== value) {\n if (value === null) previous.state.removeAttribute(HIDDEN);\n else previous.state.setAttribute(HIDDEN, value);\n }\n if (element.getAttribute(HIDDEN) !== value) {\n if (value === null) element.removeAttribute(HIDDEN);\n else element.setAttribute(HIDDEN, value);\n }\n previous.value = value;\n }\n\n function restore(element: HTMLElement) {\n const previous = written.get(element)!;\n previous.state.remove();\n if (element.getAttribute(HIDDEN) === previous.value) {\n if (previous.original === null) element.removeAttribute(HIDDEN);\n else element.setAttribute(HIDDEN, previous.original);\n }\n written.delete(element);\n }\n\n function contentWidth(toolbar: HTMLElement) {\n const style = view!.getComputedStyle(toolbar);\n const inset = (property: string) =>\n parseFloat(style.getPropertyValue(property)) || 0;\n const padding = inset('padding-left') + inset('padding-right');\n const border = inset('border-left-width') + inset('border-right-width');\n const width = parseFloat(style.width);\n return Math.max(\n 0,\n Number.isFinite(width)\n ? width - (style.boxSizing === 'border-box' ? padding + border : 0)\n : toolbar.getBoundingClientRect().width - padding - border,\n );\n }\n\n function createMeasurement(toolbar: HTMLElement): Measurement {\n const box = document.createElement('div');\n box.setAttribute(PROBE, '');\n // Kerf's render template never owns these injected measurements. Keep\n // them across morphs instead of recreating probes and observers each pass.\n box.setAttribute('data-morph-preserve', '');\n box.setAttribute('aria-hidden', 'true');\n // An out-of-flow clipped box never participates in the toolbar's grid,\n // intrinsic size or scroll overflow. Its explicit width defines % lengths.\n const hide = document.createElement('div');\n const show = document.createElement('div');\n box.append(hide, show);\n toolbar.append(box);\n return { toolbar, box, hide, show };\n }\n\n function resolveLength(\n probe: HTMLElement,\n value: string | null,\n ): number | null {\n if (value === null) return null;\n // The typed private property uses -1px as its invalid/unresolved sentinel.\n // Absolute left's used-value CSSOM alone turns invalid auto into 0px.\n setStyle(probe, LENGTH, value);\n const left = view!.getComputedStyle(probe).left;\n if (!/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/.test(left)) return null;\n const pixels = parseFloat(left);\n return Number.isFinite(pixels) && pixels >= 0 ? pixels : null;\n }\n\n function copyContext(\n item: HTMLElement,\n measurement: Measurement,\n width: number,\n ) {\n const style = view!.getComputedStyle(item);\n const { box } = measurement;\n setStyle(box, 'width', `${width}px`);\n for (const property of [\n 'font-size',\n 'font-family',\n 'font-weight',\n 'font-style',\n 'font-stretch',\n 'font-variant',\n 'font-feature-settings',\n 'font-variation-settings',\n 'font-size-adjust',\n 'font-kerning',\n 'line-height',\n ])\n setStyle(box, property, style.getPropertyValue(property));\n // The box remains outside a hidden item, but resolves tokens from that\n // item, including tokens declared on its root rather than the toolbar.\n const properties = new Set<string>();\n for (let index = 0; index < style.length; index++) {\n const name = style.item(index);\n if (!name.startsWith('--')) continue;\n properties.add(name);\n setStyle(box, name, style.getPropertyValue(name));\n }\n for (let index = box.style.length - 1; index >= 0; index--) {\n const name = box.style.item(index);\n if (name.startsWith('--') && !properties.has(name))\n box.style.removeProperty(name);\n }\n }\n\n function refresh() {\n if (disposed || refreshing || !view) return;\n refreshing = true;\n try {\n const items = new Set<HTMLElement>();\n for (const { root } of registrations) {\n root\n .querySelectorAll<HTMLElement>(ITEM)\n .forEach((item) => items.add(item));\n if (root.nodeType === 1 && (root as HTMLElement).matches(ITEM))\n items.add(root as HTMLElement);\n }\n const wanted = new Set<Element>();\n const marked = new Set<HTMLElement>();\n for (const item of items) {\n const toolbar = item.closest<HTMLElement>('[data-component=\"toolbar\"]');\n if (!toolbar) continue;\n let measurement = measurements.get(item);\n if (\n measurement?.toolbar !== toolbar ||\n measurement.box.parentElement !== toolbar\n ) {\n measurement?.box.remove();\n measurement = createMeasurement(toolbar);\n measurements.set(item, measurement);\n }\n const width = contentWidth(toolbar);\n copyContext(item, measurement, width);\n const hideValue = item.getAttribute(HIDE_BELOW);\n const showValue = item.getAttribute(SHOW_BELOW);\n const hide = resolveLength(measurement.hide, hideValue);\n const show = resolveLength(measurement.show, showValue);\n const valid =\n (hideValue === null || hide !== null) &&\n (showValue === null || show !== null) &&\n !(hide !== null && show !== null && hide >= show);\n const hidden =\n valid &&\n ((hide !== null && width < hide) || (show !== null && width >= show));\n write(item, hidden);\n marked.add(item);\n const status = item.nextElementSibling;\n if (\n status instanceof view.HTMLElement &&\n item.matches(\n '[data-component=\"toolbar-control-group\"][data-busy=\"true\"]',\n ) &&\n status.matches(\n '.kui-toolbar-control-group__busy-status[role=\"status\"]',\n )\n ) {\n write(\n status,\n hidden || view.getComputedStyle(item).display === 'none',\n );\n marked.add(status);\n }\n wanted.add(toolbar);\n wanted.add(measurement.hide);\n wanted.add(measurement.show);\n }\n for (const [item, measurement] of measurements)\n if (\n !items.has(item) ||\n item.closest('[data-component=\"toolbar\"]') !== measurement.toolbar\n ) {\n measurement.box.remove();\n measurements.delete(item);\n }\n for (const element of written.keys())\n if (!marked.has(element)) restore(element);\n for (const element of observed)\n if (!wanted.has(element)) {\n resizeObserver?.unobserve(element);\n observed.delete(element);\n }\n for (const element of wanted)\n if (!observed.has(element)) {\n resizeObserver?.observe(element);\n observed.add(element);\n }\n } finally {\n refreshing = false;\n }\n }\n\n const resizeObserver = view?.ResizeObserver\n ? new view.ResizeObserver(refresh)\n : null;\n const mutationObserver = view?.MutationObserver\n ? new view.MutationObserver((records) => {\n const internal = (node: Node) =>\n node.nodeType === 1 &&\n ((node as Element).hasAttribute(PROBE) ||\n (node as Element).hasAttribute(STATE) ||\n Boolean((node as Element).closest(`[${PROBE}], [${STATE}]`)));\n if (\n records.some(\n (record) =>\n (record.type === 'childList' &&\n [...record.removedNodes].some(\n (node) =>\n node.nodeType === 1 &&\n ((node as Element).hasAttribute(PROBE) ||\n (node as Element).hasAttribute(STATE)),\n )) ||\n (!internal(record.target) &&\n (record.type !== 'childList' ||\n [...record.addedNodes, ...record.removedNodes].some(\n (node) => !internal(node),\n ))),\n )\n )\n refresh();\n })\n : null;\n // Ancestor theme/font changes must refresh even outside the wired subtree.\n mutationObserver?.observe(document.documentElement, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [\n HIDE_BELOW,\n SHOW_BELOW,\n HIDDEN,\n 'class',\n 'style',\n 'data-component',\n 'data-busy',\n 'data-expanded',\n 'data-visibility',\n 'data-size',\n 'data-tone',\n ],\n });\n // Preference media can change inherited tokens at a fixed viewport width.\n const media = [\n '(prefers-color-scheme: dark)',\n '(prefers-contrast: more)',\n '(prefers-contrast: less)',\n '(forced-colors: active)',\n '(prefers-reduced-motion: reduce)',\n '(pointer: coarse)',\n '(hover: hover)',\n ].flatMap((query) => (view?.matchMedia ? [view.matchMedia(query)] : []));\n for (const preference of media)\n preference.addEventListener('change', refresh);\n document.fonts?.addEventListener('loadingdone', refresh);\n view?.addEventListener('resize', refresh);\n\n return (root: Root) => {\n const registration = { root };\n registrations.add(registration);\n refresh();\n return () => {\n if (!registrations.delete(registration)) return;\n if (registrations.size) refresh();\n else {\n disposed = true;\n mutationObserver?.disconnect();\n resizeObserver?.disconnect();\n for (const preference of media)\n preference.removeEventListener('change', refresh);\n document.fonts?.removeEventListener('loadingdone', refresh);\n view?.removeEventListener('resize', refresh);\n for (const measurement of measurements.values())\n measurement.box.remove();\n for (const element of written.keys()) restore(element);\n measurements.clear();\n observed.clear();\n controllers.delete(document);\n }\n };\n };\n}\n"]}
@@ -18,6 +18,7 @@ function Text({
18
18
  font = "default",
19
19
  border = "transparent",
20
20
  flush = false,
21
+ controlMargins = "",
21
22
  lineHeight = "default",
22
23
  wrap = "normal",
23
24
  maxLines,
@@ -42,6 +43,7 @@ function Text({
42
43
  "data-size": size,
43
44
  "data-font": font,
44
45
  "data-border": border,
46
+ "data-control-margins": controlMargins || void 0,
45
47
  "data-flush": flush ? "true" : void 0,
46
48
  "data-line-height": lineHeight === "tight" ? "tight" : void 0,
47
49
  "data-wrap": wrap === "normal" ? void 0 : wrap,
@@ -61,4 +63,4 @@ function Text({
61
63
 
62
64
  export { FieldLabel, Text };
63
65
 
64
- //# sourceMappingURL=chunk-ZVYA6TLR.js.map
66
+ //# sourceMappingURL=chunk-4Y4L5YEI.js.map