@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.
Files changed (46) hide show
  1. package/ai/component-catalog.json +21 -5
  2. package/ai/component-catalog.schema.json +2 -0
  3. package/ai/component-composition-rules.json +4 -4
  4. package/ai/component-composition.d.ts +2 -0
  5. package/ai/component-composition.json +23 -5
  6. package/ai/component-composition.schema.json +9 -0
  7. package/ai/components/toolbar-text.md +3 -1
  8. package/ai/components/toolbar.md +6 -6
  9. package/ai/public-api-signatures-v1.md +16 -11
  10. package/ai/skill.md +1 -1
  11. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  12. package/analyzer/component-ownership.mjs +131 -16
  13. package/analyzer/index.d.mts +2 -1
  14. package/analyzer/index.mjs +298 -29
  15. package/dist/catalog.js +2 -2
  16. package/dist/{chunk-KCCEH7RF.js → chunk-3UE24JSC.js} +2 -2
  17. package/dist/{chunk-KCCEH7RF.js.map → chunk-3UE24JSC.js.map} +1 -1
  18. package/dist/{chunk-V3DX3K65.js → chunk-R5F5XUJ7.js} +19 -2
  19. package/dist/chunk-R5F5XUJ7.js.map +1 -0
  20. package/dist/{chunk-T2E5WBCZ.js → chunk-SLWCCHNP.js} +11 -2
  21. package/dist/chunk-SLWCCHNP.js.map +1 -0
  22. package/dist/{chunk-DC6CN2DS.js → chunk-VLYIPBCQ.js} +2 -2
  23. package/dist/{chunk-DC6CN2DS.js.map → chunk-VLYIPBCQ.js.map} +1 -1
  24. package/dist/index.js +2 -2
  25. package/dist/nav-stack.js +1 -1
  26. package/dist/split-view.js +1 -1
  27. package/dist/styles/app-tab.css +3 -0
  28. package/dist/styles/chip.css +6 -0
  29. package/dist/styles/toolbar-text.css +20 -0
  30. package/dist/styles/toolbar.css +10 -1
  31. package/dist/toolbar-text.d.ts +15 -10
  32. package/dist/toolbar-text.js +1 -1
  33. package/dist/wire-tab-bars.js +1 -1
  34. package/dist/workbench.js +1 -1
  35. package/docs/accessibility.md +5 -0
  36. package/docs/component-contract.md +17 -2
  37. package/docs/component-selection.md +27 -16
  38. package/docs/layout.md +4 -0
  39. package/docs/ui-analyzer.md +42 -5
  40. package/docs/ui-doctor.md +28 -3
  41. package/doctor/config.schema.json +23 -1
  42. package/doctor/index.d.mts +2 -1
  43. package/doctor/index.mjs +42 -2
  44. package/package.json +1 -1
  45. package/dist/chunk-T2E5WBCZ.js.map +0 -1
  46. package/dist/chunk-V3DX3K65.js.map +0 -1
