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,120 @@
|
|
|
1
|
+
import { parseColor } from '../tokens';
|
|
2
|
+
import { extractArbitraryValue, parseLength } from '../tailwind';
|
|
3
|
+
|
|
4
|
+
export type RingInfo = { width: number; color: { r: number; g: number; b: number; a?: number } };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse a ring-width utility (e.g. "ring", "ring-2", "ring-[3px]") to pixels.
|
|
8
|
+
* Returns null for non-width ring utilities (color, inset, offset).
|
|
9
|
+
*/
|
|
10
|
+
export function parseRingWidth(utility: string): number | null {
|
|
11
|
+
if (utility === 'ring') return 3;
|
|
12
|
+
if (!utility.startsWith('ring-')) return null;
|
|
13
|
+
const token = utility.substring(5);
|
|
14
|
+
if (token === 'inset' || token.startsWith('offset-')) return null;
|
|
15
|
+
if (token.startsWith('[')) {
|
|
16
|
+
const arbitrary = extractArbitraryValue(utility);
|
|
17
|
+
if (!arbitrary) return null;
|
|
18
|
+
return parseLength(arbitrary);
|
|
19
|
+
}
|
|
20
|
+
const num = parseFloat(token);
|
|
21
|
+
if (!Number.isNaN(num) && String(num) === token) return num;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse a ring-color utility (e.g. "ring-primary", "ring-primary/50") to RGB.
|
|
27
|
+
* Returns null for non-color ring utilities.
|
|
28
|
+
*/
|
|
29
|
+
export function parseRingColor(
|
|
30
|
+
utility: string,
|
|
31
|
+
colorGroup: Record<string, string>
|
|
32
|
+
): { r: number; g: number; b: number; a?: number } | null {
|
|
33
|
+
if (!utility.startsWith('ring-')) return null;
|
|
34
|
+
const token = utility.substring(5);
|
|
35
|
+
if (token === 'inset' || token.startsWith('offset-')) return null;
|
|
36
|
+
if (token.startsWith('[')) return null;
|
|
37
|
+
const num = parseFloat(token);
|
|
38
|
+
if (!Number.isNaN(num) && String(num) === token) return null;
|
|
39
|
+
let colorToken = token;
|
|
40
|
+
let opacityMultiplier: number | null = null;
|
|
41
|
+
const slashIndex = token.lastIndexOf('/');
|
|
42
|
+
if (slashIndex > 0 && slashIndex < token.length - 1) {
|
|
43
|
+
colorToken = token.substring(0, slashIndex);
|
|
44
|
+
const opacityRaw = token.substring(slashIndex + 1).trim();
|
|
45
|
+
const opacityNum = parseFloat(opacityRaw);
|
|
46
|
+
if (!Number.isNaN(opacityNum)) {
|
|
47
|
+
opacityMultiplier = Math.max(0, Math.min(1, opacityNum / 100));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const resolved = colorGroup[colorToken];
|
|
51
|
+
if (!resolved) return null;
|
|
52
|
+
const parsed = parseColor(resolved);
|
|
53
|
+
if (opacityMultiplier == null) return parsed;
|
|
54
|
+
const baseAlpha = parsed.a == null ? 1 : parsed.a;
|
|
55
|
+
return {
|
|
56
|
+
r: parsed.r,
|
|
57
|
+
g: parsed.g,
|
|
58
|
+
b: parsed.b,
|
|
59
|
+
a: Math.max(0, Math.min(1, baseAlpha * opacityMultiplier)),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Derive a RingInfo from a list of Tailwind classes.
|
|
65
|
+
* Returns null when no ring is declared OR when only a ring colour is
|
|
66
|
+
* present without an accompanying width utility.
|
|
67
|
+
*
|
|
68
|
+
* CSS subtlety: in Tailwind, `ring-COLOR` (e.g. `ring-destructive`)
|
|
69
|
+
* sets the ring's color via `--tw-ring-color` but does NOT make the
|
|
70
|
+
* ring visible — that requires a width utility (`ring`, `ring-2`,
|
|
71
|
+
* `ring-[3px]`, …). shadcn's invalid-input pattern relies on this:
|
|
72
|
+
*
|
|
73
|
+
* `aria-invalid:ring-destructive/20 focus-visible:ring-[3px]`
|
|
74
|
+
*
|
|
75
|
+
* "When invalid, the ring is destructive-tinted; when focused, the
|
|
76
|
+
* ring becomes 3px visible." Without focus, ring width = 0, so the
|
|
77
|
+
* invalid-but-unfocused input renders with just the red border, no
|
|
78
|
+
* ring. Previously this function defaulted width to 3 whenever a
|
|
79
|
+
* color was present — so the State Matrix `error` variant rendered
|
|
80
|
+
* with a doubled red ring outside the destructive border, instead of
|
|
81
|
+
* just the border. Now: no width → no ring.
|
|
82
|
+
*/
|
|
83
|
+
export function getRingInfoFromClasses(
|
|
84
|
+
classes: string[],
|
|
85
|
+
colorGroup: Record<string, string>
|
|
86
|
+
): RingInfo | null {
|
|
87
|
+
let width: number | null = null;
|
|
88
|
+
let color: { r: number; g: number; b: number; a?: number } | null = null;
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < classes.length; i++) {
|
|
91
|
+
const cls = classes[i];
|
|
92
|
+
const nextWidth = parseRingWidth(cls);
|
|
93
|
+
if (nextWidth != null) width = nextWidth;
|
|
94
|
+
const nextColor = parseRingColor(cls, colorGroup);
|
|
95
|
+
if (nextColor) color = nextColor;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (width == null) return null;
|
|
99
|
+
if (!color) {
|
|
100
|
+
const fallback = colorGroup.ring || colorGroup.primary;
|
|
101
|
+
if (!fallback) return null;
|
|
102
|
+
color = parseColor(fallback);
|
|
103
|
+
}
|
|
104
|
+
if (!width || width <= 0) return null;
|
|
105
|
+
return { width: width, color: color };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Ring rendering — the OVERLAY-FRAME implementation, the FIXED-toggle
|
|
109
|
+
// invariant, and the post-pass scheduling all live in
|
|
110
|
+
// `src/layout/deferred-layout.ts` (`markRingNode` / `applyRingIfPossible`).
|
|
111
|
+
// This file owns the *parsing* contract only:
|
|
112
|
+
//
|
|
113
|
+
// parseRingWidth / parseRingColor / getRingInfoFromClasses
|
|
114
|
+
//
|
|
115
|
+
// Callers in the parser path (`tailwind.ts`) and the imperative frame
|
|
116
|
+
// builders (`component-gen.ts`, `cva-master.ts`, `preview-builder.ts`,
|
|
117
|
+
// `state-master.ts`) all funnel through that single deferred entry point.
|
|
118
|
+
// See `.ai/troubleshooting.md` "DO NOT use Figma DROP_SHADOW spread" and
|
|
119
|
+
// "State Matrix focus/error variants render TALLER/WIDER" for the
|
|
120
|
+
// architectural rationale.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { parseUtilityClass } from '../tailwind';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a Tailwind size token (e.g. "4", "px", "[1.5rem]") to pixels.
|
|
5
|
+
* This is the canonical implementation — handles px, rem, em, and Tailwind
|
|
6
|
+
* scale multiplier (×4). story-builder and ui-builder both import from here.
|
|
7
|
+
*/
|
|
8
|
+
export function parseSquareSizeToken(token: string): number | null {
|
|
9
|
+
const normalized = String(token || '').trim();
|
|
10
|
+
if (!normalized) return null;
|
|
11
|
+
if (normalized === 'px') return 1;
|
|
12
|
+
if (normalized.startsWith('[') && normalized.endsWith(']')) {
|
|
13
|
+
const arbitrary = normalized.slice(1, -1).trim();
|
|
14
|
+
if (!arbitrary) return null;
|
|
15
|
+
if (arbitrary.endsWith('px')) {
|
|
16
|
+
const px = parseFloat(arbitrary.slice(0, -2));
|
|
17
|
+
return Number.isFinite(px) ? px : null;
|
|
18
|
+
}
|
|
19
|
+
if (arbitrary.endsWith('rem') || arbitrary.endsWith('em')) {
|
|
20
|
+
const rem = parseFloat(arbitrary.slice(0, -3));
|
|
21
|
+
return Number.isFinite(rem) ? rem * 16 : null;
|
|
22
|
+
}
|
|
23
|
+
const raw = parseFloat(arbitrary);
|
|
24
|
+
return Number.isFinite(raw) ? raw : null;
|
|
25
|
+
}
|
|
26
|
+
const numeric = parseFloat(normalized);
|
|
27
|
+
if (!Number.isNaN(numeric)) return numeric * 4;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve fixed dimensions from variantless Tailwind sizing utilities.
|
|
33
|
+
*
|
|
34
|
+
* Supports `size-*`, `w-*`, and `h-*` so all fixed-size controls use the same
|
|
35
|
+
* sizing rule regardless of which builder renders them.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveFixedBoxSizeFromClasses(classes: string[]): { width: number | null; height: number | null } {
|
|
38
|
+
let width: number | null = null;
|
|
39
|
+
let height: number | null = null;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < classes.length; i++) {
|
|
42
|
+
const atom = parseUtilityClass(classes[i]);
|
|
43
|
+
if (!atom || !atom.utility) continue;
|
|
44
|
+
if (Array.isArray(atom.variants) && atom.variants.length > 0) continue;
|
|
45
|
+
const utility = atom.utility || '';
|
|
46
|
+
|
|
47
|
+
const sizeMatch = utility.match(/^size-(.+)$/);
|
|
48
|
+
if (sizeMatch) {
|
|
49
|
+
const resolved = parseSquareSizeToken(sizeMatch[1]);
|
|
50
|
+
if (resolved != null && resolved > 0) {
|
|
51
|
+
width = resolved;
|
|
52
|
+
height = resolved;
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const widthMatch = utility.match(/^w-(.+)$/);
|
|
58
|
+
if (widthMatch) {
|
|
59
|
+
const token = widthMatch[1];
|
|
60
|
+
// Skip fractional widths (`w-1/2`, `w-2/3`, …); they're parent-relative
|
|
61
|
+
// and resolved via markFractionWidthNode, not as a fixed size.
|
|
62
|
+
// Without this guard, parseSquareSizeToken("2/3") → parseFloat("2/3") → 2
|
|
63
|
+
// → 8px, clobbering the correctly-resolved fractional width.
|
|
64
|
+
if (
|
|
65
|
+
token !== 'full' && token !== 'screen' && token !== 'auto'
|
|
66
|
+
&& token !== 'fit' && token !== 'min' && token !== 'max'
|
|
67
|
+
&& !token.includes('/')
|
|
68
|
+
) {
|
|
69
|
+
const resolved = parseSquareSizeToken(token);
|
|
70
|
+
if (resolved != null && resolved > 0) width = resolved;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const heightMatch = utility.match(/^h-(.+)$/);
|
|
76
|
+
if (heightMatch) {
|
|
77
|
+
const token = heightMatch[1];
|
|
78
|
+
if (
|
|
79
|
+
token !== 'full' && token !== 'screen' && token !== 'auto'
|
|
80
|
+
&& token !== 'fit' && token !== 'min' && token !== 'max'
|
|
81
|
+
&& !token.includes('/')
|
|
82
|
+
) {
|
|
83
|
+
const resolved = parseSquareSizeToken(token);
|
|
84
|
+
if (resolved != null && resolved > 0) height = resolved;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { width, height };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Return the resolved pixel size from a variantless `size-*` utility, or null.
|
|
94
|
+
*/
|
|
95
|
+
export function getSquareSizeFromClasses(classes: string[]): number | null {
|
|
96
|
+
const fixed = resolveFixedBoxSizeFromClasses(classes);
|
|
97
|
+
if (fixed.width != null && fixed.height != null && fixed.width === fixed.height) {
|
|
98
|
+
return fixed.width;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* After auto-layout is applied, re-enforce fixed dimensions declared by
|
|
105
|
+
* `size-*`, `w-*`, and `h-*` utilities so Figma's layout engine cannot
|
|
106
|
+
* stretch or squeeze the node.
|
|
107
|
+
*/
|
|
108
|
+
export function enforceFixedBoxSizingAfterLayout(node: SceneNode, classes: string[]): void {
|
|
109
|
+
const fixed = resolveFixedBoxSizeFromClasses(classes);
|
|
110
|
+
if (fixed.width == null && fixed.height == null) return;
|
|
111
|
+
if (!node || !('resize' in node)) return;
|
|
112
|
+
|
|
113
|
+
const currentWidth = 'width' in node && typeof node.width === 'number' ? node.width : 1;
|
|
114
|
+
const currentHeight = 'height' in node && typeof node.height === 'number' ? node.height : 1;
|
|
115
|
+
const targetWidth = fixed.width != null ? fixed.width : currentWidth;
|
|
116
|
+
const targetHeight = fixed.height != null ? fixed.height : currentHeight;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
node.resize(Math.max(1, targetWidth), Math.max(1, targetHeight));
|
|
120
|
+
if ('layoutGrow' in node) node.layoutGrow = 0;
|
|
121
|
+
|
|
122
|
+
const mode = 'layoutMode' in node ? String(node.layoutMode || '') : '';
|
|
123
|
+
if ('layoutSizingHorizontal' in node && fixed.width != null) node.layoutSizingHorizontal = 'FIXED';
|
|
124
|
+
if ('layoutSizingVertical' in node && fixed.height != null) node.layoutSizingVertical = 'FIXED';
|
|
125
|
+
|
|
126
|
+
if (mode === 'HORIZONTAL') {
|
|
127
|
+
if (fixed.width != null && 'primaryAxisSizingMode' in node) node.primaryAxisSizingMode = 'FIXED';
|
|
128
|
+
if (fixed.height != null && 'counterAxisSizingMode' in node) node.counterAxisSizingMode = 'FIXED';
|
|
129
|
+
} else if (mode === 'VERTICAL') {
|
|
130
|
+
if (fixed.height != null && 'primaryAxisSizingMode' in node) node.primaryAxisSizingMode = 'FIXED';
|
|
131
|
+
if (fixed.width != null && 'counterAxisSizingMode' in node) node.counterAxisSizingMode = 'FIXED';
|
|
132
|
+
} else {
|
|
133
|
+
if (fixed.height != null && 'primaryAxisSizingMode' in node) node.primaryAxisSizingMode = 'FIXED';
|
|
134
|
+
if (fixed.width != null && 'counterAxisSizingMode' in node) node.counterAxisSizingMode = 'FIXED';
|
|
135
|
+
}
|
|
136
|
+
} catch (_err) {
|
|
137
|
+
// ignore
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function enforceSquareSizingAfterLayout(node: SceneNode, classes: string[]): void {
|
|
142
|
+
enforceFixedBoxSizingAfterLayout(node, classes);
|
|
143
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision helper for the "resize text node to parent's content width" step
|
|
3
|
+
* inside `buildFigmaNode`'s frame-with-text branch.
|
|
4
|
+
*
|
|
5
|
+
* Extracted into a pure function because the decision has historically been
|
|
6
|
+
* brittle — site (c) of the recurring inline-flex pill bug lives in this
|
|
7
|
+
* branch (see `tools/figma-plugin/.ai/troubleshooting.md`). Keeping it pure
|
|
8
|
+
* makes the truth table testable without stubbing `buildFigmaNode`'s whole
|
|
9
|
+
* dependency surface.
|
|
10
|
+
*
|
|
11
|
+
* Returns the target width (positive number) if the text node should be
|
|
12
|
+
* resized, or `undefined` if no resize should occur — including when the
|
|
13
|
+
* computed width would be 0 or negative after padding subtraction.
|
|
14
|
+
*/
|
|
15
|
+
export interface TextResizeFrameInput {
|
|
16
|
+
// Match Figma's FrameNode['layoutMode'] which includes 'GRID'.
|
|
17
|
+
layoutMode: 'NONE' | 'HORIZONTAL' | 'VERTICAL' | 'GRID';
|
|
18
|
+
primaryAxisAlignItems: string;
|
|
19
|
+
primaryAxisSizingMode: 'AUTO' | 'FIXED';
|
|
20
|
+
paddingLeft: number;
|
|
21
|
+
paddingRight: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveTextResizeWidth(
|
|
25
|
+
frame: TextResizeFrameInput,
|
|
26
|
+
contextMaxWidth: number | undefined | null,
|
|
27
|
+
): number | undefined {
|
|
28
|
+
if (contextMaxWidth == null || !Number.isFinite(contextMaxWidth) || contextMaxWidth <= 0) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// In a HORIZONTAL frame with CENTER alignment, don't force the text node
|
|
33
|
+
// to a fixed width — let it stay HUG-sized so the frame's CENTER alignment
|
|
34
|
+
// can position it (e.g. a numbered circle with justify-center).
|
|
35
|
+
const isHorizontalCentered =
|
|
36
|
+
frame.layoutMode === 'HORIZONTAL' && frame.primaryAxisAlignItems === 'CENTER';
|
|
37
|
+
|
|
38
|
+
// Skip the resize for HORIZONTAL frames that hug their content width
|
|
39
|
+
// (inline-flex pills / chips / badges). Their natural width IS the text
|
|
40
|
+
// width — if we resize the text to the parent's maxWidth, the hug-frame
|
|
41
|
+
// inherits that width and stretches across the whole card. This is the
|
|
42
|
+
// recurring inline-flex pill bug — site (c).
|
|
43
|
+
const isHorizontalHugging =
|
|
44
|
+
frame.layoutMode === 'HORIZONTAL' && frame.primaryAxisSizingMode === 'AUTO';
|
|
45
|
+
|
|
46
|
+
if (isHorizontalCentered || isHorizontalHugging) return undefined;
|
|
47
|
+
|
|
48
|
+
const padding = (frame.paddingLeft || 0) + (frame.paddingRight || 0);
|
|
49
|
+
const target = contextMaxWidth - padding;
|
|
50
|
+
return target > 0 ? target : undefined;
|
|
51
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LayoutParser } from './layout-parser';
|
|
2
|
-
import { type NodeIR, isElementLikeNode } from '
|
|
2
|
+
import { type NodeIR, isElementLikeNode, getBaseClass } from '../tailwind';
|
|
3
3
|
|
|
4
4
|
export type NodeLayoutComputed = {
|
|
5
5
|
layoutIR: ReturnType<typeof LayoutParser.parseToIR>;
|
|
@@ -30,6 +30,18 @@ const BLOCK_TAGS = new Set([
|
|
|
30
30
|
'tbody',
|
|
31
31
|
'tfoot',
|
|
32
32
|
'tr',
|
|
33
|
+
// Heading and paragraph tags are block-level in CSS and stretch to fill a
|
|
34
|
+
// flex-col parent, giving text-center/text-right meaningful visual effect.
|
|
35
|
+
// Note: shouldStretchToParentWidth guards against mx-auto (which signals
|
|
36
|
+
// explicit HUG+centering intent) and w-*/max-w-* (explicit width constraints).
|
|
37
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p',
|
|
38
|
+
// `a`, `button`, and `label` are inline by CSS default, but become block-level
|
|
39
|
+
// when given `display: flex|grid|block` via classes. The frame-creation branch
|
|
40
|
+
// in ui-builder only reaches this check when the element has layout classes /
|
|
41
|
+
// children / background, i.e. after it has been promoted to block-level, so
|
|
42
|
+
// including them here is safe. Plain inline usage goes through the inline-text
|
|
43
|
+
// path and never hits shouldStretchToParentWidth.
|
|
44
|
+
'a', 'button', 'label',
|
|
33
45
|
]);
|
|
34
46
|
|
|
35
47
|
const GRID_BREAKPOINTS: Record<string, number> = {
|
|
@@ -40,10 +52,101 @@ const GRID_BREAKPOINTS: Record<string, number> = {
|
|
|
40
52
|
'2xl': 1536,
|
|
41
53
|
};
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
const DISPLAY_UTILITIES = new Set([
|
|
56
|
+
'block',
|
|
57
|
+
'inline-block',
|
|
58
|
+
'inline',
|
|
59
|
+
'flex',
|
|
60
|
+
'inline-flex',
|
|
61
|
+
'grid',
|
|
62
|
+
'inline-grid',
|
|
63
|
+
'contents',
|
|
64
|
+
'table',
|
|
65
|
+
'table-row',
|
|
66
|
+
'table-cell',
|
|
67
|
+
'flow-root',
|
|
68
|
+
'list-item',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
function resolveActiveDisplayUtility(classes: string[] | undefined, availableWidth?: number): string | null {
|
|
72
|
+
if (!classes || classes.length === 0) return null;
|
|
73
|
+
type DisplayCandidate = { minWidth: number; utility: string; order: number };
|
|
74
|
+
const candidates: DisplayCandidate[] = [];
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < classes.length; i++) {
|
|
77
|
+
const cls = classes[i];
|
|
78
|
+
const parts = cls.split(':');
|
|
79
|
+
const tail = parts[parts.length - 1];
|
|
80
|
+
if (!DISPLAY_UTILITIES.has(tail)) continue;
|
|
81
|
+
const prefixes = parts.slice(0, -1);
|
|
82
|
+
|
|
83
|
+
let minWidth = 0;
|
|
84
|
+
let unsupported = false;
|
|
85
|
+
for (let j = 0; j < prefixes.length; j++) {
|
|
86
|
+
const bp = GRID_BREAKPOINTS[prefixes[j]];
|
|
87
|
+
if (!bp && bp !== 0) {
|
|
88
|
+
unsupported = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (bp > minWidth) minWidth = bp;
|
|
92
|
+
}
|
|
93
|
+
if (unsupported) continue;
|
|
94
|
+
candidates.push({ minWidth: minWidth, utility: tail, order: i });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (candidates.length === 0) return null;
|
|
98
|
+
|
|
99
|
+
if (availableWidth != null && Number.isFinite(availableWidth)) {
|
|
100
|
+
let chosen: DisplayCandidate | null = null;
|
|
101
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
102
|
+
const candidate = candidates[i];
|
|
103
|
+
if (availableWidth < candidate.minWidth) continue;
|
|
104
|
+
if (
|
|
105
|
+
!chosen ||
|
|
106
|
+
candidate.minWidth > chosen.minWidth ||
|
|
107
|
+
(candidate.minWidth === chosen.minWidth && candidate.order > chosen.order)
|
|
108
|
+
) {
|
|
109
|
+
chosen = candidate;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return chosen ? chosen.utility : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let chosen: DisplayCandidate | null = null;
|
|
116
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
117
|
+
const candidate = candidates[i];
|
|
118
|
+
if (
|
|
119
|
+
!chosen ||
|
|
120
|
+
candidate.minWidth > chosen.minWidth ||
|
|
121
|
+
(candidate.minWidth === chosen.minWidth && candidate.order > chosen.order)
|
|
122
|
+
) {
|
|
123
|
+
chosen = candidate;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return chosen ? chosen.utility : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function stripVariantPrefix(value: string): string {
|
|
130
|
+
if (!value) return value;
|
|
131
|
+
const idx = value.lastIndexOf(':');
|
|
132
|
+
return idx === -1 ? value : value.slice(idx + 1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hasOutOfFlowPositioning(classes: string[] | undefined): boolean {
|
|
136
|
+
if (!classes || classes.length === 0) return false;
|
|
137
|
+
for (const cls of classes) {
|
|
138
|
+
const base = stripVariantPrefix(cls);
|
|
139
|
+
if (base === 'absolute' || base === 'fixed' || base === 'sticky') {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function nodeHasOutOfFlowPositioning(node: NodeIR): boolean {
|
|
147
|
+
if (node.kind === 'ring') return hasOutOfFlowPositioning(node.classes);
|
|
148
|
+
if (isElementLikeNode(node)) return hasOutOfFlowPositioning(node.classes);
|
|
149
|
+
return false;
|
|
47
150
|
}
|
|
48
151
|
|
|
49
152
|
export function extractFixedWidth(classes: string[] | undefined): number | null {
|
|
@@ -86,17 +189,15 @@ export function extractMaxWidth(classes: string[] | undefined): number | null {
|
|
|
86
189
|
|
|
87
190
|
export function extractGridColumns(classes: string[] | undefined, availableWidth?: number): number | null {
|
|
88
191
|
if (!classes) return null;
|
|
192
|
+
const activeDisplay = resolveActiveDisplayUtility(classes, availableWidth);
|
|
193
|
+
const displayIsGrid = activeDisplay === 'grid' || activeDisplay === 'inline-grid';
|
|
89
194
|
const responsivePrefixes = new Set(Object.keys(GRID_BREAKPOINTS));
|
|
90
195
|
let baseCols: number | null = null;
|
|
91
196
|
let best: { bp: number; cols: number } | null = null;
|
|
92
|
-
let hasBaseGrid = false;
|
|
93
197
|
for (const cls of classes) {
|
|
94
198
|
const parts = cls.split(':');
|
|
95
199
|
const tail = parts[parts.length - 1];
|
|
96
200
|
const prefixes = parts.slice(0, -1);
|
|
97
|
-
if ((tail === 'grid' || tail === 'inline-grid') && prefixes.length === 0) {
|
|
98
|
-
hasBaseGrid = true;
|
|
99
|
-
}
|
|
100
201
|
const match = tail.match(/^grid-cols-(\d+)$/);
|
|
101
202
|
if (!match) continue;
|
|
102
203
|
const cols = parseInt(match[1], 10);
|
|
@@ -117,19 +218,21 @@ export function extractGridColumns(classes: string[] | undefined, availableWidth
|
|
|
117
218
|
}
|
|
118
219
|
}
|
|
119
220
|
|
|
120
|
-
if (!
|
|
221
|
+
if (!displayIsGrid && baseCols == null && !best) return null;
|
|
121
222
|
|
|
122
223
|
if (availableWidth != null && Number.isFinite(availableWidth)) {
|
|
224
|
+
if (!displayIsGrid) return null;
|
|
123
225
|
if (best && availableWidth >= best.bp) return best.cols;
|
|
124
226
|
if (baseCols != null) return baseCols;
|
|
125
227
|
// Tailwind defaults to one implicit column for grid containers until a responsive override applies.
|
|
126
|
-
if (
|
|
228
|
+
if (displayIsGrid) return 1;
|
|
127
229
|
return best ? best.cols : null;
|
|
128
230
|
}
|
|
129
231
|
|
|
232
|
+
if (!displayIsGrid && baseCols == null && !best) return null;
|
|
130
233
|
if (baseCols != null) return baseCols;
|
|
131
234
|
if (best) return best.cols;
|
|
132
|
-
if (
|
|
235
|
+
if (displayIsGrid) return 1;
|
|
133
236
|
return null;
|
|
134
237
|
}
|
|
135
238
|
|
|
@@ -150,15 +253,28 @@ export function extractGridBreakpointWidth(classes: string[] | undefined): numbe
|
|
|
150
253
|
|
|
151
254
|
export function shouldStretchToParentWidth(tag: string, classes: string[]): boolean {
|
|
152
255
|
if (!BLOCK_TAGS.has(tag)) return false;
|
|
256
|
+
if (hasOutOfFlowPositioning(classes)) return false;
|
|
153
257
|
for (const cls of classes) {
|
|
154
258
|
const base = getBaseClass(cls);
|
|
155
259
|
if (!base) continue;
|
|
260
|
+
// Out-of-flow positioned nodes should not inherit vertical stretch behavior.
|
|
261
|
+
// These nodes are anchored by top/right/bottom/left (e.g. dialog close button).
|
|
262
|
+
if (base === 'absolute' || base === 'fixed' || base === 'sticky') {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
156
265
|
if (base === 'inline' || base === 'inline-block' || base === 'inline-flex' || base === 'inline-grid') {
|
|
157
266
|
return false;
|
|
158
267
|
}
|
|
159
268
|
if (base.startsWith('w-') || base.startsWith('max-w-') || base.startsWith('min-w-')) {
|
|
160
269
|
return false;
|
|
161
270
|
}
|
|
271
|
+
// `size-N` (Tailwind's both-axis shorthand) pins the width, so the element
|
|
272
|
+
// should NOT stretch to parent width — same intent as `w-N`. Numeric and
|
|
273
|
+
// arbitrary forms only; `size-full` was already normalized into `w-full`
|
|
274
|
+
// by `splitClassName`, so it never appears here as a separate token.
|
|
275
|
+
if (base.startsWith('size-')) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
162
278
|
if (base.startsWith('self-')) {
|
|
163
279
|
return false;
|
|
164
280
|
}
|
|
@@ -182,13 +298,13 @@ export function resolveExplicitWidthFromClasses(classes: string[]): number | nul
|
|
|
182
298
|
}
|
|
183
299
|
|
|
184
300
|
export function getNodeLayoutComputed(node: NodeIR): NodeLayoutComputed {
|
|
185
|
-
const cached = NODE_LAYOUT_CACHE.get(node
|
|
301
|
+
const cached = NODE_LAYOUT_CACHE.get(node);
|
|
186
302
|
if (cached) return cached;
|
|
187
303
|
|
|
188
304
|
const classes = isElementLikeNode(node) ? node.classes : [];
|
|
189
305
|
const semanticLayoutClasses = getSemanticLayoutClasses(node);
|
|
190
306
|
const layoutClasses = semanticLayoutClasses.length > 0
|
|
191
|
-
?
|
|
307
|
+
? semanticLayoutClasses.concat(classes)
|
|
192
308
|
: classes;
|
|
193
309
|
const layoutIR = LayoutParser.parseToIR(layoutClasses);
|
|
194
310
|
const maxWidth = extractMaxWidth(classes);
|
|
@@ -208,7 +324,8 @@ export function getNodeLayoutComputed(node: NodeIR): NodeLayoutComputed {
|
|
|
208
324
|
);
|
|
209
325
|
const hasExplicitSize = classes.some(c =>
|
|
210
326
|
/^w-\d+$/.test(c) || /^w-\[/.test(c) || /^h-\d+$/.test(c) || /^h-\[/.test(c) ||
|
|
211
|
-
c === 'w-full' || c === 'h-full' || /^w-\d+\/\d+$/.test(c)
|
|
327
|
+
c === 'w-full' || c === 'h-full' || /^w-\d+\/\d+$/.test(c) ||
|
|
328
|
+
/^size-\d+(?:\.\d+)?$/.test(c) || /^size-\[/.test(c) || c === 'size-full'
|
|
212
329
|
);
|
|
213
330
|
|
|
214
331
|
const computed: NodeLayoutComputed = {
|
|
@@ -220,7 +337,7 @@ export function getNodeLayoutComputed(node: NodeIR): NodeLayoutComputed {
|
|
|
220
337
|
hasFlexChildClass: hasFlexChildClass,
|
|
221
338
|
hasExplicitSize: hasExplicitSize,
|
|
222
339
|
};
|
|
223
|
-
NODE_LAYOUT_CACHE.set(node
|
|
340
|
+
NODE_LAYOUT_CACHE.set(node, computed);
|
|
224
341
|
return computed;
|
|
225
342
|
}
|
|
226
343
|
|
|
@@ -253,7 +370,8 @@ export function solveLayoutWidths(node: NodeIR, availableWidth?: number): NodeIR
|
|
|
253
370
|
}
|
|
254
371
|
|
|
255
372
|
if (node.kind === 'ring') {
|
|
256
|
-
|
|
373
|
+
const ringIsOutOfFlow = hasOutOfFlowPositioning(node.classes);
|
|
374
|
+
let childWidth: number | undefined = ringIsOutOfFlow ? undefined : availableWidth;
|
|
257
375
|
if (childWidth != null) {
|
|
258
376
|
childWidth = Math.max(0, childWidth - (node.ringWidth + node.offsetWidth) * 2);
|
|
259
377
|
}
|
|
@@ -283,8 +401,12 @@ export function solveLayoutWidths(node: NodeIR, availableWidth?: number): NodeIR
|
|
|
283
401
|
}
|
|
284
402
|
// Component wrappers (Card, CardHeader, etc.) are not in BLOCK_TAGS but should
|
|
285
403
|
// still inherit the parent's available width when they have no explicit width of their own.
|
|
404
|
+
// Restrict this to components that actually wrap children. Leaf components include
|
|
405
|
+
// icon components (XIcon, ChevronDown, etc.) and forcing full width on them causes
|
|
406
|
+
// dialog close buttons and similar absolute controls to misplace.
|
|
286
407
|
if (widthOverride == null && availableWidth != null && node.kind === 'component') {
|
|
287
|
-
|
|
408
|
+
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
409
|
+
if (hasChildren && !hasNonFullWidthClass(classes) && !hasOutOfFlowPositioning(classes)) {
|
|
288
410
|
widthOverride = availableWidth;
|
|
289
411
|
}
|
|
290
412
|
}
|
|
@@ -321,12 +443,18 @@ export function solveLayoutWidths(node: NodeIR, availableWidth?: number): NodeIR
|
|
|
321
443
|
let remainingChildWidth: number | null = null;
|
|
322
444
|
if (layoutIR.layoutMode === 'HORIZONTAL' && contentWidth != null && node.children && node.children.length > 0) {
|
|
323
445
|
const gap = layoutIR.gapX != null ? layoutIR.gapX : layoutIR.gap;
|
|
324
|
-
|
|
446
|
+
// CSS flex: absolute/fixed children are out-of-flow and don't consume space
|
|
447
|
+
// in the row. Excluding them prevents their width from shrinking siblings
|
|
448
|
+
// (e.g. an absolute check-indicator in a Select item would otherwise steal
|
|
449
|
+
// 14px from the label text's allocation and force wrapping).
|
|
450
|
+
const inFlowChildren = node.children
|
|
451
|
+
.filter(isElementLikeNode)
|
|
452
|
+
.filter((child) => !nodeHasOutOfFlowPositioning(child));
|
|
453
|
+
const totalGap = gap * Math.max(0, inFlowChildren.length - 1);
|
|
325
454
|
let fixedTotal = 0;
|
|
326
455
|
let growCount = 0;
|
|
327
456
|
let variableCount = 0;
|
|
328
|
-
for (const child of
|
|
329
|
-
if (!isElementLikeNode(child)) continue;
|
|
457
|
+
for (const child of inFlowChildren) {
|
|
330
458
|
const explicitChildWidth = resolveExplicitWidthFromChild(child);
|
|
331
459
|
if (explicitChildWidth != null) {
|
|
332
460
|
fixedTotal += explicitChildWidth;
|
|
@@ -351,22 +479,25 @@ export function solveLayoutWidths(node: NodeIR, availableWidth?: number): NodeIR
|
|
|
351
479
|
const nextChildren: NodeIR[] = [];
|
|
352
480
|
for (const child of node.children) {
|
|
353
481
|
let nextChild = child;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
482
|
+
const childIsOutOfFlow = nodeHasOutOfFlowPositioning(child);
|
|
483
|
+
let nextWidth = childIsOutOfFlow ? undefined : childWidth;
|
|
484
|
+
if (!childIsOutOfFlow) {
|
|
485
|
+
if (gridChildWidth != null) {
|
|
486
|
+
nextChild = enforceFixedWidthOnGridChild(child, gridChildWidth);
|
|
487
|
+
nextWidth = gridChildWidth;
|
|
488
|
+
} else if (layoutIR.layoutMode === 'HORIZONTAL' && contentWidth != null) {
|
|
489
|
+
const explicitChildWidth = resolveExplicitWidthFromChild(child);
|
|
490
|
+
if (explicitChildWidth != null) {
|
|
491
|
+
nextWidth = explicitChildWidth;
|
|
492
|
+
} else if (flexChildWidth != null && isElementLikeNode(child) && hasGrowClass(child.classes)) {
|
|
493
|
+
nextChild = enforceFixedWidthOnFlexChild(child, flexChildWidth);
|
|
494
|
+
nextWidth = flexChildWidth;
|
|
495
|
+
} else if (remainingChildWidth != null && isElementLikeNode(child)) {
|
|
496
|
+
nextChild = enforceFixedWidthOnGridChild(child, remainingChildWidth);
|
|
497
|
+
nextWidth = remainingChildWidth;
|
|
498
|
+
} else {
|
|
499
|
+
nextWidth = undefined;
|
|
500
|
+
}
|
|
370
501
|
}
|
|
371
502
|
}
|
|
372
503
|
nextChildren.push(solveLayoutWidths(nextChild, nextWidth));
|