@kerfjs/ui 5.0.0-beta.70 → 5.0.0-beta.72
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/ai/component-catalog.json +21 -5
- package/ai/component-catalog.schema.json +2 -0
- package/ai/component-composition-rules.json +4 -4
- package/ai/component-composition.d.ts +2 -0
- package/ai/component-composition.json +23 -5
- package/ai/component-composition.schema.json +9 -0
- package/ai/components/toolbar-text.md +3 -1
- package/ai/components/toolbar.md +6 -6
- package/ai/public-api-signatures-v1.md +16 -11
- package/ai/skill.md +1 -1
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/component-ownership.mjs +131 -16
- package/analyzer/index.d.mts +2 -1
- package/analyzer/index.mjs +298 -29
- package/dist/catalog.js +2 -2
- package/dist/{chunk-KCCEH7RF.js → chunk-3UE24JSC.js} +2 -2
- package/dist/{chunk-KCCEH7RF.js.map → chunk-3UE24JSC.js.map} +1 -1
- package/dist/{chunk-V3DX3K65.js → chunk-R5F5XUJ7.js} +19 -2
- package/dist/chunk-R5F5XUJ7.js.map +1 -0
- package/dist/{chunk-T2E5WBCZ.js → chunk-SLWCCHNP.js} +11 -2
- package/dist/chunk-SLWCCHNP.js.map +1 -0
- package/dist/{chunk-DC6CN2DS.js → chunk-VLYIPBCQ.js} +2 -2
- package/dist/{chunk-DC6CN2DS.js.map → chunk-VLYIPBCQ.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/nav-stack.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/styles/app-tab.css +3 -0
- package/dist/styles/chip.css +6 -0
- package/dist/styles/toolbar-text.css +20 -0
- package/dist/styles/toolbar.css +10 -1
- package/dist/toolbar-text.d.ts +15 -10
- package/dist/toolbar-text.js +1 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +5 -0
- package/docs/component-contract.md +17 -2
- package/docs/component-selection.md +27 -16
- package/docs/layout.md +4 -0
- package/docs/ui-analyzer.md +42 -5
- package/docs/ui-doctor.md +28 -3
- package/doctor/config.schema.json +23 -1
- package/doctor/index.d.mts +2 -1
- package/doctor/index.mjs +42 -2
- package/package.json +1 -1
- package/dist/chunk-T2E5WBCZ.js.map +0 -1
- package/dist/chunk-V3DX3K65.js.map +0 -1
|
@@ -187,6 +187,18 @@
|
|
|
187
187
|
},
|
|
188
188
|
"wiring": [],
|
|
189
189
|
"publicClasses": [],
|
|
190
|
+
"rootElement": "svg",
|
|
191
|
+
"descendantRootAttribute": "data-lucide",
|
|
192
|
+
"descendantElements": [
|
|
193
|
+
"circle",
|
|
194
|
+
"ellipse",
|
|
195
|
+
"g",
|
|
196
|
+
"line",
|
|
197
|
+
"path",
|
|
198
|
+
"polygon",
|
|
199
|
+
"polyline",
|
|
200
|
+
"rect"
|
|
201
|
+
],
|
|
190
202
|
"publicTokens": [],
|
|
191
203
|
"links": {
|
|
192
204
|
"catalogRoute": "?component=lucide-icon",
|
|
@@ -1492,7 +1504,8 @@
|
|
|
1492
1504
|
"description": "Leading, centered, and trailing toolbar composition.",
|
|
1493
1505
|
"uses": [
|
|
1494
1506
|
"toolbar-text",
|
|
1495
|
-
"toolbar-control-group"
|
|
1507
|
+
"toolbar-control-group",
|
|
1508
|
+
"wa-button"
|
|
1496
1509
|
],
|
|
1497
1510
|
"purpose": "Leading, centered, and trailing toolbar composition.",
|
|
1498
1511
|
"publicExports": [
|
|
@@ -1520,7 +1533,7 @@
|
|
|
1520
1533
|
"content-driven wrap that keeps a heading title whole",
|
|
1521
1534
|
"center priority for an expanded center control",
|
|
1522
1535
|
"trailing priority for an expanded trailing control at compact or narrow widths",
|
|
1523
|
-
"trailing groups wrap instead of clipping",
|
|
1536
|
+
"trailing groups and a standalone primary wa-button wrap instead of clipping",
|
|
1524
1537
|
"outer inset independently configurable from zone gap",
|
|
1525
1538
|
"app bar or bottom bar claiming its screen edges (safeAreaEdges)"
|
|
1526
1539
|
],
|
|
@@ -1886,7 +1899,8 @@
|
|
|
1886
1899
|
"ToolbarText"
|
|
1887
1900
|
],
|
|
1888
1901
|
"useWhen": [
|
|
1889
|
-
"Render toolbar identity or heading text at a supported hierarchy size; pass headingLevel for heading semantics."
|
|
1902
|
+
"Render toolbar identity or heading text at a supported hierarchy size; pass headingLevel for heading semantics.",
|
|
1903
|
+
"Use action for an editable toolbar title: it renders a native button that shrinks and ellipsizes beside sibling status chips."
|
|
1890
1904
|
],
|
|
1891
1905
|
"avoidWhen": [
|
|
1892
1906
|
"Do not omit headingLevel when the text is the page or section heading.",
|
|
@@ -1906,7 +1920,8 @@
|
|
|
1906
1920
|
],
|
|
1907
1921
|
"appOwns": [
|
|
1908
1922
|
"text",
|
|
1909
|
-
"responsive priority"
|
|
1923
|
+
"responsive priority",
|
|
1924
|
+
"the delegated action handler and display-to-input editing swap"
|
|
1910
1925
|
],
|
|
1911
1926
|
"variants": [
|
|
1912
1927
|
"xlarge",
|
|
@@ -1918,7 +1933,8 @@
|
|
|
1918
1933
|
"placeholder (loading)"
|
|
1919
1934
|
],
|
|
1920
1935
|
"accessibility": [
|
|
1921
|
-
"Plain text is not a heading; pass headingLevel to expose role=heading + aria-level for a page/section title."
|
|
1936
|
+
"Plain text is not a heading; pass headingLevel to expose role=heading + aria-level for a page/section title.",
|
|
1937
|
+
"Actionable text is a native button with its full text as its accessible name; action cannot be combined with headingLevel, placeholder, or wrap."
|
|
1922
1938
|
],
|
|
1923
1939
|
"geometry": {
|
|
1924
1940
|
"margin": "none",
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"publicClasses": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
|
62
62
|
"placeableClasses": { "description": "Public classes an application may place on its own elements (layout utilities, a document root, or item geometry on a non-div carrier). Every other public class is component anatomy: render the component instead.", "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
|
63
63
|
"rootElement": { "description": "The intrinsic element the component renders at its root. Required when a component renders placeableClasses; also enables classless child-root ownership checks.", "type": "string", "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$" },
|
|
64
|
+
"descendantRootAttribute": { "description": "A root data attribute that anchors ownership checks for arbitrary unclassed descendant tags; requires rootElement.", "type": "string", "pattern": "^data-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$" },
|
|
65
|
+
"descendantElements": { "description": "Intrinsic element tags the component can render below its root, for composed-child selector ownership checks.", "type": "array", "items": { "type": "string", "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$" }, "uniqueItems": true },
|
|
64
66
|
"publicTokens": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
|
65
67
|
"cssValueProps": { "type": "array", "items": { "$ref": "#/$defs/cssValueProp" }, "uniqueItems": true },
|
|
66
68
|
"geometry": { "$ref": "#/$defs/geometry" },
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
{
|
|
26
26
|
"id": "trailing",
|
|
27
27
|
"jsx": { "prop": "trailing" },
|
|
28
|
-
"accepts": ["toolbar-text", "toolbar-control-group"],
|
|
28
|
+
"accepts": ["toolbar-text", "toolbar-control-group", "wa-button"],
|
|
29
29
|
"cardinality": { "min": 0, "max": "unbounded" },
|
|
30
30
|
"exclusiveWith": []
|
|
31
31
|
}
|
|
32
32
|
],
|
|
33
33
|
"children": {
|
|
34
34
|
"mode": "listed",
|
|
35
|
-
"concepts": ["toolbar-text", "toolbar-control-group"],
|
|
35
|
+
"concepts": ["toolbar-text", "toolbar-control-group", "wa-button"],
|
|
36
36
|
"requiredConcepts": []
|
|
37
37
|
},
|
|
38
38
|
"diagnostics": [
|
|
39
39
|
{
|
|
40
40
|
"id": "KUI-C101",
|
|
41
41
|
"severity": "error",
|
|
42
|
-
"when": "a direct zone child is neither ToolbarText nor ToolbarControlGroup",
|
|
43
|
-
"message": "Wrap toolbar controls in ToolbarControlGroup; only ToolbarText and ToolbarControlGroup are direct zone children."
|
|
42
|
+
"when": "a direct zone child is neither ToolbarText nor ToolbarControlGroup nor a standalone wa-button in trailing",
|
|
43
|
+
"message": "Wrap toolbar controls in ToolbarControlGroup; only ToolbarText and ToolbarControlGroup, plus one standalone primary wa-button in trailing, are direct zone children."
|
|
44
44
|
}
|
|
45
45
|
]
|
|
46
46
|
},
|
|
@@ -111,6 +111,8 @@ export interface CatalogCompositionEntry {
|
|
|
111
111
|
placeableClasses?: string[];
|
|
112
112
|
/** The intrinsic element the component renders at its root; a placeable class on a plain element of this tag recreates the component. */
|
|
113
113
|
rootElement?: string;
|
|
114
|
+
descendantElements?: string[];
|
|
115
|
+
descendantRootAttribute?: string;
|
|
114
116
|
publicTokens: string[];
|
|
115
117
|
publicParts?: string[];
|
|
116
118
|
};
|
|
@@ -148,6 +148,18 @@
|
|
|
148
148
|
"boundaries": {
|
|
149
149
|
"rootClass": null,
|
|
150
150
|
"publicClasses": [],
|
|
151
|
+
"rootElement": "svg",
|
|
152
|
+
"descendantElements": [
|
|
153
|
+
"circle",
|
|
154
|
+
"ellipse",
|
|
155
|
+
"g",
|
|
156
|
+
"line",
|
|
157
|
+
"path",
|
|
158
|
+
"polygon",
|
|
159
|
+
"polyline",
|
|
160
|
+
"rect"
|
|
161
|
+
],
|
|
162
|
+
"descendantRootAttribute": "data-lucide",
|
|
151
163
|
"publicTokens": []
|
|
152
164
|
},
|
|
153
165
|
"diagnostics": [],
|
|
@@ -1412,14 +1424,14 @@
|
|
|
1412
1424
|
{
|
|
1413
1425
|
"id": "trailing",
|
|
1414
1426
|
"jsx": { "prop": "trailing" },
|
|
1415
|
-
"accepts": ["toolbar-text", "toolbar-control-group"],
|
|
1427
|
+
"accepts": ["toolbar-text", "toolbar-control-group", "wa-button"],
|
|
1416
1428
|
"cardinality": { "min": 0, "max": "unbounded" },
|
|
1417
1429
|
"exclusiveWith": []
|
|
1418
1430
|
}
|
|
1419
1431
|
],
|
|
1420
1432
|
"children": {
|
|
1421
1433
|
"mode": "listed",
|
|
1422
|
-
"concepts": ["toolbar-text", "toolbar-control-group"],
|
|
1434
|
+
"concepts": ["toolbar-text", "toolbar-control-group", "wa-button"],
|
|
1423
1435
|
"requiredConcepts": []
|
|
1424
1436
|
},
|
|
1425
1437
|
"state": [
|
|
@@ -1505,8 +1517,8 @@
|
|
|
1505
1517
|
{
|
|
1506
1518
|
"id": "KUI-C101",
|
|
1507
1519
|
"severity": "error",
|
|
1508
|
-
"when": "a direct zone child is neither ToolbarText nor ToolbarControlGroup",
|
|
1509
|
-
"message": "Wrap toolbar controls in ToolbarControlGroup; only ToolbarText and ToolbarControlGroup are direct zone children."
|
|
1520
|
+
"when": "a direct zone child is neither ToolbarText nor ToolbarControlGroup nor a standalone wa-button in trailing",
|
|
1521
|
+
"message": "Wrap toolbar controls in ToolbarControlGroup; only ToolbarText and ToolbarControlGroup, plus one standalone primary wa-button in trailing, are direct zone children."
|
|
1510
1522
|
}
|
|
1511
1523
|
],
|
|
1512
1524
|
"provenance": {
|
|
@@ -1748,6 +1760,11 @@
|
|
|
1748
1760
|
"id": "responsive priority",
|
|
1749
1761
|
"owner": "application",
|
|
1750
1762
|
"required": false
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
"id": "the delegated action handler and display-to-input editing swap",
|
|
1766
|
+
"owner": "application",
|
|
1767
|
+
"required": false
|
|
1751
1768
|
}
|
|
1752
1769
|
],
|
|
1753
1770
|
"wiring": {
|
|
@@ -1763,7 +1780,8 @@
|
|
|
1763
1780
|
},
|
|
1764
1781
|
"accessibility": {
|
|
1765
1782
|
"obligations": [
|
|
1766
|
-
"Plain text is not a heading; pass headingLevel to expose role=heading + aria-level for a page/section title."
|
|
1783
|
+
"Plain text is not a heading; pass headingLevel to expose role=heading + aria-level for a page/section title.",
|
|
1784
|
+
"Actionable text is a native button with its full text as its accessible name; action cannot be combined with headingLevel, placeholder, or wrap."
|
|
1767
1785
|
]
|
|
1768
1786
|
},
|
|
1769
1787
|
"cssValueProps": [],
|
|
@@ -335,6 +335,15 @@
|
|
|
335
335
|
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
336
336
|
"description": "The intrinsic element the component renders at its root. It identifies composed child roots for ownership checks; a placeableClasses class on a plain element of this tag recreates the component, while the class stays placeable on another carrier."
|
|
337
337
|
},
|
|
338
|
+
"descendantElements": {
|
|
339
|
+
"$ref": "#/$defs/stringList",
|
|
340
|
+
"description": "Intrinsic element tags that can appear below the component root."
|
|
341
|
+
},
|
|
342
|
+
"descendantRootAttribute": {
|
|
343
|
+
"type": "string",
|
|
344
|
+
"pattern": "^data-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
345
|
+
"description": "Root data attribute anchoring ownership of arbitrary unclassed descendants."
|
|
346
|
+
},
|
|
338
347
|
"publicTokens": { "$ref": "#/$defs/stringList" },
|
|
339
348
|
"publicParts": { "$ref": "#/$defs/stringList" }
|
|
340
349
|
},
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
Extra-large through extra-small toolbar identity text.
|
|
10
10
|
|
|
11
11
|
- Render toolbar identity or heading text at a supported hierarchy size; pass headingLevel for heading semantics.
|
|
12
|
+
- Use action for an editable toolbar title: it renders a native button that shrinks and ellipsizes beside sibling status chips.
|
|
12
13
|
|
|
13
14
|
**Not when:**
|
|
14
15
|
|
|
@@ -48,7 +49,7 @@ Exact prop names and types: [`@kerfjs/ui/toolbar-text`](../public-api-signatures
|
|
|
48
49
|
|
|
49
50
|
## State and wiring
|
|
50
51
|
|
|
51
|
-
**The app owns:** text; responsive priority.
|
|
52
|
+
**The app owns:** text; responsive priority; the delegated action handler and display-to-input editing swap.
|
|
52
53
|
|
|
53
54
|
No wiring helper.
|
|
54
55
|
|
|
@@ -59,6 +60,7 @@ Margin: none · border: none · padding: none (layout role: structure). `self` m
|
|
|
59
60
|
## Accessibility
|
|
60
61
|
|
|
61
62
|
- Plain text is not a heading; pass headingLevel to expose role=heading + aria-level for a page/section title.
|
|
63
|
+
- Actionable text is a native button with its full text as its accessible name; action cannot be combined with headingLevel, placeholder, or wrap.
|
|
62
64
|
|
|
63
65
|
## Styling boundary
|
|
64
66
|
|
package/ai/components/toolbar.md
CHANGED
|
@@ -35,7 +35,7 @@ Exact prop names and types: [`@kerfjs/ui/toolbar`](../public-api-signatures-v1.m
|
|
|
35
35
|
- content-driven wrap that keeps a heading title whole
|
|
36
36
|
- center priority for an expanded center control
|
|
37
37
|
- trailing priority for an expanded trailing control at compact or narrow widths
|
|
38
|
-
- trailing groups wrap instead of clipping
|
|
38
|
+
- trailing groups and a standalone primary wa-button wrap instead of clipping
|
|
39
39
|
- outer inset independently configurable from zone gap
|
|
40
40
|
- app bar or bottom bar claiming its screen edges (safeAreaEdges)
|
|
41
41
|
|
|
@@ -43,14 +43,14 @@ Exact prop names and types: [`@kerfjs/ui/toolbar`](../public-api-signatures-v1.m
|
|
|
43
43
|
|
|
44
44
|
- Parents: any.
|
|
45
45
|
- Contexts: `application-chrome`, `page-header`, `panel-header`, `dialog-header`.
|
|
46
|
-
- Children: only [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md).
|
|
47
|
-
- Built from: [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md).
|
|
46
|
+
- Children: only [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md).
|
|
47
|
+
- Built from: [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md).
|
|
48
48
|
|
|
49
49
|
Zones (a zone is bound to JSX only through its listed prop; never assume the zone id is a prop):
|
|
50
50
|
|
|
51
51
|
- `leading` — JSX prop `leading`; accepts [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md); any number.
|
|
52
52
|
- `center` — JSX prop `center`; accepts [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md); any number.
|
|
53
|
-
- `trailing` — JSX prop `trailing`; accepts [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md); any number.
|
|
53
|
+
- `trailing` — JSX prop `trailing`; accepts [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md); any number.
|
|
54
54
|
|
|
55
55
|
## State and wiring
|
|
56
56
|
|
|
@@ -88,10 +88,10 @@ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-
|
|
|
88
88
|
|
|
89
89
|
## Diagnostics
|
|
90
90
|
|
|
91
|
-
- `KUI-C101` (error) when a direct zone child is neither ToolbarText nor ToolbarControlGroup: Wrap toolbar controls in ToolbarControlGroup; only ToolbarText and ToolbarControlGroup are direct zone children.
|
|
91
|
+
- `KUI-C101` (error) when a direct zone child is neither ToolbarText nor ToolbarControlGroup nor a standalone wa-button in trailing: Wrap toolbar controls in ToolbarControlGroup; only ToolbarText and ToolbarControlGroup, plus one standalone primary wa-button in trailing, are direct zone children.
|
|
92
92
|
|
|
93
93
|
## Related
|
|
94
94
|
|
|
95
|
-
- Components: [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md).
|
|
95
|
+
- Components: [ToolbarText](./toolbar-text.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md).
|
|
96
96
|
- Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/component-selection.md#ambiguous-choices`](../../docs/component-selection.md#ambiguous-choices).
|
|
97
97
|
- UX catalog route: `?component=toolbar`.
|
|
@@ -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.72` and `kerfjs@5.0.0-beta.72`. 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
|
|
|
@@ -222,14 +222,6 @@ interface ToolbarTextBaseProps {
|
|
|
222
222
|
className?: string;
|
|
223
223
|
/** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */
|
|
224
224
|
id?: string;
|
|
225
|
-
/**
|
|
226
|
-
* Expose heading semantics (`role="heading"` + `aria-level`) so the text acts as
|
|
227
|
-
* a heading landmark — e.g. a page's primary title. Omit to keep the plain span
|
|
228
|
-
* (the default), which suits a dialog title referenced via `aria-labelledby`.
|
|
229
|
-
*/
|
|
230
|
-
headingLevel?: HeadingLevel;
|
|
231
|
-
/** Render the text as an unanimated loading skeleton instead of its value. */
|
|
232
|
-
placeholder?: boolean;
|
|
233
225
|
/**
|
|
234
226
|
* Show a trailing ellipsis (…) where the text is truncated — on the single line
|
|
235
227
|
* (default), or at the `maxLines` boundary when wrapping. Set false to hard-clip
|
|
@@ -253,8 +245,21 @@ type ToolbarTextWrappingProps = {
|
|
|
253
245
|
wrap?: false;
|
|
254
246
|
maxLines?: never;
|
|
255
247
|
};
|
|
256
|
-
type ToolbarTextProps = ToolbarTextBaseProps &
|
|
257
|
-
|
|
248
|
+
type ToolbarTextProps = ToolbarTextBaseProps & (({
|
|
249
|
+
action?: never;
|
|
250
|
+
/** Expose `role="heading"` and `aria-level` on read-only text. */
|
|
251
|
+
headingLevel?: HeadingLevel;
|
|
252
|
+
/** Render an unanimated loading skeleton instead of the text. */
|
|
253
|
+
placeholder?: boolean;
|
|
254
|
+
} & ToolbarTextWrappingProps) | {
|
|
255
|
+
/** Render a native button with this delegated action; the app owns editing. */
|
|
256
|
+
action: string;
|
|
257
|
+
headingLevel?: never;
|
|
258
|
+
placeholder?: never;
|
|
259
|
+
wrap?: never;
|
|
260
|
+
maxLines?: never;
|
|
261
|
+
});
|
|
262
|
+
declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, fill, slot, action, }: ToolbarTextProps): kerfjs.SafeHtml;
|
|
258
263
|
|
|
259
264
|
export { type HeadingLevel, ToolbarText, type ToolbarTextProps, type ToolbarTextSize };
|
|
260
265
|
```
|
package/ai/skill.md
CHANGED
|
@@ -228,7 +228,7 @@ Hard rules:
|
|
|
228
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`.
|
|
229
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.
|
|
230
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.
|
|
231
|
-
17. The
|
|
231
|
+
17. The direct children of a `Toolbar` zone are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; `trailing` also accepts one standalone primary `wa-button` that keeps its own Web Awesome chrome. Never drop other bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and other Web Awesome controls live inside a group. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button. By default a group stays on one row: each icon sits beside its label, and the toolbar's `responsive` policy relocates whole groups when needed. Set `overflow="wrap"` only for independent links that should move as whole actions to another row within the group; use `overflow="scroll"` for a bounded one-row action strip. A `ToolbarText` is one line ending in an ellipsis by default; opt into `wrap` (capped by `maxLines`) or `ellipsis={false}` deliberately. Inside a group, a `Select` or `PopupMenu` trigger drops its focus ring while its popup is open (the open popup and its current row show where focus is) and the ring returns when the popup closes; a ring follows the control's shape (a pill for an icon-and-caret trigger, a circle for a compact icon button) — set `focusRingOwner="group"` on a Select whose group paints the composed ring, and never add CSS to show, hide, or reshape a ring. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A non-interactive heading icon is an icon tile: put the `LucideIcon` directly in its `ToolbarControlGroup` (it takes an icon button's 40px slot and centers), never inside a `<button>` that does nothing. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Each zone item sits in the toolbar's top 44px control band, centered by its own size; a taller item (a wrapped title, a second trailing row) starts at the band's top and grows down without moving the others, so a wrapped `ToolbarText` keeps trailing controls beside its first line — do not add alignment CSS for this. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `width="content"` to shrink-wrap while retaining its normal geometry, or `inline` when it belongs in surrounding text without outer margin, border, or padding. It keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square whose transparent hit layer keeps the pointer target at least 44px (44px wide by the 36px row in `density="compact"`); never shrink the target to match the visual, and never force the button to 44px. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets. Dividers are chrome and earn their place: a `Toolbar` draws none by default, and the line between pinned chrome and scrolling content comes from scroll state (rule 16a), so never add a border, a permanent `dividerSides`, or a margin to fake or adjust one.
|
|
232
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.
|
|
233
233
|
|
|
234
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.
|
|
@@ -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.72` 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';
|
|
@@ -21,9 +21,26 @@ import {
|
|
|
21
21
|
subjectTypes,
|
|
22
22
|
withoutRelationalArguments,
|
|
23
23
|
} from './selectors.mjs';
|
|
24
|
+
import { resolve } from 'node:path';
|
|
24
25
|
|
|
25
26
|
const kebab = (value) =>
|
|
26
27
|
value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
28
|
+
const bemBlock = (name) => name.split(/__|--/, 1)[0];
|
|
29
|
+
|
|
30
|
+
export function classOwnerFor(facts, className, componentMode = false) {
|
|
31
|
+
if (facts.classOwners.has(className)) return facts.classOwners.get(className);
|
|
32
|
+
if (!componentMode || bemBlock(className) === className) return undefined;
|
|
33
|
+
return facts.blockOwners.get(bemBlock(className));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function ownsClassBlock(facts, className, componentMode = false) {
|
|
37
|
+
return (
|
|
38
|
+
facts.classOwners.has(className) ||
|
|
39
|
+
(componentMode &&
|
|
40
|
+
bemBlock(className) !== className &&
|
|
41
|
+
facts.blockOwners.has(bemBlock(className)))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
27
44
|
|
|
28
45
|
function isWebAwesomeTagEntry(entry) {
|
|
29
46
|
return entry.source === 'webawesome' && /^wa-[a-z0-9-]+$/.test(entry.id);
|
|
@@ -37,17 +54,51 @@ function isWebAwesomeTagEntry(entry) {
|
|
|
37
54
|
*/
|
|
38
55
|
export function componentOwnershipFacts(entries) {
|
|
39
56
|
const classOwners = new Map();
|
|
57
|
+
const blockOwners = new Map();
|
|
40
58
|
const dataComponentOwners = new Map();
|
|
41
59
|
const tagOwners = new Map();
|
|
60
|
+
const descendantRootAttributeOwners = [];
|
|
42
61
|
const privatePrefixes = [];
|
|
43
62
|
const typedTokens = new Map();
|
|
44
63
|
const setOnce = (map, key, entry) => {
|
|
45
64
|
if (!map.has(key)) map.set(key, entry);
|
|
46
65
|
};
|
|
66
|
+
const sharesStylesheet = (left, right) => {
|
|
67
|
+
if (
|
|
68
|
+
left.package !== right.package ||
|
|
69
|
+
!left.catalogDirectory ||
|
|
70
|
+
!right.catalogDirectory
|
|
71
|
+
)
|
|
72
|
+
return false;
|
|
73
|
+
const styles = new Set(
|
|
74
|
+
(left.styleSources ?? []).map((path) =>
|
|
75
|
+
resolve(left.catalogDirectory, path),
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
return (right.styleSources ?? []).some((path) =>
|
|
79
|
+
styles.has(resolve(right.catalogDirectory, path)),
|
|
80
|
+
);
|
|
81
|
+
};
|
|
47
82
|
for (const entry of entries) {
|
|
48
83
|
const boundaries = entry.boundaries ?? {};
|
|
49
|
-
|
|
50
|
-
|
|
84
|
+
if (boundaries.rootElement && boundaries.descendantRootAttribute)
|
|
85
|
+
descendantRootAttributeOwners.push(entry);
|
|
86
|
+
for (const className of new Set(
|
|
87
|
+
[boundaries.rootClass, ...(boundaries.publicClasses ?? [])].filter(
|
|
88
|
+
Boolean,
|
|
89
|
+
),
|
|
90
|
+
)) {
|
|
91
|
+
const previous = classOwners.get(className);
|
|
92
|
+
if (!classOwners.has(className)) classOwners.set(className, entry);
|
|
93
|
+
else if (previous && sharesStylesheet(previous, entry))
|
|
94
|
+
// The class is co-owned. A selector cannot attribute it to one
|
|
95
|
+
// component without depending on catalog order.
|
|
96
|
+
classOwners.set(className, null);
|
|
97
|
+
const block = bemBlock(className);
|
|
98
|
+
const previousBlock = blockOwners.get(block);
|
|
99
|
+
if (!blockOwners.has(block)) blockOwners.set(block, entry);
|
|
100
|
+
else if (previousBlock && sharesStylesheet(previousBlock, entry))
|
|
101
|
+
blockOwners.set(block, null);
|
|
51
102
|
if (
|
|
52
103
|
className.startsWith('kui-') &&
|
|
53
104
|
!className.includes('__') &&
|
|
@@ -81,8 +132,10 @@ export function componentOwnershipFacts(entries) {
|
|
|
81
132
|
privatePrefixes.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
82
133
|
return {
|
|
83
134
|
classOwners,
|
|
135
|
+
blockOwners,
|
|
84
136
|
dataComponentOwners,
|
|
85
137
|
tagOwners,
|
|
138
|
+
descendantRootAttributeOwners,
|
|
86
139
|
privatePrefixes,
|
|
87
140
|
typedTokens,
|
|
88
141
|
};
|
|
@@ -106,6 +159,7 @@ export function restyledComponents(
|
|
|
106
159
|
ownPackage,
|
|
107
160
|
hooks = new Map(),
|
|
108
161
|
composedChildren = new Map(),
|
|
162
|
+
composedDescendants = new Map(),
|
|
109
163
|
} = {},
|
|
110
164
|
) {
|
|
111
165
|
const found = [];
|
|
@@ -123,7 +177,11 @@ export function restyledComponents(
|
|
|
123
177
|
const bare = withoutRelationalArguments(subject);
|
|
124
178
|
const subjectClasses = classNames(bare);
|
|
125
179
|
for (const className of classNames(bare))
|
|
126
|
-
add(
|
|
180
|
+
add(
|
|
181
|
+
classOwnerFor(facts, className, componentMode),
|
|
182
|
+
'class',
|
|
183
|
+
`.${className}`,
|
|
184
|
+
);
|
|
127
185
|
for (const value of dataComponents(bare))
|
|
128
186
|
add(
|
|
129
187
|
facts.dataComponentOwners.get(value),
|
|
@@ -132,13 +190,38 @@ export function restyledComponents(
|
|
|
132
190
|
);
|
|
133
191
|
// An application class on the subject scopes a Web Awesome tag to the
|
|
134
192
|
// application's own element; a component package never places one there.
|
|
135
|
-
if (
|
|
193
|
+
if (
|
|
194
|
+
subjectClasses.some((name) => !ownsClassBlock(facts, name, componentMode))
|
|
195
|
+
)
|
|
196
|
+
continue;
|
|
136
197
|
for (const type of subjectTypes(subject) ?? [])
|
|
137
198
|
add(facts.tagOwners.get(type), 'tag', type);
|
|
138
199
|
// A raw descendant of a component's cataloged anatomy is still inside
|
|
139
200
|
// that component. Do not infer ownership through its root, where an app
|
|
140
201
|
// may place its own children, or through an app-named intermediate node.
|
|
141
202
|
if (subjectClasses.length || parts.length < 2) continue;
|
|
203
|
+
if (componentMode)
|
|
204
|
+
for (const ancestor of parts.slice(0, -1))
|
|
205
|
+
for (const entry of facts.descendantRootAttributeOwners) {
|
|
206
|
+
const { rootElement, descendantRootAttribute } = entry.boundaries;
|
|
207
|
+
if (
|
|
208
|
+
(subjectTypes(subject) ?? []).some((type) =>
|
|
209
|
+
entry.boundaries.descendantElements?.includes(type),
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
continue;
|
|
213
|
+
if (
|
|
214
|
+
subjectTypes(ancestor.compound)?.includes(rootElement) &&
|
|
215
|
+
new RegExp(
|
|
216
|
+
`\\[\\s*${descendantRootAttribute}(?=\\s|\\]|[~|^$*]?=)`,
|
|
217
|
+
).test(withoutRelationalArguments(ancestor.compound))
|
|
218
|
+
)
|
|
219
|
+
add(
|
|
220
|
+
entry,
|
|
221
|
+
'descendant',
|
|
222
|
+
`${rootElement}[${descendantRootAttribute}]`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
142
225
|
if (componentMode && parts.at(-1).combinator === '>') {
|
|
143
226
|
const types = subjectTypes(subject) ?? [];
|
|
144
227
|
for (const name of classNames(
|
|
@@ -155,6 +238,14 @@ export function restyledComponents(
|
|
|
155
238
|
const classes = classNames(
|
|
156
239
|
withoutRelationalArguments(parts[index].compound),
|
|
157
240
|
);
|
|
241
|
+
if (componentMode && parts.at(-1).combinator !== '>')
|
|
242
|
+
for (const name of classes)
|
|
243
|
+
for (const type of subjectTypes(subject) ?? [])
|
|
244
|
+
add(
|
|
245
|
+
composedDescendants.get(`${name}|${type}`),
|
|
246
|
+
'descendant',
|
|
247
|
+
`.${name}`,
|
|
248
|
+
);
|
|
158
249
|
const hook = componentMode
|
|
159
250
|
? classes.find((name) => hooks.has(name))
|
|
160
251
|
: undefined;
|
|
@@ -162,19 +253,27 @@ export function restyledComponents(
|
|
|
162
253
|
add(hooks.get(hook), 'descendant', `.${hook}`);
|
|
163
254
|
break;
|
|
164
255
|
}
|
|
165
|
-
if (classes.some((name) => !facts
|
|
256
|
+
if (classes.some((name) => !ownsClassBlock(facts, name, componentMode)))
|
|
257
|
+
break;
|
|
166
258
|
const anatomy = classes.find(
|
|
167
|
-
(name) =>
|
|
259
|
+
(name) =>
|
|
260
|
+
name.includes('__') && ownsClassBlock(facts, name, componentMode),
|
|
168
261
|
);
|
|
169
262
|
if (anatomy) {
|
|
170
|
-
add(
|
|
263
|
+
add(
|
|
264
|
+
classOwnerFor(facts, anatomy, componentMode),
|
|
265
|
+
'descendant',
|
|
266
|
+
`.${anatomy}`,
|
|
267
|
+
);
|
|
171
268
|
break;
|
|
172
269
|
}
|
|
173
270
|
if (componentMode) {
|
|
174
|
-
const root = classes.find((name) =>
|
|
271
|
+
const root = classes.find((name) =>
|
|
272
|
+
ownsClassBlock(facts, name, componentMode),
|
|
273
|
+
);
|
|
175
274
|
if (root) {
|
|
176
|
-
const entry = facts
|
|
177
|
-
if (entry
|
|
275
|
+
const entry = classOwnerFor(facts, root, componentMode);
|
|
276
|
+
if (entry?.package === ownPackage)
|
|
178
277
|
add(entry, 'descendant', `.${root}`);
|
|
179
278
|
break;
|
|
180
279
|
}
|
|
@@ -184,12 +283,13 @@ export function restyledComponents(
|
|
|
184
283
|
return found;
|
|
185
284
|
}
|
|
186
285
|
|
|
187
|
-
/**
|
|
286
|
+
/** Foreign component handles used to key another component's selector. */
|
|
188
287
|
export function contextualComponents(
|
|
189
288
|
selectorList,
|
|
190
289
|
facts,
|
|
191
290
|
isForeign,
|
|
192
291
|
ownPackage,
|
|
292
|
+
anyPackage = false,
|
|
193
293
|
) {
|
|
194
294
|
const found = [];
|
|
195
295
|
const seen = new Set();
|
|
@@ -197,13 +297,28 @@ export function contextualComponents(
|
|
|
197
297
|
for (const parts of complexSelectorParts(selectorList)) {
|
|
198
298
|
for (const [index, part] of parts.entries()) {
|
|
199
299
|
const compound = part.compound;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
300
|
+
const handles = [
|
|
301
|
+
...selectorClasses(compound).map((name) => ({
|
|
302
|
+
entry: classOwnerFor(facts, name, true),
|
|
303
|
+
name: `.${name}`,
|
|
304
|
+
inArgument: (argument) => selectorClasses(argument).includes(name),
|
|
305
|
+
})),
|
|
306
|
+
...(anyPackage ? dataComponents(compound) : []).map((value) => ({
|
|
307
|
+
entry: facts.dataComponentOwners.get(value),
|
|
308
|
+
name: `[data-component="${value}"]`,
|
|
309
|
+
inArgument: (argument) => dataComponents(argument).includes(value),
|
|
310
|
+
})),
|
|
311
|
+
];
|
|
312
|
+
for (const { entry, name, inArgument } of handles) {
|
|
313
|
+
if (
|
|
314
|
+
!entry ||
|
|
315
|
+
(!anyPackage && entry.package !== ownPackage) ||
|
|
316
|
+
!isForeign(entry)
|
|
317
|
+
)
|
|
203
318
|
continue;
|
|
204
319
|
const pseudo = ['has', 'is', 'where', 'not'].find((candidate) =>
|
|
205
320
|
pseudoArguments(compound, candidate).some((argument) =>
|
|
206
|
-
|
|
321
|
+
inArgument(argument),
|
|
207
322
|
),
|
|
208
323
|
);
|
|
209
324
|
const position = pseudo
|
|
@@ -218,7 +333,7 @@ export function contextualComponents(
|
|
|
218
333
|
seen.add(key);
|
|
219
334
|
found.push({
|
|
220
335
|
entry,
|
|
221
|
-
name
|
|
336
|
+
name,
|
|
222
337
|
position,
|
|
223
338
|
});
|
|
224
339
|
}
|
package/analyzer/index.d.mts
CHANGED
|
@@ -44,8 +44,9 @@ export function analyzeUiProject(options?: {
|
|
|
44
44
|
knownRules?: Iterable<string>;
|
|
45
45
|
adoption?: boolean;
|
|
46
46
|
ownership?: 'package' | 'component';
|
|
47
|
-
ownershipContext?: 'subject' | 'any';
|
|
47
|
+
ownershipContext?: 'subject' | 'any' | 'any-package';
|
|
48
48
|
implicitComponentOwnership?: boolean;
|
|
49
|
+
ownershipGroups?: Array<{ styleSources: string[]; sources: string[] }>;
|
|
49
50
|
}): Promise<UiAnalysisReport>;
|
|
50
51
|
|
|
51
52
|
export function formatUiAnalysisText(report: UiAnalysisReport): string;
|