@kerfjs/ui 5.0.0-beta.69 → 5.0.0-beta.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/ai/component-catalog.json +11 -1
  2. package/ai/component-catalog.schema.json +1 -1
  3. package/ai/component-composition.json +1 -0
  4. package/ai/component-composition.schema.json +1 -1
  5. package/ai/components/lucide-icon.md +12 -0
  6. package/ai/public-api-signatures-v1.md +7 -3
  7. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  8. package/analyzer/component-ownership.d.mts +16 -0
  9. package/analyzer/component-ownership.mjs +87 -4
  10. package/analyzer/index.d.mts +2 -0
  11. package/analyzer/index.mjs +490 -9
  12. package/dist/catalog.js +3 -3
  13. package/dist/{chunk-2ZOLX3IE.js → chunk-DB5ADJKF.js} +2 -2
  14. package/dist/{chunk-2ZOLX3IE.js.map → chunk-DB5ADJKF.js.map} +1 -1
  15. package/dist/{chunk-X3X4KRDK.js → chunk-DC6CN2DS.js} +2 -2
  16. package/dist/{chunk-X3X4KRDK.js.map → chunk-DC6CN2DS.js.map} +1 -1
  17. package/dist/{chunk-AUVG54KC.js → chunk-G4S2QU6V.js} +2 -2
  18. package/dist/{chunk-AUVG54KC.js.map → chunk-G4S2QU6V.js.map} +1 -1
  19. package/dist/{chunk-CRVDF4A6.js → chunk-K67Y2JNF.js} +2 -2
  20. package/dist/{chunk-CRVDF4A6.js.map → chunk-K67Y2JNF.js.map} +1 -1
  21. package/dist/{chunk-H7IODXGY.js → chunk-KCCEH7RF.js} +3 -3
  22. package/dist/{chunk-H7IODXGY.js.map → chunk-KCCEH7RF.js.map} +1 -1
  23. package/dist/{chunk-3BCJGM36.js → chunk-M6DEEBEF.js} +2 -2
  24. package/dist/{chunk-3BCJGM36.js.map → chunk-M6DEEBEF.js.map} +1 -1
  25. package/dist/{chunk-X3TPECWG.js → chunk-OXO2QJWV.js} +3 -1
  26. package/dist/chunk-OXO2QJWV.js.map +1 -0
  27. package/dist/{chunk-B3XWUX3A.js → chunk-RNQNEENY.js} +2 -2
  28. package/dist/{chunk-B3XWUX3A.js.map → chunk-RNQNEENY.js.map} +1 -1
  29. package/dist/{chunk-PQZPVBP4.js → chunk-RVERAFFY.js} +2 -2
  30. package/dist/{chunk-PQZPVBP4.js.map → chunk-RVERAFFY.js.map} +1 -1
  31. package/dist/{chunk-UA6JW52D.js → chunk-T2E5WBCZ.js} +110 -5
  32. package/dist/chunk-T2E5WBCZ.js.map +1 -0
  33. package/dist/{chunk-MOSIE4Z2.js → chunk-WTSNV5RG.js} +3 -2
  34. package/dist/chunk-WTSNV5RG.js.map +1 -0
  35. package/dist/collapsible-panel.js +3 -3
  36. package/dist/disclosure-arrow.js +1 -1
  37. package/dist/index.js +8 -8
  38. package/dist/list-header.js +1 -1
  39. package/dist/lucide-icon.d.ts +3 -1
  40. package/dist/lucide-icon.js +1 -1
  41. package/dist/nav-stack.js +1 -1
  42. package/dist/select.js +1 -1
  43. package/dist/split-view.js +1 -1
  44. package/dist/styles/app-tab.css +4 -0
  45. package/dist/styles/tab-bar.css +8 -0
  46. package/dist/styles/toolbar-control-group.css +1 -1
  47. package/dist/tab-bar.d.ts +3 -1
  48. package/dist/tab-bar.js +1 -1
  49. package/dist/token-search-field.js +1 -1
  50. package/dist/wire-tab-bars.js +1 -1
  51. package/dist/wire-token-search-fields.js +1 -1
  52. package/dist/workbench.js +1 -1
  53. package/docs/component-contract.md +16 -3
  54. package/docs/component-selection.md +8 -1
  55. package/docs/ui-analyzer.md +30 -9
  56. package/docs/ui-doctor.md +15 -6
  57. package/doctor/config.schema.json +2 -0
  58. package/doctor/index.d.mts +2 -0
  59. package/doctor/index.mjs +21 -0
  60. package/package.json +1 -1
  61. package/dist/chunk-MOSIE4Z2.js.map +0 -1
  62. package/dist/chunk-UA6JW52D.js.map +0 -1
  63. package/dist/chunk-X3TPECWG.js.map +0 -1
