@kerfjs/ui 5.0.0-beta.17 → 5.0.0-beta.21
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 +221 -81
- package/ai/application-ui-diagnostic-ids-v1.json +92 -0
- package/ai/application-ui-profile-sync.cjs +529 -0
- package/ai/application-ui-profile.d.mts +1 -0
- package/ai/application-ui-profile.d.ts +102 -0
- package/ai/application-ui-profile.defaults.json +41 -0
- package/ai/application-ui-profile.mjs +626 -0
- package/ai/application-ui-profile.schema.json +130 -0
- package/ai/catalog-authoring.json +25 -0
- package/ai/catalog-authoring.schema.json +61 -0
- package/ai/compile-time-contracts-v1.json +101 -0
- package/ai/compile-time-contracts-v1.schema.json +49 -0
- package/ai/component-catalog-extension-v2.d.ts +7 -0
- package/ai/component-catalog-extension-v2.schema.json +7 -0
- package/ai/component-catalog-extension.schema.json +79 -0
- package/ai/component-catalog-v2-overrides.json +326 -0
- package/ai/component-catalog-v2.d.ts +67 -0
- package/ai/component-catalog-v2.json +4441 -0
- package/ai/component-catalog-v2.schema.json +246 -0
- package/ai/component-catalog.json +649 -6
- package/ai/component-catalog.schema.json +15 -0
- package/ai/public-api-signatures-v1.md +357 -91
- package/ai/skill.md +95 -50
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/cli.mjs +46 -0
- package/analyzer/index.d.mts +45 -0
- package/analyzer/index.mjs +743 -0
- package/analyzer/report.schema.json +66 -0
- package/dist/app-tab.d.ts +1 -1
- package/dist/app-tab.js +1 -1
- package/dist/browser/pane.js +3 -0
- package/dist/browser/sunken-panel.js +3 -0
- package/dist/catalog-resources.d.ts +34 -0
- package/dist/catalog-resources.js +22 -0
- package/dist/catalog-resources.js.map +1 -0
- package/dist/catalog.d.ts +29 -4
- package/dist/catalog.js +302 -84
- package/dist/catalog.js.map +1 -1
- package/dist/chunk-5VTB2BKP.js +35 -0
- package/dist/chunk-5VTB2BKP.js.map +1 -0
- package/dist/chunk-6WTETUZ6.js +21 -0
- package/dist/chunk-6WTETUZ6.js.map +1 -0
- package/dist/chunk-7KEUJIIC.js +218 -0
- package/dist/chunk-7KEUJIIC.js.map +1 -0
- package/dist/chunk-7L6EHUMV.js +31 -0
- package/dist/chunk-7L6EHUMV.js.map +1 -0
- package/dist/{chunk-XXMOARTH.js → chunk-AASLYRGE.js} +57 -16
- package/dist/chunk-AASLYRGE.js.map +1 -0
- package/dist/chunk-BBRXX6PF.js +108 -0
- package/dist/chunk-BBRXX6PF.js.map +1 -0
- package/dist/{chunk-6NLRBSVX.js → chunk-CN2XNTAM.js} +129 -72
- package/dist/chunk-CN2XNTAM.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-DSNIYOZ3.js +114 -0
- package/dist/chunk-DSNIYOZ3.js.map +1 -0
- package/dist/chunk-ELSJX4W7.js +68 -0
- package/dist/chunk-ELSJX4W7.js.map +1 -0
- package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
- package/dist/chunk-FEY65TBV.js.map +1 -0
- package/dist/chunk-JRKSK2HX.js +79 -0
- package/dist/chunk-JRKSK2HX.js.map +1 -0
- package/dist/chunk-K57KRAOV.js +117 -0
- package/dist/chunk-K57KRAOV.js.map +1 -0
- package/dist/chunk-LPOMRC4C.js +25 -0
- package/dist/chunk-LPOMRC4C.js.map +1 -0
- package/dist/chunk-M7JBER5U.js +41 -0
- package/dist/chunk-M7JBER5U.js.map +1 -0
- package/dist/chunk-NENBUYX5.js +79 -0
- package/dist/chunk-NENBUYX5.js.map +1 -0
- package/dist/chunk-OHU6CI22.js +83 -0
- package/dist/chunk-OHU6CI22.js.map +1 -0
- package/dist/{chunk-YGN7OMLT.js → chunk-OZWU5ZPZ.js} +2 -2
- package/dist/chunk-OZWU5ZPZ.js.map +1 -0
- package/dist/chunk-PIDTEQ3U.js +105 -0
- package/dist/chunk-PIDTEQ3U.js.map +1 -0
- package/dist/chunk-PX3YSS5C.js +32 -0
- package/dist/chunk-PX3YSS5C.js.map +1 -0
- package/dist/chunk-PZQVSDLH.js +61 -0
- package/dist/chunk-PZQVSDLH.js.map +1 -0
- package/dist/chunk-QO55FN2Y.js +20 -0
- package/dist/chunk-QO55FN2Y.js.map +1 -0
- package/dist/chunk-QQFJMRIB.js +45 -0
- package/dist/chunk-QQFJMRIB.js.map +1 -0
- package/dist/chunk-RYRIFZCT.js +58 -0
- package/dist/chunk-RYRIFZCT.js.map +1 -0
- package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-VLYV65LK.js +38 -0
- package/dist/chunk-VLYV65LK.js.map +1 -0
- package/dist/chunk-W4CXQYSG.js +23 -0
- package/dist/chunk-W4CXQYSG.js.map +1 -0
- package/dist/{chunk-WXMGNX2Y.js → chunk-W7YD6E4N.js} +2 -2
- package/dist/chunk-W7YD6E4N.js.map +1 -0
- package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
- package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
- package/dist/collapsible-panel.d.ts +2 -2
- package/dist/collapsible-panel.js +47 -6
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/device-class.js +33 -5
- package/dist/device-class.js.map +1 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/empty-state.d.ts +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +1 -1
- package/dist/floating-toolbar.js +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +27 -25
- package/dist/list-action-row.d.ts +1 -1
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.d.ts +30 -9
- package/dist/list-header.js +1 -1
- package/dist/list-inset-control.d.ts +1 -1
- package/dist/list-inset-control.js +1 -1
- package/dist/list-inset-text.d.ts +1 -1
- package/dist/list-inset-text.js +1 -1
- package/dist/list-item.d.ts +1 -1
- package/dist/list-item.js +1 -1
- package/dist/loading-spinner.js +1 -1
- package/dist/nav-stack.d.ts +1 -1
- package/dist/nav-stack.js +1 -1
- package/dist/pane.d.ts +49 -0
- package/dist/pane.js +4 -0
- package/dist/pane.js.map +1 -0
- package/dist/panel-header.d.ts +10 -4
- package/dist/panel-header.js +1 -1
- package/dist/resizable-region.d.ts +1 -1
- package/dist/resizable-region.js +1 -1
- package/dist/segmented-control.d.ts +6 -6
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +11 -5
- package/dist/select.js +1 -1
- package/dist/skeleton.d.ts +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/split-view.d.ts +1 -1
- package/dist/split-view.js +70 -10
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +4 -3
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog.css +32 -6
- package/dist/styles/layout.css +2 -20
- package/dist/styles/pane.css +62 -0
- package/dist/styles/segmented-control.css +3 -7
- package/dist/styles/sunken-panel.css +18 -0
- package/dist/sunken-panel.d.ts +16 -0
- package/dist/sunken-panel.js +3 -0
- package/dist/sunken-panel.js.map +1 -0
- package/dist/tab-bar.d.ts +4 -3
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +6 -6
- package/dist/tab-scaffold.js +43 -8
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/token-search-field.d.ts +25 -9
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +10 -6
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +10 -11
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +1 -1
- package/dist/toolbar.js +1 -1
- package/dist/value-table.d.ts +2 -2
- package/dist/value-table.js +1 -1
- package/dist/wire-catalog.d.ts +31 -2
- package/dist/wire-catalog.js +203 -8
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-nav-stack.js +22 -7
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-resizable-regions.d.ts +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.js +52 -41
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-tab-bars.d.ts +5 -2
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.js +9 -4
- package/dist/wire-tab-scaffold.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +12 -7
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.js +56 -9
- package/dist/workbench.js.map +1 -1
- package/docs/app-layouts.md +7 -7
- package/docs/catalog.md +249 -46
- package/docs/collapsible-panel.md +38 -11
- package/docs/component-contract.md +132 -7
- package/docs/component-selection.md +79 -67
- package/docs/design/templates.md +1 -1
- package/docs/design-philosophy.md +7 -9
- package/docs/device-class.md +8 -8
- package/docs/examples/application-ui-profile.json +60 -0
- package/docs/examples/command-palette-adapter.tsx +67 -17
- package/docs/examples/component-catalog-extension-v2.json +81 -0
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +51 -32
- package/docs/nav-stack.md +15 -6
- package/docs/recipes.md +4 -4
- package/docs/split-view.md +3 -3
- package/docs/sunken-panel.md +43 -0
- package/docs/tab-scaffold.md +16 -7
- package/docs/type-contracts.md +99 -0
- package/docs/ui-analyzer.md +81 -0
- package/docs/ui-doctor.md +77 -0
- package/docs/ui-evaluator.md +122 -0
- package/docs/webawesome-theme.md +8 -8
- package/docs/workbench.md +18 -5
- package/doctor/cli.mjs +115 -0
- package/doctor/config.schema.json +68 -0
- package/doctor/index.d.mts +116 -0
- package/doctor/index.mjs +1256 -0
- package/doctor/report.schema.json +118 -0
- package/evaluator/cli.mjs +89 -0
- package/evaluator/index.d.mts +90 -0
- package/evaluator/index.mjs +1138 -0
- package/evaluator/report.schema.json +152 -0
- package/llms.txt +58 -15
- package/package.json +70 -10
- package/ux-demo/recipes/app-shell.tsx +206 -10
- package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
- package/ux-demo/recipes/compact-toolbar.tsx +114 -12
- package/ux-demo/recipes/composer-form.tsx +106 -18
- package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
- package/ux-demo/recipes/list-workspace-states.tsx +165 -8
- package/ux-demo/recipes/loaders.ts +5 -2
- package/ux-demo/recipes/loading-inspector.tsx +124 -41
- package/ux-demo/recipes/mount-recipe.ts +27 -6
- package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
- package/ux-demo/recipes/navigation-stack.tsx +61 -19
- package/ux-demo/recipes/types.ts +3 -1
- package/ux-demo/recipes/workspace-header.tsx +57 -2
- package/dist/chunk-2Z5TWHKQ.js +0 -34
- package/dist/chunk-2Z5TWHKQ.js.map +0 -1
- package/dist/chunk-37T3VNIZ.js +0 -19
- package/dist/chunk-37T3VNIZ.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-5ATCDSQS.js +0 -42
- package/dist/chunk-5ATCDSQS.js.map +0 -1
- package/dist/chunk-5E2HVJ33.js +0 -10
- package/dist/chunk-5E2HVJ33.js.map +0 -1
- package/dist/chunk-6NLRBSVX.js.map +0 -1
- package/dist/chunk-7BR3DO2A.js.map +0 -1
- package/dist/chunk-7JB7VPRI.js +0 -34
- package/dist/chunk-7JB7VPRI.js.map +0 -1
- package/dist/chunk-7LDCTU7D.js +0 -134
- package/dist/chunk-7LDCTU7D.js.map +0 -1
- package/dist/chunk-AHQI52QO.js.map +0 -1
- package/dist/chunk-AQNKJRFH.js +0 -79
- package/dist/chunk-AQNKJRFH.js.map +0 -1
- package/dist/chunk-AVZHJN6C.js +0 -14
- package/dist/chunk-AVZHJN6C.js.map +0 -1
- package/dist/chunk-B24C4A6T.js +0 -22
- package/dist/chunk-B24C4A6T.js.map +0 -1
- package/dist/chunk-CH4SQ6H5.js +0 -39
- package/dist/chunk-CH4SQ6H5.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-I7INRJO4.js +0 -17
- package/dist/chunk-I7INRJO4.js.map +0 -1
- package/dist/chunk-L4OKFDJY.js +0 -24
- package/dist/chunk-L4OKFDJY.js.map +0 -1
- package/dist/chunk-M5JRI6MN.js +0 -27
- package/dist/chunk-M5JRI6MN.js.map +0 -1
- package/dist/chunk-RNPGFQ33.js +0 -10
- package/dist/chunk-RNPGFQ33.js.map +0 -1
- package/dist/chunk-VPCTCUED.js +0 -10
- package/dist/chunk-VPCTCUED.js.map +0 -1
- package/dist/chunk-WRTAQ7BZ.js +0 -23
- package/dist/chunk-WRTAQ7BZ.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-WXMGNX2Y.js.map +0 -1
- package/dist/chunk-XXMOARTH.js.map +0 -1
- package/dist/chunk-YGN7OMLT.js.map +0 -1
- package/dist/chunk-ZQQ6P4SV.js +0 -20
- package/dist/chunk-ZQQ6P4SV.js.map +0 -1
package/docs/catalog.md
CHANGED
|
@@ -7,7 +7,7 @@ you get the same shell without rebuilding it. Like the app layouts, it is a
|
|
|
7
7
|
subpath-only, tree-shakeable module that adds nothing to the main barrel.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @kerfjs/ui
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
- `Catalog(props)` returns the shell as `SafeHtml` (a `<main class="kui-catalog">`).
|
|
@@ -17,20 +17,20 @@ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is neede
|
|
|
17
17
|
- `wireCatalog(root, options)` wires the interactions (sidebar selection, the
|
|
18
18
|
related-entry popup menu, and the collapse/theme toggles) with one delegated
|
|
19
19
|
listener set and returns a disposer; it can also mirror the active id into the
|
|
20
|
-
URL.
|
|
20
|
+
URL and reveal the active sidebar row after a controlled render.
|
|
21
21
|
|
|
22
22
|
## What you supply
|
|
23
23
|
|
|
24
24
|
- **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
|
|
25
|
-
|
|
25
|
+
description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
|
|
26
26
|
`ListItem` under a `ListHeader` for its category. Short `tags` render as quiet
|
|
27
27
|
trailing pills 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.
|
|
31
31
|
- **`secondarySections`** — an optional secondary "ecosystem" group shown below the
|
|
32
32
|
primary sections with a quieter treatment: `{ label, sections, collapsible?,
|
|
33
|
-
|
|
33
|
+
expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
|
|
34
34
|
`expanded` (the app owns it; wire it with `wireCatalog`'s `onToggleSecondary`).
|
|
35
35
|
- Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
|
|
36
36
|
sidebar content), and `status` (a footer status line).
|
|
@@ -40,94 +40,297 @@ Per-entry `resources` render as "open in new tab" links in the footer, and
|
|
|
40
40
|
around a `wa-dropdown` (grouped by each entry's `group`), so register its elements
|
|
41
41
|
with `@kerfjs/ui/select/register` when you use it.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Catalog demo authoring contract
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
This section is the single authoritative contract for tools and people that
|
|
46
|
+
author Catalog previews. The machine-readable discovery entry is
|
|
47
|
+
[`catalog-authoring.json`](../ai/catalog-authoring.json); exact props remain in
|
|
48
|
+
[`public-api-signatures-v1.md`](../ai/public-api-signatures-v1.md#kerfjsuicatalog).
|
|
49
|
+
The component catalog deliberately does not duplicate these rules: it describes
|
|
50
|
+
which component to choose, while this contract describes how to present the
|
|
51
|
+
chosen component.
|
|
52
|
+
|
|
53
|
+
### Choose the demo mode
|
|
54
|
+
|
|
55
|
+
Classify every entry before rendering it:
|
|
56
|
+
|
|
57
|
+
| Entry kind | Preview purpose | Geometry overlay |
|
|
58
|
+
| --------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
59
|
+
| Focused component | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
|
|
60
|
+
| Composition or recipe | Show several components cooperating as one product surface. | `false`; child geometry remains unmarked so the composition can be read as a whole. |
|
|
61
|
+
|
|
62
|
+
Keep `geometryOverlay` present and compute it from the active entry. Do not make
|
|
63
|
+
it a permanent catalog-wide `true`, and do not give individual specimens their
|
|
64
|
+
own overlay implementation.
|
|
65
|
+
|
|
66
|
+
### Required structure
|
|
67
|
+
|
|
68
|
+
- `Catalog` is the one shell. The app owns active-entry state and passes one
|
|
69
|
+
active preview through `content`.
|
|
70
|
+
- `CatalogExampleStack` is the group for one preview's rows. Put route/test
|
|
71
|
+
metadata such as `data-demo` on its rendered root through `rootAttributes`.
|
|
72
|
+
Pass `label` when the stack needs an accessible name: the rendered `section`
|
|
73
|
+
is then exposed as a named region. An unlabeled stack remains an ordinary
|
|
74
|
+
grouping rather than adding an unnecessary landmark.
|
|
75
|
+
- `CatalogExample` is one row: optional generated `ListHeader` label, optional
|
|
76
|
+
generated note, then one specimen or one intentionally coupled specimen
|
|
77
|
+
cluster. Use one row per variant/state; do not hand-author the helper's private
|
|
78
|
+
classes.
|
|
79
|
+
- The specimen is an immediate child of `CatalogExample`. A focused component
|
|
80
|
+
row should place the component root there, without a decorative card or
|
|
81
|
+
spacing wrapper. A composition row may place the composition root there.
|
|
82
|
+
|
|
83
|
+
Use `align="glyph"` for a bare glyph/text specimen, `align="inline-control"`
|
|
84
|
+
for a control whose own inline padding contributes about 8px, and `align="none"`
|
|
85
|
+
(the default) for a content item or composition that owns its geometry.
|
|
48
86
|
|
|
49
87
|
```tsx
|
|
50
|
-
import { CatalogExample, CatalogExampleStack } from
|
|
88
|
+
import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
|
|
51
89
|
|
|
52
90
|
const buttonPreview = (
|
|
53
|
-
<CatalogExampleStack
|
|
91
|
+
<CatalogExampleStack
|
|
92
|
+
label="Button variants"
|
|
93
|
+
rootAttributes={{ "data-demo": "button" }}
|
|
94
|
+
>
|
|
54
95
|
<CatalogExample label="Icon" note="A bare glyph." align="glyph">
|
|
55
96
|
<LucideIcon icon={Plus} name="plus" />
|
|
56
97
|
</CatalogExample>
|
|
57
98
|
<CatalogExample label="Control" align="inline-control">
|
|
58
99
|
<SegmentedControl id="view" label="View" value="list" choices={choices} />
|
|
59
100
|
</CatalogExample>
|
|
60
|
-
<CatalogExample
|
|
61
|
-
|
|
101
|
+
<CatalogExample
|
|
102
|
+
label="Authoring note"
|
|
103
|
+
note="Explanatory chrome is not a specimen."
|
|
104
|
+
rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
|
|
105
|
+
>
|
|
106
|
+
<p>Use the public helper contract.</p>
|
|
62
107
|
</CatalogExample>
|
|
63
108
|
</CatalogExampleStack>
|
|
64
109
|
);
|
|
65
110
|
```
|
|
66
111
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
the
|
|
73
|
-
|
|
112
|
+
The overlay selects every immediate child of a `CatalogExample` except the
|
|
113
|
+
helper-generated label and note. It does not recursively promote a nested child
|
|
114
|
+
to be the specimen. Outside an example row, it selects only top-level
|
|
115
|
+
`[data-component]` roots in the canvas and ignores nested component descendants.
|
|
116
|
+
These rules keep a row's label/group scaffolding out of the measurement and make
|
|
117
|
+
the authored nesting determine exactly what is inspected.
|
|
118
|
+
|
|
119
|
+
### Metadata ownership
|
|
120
|
+
|
|
121
|
+
Use `rootAttributes` on either helper for authoring metadata such as `data-demo`
|
|
122
|
+
or `data-catalog-geometry-overlay-skip`; the metadata lands on that helper's
|
|
123
|
+
rendered root. The slot accepts only `data-*` strings. Structural
|
|
124
|
+
`data-catalog-example`, `data-catalog-example-stack`, and `data-align` semantics
|
|
125
|
+
remain helper-owned and are rejected case-insensitively at runtime, including
|
|
126
|
+
from structurally widened or JavaScript objects. Do not copy the helpers'
|
|
127
|
+
private `kui-catalog-*` classes into preview markup.
|
|
128
|
+
|
|
129
|
+
The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
|
|
130
|
+
The helpers own their structural markers, label/note anatomy, alignment marker,
|
|
131
|
+
and private classes. Component metadata such as margin/border/padding ownership
|
|
132
|
+
lives in `component-catalog.json`; do not infer or overwrite it from overlay
|
|
133
|
+
pixels.
|
|
134
|
+
|
|
135
|
+
### Geometry overlay and legend
|
|
136
|
+
|
|
137
|
+
Pass the conditional `geometryOverlay` boolean to `Catalog`, then call
|
|
138
|
+
`wireCatalogGeometryOverlay(root)` once after the first render and retain its
|
|
139
|
+
disposer alongside `wireCatalog`'s.
|
|
140
|
+
|
|
141
|
+
| Overlay mark | Meaning | It is not |
|
|
142
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
143
|
+
| Orange translucent band | A positive computed margin on the selected specimen, after subtracting the helper's alignment inset. | Padding, gap, or empty content. Zero and negative margins are not drawn. |
|
|
144
|
+
| Quiet dashed outline | The border-box outer bound of a selected specimen whose computed background is transparent. | A real CSS border, focus ring, padding edge, or proof that the specimen owns its size. |
|
|
145
|
+
|
|
146
|
+
The overlay does not visualize padding, borders, gaps, negative/zero margins,
|
|
147
|
+
scroll overflow, hit targets, nested descendants, or geometry ownership. Inspect
|
|
148
|
+
computed styles and the machine-readable `geometry` metadata for those facts.
|
|
149
|
+
Opaque specimens do not receive the transparent-bound outline.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<Catalog
|
|
153
|
+
{...props}
|
|
154
|
+
geometryOverlay={activeEntry.kind === "component"}
|
|
155
|
+
content={renderers[active.value]()}
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Put `rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}` on a
|
|
160
|
+
`CatalogExample` or `CatalogExampleStack` only when that whole subtree is
|
|
161
|
+
explanatory chrome rather than a specimen. The marker excludes the marked root
|
|
162
|
+
and every descendant from selection; it does not merely hide one band. It is
|
|
163
|
+
normally unnecessary in a composition because the active entry already sets the
|
|
164
|
+
global overlay to `false`.
|
|
165
|
+
|
|
166
|
+
Use the overlay together with machine-readable geometry ownership metadata; the
|
|
167
|
+
overlay verifies what is rendered, while metadata tells people and AI tools
|
|
168
|
+
whether the component, its parent, or its children are responsible for margin,
|
|
169
|
+
border, and padding. Catalogs for downstream components should conform to the
|
|
170
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
171
|
+
contract and can start from the checked
|
|
172
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
173
|
+
example; provide those entries beside Kerf's shipped catalog to AI tools.
|
|
174
|
+
|
|
175
|
+
### Automated conformance and reviewed exceptions
|
|
176
|
+
|
|
177
|
+
Run `npm run check:demo-conformance` after changing a first-party demo, its
|
|
178
|
+
catalog kind, or the shell's overlay logic. The TypeScript-AST gate verifies
|
|
179
|
+
facts that source can prove without guessing at rendered intent:
|
|
180
|
+
|
|
181
|
+
- focused component routes import and use `CatalogExampleStack` and
|
|
182
|
+
`CatalogExample` from the public package;
|
|
183
|
+
- focused route metadata uses the helpers' `rootAttributes` slot, example rows
|
|
184
|
+
are not empty, and composition routes do not add redundant skip markers;
|
|
185
|
+
- every `@kerfjs/ui` import is a published package export and relative imports
|
|
186
|
+
do not reach into `ui/src`;
|
|
187
|
+
- demo JSX does not copy private `kui-catalog-*` structural classes; and
|
|
188
|
+
- the shell derives both geometry-overlay enablement and documented demo mode
|
|
189
|
+
from the active entry's source and kind.
|
|
190
|
+
|
|
191
|
+
The gate deliberately does not infer component ownership from arbitrary class
|
|
192
|
+
names, margins, borders, or nested descendants. Runtime selection and geometry
|
|
193
|
+
remain the browser suite's job.
|
|
194
|
+
|
|
195
|
+
A focused route may bypass the two public layout helpers only when the route's
|
|
196
|
+
stage geometry is itself the reviewed specimen. Add the narrow waiver to
|
|
197
|
+
[`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
|
|
198
|
+
with the exact route, source file, stable diagnostic ids, a substantive reason,
|
|
199
|
+
and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
|
|
200
|
+
private imports, private markup, empty examples, composition overlay drift, and
|
|
201
|
+
shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
|
|
202
|
+
and stale exceptions, so delete a waiver when its route adopts the standard
|
|
203
|
+
helpers.
|
|
204
|
+
|
|
205
|
+
## Selection reveal
|
|
206
|
+
|
|
207
|
+
Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
|
|
208
|
+
`onSelect` updates controlled state, the helper waits one animation frame, finds
|
|
209
|
+
the exact matching `data-item-id`, and scrolls it into view without changing
|
|
210
|
+
focus. A newer selection or disposal cancels the pending reveal. The default
|
|
211
|
+
media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
|
|
212
|
+
layouts keep their existing scroll position.
|
|
213
|
+
|
|
214
|
+
Pass an options object instead of `true` to customize `block`, `inline`,
|
|
215
|
+
`behavior`, or `media`; `media: false` deliberately enables the behavior at all
|
|
216
|
+
sizes. For an initial deep link that did not come through `wireCatalog`, call
|
|
217
|
+
`revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
|
|
74
218
|
|
|
75
219
|
## Complete example
|
|
76
220
|
|
|
77
221
|
```tsx
|
|
78
|
-
import { mount, signal } from
|
|
79
|
-
import {
|
|
80
|
-
|
|
81
|
-
|
|
222
|
+
import { mount, signal, type SafeHtml } from "kerfjs";
|
|
223
|
+
import {
|
|
224
|
+
Catalog,
|
|
225
|
+
CatalogExample,
|
|
226
|
+
CatalogExampleStack,
|
|
227
|
+
type CatalogSection,
|
|
228
|
+
} from "@kerfjs/ui/catalog";
|
|
229
|
+
import {
|
|
230
|
+
revealCatalogEntry,
|
|
231
|
+
wireCatalog,
|
|
232
|
+
wireCatalogGeometryOverlay,
|
|
233
|
+
} from "@kerfjs/ui/wire-catalog";
|
|
234
|
+
import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
|
|
82
235
|
|
|
83
|
-
|
|
84
|
-
|
|
236
|
+
type DemoKind = "component" | "composition";
|
|
237
|
+
type DemoEntry = CatalogSection["entries"][number] & { kind: DemoKind };
|
|
238
|
+
|
|
239
|
+
// 1. Describe selection and overlay mode once.
|
|
240
|
+
const entries: DemoEntry[] = [
|
|
85
241
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
242
|
+
id: "button",
|
|
243
|
+
name: "Button",
|
|
244
|
+
kind: "component",
|
|
245
|
+
description: "A pressable control.",
|
|
246
|
+
resources: [
|
|
247
|
+
{ label: "Source", href: "/src/button.tsx", detail: "src/button.tsx" },
|
|
92
248
|
],
|
|
93
249
|
},
|
|
94
|
-
{
|
|
250
|
+
{
|
|
251
|
+
id: "profile-form",
|
|
252
|
+
name: "Profile form",
|
|
253
|
+
kind: "composition",
|
|
254
|
+
description: "A labeled field and save action working together.",
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
const sections: CatalogSection[] = [
|
|
258
|
+
{
|
|
259
|
+
category: "Examples",
|
|
260
|
+
entries: entries.map(({ kind: _kind, ...entry }) => entry),
|
|
261
|
+
},
|
|
95
262
|
];
|
|
96
263
|
|
|
97
|
-
// 2.
|
|
98
|
-
const renderers: Record<string, () =>
|
|
99
|
-
button: () =>
|
|
100
|
-
|
|
101
|
-
|
|
264
|
+
// 2. Every preview uses one public group and public example rows.
|
|
265
|
+
const renderers: Record<string, () => SafeHtml> = {
|
|
266
|
+
button: () => (
|
|
267
|
+
<CatalogExampleStack
|
|
268
|
+
label="Button states"
|
|
269
|
+
rootAttributes={{ "data-demo": "button" }}
|
|
270
|
+
>
|
|
271
|
+
<CatalogExample label="Default" align="inline-control">
|
|
272
|
+
<Button label="Save" />
|
|
273
|
+
</CatalogExample>
|
|
274
|
+
<CatalogExample
|
|
275
|
+
label="Authoring note"
|
|
276
|
+
note="This explanatory row is deliberately excluded from inspection."
|
|
277
|
+
rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
|
|
278
|
+
>
|
|
279
|
+
<p>The application owns product copy and actions.</p>
|
|
280
|
+
</CatalogExample>
|
|
281
|
+
</CatalogExampleStack>
|
|
282
|
+
),
|
|
283
|
+
"profile-form": () => (
|
|
284
|
+
<CatalogExampleStack
|
|
285
|
+
label="Profile form composition"
|
|
286
|
+
rootAttributes={{ "data-demo": "profile-form" }}
|
|
287
|
+
>
|
|
288
|
+
<CatalogExample label="Complete composition">
|
|
289
|
+
<ProfileForm />
|
|
290
|
+
</CatalogExample>
|
|
291
|
+
</CatalogExampleStack>
|
|
292
|
+
),
|
|
102
293
|
};
|
|
103
294
|
|
|
104
295
|
// 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
|
|
105
|
-
const initial =
|
|
296
|
+
const initial =
|
|
297
|
+
new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
|
|
106
298
|
const active = signal(initial);
|
|
107
299
|
const collapsed = signal(false);
|
|
108
|
-
const theme = signal<
|
|
300
|
+
const theme = signal<"light" | "dark">("light");
|
|
301
|
+
const activeEntry = () => entries.find(({ id }) => id === active.value) ?? entries[0];
|
|
109
302
|
|
|
110
|
-
const app = document.getElementById(
|
|
303
|
+
const app = document.getElementById("app")!;
|
|
111
304
|
mount(app, () => (
|
|
112
305
|
<Catalog
|
|
113
|
-
brand={{ title:
|
|
306
|
+
brand={{ title: "Acme UI", subtitle: "Design system" }}
|
|
114
307
|
sections={sections}
|
|
115
308
|
active={active.value}
|
|
116
309
|
content={renderers[active.value]?.() ?? <></>}
|
|
117
310
|
collapsed={collapsed.value}
|
|
118
311
|
theme={theme.value}
|
|
312
|
+
geometryOverlay={activeEntry().kind === "component"}
|
|
119
313
|
/>
|
|
120
314
|
));
|
|
121
315
|
|
|
122
316
|
wireCatalog(app, {
|
|
123
|
-
onSelect: (id) => {
|
|
124
|
-
|
|
317
|
+
onSelect: (id) => {
|
|
318
|
+
active.value = id;
|
|
319
|
+
},
|
|
320
|
+
onToggleSidebar: () => {
|
|
321
|
+
collapsed.value = !collapsed.value;
|
|
322
|
+
},
|
|
125
323
|
onToggleTheme: () => {
|
|
126
|
-
theme.value = theme.value ===
|
|
324
|
+
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
127
325
|
document.documentElement.dataset.theme = theme.value; // apply your theme however you like
|
|
128
326
|
},
|
|
129
|
-
urlParam:
|
|
327
|
+
urlParam: "c", // mirror the active id into ?c=<id>
|
|
328
|
+
revealSelection: true, // reveal long desktop sidebars without moving focus
|
|
130
329
|
});
|
|
330
|
+
wireCatalogGeometryOverlay(app);
|
|
331
|
+
|
|
332
|
+
// Optional for an initial deep link whose row may start outside the viewport.
|
|
333
|
+
revealCatalogEntry(app, initial, { block: "center" });
|
|
131
334
|
```
|
|
132
335
|
|
|
133
336
|
## Ownership boundary
|
|
@@ -7,7 +7,7 @@ a full shell so you can dock one panel wherever your layout needs it. They are
|
|
|
7
7
|
subpath-only, tree-shakeable modules that add nothing to the main barrel.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @kerfjs/ui
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.css`.
|
|
@@ -46,33 +46,60 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
|
|
|
46
46
|
## Example
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
|
-
import { signal, mount } from
|
|
50
|
-
import { deviceClass } from
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
import { signal, mount } from "kerfjs";
|
|
50
|
+
import { deviceClass } from "@kerfjs/ui/device-class";
|
|
51
|
+
import {
|
|
52
|
+
CollapsiblePanel,
|
|
53
|
+
CollapsiblePanelToggle,
|
|
54
|
+
} from "@kerfjs/ui/collapsible-panel";
|
|
55
|
+
import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
|
|
56
|
+
import "@kerfjs/ui/collapsible-panel.css";
|
|
54
57
|
|
|
55
58
|
const navCollapsed = signal(false);
|
|
56
59
|
const device = deviceClass();
|
|
57
60
|
|
|
58
|
-
const app = document.querySelector(
|
|
61
|
+
const app = document.querySelector("#app")!;
|
|
59
62
|
mount(app, () => (
|
|
60
63
|
<div class="layout">
|
|
61
|
-
<CollapsiblePanel
|
|
64
|
+
<CollapsiblePanel
|
|
65
|
+
id="nav"
|
|
66
|
+
side="left"
|
|
67
|
+
collapsed={navCollapsed.value}
|
|
68
|
+
label="Navigator"
|
|
69
|
+
>
|
|
62
70
|
<header>
|
|
63
|
-
<CollapsiblePanelToggle
|
|
71
|
+
<CollapsiblePanelToggle
|
|
72
|
+
side="left"
|
|
73
|
+
collapsed={navCollapsed.value}
|
|
74
|
+
action="toggle-nav"
|
|
75
|
+
panelId="nav"
|
|
76
|
+
/>
|
|
64
77
|
</header>
|
|
65
78
|
{/* nav items */}
|
|
66
79
|
</CollapsiblePanel>
|
|
67
80
|
<main>
|
|
68
|
-
{navCollapsed.value &&
|
|
81
|
+
{navCollapsed.value && (
|
|
82
|
+
<CollapsiblePanelToggle
|
|
83
|
+
side="left"
|
|
84
|
+
collapsed
|
|
85
|
+
action="toggle-nav"
|
|
86
|
+
label="Show navigator"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
69
89
|
{/* content */}
|
|
70
90
|
</main>
|
|
71
91
|
</div>
|
|
72
92
|
));
|
|
73
93
|
|
|
74
94
|
const stop = wireSidebar(app, {
|
|
75
|
-
panels: [
|
|
95
|
+
panels: [
|
|
96
|
+
{
|
|
97
|
+
id: "nav",
|
|
98
|
+
collapsed: navCollapsed,
|
|
99
|
+
toggleAction: "toggle-nav",
|
|
100
|
+
storageKey: "app.nav-collapsed",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
76
103
|
deviceClass: device,
|
|
77
104
|
});
|
|
78
105
|
```
|
|
@@ -18,8 +18,124 @@ package delivery paths, Web Awesome's installed manifest, AI guidance, and
|
|
|
18
18
|
documentation links. Nuanced prose in this contract and the selection guide
|
|
19
19
|
remains authored rather than generated.
|
|
20
20
|
|
|
21
|
+
### Composition catalog v2
|
|
22
|
+
|
|
23
|
+
[`component-catalog-v2.json`](../ai/component-catalog-v2.json) adds a formal,
|
|
24
|
+
machine-evaluable composition layer without changing the v1 selection and
|
|
25
|
+
delivery contract. Every v1 entry projects once, in order, under the stable
|
|
26
|
+
qualified key `package:id`. Each v2 entry explicitly covers parents/contexts,
|
|
27
|
+
named zones and cardinality, child concepts, state ownership, required wiring,
|
|
28
|
+
responsive ownership, layout and geometry, accessibility obligations, public
|
|
29
|
+
CSS boundaries, diagnostics, and provenance.
|
|
30
|
+
|
|
31
|
+
The generator starts with permissive defaults. An `any` mode records that the
|
|
32
|
+
catalog has no defensible prohibition; it does not claim every composition is
|
|
33
|
+
recommended. Objective rules live in
|
|
34
|
+
`component-catalog-v2-overrides.json` and may carry stable `KUI-C###`
|
|
35
|
+
diagnostics. A tool reports a diagnostic only after proving its exact `when`
|
|
36
|
+
condition. Subjective choice, product policy, and visual taste remain prose.
|
|
37
|
+
|
|
38
|
+
V1 consumers continue unchanged. V2 consumers read v1 for selection/delivery
|
|
39
|
+
and v2 for composition. `npm run catalog:sync` projects every new component,
|
|
40
|
+
recipe, and supported Web Awesome entry, and the completeness gate prevents
|
|
41
|
+
silent omissions. Downstream catalogs use the v2 extension schema and types,
|
|
42
|
+
retain their own package identity, and qualify cross-catalog references. See
|
|
43
|
+
the checked
|
|
44
|
+
[`component-catalog-extension-v2.json`](./examples/component-catalog-extension-v2.json)
|
|
45
|
+
application-owned example.
|
|
46
|
+
|
|
47
|
+
### Compile-time contract boundary
|
|
48
|
+
|
|
49
|
+
The versioned
|
|
50
|
+
[`compile-time-contracts-v1.json`](../ai/compile-time-contracts-v1.json)
|
|
51
|
+
artifact maps stable `KUI-T###` ids to public imports, emitted symbols, and
|
|
52
|
+
catalog identities. Its positive/negative fixture compiles against both source
|
|
53
|
+
and a freshly packed package, so source declarations and shipped declarations
|
|
54
|
+
cannot silently diverge. See [Compile-time contracts](./type-contracts.md) for
|
|
55
|
+
the full audit, migration guidance, and the relationships deliberately left to
|
|
56
|
+
runtime/catalog checks because TypeScript cannot prove them.
|
|
57
|
+
|
|
58
|
+
### Application UI profile
|
|
59
|
+
|
|
60
|
+
The component catalogs describe what packages provide; an application profile
|
|
61
|
+
describes which supported choices a project has approved. A workspace may
|
|
62
|
+
check in `.kerf-ui-profile.json` conforming to
|
|
63
|
+
[`application-ui-profile.schema.json`](../ai/application-ui-profile.schema.json).
|
|
64
|
+
Keep package-qualified catalog locations and recurring-concept preferences,
|
|
65
|
+
allowed color schemes and density, public semantic-token overrides,
|
|
66
|
+
layout/responsive conventions, and narrow rule exceptions there. Product
|
|
67
|
+
records, copy, permissions, user preferences, and transport state do not belong
|
|
68
|
+
in this policy file.
|
|
69
|
+
|
|
70
|
+
Every catalog location names a v2 composition artifact. `selection` is optional
|
|
71
|
+
for consumer packages whose generated metadata declares v1 selection guidance
|
|
72
|
+
not applicable; `@kerfjs/ui` retains its required v1 selection artifact. This
|
|
73
|
+
lets a generated `component-catalog-v2.json` participate directly without a
|
|
74
|
+
fabricated compatibility file.
|
|
75
|
+
|
|
76
|
+
Discovery and precedence are deterministic:
|
|
77
|
+
|
|
78
|
+
1. Load `application-ui-profile.defaults.json` from `@kerfjs/ui`.
|
|
79
|
+
2. Load `.kerf-ui-profile.json` at the workspace root when present.
|
|
80
|
+
3. Walk from the workspace root toward the target directory and load each
|
|
81
|
+
directory-local profile in parent-to-child order.
|
|
82
|
+
|
|
83
|
+
Later scalar and object-map values win. Catalogs merge by package; preferences
|
|
84
|
+
and token overrides merge by key; exceptions merge by stable id. Lists such as
|
|
85
|
+
allowed themes/densities replace the earlier list rather than accumulating.
|
|
86
|
+
Every resolved field retains its source file. The shipped
|
|
87
|
+
`application-ui-profile.mjs` API implements discovery, merge, loading, and
|
|
88
|
+
validation and reports actionable originating file + JSON-path diagnostics for stale
|
|
89
|
+
catalogs, unknown component/rule/token references, preference conflicts, and
|
|
90
|
+
broad exceptions. Each raw layer is validated against the catalogs effective at
|
|
91
|
+
that exact precedence point before merge, so a broken parent catalog, stale
|
|
92
|
+
parent reference, invalid value, or unknown field cannot disappear merely
|
|
93
|
+
because a child profile replaces it. The shipped
|
|
94
|
+
`application-ui-profile-sync.cjs` projects the same merge and validation contract
|
|
95
|
+
for synchronous hosts such as ESLint rules; it deliberately performs no async
|
|
96
|
+
I/O.
|
|
97
|
+
See the
|
|
98
|
+
[`application-ui-profile.json`](./examples/application-ui-profile.json) example.
|
|
99
|
+
|
|
100
|
+
Catalog detail footers use one standard resource vocabulary and order. Build
|
|
101
|
+
them with `catalogResources()` from `@kerfjs/ui/catalog-resources`: `Demo source`
|
|
102
|
+
first, optional `Component source` and `Design template`, then `Guidance`.
|
|
103
|
+
Third-party entries use the typed `integrationGuidance` kind, which renders
|
|
104
|
+
`Integration guidance`, and omit `Component source` when the implementation is
|
|
105
|
+
not owned by the catalog's project. Prefer these semantic kinds over local labels
|
|
106
|
+
such as “View source” or “Read UI guidance”; the helper makes that convention a
|
|
107
|
+
compile-time choice while still returning ordinary `CatalogResource[]` values.
|
|
108
|
+
|
|
21
109
|
## Ownership boundaries
|
|
22
110
|
|
|
111
|
+
### Geometry ownership metadata
|
|
112
|
+
|
|
113
|
+
The canonical [`component-catalog.json`](../ai/component-catalog.json) records
|
|
114
|
+
the margin, border, and padding owner for every component and composition under
|
|
115
|
+
`geometry`. Read it before adding a wrapper or local spacing rule:
|
|
116
|
+
|
|
117
|
+
- `self` means the entry's public visual contract supplies that geometry.
|
|
118
|
+
- `parent` means the embedding layout must supply it.
|
|
119
|
+
- `child` means composed descendants supply it; the entry's outer shell does not.
|
|
120
|
+
- `none` means that dimension is intentionally absent from the standard contract.
|
|
121
|
+
- `conditional` means a variant, part, or configuration changes ownership; the
|
|
122
|
+
accompanying `notes` explain the boundary.
|
|
123
|
+
|
|
124
|
+
The values describe public ownership, including themed shadow parts, rather
|
|
125
|
+
than merely asking whether a CSS declaration appears on the host element.
|
|
126
|
+
Recipes are exempt because they arrange entries whose individual ownership is
|
|
127
|
+
already cataloged.
|
|
128
|
+
|
|
129
|
+
Downstream packages and applications should publish the same contract for their
|
|
130
|
+
reusable visual components. Use
|
|
131
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
132
|
+
and copy the structure of the checked
|
|
133
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
134
|
+
example. Keep app and Kerf entries as package-qualified inputs, then let people
|
|
135
|
+
or AI tools search their combined entries and compare `geometry` before adding
|
|
136
|
+
wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
|
|
137
|
+
claim Kerf delivery paths for them.
|
|
138
|
+
|
|
23
139
|
- Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
|
|
24
140
|
- Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
|
|
25
141
|
- Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
|
|
@@ -27,7 +143,8 @@ remains authored rather than generated.
|
|
|
27
143
|
- Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables; prefer an equivalent prop or token before writing a selector.
|
|
28
144
|
|
|
29
145
|
`ListItem.rootAttributes`, `ListActionRow.rootAttributes`,
|
|
30
|
-
`ListHeader.rootAttributes`,
|
|
146
|
+
`ListHeader.rootAttributes`, `AppTab.rootAttributes`,
|
|
147
|
+
`CatalogExample.rootAttributes`, and `CatalogExampleStack.rootAttributes` accept
|
|
31
148
|
application-owned `data-*` metadata without
|
|
32
149
|
adding product fields to the shared API. A `ListActionRow` trailing action uses
|
|
33
150
|
`trailingActionAttributes`, and a `ListHeader` action/disclosure uses
|
|
@@ -36,7 +153,7 @@ the corresponding `aria-controls`/`aria-haspopup` relationship. These slots do
|
|
|
36
153
|
not accept roles or component-owned action, selection, disclosure, accessible
|
|
37
154
|
name, disabled, or icon fields. Extension objects are filtered at runtime,
|
|
38
155
|
including case-insensitive rejection of protected `data-*` names, and the
|
|
39
|
-
component writes its protected attributes after the accepted metadata. Typed,
|
|
156
|
+
component or helper writes its protected attributes after the accepted metadata. Typed,
|
|
40
157
|
structurally widened, and JavaScript callers therefore cannot replace its
|
|
41
158
|
contract.
|
|
42
159
|
|
|
@@ -65,6 +182,11 @@ layout cannot be expressed by a prop or token. A class being public does not
|
|
|
65
182
|
make copied component markup an invocation or transfer state and accessibility
|
|
66
183
|
ownership to the application.
|
|
67
184
|
|
|
185
|
+
The v2 composition catalog additionally requires `boundaries.rootClass` to be
|
|
186
|
+
either one exact member of `publicClasses` or `null` when the entry has no
|
|
187
|
+
rendered class root. Runtime geometry tooling uses this explicit field; array
|
|
188
|
+
order never implies root ownership.
|
|
189
|
+
|
|
68
190
|
Do not select a component's descendant by element name, id, attribute alone, or
|
|
69
191
|
an unlisted implementation class. Selectors such as `.kui-state-banner span`,
|
|
70
192
|
`.kui-list-item [data-state]`, and `.kui-list-item .local-label` depend on
|
|
@@ -103,9 +225,12 @@ separator with a compact grip that appears on hover or keyboard focus. Override
|
|
|
103
225
|
`--kui-resizable-region-handle-active-color` on a region when its containing
|
|
104
226
|
surface needs a different contrast level.
|
|
105
227
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
228
|
+
`Pane` defines one structural model for sidebars, main areas, inspectors, and
|
|
229
|
+
dialogs. Its `.kui-pane` root is unpadded and contains an optional vertical
|
|
230
|
+
header, one scrolling vertical `.kui-pane__content`, and an optional footer.
|
|
231
|
+
Logical-edge separator lines are independently opt-in and default off.
|
|
232
|
+
`@kerfjs/ui/layout.css` retains the pane roles and supplies the related content
|
|
233
|
+
geometry classes.
|
|
109
234
|
`.kui-content` gives major children 24px vertical separation.
|
|
110
235
|
`.kui-content-item` gives one child 8px inline margin, a real 1px border,
|
|
111
236
|
8px padding, and 12px corners; border and background may be transparent without
|
|
@@ -166,14 +291,14 @@ render, hands in its own via `collapsible.signals`, drives it through
|
|
|
166
291
|
UI is consistent by default without every app reinventing it.
|
|
167
292
|
|
|
168
293
|
`wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
|
|
169
|
-
helpers. Its collapse behavior was
|
|
294
|
+
helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
|
|
170
295
|
transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
|
|
171
296
|
apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
|
|
172
297
|
app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
|
|
173
298
|
`NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
|
|
174
299
|
and tab order are data, a `ResizableRegion`'s committed size and a
|
|
175
300
|
`Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
|
|
176
|
-
Each helper already owns only the
|
|
301
|
+
Each helper already owns only the _ephemeral mechanics_ around that state —
|
|
177
302
|
`wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
|
|
178
303
|
drag preview, `wireResizableRegions` the live drag preview — and reports committed
|
|
179
304
|
changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is
|