@kerfjs/ui 5.0.0-beta.49 → 5.0.0-beta.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -15
- package/ai/catalog-authoring.json +12 -1
- package/ai/catalog-authoring.schema.json +49 -1
- package/ai/compile-time-contracts-v1.json +19 -2
- package/ai/compile-time-contracts-v1.schema.json +1 -0
- package/ai/component-catalog-v2-overrides.json +7 -0
- package/ai/component-catalog-v2.d.ts +19 -1
- package/ai/component-catalog-v2.json +920 -149
- package/ai/component-catalog-v2.schema.json +33 -1
- package/ai/component-catalog.json +689 -153
- package/ai/component-catalog.schema.json +21 -1
- package/ai/public-api-signatures-v1.md +404 -184
- package/ai/skill.md +31 -14
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/index.mjs +105 -35
- package/dist/app-tab.d.ts +3 -1
- package/dist/app-tab.js +1 -1
- package/dist/badge.d.ts +24 -0
- package/dist/badge.js +3 -0
- package/dist/badge.js.map +1 -0
- package/dist/browser/badge.js +3 -0
- package/dist/browser/list-header.js +1 -0
- package/dist/browser/state-banner.js +1 -0
- package/dist/catalog-resources.d.ts +2 -2
- package/dist/catalog.d.ts +47 -125
- package/dist/catalog.js +429 -262
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-W2FK7ZNY.js → chunk-4R777DUZ.js} +10 -2
- package/dist/chunk-4R777DUZ.js.map +1 -0
- package/dist/{chunk-OMSRSL3B.js → chunk-5FZZMIMD.js} +6 -4
- package/dist/chunk-5FZZMIMD.js.map +1 -0
- package/dist/{chunk-AQFWBG23.js → chunk-5PMGQDG7.js} +5 -3
- package/dist/chunk-5PMGQDG7.js.map +1 -0
- package/dist/{chunk-SOK5EUF7.js → chunk-6ZXOQEAN.js} +4 -2
- package/dist/chunk-6ZXOQEAN.js.map +1 -0
- package/dist/{chunk-I4V3UDSA.js → chunk-7GUQYHKA.js} +25 -6
- package/dist/chunk-7GUQYHKA.js.map +1 -0
- package/dist/{chunk-XKFRMGMW.js → chunk-7IUSV4XG.js} +3 -1
- package/dist/chunk-7IUSV4XG.js.map +1 -0
- package/dist/{chunk-IU4QUY5I.js → chunk-AEPXGCNF.js} +20 -3
- package/dist/chunk-AEPXGCNF.js.map +1 -0
- package/dist/{chunk-VDUJZF5G.js → chunk-CMUIXJZM.js} +2 -2
- package/dist/{chunk-VDUJZF5G.js.map → chunk-CMUIXJZM.js.map} +1 -1
- package/dist/{chunk-DAO2VFTT.js → chunk-D547PH4L.js} +7 -5
- package/dist/chunk-D547PH4L.js.map +1 -0
- package/dist/{chunk-2TYUFZUC.js → chunk-D7GWXIAS.js} +4 -2
- package/dist/chunk-D7GWXIAS.js.map +1 -0
- package/dist/{chunk-E34TE2ZI.js → chunk-DVZCMMB3.js} +6 -4
- package/dist/chunk-DVZCMMB3.js.map +1 -0
- package/dist/{chunk-6NDBE2OK.js → chunk-E36EVCKG.js} +5 -3
- package/dist/chunk-E36EVCKG.js.map +1 -0
- package/dist/chunk-EKYZCRCX.js +256 -0
- package/dist/chunk-EKYZCRCX.js.map +1 -0
- package/dist/{chunk-TIGSG6BX.js → chunk-HJKRCKDR.js} +7 -3
- package/dist/chunk-HJKRCKDR.js.map +1 -0
- package/dist/{chunk-Y6LYKFXY.js → chunk-HMQ2ZDGG.js} +5 -3
- package/dist/chunk-HMQ2ZDGG.js.map +1 -0
- package/dist/{chunk-UDHHKZWO.js → chunk-JM53SMVF.js} +9 -5
- package/dist/chunk-JM53SMVF.js.map +1 -0
- package/dist/{chunk-OGTJNHRG.js → chunk-JMGKM7RK.js} +6 -4
- package/dist/chunk-JMGKM7RK.js.map +1 -0
- package/dist/{chunk-GYGM363P.js → chunk-KX7DP2GO.js} +17 -7
- package/dist/chunk-KX7DP2GO.js.map +1 -0
- package/dist/{chunk-OL4TC3U2.js → chunk-MO56ZZLG.js} +6 -2
- package/dist/chunk-MO56ZZLG.js.map +1 -0
- package/dist/{chunk-VXFGMDA7.js → chunk-MTM43SJO.js} +16 -2
- package/dist/chunk-MTM43SJO.js.map +1 -0
- package/dist/{chunk-ZPWZIVI6.js → chunk-N3GKMPDW.js} +7 -3
- package/dist/chunk-N3GKMPDW.js.map +1 -0
- package/dist/{chunk-DYBPI5O2.js → chunk-NDUGJ4TU.js} +4 -2
- package/dist/chunk-NDUGJ4TU.js.map +1 -0
- package/dist/{chunk-I45QO2IZ.js → chunk-NMGTHYW3.js} +7 -5
- package/dist/chunk-NMGTHYW3.js.map +1 -0
- package/dist/{chunk-WSXUPSID.js → chunk-NN6GYOA2.js} +7 -3
- package/dist/chunk-NN6GYOA2.js.map +1 -0
- package/dist/{chunk-OGNE3OFT.js → chunk-NZXWU4R5.js} +24 -3
- package/dist/chunk-NZXWU4R5.js.map +1 -0
- package/dist/{chunk-V6GQKOX2.js → chunk-O3RCCSQJ.js} +6 -4
- package/dist/chunk-O3RCCSQJ.js.map +1 -0
- package/dist/{chunk-WVLMZ5SG.js → chunk-S4QRSBFI.js} +25 -3
- package/dist/chunk-S4QRSBFI.js.map +1 -0
- package/dist/{chunk-CK6P5AUK.js → chunk-UBH743HV.js} +10 -7
- package/dist/chunk-UBH743HV.js.map +1 -0
- package/dist/{chunk-T7DOWS6T.js → chunk-ULCDZTWL.js} +7 -4
- package/dist/chunk-ULCDZTWL.js.map +1 -0
- package/dist/{chunk-V6HCTCVD.js → chunk-UPZ6EEJN.js} +9 -5
- package/dist/chunk-UPZ6EEJN.js.map +1 -0
- package/dist/{chunk-CAB6NQ2T.js → chunk-VOCPXGQN.js} +3 -1
- package/dist/chunk-VOCPXGQN.js.map +1 -0
- package/dist/chunk-XD7DWYXG.js +34 -0
- package/dist/chunk-XD7DWYXG.js.map +1 -0
- package/dist/{chunk-B6JYRX3P.js → chunk-ZIYOJFZD.js} +43 -4
- package/dist/chunk-ZIYOJFZD.js.map +1 -0
- package/dist/{chunk-EYALJQQD.js → chunk-ZK5EGVFP.js} +4 -2
- package/dist/chunk-ZK5EGVFP.js.map +1 -0
- package/dist/{chunk-6WTETUZ6.js → chunk-ZLWQZUGF.js} +8 -2
- package/dist/chunk-ZLWQZUGF.js.map +1 -0
- package/dist/collapsible-panel.d.ts +4 -2
- package/dist/collapsible-panel.js +4 -2
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/css-values.d.ts +39 -3
- package/dist/css-values.js +1 -1
- package/dist/disclosure-arrow.d.ts +3 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/empty-state.d.ts +3 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +3 -1
- package/dist/floating-toolbar.js +1 -1
- package/dist/grid.d.ts +17 -1
- package/dist/grid.js +2 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +34 -33
- package/dist/list-action-row.d.ts +3 -1
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.d.ts +6 -1
- package/dist/list-header.js +2 -1
- package/dist/list-inset-control.d.ts +3 -1
- package/dist/list-inset-control.js +1 -1
- package/dist/list-inset-text.d.ts +3 -1
- package/dist/list-inset-text.js +1 -1
- package/dist/list-item.d.ts +3 -1
- package/dist/list-item.js +1 -1
- package/dist/list.d.ts +23 -2
- package/dist/list.js +2 -1
- package/dist/loading-spinner.d.ts +3 -1
- package/dist/loading-spinner.js +1 -1
- package/dist/lucide-icon.d.ts +3 -1
- package/dist/lucide-icon.js +1 -1
- package/dist/nav-stack.d.ts +3 -1
- package/dist/nav-stack.js +1 -1
- package/dist/pane.d.ts +19 -2
- package/dist/pane.js +1 -1
- package/dist/resizable-region.d.ts +5 -2
- package/dist/resizable-region.js +1 -1
- package/dist/row.d.ts +21 -1
- package/dist/row.js +2 -1
- package/dist/segmented-control.d.ts +3 -1
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +16 -4
- package/dist/select.js +1 -1
- package/dist/skeleton.d.ts +3 -1
- package/dist/skeleton.js +1 -1
- package/dist/spacer.d.ts +3 -1
- package/dist/spacer.js +1 -1
- package/dist/split-view.d.ts +3 -1
- package/dist/split-view.js +24 -5
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +3 -1
- package/dist/state-banner.js +2 -1
- package/dist/styles/badge.css +86 -0
- package/dist/styles/catalog/components/catalog-detail.css +35 -0
- package/dist/styles/catalog/components/catalog-example-stack.css +9 -0
- package/dist/styles/catalog/components/catalog-example.css +180 -0
- package/dist/styles/catalog/components/catalog-resource-footer.css +37 -0
- package/dist/styles/catalog/components/catalog-secondary-sections.css +38 -0
- package/dist/styles/catalog/components/catalog-section-list.css +22 -0
- package/dist/styles/catalog/components/catalog-sidebar.css +82 -0
- package/dist/styles/catalog/components/catalog-stage.css +60 -0
- package/dist/styles/catalog/components/catalog.css +22 -0
- package/dist/styles/catalog.css +10 -587
- package/dist/styles/collapsible-panel.css +132 -4
- package/dist/styles/document.css +23 -0
- package/dist/styles/foundation.css +21 -7
- package/dist/styles/grid.css +10 -0
- package/dist/styles/list-header.css +49 -60
- package/dist/styles/list-inset-control.css +8 -1
- package/dist/styles/list-item.css +6 -0
- package/dist/styles/list.css +11 -0
- package/dist/styles/nav-stack.css +89 -2
- package/dist/styles/pane.css +86 -1
- package/dist/styles/resizable-region.css +117 -5
- package/dist/styles/row.css +10 -0
- package/dist/styles/select.css +39 -7
- package/dist/styles/split-view.css +108 -2
- package/dist/styles/state-banner.css +0 -27
- package/dist/styles/styles.css +1 -0
- package/dist/styles/tab-bar.css +6 -1
- package/dist/styles/tab-scaffold.css +64 -1
- package/dist/styles/text.css +10 -0
- package/dist/styles/token-search-field.css +5 -40
- package/dist/styles/toolbar-control-group.css +198 -25
- package/dist/styles/toolbar-text.css +8 -1
- package/dist/styles/toolbar.css +169 -5
- package/dist/styles/webawesome.css +70 -9
- package/dist/styles/workbench.css +136 -4
- package/dist/sunken-panel.d.ts +3 -1
- package/dist/sunken-panel.js +1 -1
- package/dist/surface-scaffold.d.ts +6 -2
- package/dist/surface-scaffold.js +1 -1
- package/dist/tab-bar.d.ts +8 -1
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +3 -1
- package/dist/tab-scaffold.js +3 -1
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/text.d.ts +9 -5
- package/dist/text.js +1 -1
- package/dist/token-search-field.d.ts +5 -1
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +33 -3
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +3 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +30 -1
- package/dist/toolbar.js +1 -1
- package/dist/types-CEsieBZi.d.ts +69 -0
- package/dist/value-table.d.ts +6 -2
- package/dist/value-table.js +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.d.ts +18 -3
- package/dist/wire-sidebar.js +95 -14
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +3 -1
- package/dist/workbench.js +3 -1
- package/dist/workbench.js.map +1 -1
- package/docs/accessibility.md +56 -9
- package/docs/app-layouts.md +73 -0
- package/docs/catalog.md +48 -9
- package/docs/collapsible-panel.md +81 -8
- package/docs/component-contract.md +96 -28
- package/docs/component-integration.md +9 -4
- package/docs/component-selection.md +36 -15
- package/docs/design/templates/list/stack-dark.svg +1 -1
- package/docs/design/templates/list/stack.svg +1 -1
- package/docs/design/templates/list-dark.svg +1 -1
- package/docs/design/templates/list-header/action-dark.svg +1 -1
- package/docs/design/templates/list-header/action.svg +1 -1
- package/docs/design/templates/list-header/count-dark.svg +1 -1
- package/docs/design/templates/list-header/count.svg +1 -1
- package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
- package/docs/design/templates/list-header/inline-action.svg +1 -1
- package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
- package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
- package/docs/design/templates/list-header-dark.svg +1 -1
- package/docs/design/templates/list-header.svg +1 -1
- package/docs/design/templates/list.svg +1 -1
- package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
- package/docs/design/templates/state-banner/info-badge.svg +1 -1
- package/docs/design/templates/state-banner-dark.svg +1 -1
- package/docs/design/templates/state-banner.svg +1 -1
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
- package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/default.svg +1 -1
- package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/large.svg +1 -1
- package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/small.svg +1 -1
- package/docs/design/templates/toolbar-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-text.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/design-philosophy.md +6 -5
- package/docs/document-baseline.md +31 -0
- package/docs/layout.md +86 -21
- package/docs/nav-stack.md +8 -0
- package/docs/recipes.md +78 -43
- package/docs/split-view.md +8 -0
- package/docs/tab-scaffold.md +4 -2
- package/docs/type-contracts.md +21 -16
- package/docs/ui-analyzer.md +19 -0
- package/docs/ui-evaluator.md +17 -0
- package/docs/ux-demo.md +8 -8
- package/docs/webawesome-theme.md +25 -6
- package/docs/workbench.md +20 -1
- package/evaluator/index.mjs +73 -8
- package/llms.txt +61 -23
- package/package.json +12 -3
- package/ux-demo/recipes/app-shell.tsx +244 -184
- package/ux-demo/recipes/collapsible-sidebar.tsx +175 -116
- package/ux-demo/recipes/compact-toolbar.tsx +119 -102
- package/ux-demo/recipes/composer-form.tsx +79 -69
- package/ux-demo/recipes/list-detail-dialog.tsx +120 -136
- package/ux-demo/recipes/list-workspace-states.tsx +85 -79
- package/ux-demo/recipes/loaders.ts +5 -2
- package/ux-demo/recipes/loading-inspector.tsx +151 -134
- package/ux-demo/recipes/navigation-sidebar.tsx +93 -80
- package/ux-demo/recipes/navigation-stack.tsx +33 -22
- package/ux-demo/recipes/types.ts +14 -0
- package/ux-demo/recipes/workspace-header.tsx +73 -57
- package/dist/chunk-2TYUFZUC.js.map +0 -1
- package/dist/chunk-6NDBE2OK.js.map +0 -1
- package/dist/chunk-6WTETUZ6.js.map +0 -1
- package/dist/chunk-AQFWBG23.js.map +0 -1
- package/dist/chunk-B6JYRX3P.js.map +0 -1
- package/dist/chunk-CAB6NQ2T.js.map +0 -1
- package/dist/chunk-CK6P5AUK.js.map +0 -1
- package/dist/chunk-DAO2VFTT.js.map +0 -1
- package/dist/chunk-DYBPI5O2.js.map +0 -1
- package/dist/chunk-E34TE2ZI.js.map +0 -1
- package/dist/chunk-EYALJQQD.js.map +0 -1
- package/dist/chunk-GYGM363P.js.map +0 -1
- package/dist/chunk-I45QO2IZ.js.map +0 -1
- package/dist/chunk-I4V3UDSA.js.map +0 -1
- package/dist/chunk-IU4QUY5I.js.map +0 -1
- package/dist/chunk-OGNE3OFT.js.map +0 -1
- package/dist/chunk-OGTJNHRG.js.map +0 -1
- package/dist/chunk-OL4TC3U2.js.map +0 -1
- package/dist/chunk-OMSRSL3B.js.map +0 -1
- package/dist/chunk-SOK5EUF7.js.map +0 -1
- package/dist/chunk-T7DOWS6T.js.map +0 -1
- package/dist/chunk-TIGSG6BX.js.map +0 -1
- package/dist/chunk-UDHHKZWO.js.map +0 -1
- package/dist/chunk-V6GQKOX2.js.map +0 -1
- package/dist/chunk-V6HCTCVD.js.map +0 -1
- package/dist/chunk-VXFGMDA7.js.map +0 -1
- package/dist/chunk-W2FK7ZNY.js.map +0 -1
- package/dist/chunk-WN52USHV.js +0 -115
- package/dist/chunk-WN52USHV.js.map +0 -1
- package/dist/chunk-WSXUPSID.js.map +0 -1
- package/dist/chunk-WVLMZ5SG.js.map +0 -1
- package/dist/chunk-XKFRMGMW.js.map +0 -1
- package/dist/chunk-Y6LYKFXY.js.map +0 -1
- package/dist/chunk-ZPWZIVI6.js.map +0 -1
- package/ux-demo/recipes/recipes.css +0 -453
package/docs/accessibility.md
CHANGED
|
@@ -10,7 +10,7 @@ These are normative contracts for package components and consuming applications.
|
|
|
10
10
|
- `ListHeader` owns action naming, disabled state, and disclosure `aria-expanded`. Toggle mode supplies one decorative 18px `DisclosureArrow` when `actionIcon` is omitted; it mirrors `expanded` while the native button's accessible name stays stable. The application must update that controlled state and reveal or hide real content. A valid `count` is visually rendered in an `aria-hidden` neutral pill while the required localized `countLabel` becomes part of the owning heading or disclosure button's accessible name; zero remains a real count. Its narrow `triggerAttributes` slot may describe a native popover or controlled-content relationship with `popoverTarget`, `popoverTargetAction`, `aria-controls`, and `aria-haspopup`; the application owns the target surface and its focus/dismissal behavior.
|
|
11
11
|
- Dispose `wireTabBars` and `wireTokenSearchFields` with their owning view. Disposal cancels queued controlled-render focus restoration, so a torn-down view cannot reclaim focus from its successor.
|
|
12
12
|
- `wireNavStack` moves focus into every newly active top view and remembers the last focused descendant of each mounted view for pop restoration. Mark a preferred initial heading or control with `data-nav-focus` (and `tabindex="-1"` when it is not normally tabbable); otherwise the helper uses the first enabled focusable descendant, then a programmatically focusable view-container fallback.
|
|
13
|
-
- In any `.kui-pane`, menu rows, `ListHeader` actions, and toolbar groups keep a minimum 44px target in both dimensions. A `ListHeader` fills the available inline width and keeps that action at the logical end, including in RTL. Its action visual defaults to 18px through `--kui-list-header-action-icon-size
|
|
13
|
+
- In any `.kui-pane`, menu rows, `ListHeader` actions, and toolbar groups keep a minimum 44px target in both dimensions. A `ListHeader` fills the available inline width and keeps that action at the logical end, including in RTL. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square; a transparent hit layer extends the pointer target to at least 44 × 44 around that square without moving layout or enlarging the painted surface. Compact headers are 36px tall like the rest of the compact List family, so their action target is 44px wide and clamped to the 36px header height instead of reaching into a neighboring row. Do not reduce the target to the visible icon, and do not force the button itself to 44px.
|
|
14
14
|
- `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
|
|
15
15
|
- `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
|
|
16
16
|
- `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
|
|
@@ -29,10 +29,23 @@ adding height or spacing. This also applies to `renderSelected`: selected conten
|
|
|
29
29
|
stays separate from the control's stable accessible name. Applications do not
|
|
30
30
|
need shadow-DOM patches or extra label styling.
|
|
31
31
|
|
|
32
|
-
Use `presentation="toolbar-borderless"`
|
|
33
|
-
`
|
|
34
|
-
composed focus ring. `selectedPresentation="icon-only"` hides
|
|
35
|
-
selected label—the required `label` or `ariaLabel` still names
|
|
32
|
+
Use `presentation="toolbar-borderless"` inside a `ToolbarControlGroup`, with
|
|
33
|
+
`size="compact"` in a compact group; set `focusRingOwner="group"` when that
|
|
34
|
+
parent paints the composed focus ring. `selectedPresentation="icon-only"` hides
|
|
35
|
+
only the visible selected label—the required `label` or `ariaLabel` still names
|
|
36
|
+
the combobox. An icon-only trigger keeps its disclosure caret: the selected icon
|
|
37
|
+
and caret form one pill with the same geometry as a popup-menu `wa-dropdown`
|
|
38
|
+
trigger (8px inline padding on both sides, 12px from the icon to the caret, a
|
|
39
|
+
40px default or 32px compact control height), so the caret still signals a
|
|
40
|
+
choice list. Inside a group the trigger is inset evenly on every side (2px in a
|
|
41
|
+
default group, 1px in a compact one) with a concentric radius; the group grows
|
|
42
|
+
to the trigger's width, so a single group is wider than it is tall and its
|
|
43
|
+
focus ring follows the pill while focused and while its listbox is open.
|
|
44
|
+
Beside other controls in one group, the icon-only trigger is one more segment:
|
|
45
|
+
it keeps the group's gap and its siblings' block inset, height, item radius,
|
|
46
|
+
and hover pill. In a group with per-control rings (`focusRing="control"`, the
|
|
47
|
+
default) it paints the same ring as its sibling buttons, even when
|
|
48
|
+
`focusRingOwner="group"` was set, because no group ring would show focus.
|
|
36
49
|
Navigation selects can use intrinsic `presentation="navigation"` plus
|
|
37
50
|
`labelMaxWidth` for component-owned ellipsis. These props own the control's
|
|
38
51
|
appearance; its parent continues to own outer placement.
|
|
@@ -85,13 +98,38 @@ popover or context menu.
|
|
|
85
98
|
|
|
86
99
|
## ResizableRegion
|
|
87
100
|
|
|
101
|
+
Set `responsiveFillAt="narrow"` or `"compact"` when a responsive composition
|
|
102
|
+
shows the region as its only inline track. The component then owns its
|
|
103
|
+
full-width geometry and removes its dormant separator at that container
|
|
104
|
+
breakpoint; the application owns which pane is visible.
|
|
105
|
+
|
|
88
106
|
The handle exposes separator role, orientation, name, minimum, maximum, and current values. `wireResizableRegions()` adds:
|
|
89
107
|
|
|
90
108
|
- the axis arrow keys in 16 px steps by default;
|
|
91
109
|
- Shift+Arrow in 64 px steps;
|
|
92
110
|
- Home and End for minimum and maximum;
|
|
93
111
|
- primary-pointer drag with clamping;
|
|
94
|
-
-
|
|
112
|
+
- a visible maximum: when a parent clamps the track below the requested size
|
|
113
|
+
(a `max-width` stage, a narrow container), pointer and keyboard resizing stop
|
|
114
|
+
at the size actually shown, and the handle announces that size in
|
|
115
|
+
`aria-valuenow` and that bound in `aria-valuemax`. Focusing the handle
|
|
116
|
+
reports a stale larger committed size as the shown size without committing
|
|
117
|
+
it, so the first key press moves the visible separator. The reported values
|
|
118
|
+
also stay current at rest: a `ResizeObserver` on each region and its parent
|
|
119
|
+
re-clamps them when the space changes, and a `MutationObserver` re-clamps
|
|
120
|
+
them after a re-render writes the rendered props back, so assistive
|
|
121
|
+
technology never reads an unclamped size between interactions (the
|
|
122
|
+
disposer disconnects both observers);
|
|
123
|
+
- a whole hit target and focus ring at a clamped edge: when the separator has
|
|
124
|
+
less room past it than the handle's 10px overhang (a parent clamps the track
|
|
125
|
+
to its own edge), the region carries `data-handle-inset` and the handle sits
|
|
126
|
+
wholly inside it, so a clipping ancestor cannot cut off half the handle or
|
|
127
|
+
its focus ring. The ring is drawn inside the handle's box, so the region's
|
|
128
|
+
ends meeting a clipping edge never shave it either;
|
|
129
|
+
- preview callbacks while dragging and one commit callback on release;
|
|
130
|
+
- a live size that also resizes slide-motion content, so the content tracks the
|
|
131
|
+
separator during a drag or key press instead of waiting for the app to
|
|
132
|
+
re-render the committed size.
|
|
95
133
|
|
|
96
134
|
The application owns persistence and collapsed/expanded policy. Keep the last expanded size outside the component and restore it when reopening. An optional `handleIcon` replaces only decorative dormant content; it must not contain controls or interactive roles because the separator remains the sole focus and interaction owner.
|
|
97
135
|
|
|
@@ -129,10 +167,15 @@ and — via `position` and `--kui-floating-toolbar-inset` — where it sits.
|
|
|
129
167
|
## Tabs
|
|
130
168
|
|
|
131
169
|
`AppTab` renders one controlled tab. `TabBar` supplies the containing tab list,
|
|
132
|
-
fixed leading/trailing regions, and a horizontally scrollable strip.
|
|
170
|
+
fixed leading/trailing/end regions, and a horizontally scrollable strip.
|
|
133
171
|
Use `TabBar.presentation` for rail, segmented, or inspector chrome,
|
|
134
172
|
`allocation="fill"` when peers should divide the strip, and
|
|
135
173
|
`trailingPlacement="adjacent"` when an action belongs beside the final tab.
|
|
174
|
+
When that local action must coexist with a workspace-level action, put the
|
|
175
|
+
workspace action in `end`; TabBar keeps the adjacent action immediately after
|
|
176
|
+
the shrinkable/scrolling tabs and pins `end` at the far edge. Leading,
|
|
177
|
+
trailing, and end actions remain visible and do not shrink. The component owns
|
|
178
|
+
that geometry; applications must not override its anatomy to create the split.
|
|
136
179
|
`AppTab` provides compact 32px, segmented, truncating-label, and icon-only
|
|
137
180
|
presentations. Icon-only tabs keep the required `name` as the tab button's
|
|
138
181
|
accessible name while visually hiding the duplicate label. These props own
|
|
@@ -237,9 +280,13 @@ standard per-side collapse/expand glyph (`PanelLeft*` / `PanelRight*` /
|
|
|
237
280
|
`PanelBottom*`); place a collapse toggle inside the panel and an expand toggle in
|
|
238
281
|
an always-visible location so it is reachable while collapsed. `wireSidebar()`
|
|
239
282
|
moves focus into the panel when it opens and restores it to the trigger when it
|
|
240
|
-
closes
|
|
283
|
+
closes; a trigger inside the now-hidden panel hands focus to the panel's toggle
|
|
284
|
+
outside it instead, so an app may render the expand toggle only while the panel
|
|
285
|
+
is collapsed. When a `deviceClass()` reports `compact`, an open panel becomes an overlay
|
|
241
286
|
with a dismissable backdrop, Escape and backdrop-click collapse it, and Tab is
|
|
242
|
-
trapped within it (the ARIA dialog pattern).
|
|
287
|
+
trapped within it (the ARIA dialog pattern). That modal state only ever begins
|
|
288
|
+
from a user action: at wire-up on a compact device and on a wide → compact
|
|
289
|
+
crossing, every panel starts collapsed, so no page load or resize traps focus. The application owns each `collapsed`
|
|
243
290
|
signal, the panels, their sizes, and content; the wire may persist the collapsed
|
|
244
291
|
state per panel.
|
|
245
292
|
|
package/docs/app-layouts.md
CHANGED
|
@@ -19,6 +19,79 @@ Derive responsiveness from `deviceClass()`: `compact` (a handset or portrait
|
|
|
19
19
|
tablet) means "one pane at a time"; `atLeast('tablet')` / `atLeast('desktop')`
|
|
20
20
|
gate the roomier presentations.
|
|
21
21
|
|
|
22
|
+
Full-height shells retain `height: 100%` so they remain embeddable. Give a
|
|
23
|
+
top-level shell a definite height chain by importing `@kerfjs/ui/document.css`
|
|
24
|
+
and applying `.kui-app-root` to the direct mount container; see the
|
|
25
|
+
[document baseline](document-baseline.md). Do not replace an embeddable shell's
|
|
26
|
+
height with `100dvh`.
|
|
27
|
+
|
|
28
|
+
## Safe areas
|
|
29
|
+
|
|
30
|
+
On a device with unsafe areas (a notch, rounded corners, a home indicator) and a
|
|
31
|
+
page that opts into them with `<meta name="viewport" content="…, viewport-fit=cover">`,
|
|
32
|
+
the layouts handle the insets for you. There is nothing to configure:
|
|
33
|
+
|
|
34
|
+
- **Surfaces paint edge to edge.** Pane and panel backgrounds, separators, and
|
|
35
|
+
dividers run through the unsafe area. A collapsible rail or drawer grows by
|
|
36
|
+
the inset it reaches, so its content keeps its configured width or height.
|
|
37
|
+
- **Content is padded on the sides it touches.** Inline insets pad the sides a
|
|
38
|
+
region actually reaches. Block insets become padding _inside_ the scroll
|
|
39
|
+
container (with matching `scroll-padding`), so content scrolls under the
|
|
40
|
+
status bar or home indicator but its first and last items can always be
|
|
41
|
+
scrolled clear.
|
|
42
|
+
- **Interior edges get nothing.** A `Workbench` center next to an expanded rail,
|
|
43
|
+
a `SplitView` detail beside its list, or a `NavStack` view under its chrome is
|
|
44
|
+
not inset on that edge. Collapse the rail and the center picks up the edge.
|
|
45
|
+
- **No double inset.** Whichever region applies an inset clears it for its
|
|
46
|
+
descendants. A layout region whose only child is a `Pane` or another layout
|
|
47
|
+
lets that child own the insets. A `Pane` header or footer whose only child is
|
|
48
|
+
a `Toolbar` hands the inline insets to the toolbar, so the toolbar's dividers
|
|
49
|
+
still reach the edge.
|
|
50
|
+
|
|
51
|
+
A plain `Pane` follows the same rules. It assumes it may touch every screen edge
|
|
52
|
+
unless its layout says otherwise. When your own markup puts panes side by side,
|
|
53
|
+
say which sides each pane reaches with `safeAreaEdges`:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<div class="app-columns">
|
|
57
|
+
<Pane label="Library" safeAreaEdges={['block-start', 'block-end', 'inline-start']}>…</Pane>
|
|
58
|
+
<Pane label="Reader" safeAreaEdges={['block-start', 'block-end', 'inline-end']}>…</Pane>
|
|
59
|
+
</div>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
An app bar or bottom bar that sits directly at a screen edge, outside any
|
|
63
|
+
`Pane` header or footer, claims its edges the same way. A `Toolbar` only pads
|
|
64
|
+
the inline edges an owner hands it until you list the sides it reaches:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<List fill>
|
|
68
|
+
<Toolbar
|
|
69
|
+
label="App bar"
|
|
70
|
+
safeAreaEdges={['block-start', 'inline-start', 'inline-end']}
|
|
71
|
+
leading={<ToolbarText text="Atlas" />}
|
|
72
|
+
/>
|
|
73
|
+
<Row gap="none" flex>…panes that list block-end and their outer side…</Row>
|
|
74
|
+
</List>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Each claimed side pads by the edge context, or the full device inset when
|
|
78
|
+
nothing routes that edge, while the bar's surface and divider still reach the
|
|
79
|
+
screen edge. A claimed toolbar inside a `Pane` header or footer adds nothing
|
|
80
|
+
extra, because the pane already owns those edges.
|
|
81
|
+
|
|
82
|
+
Pass `safeAreaEdges={[]}` for a pane that never sits at a screen edge. For a
|
|
83
|
+
region you own, set `--kui-edge-inset-block-start`, `--kui-edge-inset-block-end`,
|
|
84
|
+
`--kui-edge-inset-inline-start`, or `--kui-edge-inset-inline-end` to `0px` on the
|
|
85
|
+
region for each edge it does not reach, and kerf descendants follow. A
|
|
86
|
+
`CollapsiblePanel` does this for its direct flex siblings automatically. A
|
|
87
|
+
panel wrapped in your own grid cell cannot see its siblings, so route those
|
|
88
|
+
edges yourself.
|
|
89
|
+
|
|
90
|
+
The device insets come from `--kui-safe-area-block-start`, `-block-end`,
|
|
91
|
+
`-inline-start`, and `-inline-end`, which default to `env(safe-area-inset-*)`.
|
|
92
|
+
Override them on `:root` to reserve room for app-owned chrome or to simulate a
|
|
93
|
+
device in tests.
|
|
94
|
+
|
|
22
95
|
## Decision matrix
|
|
23
96
|
|
|
24
97
|
| Situation | Layout | Device threshold |
|
package/docs/catalog.md
CHANGED
|
@@ -23,8 +23,8 @@ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed
|
|
|
23
23
|
|
|
24
24
|
- **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
|
|
25
25
|
description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
|
|
26
|
-
`ListItem` under a `ListHeader` for its category. Short `tags` render
|
|
27
|
-
|
|
26
|
+
`ListItem` under a `ListHeader` for its category. Short `tags` render through
|
|
27
|
+
the row's quiet status treatment for decision metadata such as `Discouraged`.
|
|
28
28
|
- **`content`** — the rendered preview for the active entry. Keep a map of `id →
|
|
29
29
|
() => SafeHtml` in your app and call `renderers[active]()` in your render.
|
|
30
30
|
- **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
|
|
@@ -54,10 +54,25 @@ chosen component.
|
|
|
54
54
|
|
|
55
55
|
Classify every entry before rendering it:
|
|
56
56
|
|
|
57
|
-
| Entry kind
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
| Composition
|
|
57
|
+
| Entry kind | Preview purpose | Geometry overlay |
|
|
58
|
+
| --------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
59
|
+
| **Component** | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
|
|
60
|
+
| **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. |
|
|
61
|
+
| **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. |
|
|
62
|
+
|
|
63
|
+
These three kinds are exhaustive. Theme/foundation showcases use a functional
|
|
64
|
+
category, third-party specimens use source metadata, and state galleries retain
|
|
65
|
+
the kind of the thing they demonstrate; none needs another demo kind.
|
|
66
|
+
|
|
67
|
+
Organize the catalog by functional group, with groups in descending product
|
|
68
|
+
importance. Within every group, list all component demos before all composition
|
|
69
|
+
demos. Order each kind by descending importance where a meaningful distinction
|
|
70
|
+
exists, then alphabetically by display name to break ties. Put recipes after all
|
|
71
|
+
component/composition groups, ordered by the same importance-then-alphabetical
|
|
72
|
+
rule. Treat source order as the authored importance order when no explicit rank
|
|
73
|
+
field exists. Show a `Composition` tag rather than repeating the word in a
|
|
74
|
+
composition's display name; the generic `Catalog` accepts that through the
|
|
75
|
+
entry's `tags` array.
|
|
61
76
|
|
|
62
77
|
Keep `geometryOverlay` present and compute it from the active entry. Do not make
|
|
63
78
|
it a permanent catalog-wide `true`, and do not give individual specimens their
|
|
@@ -76,6 +91,18 @@ own overlay implementation.
|
|
|
76
91
|
generated note, then one specimen or one intentionally coupled specimen
|
|
77
92
|
cluster. Use one row per variant/state; do not hand-author the helper's private
|
|
78
93
|
classes.
|
|
94
|
+
- Focused demos do not import local stylesheets, use inline `style`, or invent
|
|
95
|
+
styling-only `demo-*` classes. Prefer a public Kerf component, then a Web
|
|
96
|
+
Awesome surface when Kerf has no equivalent, then ordinary semantic HTML.
|
|
97
|
+
Use `CatalogExample.viewport` for bounded specimen width, height, frame,
|
|
98
|
+
surface, overflow, responsive visibility, and public custom-property values;
|
|
99
|
+
keep those constraints catalog-owned instead of recreating a demo stylesheet.
|
|
100
|
+
Component heights run `short` (220px) through `tall` (360px); frame an app
|
|
101
|
+
shell, whole-screen layout, or other application-sized recipe with
|
|
102
|
+
`height: "app"` (592px), and give its root `fill` rather than a frame `Pane`.
|
|
103
|
+
An app frame stands in for the screen: it is the containing block for
|
|
104
|
+
screen-fixed chrome, so a compact `CollapsiblePanel` overlay, its backdrop,
|
|
105
|
+
and restore controls dock to the frame's edges rather than the page's.
|
|
79
106
|
- The specimen is an immediate child of `CatalogExample`. A focused component
|
|
80
107
|
row should place the component root there, without a decorative card or
|
|
81
108
|
spacing wrapper. A composition row may place the composition root there.
|
|
@@ -128,6 +155,9 @@ from structurally widened or JavaScript objects. Do not copy the helpers'
|
|
|
128
155
|
private `kui-catalog-*` classes into preview markup.
|
|
129
156
|
|
|
130
157
|
The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
|
|
158
|
+
`Catalog.stageRootAttributes` places app-owned `data-*` state on the
|
|
159
|
+
catalog-owned preview stage without an extra wrapper; `data-catalog-stage`
|
|
160
|
+
remains protected.
|
|
131
161
|
The helpers own their structural markers, label/note anatomy, alignment marker,
|
|
132
162
|
and private classes. Component metadata such as margin/border/padding ownership
|
|
133
163
|
lives in `component-catalog.json`; do not infer or overwrite it from overlay
|
|
@@ -189,7 +219,13 @@ facts that source can prove without guessing at rendered intent:
|
|
|
189
219
|
are not empty, and composition routes do not add redundant skip markers;
|
|
190
220
|
- every `@kerfjs/ui` import is a published package export and relative imports
|
|
191
221
|
do not reach into `ui/src`;
|
|
192
|
-
-
|
|
222
|
+
- focused demos have no relative stylesheet imports, inline style attributes,
|
|
223
|
+
or styling-only `demo-*`, `wa-demo-*`, and token-search class names;
|
|
224
|
+
- demo JSX does not copy private `kui-catalog-*` structural classes;
|
|
225
|
+
- recipe sources (checked by `npm run check:recipes` with the same shared
|
|
226
|
+
analyzer) import no stylesheet other than public `@kerfjs/ui` CSS, set no
|
|
227
|
+
inline style, and name only published layout and Web Awesome classes; each
|
|
228
|
+
recipe declares its catalog frame through an exported `presentation`; and
|
|
193
229
|
- the shell derives both geometry-overlay enablement and documented demo mode
|
|
194
230
|
from the active entry's source and kind.
|
|
195
231
|
|
|
@@ -202,7 +238,7 @@ stage geometry is itself the reviewed specimen. Add the narrow waiver to
|
|
|
202
238
|
[`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
|
|
203
239
|
with the exact route, source file, stable diagnostic ids, a substantive reason,
|
|
204
240
|
and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
|
|
205
|
-
private imports, private markup, empty examples, composition overlay drift, and
|
|
241
|
+
private imports, local styling, private markup, empty examples, composition overlay drift, and
|
|
206
242
|
shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
|
|
207
243
|
and stale exceptions, so delete a waiver when its route adopts the standard
|
|
208
244
|
helpers.
|
|
@@ -262,7 +298,10 @@ const entries: DemoEntry[] = [
|
|
|
262
298
|
const sections: CatalogSection[] = [
|
|
263
299
|
{
|
|
264
300
|
category: "Examples",
|
|
265
|
-
entries: entries.map(({ kind
|
|
301
|
+
entries: entries.map(({ kind, ...entry }) => ({
|
|
302
|
+
...entry,
|
|
303
|
+
tags: kind === "composition" ? ["Composition"] : undefined,
|
|
304
|
+
})),
|
|
266
305
|
},
|
|
267
306
|
];
|
|
268
307
|
|
|
@@ -37,25 +37,85 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
|
|
|
37
37
|
is the app's: put a collapse toggle in the panel's own header and an expand toggle
|
|
38
38
|
somewhere always-visible (a toolbar) so it is reachable while collapsed.
|
|
39
39
|
- **`wireSidebar(root, { panels, deviceClass?, storage? })`** — the interaction
|
|
40
|
-
semantics. Each `panels` entry is
|
|
41
|
-
It:
|
|
40
|
+
semantics. Each `panels` entry is
|
|
41
|
+
`{ id, collapsed, toggleAction, storageKey?, inlineCollapsed? }`. It:
|
|
42
42
|
- **toggles** the panel's `collapsed` signal when any `[data-action=toggleAction]`
|
|
43
43
|
button is clicked, and remembers the trigger;
|
|
44
44
|
- **manages focus** — moves focus into the panel when it opens, and restores it
|
|
45
|
-
to the trigger when it closes
|
|
45
|
+
to the trigger when it closes. When that trigger is the collapse toggle inside
|
|
46
|
+
the now-hidden panel, focus goes to the panel's toggle outside it instead, so
|
|
47
|
+
an app may render its expand toggle only while the panel is collapsed (one
|
|
48
|
+
control per action, as the recipe does);
|
|
46
49
|
- **presents a compact overlay** when `deviceClass.compact` is true (pass a
|
|
47
50
|
`deviceClass()` signal): the open panel floats over the content with a
|
|
48
51
|
dismissable backdrop, Escape and backdrop-click collapse it, and Tab is trapped
|
|
49
|
-
within the panel (the ARIA dialog pattern)
|
|
52
|
+
within the panel (the ARIA dialog pattern). The overlay and its backdrop are
|
|
53
|
+
fixed to the screen; an ancestor that establishes a containing block for
|
|
54
|
+
fixed descendants (such as the catalog's `height: "app"` frame) docks them to
|
|
55
|
+
its edges instead;
|
|
50
56
|
- accepts `compactPresentation: "hidden"` when a compact application replaces
|
|
51
57
|
the panel with different navigation instead of overlaying it;
|
|
58
|
+
- **starts every compact overlay closed** (see
|
|
59
|
+
[Compact initial state](#compact-initial-state));
|
|
52
60
|
- keeps compact overlays exclusive by default, collapsing another open panel
|
|
53
61
|
when a new one opens (`exclusiveCompact: false` opts out);
|
|
54
|
-
- **persists** the collapsed state to `storage` (default
|
|
55
|
-
`storageKey`, seeding the signal on wire-up.
|
|
62
|
+
- **persists** the inline collapsed state to `storage` (default
|
|
63
|
+
`localStorage`) under `storageKey`, seeding the signal on wire-up. An
|
|
64
|
+
overlay's open/closed state is never persisted;
|
|
65
|
+
- **owns the root's `data-collapsible-responsive` / `data-collapsible-overlay`
|
|
66
|
+
attributes and the injected backdrop.** Your app may re-render (and morph)
|
|
67
|
+
the wired root for reasons that never touch a panel signal — a nav
|
|
68
|
+
selection, say — and the morph drops attributes and nodes its template
|
|
69
|
+
doesn't emit. The wire observes the root and re-applies them before the
|
|
70
|
+
next paint, so don't render these attributes yourself.
|
|
56
71
|
|
|
57
72
|
Returns a disposer. Retain it and call it on teardown.
|
|
58
73
|
|
|
74
|
+
## Compact initial state
|
|
75
|
+
|
|
76
|
+
A compact overlay is transient chrome the user summons, like a slide-over
|
|
77
|
+
sidebar on a phone: it covers the content, traps focus, and must be dismissed.
|
|
78
|
+
It therefore opens **only on a user action** (a toggle, or the app setting the
|
|
79
|
+
signal in response to one), never by itself:
|
|
80
|
+
|
|
81
|
+
- **Wire-up on a compact device.** Every panel starts collapsed, whatever its
|
|
82
|
+
signal, `inlineCollapsed`, or stored choice says. No backdrop, focus trap, or
|
|
83
|
+
Escape handling is active until the user opens a panel.
|
|
84
|
+
- **Wide → compact crossing.** Open panels collapse; nothing covers the page.
|
|
85
|
+
- **Compact → wide crossing.** Each panel returns to its remembered inline state
|
|
86
|
+
(open or collapsed), whatever the user did with the overlay meanwhile.
|
|
87
|
+
- **Disposal** hands the inline state back to the signals.
|
|
88
|
+
- **Persistence** records only the inline choice. A stored "open" rail restores
|
|
89
|
+
inline on a wide screen and is remembered, not opened, on a compact one.
|
|
90
|
+
|
|
91
|
+
These presentation changes never move focus, except to rescue focus that a
|
|
92
|
+
collapse would strand inside the hidden panel (it returns to the panel's last
|
|
93
|
+
trigger). `compactPresentation: "hidden"` leaves the signals alone.
|
|
94
|
+
|
|
95
|
+
`wireSidebar` runs after the first render, so an app that seeds an open inline
|
|
96
|
+
default would render the rail open for that first frame on a compact device.
|
|
97
|
+
Seed the signal from the device class instead, and declare the inline default
|
|
98
|
+
with `inlineCollapsed` so a later wide crossing still opens it:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const device = deviceClass();
|
|
102
|
+
const navCollapsed = signal(device.value.compact);
|
|
103
|
+
// …
|
|
104
|
+
wireSidebar(app, {
|
|
105
|
+
panels: [
|
|
106
|
+
{
|
|
107
|
+
id: "nav",
|
|
108
|
+
collapsed: navCollapsed,
|
|
109
|
+
toggleAction: "toggle-nav",
|
|
110
|
+
inlineCollapsed: false,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
deviceClass: device,
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A stored inline choice takes precedence over `inlineCollapsed`.
|
|
118
|
+
|
|
59
119
|
## Example
|
|
60
120
|
|
|
61
121
|
```tsx
|
|
@@ -68,8 +128,10 @@ import {
|
|
|
68
128
|
import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
|
|
69
129
|
import "@kerfjs/ui/collapsible-panel.css";
|
|
70
130
|
|
|
71
|
-
const navCollapsed = signal(false);
|
|
72
131
|
const device = deviceClass();
|
|
132
|
+
// Start collapsed on a compact device; `inlineCollapsed` keeps the open inline
|
|
133
|
+
// default for wide screens.
|
|
134
|
+
const navCollapsed = signal(device.value.compact);
|
|
73
135
|
|
|
74
136
|
const app = document.querySelector("#app")!;
|
|
75
137
|
mount(app, () => (
|
|
@@ -111,12 +173,22 @@ const stop = wireSidebar(app, {
|
|
|
111
173
|
collapsed: navCollapsed,
|
|
112
174
|
toggleAction: "toggle-nav",
|
|
113
175
|
storageKey: "app.nav-collapsed",
|
|
176
|
+
inlineCollapsed: false,
|
|
114
177
|
},
|
|
115
178
|
],
|
|
116
179
|
deviceClass: device,
|
|
117
180
|
});
|
|
118
181
|
```
|
|
119
182
|
|
|
183
|
+
## Safe areas
|
|
184
|
+
|
|
185
|
+
A panel grows by the unsafe inset of the edge it docks to and pads its content
|
|
186
|
+
for every edge it touches except its interior edge. While expanded inline, it
|
|
187
|
+
also clears that edge for its direct flex siblings, so a neighboring pane gains
|
|
188
|
+
the inset when the panel collapses. When you wrap the panel in your own grid
|
|
189
|
+
cell, set `--kui-edge-inset-*: 0px` on the sibling regions yourself (or pass
|
|
190
|
+
`safeAreaEdges` to a sibling `Pane`). See [Choosing an app layout › Safe areas](app-layouts.md#safe-areas).
|
|
191
|
+
|
|
120
192
|
## When to use which
|
|
121
193
|
|
|
122
194
|
- One or two independent rails / a drawer you place yourself → **`CollapsiblePanel` +
|
|
@@ -138,5 +210,6 @@ persistence: [open it](../ux-demo/?component=recipe-collapsible-sidebar) or read
|
|
|
138
210
|
[`recipes.md`](recipes.md#collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx).
|
|
139
211
|
It is covered end-to-end across Chromium, Firefox, and WebKit by
|
|
140
212
|
`tests/browser/collapsible-sidebar-recipe.spec.ts` (collapse/expand, focus
|
|
141
|
-
move/restore, the compact overlay + Escape/backdrop dismiss,
|
|
213
|
+
move/restore, the compact overlay + Escape/backdrop dismiss, the Tab trap, and
|
|
214
|
+
the compact initial state across first load and wide/compact crossings),
|
|
142
215
|
alongside the component/wire unit tests.
|
|
@@ -36,6 +36,19 @@ bounded `items[].property` form. Consumer catalogs can publish the same facts;
|
|
|
36
36
|
ESLint, `kerf-ui-analyze`, and `kerf-ui-doctor` then issue the same stable
|
|
37
37
|
`KUI-L013`–`KUI-L017` feedback for application and third-party components.
|
|
38
38
|
|
|
39
|
+
`wiring.stateAttributes` lists the **wiring-owned state attributes**: the
|
|
40
|
+
`data-*` attributes a wiring helper writes at runtime, such as
|
|
41
|
+
`data-handle-inset` and `data-resizing`, which `wireResizableRegions` puts on a
|
|
42
|
+
`ResizableRegion`. Each entry names the attribute, the element that carries it
|
|
43
|
+
(`on`), the `helper` that writes it, and what it means. An application must not
|
|
44
|
+
render, remove, or treat these attributes as its own state; tools use the list
|
|
45
|
+
to tell them apart from app-authored attributes. They are declared once, on the
|
|
46
|
+
helper's wiring item in the v1 catalog, and v2 flattens them. `check:catalog`
|
|
47
|
+
reads each `wire*` helper's source and fails when its declarations differ from
|
|
48
|
+
the `data-*` attributes it actually writes. First-party entries always carry
|
|
49
|
+
the list, empty when their helpers write none; it is optional in extension
|
|
50
|
+
catalogs.
|
|
51
|
+
|
|
39
52
|
The generator starts with permissive defaults. An `any` mode records that the
|
|
40
53
|
catalog has no defensible prohibition; it does not claim every composition is
|
|
41
54
|
recommended. Objective rules live in
|
|
@@ -54,6 +67,21 @@ explicitly. Zones without that binding remain guidance and are never guessed
|
|
|
54
67
|
from their ids. Static tools inspect only visible JSX structure and leave
|
|
55
68
|
dynamic `SafeHtml` expressions to runtime and accessibility evaluation.
|
|
56
69
|
|
|
70
|
+
### Native named-slot assignment
|
|
71
|
+
|
|
72
|
+
Every public visual component with one stable conceptual root accepts an
|
|
73
|
+
explicit `slot?: string` prop and forwards it to that root on every render
|
|
74
|
+
branch. Use it when a Kerf component is a light-DOM child of a custom element,
|
|
75
|
+
for example `StateBanner({ title: 'Ready', slot: 'status' })`. Omitting the prop
|
|
76
|
+
omits the attribute.
|
|
77
|
+
|
|
78
|
+
This contract does not open a generic native-attribute bag: components still
|
|
79
|
+
accept only their documented props and narrow extension-attribute objects.
|
|
80
|
+
`ResizableRegion` and `CollapsiblePanel` are deliberately excluded because each
|
|
81
|
+
can return a region plus a sibling restore control, leaving no single root that
|
|
82
|
+
can truthfully own native slot assignment. Their single-root controls and all
|
|
83
|
+
alternate-root branches of supported components retain the contract.
|
|
84
|
+
|
|
57
85
|
V1 consumers continue unchanged. V2 consumers read v1 for selection/delivery
|
|
58
86
|
and v2 for composition. `npm run catalog:sync` projects every new component,
|
|
59
87
|
recipe, and supported Web Awesome entry, and the completeness gate prevents
|
|
@@ -159,7 +187,7 @@ claim Kerf delivery paths for them.
|
|
|
159
187
|
- Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
|
|
160
188
|
- Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
|
|
161
189
|
- A reusable component never owns per-instance mutable module state.
|
|
162
|
-
- Consumers
|
|
190
|
+
- Consumers configure presentation through component props and documented `--kui-*` semantic tokens. Foundation tokens provide opinionated neutral, brand/info, non-status pop, success, warning, and danger fill/border/foreground roles. Application stylesheets must not select package component classes or descendants. If genuinely new structure needs CSS, encapsulate that structure and its stylesheet in an application-owned component; configure any nested Kerf components through their public APIs.
|
|
163
191
|
|
|
164
192
|
`ListItem.rootAttributes`, `ListActionRow.rootAttributes`,
|
|
165
193
|
`ListHeader.rootAttributes`, `AppTab.rootAttributes`,
|
|
@@ -194,23 +222,29 @@ visual anchor.
|
|
|
194
222
|
### Public CSS anatomy
|
|
195
223
|
|
|
196
224
|
The `publicClasses` array on each entry in
|
|
197
|
-
[`component-catalog.json`](../ai/component-catalog.json)
|
|
198
|
-
anatomy
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
225
|
+
[`component-catalog.json`](../ai/component-catalog.json) identifies stable
|
|
226
|
+
anatomy for diagnostics, tooling, browser assertions, and package-owned
|
|
227
|
+
composition. It is not an application customization API. Applications choose
|
|
228
|
+
documented props and tokens; package components may compose other components
|
|
229
|
+
only through those public contracts, never by styling a child's classes from a
|
|
230
|
+
parent stylesheet.
|
|
203
231
|
|
|
204
232
|
The v2 composition catalog additionally requires `boundaries.rootClass` to be
|
|
205
233
|
either one exact member of `publicClasses` or `null` when the entry has no
|
|
206
234
|
rendered class root. Runtime geometry tooling uses this explicit field; array
|
|
207
235
|
order never implies root ownership.
|
|
208
236
|
|
|
209
|
-
Do not select a component
|
|
210
|
-
|
|
211
|
-
`.kui-
|
|
212
|
-
|
|
213
|
-
|
|
237
|
+
Do not select a component root or descendant by class, element name, id, or
|
|
238
|
+
attribute. Selectors such as `.workspace .kui-toolbar`,
|
|
239
|
+
`.kui-state-banner span`, and `.kui-list-item [data-state]` cross the ownership
|
|
240
|
+
boundary. If no prop or token expresses a recurring need, add the configuration
|
|
241
|
+
to the owning component instead of inferring it from rendered markup.
|
|
242
|
+
|
|
243
|
+
`Badge` owns the complete visual treatment for compact status, count, category,
|
|
244
|
+
and metadata labels. Configure its semantic `tone`, `appearance`, `shape`, and
|
|
245
|
+
`size`; do not recreate or override badge anatomy in an application or consuming
|
|
246
|
+
component. Use `label` when visible content is abbreviated and `ariaHidden` only
|
|
247
|
+
when a surrounding component already includes the same value in its accessible name.
|
|
214
248
|
|
|
215
249
|
`ListItem` renders its leading icon and nested SVG at a root-scaled 18px by
|
|
216
250
|
default while retaining the row's 44px minimum interactive target. Multiline
|
|
@@ -240,9 +274,8 @@ the logical end while the visible glyph defaults to 18px through
|
|
|
240
274
|
|
|
241
275
|
`StateBanner` exposes instance-level `--kui-state-banner-background`,
|
|
242
276
|
`--kui-state-banner-border`, `--kui-state-banner-foreground`,
|
|
243
|
-
`--kui-state-banner-detail
|
|
244
|
-
|
|
245
|
-
badge is a compact pill beside the title and follows the banner tone by default. Its five built-in
|
|
277
|
+
`--kui-state-banner-detail` and action background variables. Its optional
|
|
278
|
+
badge composes `Badge` beside the title and follows the banner tone by default. Its five built-in
|
|
246
279
|
tones can be rethemed globally with
|
|
247
280
|
`--kui-state-banner-{tone}-{background|border|foreground}`. Toolbar control,
|
|
248
281
|
segmented-control, app-tab, and tab-bar colors likewise use their public
|
|
@@ -286,8 +319,10 @@ props. Its opaque string brand improves authoring correctness; it is not a CSS
|
|
|
286
319
|
sanitizer or security boundary.
|
|
287
320
|
`Row.flex` and `List.flex` likewise accept their boolean default, finite keywords, or `CssFlex`
|
|
288
321
|
from `flex(grow, shrink, basis)`. `Skeleton` width/height use `CssSize`, radius
|
|
289
|
-
uses `CssLength`, and `SelectChoice.color` uses `
|
|
290
|
-
`
|
|
322
|
+
uses `CssLength`, and `SelectChoice.color` uses `CssForegroundColor` from
|
|
323
|
+
`uiColor()` with a foreground token such as `success-on-quiet`, or from
|
|
324
|
+
`foregroundColorVar()`. Fill tokens such as `success` are pale background tints
|
|
325
|
+
and do not type-check there. Never exchange these property grammars. List rows expose
|
|
291
326
|
`className`, public tokens, and semantic props instead of unrestricted `style`
|
|
292
327
|
declarations.
|
|
293
328
|
`.kui-content` gives major children 24px vertical separation.
|
|
@@ -299,21 +334,39 @@ changing geometry. `.kui-content-item--pill` selects the 22px radius.
|
|
|
299
334
|
text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
|
|
300
335
|
groups and inside items. For an ordinary icon/action control inside a group, use
|
|
301
336
|
a plain `<button>` — the group styles `> button` fully, and it keeps the group
|
|
302
|
-
free of a Web Awesome dependency and shadow DOM.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
337
|
+
free of a Web Awesome dependency and shadow DOM. Use `ToolbarActionLink` when
|
|
338
|
+
the action must retain native anchor navigation; the group owns its geometry,
|
|
339
|
+
hover, and focus treatment. Set `overflow="scroll"` when a row of controls must
|
|
340
|
+
stay inside the available toolbar width. Set `visibility="compact-only"` for a
|
|
341
|
+
group that replaces wider toolbar controls below the Toolbar's compact container
|
|
342
|
+
breakpoint; do not hide the group with an application class. Reach for
|
|
343
|
+
`wa-button` only when you need
|
|
344
|
+
a Web Awesome feature, chiefly the `slot="trigger"` button of a `wa-dropdown`
|
|
345
|
+
popup menu. For a compact mixed-content group, set `nestedDropdown`; a
|
|
346
|
+
text-and-caret trigger grows to its intrinsic width while an icon-only trigger
|
|
347
|
+
can remain square. A compact icon `<button>` is a 32px circle, so a selected
|
|
348
|
+
compact segment never becomes an oval. An icon-only `Select` joins a group as
|
|
349
|
+
one more segment with its siblings' inset, radius, hover, and focus ring; only a
|
|
350
|
+
lone Select grows the group to its icon-and-caret pill. Set `menuInset` to configure the dropdown menu surface
|
|
351
|
+
without application `::part(menu)` CSS. Compact mixed controls retain the standard
|
|
307
352
|
item padding, omit an internal separator, and let the raised selected item paint
|
|
308
|
-
over the outer border instead of shrinking to an inset highlight. The
|
|
309
|
-
trigger
|
|
310
|
-
|
|
311
|
-
|
|
353
|
+
over the outer border instead of shrinking to an inset highlight. The Web
|
|
354
|
+
Awesome trigger's shadow base owns max-content sizing, so icons, localized text,
|
|
355
|
+
and the shadow caret determine the width without a fixed host measurement.
|
|
356
|
+
The enclosing `Toolbar` owns zone alignment and responsive topology: use
|
|
357
|
+
`centerAlign="stretch"` when the center group should consume its track, and
|
|
358
|
+
choose `responsive="stack"` with `responsiveAt="compact" | "narrow"`,
|
|
359
|
+
`responsive="wrap"` for a heading whose title must stay whole, or
|
|
360
|
+
`responsive="center-priority"` instead of selecting its zone classes from a
|
|
361
|
+
parent stylesheet. No policy clips an action: the trailing zone, and a stacked
|
|
362
|
+
center zone, wrap whole groups onto another row.
|
|
312
363
|
`ListHeader` similarly separates its dormant title and
|
|
313
364
|
optional count or badge from its optional 44px action. Use the mutually
|
|
314
365
|
exclusive `count`/`countLabel` pair for non-negative safe-integer section
|
|
315
366
|
quantities; reserve `badge` for non-count `SafeHtml`. Do not concatenate counts
|
|
316
|
-
into the section label.
|
|
367
|
+
into the section label. Set `width="content"` to shrink-wrap while retaining
|
|
368
|
+
normal header geometry; `inline` is the separate zero-outer-geometry mode. Do
|
|
369
|
+
not add padding to pane shells,
|
|
317
370
|
double child-owned geometry with wrapper insets, or create competing scroll
|
|
318
371
|
owners. The [layout contract](./layout.md) lists the public roles and tokens.
|
|
319
372
|
|
|
@@ -354,6 +407,14 @@ render, hands in its own via `collapsible.signals`, drives it through
|
|
|
354
407
|
`handle.open`/`handle.close`, or disables any individual behavior — so transient
|
|
355
408
|
UI is consistent by default without every app reinventing it.
|
|
356
409
|
|
|
410
|
+
A public subpath that exposes several visual components is a folder-backed
|
|
411
|
+
surface. Put each component in its own source file and give each visual
|
|
412
|
+
component exactly one owned stylesheet alongside it. Keep the public
|
|
413
|
+
compatibility entrypoints implementation-free: the JavaScript/TypeScript
|
|
414
|
+
entrypoint only re-exports the folder modules, and the CSS entrypoint only
|
|
415
|
+
imports their owner stylesheets. A shared public name is not permission to put
|
|
416
|
+
several component implementations or visual contracts back into one file.
|
|
417
|
+
|
|
357
418
|
`wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
|
|
358
419
|
helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
|
|
359
420
|
transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
|
|
@@ -371,6 +432,13 @@ render the section body, so a managed helper would remove no real complexity. Re
|
|
|
371
432
|
for a managed default only when the transient behavior is substantial enough that
|
|
372
433
|
hand-rolling it produces genuine, inconsistent variation.
|
|
373
434
|
|
|
435
|
+
TabBar itself owns the inline geometry around those mechanics. Its tab list is
|
|
436
|
+
the only shrinking, horizontally scrolling region; `leading`, `trailing`, and
|
|
437
|
+
`end` action regions remain fixed. Set `trailingPlacement="adjacent"` when the
|
|
438
|
+
trailing action belongs immediately after the final tab and use `end` for a
|
|
439
|
+
separate action pinned to the far edge. Applications own the action content,
|
|
440
|
+
but must not restyle TabBar descendants to recreate that allocation.
|
|
441
|
+
|
|
374
442
|
CSS, the generated wrappers that make it
|
|
375
443
|
reachable, and the registration module are the package's only declared side
|
|
376
444
|
effects.
|
|
@@ -421,4 +489,4 @@ Keep product adapters outside the package: connection-state maps, ticket empty-s
|
|
|
421
489
|
|
|
422
490
|
## Testing contract
|
|
423
491
|
|
|
424
|
-
Each behavior has focused unit coverage and a real-browser flow through the production-backed catalog. Consumer bundle tests enforce subpath CSS reachability, transitive component styles, root/SSR isolation, optional registration boundaries, and the CSS-only Web Awesome theme boundary. Visual evidence supplements—never replaces—keyboard, focus, state, and event assertions.
|
|
492
|
+
Each behavior has focused unit coverage and a real-browser flow through the production-backed catalog. Consumer bundle tests enforce subpath CSS reachability, transitive component styles, root/SSR isolation, optional registration boundaries, and the CSS-only Web Awesome theme boundary. `npm run check:css-ownership` rejects application selectors into Kerf or Web Awesome components, a non-minimal catalog compatibility stylesheet, or a return of the shared recipe stylesheet. Visual evidence supplements—never replaces—keyboard, focus, state, and event assertions.
|