@@ -159,7 +159,16 @@
159
159
  ],
160
160
  "variants": [
161
161
  "decorative",
162
- "labeled"
162
+ "labeled",
163
+ "outline (default appearance)",
164
+ "solid (appearance=solid)"
165
+ ],
166
+ "usageExamples": [
167
+ {
168
+ "title": "Solid selected glyph",
169
+ "description": "The solid appearance fills a compatible Lucide glyph with currentColor while retaining its stroke and accessible name policy.",
170
+ "code": "<LucideIcon icon={Star} name=\"star\" appearance=\"solid\" />"
171
+ }
163
172
  ],
164
173
  "accessibility": [
165
174
  "Decorative icons are aria-hidden; meaningful icons require a label."
@@ -4665,6 +4674,7 @@
4665
4674
  {
4666
4675
  "id": "loading-spinner",
4667
4676
  "name": "LoadingSpinner",
4677
+ "rootElement": "svg",
4668
4678
  "category": "Feedback",
4669
4679
  "kind": "component",
4670
4680
  "source": "kerf",
@@ -60,7 +60,7 @@
60
60
  "accessibility": { "type": "array", "items": { "type": "string" } },
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
- "rootElement": { "description": "The intrinsic element the component renders at its root. Required for a component whose placeableClasses it renders itself: a placeable class on a plain element of this tag is the component recreated, so render the component instead; another carrier element may keep the class.", "type": "string", "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$" },
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
64
  "publicTokens": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
65
65
  "cssValueProps": { "type": "array", "items": { "$ref": "#/$defs/cssValueProp" }, "uniqueItems": true },
66
66
  "geometry": { "$ref": "#/$defs/geometry" },
@@ -3873,6 +3873,7 @@
3873
3873
  "boundaries": {
3874
3874
  "rootClass": "kui-loading-spinner",
3875
3875
  "publicClasses": ["kui-loading-spinner"],
3876
+ "rootElement": "svg",
3876
3877
  "publicTokens": []
3877
3878
  },
3878
3879
  "diagnostics": [],
@@ -333,7 +333,7 @@
333
333
  "rootElement": {
334
334
  "type": "string",
335
335
  "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
336
- "description": "The intrinsic element the component renders at its root. A placeableClasses class on a plain element of this tag recreates the component; the class stays placeable on any other carrier element."
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
338
  "publicTokens": { "$ref": "#/$defs/stringList" },
339
339
  "publicParts": { "$ref": "#/$defs/stringList" }
@@ -32,6 +32,8 @@ Exact prop names and types: [`@kerfjs/ui/lucide-icon`](../public-api-signatures-
32
32
 
33
33
  - decorative
34
34
  - labeled
35
+ - outline (default appearance)
36
+ - solid (appearance=solid)
35
37
 
36
38
  ## Composition
37
39
 
@@ -53,6 +55,16 @@ Margin: none · border: none · padding: none (layout role: foundation). `self`
53
55
 
54
56
  - Decorative icons are aria-hidden; meaningful icons require a label.
55
57
 
58
+ ## Usage examples
59
+
60
+ ### Solid selected glyph
61
+
62
+ The solid appearance fills a compatible Lucide glyph with currentColor while retaining its stroke and accessible name policy.
63
+
64
+ ```tsx
65
+ <LucideIcon icon={Star} name="star" appearance="solid" />
66
+ ```
67
+
56
68
  ## Styling boundary
57
69
 
58
70
  The component owns its own styles. Configure it through its props and variants; do not override its internals.
@@ -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.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.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.70` and `kerfjs@5.0.0-beta.70`. 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
 
@@ -1110,11 +1110,13 @@ interface TabBarProps {
1110
1110
  iconOnlyAt?: TabBarIconOnlyAt;
1111
1111
  /** Keep a trailing action beside the final tab or at the far edge of the bar. */
1112
1112
  trailingPlacement?: TabBarTrailingPlacement;
1113
+ /** Settle scrolling peers at whole-tab starts; wireTabBars measures a pinned leading tab's inset. */
1114
+ snapTabs?: boolean;
1113
1115
  /** Native named-slot assignment when composed inside a web component. */
1114
1116
  slot?: string;
1115
1117
  }
1116
1118
  /** Render a controlled tab strip. The application owns selection, order, and persistence. */
1117
- declare function TabBar({ id, label, children, leading, trailing, end, className, activation, allocation, presentation, iconOnlyAt, trailingPlacement, slot, }: TabBarProps): kerfjs.SafeHtml;
1119
+ declare function TabBar({ id, label, children, leading, trailing, end, className, activation, allocation, presentation, iconOnlyAt, trailingPlacement, snapTabs, slot, }: TabBarProps): kerfjs.SafeHtml;
1118
1120
 
1119
1121
  export { type TabActivation, TabBar, type TabBarAllocation, type TabBarIconOnlyAt, type TabBarPresentation, type TabBarProps, type TabBarTrailingPlacement };
1120
1122
  ```
@@ -3258,6 +3260,8 @@ type LucideIconSize = 'xs' | 's' | 'm' | 'l' | 'xl' | number;
3258
3260
  interface LucideIconProps {
3259
3261
  icon: LucideNode;
3260
3262
  name: string;
3263
+ /** Outline by default; solid fills compatible glyphs with the current text color. */
3264
+ appearance?: 'outline' | 'solid';
3261
3265
  className?: string;
3262
3266
  label?: string;
3263
3267
  /** Named icon scale or positive pixel size, converted to rem. Omit for 1em. */
@@ -3266,7 +3270,7 @@ interface LucideIconProps {
3266
3270
  slot?: string;
3267
3271
  }
3268
3272
  /** Render a Lucide-compatible icon node without copying icon SVG strings. */
3269
- declare function LucideIcon({ icon, name, className, label, size, slot, }: LucideIconProps): kerfjs.SafeHtml;
3273
+ declare function LucideIcon({ icon, name, appearance, className, label, size, slot, }: LucideIconProps): kerfjs.SafeHtml;
3270
3274
 
3271
3275
  export { LucideIcon, type LucideIconProps, type LucideIconSize, type LucideNode };
3272
3276
  ```
@@ -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.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.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.70` 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';
@@ -33,7 +33,23 @@ export function restyledComponents(
33
33
  selectorList: string,
34
34
  facts: ComponentOwnershipFacts,
35
35
  isForeign: (entry: OwnershipCatalogEntry) => boolean,
36
+ options?: {
37
+ componentMode?: boolean;
38
+ ownPackage?: string;
39
+ hooks?: Map<string, OwnershipCatalogEntry>;
40
+ composedChildren?: Map<string, OwnershipCatalogEntry>;
41
+ },
36
42
  ): RestyledComponent[];
43
+ export function contextualComponents(
44
+ selectorList: string,
45
+ facts: ComponentOwnershipFacts,
46
+ isForeign: (entry: OwnershipCatalogEntry) => boolean,
47
+ ownPackage: string | undefined,
48
+ ): Array<{
49
+ entry: OwnershipCatalogEntry;
50
+ name: string;
51
+ position: 'subject' | 'ancestor' | 'sibling' | 'has' | 'is' | 'where' | 'not';
52
+ }>;
37
53
  export function privateVariableOwner(
38
54
  property: string,
39
55
  facts: ComponentOwnershipFacts,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Downstream component-ownership facts for `kerf-ui-analyze` (`KUI-L019` –
3
- * `KUI-L022`): the consumer-side form of the package's own
3
+ * `KUI-L023`): the consumer-side form of the package's own
4
4
  * `check:css-ownership` rules.
5
5
  *
6
6
  * Components own their styles and are configured, never overridden. An
@@ -9,13 +9,15 @@
9
9
  * application's stylesheet), but it never makes another package's component
10
10
  * the subject of a rule, writes that component's private variables, overrides
11
11
  * a token its typed prop configures, or restyles it through a hook class on
12
- * its root. Every fact here comes from the loaded composition catalogs, so
13
- * third-party component packages get the same protection as `@kerfjs/ui`.
12
+ * its root. Facts come from composition and selection catalogs, plus optional
13
+ * source inference, so third-party packages get the same protection as
14
+ * `@kerfjs/ui`.
14
15
  */
15
16
  import {
16
17
  classNames,
17
18
  complexSelectorParts,
18
19
  dataComponents,
20
+ pseudoArguments,
19
21
  subjectTypes,
20
22
  withoutRelationalArguments,
21
23
  } from './selectors.mjs';
@@ -95,7 +97,17 @@ export function componentOwnershipFacts(entries) {
95
97
  * Returns `{ entry, via, name }` records; `isForeign(entry)` decides which
96
98
  * entries the stylesheet does not own.
97
99
  */
98
- export function restyledComponents(selectorList, facts, isForeign) {
100
+ export function restyledComponents(
101
+ selectorList,
102
+ facts,
103
+ isForeign,
104
+ {
105
+ componentMode = false,
106
+ ownPackage,
107
+ hooks = new Map(),
108
+ composedChildren = new Map(),
109
+ } = {},
110
+ ) {
99
111
  const found = [];
100
112
  const seen = new Set();
101
113
  const add = (entry, via, name) => {
@@ -127,10 +139,29 @@ export function restyledComponents(selectorList, facts, isForeign) {
127
139
  // that component. Do not infer ownership through its root, where an app
128
140
  // may place its own children, or through an app-named intermediate node.
129
141
  if (subjectClasses.length || parts.length < 2) continue;
142
+ if (componentMode && parts.at(-1).combinator === '>') {
143
+ const types = subjectTypes(subject) ?? [];
144
+ for (const name of classNames(
145
+ withoutRelationalArguments(parts.at(-2).compound),
146
+ ))
147
+ for (const type of types)
148
+ add(
149
+ composedChildren.get(`${name}|${type}`),
150
+ 'descendant',
151
+ `.${name}`,
152
+ );
153
+ }
130
154
  for (let index = parts.length - 2; index >= 0; index -= 1) {
131
155
  const classes = classNames(
132
156
  withoutRelationalArguments(parts[index].compound),
133
157
  );
158
+ const hook = componentMode
159
+ ? classes.find((name) => hooks.has(name))
160
+ : undefined;
161
+ if (hook) {
162
+ add(hooks.get(hook), 'descendant', `.${hook}`);
163
+ break;
164
+ }
134
165
  if (classes.some((name) => !facts.classOwners.has(name))) break;
135
166
  const anatomy = classes.find(
136
167
  (name) => name.includes('__') && facts.classOwners.has(name),
@@ -139,6 +170,58 @@ export function restyledComponents(selectorList, facts, isForeign) {
139
170
  add(facts.classOwners.get(anatomy), 'descendant', `.${anatomy}`);
140
171
  break;
141
172
  }
173
+ if (componentMode) {
174
+ const root = classes.find((name) => facts.classOwners.has(name));
175
+ if (root) {
176
+ const entry = facts.classOwners.get(root);
177
+ if (entry.package === ownPackage)
178
+ add(entry, 'descendant', `.${root}`);
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ }
184
+ return found;
185
+ }
186
+
187
+ /** Same-package classes used to key another component's selector. */
188
+ export function contextualComponents(
189
+ selectorList,
190
+ facts,
191
+ isForeign,
192
+ ownPackage,
193
+ ) {
194
+ const found = [];
195
+ const seen = new Set();
196
+ const selectorClasses = (text) => classNames(text.replace(/\[[^\]]*\]/g, ''));
197
+ for (const parts of complexSelectorParts(selectorList)) {
198
+ for (const [index, part] of parts.entries()) {
199
+ const compound = part.compound;
200
+ for (const name of selectorClasses(compound)) {
201
+ const entry = facts.classOwners.get(name);
202
+ if (!entry || entry.package !== ownPackage || !isForeign(entry))
203
+ continue;
204
+ const pseudo = ['has', 'is', 'where', 'not'].find((candidate) =>
205
+ pseudoArguments(compound, candidate).some((argument) =>
206
+ selectorClasses(argument).includes(name),
207
+ ),
208
+ );
209
+ const position = pseudo
210
+ ? pseudo
211
+ : index === parts.length - 1
212
+ ? 'subject'
213
+ : ['+', '~'].includes(parts[index + 1].combinator)
214
+ ? 'sibling'
215
+ : 'ancestor';
216
+ const key = `${entry.key}|${name}|${position}`;
217
+ if (seen.has(key)) continue;
218
+ seen.add(key);
219
+ found.push({
220
+ entry,
221
+ name: `.${name}`,
222
+ position,
223
+ });
224
+ }
142
225
  }
143
226
  }
144
227
  return found;
@@ -44,6 +44,8 @@ export function analyzeUiProject(options?: {
44
44
  knownRules?: Iterable<string>;
45
45
  adoption?: boolean;
46
46
  ownership?: 'package' | 'component';
47
+ ownershipContext?: 'subject' | 'any';
48
+ implicitComponentOwnership?: boolean;
47
49
  }): Promise<UiAnalysisReport>;
48
50
 
49
51
  export function formatUiAnalysisText(report: UiAnalysisReport): string;