@kerfjs/ui 5.0.0-beta.56 → 5.0.0-beta.58
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 +81 -37
- package/ai/application-ui-diagnostic-ids-v1.json +9 -1
- package/ai/application-ui-profile-sync.cjs +1 -1
- package/ai/application-ui-profile.defaults.json +63 -8
- package/ai/application-ui-profile.mjs +1 -1
- package/ai/component-catalog.json +211 -33
- package/ai/component-catalog.schema.json +2 -0
- package/ai/component-class-owners.cjs +83 -0
- package/ai/component-composition-extension.d.ts +7 -0
- package/ai/component-composition-extension.schema.json +7 -0
- package/ai/{component-catalog-v2-overrides.json → component-composition-rules.json} +2 -1
- package/ai/{component-catalog-v2.d.ts → component-composition.d.ts} +9 -5
- package/ai/{component-catalog-v2.json → component-composition.json} +155 -24
- package/ai/{component-catalog-v2.schema.json → component-composition.schema.json} +21 -5
- package/ai/components/README.md +170 -0
- package/ai/components/application-tabs.md +65 -0
- package/ai/components/badge.md +72 -0
- package/ai/components/collapsible-panel.md +102 -0
- package/ai/components/content-item.md +90 -0
- package/ai/components/disclosure-arrow.md +77 -0
- package/ai/components/document-baseline.md +75 -0
- package/ai/components/empty-state.md +80 -0
- package/ai/components/feedback.md +71 -0
- package/ai/components/floating-toolbar.md +80 -0
- package/ai/components/foundation.md +65 -0
- package/ai/components/grid.md +93 -0
- package/ai/components/headers.md +63 -0
- package/ai/components/layout.md +75 -0
- package/ai/components/list-action-row.md +91 -0
- package/ai/components/list-header.md +89 -0
- package/ai/components/list-inset-control.md +74 -0
- package/ai/components/list-inset-text.md +72 -0
- package/ai/components/list-item.md +88 -0
- package/ai/components/list.md +103 -0
- package/ai/components/loading-spinner.md +72 -0
- package/ai/components/lucide-icon.md +66 -0
- package/ai/components/nav-stack.md +103 -0
- package/ai/components/pane.md +100 -0
- package/ai/components/popup-menu.md +82 -0
- package/ai/components/recipe-app-shell.md +57 -0
- package/ai/components/recipe-collapsible-sidebar.md +57 -0
- package/ai/components/recipe-compact-toolbar.md +57 -0
- package/ai/components/recipe-composer-form.md +57 -0
- package/ai/components/recipe-list-detail-dialog.md +57 -0
- package/ai/components/recipe-list-workspace-states.md +57 -0
- package/ai/components/recipe-loading-inspector.md +57 -0
- package/ai/components/recipe-navigation-sidebar.md +57 -0
- package/ai/components/recipe-navigation-stack.md +57 -0
- package/ai/components/recipe-workspace-header.md +57 -0
- package/ai/components/resize.md +101 -0
- package/ai/components/row.md +96 -0
- package/ai/components/segmented-control.md +82 -0
- package/ai/components/select.md +107 -0
- package/ai/components/skeleton.md +84 -0
- package/ai/components/spacer.md +86 -0
- package/ai/components/split-view.md +102 -0
- package/ai/components/state-banner.md +92 -0
- package/ai/components/sunken-panel.md +79 -0
- package/ai/components/surface-scaffold.md +78 -0
- package/ai/components/tab-bar.md +112 -0
- package/ai/components/tab-scaffold.md +94 -0
- package/ai/components/tabs.md +90 -0
- package/ai/components/text.md +82 -0
- package/ai/components/token-search-field.md +101 -0
- package/ai/components/toolbar-control-group.md +102 -0
- package/ai/components/toolbar-text.md +76 -0
- package/ai/components/toolbar.md +95 -0
- package/ai/components/value-table.md +72 -0
- package/ai/components/wa-accordion-item.md +55 -0
- package/ai/components/wa-accordion.md +65 -0
- package/ai/components/wa-animated-image.md +55 -0
- package/ai/components/wa-animation.md +55 -0
- package/ai/components/wa-avatar.md +55 -0
- package/ai/components/wa-badge.md +55 -0
- package/ai/components/wa-breadcrumb-item.md +53 -0
- package/ai/components/wa-breadcrumb.md +55 -0
- package/ai/components/wa-button-group.md +59 -0
- package/ai/components/wa-button.md +55 -0
- package/ai/components/wa-callout.md +58 -0
- package/ai/components/wa-card.md +59 -0
- package/ai/components/wa-carousel-item.md +53 -0
- package/ai/components/wa-carousel.md +55 -0
- package/ai/components/wa-checkbox-group.md +55 -0
- package/ai/components/wa-checkbox.md +55 -0
- package/ai/components/wa-color-picker.md +55 -0
- package/ai/components/wa-comparison.md +55 -0
- package/ai/components/wa-copy-button.md +55 -0
- package/ai/components/wa-details.md +61 -0
- package/ai/components/wa-dialog.md +59 -0
- package/ai/components/wa-divider.md +53 -0
- package/ai/components/wa-drawer.md +55 -0
- package/ai/components/wa-dropdown-item.md +60 -0
- package/ai/components/wa-dropdown.md +60 -0
- package/ai/components/wa-format-bytes.md +53 -0
- package/ai/components/wa-format-date.md +53 -0
- package/ai/components/wa-format-number.md +53 -0
- package/ai/components/wa-icon.md +58 -0
- package/ai/components/wa-include.md +53 -0
- package/ai/components/wa-input.md +59 -0
- package/ai/components/wa-intersection-observer.md +53 -0
- package/ai/components/wa-known-date.md +53 -0
- package/ai/components/wa-markdown.md +53 -0
- package/ai/components/wa-mutation-observer.md +53 -0
- package/ai/components/wa-number-input.md +55 -0
- package/ai/components/wa-option.md +59 -0
- package/ai/components/wa-otp-input.md +53 -0
- package/ai/components/wa-page.md +53 -0
- package/ai/components/wa-pagination.md +55 -0
- package/ai/components/wa-popover.md +55 -0
- package/ai/components/wa-popup.md +53 -0
- package/ai/components/wa-progress-bar.md +53 -0
- package/ai/components/wa-progress-ring.md +53 -0
- package/ai/components/wa-qr-code.md +53 -0
- package/ai/components/wa-radio-group.md +59 -0
- package/ai/components/wa-radio.md +55 -0
- package/ai/components/wa-random-content.md +53 -0
- package/ai/components/wa-rating.md +55 -0
- package/ai/components/wa-relative-time.md +53 -0
- package/ai/components/wa-resize-observer.md +53 -0
- package/ai/components/wa-scroller.md +53 -0
- package/ai/components/wa-select.md +63 -0
- package/ai/components/wa-skeleton.md +53 -0
- package/ai/components/wa-slider.md +55 -0
- package/ai/components/wa-spinner.md +58 -0
- package/ai/components/wa-split-panel.md +58 -0
- package/ai/components/wa-switch.md +53 -0
- package/ai/components/wa-tab-group.md +69 -0
- package/ai/components/wa-tab-panel.md +60 -0
- package/ai/components/wa-tab.md +59 -0
- package/ai/components/wa-tag.md +59 -0
- package/ai/components/wa-textarea.md +53 -0
- package/ai/components/wa-time-input.md +55 -0
- package/ai/components/wa-toast-item.md +60 -0
- package/ai/components/wa-toast.md +60 -0
- package/ai/components/wa-tooltip.md +55 -0
- package/ai/components/wa-tree-item.md +55 -0
- package/ai/components/wa-tree.md +55 -0
- package/ai/components/wa-zoomable-frame.md +55 -0
- package/ai/components/workbench.md +114 -0
- package/ai/public-api-signatures-v1.md +174 -43
- package/ai/skill.md +100 -48
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/component-ownership.d.mts +56 -0
- package/analyzer/component-ownership.mjs +184 -0
- package/analyzer/index.d.mts +3 -0
- package/analyzer/index.mjs +274 -20
- package/analyzer/selectors.d.mts +21 -0
- package/analyzer/selectors.mjs +212 -0
- package/dist/browser/catalog.js +25 -0
- package/dist/browser/collapsible-panel.js +7 -0
- package/dist/browser/nav-stack.js +8 -0
- package/dist/browser/split-view.js +10 -0
- package/dist/browser/tab-scaffold.js +4 -0
- package/dist/browser/workbench.js +10 -0
- package/dist/catalog-resources.d.ts +2 -2
- package/dist/catalog.d.ts +3 -3
- package/dist/catalog.js +8 -7
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-QQOTQTDD.js → chunk-2CCMJTVY.js} +13 -10
- package/dist/chunk-2CCMJTVY.js.map +1 -0
- package/dist/{chunk-PK4N5QTO.js → chunk-553ASUK6.js} +2 -3
- package/dist/chunk-553ASUK6.js.map +1 -0
- package/dist/{chunk-G33XC2NB.js → chunk-6YFHCLHG.js} +2 -2
- package/dist/chunk-6YFHCLHG.js.map +1 -0
- package/dist/{chunk-ZIYOJFZD.js → chunk-7JUBXOIV.js} +17 -4
- package/dist/chunk-7JUBXOIV.js.map +1 -0
- package/dist/{chunk-T4UZTNZ4.js → chunk-EVYKTVBQ.js} +2 -11
- package/dist/chunk-EVYKTVBQ.js.map +1 -0
- package/dist/{chunk-AK67F6QN.js → chunk-EZVNC4J6.js} +4 -1
- package/dist/chunk-EZVNC4J6.js.map +1 -0
- package/dist/{chunk-MA6Z6EIM.js → chunk-F4QMLOXJ.js} +3 -2
- package/dist/chunk-F4QMLOXJ.js.map +1 -0
- package/dist/chunk-JCV4GXWB.js +249 -0
- package/dist/chunk-JCV4GXWB.js.map +1 -0
- package/dist/chunk-K47P7QZC.js +17 -0
- package/dist/chunk-K47P7QZC.js.map +1 -0
- package/dist/{chunk-MOBRFZH3.js → chunk-OUK6QRDR.js} +2 -12
- package/dist/chunk-OUK6QRDR.js.map +1 -0
- package/dist/{chunk-VNPQDCQZ.js → chunk-QOWG4ZCL.js} +3 -2
- package/dist/chunk-QOWG4ZCL.js.map +1 -0
- package/dist/{chunk-V65EOWNF.js → chunk-QPUPLNNB.js} +20 -8
- package/dist/chunk-QPUPLNNB.js.map +1 -0
- package/dist/{chunk-O3EXIBEZ.js → chunk-W4XKTST6.js} +8 -7
- package/dist/chunk-W4XKTST6.js.map +1 -0
- package/dist/{chunk-K6UNVKDK.js → chunk-XDKAYSVD.js} +3 -1
- package/dist/chunk-XDKAYSVD.js.map +1 -0
- package/dist/collapsible-panel.d.ts +9 -9
- package/dist/collapsible-panel.js +2 -2
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +11 -9
- package/dist/list-action-row.js +2 -1
- package/dist/list-header.js +1 -1
- package/dist/list-item.js +2 -1
- package/dist/nav-stack.d.ts +13 -1
- package/dist/nav-stack.js +1 -1
- package/dist/pane.d.ts +20 -3
- package/dist/pane.js +1 -1
- package/dist/{panel-toolbar-C8sF-YZC.d.ts → panel-toolbar-DqwtBxQL.d.ts} +13 -13
- package/dist/select.js +1 -1
- package/dist/split-view.d.ts +3 -3
- package/dist/split-view.js +3 -2
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/app-tab.css +31 -0
- package/dist/styles/badge.css +19 -11
- package/dist/styles/collapsible-panel.css +13 -9
- package/dist/styles/floating-toolbar.css +25 -6
- package/dist/styles/list-header.css +0 -12
- package/dist/styles/list.css +4 -4
- package/dist/styles/nav-stack.css +46 -19
- package/dist/styles/pane.css +50 -0
- package/dist/styles/popup-menu.css +151 -0
- package/dist/styles/resizable-region.css +22 -10
- package/dist/styles/select.css +28 -14
- package/dist/styles/skeleton.css +30 -3
- package/dist/styles/split-view.css +10 -7
- package/dist/styles/state-banner.css +0 -19
- package/dist/styles/surface-scaffold.css +8 -3
- package/dist/styles/tab-bar.css +79 -28
- package/dist/styles/tab-scaffold.css +25 -1
- package/dist/styles/text.css +16 -0
- package/dist/styles/toolbar-control-group.css +121 -66
- package/dist/styles/toolbar-text.css +21 -0
- package/dist/styles/toolbar.css +8 -32
- package/dist/styles/workbench.css +18 -18
- package/dist/tab-scaffold.d.ts +11 -1
- package/dist/tab-scaffold.js +2 -0
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/toolbar-control-group.d.ts +3 -1
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +7 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +8 -1
- package/dist/toolbar.js +1 -1
- package/dist/{types-DXAGogOS.d.ts → types-BuHELdXR.d.ts} +1 -1
- package/dist/wire-catalog.d.ts +3 -1
- package/dist/wire-catalog.js +2 -0
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-scroll-dividers.d.ts +69 -0
- package/dist/wire-scroll-dividers.js +3 -0
- package/dist/wire-scroll-dividers.js.map +1 -0
- package/dist/workbench.d.ts +17 -16
- package/dist/workbench.js +1 -1
- package/docs/app-layouts.md +9 -1
- package/docs/catalog.md +10 -5
- package/docs/collapsible-panel.md +22 -13
- package/docs/component-contract.md +147 -21
- package/docs/component-selection.md +21 -8
- package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -1
- package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -1
- package/docs/design/templates/tab-bar-dark.svg +1 -1
- package/docs/design/templates/tab-bar.svg +1 -1
- package/docs/design-philosophy.md +30 -3
- package/docs/examples/application-ui-profile.json +4 -4
- package/docs/examples/{component-catalog-extension-v2.json → component-composition-extension.json} +3 -3
- package/docs/layout.md +82 -3
- package/docs/nav-stack.md +33 -3
- package/docs/split-view.md +10 -5
- package/docs/tab-scaffold.md +23 -1
- package/docs/type-contracts.md +4 -3
- package/docs/ui-analyzer.md +99 -5
- package/docs/ui-doctor.md +10 -0
- package/docs/workbench.md +31 -20
- package/doctor/index.mjs +6 -1
- package/llms.txt +85 -20
- package/package.json +17 -5
- package/ux-demo/recipes/collapsible-sidebar.tsx +0 -1
- package/ux-demo/recipes/composer-form.tsx +0 -1
- package/ux-demo/recipes/list-detail-dialog.tsx +0 -2
- package/ux-demo/recipes/list-workspace-states.tsx +0 -1
- package/ux-demo/recipes/loading-inspector.tsx +0 -1
- package/ux-demo/recipes/workspace-header.tsx +0 -1
- package/ai/component-catalog-extension-v2.d.ts +0 -7
- package/ai/component-catalog-extension-v2.schema.json +0 -7
- package/dist/chunk-AK67F6QN.js.map +0 -1
- package/dist/chunk-G33XC2NB.js.map +0 -1
- package/dist/chunk-K6UNVKDK.js.map +0 -1
- package/dist/chunk-MA6Z6EIM.js.map +0 -1
- package/dist/chunk-MOBRFZH3.js.map +0 -1
- package/dist/chunk-O3EXIBEZ.js.map +0 -1
- package/dist/chunk-PK4N5QTO.js.map +0 -1
- package/dist/chunk-QQOTQTDD.js.map +0 -1
- package/dist/chunk-T4UZTNZ4.js.map +0 -1
- package/dist/chunk-V65EOWNF.js.map +0 -1
- package/dist/chunk-VNPQDCQZ.js.map +0 -1
- package/dist/chunk-ZIYOJFZD.js.map +0 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Downstream component-ownership facts for `kerf-ui-analyze` (`KUI-L019` –
|
|
3
|
+
* `KUI-L022`): the consumer-side form of the package's own
|
|
4
|
+
* `check:css-ownership` rules.
|
|
5
|
+
*
|
|
6
|
+
* Components own their styles and are configured, never overridden. An
|
|
7
|
+
* application or component package may style its own elements, including in a
|
|
8
|
+
* cataloged component's context (`.kui-toolbar > .my-widget` lives in the
|
|
9
|
+
* application's stylesheet), but it never makes another package's component
|
|
10
|
+
* the subject of a rule, writes that component's private variables, overrides
|
|
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`.
|
|
14
|
+
*/
|
|
15
|
+
import {
|
|
16
|
+
classNames,
|
|
17
|
+
complexSelectorParts,
|
|
18
|
+
dataComponents,
|
|
19
|
+
subjectTypes,
|
|
20
|
+
withoutRelationalArguments,
|
|
21
|
+
} from './selectors.mjs';
|
|
22
|
+
|
|
23
|
+
const kebab = (value) =>
|
|
24
|
+
value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
25
|
+
|
|
26
|
+
function isWebAwesomeTagEntry(entry) {
|
|
27
|
+
return entry.source === 'webawesome' && /^wa-[a-z0-9-]+$/.test(entry.id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Index the loaded catalog entries by every handle a stylesheet can use to
|
|
32
|
+
* reach them: public classes, `data-component` values, Web Awesome tags the
|
|
33
|
+
* package themes, private-variable prefixes, and component tokens a typed
|
|
34
|
+
* prop configures.
|
|
35
|
+
*/
|
|
36
|
+
export function componentOwnershipFacts(entries) {
|
|
37
|
+
const classOwners = new Map();
|
|
38
|
+
const dataComponentOwners = new Map();
|
|
39
|
+
const tagOwners = new Map();
|
|
40
|
+
const privatePrefixes = [];
|
|
41
|
+
const typedTokens = new Map();
|
|
42
|
+
const setOnce = (map, key, entry) => {
|
|
43
|
+
if (!map.has(key)) map.set(key, entry);
|
|
44
|
+
};
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const boundaries = entry.boundaries ?? {};
|
|
47
|
+
for (const className of boundaries.publicClasses ?? []) {
|
|
48
|
+
setOnce(classOwners, className, entry);
|
|
49
|
+
if (
|
|
50
|
+
className.startsWith('kui-') &&
|
|
51
|
+
!className.includes('__') &&
|
|
52
|
+
!className.includes('--')
|
|
53
|
+
)
|
|
54
|
+
setOnce(dataComponentOwners, className.slice('kui-'.length), entry);
|
|
55
|
+
}
|
|
56
|
+
if (entry.id && !isWebAwesomeTagEntry(entry))
|
|
57
|
+
setOnce(dataComponentOwners, entry.id, entry);
|
|
58
|
+
if (isWebAwesomeTagEntry(entry)) setOnce(tagOwners, entry.id, entry);
|
|
59
|
+
// Every root-like public class names the component (Toolbar Control
|
|
60
|
+
// Group's first class is its action link, not the group).
|
|
61
|
+
const roots = [
|
|
62
|
+
...new Set(
|
|
63
|
+
[boundaries.rootClass, ...(boundaries.publicClasses ?? [])].filter(
|
|
64
|
+
(name) => name && !name.includes('__') && !name.includes('--'),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
];
|
|
68
|
+
const tokens = new Set(boundaries.publicTokens ?? []);
|
|
69
|
+
for (const root of roots) {
|
|
70
|
+
privatePrefixes.push({ prefix: `--_${root}`, entry });
|
|
71
|
+
for (const contract of entry.cssValueProps ?? []) {
|
|
72
|
+
const [head] = contract.path.split('[].');
|
|
73
|
+
const token = `--${root}-${kebab(head)}`;
|
|
74
|
+
if (tokens.has(token))
|
|
75
|
+
setOnce(typedTokens, token, { entry, path: contract.path });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
privatePrefixes.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
80
|
+
return {
|
|
81
|
+
classOwners,
|
|
82
|
+
dataComponentOwners,
|
|
83
|
+
tagOwners,
|
|
84
|
+
privatePrefixes,
|
|
85
|
+
typedTokens,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The cataloged components a selector list makes its subject: the rightmost
|
|
91
|
+
* compound of each complex selector, with `:has()` / `:not()` arguments
|
|
92
|
+
* removed because keying on or excluding a component does not style it. A
|
|
93
|
+
* subject that is a `::part()` is left to the shadow-part rule (`KUI-L011`).
|
|
94
|
+
* Returns `{ entry, via, name }` records; `isForeign(entry)` decides which
|
|
95
|
+
* entries the stylesheet does not own.
|
|
96
|
+
*/
|
|
97
|
+
export function restyledComponents(selectorList, facts, isForeign) {
|
|
98
|
+
const found = [];
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const add = (entry, via, name) => {
|
|
101
|
+
if (!entry || !isForeign(entry)) return;
|
|
102
|
+
const key = `${entry.key}|${via}|${name}`;
|
|
103
|
+
if (seen.has(key)) return;
|
|
104
|
+
seen.add(key);
|
|
105
|
+
found.push({ entry, via, name });
|
|
106
|
+
};
|
|
107
|
+
for (const parts of complexSelectorParts(selectorList)) {
|
|
108
|
+
const subject = parts.at(-1)?.compound;
|
|
109
|
+
if (!subject || /::part\(/i.test(subject)) continue;
|
|
110
|
+
const bare = withoutRelationalArguments(subject);
|
|
111
|
+
for (const className of classNames(bare))
|
|
112
|
+
add(facts.classOwners.get(className), 'class', `.${className}`);
|
|
113
|
+
for (const value of dataComponents(bare))
|
|
114
|
+
add(
|
|
115
|
+
facts.dataComponentOwners.get(value),
|
|
116
|
+
'data-component',
|
|
117
|
+
`[data-component="${value}"]`,
|
|
118
|
+
);
|
|
119
|
+
// An application class on the subject scopes a Web Awesome tag to the
|
|
120
|
+
// application's own element; a component package never places one there.
|
|
121
|
+
if (classNames(bare).some((name) => !facts.classOwners.has(name))) continue;
|
|
122
|
+
for (const type of subjectTypes(subject) ?? [])
|
|
123
|
+
add(facts.tagOwners.get(type), 'tag', type);
|
|
124
|
+
}
|
|
125
|
+
return found;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The catalog entry whose private `--_<root>-*` variable `property` is. */
|
|
129
|
+
export function privateVariableOwner(property, facts) {
|
|
130
|
+
if (!property.startsWith('--_')) return undefined;
|
|
131
|
+
const owner = facts.privatePrefixes.find(
|
|
132
|
+
({ prefix }) => property === prefix || property.startsWith(`${prefix}-`),
|
|
133
|
+
);
|
|
134
|
+
if (owner) return owner.entry;
|
|
135
|
+
return property.startsWith('--_kui-') ? null : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The typed prop that configures a component token, if one does. */
|
|
139
|
+
export function typedPropForToken(token, facts) {
|
|
140
|
+
return facts.typedTokens.get(token);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Every configuration seam an entry publishes, as prose. */
|
|
144
|
+
export function configurationFor(entry) {
|
|
145
|
+
if (!entry) return 'its documented props and variants';
|
|
146
|
+
if (isWebAwesomeTagEntry(entry))
|
|
147
|
+
return `<${entry.id}>'s documented attributes and ${entry.package}'s Web Awesome theme tokens`;
|
|
148
|
+
const props = (entry.cssValueProps ?? []).map(({ path }) => `\`${path}\``);
|
|
149
|
+
const rootClass = entry.boundaries?.rootClass;
|
|
150
|
+
const tokens = (entry.boundaries?.publicTokens ?? []).filter(
|
|
151
|
+
(token) => rootClass && token.startsWith(`--${rootClass}-`),
|
|
152
|
+
);
|
|
153
|
+
const seams = [
|
|
154
|
+
...(props.length ? [`its props (${props.join(', ')})`] : []),
|
|
155
|
+
...(tokens.length
|
|
156
|
+
? [
|
|
157
|
+
`its public tokens (${tokens.map((item) => `\`${item}\``).join(', ')})`,
|
|
158
|
+
]
|
|
159
|
+
: []),
|
|
160
|
+
];
|
|
161
|
+
return seams.length
|
|
162
|
+
? `${seams.join(' or ')}, or its documented variants`
|
|
163
|
+
: 'its documented props and variants';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The shared closing guidance: a missing seam is a component gap. */
|
|
167
|
+
export function reportGap(entry) {
|
|
168
|
+
const owner = entry?.package ?? '@kerfjs/ui';
|
|
169
|
+
return `If no configuration covers this need, report the component gap to ${owner} (open a feature request) instead of overriding it.`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A component's short name: its export, or its Web Awesome tag. */
|
|
173
|
+
export function componentName(entry) {
|
|
174
|
+
if (!entry) return 'a Kerf component';
|
|
175
|
+
return isWebAwesomeTagEntry(entry) ? `<${entry.id}>` : entry.name;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A component's name with its catalog key. */
|
|
179
|
+
export function componentLabel(entry) {
|
|
180
|
+
if (!entry) return 'a Kerf component';
|
|
181
|
+
return isWebAwesomeTagEntry(entry)
|
|
182
|
+
? `<${entry.id}>`
|
|
183
|
+
: `${entry.name} (${entry.key})`;
|
|
184
|
+
}
|
package/analyzer/index.d.mts
CHANGED
|
@@ -34,6 +34,9 @@ export const UI_ANALYSIS_RULES: Readonly<
|
|
|
34
34
|
Record<`KUI-L${string}`, { severity: UiAnalysisSeverity; title: string }>
|
|
35
35
|
>;
|
|
36
36
|
|
|
37
|
+
/** The doctor's repair action for rules whose fix is more specific than the default. */
|
|
38
|
+
export const UI_ANALYSIS_ACTIONS: Readonly<Record<`KUI-L${string}`, string>>;
|
|
39
|
+
|
|
37
40
|
export function analyzeUiProject(options?: {
|
|
38
41
|
root?: string;
|
|
39
42
|
paths?: string[];
|
package/analyzer/index.mjs
CHANGED
|
@@ -14,7 +14,22 @@ import ts from 'typescript';
|
|
|
14
14
|
|
|
15
15
|
import { loadApplicationUiProfile } from '../ai/application-ui-profile.mjs';
|
|
16
16
|
import { isUiTraversalExcluded } from '../traversal-exclusions.mjs';
|
|
17
|
+
import {
|
|
18
|
+
componentLabel,
|
|
19
|
+
componentName,
|
|
20
|
+
componentOwnershipFacts,
|
|
21
|
+
configurationFor,
|
|
22
|
+
privateVariableOwner,
|
|
23
|
+
reportGap,
|
|
24
|
+
restyledComponents,
|
|
25
|
+
typedPropForToken,
|
|
26
|
+
} from './component-ownership.mjs';
|
|
17
27
|
import { collectLoudDeclarations, inspectLoudPairs } from './loud-pairs.mjs';
|
|
28
|
+
import {
|
|
29
|
+
classNames,
|
|
30
|
+
complexSelectorParts,
|
|
31
|
+
withoutRelationalArguments,
|
|
32
|
+
} from './selectors.mjs';
|
|
18
33
|
|
|
19
34
|
export const UI_ANALYSIS_SCHEMA_VERSION = 1;
|
|
20
35
|
|
|
@@ -23,7 +38,10 @@ export const UI_ANALYSIS_RULES = Object.freeze({
|
|
|
23
38
|
'KUI-L002': { severity: 'error', title: 'Unknown Kerf token' },
|
|
24
39
|
'KUI-L003': { severity: 'error', title: 'Competing geometry owners' },
|
|
25
40
|
'KUI-L004': { severity: 'review', title: 'Repeated content inset' },
|
|
26
|
-
|
|
41
|
+
// KUI-L005 ('Forced component dimension') is retired: KUI-L019 reports any
|
|
42
|
+
// restyle of a cataloged component, dimensions included, and L005's only
|
|
43
|
+
// other shape (a public class as an ancestor) sizes an application element.
|
|
44
|
+
// The id stays in the diagnostic registry so existing exceptions still load.
|
|
27
45
|
'KUI-L006': {
|
|
28
46
|
severity: 'review',
|
|
29
47
|
title: 'Hard-coded spacing outside the approved scale',
|
|
@@ -46,6 +64,34 @@ export const UI_ANALYSIS_RULES = Object.freeze({
|
|
|
46
64
|
severity: 'review',
|
|
47
65
|
title: 'Loud fill override without its on-loud pair',
|
|
48
66
|
},
|
|
67
|
+
'KUI-L019': {
|
|
68
|
+
severity: 'error',
|
|
69
|
+
title: 'Application CSS restyles a cataloged component',
|
|
70
|
+
},
|
|
71
|
+
'KUI-L020': {
|
|
72
|
+
severity: 'error',
|
|
73
|
+
title: "Another component's private variable",
|
|
74
|
+
},
|
|
75
|
+
'KUI-L021': {
|
|
76
|
+
severity: 'error',
|
|
77
|
+
title: 'Component token overridden where a typed prop exists',
|
|
78
|
+
},
|
|
79
|
+
'KUI-L022': {
|
|
80
|
+
severity: 'error',
|
|
81
|
+
title: "Hook class restyles a cataloged component's root",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const ownershipAction =
|
|
86
|
+
"Configure the component through its typed props, variants, or public tokens; style only your own elements (in the component's context when needed). If no configuration covers the need, report the component gap to its package instead of overriding it.";
|
|
87
|
+
|
|
88
|
+
/** The doctor's per-rule repair action, where it is more specific than the default. */
|
|
89
|
+
export const UI_ANALYSIS_ACTIONS = Object.freeze({
|
|
90
|
+
'KUI-L019': ownershipAction,
|
|
91
|
+
'KUI-L020': ownershipAction,
|
|
92
|
+
'KUI-L021':
|
|
93
|
+
'Set the typed prop the diagnostic names on the component instead of overriding its token.',
|
|
94
|
+
'KUI-L022': ownershipAction,
|
|
49
95
|
});
|
|
50
96
|
|
|
51
97
|
const adoptionRules = new Set([
|
|
@@ -54,12 +100,14 @@ const adoptionRules = new Set([
|
|
|
54
100
|
'KUI-L010',
|
|
55
101
|
'KUI-L011',
|
|
56
102
|
'KUI-L012',
|
|
103
|
+
'KUI-L019',
|
|
104
|
+
'KUI-L020',
|
|
105
|
+
'KUI-L021',
|
|
106
|
+
'KUI-L022',
|
|
57
107
|
]);
|
|
58
108
|
|
|
59
109
|
const sourceExtensions = new Set(['.js', '.jsx', '.mjs', '.ts', '.tsx']);
|
|
60
110
|
const spacingProperties = /^(?:margin|padding|gap|inset)(?:-|$)/;
|
|
61
|
-
const dimensionProperties =
|
|
62
|
-
/^(?:width|height|min-width|max-width|min-height|max-height)$/;
|
|
63
111
|
const approvedSpacing = new Set([0, 4, 8, 16, 24]);
|
|
64
112
|
async function collectFiles(root, paths) {
|
|
65
113
|
const files = [];
|
|
@@ -177,6 +225,7 @@ function reachableStyleFacts(file, imports, styleFacts) {
|
|
|
177
225
|
const current = result.get(className) ?? { scroll: false, inset: false };
|
|
178
226
|
current.scroll ||= facts.scroll;
|
|
179
227
|
current.inset ||= facts.inset;
|
|
228
|
+
current.styled ??= facts.styled;
|
|
180
229
|
result.set(className, current);
|
|
181
230
|
}
|
|
182
231
|
}
|
|
@@ -326,6 +375,7 @@ function catalogFacts(entries) {
|
|
|
326
375
|
publicParts,
|
|
327
376
|
classEntries,
|
|
328
377
|
exportEntries,
|
|
378
|
+
ownership: componentOwnershipFacts(entries),
|
|
329
379
|
};
|
|
330
380
|
}
|
|
331
381
|
|
|
@@ -342,6 +392,121 @@ function escapeRegExp(value) {
|
|
|
342
392
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
343
393
|
}
|
|
344
394
|
|
|
395
|
+
// A declaration that restyles, as opposed to one that only sets a custom
|
|
396
|
+
// property: setting a component's public token on its host is configuration,
|
|
397
|
+
// which the token rules (KUI-L002, KUI-L012, KUI-L020, KUI-L021) judge.
|
|
398
|
+
function restylingDeclarations(rule) {
|
|
399
|
+
return (rule.nodes ?? []).filter(
|
|
400
|
+
(node) => node.type === 'decl' && !node.prop.startsWith('--'),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function inKeyframes(rule) {
|
|
405
|
+
return rule.parent?.type === 'atrule' && /keyframes$/i.test(rule.parent.name);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// The classes a rule makes its subject (the rightmost compound of each complex
|
|
409
|
+
// selector, outside :has()/:not()).
|
|
410
|
+
function subjectClasses(selector) {
|
|
411
|
+
return complexSelectorParts(selector).flatMap((parts) => {
|
|
412
|
+
const subject = parts.at(-1)?.compound;
|
|
413
|
+
return subject ? classNames(withoutRelationalArguments(subject)) : [];
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function inspectComponentOwnership(
|
|
418
|
+
file,
|
|
419
|
+
rule,
|
|
420
|
+
facts,
|
|
421
|
+
diagnostics,
|
|
422
|
+
adoption,
|
|
423
|
+
isForeign,
|
|
424
|
+
) {
|
|
425
|
+
if (inKeyframes(rule)) return;
|
|
426
|
+
const restyling = restylingDeclarations(rule);
|
|
427
|
+
if (restyling.length === 0) return;
|
|
428
|
+
const properties = [...new Set(restyling.map((decl) => decl.prop))];
|
|
429
|
+
for (const { entry, via, name } of restyledComponents(
|
|
430
|
+
rule.selector,
|
|
431
|
+
facts.ownership,
|
|
432
|
+
isForeign,
|
|
433
|
+
))
|
|
434
|
+
diagnostics.push(
|
|
435
|
+
diagnostic(
|
|
436
|
+
'KUI-L019',
|
|
437
|
+
location(file, rule),
|
|
438
|
+
`\`${name}\` makes ${componentLabel(entry)} the subject of an application rule (${properties.join(', ')}); components own their styles. Configure it through ${configurationFor(entry)}. To place your own content in its context, style your own element (\`${name} > .your-element\`). ${reportGap(entry)}`,
|
|
439
|
+
{
|
|
440
|
+
selector: rule.selector,
|
|
441
|
+
component: entry.key,
|
|
442
|
+
via,
|
|
443
|
+
target: name,
|
|
444
|
+
properties,
|
|
445
|
+
},
|
|
446
|
+
undefined,
|
|
447
|
+
adoption,
|
|
448
|
+
),
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function inspectVariableOwnership(
|
|
453
|
+
file,
|
|
454
|
+
decl,
|
|
455
|
+
facts,
|
|
456
|
+
diagnostics,
|
|
457
|
+
adoption,
|
|
458
|
+
isForeign,
|
|
459
|
+
) {
|
|
460
|
+
const at = location(file, decl);
|
|
461
|
+
const privates = new Set([
|
|
462
|
+
...(decl.prop.match(/--_[a-z0-9_-]+/gi) ?? []),
|
|
463
|
+
...(decl.value.match(/--_[a-z0-9_-]+/gi) ?? []),
|
|
464
|
+
]);
|
|
465
|
+
for (const variable of privates) {
|
|
466
|
+
const owner = privateVariableOwner(variable, facts.ownership);
|
|
467
|
+
if (owner === undefined || (owner && !isForeign(owner))) continue;
|
|
468
|
+
const written = decl.prop === variable;
|
|
469
|
+
diagnostics.push(
|
|
470
|
+
diagnostic(
|
|
471
|
+
'KUI-L020',
|
|
472
|
+
at,
|
|
473
|
+
`\`${variable}\` is ${owner ? `${componentName(owner)}'s` : "a Kerf component's"} private variable; ${written ? 'writing it overrides' : 'reading it couples to'} that component's implementation. Configure it through ${configurationFor(owner)}. ${reportGap(owner)}`,
|
|
474
|
+
{
|
|
475
|
+
property: decl.prop,
|
|
476
|
+
value: decl.value,
|
|
477
|
+
variable,
|
|
478
|
+
...(owner ? { component: owner.key } : {}),
|
|
479
|
+
access: written ? 'write' : 'read',
|
|
480
|
+
},
|
|
481
|
+
undefined,
|
|
482
|
+
adoption,
|
|
483
|
+
),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (!decl.prop.startsWith('--')) return;
|
|
487
|
+
const typed = typedPropForToken(decl.prop, facts.ownership);
|
|
488
|
+
if (!typed || !isForeign(typed.entry)) return;
|
|
489
|
+
const contract = (typed.entry.cssValueProps ?? []).find(
|
|
490
|
+
({ path }) => path === typed.path,
|
|
491
|
+
);
|
|
492
|
+
const example = contract?.examples?.[0];
|
|
493
|
+
diagnostics.push(
|
|
494
|
+
diagnostic(
|
|
495
|
+
'KUI-L021',
|
|
496
|
+
at,
|
|
497
|
+
`\`${decl.prop}\` is what ${typed.entry.name}'s typed \`${typed.path}\` prop sets; set the prop on ${typed.entry.name}${example ? ` (\`<${typed.entry.name} ${example} />\`)` : ''} instead of overriding the token. ${reportGap(typed.entry)}`,
|
|
498
|
+
{
|
|
499
|
+
property: decl.prop,
|
|
500
|
+
value: decl.value,
|
|
501
|
+
component: typed.entry.key,
|
|
502
|
+
prop: typed.path,
|
|
503
|
+
},
|
|
504
|
+
undefined,
|
|
505
|
+
adoption,
|
|
506
|
+
),
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
345
510
|
async function inspectCss(
|
|
346
511
|
file,
|
|
347
512
|
source,
|
|
@@ -350,6 +515,7 @@ async function inspectCss(
|
|
|
350
515
|
cssFacts,
|
|
351
516
|
adoption = false,
|
|
352
517
|
siblingOnLoud = [],
|
|
518
|
+
isForeign = () => true,
|
|
353
519
|
) {
|
|
354
520
|
let root;
|
|
355
521
|
try {
|
|
@@ -374,6 +540,27 @@ async function inspectCss(
|
|
|
374
540
|
siblingOnLoud,
|
|
375
541
|
);
|
|
376
542
|
root.walkRules((rule) => {
|
|
543
|
+
inspectComponentOwnership(
|
|
544
|
+
file,
|
|
545
|
+
rule,
|
|
546
|
+
facts,
|
|
547
|
+
diagnostics,
|
|
548
|
+
adoption,
|
|
549
|
+
isForeign,
|
|
550
|
+
);
|
|
551
|
+
if (!inKeyframes(rule) && restylingDeclarations(rule).length > 0)
|
|
552
|
+
for (const className of subjectClasses(rule.selector)) {
|
|
553
|
+
const record = cssFacts.get(className) ?? {
|
|
554
|
+
scroll: false,
|
|
555
|
+
inset: false,
|
|
556
|
+
};
|
|
557
|
+
record.styled ??= {
|
|
558
|
+
stylesheet: file,
|
|
559
|
+
line: rule.source?.start?.line ?? 1,
|
|
560
|
+
selector: rule.selector,
|
|
561
|
+
};
|
|
562
|
+
cssFacts.set(className, record);
|
|
563
|
+
}
|
|
377
564
|
const classes = [
|
|
378
565
|
...rule.selector.matchAll(/\.([_a-zA-Z]+[_a-zA-Z0-9-]*)/g),
|
|
379
566
|
].map((match) => match[1]);
|
|
@@ -444,6 +631,14 @@ async function inspectCss(
|
|
|
444
631
|
);
|
|
445
632
|
}
|
|
446
633
|
rule.walkDecls((decl) => {
|
|
634
|
+
inspectVariableOwnership(
|
|
635
|
+
file,
|
|
636
|
+
decl,
|
|
637
|
+
facts,
|
|
638
|
+
diagnostics,
|
|
639
|
+
adoption,
|
|
640
|
+
isForeign,
|
|
641
|
+
);
|
|
447
642
|
const at = location(file, decl);
|
|
448
643
|
const tokens = [
|
|
449
644
|
...(decl.prop.match(/--kui-[a-z0-9-]+/g) ?? []),
|
|
@@ -496,22 +691,6 @@ async function inspectCss(
|
|
|
496
691
|
if (/^padding(?:-|$)/.test(decl.prop) && decl.value !== '0')
|
|
497
692
|
record.inset = true;
|
|
498
693
|
cssFacts.set(className, record);
|
|
499
|
-
if (
|
|
500
|
-
facts.publicClasses.has(className) &&
|
|
501
|
-
dimensionProperties.test(decl.prop)
|
|
502
|
-
)
|
|
503
|
-
diagnostics.push(
|
|
504
|
-
diagnostic(
|
|
505
|
-
'KUI-L005',
|
|
506
|
-
at,
|
|
507
|
-
`.${className} forces ${decl.prop}; prefer the component's public sizing contract.`,
|
|
508
|
-
{
|
|
509
|
-
selector: rule.selector,
|
|
510
|
-
property: decl.prop,
|
|
511
|
-
value: decl.value,
|
|
512
|
-
},
|
|
513
|
-
),
|
|
514
|
-
);
|
|
515
694
|
}
|
|
516
695
|
});
|
|
517
696
|
});
|
|
@@ -696,7 +875,15 @@ function inspectCssValues(
|
|
|
696
875
|
}
|
|
697
876
|
}
|
|
698
877
|
|
|
699
|
-
function inspectTsx(
|
|
878
|
+
function inspectTsx(
|
|
879
|
+
file,
|
|
880
|
+
sourceText,
|
|
881
|
+
facts,
|
|
882
|
+
cssFacts,
|
|
883
|
+
diagnostics,
|
|
884
|
+
adoption = false,
|
|
885
|
+
isForeign = () => true,
|
|
886
|
+
) {
|
|
700
887
|
const source = ts.createSourceFile(
|
|
701
888
|
file,
|
|
702
889
|
sourceText,
|
|
@@ -769,6 +956,29 @@ function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
|
|
|
769
956
|
namespaces,
|
|
770
957
|
diagnostics,
|
|
771
958
|
);
|
|
959
|
+
if (entry && isForeign(entry))
|
|
960
|
+
for (const className of classes.values) {
|
|
961
|
+
if (facts.ownership.classOwners.has(className)) continue;
|
|
962
|
+
const styled = cssFacts.get(className)?.styled;
|
|
963
|
+
if (!styled) continue;
|
|
964
|
+
diagnostics.push(
|
|
965
|
+
diagnostic(
|
|
966
|
+
'KUI-L022',
|
|
967
|
+
at,
|
|
968
|
+
`\`${className}\` is placed on ${entry.name}'s root and styled by ${basename(styled.stylesheet)}:${styled.line} (\`${styled.selector}\`), which restyles ${componentLabel(entry)}; components own their styles. Configure it through ${configurationFor(entry)}, or wrap it in your own element and style that. ${reportGap(entry)}`,
|
|
969
|
+
{
|
|
970
|
+
tag,
|
|
971
|
+
component: entry.key,
|
|
972
|
+
className,
|
|
973
|
+
stylesheet: styled.stylesheet,
|
|
974
|
+
line: styled.line,
|
|
975
|
+
selector: styled.selector,
|
|
976
|
+
},
|
|
977
|
+
undefined,
|
|
978
|
+
adoption,
|
|
979
|
+
),
|
|
980
|
+
);
|
|
981
|
+
}
|
|
772
982
|
if (classes.dynamic)
|
|
773
983
|
diagnostics.push(
|
|
774
984
|
diagnostic(
|
|
@@ -932,6 +1142,39 @@ export async function analyzeUiProject({
|
|
|
932
1142
|
const imports = new Map(
|
|
933
1143
|
files.map((file) => [file, relativeStyleImports(file, contents.get(file))]),
|
|
934
1144
|
);
|
|
1145
|
+
// A stylesheet or source file belongs to the package whose manifest is
|
|
1146
|
+
// nearest it; entries of every other catalog package are foreign to it.
|
|
1147
|
+
const packageNames = new Map();
|
|
1148
|
+
const packageNameOf = async (directory) => {
|
|
1149
|
+
if (!packageNames.has(directory))
|
|
1150
|
+
packageNames.set(
|
|
1151
|
+
directory,
|
|
1152
|
+
(async () => {
|
|
1153
|
+
try {
|
|
1154
|
+
const manifest = JSON.parse(
|
|
1155
|
+
await readFile(resolve(directory, 'package.json'), 'utf8'),
|
|
1156
|
+
);
|
|
1157
|
+
if (typeof manifest.name === 'string') return manifest.name;
|
|
1158
|
+
} catch {
|
|
1159
|
+
// No readable manifest here; keep walking up.
|
|
1160
|
+
}
|
|
1161
|
+
const parent = dirname(directory);
|
|
1162
|
+
const inside = relative(root, parent);
|
|
1163
|
+
if (
|
|
1164
|
+
parent === directory ||
|
|
1165
|
+
inside === '..' ||
|
|
1166
|
+
inside.startsWith('../')
|
|
1167
|
+
)
|
|
1168
|
+
return undefined;
|
|
1169
|
+
return packageNameOf(parent);
|
|
1170
|
+
})(),
|
|
1171
|
+
);
|
|
1172
|
+
return packageNames.get(directory);
|
|
1173
|
+
};
|
|
1174
|
+
const foreignTo = async (file) => {
|
|
1175
|
+
const own = await packageNameOf(dirname(file));
|
|
1176
|
+
return (entry) => entry.package !== own;
|
|
1177
|
+
};
|
|
935
1178
|
const styleFacts = new Map();
|
|
936
1179
|
const recordDiagnostics = (items, context) => {
|
|
937
1180
|
for (const item of items) {
|
|
@@ -1022,6 +1265,8 @@ export async function analyzeUiProject({
|
|
|
1022
1265
|
context.facts,
|
|
1023
1266
|
reachableStyleFacts(file, imports, styleFacts),
|
|
1024
1267
|
fileDiagnostics,
|
|
1268
|
+
adoption,
|
|
1269
|
+
await foreignTo(file),
|
|
1025
1270
|
);
|
|
1026
1271
|
recordDiagnostics(fileDiagnostics, context);
|
|
1027
1272
|
}
|
|
@@ -1039,6 +1284,7 @@ export async function analyzeUiProject({
|
|
|
1039
1284
|
new Map(),
|
|
1040
1285
|
adoption,
|
|
1041
1286
|
siblingOnLoud(file),
|
|
1287
|
+
await foreignTo(file),
|
|
1042
1288
|
);
|
|
1043
1289
|
recordDiagnostics(fileDiagnostics, context);
|
|
1044
1290
|
}
|
|
@@ -1121,6 +1367,14 @@ export async function analyzeUiProject({
|
|
|
1121
1367
|
...item.location,
|
|
1122
1368
|
file: portablePath(item.location.file),
|
|
1123
1369
|
},
|
|
1370
|
+
...(item.evidence?.stylesheet
|
|
1371
|
+
? {
|
|
1372
|
+
evidence: {
|
|
1373
|
+
...item.evidence,
|
|
1374
|
+
stylesheet: portablePath(item.evidence.stylesheet),
|
|
1375
|
+
},
|
|
1376
|
+
}
|
|
1377
|
+
: {}),
|
|
1124
1378
|
})),
|
|
1125
1379
|
summary: {
|
|
1126
1380
|
errors:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface SelectorCompound {
|
|
2
|
+
combinator: '' | ' ' | '>' | '+' | '~';
|
|
3
|
+
compound: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function ownsClass(rootClass: string, candidate: string): boolean;
|
|
7
|
+
export function complexSelectorParts(
|
|
8
|
+
selectorList: string,
|
|
9
|
+
): SelectorCompound[][];
|
|
10
|
+
export function complexSelectors(selectorList: string): string[][];
|
|
11
|
+
export function kuiClasses(selector: string): string[];
|
|
12
|
+
export function classNames(selector: string): string[];
|
|
13
|
+
export function dataComponents(selector: string): string[];
|
|
14
|
+
export function withoutPseudoArguments(
|
|
15
|
+
compound: string,
|
|
16
|
+
names: string[],
|
|
17
|
+
): string;
|
|
18
|
+
export function withoutRelationalArguments(compound: string): string;
|
|
19
|
+
export function pseudoArguments(compound: string, name: string): string[];
|
|
20
|
+
export function splitTopLevel(list: string): string[];
|
|
21
|
+
export function subjectTypes(compound: string): string[] | null;
|