@@ -20,6 +20,7 @@ import {
20
20
  componentOwnershipFacts,
21
21
  contextualComponents,
22
22
  configurationFor,
23
+ ownsClassBlock,
23
24
  privateVariableOwner,
24
25
  reportGap,
25
26
  restyledComponents,
@@ -114,6 +115,13 @@ const adoptionRules = new Set([
114
115
  ]);
115
116
 
116
117
  const sourceExtensions = new Set(['.js', '.jsx', '.mjs', '.ts', '.tsx']);
118
+ function isTestModule(file) {
119
+ const path = file.replaceAll('\\', '/');
120
+ return (
121
+ /(?:^|\/)__tests__\//.test(path) ||
122
+ /\.(?:test|spec)\.(?:js|jsx|mjs|ts|tsx)$/.test(path)
123
+ );
124
+ }
117
125
  const spacingProperties = /^(?:margin|padding|gap|inset)(?:-|$)/;
118
126
  const approvedSpacing = new Set([0, 4, 8, 16, 24]);
119
127
  async function collectFiles(root, paths) {
@@ -441,6 +449,7 @@ function catalogFacts(entries) {
441
449
  const publicTokens = new Set();
442
450
  const publicParts = new Map();
443
451
  const classEntries = new Map();
452
+ const blockEntries = new Map();
444
453
  const exportEntries = new Map();
445
454
  const addExport = (name, entry) => {
446
455
  const candidates = exportEntries.get(name) ?? [];
@@ -453,11 +462,20 @@ function catalogFacts(entries) {
453
462
  const name = typeof item === 'string' ? item : item.name;
454
463
  if (isComponentExport(name)) addExport(name, entry);
455
464
  }
456
- for (const className of entry.boundaries?.publicClasses ?? []) {
465
+ for (const className of new Set(
466
+ [
467
+ entry.boundaries?.rootClass,
468
+ ...(entry.boundaries?.publicClasses ?? []),
469
+ ].filter(Boolean),
470
+ )) {
457
471
  publicClasses.add(className);
458
472
  const owners = classEntries.get(className) ?? [];
459
473
  owners.push(entry);
460
474
  classEntries.set(className, owners);
475
+ const block = bemBlock(className);
476
+ const blockOwners = blockEntries.get(block) ?? [];
477
+ if (!blockOwners.includes(entry)) blockOwners.push(entry);
478
+ blockEntries.set(block, blockOwners);
461
479
  }
462
480
  for (const token of entry.boundaries?.publicTokens ?? [])
463
481
  publicTokens.add(token);
@@ -473,6 +491,7 @@ function catalogFacts(entries) {
473
491
  publicTokens,
474
492
  publicParts,
475
493
  classEntries,
494
+ blockEntries,
476
495
  exportEntries,
477
496
  ownership: componentOwnershipFacts(entries),
478
497
  };
@@ -553,6 +572,46 @@ function inferOwnedClasses(entries, contents, imports) {
553
572
  });
554
573
  }
555
574
 
575
+ async function ownershipGroupEntries(groups, root, contents) {
576
+ if (!groups?.length) return [];
577
+ const manifest = JSON.parse(
578
+ await readFile(resolve(root, 'package.json'), 'utf8'),
579
+ );
580
+ return Promise.all(
581
+ groups.map(async (group, index) => {
582
+ const classes = new Set();
583
+ for (const style of group.styleSources) {
584
+ const file = resolve(root, style);
585
+ let css = contents.get(file);
586
+ if (css === undefined)
587
+ try {
588
+ css = await readFile(file, 'utf8');
589
+ } catch {
590
+ // A missing configured file owns no classes.
591
+ }
592
+ if (!css) continue;
593
+ try {
594
+ postcss.parse(css, { from: file }).walkRules((rule) => {
595
+ for (const name of subjectClasses(rule.selector)) classes.add(name);
596
+ });
597
+ } catch {
598
+ // The normal stylesheet pass reports malformed CSS.
599
+ }
600
+ }
601
+ return {
602
+ key: `${manifest.name}:ownership-group-${index + 1}`,
603
+ package: manifest.name,
604
+ id: `ownership-group-${index + 1}`,
605
+ name: `Ownership group ${index + 1}`,
606
+ catalogDirectory: root,
607
+ styleSources: group.styleSources,
608
+ ownershipGroupSources: group.sources,
609
+ boundaries: { publicClasses: [...classes] },
610
+ };
611
+ }),
612
+ );
613
+ }
614
+
556
615
  async function implicitOwnershipEntries(files, imports, root) {
557
616
  const found = [];
558
617
  const packages = new Map();
@@ -662,7 +721,7 @@ function inspectComponentOwnership(
662
721
  'KUI-L019',
663
722
  location(file, rule),
664
723
  via === 'descendant'
665
- ? `\`${name}\` reaches an unclassed descendant inside ${componentLabel(entry)} (${properties.join(', ')}); components own their styles. Configure it through ${configurationFor(entry)}. To place your own content in its context, style your own element. ${reportGap(entry)}`
724
+ ? `\`${name}\` reaches an unclassed descendant inside ${componentLabel(entry)} (${properties.join(', ')}); components own their styles. Use a child combinator or an own class to target your element, or configure the child through ${configurationFor(entry)}. ${reportGap(entry)}`
666
725
  : `\`${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)}`,
667
726
  {
668
727
  selector: rule.selector,
@@ -675,12 +734,13 @@ function inspectComponentOwnership(
675
734
  adoption,
676
735
  ),
677
736
  );
678
- if (ownershipContext !== 'any' || !ownershipEvidence?.ownPackage) return;
737
+ if (ownershipContext === 'subject' || !ownershipEvidence?.ownPackage) return;
679
738
  for (const { entry, name, position } of contextualComponents(
680
739
  rule.selector,
681
740
  facts.ownership,
682
741
  isForeign,
683
742
  ownershipEvidence.ownPackage,
743
+ ownershipContext === 'any-package',
684
744
  )) {
685
745
  if (position === 'subject') continue;
686
746
  if (
@@ -693,7 +753,7 @@ function inspectComponentOwnership(
693
753
  diagnostic(
694
754
  'KUI-L019',
695
755
  location(file, rule),
696
- `\`${name}\` uses ${componentLabel(entry)} as ${position} selector context (${properties.join(', ')}); in strict component ownership, sibling classes cannot key this stylesheet. Configure the component through ${configurationFor(entry)}. ${reportGap(entry)}`,
756
+ `\`${name}\` uses ${componentLabel(entry)} as ${position} selector context (${properties.join(', ')}); foreign component handles cannot key this stylesheet in this ownership mode. Configure the component through ${configurationFor(entry)}. ${reportGap(entry)}`,
697
757
  {
698
758
  selector: rule.selector,
699
759
  component: entry.key,
@@ -995,8 +1055,10 @@ function literalClassValues(expression) {
995
1055
  function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
996
1056
  const hooks = new Map();
997
1057
  const composedChildren = new Map();
1058
+ const composedDescendants = new Map();
998
1059
  const ambiguousHooks = new Set();
999
1060
  const ambiguousChildren = new Set();
1061
+ const ambiguousDescendants = new Set();
1000
1062
  const record = (map, ambiguous, key, entry) => {
1001
1063
  if (ambiguous.has(key)) return;
1002
1064
  if (map.has(key) && map.get(key).key !== entry.key) {
@@ -1021,6 +1083,27 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
1021
1083
  true,
1022
1084
  ts.ScriptKind.TSX,
1023
1085
  );
1086
+ const localComponents = new Map();
1087
+ for (const statement of source.statements) {
1088
+ if (
1089
+ ts.isFunctionDeclaration(statement) &&
1090
+ statement.name &&
1091
+ statement.body
1092
+ )
1093
+ localComponents.set(statement.name.text, statement.body);
1094
+ if (!ts.isVariableStatement(statement)) continue;
1095
+ for (const declaration of statement.declarationList.declarations)
1096
+ if (
1097
+ ts.isIdentifier(declaration.name) &&
1098
+ declaration.initializer &&
1099
+ (ts.isArrowFunction(declaration.initializer) ||
1100
+ ts.isFunctionExpression(declaration.initializer))
1101
+ )
1102
+ localComponents.set(
1103
+ declaration.name.text,
1104
+ declaration.initializer.body,
1105
+ );
1106
+ }
1024
1107
  const imports = new Map();
1025
1108
  const namespaces = new Map();
1026
1109
  const compilerOptions = compilerOptionsFor(file, root, compilerCache);
@@ -1084,6 +1167,82 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
1084
1167
  ['class', 'className'].includes(item.name.getText(source)),
1085
1168
  ),
1086
1169
  ).values;
1170
+ const returnedJsx = (body) => {
1171
+ if (!ts.isBlock(body)) return [body];
1172
+ const expressions = [];
1173
+ const visitReturns = (node) => {
1174
+ if (ts.isReturnStatement(node)) {
1175
+ if (node.expression) expressions.push(node.expression);
1176
+ return;
1177
+ }
1178
+ if (
1179
+ node !== body &&
1180
+ (ts.isFunctionDeclaration(node) ||
1181
+ ts.isFunctionExpression(node) ||
1182
+ ts.isArrowFunction(node))
1183
+ )
1184
+ return;
1185
+ ts.forEachChild(node, visitReturns);
1186
+ };
1187
+ visitReturns(body);
1188
+ return expressions;
1189
+ };
1190
+ const localReturns = (opening, seen) => {
1191
+ const name = opening.tagName.getText(source);
1192
+ const body = localComponents.get(name);
1193
+ if (!body || seen.has(name)) return undefined;
1194
+ return { name, expressions: returnedJsx(body) };
1195
+ };
1196
+ const composedBelow = (node, found, seen = new Set()) => {
1197
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
1198
+ const opening = openingOf(node);
1199
+ const entry = entryFor(opening);
1200
+ if (entry) {
1201
+ for (const type of [
1202
+ entry.boundaries?.rootElement,
1203
+ ...(entry.boundaries?.descendantElements ?? []),
1204
+ ]) {
1205
+ if (!type) continue;
1206
+ const entries = found.get(type) ?? new Set();
1207
+ entries.add(entry);
1208
+ found.set(type, entries);
1209
+ }
1210
+ return;
1211
+ }
1212
+ const local = localReturns(opening, seen);
1213
+ if (local) {
1214
+ const next = new Set([...seen, local.name]);
1215
+ for (const expression of local.expressions)
1216
+ composedBelow(expression, found, next);
1217
+ return;
1218
+ }
1219
+ if (ts.isJsxElement(node))
1220
+ for (const child of node.children) composedBelow(child, found, seen);
1221
+ return;
1222
+ }
1223
+ if (ts.isJsxFragment(node)) {
1224
+ for (const child of node.children) composedBelow(child, found, seen);
1225
+ return;
1226
+ }
1227
+ ts.forEachChild(node, (child) => composedBelow(child, found, seen));
1228
+ };
1229
+ const expandedRoots = (node, seen = new Set()) => {
1230
+ if (ts.isJsxFragment(node))
1231
+ return node.children.flatMap((child) => expandedRoots(child, seen));
1232
+ if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) {
1233
+ const roots = [];
1234
+ ts.forEachChild(node, (child) =>
1235
+ roots.push(...expandedRoots(child, seen)),
1236
+ );
1237
+ return roots;
1238
+ }
1239
+ const local = localReturns(openingOf(node), seen);
1240
+ if (!local) return [node];
1241
+ const next = new Set([...seen, local.name]);
1242
+ return local.expressions.flatMap((expression) =>
1243
+ expandedRoots(expression, next),
1244
+ );
1245
+ };
1087
1246
  const visit = (node) => {
1088
1247
  if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
1089
1248
  const opening = openingOf(node);
@@ -1093,23 +1252,39 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
1093
1252
  record(hooks, ambiguousHooks, name, entry);
1094
1253
  if (ts.isJsxElement(node)) {
1095
1254
  const parentClasses = classesOf(opening);
1255
+ if (parentClasses.length) {
1256
+ const descendants = new Map();
1257
+ for (const child of node.children)
1258
+ composedBelow(child, descendants);
1259
+ for (const [type, entries] of descendants)
1260
+ for (const entry of entries)
1261
+ for (const name of parentClasses)
1262
+ record(
1263
+ composedDescendants,
1264
+ ambiguousDescendants,
1265
+ `${name}|${type}`,
1266
+ entry,
1267
+ );
1268
+ }
1096
1269
  const possible = new Map();
1097
1270
  let dynamic = false;
1098
1271
  for (const child of node.children) {
1099
1272
  if (ts.isJsxExpression(child) && child.expression) dynamic = true;
1100
1273
  if (!ts.isJsxElement(child) && !ts.isJsxSelfClosingElement(child))
1101
1274
  continue;
1102
- const childOpening = openingOf(child);
1103
- const childEntry = entryFor(childOpening);
1104
- const type =
1105
- childEntry?.boundaries?.rootElement ??
1106
- (childEntry
1107
- ? undefined
1108
- : childOpening.tagName.getText(source).toLowerCase());
1109
- if (!type) continue;
1110
- const candidates = possible.get(type) ?? [];
1111
- candidates.push(childEntry);
1112
- possible.set(type, candidates);
1275
+ for (const root of expandedRoots(child)) {
1276
+ const childOpening = openingOf(root);
1277
+ const childEntry = entryFor(childOpening);
1278
+ const type =
1279
+ childEntry?.boundaries?.rootElement ??
1280
+ (childEntry
1281
+ ? undefined
1282
+ : childOpening.tagName.getText(source).toLowerCase());
1283
+ if (!type) continue;
1284
+ const candidates = possible.get(type) ?? [];
1285
+ candidates.push(childEntry);
1286
+ possible.set(type, candidates);
1287
+ }
1113
1288
  }
1114
1289
  if (!dynamic)
1115
1290
  for (const [type, candidates] of possible)
@@ -1133,7 +1308,7 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
1133
1308
  };
1134
1309
  visit(source);
1135
1310
  }
1136
- return { componentMode: true, hooks, composedChildren };
1311
+ return { componentMode: true, hooks, composedChildren, composedDescendants };
1137
1312
  }
1138
1313
 
1139
1314
  function cssPreferred(contract) {
@@ -1308,6 +1483,7 @@ function inspectTsx(
1308
1483
  isForeign = () => true,
1309
1484
  packageDirectory,
1310
1485
  compilerOptions,
1486
+ componentMode = false,
1311
1487
  ) {
1312
1488
  const source = ts.createSourceFile(
1313
1489
  file,
@@ -1365,10 +1541,18 @@ function inspectTsx(
1365
1541
  }
1366
1542
  }
1367
1543
  const stack = [];
1544
+ const inspectBorrowedMarkup = !isTestModule(file);
1368
1545
  const borrowed = (className, node, via) => {
1369
- for (const owner of facts.classEntries.get(className) ?? []) {
1546
+ if (!inspectBorrowedMarkup) return;
1547
+ const owners =
1548
+ facts.classEntries.get(className) ??
1549
+ (componentMode && bemBlock(className) !== className
1550
+ ? facts.blockEntries.get(bemBlock(className))
1551
+ : undefined) ??
1552
+ [];
1553
+ for (const owner of owners) {
1370
1554
  if (
1371
- !isForeign(owner) ||
1555
+ !isForeign(owner, className) ||
1372
1556
  owner.boundaries?.placeableClasses?.includes(className)
1373
1557
  )
1374
1558
  continue;
@@ -1387,6 +1571,22 @@ function inspectTsx(
1387
1571
  const borrowedValues = (values, node, via) => {
1388
1572
  for (const className of new Set(values)) borrowed(className, node, via);
1389
1573
  };
1574
+ const templateGap = '\uE000';
1575
+ const rawHtmlClasses = (value) => {
1576
+ const classes = [];
1577
+ for (const match of value.matchAll(
1578
+ /<[a-z][^>]*\sclass\s*=\s*["']([^"']+)["']/gi,
1579
+ )) {
1580
+ const pieces = match[1].split(templateGap);
1581
+ for (const [index, piece] of pieces.entries()) {
1582
+ const words = piece.split(/\s+/).filter(Boolean);
1583
+ if (index > 0 && !/^\s/.test(piece)) words.shift();
1584
+ if (index < pieces.length - 1 && !/\s$/.test(piece)) words.pop();
1585
+ classes.push(...words);
1586
+ }
1587
+ }
1588
+ return classes;
1589
+ };
1390
1590
  const visit = (node) => {
1391
1591
  if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
1392
1592
  const opening = ts.isJsxElement(node) ? node.openingElement : node;
@@ -1411,7 +1611,7 @@ function inspectTsx(
1411
1611
  );
1412
1612
  const classes = literalClasses(classAttribute);
1413
1613
  const at = location(file, opening, source);
1414
- if (!entry && /^[a-z]/.test(tag))
1614
+ if ((!entry && /^[a-z]/.test(tag)) || (componentMode && entry))
1415
1615
  borrowedValues(
1416
1616
  classAttribute?.initializer &&
1417
1617
  ts.isJsxExpression(classAttribute.initializer)
@@ -1432,7 +1632,8 @@ function inspectTsx(
1432
1632
  );
1433
1633
  if (entry && isForeign(entry))
1434
1634
  for (const className of classes.values) {
1435
- if (facts.ownership.classOwners.has(className)) continue;
1635
+ if (ownsClassBlock(facts.ownership, className, componentMode))
1636
+ continue;
1436
1637
  const styled = cssFacts.get(className)?.styled;
1437
1638
  if (!styled) continue;
1438
1639
  diagnostics.push(
@@ -1585,11 +1786,15 @@ function inspectTsx(
1585
1786
  node.right,
1586
1787
  'className assignment',
1587
1788
  );
1588
- if (ts.isStringLiteralLike(node) && node.text.includes('<')) {
1589
- for (const match of node.text.matchAll(
1590
- /<[a-z][^>]*\sclass\s*=\s*["']([^"']+)["']/gi,
1591
- ))
1592
- borrowedValues(match[1].split(/\s+/).filter(Boolean), node, 'raw HTML');
1789
+ if (ts.isStringLiteralLike(node) && node.text.includes('<'))
1790
+ borrowedValues(rawHtmlClasses(node.text), node, 'raw HTML');
1791
+ if (ts.isTemplateExpression(node)) {
1792
+ const staticHtml = [
1793
+ node.head.text,
1794
+ ...node.templateSpans.map((span) => span.literal.text),
1795
+ ].join(templateGap);
1796
+ if (staticHtml.includes('<'))
1797
+ borrowedValues(rawHtmlClasses(staticHtml), node, 'raw HTML');
1593
1798
  }
1594
1799
  ts.forEachChild(node, visit);
1595
1800
  };
@@ -1604,6 +1809,7 @@ export async function analyzeUiProject({
1604
1809
  ownership = 'package',
1605
1810
  ownershipContext = 'subject',
1606
1811
  implicitComponentOwnership = false,
1812
+ ownershipGroups = [],
1607
1813
  profile: packageProfile = resolve(
1608
1814
  import.meta.dirname,
1609
1815
  '../ai/application-ui-profile.defaults.json',
@@ -1646,6 +1852,10 @@ export async function analyzeUiProject({
1646
1852
  ownership === 'component' && implicitComponentOwnership
1647
1853
  ? await implicitOwnershipEntries(files, imports, root)
1648
1854
  : [];
1855
+ const groupEntries =
1856
+ ownership === 'component'
1857
+ ? await ownershipGroupEntries(ownershipGroups, root, contents)
1858
+ : [];
1649
1859
  const profileContexts = new Map();
1650
1860
  const loadContext = async (file) => {
1651
1861
  const startDirectory = file === root ? root : dirname(file);
@@ -1669,6 +1879,7 @@ export async function analyzeUiProject({
1669
1879
  );
1670
1880
  const ownershipEntries = [
1671
1881
  ...entries,
1882
+ ...groupEntries,
1672
1883
  ...implicitEntries.filter(
1673
1884
  (entry) =>
1674
1885
  !catalogedSources.has(
@@ -1726,12 +1937,69 @@ export async function analyzeUiProject({
1726
1937
  );
1727
1938
  return packages.get(directory);
1728
1939
  };
1729
- const foreignTo = async (file) => {
1940
+ const styledBlocksByFile = new Map();
1941
+ const styledBlocksIn = (style) => {
1942
+ if (!styledBlocksByFile.has(style)) {
1943
+ const blocks = new Set();
1944
+ const css = contents.get(style);
1945
+ if (css)
1946
+ try {
1947
+ postcss.parse(css, { from: style }).walkRules((rule) => {
1948
+ for (const name of subjectClasses(rule.selector))
1949
+ blocks.add(bemBlock(name));
1950
+ });
1951
+ } catch {
1952
+ // Malformed CSS is reported by the normal inspection pass.
1953
+ }
1954
+ styledBlocksByFile.set(style, blocks);
1955
+ }
1956
+ return styledBlocksByFile.get(style);
1957
+ };
1958
+ const foreignTo = async (file, facts) => {
1730
1959
  const own = await packageOf(dirname(file));
1731
1960
  const source = own && relative(own.directory, file).replaceAll('\\', '/');
1732
- return (entry) => {
1961
+ const ownedStyles = new Set(imports.get(file) ?? []);
1962
+ if (ownership === 'component' && !file.endsWith('.css'))
1963
+ for (const entry of facts.entries) {
1964
+ if (
1965
+ entry.package !== own?.name ||
1966
+ !entry.componentSource ||
1967
+ !entry.catalogDirectory ||
1968
+ resolve(entry.catalogDirectory, entry.componentSource) !== file
1969
+ )
1970
+ continue;
1971
+ for (const style of entry.styleSources ?? [])
1972
+ ownedStyles.add(resolve(entry.catalogDirectory, style));
1973
+ }
1974
+ return (entry, className) => {
1733
1975
  if (entry.package !== own?.name) return true;
1734
1976
  if (ownership !== 'component') return false;
1977
+ if (entry.ownershipGroupSources) {
1978
+ const relativeFile = relative(entry.catalogDirectory, file).replaceAll(
1979
+ '\\',
1980
+ '/',
1981
+ );
1982
+ if (file.endsWith('.css'))
1983
+ return !entry.styleSources?.includes(relativeFile);
1984
+ return !entry.ownershipGroupSources.some((path) =>
1985
+ path.endsWith('/')
1986
+ ? relativeFile.startsWith(path)
1987
+ : relativeFile === path,
1988
+ );
1989
+ }
1990
+ if (
1991
+ !file.endsWith('.css') &&
1992
+ className &&
1993
+ entry.catalogDirectory &&
1994
+ entry.styleSources?.some((style) => {
1995
+ const path = resolve(entry.catalogDirectory, style);
1996
+ return (
1997
+ ownedStyles.has(path) &&
1998
+ styledBlocksIn(path).has(bemBlock(className))
1999
+ );
2000
+ })
2001
+ )
2002
+ return false;
1735
2003
  const ownedSources = file.endsWith('.css')
1736
2004
  ? entry.styleSources
1737
2005
  : [entry.componentSource];
@@ -1830,9 +2098,10 @@ export async function analyzeUiProject({
1830
2098
  reachableStyleFacts(file, imports, styleFacts),
1831
2099
  fileDiagnostics,
1832
2100
  adoption,
1833
- await foreignTo(file),
2101
+ await foreignTo(file, context.facts),
1834
2102
  own?.directory,
1835
2103
  compilerOptionsFor(file, root, compilerOptions),
2104
+ ownership === 'component',
1836
2105
  );
1837
2106
  recordDiagnostics(fileDiagnostics, context);
1838
2107
  }
@@ -1851,7 +2120,7 @@ export async function analyzeUiProject({
1851
2120
  new Map(),
1852
2121
  adoption,
1853
2122
  siblingOnLoud(file),
1854
- await foreignTo(file),
2123
+ await foreignTo(file, context.facts),
1855
2124
  ownership === 'component'
1856
2125
  ? {
1857
2126
  ...jsxOwnershipEvidence(
package/dist/catalog.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Workbench } from './chunk-KCCEH7RF.js';
1
+ import { Workbench } from './chunk-3UE24JSC.js';
2
2
 
3
3
 
4
4
  import { PopupMenu } from './chunk-J3KGJ6P6.js';
@@ -8,7 +8,7 @@ import { ListHeader } from './chunk-M6DEEBEF.js';
8
8
  import { Text } from './chunk-YDGNU2OD.js';
9
9
  import { ListInsetText } from './chunk-DG6G5EPF.js';
10
10
  import { ListItem } from './chunk-NC4CFOID.js';
11
- import { ToolbarText } from './chunk-V3DX3K65.js';
11
+ import { ToolbarText } from './chunk-R5F5XUJ7.js';
12
12
 
13
13
 
14
14
 
@@ -1,5 +1,5 @@
1
1
  import { WORKBENCH_MAIN_MIN_SIZE, WORKBENCH_MAIN_MIN_HEIGHT, workbenchRegionId, WORKBENCH_RESIZE_DEFAULTS } from './chunk-PSHUSWQH.js';
2
- import { NavStack } from './chunk-DC6CN2DS.js';
2
+ import { NavStack } from './chunk-VLYIPBCQ.js';
3
3
  import { clampRegionSize, ResizeGrip } from './chunk-KLJKU6ZL.js';
4
4
  import { FloatingToolbar } from './chunk-I3Q7KKYM.js';
5
5
  import { panelToggleGroup, composedPanelBody, relocatedPanelGroups } from './chunk-RNQNEENY.js';
@@ -489,4 +489,4 @@ function Workbench({
489
489
 
490
490
  export { Workbench };
491
491
 
492
- //# sourceMappingURL=chunk-KCCEH7RF.js.map
492
+ //# sourceMappingURL=chunk-3UE24JSC.js.map