@kerfjs/ui 5.0.0-beta.68 → 5.0.0-beta.69
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 +3 -0
- package/ai/component-catalog-extension.schema.json +12 -0
- package/ai/component-catalog.json +14 -4
- package/ai/component-catalog.schema.json +14 -0
- package/ai/component-composition-rules.json +2 -2
- package/ai/component-composition.d.ts +2 -0
- package/ai/component-composition.json +2 -2
- package/ai/component-composition.schema.json +23 -0
- package/ai/components/loading-spinner.md +1 -0
- package/ai/components/tab-bar.md +5 -4
- package/ai/components/toolbar-control-group.md +2 -2
- package/ai/public-api-signatures-v1.md +34 -14
- package/ai/skill.md +18 -3
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/index.d.mts +1 -0
- package/analyzer/index.mjs +108 -12
- package/dist/catalog.js +4 -4
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-P4UXSYIJ.js → chunk-2ZOLX3IE.js} +2 -2
- package/dist/{chunk-P4UXSYIJ.js.map → chunk-2ZOLX3IE.js.map} +1 -1
- package/dist/{chunk-E36EVCKG.js → chunk-47CKKLBE.js} +2 -2
- package/dist/{chunk-E36EVCKG.js.map → chunk-47CKKLBE.js.map} +1 -1
- package/dist/{chunk-JCV4GXWB.js → chunk-6DFDTWGG.js} +100 -61
- package/dist/chunk-6DFDTWGG.js.map +1 -0
- package/dist/{chunk-IAOAZUOO.js → chunk-7OKX3EF6.js} +2 -2
- package/dist/{chunk-IAOAZUOO.js.map → chunk-7OKX3EF6.js.map} +1 -1
- package/dist/{chunk-C74LITXE.js → chunk-B3XWUX3A.js} +2 -2
- package/dist/{chunk-C74LITXE.js.map → chunk-B3XWUX3A.js.map} +1 -1
- package/dist/{chunk-F7OCOBTG.js → chunk-CRVDF4A6.js} +4 -1
- package/dist/chunk-CRVDF4A6.js.map +1 -0
- package/dist/{chunk-TGM424PI.js → chunk-H7IODXGY.js} +3 -3
- package/dist/{chunk-TGM424PI.js.map → chunk-H7IODXGY.js.map} +1 -1
- package/dist/chunk-KVBRMEPM.js +44 -0
- package/dist/chunk-KVBRMEPM.js.map +1 -0
- package/dist/{chunk-EN4LDR5Y.js → chunk-NC4CFOID.js} +2 -2
- package/dist/{chunk-EN4LDR5Y.js.map → chunk-NC4CFOID.js.map} +1 -1
- package/dist/{chunk-5SYE4CCT.js → chunk-PQZPVBP4.js} +25 -18
- package/dist/chunk-PQZPVBP4.js.map +1 -0
- package/dist/{chunk-NXWE5S57.js → chunk-QJ5VFCW3.js} +12 -3
- package/dist/chunk-QJ5VFCW3.js.map +1 -0
- package/dist/{chunk-RKTTF3L4.js → chunk-X3TPECWG.js} +1 -1
- package/dist/chunk-X3TPECWG.js.map +1 -0
- package/dist/{chunk-VY6YRCTV.js → chunk-X3X4KRDK.js} +2 -2
- package/dist/{chunk-VY6YRCTV.js.map → chunk-X3X4KRDK.js.map} +1 -1
- package/dist/collapsible-panel.js +2 -2
- package/dist/empty-state.js +2 -1
- package/dist/index.js +10 -10
- package/dist/list-action-row.js +1 -1
- package/dist/list-item.js +1 -1
- package/dist/loading-spinner.d.ts +7 -2
- package/dist/loading-spinner.js +2 -1
- package/dist/nav-stack.js +1 -1
- package/dist/select.d.ts +4 -2
- package/dist/select.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/styles/app-tab.css +18 -1
- package/dist/styles/loading-spinner.css +4 -0
- package/dist/styles/select.css +79 -0
- package/dist/styles/tab-bar.css +12 -8
- package/dist/styles/token-search-field.css +19 -10
- package/dist/styles/toolbar-control-group.css +96 -3
- package/dist/tab-bar.d.ts +2 -0
- package/dist/tab-bar.js +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +13 -5
- package/dist/toolbar-control-group.js +2 -1
- package/dist/wire-catalog.js +1 -1
- package/dist/wire-scroll-dividers.d.ts +7 -4
- package/dist/wire-scroll-dividers.js +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +10 -0
- package/docs/catalog.md +3 -0
- package/docs/component-contract.md +58 -9
- package/docs/component-selection.md +22 -6
- package/docs/examples/component-catalog-extension.json +1 -0
- package/docs/layout.md +6 -0
- package/docs/ui-analyzer.md +12 -0
- package/docs/ui-doctor.md +15 -3
- package/doctor/cli.mjs +5 -0
- package/doctor/config.schema.json +2 -0
- package/doctor/index.d.mts +3 -0
- package/doctor/index.mjs +27 -1
- package/llms.txt +15 -5
- package/package.json +1 -1
- package/dist/chunk-5SYE4CCT.js.map +0 -1
- package/dist/chunk-F7OCOBTG.js.map +0 -1
- package/dist/chunk-JCV4GXWB.js.map +0 -1
- package/dist/chunk-NXWE5S57.js.map +0 -1
- package/dist/chunk-RKTTF3L4.js.map +0 -1
- package/dist/chunk-ZLWQZUGF.js +0 -27
- package/dist/chunk-ZLWQZUGF.js.map +0 -1
package/README.md
CHANGED
|
@@ -418,6 +418,9 @@ restyle). It reports repeated insets, exceptional spacing shorthands, and
|
|
|
418
418
|
dynamic classes separately as review findings. Text, versioned JSON,
|
|
419
419
|
and SARIF outputs carry stable `KUI-L###` ids, repository-relative locations,
|
|
420
420
|
evidence, and ownership chains. See the [analyzer guide](./docs/ui-analyzer.md).
|
|
421
|
+
Opt into ownership between components in one package with Doctor's
|
|
422
|
+
`"ownership": "component"` setting and catalog `styleSources` declarations;
|
|
423
|
+
the default keeps package-level ownership.
|
|
421
424
|
|
|
422
425
|
### Browser-backed integration evaluation
|
|
423
426
|
|
|
@@ -59,6 +59,18 @@
|
|
|
59
59
|
"uniqueItems": true
|
|
60
60
|
},
|
|
61
61
|
"source": { "$ref": "#/$defs/repositoryPath" },
|
|
62
|
+
"styleSources": {
|
|
63
|
+
"description": "Stylesheets owned by this component, relative to the consumer package root. Used by Doctor's opt-in component ownership mode.",
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": {
|
|
66
|
+
"allOf": [
|
|
67
|
+
{ "$ref": "#/$defs/repositoryPath" },
|
|
68
|
+
{ "pattern": "\\.css$" }
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"minItems": 1,
|
|
72
|
+
"uniqueItems": true
|
|
73
|
+
},
|
|
62
74
|
"demoSource": { "$ref": "#/$defs/repositoryPath" },
|
|
63
75
|
"geometry": { "$ref": "#/$defs/geometry" },
|
|
64
76
|
"documentation": { "type": "string", "minLength": 1 }
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
"schemaVersion": 1,
|
|
4
4
|
"package": "@kerfjs/ui",
|
|
5
5
|
"description": "Machine-readable selection, composition, delivery, ownership, and accessibility metadata for Kerf UI and its supported Web Awesome set.",
|
|
6
|
+
"wiringProviders": [
|
|
7
|
+
{
|
|
8
|
+
"export": "wireCatalog",
|
|
9
|
+
"import": "@kerfjs/ui/wire-catalog",
|
|
10
|
+
"provides": ["wireScrollDividers"]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
6
13
|
"entries": [
|
|
7
14
|
{
|
|
8
15
|
"id": "badge",
|
|
@@ -1591,7 +1598,7 @@
|
|
|
1591
1598
|
"useWhen": [
|
|
1592
1599
|
"Group related controls inside a toolbar with a supported visual treatment.",
|
|
1593
1600
|
"Use ToolbarActionLink for navigation or external resources that must retain native anchor semantics inside a group.",
|
|
1594
|
-
"Fill it with plain <button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup.
|
|
1601
|
+
"Fill it with plain <button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Each control stays whole: an icon sits beside its label on one row and no trigger gets a fixed width. Use overflow=wrap for independent links that should move as whole actions to another row on narrow surfaces.",
|
|
1595
1602
|
"For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight.",
|
|
1596
1603
|
"In a Workbench or CollapsiblePanel toolbar, mark a group relocateOnCollapse to keep it available in the work-area toolbar when the panel closes; place it directly or in an array in any toolbar zone."
|
|
1597
1604
|
],
|
|
@@ -1628,7 +1635,7 @@
|
|
|
1628
1635
|
"brand, neutral, or pop selected tone",
|
|
1629
1636
|
"nested dropdown trigger",
|
|
1630
1637
|
"standard, compact, or list-zero dropdown menu inset",
|
|
1631
|
-
"visible
|
|
1638
|
+
"visible, horizontally scrollable, or whole-action wrapping overflow",
|
|
1632
1639
|
"always visible or compact-only responsive visibility",
|
|
1633
1640
|
"relocate on panel collapse",
|
|
1634
1641
|
"avatar scrim",
|
|
@@ -3592,7 +3599,8 @@
|
|
|
3592
3599
|
"description": "Controlled tab strips with typed presentation and allocation, a scroll-owning tab region, an optional adjacent trailing action, and a separately pinned far-edge end action.",
|
|
3593
3600
|
"uses": [
|
|
3594
3601
|
"tabs",
|
|
3595
|
-
"toolbar-control-group"
|
|
3602
|
+
"toolbar-control-group",
|
|
3603
|
+
"wa-button"
|
|
3596
3604
|
],
|
|
3597
3605
|
"purpose": "Controlled tab strips whose tabs shrink and scroll while fixed leading, trailing, and far-edge end actions remain visible.",
|
|
3598
3606
|
"publicExports": [
|
|
@@ -3637,6 +3645,7 @@
|
|
|
3637
3645
|
"intrinsic or fill allocation",
|
|
3638
3646
|
"separate or adjacent trailing action",
|
|
3639
3647
|
"far-edge end action",
|
|
3648
|
+
"standalone Web Awesome action in trailing or end",
|
|
3640
3649
|
"overflow dividers on each side with tabs scrolled out of view (wired by wireScrollDividers)"
|
|
3641
3650
|
],
|
|
3642
3651
|
"accessibility": [
|
|
@@ -4691,7 +4700,8 @@
|
|
|
4691
4700
|
],
|
|
4692
4701
|
"variants": [
|
|
4693
4702
|
"labeled",
|
|
4694
|
-
"decorative"
|
|
4703
|
+
"decorative",
|
|
4704
|
+
"sized"
|
|
4695
4705
|
],
|
|
4696
4706
|
"accessibility": [
|
|
4697
4707
|
"Labeled spinners use role img; decorative spinners are hidden and stop for reduced motion."
|
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
"schemaVersion": { "const": 1 },
|
|
10
10
|
"package": { "const": "@kerfjs/ui" },
|
|
11
11
|
"description": { "type": "string", "minLength": 1 },
|
|
12
|
+
"wiringProviders": {
|
|
13
|
+
"description": "Public helpers whose call installs other cataloged wiring helpers.",
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": ["export", "import", "provides"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"export": { "type": "string", "minLength": 1 },
|
|
20
|
+
"import": { "type": "string", "minLength": 1 },
|
|
21
|
+
"provides": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "uniqueItems": true }
|
|
22
|
+
},
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
25
|
+
},
|
|
12
26
|
"entries": {
|
|
13
27
|
"type": "array",
|
|
14
28
|
"items": { "$ref": "#/$defs/entry" }
|
|
@@ -171,14 +171,14 @@
|
|
|
171
171
|
{
|
|
172
172
|
"id": "trailing",
|
|
173
173
|
"jsx": { "prop": "trailing" },
|
|
174
|
-
"accepts": ["toolbar-control-group"],
|
|
174
|
+
"accepts": ["toolbar-control-group", "wa-button"],
|
|
175
175
|
"cardinality": { "min": 0, "max": 1 },
|
|
176
176
|
"exclusiveWith": []
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
"id": "end",
|
|
180
180
|
"jsx": { "prop": "end" },
|
|
181
|
-
"accepts": ["toolbar-control-group"],
|
|
181
|
+
"accepts": ["toolbar-control-group", "wa-button"],
|
|
182
182
|
"cardinality": { "min": 0, "max": 1 },
|
|
183
183
|
"exclusiveWith": []
|
|
184
184
|
}
|
|
@@ -85,6 +85,8 @@ export interface CatalogCompositionEntry {
|
|
|
85
85
|
wiring: {
|
|
86
86
|
required: boolean;
|
|
87
87
|
helpers: string[];
|
|
88
|
+
/** Defining source files for application-owned helpers, relative to the catalog package root. */
|
|
89
|
+
sources?: Array<{ export: string; source: string }>;
|
|
88
90
|
obligations: string[];
|
|
89
91
|
/** Wiring-owned `data-*` attributes a helper writes at runtime. Optional
|
|
90
92
|
* for extension catalogs; first-party entries always list it. */
|
|
@@ -3024,14 +3024,14 @@
|
|
|
3024
3024
|
{
|
|
3025
3025
|
"id": "trailing",
|
|
3026
3026
|
"jsx": { "prop": "trailing" },
|
|
3027
|
-
"accepts": ["toolbar-control-group"],
|
|
3027
|
+
"accepts": ["toolbar-control-group", "wa-button"],
|
|
3028
3028
|
"cardinality": { "min": 0, "max": 1 },
|
|
3029
3029
|
"exclusiveWith": []
|
|
3030
3030
|
},
|
|
3031
3031
|
{
|
|
3032
3032
|
"id": "end",
|
|
3033
3033
|
"jsx": { "prop": "end" },
|
|
3034
|
-
"accepts": ["toolbar-control-group"],
|
|
3034
|
+
"accepts": ["toolbar-control-group", "wa-button"],
|
|
3035
3035
|
"cardinality": { "min": 0, "max": 1 },
|
|
3036
3036
|
"exclusiveWith": []
|
|
3037
3037
|
}
|
|
@@ -159,6 +159,16 @@
|
|
|
159
159
|
},
|
|
160
160
|
"sourceLinks": { "$ref": "#/$defs/stringList" },
|
|
161
161
|
"source": { "type": "string", "minLength": 1 },
|
|
162
|
+
"styleSources": {
|
|
163
|
+
"description": "Stylesheets owned by this component, relative to the catalog package root. Required for an entry to own its CSS in opt-in component ownership mode when no selection catalog supplies it.",
|
|
164
|
+
"type": "array",
|
|
165
|
+
"items": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"pattern": "^(?!.*(?:^|/)\\.{1,2}(?:/|$))[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*\\.css$"
|
|
168
|
+
},
|
|
169
|
+
"minItems": 1,
|
|
170
|
+
"uniqueItems": true
|
|
171
|
+
},
|
|
162
172
|
"rendersAs": {
|
|
163
173
|
"description": "For a wrapper component: the cataloged roots it renders (package-qualified keys), any one of them or nothing. Composition checks treat an element of this entry as each declared root: a zone accepts it only if it accepts every root, and each root's parent contract applies to where it is placed.",
|
|
164
174
|
"type": "array",
|
|
@@ -216,6 +226,19 @@
|
|
|
216
226
|
"required": { "type": "boolean" },
|
|
217
227
|
"scope": { "enum": ["application", "module"] },
|
|
218
228
|
"helpers": { "$ref": "#/$defs/stringList" },
|
|
229
|
+
"sources": {
|
|
230
|
+
"description": "Application-owned helper exports and their defining source files, relative to the catalog package root.",
|
|
231
|
+
"type": "array",
|
|
232
|
+
"items": {
|
|
233
|
+
"type": "object",
|
|
234
|
+
"required": ["export", "source"],
|
|
235
|
+
"properties": {
|
|
236
|
+
"export": { "type": "string", "minLength": 1 },
|
|
237
|
+
"source": { "type": "string", "minLength": 1 }
|
|
238
|
+
},
|
|
239
|
+
"additionalProperties": false
|
|
240
|
+
}
|
|
241
|
+
},
|
|
219
242
|
"obligations": { "$ref": "#/$defs/stringList" },
|
|
220
243
|
"stateAttributes": {
|
|
221
244
|
"description": "Wiring-owned state attributes: data-* attributes a wiring helper writes at runtime, which the application must not render, remove, or treat as its own state. Optional for extension catalogs; first-party entries always list it, empty when their helpers write none.",
|
package/ai/components/tab-bar.md
CHANGED
|
@@ -45,6 +45,7 @@ Exact prop names and types: [`@kerfjs/ui/tab-bar`](../public-api-signatures-v1.m
|
|
|
45
45
|
- intrinsic or fill allocation
|
|
46
46
|
- separate or adjacent trailing action
|
|
47
47
|
- far-edge end action
|
|
48
|
+
- standalone Web Awesome action in trailing or end
|
|
48
49
|
- overflow dividers on each side with tabs scrolled out of view (wired by wireScrollDividers)
|
|
49
50
|
|
|
50
51
|
## Composition
|
|
@@ -52,14 +53,14 @@ Exact prop names and types: [`@kerfjs/ui/tab-bar`](../public-api-signatures-v1.m
|
|
|
52
53
|
- Parents: any.
|
|
53
54
|
- Contexts: `tabpanel-navigation`.
|
|
54
55
|
- Children: only `app-tab` (required: `app-tab`).
|
|
55
|
-
- Built from: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md).
|
|
56
|
+
- Built from: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md).
|
|
56
57
|
|
|
57
58
|
Zones (a zone is bound to JSX only through its listed prop; never assume the zone id is a prop):
|
|
58
59
|
|
|
59
60
|
- `tabs` — JSX prop `children`; accepts [AppTab](./tabs.md); at least 1.
|
|
60
61
|
- `leading` — JSX prop `leading`; accepts [ToolbarControlGroup](./toolbar-control-group.md); 0–1.
|
|
61
|
-
- `trailing` — JSX prop `trailing`; accepts [ToolbarControlGroup](./toolbar-control-group.md); 0–1.
|
|
62
|
-
- `end` — JSX prop `end`; accepts [ToolbarControlGroup](./toolbar-control-group.md); 0–1.
|
|
62
|
+
- `trailing` — JSX prop `trailing`; accepts [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md); 0–1.
|
|
63
|
+
- `end` — JSX prop `end`; accepts [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md); 0–1.
|
|
63
64
|
|
|
64
65
|
## State and wiring
|
|
65
66
|
|
|
@@ -107,6 +108,6 @@ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-
|
|
|
107
108
|
|
|
108
109
|
## Related
|
|
109
110
|
|
|
110
|
-
- Components: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md), [SegmentedControl](./segmented-control.md), [Select](./select.md), [Tab Group](./wa-tab-group.md).
|
|
111
|
+
- Components: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md), [SegmentedControl](./segmented-control.md), [Select](./select.md), [Tab Group](./wa-tab-group.md).
|
|
111
112
|
- Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/accessibility.md#tabs`](../../docs/accessibility.md#tabs).
|
|
112
113
|
- UX catalog route: `?component=tab-bar`.
|
|
@@ -12,7 +12,7 @@ Provide complete toolbar action geometry through configuration, including semant
|
|
|
12
12
|
|
|
13
13
|
- Group related controls inside a toolbar with a supported visual treatment.
|
|
14
14
|
- Use ToolbarActionLink for navigation or external resources that must retain native anchor semantics inside a group.
|
|
15
|
-
- Fill it with plain \<button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup.
|
|
15
|
+
- Fill it with plain \<button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Each control stays whole: an icon sits beside its label on one row and no trigger gets a fixed width. Use overflow=wrap for independent links that should move as whole actions to another row on narrow surfaces.
|
|
16
16
|
- For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight.
|
|
17
17
|
- In a Workbench or CollapsiblePanel toolbar, mark a group relocateOnCollapse to keep it available in the work-area toolbar when the panel closes; place it directly or in an array in any toolbar zone.
|
|
18
18
|
|
|
@@ -49,7 +49,7 @@ Exact prop names and types: [`@kerfjs/ui/toolbar-control-group`](../public-api-s
|
|
|
49
49
|
- brand, neutral, or pop selected tone
|
|
50
50
|
- nested dropdown trigger
|
|
51
51
|
- standard, compact, or list-zero dropdown menu inset
|
|
52
|
-
- visible
|
|
52
|
+
- visible, horizontally scrollable, or whole-action wrapping overflow
|
|
53
53
|
- always visible or compact-only responsive visibility
|
|
54
54
|
- relocate on panel collapse
|
|
55
55
|
- avatar scrim
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Public API signatures for the UI authoring corpus
|
|
2
2
|
|
|
3
|
-
Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.
|
|
3
|
+
Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.69` and `kerfjs@5.0.0-beta.69`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
|
|
4
4
|
|
|
5
5
|
## `@kerfjs/ui/css-values`
|
|
6
6
|
|
|
@@ -275,9 +275,11 @@ type ToolbarControlGroupContent = 'icon' | 'text' | 'mixed' | 'avatar' | 'search
|
|
|
275
275
|
type ToolbarControlGroupFocusRing = 'control' | 'outline' | 'halo';
|
|
276
276
|
type ToolbarControlGroupSelectedChrome = 'raised' | 'filled' | 'outline';
|
|
277
277
|
type ToolbarControlGroupSelectedTone = 'brand' | 'neutral' | 'pop';
|
|
278
|
-
type ToolbarControlGroupOverflow = 'visible' | 'scroll';
|
|
278
|
+
type ToolbarControlGroupOverflow = 'visible' | 'scroll' | 'wrap';
|
|
279
279
|
type ToolbarControlGroupMenuInset = 'standard' | 'compact' | 'list-zero';
|
|
280
|
-
type ToolbarControlGroupVisibility = 'always' | 'compact-only';
|
|
280
|
+
type ToolbarControlGroupVisibility = 'always' | 'compact-only' | 'hide-collapsed-tiny' | 'yield-to-expanded-sibling';
|
|
281
|
+
type ToolbarControlGroupSizing = 'content' | 'grow' | 'fill';
|
|
282
|
+
type ToolbarControlGroupPlacement = 'start' | 'end';
|
|
281
283
|
interface ToolbarActionLinkProps {
|
|
282
284
|
href: string;
|
|
283
285
|
label: string;
|
|
@@ -316,10 +318,16 @@ interface ToolbarControlGroupProps {
|
|
|
316
318
|
nestedDropdown?: boolean;
|
|
317
319
|
/** Configure the nested dropdown menu inset without consumer ::part() CSS. */
|
|
318
320
|
menuInset?: ToolbarControlGroupMenuInset;
|
|
319
|
-
/** Keep an overlong row
|
|
321
|
+
/** Keep an overlong row inside the available width by scrolling or wrapping. */
|
|
320
322
|
overflow?: ToolbarControlGroupOverflow;
|
|
321
323
|
/** Responsive visibility owned by the enclosing Toolbar container. */
|
|
322
324
|
visibility?: ToolbarControlGroupVisibility;
|
|
325
|
+
/** Intrinsic (default), grow from a basis, or occupy a full wrapping row. */
|
|
326
|
+
sizing?: ToolbarControlGroupSizing;
|
|
327
|
+
/** Place this group at the end of its flex row in a wrapping Toolbar zone. */
|
|
328
|
+
placement?: ToolbarControlGroupPlacement;
|
|
329
|
+
/** CSS length used as the minimum width and flex basis for `sizing="grow"`; defaults to 19rem. */
|
|
330
|
+
growBasis?: string;
|
|
323
331
|
/** Move this group into the work-area toolbar when its panel collapses. */
|
|
324
332
|
relocateOnCollapse?: boolean;
|
|
325
333
|
/** Add contrast behind photo-backed avatar content. */
|
|
@@ -336,9 +344,9 @@ interface ToolbarControlGroupProps {
|
|
|
336
344
|
/** Native named-slot assignment when composed inside a web component. */
|
|
337
345
|
slot?: string;
|
|
338
346
|
}
|
|
339
|
-
declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
|
|
347
|
+
declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, sizing, placement, growBasis, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
|
|
340
348
|
|
|
341
|
-
export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
|
|
349
|
+
export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupPlacement, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupSizing, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
|
|
342
350
|
```
|
|
343
351
|
|
|
344
352
|
## `@kerfjs/ui/floating-toolbar`
|
|
@@ -1078,10 +1086,12 @@ interface TabBarProps {
|
|
|
1078
1086
|
label: string;
|
|
1079
1087
|
children: KerfUiContent;
|
|
1080
1088
|
leading?: KerfUiContent;
|
|
1089
|
+
/** Tab-local action: a ToolbarControlGroup or a standalone Web Awesome button. */
|
|
1081
1090
|
trailing?: KerfUiContent;
|
|
1082
1091
|
/**
|
|
1083
1092
|
* Action pinned to the far edge of the bar. Combine with an adjacent `trailing`
|
|
1084
1093
|
* action when the tab-local and workspace-level actions must remain distinct.
|
|
1094
|
+
* Accepts a ToolbarControlGroup or a standalone Web Awesome button.
|
|
1085
1095
|
*/
|
|
1086
1096
|
end?: KerfUiContent;
|
|
1087
1097
|
className?: string;
|
|
@@ -1186,6 +1196,7 @@ interface WireScrollDividersOptions {
|
|
|
1186
1196
|
*/
|
|
1187
1197
|
targets?: readonly ScrollDividerTarget[];
|
|
1188
1198
|
}
|
|
1199
|
+
type Root = HTMLElement | Document;
|
|
1189
1200
|
/**
|
|
1190
1201
|
* Wire scroll dividers below `root`: a divider between pinned chrome and the
|
|
1191
1202
|
* content that scrolls beside it shows only while content is scrolled away
|
|
@@ -1213,11 +1224,13 @@ interface WireScrollDividersOptions {
|
|
|
1213
1224
|
*
|
|
1214
1225
|
* Structure is re-read after every DOM change below root (a re-render that
|
|
1215
1226
|
* drops the attributes gets them back before paint), scroll is tracked with
|
|
1216
|
-
* one capturing listener, and size changes of each
|
|
1217
|
-
* with a `ResizeObserver`.
|
|
1218
|
-
*
|
|
1227
|
+
* one capturing listener per registered root, and size changes of each
|
|
1228
|
+
* scroller and its children with a `ResizeObserver`. Overlapping registrations
|
|
1229
|
+
* share one attribute writer; each disposer removes its registration, and the
|
|
1230
|
+
* final disposer removes the wiring-owned attributes. See
|
|
1231
|
+
* `docs/23-app-layouts.md` §3.7.
|
|
1219
1232
|
*/
|
|
1220
|
-
declare function wireScrollDividers(root:
|
|
1233
|
+
declare function wireScrollDividers(root: Root, { targets }?: WireScrollDividersOptions): () => void;
|
|
1221
1234
|
|
|
1222
1235
|
export { type ScrollDividerTarget, type WireScrollDividersOptions, wireScrollDividers };
|
|
1223
1236
|
```
|
|
@@ -2698,7 +2711,7 @@ type SelectAccessibleName = {
|
|
|
2698
2711
|
label?: never;
|
|
2699
2712
|
ariaLabel: string;
|
|
2700
2713
|
};
|
|
2701
|
-
type SelectPresentation = 'form' | 'toolbar-borderless' | 'navigation';
|
|
2714
|
+
type SelectPresentation = 'form' | 'toolbar-borderless' | 'navigation' | 'inline' | 'title';
|
|
2702
2715
|
type SelectSize = 'default' | 'compact';
|
|
2703
2716
|
type SelectSelectedPresentation = 'label' | 'icon-only';
|
|
2704
2717
|
type SelectFocusRingOwner = 'select' | 'group';
|
|
@@ -2715,7 +2728,7 @@ interface SelectBaseProps<Value extends string = string> {
|
|
|
2715
2728
|
fitMenu?: boolean;
|
|
2716
2729
|
/** Render as an unanimated loading skeleton: the label above a static, empty control box. */
|
|
2717
2730
|
placeholder?: boolean;
|
|
2718
|
-
/** Form (default), borderless toolbar,
|
|
2731
|
+
/** Form (default), borderless toolbar, intrinsic navigation, inline, or view-title chrome. */
|
|
2719
2732
|
presentation?: SelectPresentation;
|
|
2720
2733
|
size?: SelectSize;
|
|
2721
2734
|
/** Closed trigger width; omit to keep the presentation's default. */
|
|
@@ -2724,6 +2737,8 @@ interface SelectBaseProps<Value extends string = string> {
|
|
|
2724
2737
|
caret?: boolean;
|
|
2725
2738
|
/** Let an enclosing ToolbarControlGroup paint the composed focus ring. */
|
|
2726
2739
|
focusRingOwner?: SelectFocusRingOwner;
|
|
2740
|
+
/** Keep the trigger's focus ring inside its box when an ancestor clips overflow. */
|
|
2741
|
+
focusRingInset?: boolean;
|
|
2727
2742
|
/** Maximum closed-control label width in CSS pixels before ellipsis. */
|
|
2728
2743
|
labelMaxWidth?: number;
|
|
2729
2744
|
/** Native named-slot assignment when composed inside a web component. */
|
|
@@ -2844,17 +2859,22 @@ export { EmptyState, type EmptyStateProps };
|
|
|
2844
2859
|
|
|
2845
2860
|
```ts
|
|
2846
2861
|
import * as kerfjs from 'kerfjs';
|
|
2862
|
+
import { LucideIconSize } from './lucide-icon.js';
|
|
2863
|
+
import 'lucide';
|
|
2847
2864
|
|
|
2865
|
+
type LoadingSpinnerSize = LucideIconSize;
|
|
2848
2866
|
interface LoadingSpinnerProps {
|
|
2849
2867
|
className?: string;
|
|
2850
2868
|
label?: string;
|
|
2869
|
+
/** Named icon scale or positive pixel size, converted to rem. Omit for 1em. */
|
|
2870
|
+
size?: LoadingSpinnerSize;
|
|
2851
2871
|
/** Native named-slot assignment when composed inside a web component. */
|
|
2852
2872
|
slot?: string;
|
|
2853
2873
|
}
|
|
2854
2874
|
/** Stable viewBox-centered progress ring based on svg-spinners' MIT-licensed 180-ring. */
|
|
2855
|
-
declare function LoadingSpinner({ className, label, slot, }: LoadingSpinnerProps): kerfjs.SafeHtml;
|
|
2875
|
+
declare function LoadingSpinner({ className, label, size, slot, }: LoadingSpinnerProps): kerfjs.SafeHtml;
|
|
2856
2876
|
|
|
2857
|
-
export { LoadingSpinner, type LoadingSpinnerProps };
|
|
2877
|
+
export { LoadingSpinner, type LoadingSpinnerProps, type LoadingSpinnerSize };
|
|
2858
2878
|
```
|
|
2859
2879
|
|
|
2860
2880
|
## `@kerfjs/ui/skeleton`
|
package/ai/skill.md
CHANGED
|
@@ -54,9 +54,17 @@ wa-dropdown.kui-popup-menu { … }` lives with PopupMenu). A parent may key its
|
|
|
54
54
|
(a rule whose subject is a cataloged component), `KUI-L020` (another
|
|
55
55
|
component's private `--_*` variable), `KUI-L021` (a token a typed prop sets),
|
|
56
56
|
and `KUI-L022` (a hook class on a component's root) are errors, and
|
|
57
|
+
the analyzer resolves cataloged application imports through TypeScript `paths`
|
|
58
|
+
aliases as well as direct relative imports.
|
|
57
59
|
`eslint-plugin-kerfjs`'s `ui-component-ownership` reports `KUI-L020` /
|
|
58
60
|
`KUI-L021` in script. Repair them with the configuration each message names;
|
|
59
61
|
when none exists, report the component gap rather than suppressing the finding.
|
|
62
|
+
For compact known activity, give `LoadingSpinner` its `size` prop (`xs` through
|
|
63
|
+
`xl`, or a positive pixel number) instead of styling its SVG; omitted size is
|
|
64
|
+
1em.
|
|
65
|
+
For an application's own cataloged components, opt into Doctor's
|
|
66
|
+
`"ownership": "component"` and declare each entry's `styleSources` so sibling
|
|
67
|
+
components cannot style one another; package ownership remains the default.
|
|
60
68
|
|
|
61
69
|
Read `../docs/webawesome-theme.md` when using Web Awesome components or changing
|
|
62
70
|
shared theme tokens. When overriding a loud fill
|
|
@@ -190,10 +198,14 @@ doctor feedback covers local components too.
|
|
|
190
198
|
|
|
191
199
|
Toolbar action links and menus are configuration, not application CSS: put
|
|
192
200
|
`ToolbarActionLink` inside `ToolbarControlGroup`, set `overflow="scroll"` for a
|
|
193
|
-
bounded action row
|
|
201
|
+
bounded action row or `overflow="wrap"` to keep independent links fully visible
|
|
202
|
+
at narrow widths, use `menuInset` for nested dropdown surface padding, and set
|
|
194
203
|
`visibility="compact-only"` when a group appears only below the Toolbar's compact
|
|
195
204
|
container breakpoint. Never add app-owned anchor chrome, width/visibility
|
|
196
205
|
repairs, or `::part(menu)` overrides.
|
|
206
|
+
In a stacked Toolbar, use `ToolbarControlGroup placement="end"` for a collapsed
|
|
207
|
+
search at its flex row's trailing edge. Switch that group to `sizing="fill"`
|
|
208
|
+
when expanded; the full-row search enters from above and honors reduced motion.
|
|
197
209
|
|
|
198
210
|
Hard rules:
|
|
199
211
|
|
|
@@ -206,7 +218,7 @@ Hard rules:
|
|
|
206
218
|
7. Decorative icons are hidden; controls are named; focus is visible; state never relies on color alone; reduced motion and increased contrast remain usable. `DisclosureArrow` defaults to an 18px root-scaled visual; size it with its typed `size` prop (`size={remify(24)}`); it never becomes the interaction or accessible-name owner. Put it in an owning native control with a stable accessible name and controlled `aria-expanded`. `ListHeader` toggle mode supplies it when `actionIcon` is omitted, but the app must reveal matching content; a custom icon replaces it. Author replacement icon content facing right before its configured direction transform is applied. Direction changes take the shortest rotation path, with counterclockwise chosen for a 180-degree closed-to-open tie. Kerf `Select` separately keeps its intrinsic Web Awesome expand glyph at `--kui-disclosure-icon-scale: .5`; an icon-only toolbar Select may set `caret={false}` for a round trigger while preserving its help tag and keyboard listbox, and a custom `renderSelected` icon stays centered.
|
|
207
219
|
8. `ResizableRegion` uses `wireResizableRegions()` for Arrow, Shift+Arrow, Home/End, and pointer behavior. The app owns size persistence. `handleIcon` replaces decorative dormant glyph content only. Set `responsiveFillAt="narrow"` or `"compact"` when a responsive composition exposes the pane as its only inline track; the component fills that track and hides its dormant separator. For compact overlays, set `presentation="overlay"` and the overlay maximum variables. A lone child (normally a `Pane`) fills the region and owns scrolling; several children stack at natural height. Do not add descendant width/height overrides.
|
|
208
220
|
9. Use controlled `SegmentedControl` for a small exclusive choice set. Select `appearance="toolbar"` when nesting it inside `ToolbarControlGroup`; use rounded or pill shapes for standalone contexts. The package derives inset hover/selection radii from the owning outer shape through `--kui-layout-highlight-inset`; do not restyle an inner highlight to an independent radius. Handle its action, update `value`, keep meaningful choice labels, and preserve every enabled native button in sequential Tab order.
|
|
209
|
-
10. Compose `AppTab` inside controlled `TabBar`; call `wireTabBars()` once and retain its disposer. It owns same-bar drag mechanics, including proximity-based horizontal edge autoscroll, while the app applies `onReorder` and owns order, selection, panels, close policy, routing, and persistence. For a tab-local action followed by a separately pinned workspace action, set `trailingPlacement="adjacent"`, put the local action in `trailing`, and the workspace action in `end`; TabBar owns the shrinking/scrolling tabs and fixed action geometry, so do not add descendant flex overrides. Arrow/Home/End select on move by default and restore focus by bar/tab identity when controlled activation replaces the strip; pass `activation: 'manual'` (or `TabBar activation="manual"` per strip) so arrow keys move focus only and the user selects with Enter/Space/click — use it when selecting a tab is a heavy action. Use runtime-filtered `rootAttributes` for domain metadata and keep an optional `closeIcon` decorative and noninteractive.
|
|
221
|
+
10. Compose `AppTab` inside controlled `TabBar`; call `wireTabBars()` once and retain its disposer. It owns same-bar drag mechanics, including proximity-based horizontal edge autoscroll, while the app applies `onReorder` and owns order, selection, panels, close policy, routing, and persistence. For a tab-local action followed by a separately pinned workspace action, set `trailingPlacement="adjacent"`, put the local action in `trailing`, and the workspace action in `end`; each action zone accepts one `ToolbarControlGroup` or a standalone `wa-button` when the action needs its own Web Awesome chrome. TabBar owns the shrinking/scrolling tabs and fixed action geometry, so do not add descendant flex overrides. Arrow/Home/End select on move by default and restore focus by bar/tab identity when controlled activation replaces the strip; pass `activation: 'manual'` (or `TabBar activation="manual"` per strip) so arrow keys move focus only and the user selects with Enter/Space/click — use it when selecting a tab is a heavy action. Use runtime-filtered `rootAttributes` for domain metadata and keep an optional `closeIcon` decorative and noninteractive.
|
|
210
222
|
11. Use token-controlled `TokenSearchField` when free text and removable structured filters share one editor. Editable text stays DOM-owned between token changes; to replace the text programmatically (reseeding a persistent dialog's field on reopen, applying a saved search) change the field's `revision` along with `query` for an unmanaged field, or call `model.replace({ query, tokens })` for a model-managed field; never re-key a wrapper. The leading icon, first text line, clear action, and trailing slot share one fixed row when content wraps. For automatic parsing, chips, suggestions, and evaluation, use createTokenSearchModel with grammar rules, pass it as TokenSearchField.model, and register it in wireTokenSearchFields models; a rule's `suggest(input, state)` receives committed tokens so it can exclude selected values. Otherwise the app owns parsing and suggestions; call `readTokenSearchField()` on input, empty `textContent` on clear, use `placeTokenSearchCaret()` after explicit controlled focus changes, and call `wireTokenSearchFields()` once so Enter submits without inserting a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. `clearIcon` replaces the clear action's glyph (its `clearLabel` still names it). Enable `collapsible` for an animated iconic closed state, standalone or inside `ToolbarControlGroup`; the field keeps text or tokens expanded, and `wireTokenSearchFields()` manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse). Bind the field's `expanded` to the returned handle's `expanded(id)` signal or adopt your own via `collapsible.signals`; opt a behavior out only when the app must own it. Do not hand-roll the open handler, the focusout collapse, a clear-button mousedown guard, or reopening after controlled clear or Select All deletion — the helper keeps the replacement editor open and focused. Clear and deletion restoration finish at the replacement mutation checkpoint before the next input task, so shortcut-letter typing cannot escape to the page and delayed animation frames cannot overwrite a later selection. Persist both query and tokens returned by the DOM read. For real-app adoption it also provides a focusout keep-open exception (`data-token-search-keep-open` regions or `collapsible.keepOpenOn(target)`, so a sibling suggestions/date/help surface does not collapse an empty field), opt-in atomic-chip keyboard (`keyboard`: Backspace/Delete remove the adjacent chip via `onRemoveToken`, ArrowRight moves the caret past a trailing chip), and an `onEdit({id, editor, event})` input callback (the `InputEvent` lets you gate on `inputType`/`data`) — reach for these instead of re-adding hand-rolled keydown/input handlers around the field.
|
|
211
223
|
An application helper can call `model.commit(value)` for the active `name:` prefix when it computes a valid value outside suggestions; invalid values and missing prefixes leave the query unchanged.
|
|
212
224
|
12. Demo work uses public production component subpaths and their browser-selected CSS. Focused demos and recipes have no local stylesheet, inline style, or styling-only class; use public components and `CatalogExample.viewport` for unavoidable specimen constraints (a recipe declares its frame through its exported `presentation`). When a recipe needs a capability no component offers, report the component gap instead of adding CSS. Use exactly three demo kinds: a `component` is one component with variants/states, a `composition` is a focused container of components commonly used together, and a `recipe` is the recommended production approach to a task. Organize catalog routes into product-important functional groups; within each group put all components before compositions, order each kind by importance with display-name alphabetical tie-breaking, and put recipes after every component/composition group. Show `Composition` as an entry tag instead of repeating the word in its name. Give every public visual component its own category-grouped catalog route; list themed third-party components under a clearly labeled collapsible ecosystem section, with a focused route for each. For the complete taxonomy, focused/composition decision, `CatalogExample` / `CatalogExampleStack` row/group nesting, exact specimen selection, geometry-overlay legend and exclusions, skip-marker behavior, and metadata ownership, follow the single [Catalog demo authoring contract](../docs/catalog.md#catalog-demo-authoring-contract), discovered machine-readably through [`catalog-authoring.json`](./catalog-authoring.json). Never infer structure from private `kui-catalog-*` classes. Project each component's deterministic repository-relative demo source and existing documentation path so the detail can expose `View demo source` and `Read guidance` links without a runtime export; also derive first-party component implementation paths from their canonical browser imports, and label Web Awesome documentation as Kerf integration guidance. Declare direct `uses` relationships so `Used by` stays derivable, and theme shell chrome through the same semantic tokens as the stage instead of drawing a substitute. Enable `wireCatalog({ revealSelection: true })` for long desktop sidebars so controlled selections remain visible without moving focus; keep its default compact media guard unless compact scrolling is an explicit product behavior, and use `revealCatalogEntry` for an initial deep link.
|
|
@@ -216,7 +228,7 @@ Hard rules:
|
|
|
216
228
|
15a. Use `Text` from `@kerfjs/ui/text` for ordinary headings, paragraphs, and inline secondary text outside toolbar title zones. Omit `variant` for the default `p`, pass `h1`–`h6` according to the document outline, or use `variant="span"` inside a row, label, or table cell; ordinary native attributes pass through. Express supporting, error, compact, or code text with the independent finite `tone="quiet|danger"`, `size="compact"`, and `font="monospace"` props instead of global utility classes or appearance-only heading choices. Block Text variants own a transparent 1px border and the standard item padding; `span` owns no box geometry. Keep toolbar identity/title content on `ToolbarText` and list-aligned bare prose on `ListInsetText`. `ToolbarText` below `xlarge` is intentionally quiet identity text with no tone option; a normal-color subsection title is `Text` with an `h2`–`h6` variant (or `ListHeader` for a list or menu section), never a recolored `ToolbarText`.
|
|
217
229
|
16. Keep a visible collapsible pane's collapse control in its own toolbar. When hidden, move its restore control into the adjacent main toolbar on the corresponding logical edge: leading for an inline-start sidebar and trailing for an inline-end inspector. Collapse the pane completely; do not preserve an empty icon-only rail.
|
|
218
230
|
16a. Dividers between pinned chrome and scrolling content are component chrome: a `Toolbar` draws none by default (`dividerSides` is for a permanent edge only). Call `wireScrollDividers(appRoot)` from `@kerfjs/ui/wire-scroll-dividers` once and retain its disposer; a plain `Pane` with `chromeDividers="scroll"` draws the line under its header only while content is scrolled beneath it and over its footer only while more content lies below (never when the content fits), every `NavStack`'s top chrome and bottom toolbar and every `TabScaffold` bar do the same around the active view or scene (keyed on whatever actually scrolls there, including a sole `Pane`'s content), and every `TabBar` marks each side of its strip with tabs scrolled out of view. Configure a pane with `chromeDividers` (`scroll` by default for plain panes, `always` by default for sunken panes, or explicit `scroll`, `always`, `none`, also via `PaneConfig`); `always` draws both lines even when content fits. A `NavStack` or `TabScaffold` has its own `chromeDividers` (same values: `always` draws the chrome lines without the wiring, `none` never, even when wired; a `SplitView` forwards it through `compactStack`); a `toolbarConfig.dividerSides` edge stays the Toolbar's own. Name app-owned chrome by id in `targets`. The wiring writes only `data-scroll-overflow` / `data-scroll-divider` state; never write a border onto a component to show a divider.
|
|
219
|
-
17. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a group. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button.
|
|
231
|
+
17. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a group. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button. By default a group stays on one row: each icon sits beside its label, and the toolbar's `responsive` policy relocates whole groups when needed. Set `overflow="wrap"` only for independent links that should move as whole actions to another row within the group; use `overflow="scroll"` for a bounded one-row action strip. A `ToolbarText` is one line ending in an ellipsis by default; opt into `wrap` (capped by `maxLines`) or `ellipsis={false}` deliberately. Inside a group, a `Select` or `PopupMenu` trigger drops its focus ring while its popup is open (the open popup and its current row show where focus is) and the ring returns when the popup closes; a ring follows the control's shape (a pill for an icon-and-caret trigger, a circle for a compact icon button) — set `focusRingOwner="group"` on a Select whose group paints the composed ring, and never add CSS to show, hide, or reshape a ring. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A non-interactive heading icon is an icon tile: put the `LucideIcon` directly in its `ToolbarControlGroup` (it takes an icon button's 40px slot and centers), never inside a `<button>` that does nothing. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Each zone item sits in the toolbar's top 44px control band, centered by its own size; a taller item (a wrapped title, a second trailing row) starts at the band's top and grows down without moving the others, so a wrapped `ToolbarText` keeps trailing controls beside its first line — do not add alignment CSS for this. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `width="content"` to shrink-wrap while retaining its normal geometry, or `inline` when it belongs in surrounding text without outer margin, border, or padding. It keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square whose transparent hit layer keeps the pointer target at least 44px (44px wide by the 36px row in `density="compact"`); never shrink the target to match the visual, and never force the button to 44px. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets. Dividers are chrome and earn their place: a `Toolbar` draws none by default, and the line between pinned chrome and scrolling content comes from scroll state (rule 16a), so never add a border, a permanent `dividerSides`, or a margin to fake or adjust one.
|
|
220
232
|
For a toolbar heading that must remain at the 20px `--kui-font-l` step, use `ToolbarText size="xlarge-fixed"`; `xlarge` retains the responsive page-title clamp. Pass `headingLevel` when it is a section landmark.
|
|
221
233
|
|
|
222
234
|
18. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, toolbar title/supporting-copy ids, shared 8px field/action gutters, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card or double-inset intrinsically bordered controls. For multi-line banner copy use `copyLayout="stacked"`; use `actionPlacement="below"` for an action on its own trailing row. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
|
|
@@ -225,6 +237,9 @@ For compact `Select` triggers, set `triggerWidth` to `fit-content`, `max-content
|
|
|
225
237
|
or `fill` and use `--kui-select-trigger-max-width` for a cap. Style custom
|
|
226
238
|
selected content with the public selected color, font-size, and font-weight
|
|
227
239
|
tokens; plain text ellipsizes in a narrow trigger.
|
|
240
|
+
For a rail view heading, use `presentation="title"` for the 36px bold,
|
|
241
|
+
flush-start trigger and adjacent caret. Set `focusRingInset` when an ancestor
|
|
242
|
+
clips focus painting; never restyle Select shadow parts from application CSS.
|
|
228
243
|
|
|
229
244
|
19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset. 20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling. 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap`, `Grid.gap`, or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale". 22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. The stack's top chrome is a real `Toolbar`: put a view's groups on `NavStackView.leading` / `center` / `toolbar` (trailing), and configure the bar through `toolbarConfig` (`ToolbarConfig` plus `label`, `titleSize`, `headingLevel`) and `backIcon` / `backText`, never with CSS. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (`leading`, `center`, `toolbar`, `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A list-detail dialog is a Workbench-shaped layout on roomy devices: a full-height list sidebar and a full-height main column, each a `Pane` with its own top `Toolbar`; the main column holds the primary xl title, the close control (`data-dialog="close"` in a `without-header` Web Awesome dialog), and the record actions, never a header spanning both columns. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
|
|
230
245
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Web Awesome JSX signatures for the UI authoring corpus
|
|
2
2
|
|
|
3
|
-
Generated from the emitted `@kerfjs/ui@5.0.0-beta.
|
|
3
|
+
Generated from the emitted `@kerfjs/ui@5.0.0-beta.69` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import { KerfCustomElement } from 'kerfjs/jsx-runtime';
|
package/analyzer/index.d.mts
CHANGED
|
@@ -43,6 +43,7 @@ export function analyzeUiProject(options?: {
|
|
|
43
43
|
profile?: string;
|
|
44
44
|
knownRules?: Iterable<string>;
|
|
45
45
|
adoption?: boolean;
|
|
46
|
+
ownership?: 'package' | 'component';
|
|
46
47
|
}): Promise<UiAnalysisReport>;
|
|
47
48
|
|
|
48
49
|
export function formatUiAnalysisText(report: UiAnalysisReport): string;
|