inkbridge 0.1.0-beta.2 → 0.1.0-beta.21
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 +108 -25
- package/bin/inkbridge.mjs +354 -83
- package/code.js +40 -11802
- package/manifest.json +1 -0
- package/package.json +74 -23
- package/scanner/adapter-utils-regression.ts +159 -0
- package/scanner/aspect-percent-position-regression.ts +237 -0
- package/scanner/aspect-ratio-regression.ts +90 -0
- package/scanner/blob-placement-regression.ts +2 -2
- package/scanner/block-cache-regression.ts +195 -0
- package/scanner/bundle-size-regression.ts +50 -0
- package/scanner/child-sizing-matrix-regression.ts +303 -0
- package/scanner/cli.ts +342 -13
- package/scanner/component-scanner.ts +2108 -174
- package/scanner/component-sections-regression.ts +198 -0
- package/scanner/compound-classes-lookup-regression.ts +163 -0
- package/scanner/css-token-reader-regression.ts +7 -6
- package/scanner/css-token-reader.ts +152 -31
- package/scanner/cva-jsx-child-fallback-regression.ts +98 -0
- package/scanner/cva-master-icon-regression.ts +315 -0
- package/scanner/data-attr-prop-alias-regression.ts +129 -0
- package/scanner/explicit-size-root-regression.ts +102 -0
- package/scanner/font-family-extract-regression.ts +113 -0
- package/scanner/font-style-resolver-regression.ts +1 -1
- package/scanner/framework-adapter-shadcn-regression.ts +480 -0
- package/scanner/full-width-matrix-regression.ts +338 -0
- package/scanner/grid-cols-extraction-regression.ts +110 -0
- package/scanner/image-src-collector-regression.ts +204 -0
- package/scanner/inline-flex-regression.ts +235 -0
- package/scanner/input-range-regression.ts +217 -0
- package/scanner/instance-rendering-regression.ts +224 -0
- package/scanner/jsx-prop-unresolved-regression.ts +178 -0
- package/scanner/jsx-text-regression.ts +178 -0
- package/scanner/layout-alignment-regression.ts +108 -0
- package/scanner/layout-flex-regression.ts +90 -0
- package/scanner/layout-mode-regression.ts +71 -0
- package/scanner/layout-sizing-regression.ts +227 -0
- package/scanner/layout-spacing-regression.ts +135 -0
- package/scanner/local-const-className-regression.ts +331 -0
- package/scanner/percent-position-regression.ts +105 -0
- package/scanner/provider-cascade-regression.ts +224 -0
- package/scanner/provider-flatten-regression.ts +235 -0
- package/scanner/radial-gradient-regression.ts +1 -1
- package/scanner/render-prop-parser-regression.ts +161 -0
- package/scanner/ring-utility-regression.ts +153 -0
- package/scanner/sandbox-spread-regression.ts +125 -0
- package/scanner/selection-pressed-regression.ts +241 -0
- package/scanner/size-full-normalization-regression.ts +127 -0
- package/scanner/state-classification-regression.ts +175 -0
- package/scanner/story-diagnostics-regression.ts +216 -0
- package/scanner/story-dimensioning-regression.ts +298 -0
- package/scanner/story-render-strategy-regression.ts +205 -0
- package/scanner/stretch-to-parent-width-regression.ts +147 -0
- package/scanner/svg-fill-parent-regression.ts +98 -0
- package/scanner/svg-group-inheritance-regression.ts +166 -0
- package/scanner/svg-marker-inline-regression.ts +211 -0
- package/scanner/svg-marker-regression.ts +116 -0
- package/scanner/tailwind-parser.ts +46 -4
- package/scanner/text-resize-matrix-regression.ts +173 -0
- package/scanner/transform-math-regression.ts +1 -1
- package/scanner/types.ts +26 -2
- package/src/cache/frame-cache.ts +150 -0
- package/src/cache/index.ts +2 -0
- package/src/{component-defs.ts → components/component-defs.ts} +25 -10
- package/src/{component-gen.ts → components/component-gen.ts} +43 -116
- package/src/components/component-instance.ts +386 -0
- package/src/components/component-library.ts +44 -0
- package/src/components/component-lookup.ts +161 -0
- package/src/components/index.ts +7 -0
- package/src/components/scanner-types.ts +39 -0
- package/src/components/symbol-instance-policy.ts +312 -0
- package/src/design-system/block-cache.ts +130 -0
- package/src/design-system/component-sections.ts +107 -0
- package/src/design-system/cva-inference.ts +187 -0
- package/src/design-system/cva-master.ts +427 -0
- package/src/design-system/cva-utils.ts +29 -0
- package/src/design-system/design-system.ts +334 -0
- package/src/design-system/frame-stabilizers.ts +191 -0
- package/src/design-system/frame-utils.ts +46 -0
- package/src/design-system/generated-node.ts +84 -0
- package/src/design-system/icon-rendering.ts +229 -0
- package/src/design-system/index.ts +13 -0
- package/src/design-system/instance-rendering.ts +307 -0
- package/src/design-system/master-shared.ts +133 -0
- package/src/design-system/node-helpers.ts +237 -0
- package/src/design-system/node-variants.ts +196 -0
- package/src/design-system/non-cva-master.ts +104 -0
- package/src/design-system/portal-handling.ts +138 -0
- package/src/design-system/preview-builder.ts +738 -0
- package/src/{render-context.ts → design-system/render-context.ts} +32 -6
- package/src/design-system/render-prop-parser.ts +50 -0
- package/src/design-system/responsive-resolver.ts +180 -0
- package/src/design-system/selectable-state.ts +157 -0
- package/src/design-system/state-master.ts +267 -0
- package/src/design-system/state-utils.ts +15 -0
- package/src/design-system/story-builder-context.ts +40 -0
- package/src/design-system/story-builder.ts +1322 -0
- package/src/design-system/story-diagnostics.ts +80 -0
- package/src/design-system/story-dimensioning.ts +272 -0
- package/src/design-system/story-frames.ts +400 -0
- package/src/design-system/story-instance.ts +333 -0
- package/src/{story-layout.ts → design-system/story-layout.ts} +2 -2
- package/src/design-system/story-render-strategy.ts +150 -0
- package/src/design-system/story-tree-search.ts +110 -0
- package/src/design-system/symbol-fallback.ts +89 -0
- package/src/design-system/symbol-source.ts +172 -0
- package/src/design-system/table-helpers.ts +56 -0
- package/src/design-system/tag-predicates.ts +99 -0
- package/src/design-system/theme-context.ts +52 -0
- package/src/design-system/typography.ts +100 -0
- package/src/design-system/ui-builder.ts +2676 -0
- package/src/{clip-path-decorative.ts → effects/clip-path-decorative.ts} +11 -11
- package/src/effects/icon-builder.ts +1074 -0
- package/src/effects/index.ts +5 -0
- package/src/effects/portal-panel.ts +369 -0
- package/src/{radial-gradient.ts → effects/radial-gradient.ts} +1 -1
- package/src/framework-adapters/index.ts +47 -0
- package/src/framework-adapters/shadcn.ts +541 -0
- package/src/{github.ts → github/github.ts} +46 -21
- package/src/github/index.ts +1 -0
- package/src/layout/deferred-layout.ts +1556 -0
- package/src/layout/index.ts +24 -0
- package/src/layout/layout-parser.ts +375 -0
- package/src/{layout-utils.ts → layout/layout-utils.ts} +23 -17
- package/src/layout/parser/alignment.ts +54 -0
- package/src/layout/parser/flex.ts +59 -0
- package/src/layout/parser/index.ts +65 -0
- package/src/layout/parser/ir.ts +80 -0
- package/src/layout/parser/layout-mode.ts +57 -0
- package/src/layout/parser/sizing.ts +241 -0
- package/src/layout/parser/spacing-scale.ts +78 -0
- package/src/layout/parser/spacing.ts +134 -0
- package/src/layout/ring-utils.ts +120 -0
- package/src/layout/size-utils.ts +143 -0
- package/src/layout/text-resize-decision.ts +51 -0
- package/src/{width-solver.ts → layout/width-solver.ts} +168 -37
- package/src/main.ts +444 -162
- package/src/{config.ts → plugin/config.ts} +12 -12
- package/src/{dev-server.ts → plugin/dev-server.ts} +3 -3
- package/src/plugin/image-src-collector.ts +52 -0
- package/src/plugin/index.ts +3 -0
- package/src/plugin/packs/index.ts +2 -0
- package/src/{pack-provider.ts → plugin/packs/pack-provider.ts} +12 -12
- package/src/{packs.ts → plugin/packs/packs.ts} +22 -17
- package/src/render-engine-version.ts +2 -0
- package/src/tailwind/adapter-utils.ts +137 -0
- package/src/{class-utils.ts → tailwind/class-utils.ts} +33 -6
- package/src/tailwind/index.ts +8 -0
- package/src/tailwind/jsx-utils.ts +319 -0
- package/src/{node-ir.ts → tailwind/node-ir.ts} +208 -19
- package/src/{responsive-analyzer.ts → tailwind/responsive-analyzer.ts} +32 -2
- package/src/{state-analyzer.ts → tailwind/state-analyzer.ts} +71 -5
- package/src/{tailwind.ts → tailwind/tailwind.ts} +423 -674
- package/src/{utility-resolver.ts → tailwind/utility-resolver.ts} +27 -6
- package/src/{font-style-resolver.ts → text/font-style-resolver.ts} +0 -2
- package/src/text/index.ts +4 -0
- package/src/{inline-text.ts → text/inline-text.ts} +13 -13
- package/src/{text-builder.ts → text/text-builder.ts} +24 -7
- package/src/{text-line.ts → text/text-line.ts} +2 -2
- package/src/{change-detection.ts → tokens/change-detection.ts} +12 -12
- package/src/{color-resolver.ts → tokens/color-resolver.ts} +1 -6
- package/src/{colors.ts → tokens/colors.ts} +13 -6
- package/src/tokens/index.ts +6 -0
- package/src/{token-source.ts → tokens/token-source.ts} +4 -1
- package/src/{tokens.ts → tokens/tokens.ts} +116 -20
- package/src/{variables.ts → tokens/variables.ts} +447 -102
- package/templates/patch-tokens-route.ts +25 -6
- package/templates/scan-components-route.ts +26 -5
- package/ui.html +485 -37
- package/src/component-lookup.ts +0 -82
- package/src/design-system.ts +0 -59
- package/src/icon-builder.ts +0 -607
- package/src/layout-parser.ts +0 -667
- package/src/story-builder.ts +0 -1706
- package/src/ui-builder.ts +0 -1996
- /package/src/{image-cache.ts → cache/image-cache.ts} +0 -0
- /package/src/{blob-placement.ts → effects/blob-placement.ts} +0 -0
- /package/src/{transform-math.ts → tailwind/transform-math.ts} +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { splitClassName, applyTailwindStylesToFrame, tailwindClassesToStyle } from '../tailwind';
|
|
2
|
+
import { parseColor } from '../tokens';
|
|
3
|
+
import { createTextNode } from '../text';
|
|
4
|
+
import { enforceFixedBoxSizingAfterLayout } from '../layout';
|
|
5
|
+
import {
|
|
6
|
+
analyzeSymbolPolicy,
|
|
7
|
+
extractCompoundSlotTextOverridesFromProps,
|
|
8
|
+
getInstantiableComponent,
|
|
9
|
+
tryCreateCvaComponentInstance as tryCreateCvaComponentInstanceShared,
|
|
10
|
+
tryCreateNonCvaComponentInstance as tryCreateNonCvaComponentInstanceShared,
|
|
11
|
+
} from '../components';
|
|
12
|
+
import { setGeneratedFallbackReason, setGeneratedSymbolDebugData } from './generated-node';
|
|
13
|
+
import { getThemeContext } from './theme-context';
|
|
14
|
+
import {
|
|
15
|
+
toFigmaVariantPropertyName,
|
|
16
|
+
toFigmaVariantPropertyValue,
|
|
17
|
+
ENABLE_SYMBOL_MASTERS,
|
|
18
|
+
} from './master-shared';
|
|
19
|
+
import {
|
|
20
|
+
ensureStateComponentSet,
|
|
21
|
+
isCheckedInstance,
|
|
22
|
+
hasCheckedStateVariant,
|
|
23
|
+
getStateVariantForInstance,
|
|
24
|
+
} from './state-master';
|
|
25
|
+
import {
|
|
26
|
+
ensureCvaComponentSet,
|
|
27
|
+
getCvaSelectionFromInstance,
|
|
28
|
+
isTruthyVariantValue,
|
|
29
|
+
applyStyleToFrame,
|
|
30
|
+
getCvaSymbolFallbackReason,
|
|
31
|
+
} from './cva-master';
|
|
32
|
+
import { buildCvaClassesWithSelection } from './cva-utils';
|
|
33
|
+
import { getPreviewTypography } from './typography';
|
|
34
|
+
import { createStateStoryFrame, createSimpleStoryFrame } from './story-frames';
|
|
35
|
+
import { ensureNonCvaComponentMaster } from './non-cva-master';
|
|
36
|
+
import {
|
|
37
|
+
getInstanceTextOverride,
|
|
38
|
+
applyTextOverrideToInstance,
|
|
39
|
+
getNonCvaSymbolFallbackReason,
|
|
40
|
+
getInstanceChildrenText,
|
|
41
|
+
} from './symbol-fallback';
|
|
42
|
+
import {
|
|
43
|
+
normalizeComponentName,
|
|
44
|
+
findBestStoryElementForClasses,
|
|
45
|
+
findFirstStoryText,
|
|
46
|
+
} from './story-tree-search';
|
|
47
|
+
import type { StoryBuilderContext } from './story-builder-context';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Top-level story-instance entrypoints. The createX functions try the
|
|
51
|
+
* symbol-instance path first (via tryCreateXComponentInstance) and fall
|
|
52
|
+
* back to a freshly-rendered frame when no master is available.
|
|
53
|
+
*
|
|
54
|
+
* `componentInstanceBackend` is the wiring object passed to those
|
|
55
|
+
* try-create functions; it bundles all the predicates / accessors they
|
|
56
|
+
* need to read state from a def and apply text overrides to instances.
|
|
57
|
+
*
|
|
58
|
+
* The buildStoryX helpers are used by preview-builder for the state matrix
|
|
59
|
+
* and responsive previews.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
type CompoundTextOverrides = {
|
|
63
|
+
bySubcomponent: Record<string, string>;
|
|
64
|
+
bySlot: Record<string, string>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
function buildCompoundTextOverrides(def: any, story: any, instance?: any): CompoundTextOverrides {
|
|
69
|
+
const bySubcomponent: Record<string, string> = {};
|
|
70
|
+
const bySlot = extractCompoundSlotTextOverridesFromProps(def, instance && instance.props ? instance.props : {});
|
|
71
|
+
if (!def || def.type !== 'compound') return { bySubcomponent, bySlot };
|
|
72
|
+
|
|
73
|
+
const subComponents = Array.isArray(def.subComponents) ? def.subComponents : [];
|
|
74
|
+
const instances = (story && Array.isArray(story.instances)) ? story.instances : [];
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < subComponents.length; i++) {
|
|
77
|
+
const sub = subComponents[i];
|
|
78
|
+
const subName = normalizeComponentName(sub && sub.name ? sub.name : '');
|
|
79
|
+
const slotName = String(sub && sub.slot ? sub.slot : '').toLowerCase();
|
|
80
|
+
if (!subName) continue;
|
|
81
|
+
if (slotName && bySlot[slotName]) {
|
|
82
|
+
bySubcomponent[subName] = bySlot[slotName];
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let j = 0; j < instances.length; j++) {
|
|
87
|
+
const instance = instances[j];
|
|
88
|
+
const instanceName = normalizeComponentName(instance && instance.componentName ? instance.componentName : '');
|
|
89
|
+
if (!instanceName || instanceName !== subName) continue;
|
|
90
|
+
const text = getInstanceChildrenText(instance);
|
|
91
|
+
if (!text) continue;
|
|
92
|
+
bySubcomponent[subName] = text;
|
|
93
|
+
if (slotName && !bySlot[slotName]) bySlot[slotName] = text;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (story && story.jsxTree) {
|
|
99
|
+
for (let i = 0; i < subComponents.length; i++) {
|
|
100
|
+
const sub = subComponents[i];
|
|
101
|
+
const subName = normalizeComponentName(sub && sub.name ? sub.name : '');
|
|
102
|
+
const slotName = String(sub && sub.slot ? sub.slot : '').toLowerCase();
|
|
103
|
+
const subClasses = Array.isArray(sub && sub.classes) ? sub.classes : [];
|
|
104
|
+
if (!subName || bySubcomponent[subName]) continue;
|
|
105
|
+
const match = findBestStoryElementForClasses(story.jsxTree, subClasses);
|
|
106
|
+
const text = findFirstStoryText(match);
|
|
107
|
+
if (!text) continue;
|
|
108
|
+
bySubcomponent[subName] = text;
|
|
109
|
+
if (slotName && !bySlot[slotName]) bySlot[slotName] = text;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { bySubcomponent, bySlot };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
function findFirstTextByAncestorPredicate(node: any, predicate: (name: string) => boolean, ancestry?: string[]): any | null {
|
|
118
|
+
if (!node) return null;
|
|
119
|
+
const trail = ancestry ? ancestry.slice() : [];
|
|
120
|
+
const nodeName = String(node.name || '');
|
|
121
|
+
if (nodeName) trail.push(nodeName);
|
|
122
|
+
|
|
123
|
+
if (node.type === 'TEXT') {
|
|
124
|
+
for (let i = 0; i < trail.length; i++) {
|
|
125
|
+
if (predicate(trail[i])) return node;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!Array.isArray(node.children)) return null;
|
|
130
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
131
|
+
const found = findFirstTextByAncestorPredicate(node.children[i], predicate, trail);
|
|
132
|
+
if (found) return found;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function applyCompoundTextOverridesToInstance(
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
+
def: any,
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
|
+
story: any,
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
storyInstance: any,
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
instanceNode: any
|
|
146
|
+
): { bySubcomponent: Record<string, string>; bySlot: Record<string, string> } {
|
|
147
|
+
const overrides = buildCompoundTextOverrides(def, story, storyInstance);
|
|
148
|
+
const appliedBySubcomponent: Record<string, string> = {};
|
|
149
|
+
const appliedBySlot: Record<string, string> = {};
|
|
150
|
+
const subComponents = Array.isArray(def && def.subComponents) ? def.subComponents : [];
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < subComponents.length; i++) {
|
|
153
|
+
const sub = subComponents[i];
|
|
154
|
+
const subNameRaw = String(sub && sub.name ? sub.name : '');
|
|
155
|
+
const subName = normalizeComponentName(subNameRaw);
|
|
156
|
+
const slotName = String(sub && sub.slot ? sub.slot : '').toLowerCase();
|
|
157
|
+
const value = overrides.bySubcomponent[subName] || (slotName ? overrides.bySlot[slotName] : '');
|
|
158
|
+
if (!value) continue;
|
|
159
|
+
|
|
160
|
+
let textNode = findFirstTextByAncestorPredicate(
|
|
161
|
+
instanceNode,
|
|
162
|
+
function(name: string): boolean {
|
|
163
|
+
return normalizeComponentName(name) === subName;
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (!textNode && slotName) {
|
|
168
|
+
textNode = findFirstTextByAncestorPredicate(
|
|
169
|
+
instanceNode,
|
|
170
|
+
function(name: string): boolean {
|
|
171
|
+
return normalizeComponentName(name).indexOf(normalizeComponentName(slotName)) !== -1;
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!textNode) continue;
|
|
177
|
+
try {
|
|
178
|
+
textNode.characters = String(value);
|
|
179
|
+
if (subName) appliedBySubcomponent[subName] = String(value);
|
|
180
|
+
if (slotName) appliedBySlot[slotName] = String(value);
|
|
181
|
+
} catch (_error) {
|
|
182
|
+
// If override is blocked by component structure, keep default text.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { bySubcomponent: appliedBySubcomponent, bySlot: appliedBySlot };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const componentInstanceBackend = {
|
|
189
|
+
enableSymbolMasters: ENABLE_SYMBOL_MASTERS,
|
|
190
|
+
splitClassName,
|
|
191
|
+
ensureCvaComponentSet,
|
|
192
|
+
ensureStateComponentSet,
|
|
193
|
+
ensureNonCvaComponentMaster,
|
|
194
|
+
getInstantiableComponent,
|
|
195
|
+
getCvaSelectionFromInstance,
|
|
196
|
+
toFigmaVariantPropertyName,
|
|
197
|
+
toFigmaVariantPropertyValue,
|
|
198
|
+
getStateVariantForInstance,
|
|
199
|
+
isCheckedInstance,
|
|
200
|
+
hasCheckedStateVariant,
|
|
201
|
+
getInstanceTextOverride,
|
|
202
|
+
applyTextOverrideToInstance,
|
|
203
|
+
applyCompoundTextOverridesToInstance,
|
|
204
|
+
analyzeSymbolPolicy,
|
|
205
|
+
setGeneratedSymbolDebugData,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
+
export function buildStatePreviewLabel(def: any, instance: any): string {
|
|
210
|
+
const props = instance && instance.props ? instance.props : {};
|
|
211
|
+
if (instance && instance.children) return String(instance.children);
|
|
212
|
+
if (props.children) return String(props.children);
|
|
213
|
+
if (props.placeholder) return String(props.placeholder);
|
|
214
|
+
if (props.defaultValue) return String(props.defaultValue);
|
|
215
|
+
return '';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
|
+
export function buildStoryStateClasses(def: any, instance: any): string[] {
|
|
220
|
+
const props = instance && instance.props ? instance.props : {};
|
|
221
|
+
const classes: string[] = [];
|
|
222
|
+
|
|
223
|
+
if (def && def.baseClasses) {
|
|
224
|
+
for (let i = 0; i < def.baseClasses.length; i++) classes.push(def.baseClasses[i]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (def && def.type === 'cva') {
|
|
228
|
+
const variantKeys = Object.keys(def.variants || {});
|
|
229
|
+
for (let i = 0; i < variantKeys.length; i++) {
|
|
230
|
+
const key = variantKeys[i];
|
|
231
|
+
const value = props[key] || (def.defaultVariants && def.defaultVariants[key]) || (def.variants[key] && def.variants[key][0]);
|
|
232
|
+
if (value && def.variantClasses && def.variantClasses[key] && def.variantClasses[key][value]) {
|
|
233
|
+
const variantClasses = def.variantClasses[key][value];
|
|
234
|
+
for (let j = 0; j < variantClasses.length; j++) classes.push(variantClasses[j]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const extra = splitClassName(props.className);
|
|
240
|
+
for (let i = 0; i < extra.length; i++) classes.push(extra[i]);
|
|
241
|
+
return classes;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
245
|
+
export function buildStoryInstanceClasses(def: any, instance: any): string[] {
|
|
246
|
+
const props = instance && instance.props ? instance.props : {};
|
|
247
|
+
let classes: string[] = def && def.baseClasses ? def.baseClasses.slice() : [];
|
|
248
|
+
|
|
249
|
+
if (def && def.type === 'cva') {
|
|
250
|
+
const variantKeys = Object.keys(def.variants || {});
|
|
251
|
+
for (let i = 0; i < variantKeys.length; i++) {
|
|
252
|
+
const key = variantKeys[i];
|
|
253
|
+
const value = props[key]
|
|
254
|
+
|| (def.defaultVariants && def.defaultVariants[key])
|
|
255
|
+
|| (def.variants && def.variants[key] && def.variants[key][0]);
|
|
256
|
+
if (value && def.variantClasses && def.variantClasses[key] && def.variantClasses[key][value]) {
|
|
257
|
+
classes = classes.concat(def.variantClasses[key][value]);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const extra = splitClassName(props.className);
|
|
263
|
+
if (extra.length > 0) {
|
|
264
|
+
classes = classes.concat(extra);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return classes;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
|
+
export function createCVAStoryInstance(def: any, instance: any, theme: string, ctx: StoryBuilderContext): any {
|
|
272
|
+
const componentInstance = tryCreateCvaComponentInstanceShared(def, instance, theme, ctx, componentInstanceBackend);
|
|
273
|
+
if (componentInstance) {
|
|
274
|
+
return componentInstance;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const themeContext = getThemeContext(theme);
|
|
278
|
+
const colorGroup = themeContext.colorGroup;
|
|
279
|
+
const radiusGroup = themeContext.radiusGroup;
|
|
280
|
+
const props = instance.props || {};
|
|
281
|
+
|
|
282
|
+
const selection = getCvaSelectionFromInstance(def, instance);
|
|
283
|
+
const classes = buildCvaClassesWithSelection(def, selection, props.className);
|
|
284
|
+
|
|
285
|
+
const comp = figma.createFrame();
|
|
286
|
+
comp.name = def.name;
|
|
287
|
+
comp.primaryAxisSizingMode = 'AUTO';
|
|
288
|
+
comp.counterAxisSizingMode = 'AUTO';
|
|
289
|
+
comp.counterAxisAlignItems = 'CENTER';
|
|
290
|
+
comp.primaryAxisAlignItems = 'CENTER';
|
|
291
|
+
comp.itemSpacing = 8;
|
|
292
|
+
comp.fills = [];
|
|
293
|
+
comp.strokes = [];
|
|
294
|
+
ctx.applyClipBehavior(comp, classes);
|
|
295
|
+
|
|
296
|
+
applyTailwindStylesToFrame(comp, classes, colorGroup, radiusGroup, theme);
|
|
297
|
+
|
|
298
|
+
const state = isTruthyVariantValue(props.disabled) ? 'disabled' : 'default';
|
|
299
|
+
const style = tailwindClassesToStyle(classes, state, colorGroup);
|
|
300
|
+
applyStyleToFrame(comp, style, theme);
|
|
301
|
+
|
|
302
|
+
const label = instance.children || props.children || def.name;
|
|
303
|
+
const typography = getPreviewTypography(classes);
|
|
304
|
+
const textColor = style.text ? parseColor(style.text) :
|
|
305
|
+
(colorGroup.foreground ? parseColor(colorGroup.foreground) : { r: 0, g: 0, b: 0 });
|
|
306
|
+
const text = createTextNode(label, { bold: typography.bold, fontSize: typography.fontSize, fill: textColor });
|
|
307
|
+
if (style.underline && text.textDecoration !== undefined) {
|
|
308
|
+
text.textDecoration = 'UNDERLINE';
|
|
309
|
+
}
|
|
310
|
+
comp.appendChild(text);
|
|
311
|
+
enforceFixedBoxSizingAfterLayout(comp, classes);
|
|
312
|
+
setGeneratedFallbackReason(comp, getCvaSymbolFallbackReason(def, instance));
|
|
313
|
+
|
|
314
|
+
return comp;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
|
+
export function createStateStoryInstance(def: any, instance: any, theme: string, ctx: StoryBuilderContext, story?: any): any {
|
|
319
|
+
const symbolInstance = tryCreateNonCvaComponentInstanceShared(def, instance, theme, ctx, componentInstanceBackend, story);
|
|
320
|
+
if (symbolInstance) {
|
|
321
|
+
return symbolInstance;
|
|
322
|
+
}
|
|
323
|
+
return createStateStoryFrame(def, instance, theme, ctx, getNonCvaSymbolFallbackReason(def, instance), story);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
+
export function createSimpleStoryInstance(def: any, instance: any, theme: string, ctx: StoryBuilderContext, story?: any): any {
|
|
328
|
+
const symbolInstance = tryCreateNonCvaComponentInstanceShared(def, instance, theme, ctx, componentInstanceBackend, story);
|
|
329
|
+
if (symbolInstance) {
|
|
330
|
+
return symbolInstance;
|
|
331
|
+
}
|
|
332
|
+
return createSimpleStoryFrame(def, instance, theme, ctx, getNonCvaSymbolFallbackReason(def, instance));
|
|
333
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type JsxNode, type JsxElement, splitClassName } from '
|
|
2
|
-
import { extractGridColumns, extractGridBreakpointWidth } from '
|
|
1
|
+
import { type JsxNode, type JsxElement, splitClassName } from '../tailwind';
|
|
2
|
+
import { extractGridColumns, extractGridBreakpointWidth } from '../layout';
|
|
3
3
|
|
|
4
4
|
export function extractGridColumnsFromTree(node: JsxNode | undefined): number | null {
|
|
5
5
|
if (!node || node.type !== 'element') return null;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { splitClassName, type JsxElement, type JsxNode } from '../tailwind';
|
|
2
|
+
import type { ComponentDef, ComponentStory } from '../components';
|
|
3
|
+
import { findMatchingInstance, normalizeComponentName } from './story-tree-search';
|
|
4
|
+
import { hasCheckedStateVariant } from './state-master';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Components that prefer instance rendering even when a JSX tree is available.
|
|
8
|
+
* `switch` lives here because the input element + indicator slot are hard to
|
|
9
|
+
* reconstruct correctly from the JSX tree alone — the symbol master is more
|
|
10
|
+
* reliable than the tree expansion.
|
|
11
|
+
*/
|
|
12
|
+
export const INSTANCE_FALLBACK_COMPONENTS = new Set(['switch']);
|
|
13
|
+
|
|
14
|
+
// Tree-rendering budget. Stories that exceed any of these limits fall back to
|
|
15
|
+
// symbol-instance rendering. Tuned empirically so the realistic component
|
|
16
|
+
// catalogue still renders as a tree, but pathological deep stories (giant
|
|
17
|
+
// marketing pages, accidentally-recursive trees) skip the tree path and render
|
|
18
|
+
// as a labelled instance frame instead.
|
|
19
|
+
const STORY_JSX_NODE_LIMIT = 320;
|
|
20
|
+
const STORY_JSX_CLASS_TOKEN_LIMIT = 1800;
|
|
21
|
+
const STORY_JSX_DEPTH_LIMIT = 36;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Walks the JSX tree once, counting nodes, max depth, and total class tokens.
|
|
25
|
+
* Returns true as soon as any limit is exceeded. Pure data — the only side
|
|
26
|
+
* effect is the early return.
|
|
27
|
+
*/
|
|
28
|
+
export function exceedsStoryJsxComplexityLimits(tree: JsxNode | null | undefined): boolean {
|
|
29
|
+
if (!tree || typeof tree !== 'object') return false;
|
|
30
|
+
let nodeCount = 0;
|
|
31
|
+
let classTokenCount = 0;
|
|
32
|
+
let maxDepth = 0;
|
|
33
|
+
const stack: Array<{ node: JsxNode; depth: number }> = [{ node: tree, depth: 0 }];
|
|
34
|
+
|
|
35
|
+
while (stack.length > 0) {
|
|
36
|
+
const current = stack.pop();
|
|
37
|
+
if (!current || !current.node) continue;
|
|
38
|
+
|
|
39
|
+
nodeCount += 1;
|
|
40
|
+
if (current.depth > maxDepth) maxDepth = current.depth;
|
|
41
|
+
if (nodeCount > STORY_JSX_NODE_LIMIT) return true;
|
|
42
|
+
if (maxDepth > STORY_JSX_DEPTH_LIMIT) return true;
|
|
43
|
+
|
|
44
|
+
if (current.node.type === 'element') {
|
|
45
|
+
const el = current.node as JsxElement;
|
|
46
|
+
const rawClassName = el.props ? el.props.className : '';
|
|
47
|
+
if (rawClassName) {
|
|
48
|
+
classTokenCount += splitClassName(String(rawClassName)).length;
|
|
49
|
+
if (classTokenCount > STORY_JSX_CLASS_TOKEN_LIMIT) return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const children = Array.isArray(el.children) ? el.children : [];
|
|
53
|
+
for (let i = 0; i < children.length; i++) {
|
|
54
|
+
stack.push({ node: children[i], depth: current.depth + 1 });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns true when story-tree rendering should be skipped in favour of the
|
|
64
|
+
* instance path. Two reasons:
|
|
65
|
+
* - The component is on the explicit instance-fallback list (e.g. switch).
|
|
66
|
+
* - The tree exceeds the complexity budget (`exceedsStoryJsxComplexityLimits`).
|
|
67
|
+
*/
|
|
68
|
+
export function shouldSkipStoryJsxTree(def: ComponentDef, story: ComponentStory): boolean {
|
|
69
|
+
if (!story || !story.jsxTree) return false;
|
|
70
|
+
const defName = normalizeComponentName(def && def.name ? def.name : '');
|
|
71
|
+
if (defName && INSTANCE_FALLBACK_COMPONENTS.has(defName)) return true;
|
|
72
|
+
return exceedsStoryJsxComplexityLimits(story.jsxTree);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns true when instance rendering should be preferred even though a JSX
|
|
77
|
+
* tree exists. Used by CVA and (most) state components, which represent
|
|
78
|
+
* variant matrices more compactly as instances than as expanded trees.
|
|
79
|
+
* Skipped when the story passes JSX children (manual content overrides),
|
|
80
|
+
* or when the def is on the explicit fallback list (those need true instance
|
|
81
|
+
* rendering, not the symbol-instance shortcut).
|
|
82
|
+
*/
|
|
83
|
+
export function shouldPreferInstanceRendering(def: ComponentDef, story: ComponentStory): boolean {
|
|
84
|
+
if (!def || !story) return false;
|
|
85
|
+
const defKey = normalizeComponentName(def && def.name ? def.name : '');
|
|
86
|
+
if (!defKey) return false;
|
|
87
|
+
const storyInstances = Array.isArray(story.instances) ? story.instances : [];
|
|
88
|
+
if (storyInstances.length > 0) {
|
|
89
|
+
const rootKey = normalizeComponentName(storyInstances[0] && storyInstances[0].componentName ? storyInstances[0].componentName : '');
|
|
90
|
+
// Do not treat nested subcomponents as the root story preview source.
|
|
91
|
+
if (rootKey && rootKey !== defKey) return false;
|
|
92
|
+
}
|
|
93
|
+
const matched = findMatchingInstance(def, story);
|
|
94
|
+
if (!matched) return false;
|
|
95
|
+
const normalizedName = defKey;
|
|
96
|
+
if (INSTANCE_FALLBACK_COMPONENTS.has(normalizedName)) return false;
|
|
97
|
+
const matchedProps = matched && matched.props ? matched.props : null;
|
|
98
|
+
if (matchedProps && Array.isArray(matchedProps.__jsxChildren) && matchedProps.__jsxChildren.length > 0) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const analysis = def && def.analysis ? def.analysis : def;
|
|
102
|
+
if (!analysis) return false;
|
|
103
|
+
const type = String(analysis.type || '').toLowerCase();
|
|
104
|
+
if (type === 'cva') return true;
|
|
105
|
+
if (type === 'state') {
|
|
106
|
+
// The scanner classifies a component as `state` based on the presence
|
|
107
|
+
// of a `data-[disabled]:` / `aria-invalid:` / `data-[state=…]:` modifier
|
|
108
|
+
// somewhere in its className. That's a good signal for input-shaped
|
|
109
|
+
// primitives (one element, a few state variants) but a false positive
|
|
110
|
+
// for compound primitives like Slider whose body is a track + thumb +
|
|
111
|
+
// indicator structure — the state-matrix renderer can't represent the
|
|
112
|
+
// geometry. If the story's JSX tree contains multiple nested
|
|
113
|
+
// `data-slot` markers, the component is structurally compound; fall
|
|
114
|
+
// back to tree rendering so the layout actually renders.
|
|
115
|
+
if (jsxTreeHasNestedDataSlots(story.jsxTree)) return false;
|
|
116
|
+
const childrenText = matched && matched.children != null ? String(matched.children).trim() : '';
|
|
117
|
+
if (childrenText !== '' && hasCheckedStateVariant(analysis)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// For simple/compound components, prefer story JSX tree rendering so layout/content
|
|
123
|
+
// fidelity is preserved (e.g. portal wrappers, centered hero content, nested sections).
|
|
124
|
+
// Nested atoms are still rendered as symbol instances by renderJsxTree.
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* True when the story's JSX tree has 2+ elements carrying a `data-slot`
|
|
130
|
+
* marker. A `state`-classified component with this shape is actually
|
|
131
|
+
* compound (Slider, Progress, future Tabs, …) and the state-matrix
|
|
132
|
+
* renderer can't produce a faithful preview from it.
|
|
133
|
+
*/
|
|
134
|
+
export function jsxTreeHasNestedDataSlots(tree: JsxNode | null | undefined): boolean {
|
|
135
|
+
if (!tree || typeof tree !== 'object') return false;
|
|
136
|
+
let slotsFound = 0;
|
|
137
|
+
const stack: JsxNode[] = [tree];
|
|
138
|
+
while (stack.length > 0) {
|
|
139
|
+
const node = stack.pop();
|
|
140
|
+
if (!node || typeof node !== 'object' || node.type !== 'element') continue;
|
|
141
|
+
const el = node as JsxElement;
|
|
142
|
+
if (el.props && typeof el.props['data-slot'] === 'string' && el.props['data-slot'].length > 0) {
|
|
143
|
+
slotsFound += 1;
|
|
144
|
+
if (slotsFound >= 2) return true;
|
|
145
|
+
}
|
|
146
|
+
const children = Array.isArray(el.children) ? el.children : [];
|
|
147
|
+
for (let i = 0; i < children.length; i++) stack.push(children[i]);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { splitClassName } from '../tailwind';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure tree-search and component-name helpers used across the design-system
|
|
5
|
+
* page builder. Kept in their own module so story-instance and the master
|
|
6
|
+
* builders can share them without re-importing from story-builder.ts (which
|
|
7
|
+
* would otherwise create cycles via cva-master / state-master callbacks).
|
|
8
|
+
*
|
|
9
|
+
* No file-level state, no Figma API calls — just JSX-tree traversal,
|
|
10
|
+
* class-set comparison, and name normalization.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function normalizeComponentName(name: string | undefined): string {
|
|
14
|
+
return String(name || '').toLowerCase().replace(/-/g, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
export function collectStoryJsxElements(node: any, out: any[]): void {
|
|
19
|
+
if (!node || node.type !== 'element') return;
|
|
20
|
+
out.push(node);
|
|
21
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
22
|
+
for (let i = 0; i < children.length; i++) {
|
|
23
|
+
collectStoryJsxElements(children[i], out);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
export function findFirstStoryText(node: any): string | null {
|
|
29
|
+
if (!node) return null;
|
|
30
|
+
if (node.type === 'text') {
|
|
31
|
+
const content = String(node.content || '').trim();
|
|
32
|
+
return content || null;
|
|
33
|
+
}
|
|
34
|
+
if (node.type !== 'element' || !Array.isArray(node.children)) return null;
|
|
35
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
36
|
+
const value = findFirstStoryText(node.children[i]);
|
|
37
|
+
if (value) return value;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
export function findBestStoryElementForClasses(jsxTree: any, classes: string[]): any | null {
|
|
44
|
+
if (!jsxTree || !Array.isArray(classes) || classes.length === 0) return null;
|
|
45
|
+
const targets = new Set(classes);
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
const elements: any[] = [];
|
|
48
|
+
collectStoryJsxElements(jsxTree, elements);
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
let best: any | null = null;
|
|
52
|
+
let bestOverlap = 0;
|
|
53
|
+
for (let i = 0; i < elements.length; i++) {
|
|
54
|
+
const el = elements[i];
|
|
55
|
+
const tokens = splitClassName(el && el.props ? el.props.className : '');
|
|
56
|
+
if (tokens.length === 0) continue;
|
|
57
|
+
let overlap = 0;
|
|
58
|
+
for (let j = 0; j < tokens.length; j++) {
|
|
59
|
+
if (targets.has(tokens[j])) overlap++;
|
|
60
|
+
}
|
|
61
|
+
if (overlap > bestOverlap) {
|
|
62
|
+
bestOverlap = overlap;
|
|
63
|
+
best = el;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!best || bestOverlap === 0) return null;
|
|
67
|
+
return best;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
export function getStoriesWithJsxTree(def: any): any[] {
|
|
72
|
+
const stories = def && Array.isArray(def.stories) ? def.stories : [];
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
const out: any[] = [];
|
|
75
|
+
for (let i = 0; i < stories.length; i++) {
|
|
76
|
+
const story = stories[i];
|
|
77
|
+
if (story && story.jsxTree) out.push(story);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function countClassOverlap(classes: string[], targetSet: Set<string>): number {
|
|
83
|
+
let overlap = 0;
|
|
84
|
+
for (let i = 0; i < classes.length; i++) {
|
|
85
|
+
if (targetSet.has(classes[i])) overlap++;
|
|
86
|
+
}
|
|
87
|
+
return overlap;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
|
+
export function findMatchingInstance(def: any, story: any): any | null {
|
|
92
|
+
const instances = story && story.instances ? story.instances : [];
|
|
93
|
+
const defKey = normalizeComponentName(def && def.name);
|
|
94
|
+
for (let i = 0; i < instances.length; i++) {
|
|
95
|
+
const instance = instances[i];
|
|
96
|
+
if (!instance || !instance.componentName) continue;
|
|
97
|
+
if (normalizeComponentName(instance.componentName) === defKey) return instance;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
export function findFirstMatchingInstance(def: any): any | null {
|
|
104
|
+
const stories = def && Array.isArray(def.stories) ? def.stories : [];
|
|
105
|
+
for (let i = 0; i < stories.length; i++) {
|
|
106
|
+
const instance = findMatchingInstance(def, stories[i]);
|
|
107
|
+
if (instance) return instance;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getNonDefaultSymbolPropReason, inferSafeTextOverridePropKeys } from '../components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helpers for resolving the text content that should override a symbol
|
|
5
|
+
* instance, plus the "why we can't use a symbol" reason strings the design
|
|
6
|
+
* system page records when it falls back to raw rendering.
|
|
7
|
+
*
|
|
8
|
+
* The cva-flavoured fallback reason (and the symbol-master warming pass)
|
|
9
|
+
* still live in story-builder.ts because they read CVA-master constants;
|
|
10
|
+
* they'll move out alongside the cva-master extraction.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export function getInstanceTextOverride(def: any, instance: any): string | null {
|
|
15
|
+
const props = instance && instance.props ? instance.props : {};
|
|
16
|
+
if (instance && instance.children != null && String(instance.children).trim() !== '') {
|
|
17
|
+
return String(instance.children);
|
|
18
|
+
}
|
|
19
|
+
if (props.children != null && String(props.children).trim() !== '') {
|
|
20
|
+
return String(props.children);
|
|
21
|
+
}
|
|
22
|
+
if (def && def.type !== 'compound') {
|
|
23
|
+
const inferredTextKeys = inferSafeTextOverridePropKeys(def);
|
|
24
|
+
for (let i = 0; i < inferredTextKeys.length; i++) {
|
|
25
|
+
const key = inferredTextKeys[i];
|
|
26
|
+
if (props[key] != null && String(props[key]).trim() !== '') {
|
|
27
|
+
return String(props[key]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (def && def.type === 'state') {
|
|
32
|
+
if (props.placeholder != null && String(props.placeholder).trim() !== '') {
|
|
33
|
+
return String(props.placeholder);
|
|
34
|
+
}
|
|
35
|
+
if (props.defaultValue != null && String(props.defaultValue).trim() !== '') {
|
|
36
|
+
return String(props.defaultValue);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
export function getNonCvaSymbolFallbackReason(def: any, instance: any): string {
|
|
44
|
+
if (!def || def.type === 'cva') return 'non_cva_not_applicable';
|
|
45
|
+
if (def.symbolCandidate === false) return 'symbol_candidate_disabled';
|
|
46
|
+
const propReason = getNonDefaultSymbolPropReason(def, instance);
|
|
47
|
+
if (propReason) return propReason;
|
|
48
|
+
return 'master_or_instance_unavailable';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
export function findFirstTextDescendant(node: any): any | null {
|
|
53
|
+
if (!node) return null;
|
|
54
|
+
if (node.type === 'TEXT') return node;
|
|
55
|
+
if (!Array.isArray(node.children)) return null;
|
|
56
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
57
|
+
const found = findFirstTextDescendant(node.children[i]);
|
|
58
|
+
if (found) return found;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
export function applyTextOverrideToInstance(instanceNode: any, value: string): boolean {
|
|
65
|
+
const text = String(value || '').trim();
|
|
66
|
+
if (!text) return false;
|
|
67
|
+
const textNode = findFirstTextDescendant(instanceNode);
|
|
68
|
+
if (!textNode) return false;
|
|
69
|
+
try {
|
|
70
|
+
textNode.characters = text;
|
|
71
|
+
return true;
|
|
72
|
+
} catch (_error) {
|
|
73
|
+
// If override is blocked by component structure, keep default text.
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
export function getInstanceChildrenText(instance: any): string | null {
|
|
80
|
+
if (!instance) return null;
|
|
81
|
+
if (instance.children != null && String(instance.children).trim() !== '') {
|
|
82
|
+
return String(instance.children);
|
|
83
|
+
}
|
|
84
|
+
const props = instance.props || {};
|
|
85
|
+
if (props.children != null && String(props.children).trim() !== '') {
|
|
86
|
+
return String(props.children);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|