@schemyx/mcp 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/codebase-scanner/bundle.d.ts +241 -7
- package/dist/codebase-scanner/bundle.js +3127 -109
- package/dist/codebase-scanner/bundle.js.map +1 -1
- package/dist/codebase-scanner/constants.d.ts +3 -3
- package/dist/codebase-scanner/constants.js +3 -2
- package/dist/codebase-scanner/constants.js.map +1 -1
- package/dist/codebase-scanner/database.d.ts +8 -0
- package/dist/codebase-scanner/database.js +1252 -0
- package/dist/codebase-scanner/database.js.map +1 -0
- package/dist/codebase-scanner/extractors.d.ts +55 -1
- package/dist/codebase-scanner/extractors.js +931 -18
- package/dist/codebase-scanner/extractors.js.map +1 -1
- package/dist/codebase-scanner/files.js +18 -1
- package/dist/codebase-scanner/files.js.map +1 -1
- package/dist/codebase-scanner/index.d.ts +2 -2
- package/dist/codebase-scanner/recipes.d.ts +2 -2
- package/dist/codebase-scanner/recipes.js +159 -1
- package/dist/codebase-scanner/recipes.js.map +1 -1
- package/dist/codebase-scanner/types.d.ts +221 -0
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ exports.extractModelFields = extractModelFields;
|
|
|
25
25
|
exports.extractCssVariables = extractCssVariables;
|
|
26
26
|
exports.extractCssVariableValues = extractCssVariableValues;
|
|
27
27
|
exports.extractCssRules = extractCssRules;
|
|
28
|
+
exports.extractCssAtRuleBlocks = extractCssAtRuleBlocks;
|
|
28
29
|
exports.extractCssClasses = extractCssClasses;
|
|
29
30
|
exports.extractCssIds = extractCssIds;
|
|
30
31
|
exports.extractCssVariableReferences = extractCssVariableReferences;
|
|
@@ -43,6 +44,9 @@ exports.readBalancedRange = readBalancedRange;
|
|
|
43
44
|
exports.skipQuoted = skipQuoted;
|
|
44
45
|
exports.isInsideQuotedString = isInsideQuotedString;
|
|
45
46
|
exports.extractClassListsFromStrings = extractClassListsFromStrings;
|
|
47
|
+
exports.readPrimaryClassHelperUsage = readPrimaryClassHelperUsage;
|
|
48
|
+
exports.readClassHelperUsages = readClassHelperUsages;
|
|
49
|
+
exports.parseHelperVariantProps = parseHelperVariantProps;
|
|
46
50
|
exports.analyzeClassSource = analyzeClassSource;
|
|
47
51
|
exports.toClassSourceAnalysis = toClassSourceAnalysis;
|
|
48
52
|
exports.isConditionalClassLiteral = isConditionalClassLiteral;
|
|
@@ -86,9 +90,20 @@ exports.isCompositionElement = isCompositionElement;
|
|
|
86
90
|
exports.filterClasses = filterClasses;
|
|
87
91
|
exports.isResponsiveClass = isResponsiveClass;
|
|
88
92
|
exports.isSpacingClass = isSpacingClass;
|
|
93
|
+
exports.isLayoutClass = isLayoutClass;
|
|
89
94
|
exports.isSurfaceClass = isSurfaceClass;
|
|
90
95
|
exports.isDecorativeAccentClass = isDecorativeAccentClass;
|
|
91
96
|
exports.isTypographyClass = isTypographyClass;
|
|
97
|
+
exports.isTypographyScaleBase = isTypographyScaleBase;
|
|
98
|
+
exports.isLayoutSafetyBase = isLayoutSafetyBase;
|
|
99
|
+
exports.isSizingClass = isSizingClass;
|
|
100
|
+
exports.isVisibilityClass = isVisibilityClass;
|
|
101
|
+
exports.isLineHeightClass = isLineHeightClass;
|
|
102
|
+
exports.isMaxWidthClass = isMaxWidthClass;
|
|
103
|
+
exports.createUiLayoutSafety = createUiLayoutSafety;
|
|
104
|
+
exports.createUiResponsiveProfile = createUiResponsiveProfile;
|
|
105
|
+
exports.createUiScaleProfile = createUiScaleProfile;
|
|
106
|
+
exports.responsivePrefix = responsivePrefix;
|
|
92
107
|
exports.groupClassExpressionsByTarget = groupClassExpressionsByTarget;
|
|
93
108
|
exports.buildComponentSlots = buildComponentSlots;
|
|
94
109
|
exports.createUiCompositionGuidance = createUiCompositionGuidance;
|
|
@@ -97,6 +112,28 @@ exports.extractTailwindUtilities = extractTailwindUtilities;
|
|
|
97
112
|
exports.extractPackageDependencies = extractPackageDependencies;
|
|
98
113
|
exports.extractUiElements = extractUiElements;
|
|
99
114
|
exports.summarizeDirectUiChildren = summarizeDirectUiChildren;
|
|
115
|
+
exports.createUiSemanticProfile = createUiSemanticProfile;
|
|
116
|
+
exports.createUiRoleSignature = createUiRoleSignature;
|
|
117
|
+
exports.exactRoleClassFacts = exactRoleClassFacts;
|
|
118
|
+
exports.createRoleScaleKey = createRoleScaleKey;
|
|
119
|
+
exports.createRoleDensityKey = createRoleDensityKey;
|
|
120
|
+
exports.createRoleSurfaceKey = createRoleSurfaceKey;
|
|
121
|
+
exports.createRoleLayoutKey = createRoleLayoutKey;
|
|
122
|
+
exports.isCompactHeroEyebrow = isCompactHeroEyebrow;
|
|
123
|
+
exports.isOverlayPill = isOverlayPill;
|
|
124
|
+
exports.isHeroHeadingScale = isHeroHeadingScale;
|
|
125
|
+
exports.isOversizedHeadingScale = isOversizedHeadingScale;
|
|
126
|
+
exports.isMetricValueScale = isMetricValueScale;
|
|
127
|
+
exports.isCompactStatValue = isCompactStatValue;
|
|
128
|
+
exports.isLargeMetricValue = isLargeMetricValue;
|
|
129
|
+
exports.isCompactStatCard = isCompactStatCard;
|
|
130
|
+
exports.isInteractiveServiceCard = isInteractiveServiceCard;
|
|
131
|
+
exports.isStaticArticleCard = isStaticArticleCard;
|
|
132
|
+
exports.isMediaPanelCard = isMediaPanelCard;
|
|
133
|
+
exports.isQuietAction = isQuietAction;
|
|
134
|
+
exports.isCompactAction = isCompactAction;
|
|
135
|
+
exports.roleClassSort = roleClassSort;
|
|
136
|
+
exports.deriveUiChildSemanticRole = deriveUiChildSemanticRole;
|
|
100
137
|
exports.deriveLayoutRole = deriveLayoutRole;
|
|
101
138
|
exports.isStateClass = isStateClass;
|
|
102
139
|
exports.extractApiCalls = extractApiCalls;
|
|
@@ -150,6 +187,8 @@ exports.styleConfidenceForFile = styleConfidenceForFile;
|
|
|
150
187
|
exports.entryMatchesConcept = entryMatchesConcept;
|
|
151
188
|
exports.splitClassList = splitClassList;
|
|
152
189
|
exports.classifyUiElement = classifyUiElement;
|
|
190
|
+
exports.looksLikeSurfaceContainer = looksLikeSurfaceContainer;
|
|
191
|
+
exports.looksLikePillBadge = looksLikePillBadge;
|
|
153
192
|
exports.stripTags = stripTags;
|
|
154
193
|
const path = require("node:path");
|
|
155
194
|
const constants_1 = require("./constants");
|
|
@@ -562,7 +601,13 @@ function extractCssRules(content, relPath) {
|
|
|
562
601
|
return [];
|
|
563
602
|
}
|
|
564
603
|
const rules = [];
|
|
604
|
+
const mediaBlocks = extractCssAtRuleBlocks(content, 'media');
|
|
605
|
+
const mediaRanges = mediaBlocks.map((block) => ({ start: block.start, end: block.end }));
|
|
565
606
|
for (const match of content.matchAll(/([^{}@][^{}]{0,240})\{([^{}]{1,2400})\}/g)) {
|
|
607
|
+
const matchIndex = match.index ?? 0;
|
|
608
|
+
if (mediaRanges.some((range) => matchIndex >= range.start && matchIndex <= range.end)) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
566
611
|
const selector = match[1].trim().replace(/\s+/g, ' ');
|
|
567
612
|
if (!selector || selector.includes(';') || selector.startsWith('import')) {
|
|
568
613
|
continue;
|
|
@@ -577,7 +622,59 @@ function extractCssRules(content, relPath) {
|
|
|
577
622
|
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
578
623
|
});
|
|
579
624
|
}
|
|
580
|
-
|
|
625
|
+
for (const block of mediaBlocks) {
|
|
626
|
+
for (const match of block.body.matchAll(/([^{}@][^{}]{0,240})\{([^{}]{1,2400})\}/g)) {
|
|
627
|
+
const selector = match[1].trim().replace(/\s+/g, ' ');
|
|
628
|
+
if (!selector || selector.includes(';') || selector.startsWith('import')) {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const declarations = parseCssDeclarations(match[2]).slice(0, 40);
|
|
632
|
+
if (!declarations.length) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
rules.push({
|
|
636
|
+
selector,
|
|
637
|
+
declarations,
|
|
638
|
+
line: (0, utils_1.lineNumberAt)(content, block.bodyStart + (match.index ?? 0)),
|
|
639
|
+
media: block.prelude,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return (0, utils_1.uniqueBy)(rules, (rule) => `${rule.selector}:${rule.line}:${rule.media ?? ''}`).slice(0, 320);
|
|
644
|
+
}
|
|
645
|
+
function extractCssAtRuleBlocks(content, atRule) {
|
|
646
|
+
const blocks = [];
|
|
647
|
+
const pattern = new RegExp(`@${atRule}\\s+([^{}]+)\\{`, 'g');
|
|
648
|
+
for (const match of content.matchAll(pattern)) {
|
|
649
|
+
const start = match.index ?? 0;
|
|
650
|
+
const openIndex = start + match[0].length - 1;
|
|
651
|
+
let depth = 0;
|
|
652
|
+
let end = -1;
|
|
653
|
+
for (let cursor = openIndex; cursor < content.length; cursor += 1) {
|
|
654
|
+
const char = content[cursor];
|
|
655
|
+
if (char === '{') {
|
|
656
|
+
depth += 1;
|
|
657
|
+
}
|
|
658
|
+
else if (char === '}') {
|
|
659
|
+
depth -= 1;
|
|
660
|
+
if (depth === 0) {
|
|
661
|
+
end = cursor;
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (end <= openIndex) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
blocks.push({
|
|
670
|
+
prelude: match[1].trim().replace(/\s+/g, ' '),
|
|
671
|
+
body: content.slice(openIndex + 1, end),
|
|
672
|
+
start,
|
|
673
|
+
end,
|
|
674
|
+
bodyStart: openIndex + 1,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return blocks;
|
|
581
678
|
}
|
|
582
679
|
function extractCssClasses(content) {
|
|
583
680
|
return (0, utils_1.unique)(Array.from(content.matchAll(/\.([a-zA-Z][a-zA-Z0-9_-]+)\b/g)).map((match) => match[1])).slice(0, 80);
|
|
@@ -617,6 +714,7 @@ function extractClassExpressions(content) {
|
|
|
617
714
|
const classValue = match[2] ?? match[1] ?? '';
|
|
618
715
|
const classes = splitClassList(classValue);
|
|
619
716
|
if (classes.length) {
|
|
717
|
+
const helperUsage = readPrimaryClassHelperUsage(classValue);
|
|
620
718
|
const analysis = analyzeClassSource(classValue);
|
|
621
719
|
expressions.push({
|
|
622
720
|
kind: 'attribute',
|
|
@@ -626,6 +724,10 @@ function extractClassExpressions(content) {
|
|
|
626
724
|
conditionalClasses: analysis.conditionalClasses,
|
|
627
725
|
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
628
726
|
target: readTagBefore(content, match.index ?? 0),
|
|
727
|
+
...(helperUsage ? { styleHelper: helperUsage.name } : {}),
|
|
728
|
+
...(helperUsage && Object.keys(helperUsage.variantProps).length
|
|
729
|
+
? { styleHelperVariants: helperUsage.variantProps }
|
|
730
|
+
: {}),
|
|
629
731
|
});
|
|
630
732
|
}
|
|
631
733
|
}
|
|
@@ -642,7 +744,8 @@ function extractClassExpressions(content) {
|
|
|
642
744
|
}
|
|
643
745
|
const analysis = analyzeClassSource(call);
|
|
644
746
|
const classes = analysis.classes;
|
|
645
|
-
|
|
747
|
+
const helperUsage = readPrimaryClassHelperUsage(call);
|
|
748
|
+
if (classes.length || helperUsage) {
|
|
646
749
|
expressions.push({
|
|
647
750
|
kind: helperName === 'cva' ? 'variant-definition' : 'helper',
|
|
648
751
|
raw: (0, utils_1.truncate)(call, 900),
|
|
@@ -651,10 +754,44 @@ function extractClassExpressions(content) {
|
|
|
651
754
|
conditionalClasses: analysis.conditionalClasses,
|
|
652
755
|
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
653
756
|
target: helperName,
|
|
757
|
+
...(helperUsage ? { styleHelper: helperUsage.name } : {}),
|
|
758
|
+
...(helperUsage && Object.keys(helperUsage.variantProps).length
|
|
759
|
+
? { styleHelperVariants: helperUsage.variantProps }
|
|
760
|
+
: {}),
|
|
654
761
|
});
|
|
655
762
|
}
|
|
656
763
|
}
|
|
657
764
|
}
|
|
765
|
+
for (const match of content.matchAll(/\b([A-Za-z_$][\w$]*(?:Variants?|ClassNames?|Styles?))\s*\(/g)) {
|
|
766
|
+
const helperName = match[1];
|
|
767
|
+
if (/^(?:cn|clsx|classNames|twMerge|cva)$/i.test(helperName)) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (isInsideQuotedString(content, match.index ?? 0)) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const call = readBalancedCall(content, match.index ?? 0);
|
|
774
|
+
if (!call) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const helperUsage = readPrimaryClassHelperUsage(call);
|
|
778
|
+
if (!helperUsage) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
expressions.push({
|
|
782
|
+
kind: 'helper',
|
|
783
|
+
raw: (0, utils_1.truncate)(call, 900),
|
|
784
|
+
classes: [],
|
|
785
|
+
defaultClasses: [],
|
|
786
|
+
conditionalClasses: [],
|
|
787
|
+
line: (0, utils_1.lineNumberAt)(content, match.index ?? 0),
|
|
788
|
+
target: helperName,
|
|
789
|
+
styleHelper: helperUsage.name,
|
|
790
|
+
...(Object.keys(helperUsage.variantProps).length
|
|
791
|
+
? { styleHelperVariants: helperUsage.variantProps }
|
|
792
|
+
: {}),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
658
795
|
for (const match of content.matchAll(/(["'`])([^"'`]{5,900})\1/g)) {
|
|
659
796
|
const classes = splitClassList(match[2]);
|
|
660
797
|
if (classes.length >= 2 && looksLikeClassList(classes)) {
|
|
@@ -920,6 +1057,56 @@ function isInsideQuotedString(content, targetIndex) {
|
|
|
920
1057
|
function extractClassListsFromStrings(source) {
|
|
921
1058
|
return analyzeClassSource(source).classes;
|
|
922
1059
|
}
|
|
1060
|
+
function readPrimaryClassHelperUsage(source) {
|
|
1061
|
+
const usages = readClassHelperUsages(source);
|
|
1062
|
+
return (usages.find((usage) => /button|cta|action/i.test(usage.name)) ??
|
|
1063
|
+
usages.find((usage) => Object.keys(usage.variantProps).length > 0) ??
|
|
1064
|
+
usages[0]);
|
|
1065
|
+
}
|
|
1066
|
+
function readClassHelperUsages(source) {
|
|
1067
|
+
const usages = [];
|
|
1068
|
+
const helperPattern = /\b([A-Za-z_$][\w$]*(?:Variants?|ClassNames?|Styles?))\s*\(/g;
|
|
1069
|
+
for (const match of source.matchAll(helperPattern)) {
|
|
1070
|
+
const name = match[1];
|
|
1071
|
+
if (/^(?:cn|clsx|classNames|twMerge|cva)$/i.test(name)) {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
const call = readBalancedCall(source, match.index ?? 0);
|
|
1075
|
+
if (!call) {
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
usages.push({
|
|
1079
|
+
name,
|
|
1080
|
+
variantProps: parseHelperVariantProps(call),
|
|
1081
|
+
call: (0, utils_1.truncate)(call, 700),
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
return (0, utils_1.uniqueBy)(usages, (usage) => `${usage.name}:${JSON.stringify(usage.variantProps)}`).slice(0, 12);
|
|
1085
|
+
}
|
|
1086
|
+
function parseHelperVariantProps(call) {
|
|
1087
|
+
const props = {};
|
|
1088
|
+
const openIndex = call.indexOf('{');
|
|
1089
|
+
if (openIndex < 0) {
|
|
1090
|
+
return props;
|
|
1091
|
+
}
|
|
1092
|
+
const block = readBalancedBlock(call, openIndex);
|
|
1093
|
+
if (!block) {
|
|
1094
|
+
return props;
|
|
1095
|
+
}
|
|
1096
|
+
for (const entry of parseTopLevelObjectEntries(block)) {
|
|
1097
|
+
const literalValues = readStringLiterals(entry.rawValue)
|
|
1098
|
+
.map((value) => value.trim())
|
|
1099
|
+
.filter(Boolean)
|
|
1100
|
+
.slice(0, 4);
|
|
1101
|
+
const value = literalValues.length > 1
|
|
1102
|
+
? literalValues.join('|')
|
|
1103
|
+
: (literalValues[0] ?? cleanupBareValue(entry.rawValue));
|
|
1104
|
+
if (value) {
|
|
1105
|
+
props[entry.key] = value;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return props;
|
|
1109
|
+
}
|
|
923
1110
|
function analyzeClassSource(source) {
|
|
924
1111
|
const classes = new Set();
|
|
925
1112
|
const defaultClasses = new Set();
|
|
@@ -992,7 +1179,8 @@ function directClassCandidate(source) {
|
|
|
992
1179
|
return undefined;
|
|
993
1180
|
}
|
|
994
1181
|
const withoutTemplateExpressions = trimmed.replace(/\$\{[\s\S]*?\}/g, ' ');
|
|
995
|
-
|
|
1182
|
+
const withoutArbitraryValues = withoutTemplateExpressions.replace(/\[[^\]]+\]/g, '[arbitrary]');
|
|
1183
|
+
if (/[{}"'`,;]/.test(withoutArbitraryValues)) {
|
|
996
1184
|
return undefined;
|
|
997
1185
|
}
|
|
998
1186
|
return withoutTemplateExpressions;
|
|
@@ -1010,6 +1198,7 @@ function looksLikeClassList(classes) {
|
|
|
1010
1198
|
function isLikelyStyleClass(className) {
|
|
1011
1199
|
const base = className.split(':').pop() ?? className;
|
|
1012
1200
|
return (/^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|content-|self-|gap-|space-|w-|h-|min-|max-|size-|rounded|border|bg-|from-|via-|to-|text-|font-|leading-|tracking-|shadow|ring|outline|opacity|transition|duration|ease|animate-|hover|focus|active|disabled|dark|sm|md|lg|xl|2xl|container|sr-only|absolute|relative|fixed|sticky|inset-|top-|right-|bottom-|left-|z-|overflow-|object-|aspect-|backdrop-|blur|scale-|translate-|rotate-)/.test(className) ||
|
|
1201
|
+
/^(?:btn(?:-.+)?|card(?:-.+)?|badge(?:-.+)?|form(?:-.+)?|input-group(?:-.+)?|navbar(?:-.+)?|nav(?:-.+)?|row|col(?:-.+)?|container(?:-.+)?|d-(?:sm-|md-|lg-|xl-|xxl-)?|justify-content-|align-items-|align-self-|fw-|fs-|lh-|display-|lead|small|list-group(?:-.+)?|modal(?:-.+)?|alert(?:-.+)?|rounded-(?:pill|circle|\d)|shadow(?:-.+)?|w-\d+|h-\d+|mw-\d+|mh-\d+|min-vh-|min-vw-|vw-|vh-)/.test(className) ||
|
|
1013
1202
|
/^(?:button|btn|card|panel|surface|badge|pill|chip|nav|field|input|dialog|modal|drawer|sheet|container|wrapper|section|hero|header|footer|sidebar|menu)$/.test(base) ||
|
|
1014
1203
|
className.includes('[') ||
|
|
1015
1204
|
className.includes(']'));
|
|
@@ -1468,22 +1657,28 @@ function groupTailwindClasses(classes) {
|
|
|
1468
1657
|
addClassGroup(groups, 'state', className);
|
|
1469
1658
|
}
|
|
1470
1659
|
}
|
|
1471
|
-
if (/^(flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-)/.test(base)) {
|
|
1660
|
+
if (/^(flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-|container|d-|justify-content-|align-items-|align-self-)/.test(base)) {
|
|
1472
1661
|
addClassGroup(groups, 'layout', className);
|
|
1473
1662
|
}
|
|
1474
1663
|
else if (/^(?:-?m[trblxy]?|-?p[trblxy]?|gap-|space-|inset-|top-|right-|bottom-|left-)/.test(base)) {
|
|
1475
1664
|
addClassGroup(groups, 'spacing', className);
|
|
1476
1665
|
}
|
|
1477
|
-
else if (/^(w-|h-|min-|max-|size-|aspect-|basis-|grow|shrink)/.test(base)) {
|
|
1666
|
+
else if (/^(w-|h-|mw-|mh-|vw-|vh-|min-|max-|size-|aspect-|basis-|grow|shrink)/.test(base)) {
|
|
1478
1667
|
addClassGroup(groups, 'sizing', className);
|
|
1479
1668
|
}
|
|
1669
|
+
else if (isTypographyScaleBase(base)) {
|
|
1670
|
+
addClassGroup(groups, 'typography', className);
|
|
1671
|
+
}
|
|
1672
|
+
else if (isLayoutSafetyBase(base)) {
|
|
1673
|
+
addClassGroup(groups, 'layout-safety', className);
|
|
1674
|
+
}
|
|
1480
1675
|
else if (/^(bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
|
|
1481
1676
|
addClassGroup(groups, 'color', className);
|
|
1482
1677
|
}
|
|
1483
1678
|
else if (/^(rounded|border|divide-|outline|ring)/.test(base)) {
|
|
1484
1679
|
addClassGroup(groups, 'border', className);
|
|
1485
1680
|
}
|
|
1486
|
-
else if (/^(
|
|
1681
|
+
else if (/^(list-)/.test(base)) {
|
|
1487
1682
|
addClassGroup(groups, 'typography', className);
|
|
1488
1683
|
}
|
|
1489
1684
|
else if (/^(shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
|
|
@@ -1542,12 +1737,17 @@ function filterClasses(classes, predicate) {
|
|
|
1542
1737
|
return (0, utils_1.unique)(classes.filter(predicate));
|
|
1543
1738
|
}
|
|
1544
1739
|
function isResponsiveClass(className) {
|
|
1545
|
-
return /^(?:sm|md|lg|xl|2xl|
|
|
1740
|
+
return (/^(?:sm|md|lg|xl|2xl|min-[^:]+|max-[^:]+):/.test(className) ||
|
|
1741
|
+
/^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(?:sm|md|lg|xl|xxl)-/.test(className));
|
|
1546
1742
|
}
|
|
1547
1743
|
function isSpacingClass(className) {
|
|
1548
1744
|
const base = (0, utils_1.classBase)(className);
|
|
1549
1745
|
return /^(?:-?m[trblxy]?|-?p[trblxy]?|gap-|space-|inset-|top-|right-|bottom-|left-)/.test(base);
|
|
1550
1746
|
}
|
|
1747
|
+
function isLayoutClass(className) {
|
|
1748
|
+
const base = (0, utils_1.classBase)(className);
|
|
1749
|
+
return /^(?:flex|grid|block|inline|hidden|items-|justify-|content-|self-|place-|order-|col-|row-|container|d-|justify-content-|align-items-|align-self-)/.test(base);
|
|
1750
|
+
}
|
|
1551
1751
|
function isSurfaceClass(className) {
|
|
1552
1752
|
const base = (0, utils_1.classBase)(className);
|
|
1553
1753
|
return (/^(?:bg-|border|rounded|shadow|ring|outline|divide-|opacity|backdrop|blur|drop-shadow)/.test(base) ||
|
|
@@ -1562,7 +1762,109 @@ function isDecorativeAccentClass(className) {
|
|
|
1562
1762
|
}
|
|
1563
1763
|
function isTypographyClass(className) {
|
|
1564
1764
|
const base = (0, utils_1.classBase)(className);
|
|
1565
|
-
return
|
|
1765
|
+
return isTypographyScaleBase(base);
|
|
1766
|
+
}
|
|
1767
|
+
function isTypographyScaleBase(base) {
|
|
1768
|
+
return (/^(?:font-|leading-|tracking-|line-clamp|whitespace-|break-|truncate|text-balance|text-pretty|text-nowrap|text-wrap)/.test(base) ||
|
|
1769
|
+
/^(?:fs-|fw-|lh-|display-|lead|small|text-uppercase|text-lowercase|text-capitalize)/.test(base) ||
|
|
1770
|
+
/^text-(?:xs|sm|base|lg|xl|[2-9]xl|\[[^\]]+\])/.test(base) ||
|
|
1771
|
+
/^max-w-\[(?:\d+(?:\.\d+)?)?ch\]/.test(base));
|
|
1772
|
+
}
|
|
1773
|
+
function isLayoutSafetyBase(base) {
|
|
1774
|
+
return /^(?:overflow-|min-w-|min-h-|max-w-|max-h-|w-fit|h-fit|w-max|h-max|w-min|h-min|basis-|grow|shrink|flex-1|flex-none|self-|whitespace-|break-|truncate|line-clamp|text-ellipsis|text-nowrap|text-balance|text-pretty)/.test(base);
|
|
1775
|
+
}
|
|
1776
|
+
function isSizingClass(className) {
|
|
1777
|
+
const base = (0, utils_1.classBase)(className);
|
|
1778
|
+
return /^(?:w-|h-|mw-|mh-|vw-|vh-|min-|max-|size-|aspect-|basis-|grow|shrink|flex-1|flex-none)/.test(base);
|
|
1779
|
+
}
|
|
1780
|
+
function isVisibilityClass(className) {
|
|
1781
|
+
const base = (0, utils_1.classBase)(className);
|
|
1782
|
+
return /^(?:hidden|block|inline|inline-block|flex|inline-flex|grid|inline-grid|table|contents)$/.test(base);
|
|
1783
|
+
}
|
|
1784
|
+
function isLineHeightClass(className) {
|
|
1785
|
+
return (0, utils_1.classBase)(className).startsWith('leading-');
|
|
1786
|
+
}
|
|
1787
|
+
function isMaxWidthClass(className) {
|
|
1788
|
+
return (0, utils_1.classBase)(className).startsWith('max-w-');
|
|
1789
|
+
}
|
|
1790
|
+
function createUiLayoutSafety(classes) {
|
|
1791
|
+
const wrapClasses = filterClasses(classes, (className) => /^(?:whitespace-|break-|truncate|line-clamp|text-ellipsis|text-nowrap|text-balance|text-pretty|text-wrap)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1792
|
+
const overflowClasses = filterClasses(classes, (className) => /^(?:overflow-|line-clamp|truncate|text-ellipsis)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1793
|
+
const minSizeClasses = filterClasses(classes, (className) => /^(?:min-w-|min-h-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1794
|
+
const maxSizeClasses = filterClasses(classes, (className) => /^(?:max-w-|max-h-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1795
|
+
const fitClasses = filterClasses(classes, (className) => /^(?:w-fit|h-fit|w-max|h-max|w-min|h-min|basis-|self-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1796
|
+
const flexBehaviorClasses = filterClasses(classes, (className) => /^(?:grow|shrink|flex-1|flex-none|basis-)/.test((0, utils_1.classBase)(className))).slice(0, 40);
|
|
1797
|
+
const layoutSafetyClasses = (0, utils_1.unique)([
|
|
1798
|
+
...wrapClasses,
|
|
1799
|
+
...overflowClasses,
|
|
1800
|
+
...minSizeClasses,
|
|
1801
|
+
...maxSizeClasses,
|
|
1802
|
+
...fitClasses,
|
|
1803
|
+
...flexBehaviorClasses,
|
|
1804
|
+
]).slice(0, 80);
|
|
1805
|
+
if (!layoutSafetyClasses.length) {
|
|
1806
|
+
return undefined;
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
wrapClasses,
|
|
1810
|
+
overflowClasses,
|
|
1811
|
+
minSizeClasses,
|
|
1812
|
+
maxSizeClasses,
|
|
1813
|
+
fitClasses,
|
|
1814
|
+
flexBehaviorClasses,
|
|
1815
|
+
layoutSafetyClasses,
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
function createUiResponsiveProfile(classes) {
|
|
1819
|
+
const responsiveClasses = filterClasses(classes, isResponsiveClass).slice(0, 120);
|
|
1820
|
+
if (!responsiveClasses.length) {
|
|
1821
|
+
return undefined;
|
|
1822
|
+
}
|
|
1823
|
+
const breakpoints = {};
|
|
1824
|
+
for (const className of responsiveClasses) {
|
|
1825
|
+
const prefix = responsivePrefix(className);
|
|
1826
|
+
if (!prefix) {
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
breakpoints[prefix] = (0, utils_1.unique)([...(breakpoints[prefix] ?? []), className]).slice(0, 60);
|
|
1830
|
+
}
|
|
1831
|
+
return {
|
|
1832
|
+
breakpoints,
|
|
1833
|
+
layoutClasses: filterClasses(responsiveClasses, isLayoutClass).slice(0, 60),
|
|
1834
|
+
typographyClasses: filterClasses(responsiveClasses, isTypographyClass).slice(0, 60),
|
|
1835
|
+
spacingClasses: filterClasses(responsiveClasses, isSpacingClass).slice(0, 60),
|
|
1836
|
+
sizingClasses: filterClasses(responsiveClasses, isSizingClass).slice(0, 60),
|
|
1837
|
+
visibilityClasses: filterClasses(responsiveClasses, isVisibilityClass).slice(0, 60),
|
|
1838
|
+
stateClasses: filterClasses(responsiveClasses, isStateClass).slice(0, 60),
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function createUiScaleProfile(classes) {
|
|
1842
|
+
const typographyClasses = filterClasses(classes, isTypographyClass).slice(0, 80);
|
|
1843
|
+
const spacingClasses = filterClasses(classes, isSpacingClass).slice(0, 80);
|
|
1844
|
+
const sizingClasses = filterClasses(classes, isSizingClass).slice(0, 80);
|
|
1845
|
+
const gapClasses = filterClasses(classes, (className) => /^(?:gap-|space-)/.test((0, utils_1.classBase)(className))).slice(0, 60);
|
|
1846
|
+
const maxWidthClasses = filterClasses(classes, isMaxWidthClass).slice(0, 60);
|
|
1847
|
+
const lineHeightClasses = filterClasses(classes, isLineHeightClass).slice(0, 60);
|
|
1848
|
+
if (!typographyClasses.length &&
|
|
1849
|
+
!spacingClasses.length &&
|
|
1850
|
+
!sizingClasses.length &&
|
|
1851
|
+
!gapClasses.length &&
|
|
1852
|
+
!maxWidthClasses.length &&
|
|
1853
|
+
!lineHeightClasses.length) {
|
|
1854
|
+
return undefined;
|
|
1855
|
+
}
|
|
1856
|
+
return {
|
|
1857
|
+
typographyClasses,
|
|
1858
|
+
spacingClasses,
|
|
1859
|
+
sizingClasses,
|
|
1860
|
+
gapClasses,
|
|
1861
|
+
maxWidthClasses,
|
|
1862
|
+
lineHeightClasses,
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
function responsivePrefix(className) {
|
|
1866
|
+
return (className.split(':').find((part) => /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(part)) ??
|
|
1867
|
+
className.match(/^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(sm|md|lg|xl|xxl)-/)?.[1]);
|
|
1566
1868
|
}
|
|
1567
1869
|
function groupClassExpressionsByTarget(expressions) {
|
|
1568
1870
|
const grouped = {};
|
|
@@ -1630,6 +1932,15 @@ function createUiCompositionGuidance(file) {
|
|
|
1630
1932
|
if (classes.has('section-frame') || classes.has('context-surface')) {
|
|
1631
1933
|
guidance.push('Use section-frame/context-surface only for framed technical panels, not every repeated card.');
|
|
1632
1934
|
}
|
|
1935
|
+
if (classes.has('section-frame')) {
|
|
1936
|
+
guidance.push('section-frame owns the clipped section-frame::before grid overlay for that surface; do not treat it as a simple border/background token.');
|
|
1937
|
+
}
|
|
1938
|
+
if (classes.has('section-frame') && classes.has('context-surface')) {
|
|
1939
|
+
guidance.push('Keep section-frame and context-surface paired for technical surfaces when child content must sit above the parent grid overlay.');
|
|
1940
|
+
}
|
|
1941
|
+
if (classes.has('sm:section-frame') || classes.has('bg-transparent')) {
|
|
1942
|
+
guidance.push('Preserve transparent mobile surfaces and only add framed behavior at the same responsive breakpoint as the source.');
|
|
1943
|
+
}
|
|
1633
1944
|
if (file.classReferences.some(isDecorativeAccentClass)) {
|
|
1634
1945
|
guidance.push('Treat brand-spectrum-card and purple glow shadow classes as accent/highlight-only; do not use them for default repeated cards unless the source element is explicitly current, highlighted, or featured.');
|
|
1635
1946
|
}
|
|
@@ -1647,7 +1958,7 @@ function createUiPatternGuidance(kind, decorativeAccentClasses) {
|
|
|
1647
1958
|
}
|
|
1648
1959
|
function extractTailwindUtilities(classReferences) {
|
|
1649
1960
|
return classReferences
|
|
1650
|
-
.filter((className) => /^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|gap-|space-|w-|h-|min-|max-|rounded|border|bg-|text-|font-|leading-|tracking-|shadow|ring|opacity|transition|duration|ease|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|container|sr-only)/.test(className))
|
|
1961
|
+
.filter((className) => /^(?:-?m[trblxy]?|-?p[trblxy]?|flex|grid|block|inline|hidden|items-|justify-|gap-|space-|w-|h-|min-|max-|size-|basis-|grow|shrink|aspect-|overflow-|object-|whitespace-|break-|truncate|line-clamp|text-balance|text-pretty|text-nowrap|rounded|border|bg-|text-|font-|leading-|tracking-|shadow|ring|opacity|transition|duration|ease|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|min-[^:]+:|max-[^:]+:|container|sr-only)/.test(className))
|
|
1651
1962
|
.slice(0, 160);
|
|
1652
1963
|
}
|
|
1653
1964
|
function extractPackageDependencies(relPath, content) {
|
|
@@ -1701,10 +2012,17 @@ function extractUiElements(content, relPath) {
|
|
|
1701
2012
|
const label = extractElementLabel(content, tagInfo);
|
|
1702
2013
|
const classSource = readClassSource(attrs);
|
|
1703
2014
|
const classAnalysis = classSource ? analyzeClassSource(classSource) : undefined;
|
|
2015
|
+
const styleHelper = classSource ? readPrimaryClassHelperUsage(classSource) : undefined;
|
|
1704
2016
|
const classes = classAnalysis?.classes ?? [];
|
|
1705
2017
|
const role = (0, utils_1.readAttr)(attrs, 'role');
|
|
1706
|
-
const props =
|
|
1707
|
-
|
|
2018
|
+
const props = {
|
|
2019
|
+
...readUiProps(attrs),
|
|
2020
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2021
|
+
};
|
|
2022
|
+
const variants = {
|
|
2023
|
+
...readVariantProps(props),
|
|
2024
|
+
...(styleHelper?.variantProps ?? {}),
|
|
2025
|
+
};
|
|
1708
2026
|
const kind = classifyUiElement(tagInfo.originalTag, attrs, classes, role);
|
|
1709
2027
|
const parentTagInfo = tagInfo.parentIndex === undefined ? undefined : tags[tagInfo.parentIndex];
|
|
1710
2028
|
const parentClassSource = parentTagInfo ? readClassSource(parentTagInfo.attrs) : undefined;
|
|
@@ -1716,7 +2034,21 @@ function extractUiElements(content, relPath) {
|
|
|
1716
2034
|
const layoutRole = deriveLayoutRole(tagInfo.originalTag, kind, classes, label);
|
|
1717
2035
|
const responsiveClasses = classes.filter(isResponsiveClass);
|
|
1718
2036
|
const stateClasses = classes.filter(isStateClass);
|
|
2037
|
+
const layoutSafety = createUiLayoutSafety(classes);
|
|
2038
|
+
const responsiveProfile = createUiResponsiveProfile(classes);
|
|
2039
|
+
const scaleProfile = createUiScaleProfile(classes);
|
|
1719
2040
|
const childSummary = summarizeDirectUiChildren(content, tags, index);
|
|
2041
|
+
const semanticProfile = createUiSemanticProfile(kind, tagInfo.originalTag, classes, childSummary);
|
|
2042
|
+
const roleSignature = createUiRoleSignature({
|
|
2043
|
+
kind,
|
|
2044
|
+
tag: tagInfo.originalTag,
|
|
2045
|
+
classes,
|
|
2046
|
+
children: childSummary,
|
|
2047
|
+
label,
|
|
2048
|
+
layoutRole,
|
|
2049
|
+
parentKind,
|
|
2050
|
+
parentClasses,
|
|
2051
|
+
});
|
|
1720
2052
|
if (kind === 'unknown') {
|
|
1721
2053
|
continue;
|
|
1722
2054
|
}
|
|
@@ -1748,6 +2080,11 @@ function extractUiElements(content, relPath) {
|
|
|
1748
2080
|
classGroups: groupTailwindClasses(classes),
|
|
1749
2081
|
...(responsiveClasses.length ? { responsiveClasses } : {}),
|
|
1750
2082
|
...(stateClasses.length ? { stateClasses } : {}),
|
|
2083
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
2084
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
2085
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
2086
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
2087
|
+
...(roleSignature ? { roleSignature } : {}),
|
|
1751
2088
|
...(childSummary.length ? { childSummary } : {}),
|
|
1752
2089
|
...((0, utils_1.readAttr)(attrs, 'id') ? { id: (0, utils_1.readAttr)(attrs, 'id') } : {}),
|
|
1753
2090
|
...(role ? { role } : {}),
|
|
@@ -1757,6 +2094,10 @@ function extractUiElements(content, relPath) {
|
|
|
1757
2094
|
...((0, utils_1.readAttr)(attrs, 'method') ? { method: (0, utils_1.readAttr)(attrs, 'method')?.toUpperCase() } : {}),
|
|
1758
2095
|
...(Object.keys(props).length ? { props } : {}),
|
|
1759
2096
|
...(Object.keys(variants).length ? { variants } : {}),
|
|
2097
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2098
|
+
...(styleHelper && Object.keys(styleHelper.variantProps).length
|
|
2099
|
+
? { styleHelperVariants: styleHelper.variantProps }
|
|
2100
|
+
: {}),
|
|
1760
2101
|
evidence: `<${tagInfo.originalTag}${classes.length ? ` class="${classes.slice(0, 6).join(' ')}"` : ''}>`,
|
|
1761
2102
|
});
|
|
1762
2103
|
}
|
|
@@ -1769,11 +2110,31 @@ function summarizeDirectUiChildren(content, tags, parentIndex) {
|
|
|
1769
2110
|
.map(({ tagInfo, index }) => {
|
|
1770
2111
|
const classSource = readClassSource(tagInfo.attrs);
|
|
1771
2112
|
const classAnalysis = classSource ? analyzeClassSource(classSource) : undefined;
|
|
2113
|
+
const styleHelper = classSource ? readPrimaryClassHelperUsage(classSource) : undefined;
|
|
1772
2114
|
const classes = classAnalysis?.classes ?? [];
|
|
1773
2115
|
const role = (0, utils_1.readAttr)(tagInfo.attrs, 'role');
|
|
1774
2116
|
const kind = classifyUiElement(tagInfo.originalTag, tagInfo.attrs, classes, role);
|
|
1775
2117
|
const label = extractElementLabel(content, tagInfo);
|
|
1776
2118
|
const layoutRole = deriveLayoutRole(tagInfo.originalTag, kind, classes, label);
|
|
2119
|
+
const layoutSafety = createUiLayoutSafety(classes);
|
|
2120
|
+
const responsiveProfile = createUiResponsiveProfile(classes);
|
|
2121
|
+
const scaleProfile = createUiScaleProfile(classes);
|
|
2122
|
+
const semanticRole = deriveUiChildSemanticRole(tagInfo.originalTag, kind, classes, label);
|
|
2123
|
+
const roleSignature = createUiRoleSignature({
|
|
2124
|
+
kind,
|
|
2125
|
+
tag: tagInfo.originalTag,
|
|
2126
|
+
classes,
|
|
2127
|
+
label,
|
|
2128
|
+
layoutRole,
|
|
2129
|
+
});
|
|
2130
|
+
const props = {
|
|
2131
|
+
...readUiProps(tagInfo.attrs),
|
|
2132
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2133
|
+
};
|
|
2134
|
+
const variants = {
|
|
2135
|
+
...readVariantProps(props),
|
|
2136
|
+
...(styleHelper?.variantProps ?? {}),
|
|
2137
|
+
};
|
|
1777
2138
|
return {
|
|
1778
2139
|
index,
|
|
1779
2140
|
kind,
|
|
@@ -1787,12 +2148,496 @@ function summarizeDirectUiChildren(content, tags, parentIndex) {
|
|
|
1787
2148
|
...(classAnalysis?.conditionalClasses.length
|
|
1788
2149
|
? { conditionalClasses: classAnalysis.conditionalClasses.slice(0, 32) }
|
|
1789
2150
|
: {}),
|
|
2151
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
2152
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
2153
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
2154
|
+
...(semanticRole ? { semanticRole } : {}),
|
|
2155
|
+
...(roleSignature ? { roleSignature } : {}),
|
|
2156
|
+
...(Object.keys(props).length ? { props } : {}),
|
|
2157
|
+
...(Object.keys(variants).length ? { variants } : {}),
|
|
2158
|
+
...(styleHelper ? { styleHelper: styleHelper.name } : {}),
|
|
2159
|
+
...(styleHelper && Object.keys(styleHelper.variantProps).length
|
|
2160
|
+
? { styleHelperVariants: styleHelper.variantProps }
|
|
2161
|
+
: {}),
|
|
1790
2162
|
...(label ? { label } : {}),
|
|
1791
2163
|
};
|
|
1792
2164
|
})
|
|
1793
|
-
.filter((child) => child.kind !== 'unknown' ||
|
|
2165
|
+
.filter((child) => child.kind !== 'unknown' ||
|
|
2166
|
+
child.classes.length ||
|
|
2167
|
+
child.label ||
|
|
2168
|
+
Boolean(child.semanticRole))
|
|
1794
2169
|
.slice(0, 16);
|
|
1795
2170
|
}
|
|
2171
|
+
function createUiSemanticProfile(kind, tag, classes, children) {
|
|
2172
|
+
const roles = (0, utils_1.unique)(children.flatMap((child) => child.semanticRole ?? [])).slice(0, 24);
|
|
2173
|
+
const textStyleSignatures = (0, utils_1.unique)(children
|
|
2174
|
+
.filter((child) => ['text', 'heading', 'badge'].includes(child.kind) || child.semanticRole)
|
|
2175
|
+
.map((child) => JSON.stringify(groupTailwindClasses(child.defaultClasses?.length ? child.defaultClasses : child.classes)))).filter(Boolean);
|
|
2176
|
+
const hasIcon = roles.includes('icon') || roles.includes('component-icon') || roles.includes('image');
|
|
2177
|
+
const hasDivider = roles.includes('divider');
|
|
2178
|
+
const hasImage = roles.includes('image');
|
|
2179
|
+
const hasMultipleTextStyles = textStyleSignatures.length > 1;
|
|
2180
|
+
const isCompound = children.length > 1 &&
|
|
2181
|
+
(hasIcon ||
|
|
2182
|
+
hasDivider ||
|
|
2183
|
+
hasMultipleTextStyles ||
|
|
2184
|
+
roles.includes('primary-text') ||
|
|
2185
|
+
roles.includes('secondary-text'));
|
|
2186
|
+
if (!isCompound && !roles.length) {
|
|
2187
|
+
return undefined;
|
|
2188
|
+
}
|
|
2189
|
+
return {
|
|
2190
|
+
roles,
|
|
2191
|
+
isCompound,
|
|
2192
|
+
hasIcon,
|
|
2193
|
+
hasDivider,
|
|
2194
|
+
hasImage,
|
|
2195
|
+
hasMultipleTextStyles,
|
|
2196
|
+
textStyleCount: textStyleSignatures.length,
|
|
2197
|
+
childCount: children.length,
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
function createUiRoleSignature(input) {
|
|
2201
|
+
const { kind, tag, classes } = input;
|
|
2202
|
+
const children = input.children ?? [];
|
|
2203
|
+
const tagName = tag.toLowerCase();
|
|
2204
|
+
const bases = classes.map(utils_1.classBase);
|
|
2205
|
+
const baseSet = new Set(bases);
|
|
2206
|
+
const childRoles = (0, utils_1.unique)(children.flatMap((child) => [child.semanticRole, child.roleSignature?.role].filter((value) => typeof value === 'string' && Boolean(value)))).slice(0, 24);
|
|
2207
|
+
const childKinds = (0, utils_1.unique)(children.map((child) => child.kind).filter(Boolean)).slice(0, 24);
|
|
2208
|
+
const hasIcon = childRoles.some((role) => /(?:icon|image|logo|mark)/.test(role));
|
|
2209
|
+
const hasDivider = childRoles.includes('divider');
|
|
2210
|
+
const hasMultipleTextSegments = childRoles.filter((role) => /(?:primary|secondary|text|segment)/.test(role)).length > 1;
|
|
2211
|
+
const exactClassFacts = exactRoleClassFacts(classes);
|
|
2212
|
+
const flags = new Set();
|
|
2213
|
+
const layout = createRoleLayoutKey(kind, tagName, classes, children, input.parentClasses ?? []);
|
|
2214
|
+
const scale = createRoleScaleKey(classes, children);
|
|
2215
|
+
const density = createRoleDensityKey(classes, children);
|
|
2216
|
+
const surface = createRoleSurfaceKey(classes);
|
|
2217
|
+
let roleGroup = kind;
|
|
2218
|
+
let role = `${kind}-${input.layoutRole ?? 'element'}`;
|
|
2219
|
+
if (input.layoutRole) {
|
|
2220
|
+
flags.add(`layout-role:${input.layoutRole}`);
|
|
2221
|
+
}
|
|
2222
|
+
if (hasIcon)
|
|
2223
|
+
flags.add('has-icon');
|
|
2224
|
+
if (hasDivider)
|
|
2225
|
+
flags.add('has-divider');
|
|
2226
|
+
if (hasMultipleTextSegments)
|
|
2227
|
+
flags.add('multiple-text-segments');
|
|
2228
|
+
if (baseSet.has('uppercase'))
|
|
2229
|
+
flags.add('uppercase');
|
|
2230
|
+
if (classes.some((className) => className.includes('hover:')))
|
|
2231
|
+
flags.add('has-hover-state');
|
|
2232
|
+
if (baseSet.has('group'))
|
|
2233
|
+
flags.add('group-context');
|
|
2234
|
+
if (classes.some((className) => (0, utils_1.classBase)(className).startsWith('backdrop-blur'))) {
|
|
2235
|
+
flags.add('backdrop-blur');
|
|
2236
|
+
}
|
|
2237
|
+
if (kind === 'section') {
|
|
2238
|
+
roleGroup = 'section';
|
|
2239
|
+
if (baseSet.has('min-h-screen')) {
|
|
2240
|
+
role = 'hero-section-fullscreen';
|
|
2241
|
+
flags.add('full-screen-hero');
|
|
2242
|
+
}
|
|
2243
|
+
else if (layout === 'split-media-layout') {
|
|
2244
|
+
role = 'split-media-section';
|
|
2245
|
+
flags.add('split-media');
|
|
2246
|
+
}
|
|
2247
|
+
else if (layout === 'card-grid-layout') {
|
|
2248
|
+
role = 'card-grid-section';
|
|
2249
|
+
flags.add('card-grid');
|
|
2250
|
+
}
|
|
2251
|
+
else {
|
|
2252
|
+
role = 'section-shell';
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (kind === 'layout') {
|
|
2256
|
+
roleGroup = 'layout';
|
|
2257
|
+
if (layout === 'split-media-layout') {
|
|
2258
|
+
role = 'split-media-layout';
|
|
2259
|
+
flags.add('split-media');
|
|
2260
|
+
}
|
|
2261
|
+
else if (layout === 'card-grid-layout') {
|
|
2262
|
+
role = 'card-grid-layout';
|
|
2263
|
+
flags.add('card-grid');
|
|
2264
|
+
}
|
|
2265
|
+
else if (layout === 'hero-content-layout') {
|
|
2266
|
+
role = 'hero-content-layout';
|
|
2267
|
+
flags.add('hero-content');
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
role = `layout-${layout}`;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
if (kind === 'heading') {
|
|
2274
|
+
roleGroup = tagName === 'h1' ? 'hero-heading' : 'heading';
|
|
2275
|
+
if (tagName === 'h1') {
|
|
2276
|
+
if (isOversizedHeadingScale(classes)) {
|
|
2277
|
+
role = 'hero-heading-oversized';
|
|
2278
|
+
flags.add('oversized-heading');
|
|
2279
|
+
}
|
|
2280
|
+
else if (isHeroHeadingScale(classes)) {
|
|
2281
|
+
role = 'hero-heading-standard';
|
|
2282
|
+
flags.add('hero-heading');
|
|
2283
|
+
}
|
|
2284
|
+
else {
|
|
2285
|
+
role = 'page-heading';
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
else if (isMetricValueScale(classes)) {
|
|
2289
|
+
role = 'metric-heading';
|
|
2290
|
+
flags.add('metric-value');
|
|
2291
|
+
}
|
|
2292
|
+
else {
|
|
2293
|
+
role = 'section-heading';
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (kind === 'badge') {
|
|
2297
|
+
roleGroup = 'badge';
|
|
2298
|
+
if (isOverlayPill(classes)) {
|
|
2299
|
+
role = 'overlay-disclaimer-pill';
|
|
2300
|
+
flags.add('overlay-pill');
|
|
2301
|
+
}
|
|
2302
|
+
else if (hasIcon || hasDivider || hasMultipleTextSegments) {
|
|
2303
|
+
role = 'brand-lockup-badge';
|
|
2304
|
+
flags.add('compound-badge');
|
|
2305
|
+
}
|
|
2306
|
+
else if (isCompactHeroEyebrow(classes)) {
|
|
2307
|
+
role = 'hero-eyebrow-compact';
|
|
2308
|
+
flags.add('compact-eyebrow');
|
|
2309
|
+
}
|
|
2310
|
+
else {
|
|
2311
|
+
role = 'badge-pill';
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
if (kind === 'card') {
|
|
2315
|
+
roleGroup = 'surface';
|
|
2316
|
+
if (isCompactStatCard(classes, childRoles)) {
|
|
2317
|
+
role = 'hero-stat-card-compact';
|
|
2318
|
+
flags.add('compact-stat-card');
|
|
2319
|
+
}
|
|
2320
|
+
else if (isInteractiveServiceCard(classes)) {
|
|
2321
|
+
role = 'interactive-service-card';
|
|
2322
|
+
flags.add('interactive-card');
|
|
2323
|
+
}
|
|
2324
|
+
else if (isStaticArticleCard(classes)) {
|
|
2325
|
+
role = 'static-article-card';
|
|
2326
|
+
flags.add('static-article-surface');
|
|
2327
|
+
}
|
|
2328
|
+
else if (isMediaPanelCard(classes, childKinds, childRoles)) {
|
|
2329
|
+
role = 'media-panel-card';
|
|
2330
|
+
flags.add('media-panel');
|
|
2331
|
+
}
|
|
2332
|
+
else {
|
|
2333
|
+
role = 'surface-card';
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (kind === 'text') {
|
|
2337
|
+
roleGroup = 'text';
|
|
2338
|
+
if (isCompactStatValue(classes)) {
|
|
2339
|
+
role = 'compact-stat-value';
|
|
2340
|
+
flags.add('compact-stat-value');
|
|
2341
|
+
}
|
|
2342
|
+
else if (isLargeMetricValue(classes)) {
|
|
2343
|
+
role = 'large-metric-value';
|
|
2344
|
+
flags.add('large-metric-value');
|
|
2345
|
+
}
|
|
2346
|
+
else if (isOverlayPill(classes)) {
|
|
2347
|
+
role = 'overlay-disclaimer-text';
|
|
2348
|
+
flags.add('overlay-pill');
|
|
2349
|
+
}
|
|
2350
|
+
else {
|
|
2351
|
+
role = 'body-copy';
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (kind === 'button') {
|
|
2355
|
+
roleGroup = 'action';
|
|
2356
|
+
if (isQuietAction(classes)) {
|
|
2357
|
+
role = 'quiet-action';
|
|
2358
|
+
flags.add('low-emphasis-action');
|
|
2359
|
+
}
|
|
2360
|
+
else if (isCompactAction(classes)) {
|
|
2361
|
+
role = 'compact-action-button';
|
|
2362
|
+
flags.add('compact-action');
|
|
2363
|
+
}
|
|
2364
|
+
else {
|
|
2365
|
+
role = 'action-button';
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
if (!classes.length && !childRoles.length && role === `${kind}-${input.layoutRole ?? 'element'}`) {
|
|
2369
|
+
return undefined;
|
|
2370
|
+
}
|
|
2371
|
+
return {
|
|
2372
|
+
role,
|
|
2373
|
+
roleGroup,
|
|
2374
|
+
scale,
|
|
2375
|
+
density,
|
|
2376
|
+
surface,
|
|
2377
|
+
layout,
|
|
2378
|
+
flags: Array.from(flags).sort(),
|
|
2379
|
+
exactClassFacts,
|
|
2380
|
+
...(childRoles.length ? { childRoles } : {}),
|
|
2381
|
+
...(childKinds.length ? { childKinds } : {}),
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
function exactRoleClassFacts(classes) {
|
|
2385
|
+
return (0, utils_1.unique)(classes.filter((className) => {
|
|
2386
|
+
const base = (0, utils_1.classBase)(className);
|
|
2387
|
+
return (isTypographyClass(className) ||
|
|
2388
|
+
isSpacingClass(className) ||
|
|
2389
|
+
isSizingClass(className) ||
|
|
2390
|
+
isLayoutClass(className) ||
|
|
2391
|
+
isStateClass(className) ||
|
|
2392
|
+
/^(?:bg-|text-|border|rounded|shadow|backdrop|opacity|ring|outline|z-|absolute|relative|fixed|sticky|top-|right-|bottom-|left-|overflow-|object-|aspect-|group|transition|duration|ease)/.test(base));
|
|
2393
|
+
})).slice(0, 120);
|
|
2394
|
+
}
|
|
2395
|
+
function createRoleScaleKey(classes, children = []) {
|
|
2396
|
+
const scaleClasses = (0, utils_1.unique)([
|
|
2397
|
+
...classes,
|
|
2398
|
+
...children.flatMap((child) => child.classes.slice(0, 24)),
|
|
2399
|
+
].filter((className) => {
|
|
2400
|
+
const base = (0, utils_1.classBase)(className);
|
|
2401
|
+
return (isTypographyClass(className) ||
|
|
2402
|
+
isSizingClass(className) ||
|
|
2403
|
+
/^(?:max-w-|min-w-|leading-|tracking-|text-|font-|h-|w-|min-h-|max-h-)/.test(base));
|
|
2404
|
+
}))
|
|
2405
|
+
.sort(roleClassSort)
|
|
2406
|
+
.slice(0, 40);
|
|
2407
|
+
return scaleClasses.length ? scaleClasses.join(' ') : 'scale-unspecified';
|
|
2408
|
+
}
|
|
2409
|
+
function createRoleDensityKey(classes, children = []) {
|
|
2410
|
+
const densityClasses = (0, utils_1.unique)([
|
|
2411
|
+
...classes,
|
|
2412
|
+
...children.flatMap((child) => child.classes.slice(0, 16)),
|
|
2413
|
+
].filter((className) => {
|
|
2414
|
+
const base = (0, utils_1.classBase)(className);
|
|
2415
|
+
return /^(?:p[trblxy]?|m[trblxy]?|gap-|space-|h-|min-h-|max-h-|w-|min-w-|max-w-)/.test(base);
|
|
2416
|
+
}))
|
|
2417
|
+
.sort(roleClassSort)
|
|
2418
|
+
.slice(0, 40);
|
|
2419
|
+
if (!densityClasses.length) {
|
|
2420
|
+
return 'density-unspecified';
|
|
2421
|
+
}
|
|
2422
|
+
if (densityClasses.some((className) => /(?:^|:)py-(?:1|1\.5|2)$/.test(className)) ||
|
|
2423
|
+
densityClasses.some((className) => /(?:^|:)h-(?:8|9|10|11|12)$/.test(className))) {
|
|
2424
|
+
return `compact:${densityClasses.join(' ')}`;
|
|
2425
|
+
}
|
|
2426
|
+
if (densityClasses.some((className) => /(?:^|:)p-(?:8|10|12|16|20|24)$/.test(className)) ||
|
|
2427
|
+
densityClasses.some((className) => /(?:^|:)py-(?:8|10|12|16|20|24)$/.test(className))) {
|
|
2428
|
+
return `spacious:${densityClasses.join(' ')}`;
|
|
2429
|
+
}
|
|
2430
|
+
return densityClasses.join(' ');
|
|
2431
|
+
}
|
|
2432
|
+
function createRoleSurfaceKey(classes) {
|
|
2433
|
+
const surfaceClasses = (0, utils_1.unique)(classes.filter((className) => {
|
|
2434
|
+
const base = (0, utils_1.classBase)(className);
|
|
2435
|
+
return /^(?:bg-|border|rounded|shadow|ring|outline|backdrop|opacity|group|transition|hover:border|hover:bg)/.test(base);
|
|
2436
|
+
}))
|
|
2437
|
+
.sort(roleClassSort)
|
|
2438
|
+
.slice(0, 40);
|
|
2439
|
+
return surfaceClasses.length ? surfaceClasses.join(' ') : 'surface-unspecified';
|
|
2440
|
+
}
|
|
2441
|
+
function createRoleLayoutKey(kind, tagName, classes, children = [], parentClasses = []) {
|
|
2442
|
+
const bases = classes.map(utils_1.classBase);
|
|
2443
|
+
const baseSet = new Set(bases);
|
|
2444
|
+
const childKinds = new Set(children.map((child) => child.kind));
|
|
2445
|
+
const childRoles = new Set(children.flatMap((child) => [child.semanticRole, child.roleSignature?.role].filter(Boolean)));
|
|
2446
|
+
const childClassBases = children.flatMap((child) => child.classes.map(utils_1.classBase));
|
|
2447
|
+
const hasMediaChild = childKinds.has('image') ||
|
|
2448
|
+
childRoles.has('image') ||
|
|
2449
|
+
childRoles.has('media-panel-card') ||
|
|
2450
|
+
childClassBases.some((base) => /^(?:aspect-|object-|h-\[|min-h-\[)/.test(base));
|
|
2451
|
+
const hasCardChildren = children.filter((child) => child.kind === 'card').length >= 2;
|
|
2452
|
+
const hasTextChildren = children.some((child) => ['heading', 'text', 'badge'].includes(child.kind) ||
|
|
2453
|
+
(child.kind === 'layout' &&
|
|
2454
|
+
child.classes.some((className) => /^(?:max-w-|space-y-|prose|flex|grid)/.test((0, utils_1.classBase)(className)))));
|
|
2455
|
+
const parentBaseSet = new Set(parentClasses.map(utils_1.classBase));
|
|
2456
|
+
if (baseSet.has('min-h-screen')) {
|
|
2457
|
+
return 'full-screen-hero-layout';
|
|
2458
|
+
}
|
|
2459
|
+
if (baseSet.has('absolute') &&
|
|
2460
|
+
(baseSet.has('bottom-3') || baseSet.has('bottom-4')) &&
|
|
2461
|
+
(baseSet.has('right-3') || baseSet.has('right-4'))) {
|
|
2462
|
+
return 'absolute-bottom-right-overlay';
|
|
2463
|
+
}
|
|
2464
|
+
if ((baseSet.has('grid') || parentBaseSet.has('grid')) &&
|
|
2465
|
+
hasMediaChild &&
|
|
2466
|
+
hasTextChildren &&
|
|
2467
|
+
classes.some((className) => /(?:^|:)grid-cols-|(?:^|:)lg:grid-cols-/.test(className))) {
|
|
2468
|
+
return 'split-media-layout';
|
|
2469
|
+
}
|
|
2470
|
+
if ((baseSet.has('grid') || parentBaseSet.has('grid')) && hasCardChildren) {
|
|
2471
|
+
return 'card-grid-layout';
|
|
2472
|
+
}
|
|
2473
|
+
if (tagName === 'main' || (kind === 'layout' && baseSet.has('mx-auto'))) {
|
|
2474
|
+
return 'page-content-layout';
|
|
2475
|
+
}
|
|
2476
|
+
if (baseSet.has('flex') || baseSet.has('inline-flex')) {
|
|
2477
|
+
return 'flex-layout';
|
|
2478
|
+
}
|
|
2479
|
+
if (baseSet.has('grid')) {
|
|
2480
|
+
return 'grid-layout';
|
|
2481
|
+
}
|
|
2482
|
+
return 'layout-unspecified';
|
|
2483
|
+
}
|
|
2484
|
+
function isCompactHeroEyebrow(classes) {
|
|
2485
|
+
const bases = classes.map(utils_1.classBase);
|
|
2486
|
+
const baseSet = new Set(bases);
|
|
2487
|
+
return (baseSet.has('inline-flex') &&
|
|
2488
|
+
baseSet.has('rounded-full') &&
|
|
2489
|
+
baseSet.has('uppercase') &&
|
|
2490
|
+
classes.some((className) => /(?:^|:)text-xs$/.test(className)) &&
|
|
2491
|
+
classes.some((className) => /(?:^|:)sm:text-sm$/.test(className)) &&
|
|
2492
|
+
bases.some((base) => /^py-(?:1\.5|2)$/.test(base)) &&
|
|
2493
|
+
bases.some((base) => /^px-(?:3|4|5)$/.test(base)));
|
|
2494
|
+
}
|
|
2495
|
+
function isOverlayPill(classes) {
|
|
2496
|
+
const bases = classes.map(utils_1.classBase);
|
|
2497
|
+
const baseSet = new Set(bases);
|
|
2498
|
+
return (baseSet.has('absolute') &&
|
|
2499
|
+
baseSet.has('rounded-full') &&
|
|
2500
|
+
bases.some((base) => /^bottom-/.test(base)) &&
|
|
2501
|
+
bases.some((base) => /^right-/.test(base)) &&
|
|
2502
|
+
(bases.some((base) => /^text-\[(?:10|11)px\]$/.test(base)) ||
|
|
2503
|
+
classes.some((className) => /(?:^|:)sm:text-\[(?:10|11)px\]$/.test(className))));
|
|
2504
|
+
}
|
|
2505
|
+
function isHeroHeadingScale(classes) {
|
|
2506
|
+
const classSet = new Set(classes);
|
|
2507
|
+
const bases = classes.map(utils_1.classBase);
|
|
2508
|
+
return (classSet.has('text-4xl') &&
|
|
2509
|
+
classSet.has('sm:text-6xl') &&
|
|
2510
|
+
classSet.has('lg:text-7xl') &&
|
|
2511
|
+
bases.some((base) => /^leading-/.test(base)));
|
|
2512
|
+
}
|
|
2513
|
+
function isOversizedHeadingScale(classes) {
|
|
2514
|
+
const classSet = new Set(classes);
|
|
2515
|
+
return (classSet.has('text-6xl') ||
|
|
2516
|
+
classSet.has('sm:text-7xl') ||
|
|
2517
|
+
classSet.has('lg:text-8xl') ||
|
|
2518
|
+
classSet.has('xl:text-9xl') ||
|
|
2519
|
+
classes.some((className) => /(?:^|:)text-\[(?:8|9|10)\dpx\]/.test(className)));
|
|
2520
|
+
}
|
|
2521
|
+
function isMetricValueScale(classes) {
|
|
2522
|
+
return classes.some((className) => /(?:^|:)text-(?:lg|xl|2xl|3xl|4xl)$/.test(className));
|
|
2523
|
+
}
|
|
2524
|
+
function isCompactStatValue(classes) {
|
|
2525
|
+
const classSet = new Set(classes);
|
|
2526
|
+
const bases = classes.map(utils_1.classBase);
|
|
2527
|
+
return (classSet.has('text-lg') &&
|
|
2528
|
+
bases.some((base) => /^font-(?:bold|extrabold|black)$/.test(base)) &&
|
|
2529
|
+
bases.some((base) => /^text-\[|^text-(?:brand|accent|yellow|amber|gold)/.test(base)));
|
|
2530
|
+
}
|
|
2531
|
+
function isLargeMetricValue(classes) {
|
|
2532
|
+
const classSet = new Set(classes);
|
|
2533
|
+
return (classSet.has('text-2xl') ||
|
|
2534
|
+
classSet.has('sm:text-3xl') ||
|
|
2535
|
+
classSet.has('text-3xl') ||
|
|
2536
|
+
classSet.has('text-4xl'));
|
|
2537
|
+
}
|
|
2538
|
+
function isCompactStatCard(classes, childRoles) {
|
|
2539
|
+
const bases = classes.map(utils_1.classBase);
|
|
2540
|
+
const baseSet = new Set(bases);
|
|
2541
|
+
return (childRoles.includes('compact-stat-value') ||
|
|
2542
|
+
(baseSet.has('rounded-lg') &&
|
|
2543
|
+
baseSet.has('border') &&
|
|
2544
|
+
bases.some((base) => /^p-(?:4|5)$/.test(base)) &&
|
|
2545
|
+
!baseSet.has('group')));
|
|
2546
|
+
}
|
|
2547
|
+
function isInteractiveServiceCard(classes) {
|
|
2548
|
+
const bases = classes.map(utils_1.classBase);
|
|
2549
|
+
const baseSet = new Set(bases);
|
|
2550
|
+
return (baseSet.has('group') ||
|
|
2551
|
+
classes.some((className) => className.includes('hover:')) ||
|
|
2552
|
+
(baseSet.has('transition') && bases.includes('shadow-2xl')));
|
|
2553
|
+
}
|
|
2554
|
+
function isStaticArticleCard(classes) {
|
|
2555
|
+
const bases = classes.map(utils_1.classBase);
|
|
2556
|
+
const baseSet = new Set(bases);
|
|
2557
|
+
return (baseSet.has('flex') &&
|
|
2558
|
+
baseSet.has('h-full') &&
|
|
2559
|
+
baseSet.has('flex-col') &&
|
|
2560
|
+
baseSet.has('rounded-lg') &&
|
|
2561
|
+
baseSet.has('border') &&
|
|
2562
|
+
bases.includes('p-6') &&
|
|
2563
|
+
!baseSet.has('group') &&
|
|
2564
|
+
!classes.some((className) => className.includes('hover:')));
|
|
2565
|
+
}
|
|
2566
|
+
function isMediaPanelCard(classes, childKinds, childRoles) {
|
|
2567
|
+
const bases = classes.map(utils_1.classBase);
|
|
2568
|
+
return (childKinds.includes('image') ||
|
|
2569
|
+
childRoles.includes('image') ||
|
|
2570
|
+
bases.some((base) => /^(?:aspect-|object-|overflow-hidden)$/.test(base)));
|
|
2571
|
+
}
|
|
2572
|
+
function isQuietAction(classes) {
|
|
2573
|
+
const bases = classes.map(utils_1.classBase);
|
|
2574
|
+
return (bases.includes('bg-transparent') &&
|
|
2575
|
+
(bases.includes('border-transparent') ||
|
|
2576
|
+
bases.some((base) => /^text-(?:muted|secondary|slate|gray)/.test(base))));
|
|
2577
|
+
}
|
|
2578
|
+
function isCompactAction(classes) {
|
|
2579
|
+
const bases = classes.map(utils_1.classBase);
|
|
2580
|
+
return (bases.some((base) => /^h-(?:9|10|11|12)$/.test(base)) &&
|
|
2581
|
+
bases.some((base) => /^px-(?:4|5)$/.test(base)) &&
|
|
2582
|
+
classes.some((className) => /(?:^|:)text-sm$/.test(className)));
|
|
2583
|
+
}
|
|
2584
|
+
function roleClassSort(a, b) {
|
|
2585
|
+
const priority = (value) => {
|
|
2586
|
+
const base = (0, utils_1.classBase)(value);
|
|
2587
|
+
if (/^(?:max-w-|min-w-|w-|h-|min-h-|max-h-)/.test(base))
|
|
2588
|
+
return 0;
|
|
2589
|
+
if (/^(?:text-|font-|leading-|tracking-)/.test(base))
|
|
2590
|
+
return 1;
|
|
2591
|
+
if (/^(?:p|m|gap|space)-/.test(base))
|
|
2592
|
+
return 2;
|
|
2593
|
+
if (/^(?:bg-|border|rounded|shadow|backdrop)/.test(base))
|
|
2594
|
+
return 3;
|
|
2595
|
+
return 4;
|
|
2596
|
+
};
|
|
2597
|
+
return priority(a) - priority(b) || a.localeCompare(b);
|
|
2598
|
+
}
|
|
2599
|
+
function deriveUiChildSemanticRole(tag, kind, classes, label) {
|
|
2600
|
+
const tagName = tag.toLowerCase();
|
|
2601
|
+
const tagBase = tag.split('.')[0].split(':').pop() ?? tag;
|
|
2602
|
+
const lowerBase = tagBase.toLowerCase();
|
|
2603
|
+
const classBases = classes.map(utils_1.classBase);
|
|
2604
|
+
const classHaystack = [tagName, lowerBase, ...classBases].join(' ').toLowerCase();
|
|
2605
|
+
const labelHaystack = (label ?? '').toLowerCase();
|
|
2606
|
+
if (kind === 'icon') {
|
|
2607
|
+
return 'icon';
|
|
2608
|
+
}
|
|
2609
|
+
if (kind === 'image') {
|
|
2610
|
+
return 'image';
|
|
2611
|
+
}
|
|
2612
|
+
if (kind === 'divider') {
|
|
2613
|
+
return 'divider';
|
|
2614
|
+
}
|
|
2615
|
+
if (tagName === 'img' ||
|
|
2616
|
+
tagName === 'picture' ||
|
|
2617
|
+
tagName === 'svg' ||
|
|
2618
|
+
/(?:icon|logo|mark|glyph|avatar|image|img)$/.test(lowerBase) ||
|
|
2619
|
+
/\b(?:icon|logo|mark|glyph|avatar)\b/.test(classHaystack)) {
|
|
2620
|
+
return tagName === 'img' || tagName === 'picture' ? 'image' : 'icon';
|
|
2621
|
+
}
|
|
2622
|
+
if (classes.some((className) => /^(?:w-px|h-px|border-l|border-r|border-t|border-b|divide-x|divide-y)$/.test((0, utils_1.classBase)(className))) ||
|
|
2623
|
+
/\b(?:divider|separator|sep|rule)\b/.test(classHaystack)) {
|
|
2624
|
+
return 'divider';
|
|
2625
|
+
}
|
|
2626
|
+
if (kind === 'text' || kind === 'heading' || kind === 'badge') {
|
|
2627
|
+
if (classes.some((className) => /^(?:text-muted|text-muted-foreground|text-secondary|opacity-|font-normal)/.test((0, utils_1.classBase)(className)))) {
|
|
2628
|
+
return 'secondary-text';
|
|
2629
|
+
}
|
|
2630
|
+
if (classes.some((className) => /^(?:font-bold|font-semibold|font-black|text-primary|text-brand|text-accent|text-yellow|text-amber|text-gold|text-\[)/.test((0, utils_1.classBase)(className))) ||
|
|
2631
|
+
(labelHaystack.length > 0 && labelHaystack.length <= 32)) {
|
|
2632
|
+
return 'primary-text';
|
|
2633
|
+
}
|
|
2634
|
+
return 'text-segment';
|
|
2635
|
+
}
|
|
2636
|
+
if (/^[A-Z]/.test(tagBase) && !classes.length && !label) {
|
|
2637
|
+
return 'component-icon';
|
|
2638
|
+
}
|
|
2639
|
+
return undefined;
|
|
2640
|
+
}
|
|
1796
2641
|
function deriveLayoutRole(tag, kind, classes, label) {
|
|
1797
2642
|
const tagName = tag.toLowerCase();
|
|
1798
2643
|
const tagBase = tag.split('.')[0].split(':').pop() ?? tag;
|
|
@@ -1857,7 +2702,8 @@ function deriveLayoutRole(tag, kind, classes, label) {
|
|
|
1857
2702
|
return undefined;
|
|
1858
2703
|
}
|
|
1859
2704
|
function isStateClass(className) {
|
|
1860
|
-
|
|
2705
|
+
const prefixes = className.split(':').slice(0, -1);
|
|
2706
|
+
return prefixes.some((prefix) => /^(?:hover|focus|active|disabled|visited|dark|group|peer|aria|data)/.test(prefix));
|
|
1861
2707
|
}
|
|
1862
2708
|
function extractApiCalls(content) {
|
|
1863
2709
|
const calls = new Set();
|
|
@@ -2314,7 +3160,8 @@ function isTemplateFile(relPath) {
|
|
|
2314
3160
|
relPath.endsWith('.blade.php'));
|
|
2315
3161
|
}
|
|
2316
3162
|
function isTestFile(relPath) {
|
|
2317
|
-
return (/(?:^|\/)(?:
|
|
3163
|
+
return (/(?:^|\/)(?:app|pages|src\/app|src\/pages)\/test\//i.test(relPath) ||
|
|
3164
|
+
/(?:^|\/)(?:__tests__|tests?|spec)\//i.test(relPath) ||
|
|
2318
3165
|
/\.(test|spec)\.[A-Za-z0-9]+$/.test(relPath));
|
|
2319
3166
|
}
|
|
2320
3167
|
function isConfigFile(relPath) {
|
|
@@ -2527,6 +3374,8 @@ function classifyUiElement(tag, attrs, classes, role) {
|
|
|
2527
3374
|
const tagBase = originalTag.split('.')[0].split(':').pop() ?? originalTag;
|
|
2528
3375
|
const lowerBase = tagBase.toLowerCase();
|
|
2529
3376
|
const classHaystack = [tagName, role ?? '', ...classes].join(' ').toLowerCase();
|
|
3377
|
+
const classHelper = readPrimaryClassHelperUsage(readClassSource(attrs) ?? '');
|
|
3378
|
+
const classHelperName = classHelper?.name.toLowerCase() ?? '';
|
|
2530
3379
|
const propHaystack = [
|
|
2531
3380
|
tagName,
|
|
2532
3381
|
role ?? '',
|
|
@@ -2541,12 +3390,30 @@ function classifyUiElement(tag, attrs, classes, role) {
|
|
|
2541
3390
|
if (/^h[1-6]$/.test(tagName) || /^(heading|title|headline)$/i.test(tagBase)) {
|
|
2542
3391
|
return 'heading';
|
|
2543
3392
|
}
|
|
3393
|
+
if (tagName === 'img' ||
|
|
3394
|
+
tagName === 'picture' ||
|
|
3395
|
+
/^(image|img|avatar)$/i.test(tagBase) ||
|
|
3396
|
+
/(?:image|img|avatar)$/.test(lowerBase)) {
|
|
3397
|
+
return 'image';
|
|
3398
|
+
}
|
|
3399
|
+
if (tagName === 'svg' ||
|
|
3400
|
+
/^(icon|logo|mark|glyph)$/i.test(tagBase) ||
|
|
3401
|
+
/(?:icon|logo|mark|glyph)$/.test(lowerBase) ||
|
|
3402
|
+
/\b(icon|logo|mark|glyph)\b/.test(classHaystack)) {
|
|
3403
|
+
return 'icon';
|
|
3404
|
+
}
|
|
3405
|
+
if (/^(divider|separator|rule)$/i.test(tagBase) ||
|
|
3406
|
+
/\b(divider|separator|rule)\b/.test(classHaystack) ||
|
|
3407
|
+
classes.some((className) => /^(?:w-px|h-px|border-l|border-r|border-t|border-b|divide-x|divide-y)$/.test((0, utils_1.classBase)(className)))) {
|
|
3408
|
+
return 'divider';
|
|
3409
|
+
}
|
|
2544
3410
|
if (['input', 'textarea', 'select', 'option', 'label', 'fieldset'].includes(tagName) ||
|
|
2545
3411
|
/^(input|textarea|select|field|fieldlabel|label|checkbox|switch|slider|radio|combobox)$/i.test(tagBase) ||
|
|
2546
3412
|
/\b(field|input|textarea|select|checkbox|switch|slider|radio)\b/.test(classHaystack)) {
|
|
2547
3413
|
return 'input';
|
|
2548
3414
|
}
|
|
2549
|
-
if (
|
|
3415
|
+
if (/(?:button|cta|action)/.test(classHelperName) ||
|
|
3416
|
+
tagName === 'button' ||
|
|
2550
3417
|
role === 'button' ||
|
|
2551
3418
|
/^(button|iconbutton|submitbutton|cta)$/i.test(tagBase) ||
|
|
2552
3419
|
/\b(btn|button|cta|submit)\b/.test(classHaystack) ||
|
|
@@ -2574,12 +3441,15 @@ function classifyUiElement(tag, attrs, classes, role) {
|
|
|
2574
3441
|
/\b(dialog|modal|popover|drawer|sheet)\b/.test(classHaystack)) {
|
|
2575
3442
|
return 'dialog';
|
|
2576
3443
|
}
|
|
2577
|
-
if (
|
|
2578
|
-
|
|
3444
|
+
if (/(?:badge|chip|pill|status)/.test(classHelperName) ||
|
|
3445
|
+
/^(badge|chip|pill|tag|status|eyebrow)$/i.test(tagBase) ||
|
|
3446
|
+
/\b(badge|chip|pill|tag|status|eyebrow)\b/.test(classHaystack) ||
|
|
3447
|
+
looksLikePillBadge(tagName, classes)) {
|
|
2579
3448
|
return 'badge';
|
|
2580
3449
|
}
|
|
2581
3450
|
if (/^(card|panel|tile|surface|sectionframe|frame|callout)$/i.test(tagBase) ||
|
|
2582
|
-
/\b(card|panel|tile|
|
|
3451
|
+
/\b(card|panel|tile|section-frame|context-surface|frame|callout)\b/.test(classHaystack) ||
|
|
3452
|
+
looksLikeSurfaceContainer(tagName, tagBase, classes)) {
|
|
2583
3453
|
return 'card';
|
|
2584
3454
|
}
|
|
2585
3455
|
if (tagName === 'section' || lowerBase === 'section') {
|
|
@@ -2594,6 +3464,49 @@ function classifyUiElement(tag, attrs, classes, role) {
|
|
|
2594
3464
|
}
|
|
2595
3465
|
return 'unknown';
|
|
2596
3466
|
}
|
|
3467
|
+
function looksLikeSurfaceContainer(tagName, tagBase, classes) {
|
|
3468
|
+
const bases = classes.map(utils_1.classBase);
|
|
3469
|
+
const isContainerTag = ['div', 'article', 'section', 'aside', 'figure', 'li', 'main', 'header', 'footer'].includes(tagName) ||
|
|
3470
|
+
/^[A-Z]/.test(tagBase);
|
|
3471
|
+
const isTinyIndicator = tagName === 'span' &&
|
|
3472
|
+
bases.includes('rounded-full') &&
|
|
3473
|
+
bases.some((base) => /^(?:h|size)-(?:px|0\.5|1|1\.5|2|2\.5|3)$/.test(base)) &&
|
|
3474
|
+
bases.some((base) => /^(?:w|size)-(?:px|0\.5|1|1\.5|2|2\.5|3)$/.test(base));
|
|
3475
|
+
if (!isContainerTag || isTinyIndicator) {
|
|
3476
|
+
return false;
|
|
3477
|
+
}
|
|
3478
|
+
const hasSurfaceBackground = bases.some((base) => base === 'bg-card' ||
|
|
3479
|
+
base === 'section-frame' ||
|
|
3480
|
+
base === 'context-surface' ||
|
|
3481
|
+
/^bg-/.test(base) ||
|
|
3482
|
+
/^bg-\[var\(--(?:surface|card)-/.test(base));
|
|
3483
|
+
const hasContainerShape = bases.some((base) => /^(?:border|rounded|shadow|ring|outline|overflow-hidden)/.test(base));
|
|
3484
|
+
const hasContainerStructure = bases.some((base) => /^(?:p[trblxy]?|gap-|space-|min-h-|max-w-|w-full|flex|grid|relative)/.test(base));
|
|
3485
|
+
return hasSurfaceBackground && hasContainerShape && hasContainerStructure;
|
|
3486
|
+
}
|
|
3487
|
+
function looksLikePillBadge(tagName, classes) {
|
|
3488
|
+
const bases = classes.map(utils_1.classBase);
|
|
3489
|
+
const baseSet = new Set(bases);
|
|
3490
|
+
const allowedTag = ['div', 'span', 'small', 'p', 'li'].includes(tagName);
|
|
3491
|
+
const hasPillShape = baseSet.has('rounded-full') || bases.some((base) => /^rounded-\[/.test(base));
|
|
3492
|
+
const hasCompactSpacing = bases.some((base) => /^px-(?:2|2\.5|3|4|5|6)$/.test(base)) &&
|
|
3493
|
+
bases.some((base) => /^py-(?:1|1\.5|2|2\.5|3)$/.test(base));
|
|
3494
|
+
const hasBadgeText = baseSet.has('uppercase') ||
|
|
3495
|
+
classes.some((className) => /(?:^|:)text-(?:xs|sm|\[(?:10|11|12|13|14)px\])$/.test(className));
|
|
3496
|
+
const hasCompoundPillLayout = bases.some((base) => /^gap-/.test(base));
|
|
3497
|
+
const hasBadgeSurface = bases.some((base) => /^bg-/.test(base)) ||
|
|
3498
|
+
bases.some((base) => /^border/.test(base)) ||
|
|
3499
|
+
bases.some((base) => /^shadow/.test(base));
|
|
3500
|
+
const hasInlineLayout = baseSet.has('inline-flex') ||
|
|
3501
|
+
baseSet.has('inline-grid') ||
|
|
3502
|
+
(baseSet.has('absolute') && bases.some((base) => /^bottom-|^top-/.test(base)));
|
|
3503
|
+
return (allowedTag &&
|
|
3504
|
+
hasPillShape &&
|
|
3505
|
+
hasCompactSpacing &&
|
|
3506
|
+
(hasBadgeText || hasCompoundPillLayout) &&
|
|
3507
|
+
hasBadgeSurface &&
|
|
3508
|
+
hasInlineLayout);
|
|
3509
|
+
}
|
|
2597
3510
|
function stripTags(value) {
|
|
2598
3511
|
return value.replace(/<[^>]+>/g, ' ').replace(/\{[{%][\s\S]*?[}%]\}/g, ' ');
|
|
2599
3512
|
}
|