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
package/manifest.json
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
{ "name": "Settings", "command": "settings" },
|
|
26
26
|
{ "separator": true },
|
|
27
27
|
{ "name": "Generate Design System Page", "command": "generate" },
|
|
28
|
+
{ "name": "Clean Generated Artifacts", "command": "clean-generated" },
|
|
28
29
|
{ "name": "Debug Selection (Console)", "command": "debug-selection" }
|
|
29
30
|
]
|
|
30
31
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inkbridge",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0-beta.21",
|
|
4
|
+
"description": "Generate a Figma design system from your Storybook stories — Tailwind React components rendered as native frames, design tokens, component states, and round-trip code sync.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"inkbridge": "./bin/inkbridge.mjs"
|
|
@@ -19,21 +19,6 @@
|
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "node build.mjs",
|
|
24
|
-
"watch": "node build.mjs --watch",
|
|
25
|
-
"doctor": "cd ../.. && node scripts/figma-doctor.mjs",
|
|
26
|
-
"scan": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/cli.ts",
|
|
27
|
-
"test:blob": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/blob-placement-regression.ts",
|
|
28
|
-
"test:tokens": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/css-token-reader-regression.ts",
|
|
29
|
-
"test:font": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/font-style-resolver-regression.ts",
|
|
30
|
-
"test:radial": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/radial-gradient-regression.ts",
|
|
31
|
-
"test:transform": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/transform-math-regression.ts",
|
|
32
|
-
"test:csspatch": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin-tailwind-tokens/scanner/css-patch-regression.ts",
|
|
33
|
-
"verify": "pnpm run scan && pnpm run test:blob && pnpm run test:tokens && pnpm run test:font && pnpm run test:radial && pnpm run test:transform && pnpm run test:csspatch && pnpm run build",
|
|
34
|
-
"postinstall": "node ./bin/inkbridge.mjs postinstall",
|
|
35
|
-
"prepublishOnly": "node build.mjs"
|
|
36
|
-
},
|
|
37
22
|
"keywords": [
|
|
38
23
|
"figma",
|
|
39
24
|
"plugin",
|
|
@@ -42,10 +27,76 @@
|
|
|
42
27
|
"storybook"
|
|
43
28
|
],
|
|
44
29
|
"license": "MIT",
|
|
45
|
-
"homepage": "https://inkbridge.
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
30
|
+
"homepage": "https://inkbridge.ink",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=18",
|
|
33
|
+
"react-dom": ">=18",
|
|
34
|
+
"tailwindcss": ">=4",
|
|
35
|
+
"postcss": ">=8"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"tailwind-merge": "^3.4.0",
|
|
39
|
+
"ts-morph": "^27.0.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "node build.mjs",
|
|
43
|
+
"watch": "node build.mjs --watch",
|
|
44
|
+
"doctor": "cd ../.. && node scripts/figma-doctor.mjs",
|
|
45
|
+
"scan": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/cli.ts",
|
|
46
|
+
"test:blob": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/blob-placement-regression.ts",
|
|
47
|
+
"test:tokens": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/css-token-reader-regression.ts",
|
|
48
|
+
"test:font": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/font-style-resolver-regression.ts",
|
|
49
|
+
"test:radial": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/radial-gradient-regression.ts",
|
|
50
|
+
"test:transform": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/transform-math-regression.ts",
|
|
51
|
+
"test:csspatch": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/css-patch-regression.ts",
|
|
52
|
+
"test:state-classification": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/state-classification-regression.ts",
|
|
53
|
+
"test:component-sections": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/component-sections-regression.ts",
|
|
54
|
+
"test:block-cache": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/block-cache-regression.ts",
|
|
55
|
+
"test:story-dimensioning": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/story-dimensioning-regression.ts",
|
|
56
|
+
"test:story-render-strategy": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/story-render-strategy-regression.ts",
|
|
57
|
+
"test:render-prop-parser": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/render-prop-parser-regression.ts",
|
|
58
|
+
"test:story-diagnostics": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/story-diagnostics-regression.ts",
|
|
59
|
+
"test:instance-rendering": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/instance-rendering-regression.ts",
|
|
60
|
+
"test:layout-spacing": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/layout-spacing-regression.ts",
|
|
61
|
+
"test:layout-sizing": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/layout-sizing-regression.ts",
|
|
62
|
+
"test:layout-alignment": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/layout-alignment-regression.ts",
|
|
63
|
+
"test:layout-flex": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/layout-flex-regression.ts",
|
|
64
|
+
"test:layout-mode": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/layout-mode-regression.ts",
|
|
65
|
+
"test:svg-marker": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/svg-marker-regression.ts",
|
|
66
|
+
"test:aspect-ratio": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/aspect-ratio-regression.ts",
|
|
67
|
+
"test:percent-position": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/percent-position-regression.ts",
|
|
68
|
+
"test:svg-fill-parent": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/svg-fill-parent-regression.ts",
|
|
69
|
+
"test:inline-flex": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/inline-flex-regression.ts",
|
|
70
|
+
"test:child-sizing-matrix": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/child-sizing-matrix-regression.ts",
|
|
71
|
+
"test:full-width-matrix": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/full-width-matrix-regression.ts",
|
|
72
|
+
"test:text-resize-matrix": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/text-resize-matrix-regression.ts",
|
|
73
|
+
"test:provider-flatten": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/provider-flatten-regression.ts",
|
|
74
|
+
"test:provider-cascade": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/provider-cascade-regression.ts",
|
|
75
|
+
"test:selection-pressed": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/selection-pressed-regression.ts",
|
|
76
|
+
"test:cva-jsx-child-fallback": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/cva-jsx-child-fallback-regression.ts",
|
|
77
|
+
"test:cva-master-icon": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/cva-master-icon-regression.ts",
|
|
78
|
+
"test:data-attr-prop-alias": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/data-attr-prop-alias-regression.ts",
|
|
79
|
+
"test:jsx-prop-unresolved": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/jsx-prop-unresolved-regression.ts",
|
|
80
|
+
"test:grid-cols-extraction": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/grid-cols-extraction-regression.ts",
|
|
81
|
+
"test:explicit-size-root": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/explicit-size-root-regression.ts",
|
|
82
|
+
"test:image-src-collector": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/image-src-collector-regression.ts",
|
|
83
|
+
"test:size-full-normalization": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/size-full-normalization-regression.ts",
|
|
84
|
+
"test:stretch-to-parent-width": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/stretch-to-parent-width-regression.ts",
|
|
85
|
+
"test:framework-adapter-shadcn": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/framework-adapter-shadcn-regression.ts",
|
|
86
|
+
"test:compound-classes-lookup": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/compound-classes-lookup-regression.ts",
|
|
87
|
+
"test:sandbox-spread": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/sandbox-spread-regression.ts",
|
|
88
|
+
"test:jsx-text": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/jsx-text-regression.ts",
|
|
89
|
+
"test:aspect-percent": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/aspect-percent-position-regression.ts",
|
|
90
|
+
"test:svg-group-inherit": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/svg-group-inheritance-regression.ts",
|
|
91
|
+
"test:svg-marker-inline": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/svg-marker-inline-regression.ts",
|
|
92
|
+
"test:bundle-size": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/bundle-size-regression.ts",
|
|
93
|
+
"test:ring-utility": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/ring-utility-regression.ts",
|
|
94
|
+
"test:adapter-utils": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/adapter-utils-regression.ts",
|
|
95
|
+
"test:input-range": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/input-range-regression.ts",
|
|
96
|
+
"test:font-family-extract": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/font-family-extract-regression.ts",
|
|
97
|
+
"test:local-const-className": "cd ../.. && ./node_modules/.bin/tsx tools/figma-plugin/scanner/local-const-className-regression.ts",
|
|
98
|
+
"docs:audit": "node ./scripts/docs-audit.mjs",
|
|
99
|
+
"verify": "pnpm run scan && pnpm run test:blob && pnpm run test:tokens && pnpm run test:font && pnpm run test:radial && pnpm run test:transform && pnpm run test:csspatch && pnpm run test:state-classification && pnpm run test:component-sections && pnpm run test:block-cache && pnpm run test:story-dimensioning && pnpm run test:story-render-strategy && pnpm run test:render-prop-parser && pnpm run test:story-diagnostics && pnpm run test:instance-rendering && pnpm run test:layout-spacing && pnpm run test:layout-sizing && pnpm run test:layout-alignment && pnpm run test:layout-flex && pnpm run test:layout-mode && pnpm run test:svg-marker && pnpm run test:aspect-ratio && pnpm run test:percent-position && pnpm run test:svg-fill-parent && pnpm run test:inline-flex && pnpm run test:child-sizing-matrix && pnpm run test:full-width-matrix && pnpm run test:text-resize-matrix && pnpm run test:provider-flatten && pnpm run test:provider-cascade && pnpm run test:selection-pressed && pnpm run test:cva-jsx-child-fallback && pnpm run test:cva-master-icon && pnpm run test:data-attr-prop-alias && pnpm run test:jsx-prop-unresolved && pnpm run test:grid-cols-extraction && pnpm run test:explicit-size-root && pnpm run test:image-src-collector && pnpm run test:size-full-normalization && pnpm run test:stretch-to-parent-width && pnpm run test:framework-adapter-shadcn && pnpm run test:compound-classes-lookup && pnpm run test:sandbox-spread && pnpm run test:jsx-text && pnpm run test:aspect-percent && pnpm run test:svg-group-inherit && pnpm run test:svg-marker-inline && pnpm run test:ring-utility && pnpm run test:adapter-utils && pnpm run test:input-range && pnpm run test:font-family-extract && pnpm run test:local-const-className && pnpm run docs:audit && pnpm run build && pnpm run test:bundle-size",
|
|
100
|
+
"release:beta": "pnpm publish --tag beta && npm dist-tag add inkbridge@$npm_package_version latest && npm view inkbridge dist-tags"
|
|
50
101
|
}
|
|
51
|
-
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isClassedElement,
|
|
5
|
+
mergeMissing,
|
|
6
|
+
resolveValuePercents,
|
|
7
|
+
} from '../src/tailwind/adapter-utils';
|
|
8
|
+
import type { NodeIR } from '../src/tailwind/node-ir';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Regression: `src/tailwind/adapter-utils.ts` is the shared toolbox both
|
|
12
|
+
* `framework-adapters/shadcn.ts` and `tailwind/node-ir.ts`'s native-HTML
|
|
13
|
+
* transforms call into. If these helpers drift, both adapters
|
|
14
|
+
* silently break — shadcn Slider thumbs end up at wrong positions and
|
|
15
|
+
* `<input type=range>` rewrites compute the wrong fill width.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// isClassedElement
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
const el: NodeIR = {
|
|
24
|
+
kind: 'element',
|
|
25
|
+
tagName: 'div',
|
|
26
|
+
tagLower: 'div',
|
|
27
|
+
props: {},
|
|
28
|
+
classes: [],
|
|
29
|
+
children: [],
|
|
30
|
+
};
|
|
31
|
+
const comp: NodeIR = {
|
|
32
|
+
kind: 'component',
|
|
33
|
+
tagName: 'Button',
|
|
34
|
+
tagLower: 'button',
|
|
35
|
+
props: {},
|
|
36
|
+
classes: [],
|
|
37
|
+
children: [],
|
|
38
|
+
};
|
|
39
|
+
const text: NodeIR = { kind: 'text', text: 'x' };
|
|
40
|
+
const frag: NodeIR = { kind: 'fragment', children: [] };
|
|
41
|
+
|
|
42
|
+
assert.equal(isClassedElement(el), true, 'element is classed');
|
|
43
|
+
assert.equal(isClassedElement(comp), true, 'component is classed');
|
|
44
|
+
assert.equal(isClassedElement(text), false, 'text is not classed');
|
|
45
|
+
assert.equal(isClassedElement(frag), false, 'fragment is not classed');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// mergeMissing
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
// Empty extras → return existing by reference (cache-friendly).
|
|
54
|
+
const existing = ['a', 'b'];
|
|
55
|
+
const out = mergeMissing(existing, []);
|
|
56
|
+
assert.equal(out, existing, 'empty extras returns existing by reference');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
// All extras already present → return existing by reference.
|
|
61
|
+
const existing = ['a', 'b', 'c'];
|
|
62
|
+
const out = mergeMissing(existing, ['b', 'c']);
|
|
63
|
+
assert.equal(out, existing, 'all present returns existing by reference');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
// Some extras new → return a new array with originals + new ones in order.
|
|
68
|
+
const existing = ['a', 'b'];
|
|
69
|
+
const out = mergeMissing(existing, ['b', 'c', 'd']);
|
|
70
|
+
assert.notEqual(out, existing, 'new extras return a new array');
|
|
71
|
+
assert.deepEqual(out, ['a', 'b', 'c', 'd'], 'preserves order, de-dupes b');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// resolveValuePercents — basic numeric input
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
// Default min=0, max=100: value 25 → 25%.
|
|
80
|
+
assert.deepEqual(resolveValuePercents(25, undefined, undefined), [25]);
|
|
81
|
+
// Default behavior with explicit undefined.
|
|
82
|
+
assert.deepEqual(resolveValuePercents(50, 0, 100), [50]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
// Non-zero min: (value - min) / (max - min) * 100.
|
|
87
|
+
// value=5, min=1.1, max=100 → ((5 - 1.1) / (100 - 1.1)) * 100 ≈ 3.9434
|
|
88
|
+
const out = resolveValuePercents(5, 1.1, 100);
|
|
89
|
+
assert.equal(out.length, 1);
|
|
90
|
+
assert.ok(Math.abs(out[0] - 3.9434) < 0.001, `got ${out[0]}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
// Clamped to 0..100.
|
|
95
|
+
assert.deepEqual(resolveValuePercents(-50, 0, 100), [0], 'below-min clamps to 0');
|
|
96
|
+
assert.deepEqual(resolveValuePercents(200, 0, 100), [100], 'above-max clamps to 100');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// resolveValuePercents — stringified props (scanner emits everything as
|
|
101
|
+
// string)
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
assert.deepEqual(resolveValuePercents('25', '0', '100'), [25]);
|
|
106
|
+
// String min/max with non-zero baseline.
|
|
107
|
+
const out = resolveValuePercents('50', '0', '200');
|
|
108
|
+
assert.deepEqual(out, [25], 'string 50/200 → 25%');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// resolveValuePercents — JSON array string (shadcn Slider tuple)
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
{
|
|
116
|
+
const out = resolveValuePercents('[25, 75]', 0, 100);
|
|
117
|
+
assert.deepEqual(out, [25, 75], 'JSON tuple parses both values');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// resolveValuePercents — actual array
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
const out = resolveValuePercents([10, 50, 90], 0, 100);
|
|
126
|
+
assert.deepEqual(out, [10, 50, 90]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// resolveValuePercents — degenerate ranges
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
{
|
|
134
|
+
// min === max → fallback range of 100.
|
|
135
|
+
const out = resolveValuePercents(5, 5, 5);
|
|
136
|
+
assert.equal(out.length, 1);
|
|
137
|
+
assert.equal(out[0], 0, 'min===max with value=min yields 0%');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
{
|
|
141
|
+
// max < min → fallback. We don't care about specific number, just no crash
|
|
142
|
+
// and a finite result.
|
|
143
|
+
const out = resolveValuePercents(50, 100, 0);
|
|
144
|
+
assert.equal(out.length, 1);
|
|
145
|
+
assert.ok(Number.isFinite(out[0]), 'inverted min/max stays finite');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// resolveValuePercents — unparseable value falls back to [0]
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
{
|
|
153
|
+
assert.deepEqual(resolveValuePercents(undefined, 0, 100), [0]);
|
|
154
|
+
assert.deepEqual(resolveValuePercents(null, 0, 100), [0]);
|
|
155
|
+
assert.deepEqual(resolveValuePercents('not-a-number', 0, 100), [0]);
|
|
156
|
+
assert.deepEqual(resolveValuePercents('', 0, 100), [0]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('adapter-utils-regression: ok');
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
|
|
3
|
+
(globalThis as unknown as { figma: unknown }).figma = {
|
|
4
|
+
notify: () => undefined,
|
|
5
|
+
showUI: () => undefined,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
applyAspectRatioIfPossible,
|
|
10
|
+
markAspectRatio,
|
|
11
|
+
markPositionInfo,
|
|
12
|
+
reflowDeferredAbsolutePositioningTree,
|
|
13
|
+
} from '../src/layout/deferred-layout';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Regression: an aspect-ratio container whose children are ALL absolutely
|
|
17
|
+
* positioned (e.g. the round-trip-section's triangle:
|
|
18
|
+
* `<div className="relative aspect-5/3 w-full">` with absolute icon nodes)
|
|
19
|
+
* must end up with the correct height by the time percent-position
|
|
20
|
+
* resolution runs.
|
|
21
|
+
*
|
|
22
|
+
* History: `applyAspectRatioIfPossible` is called eagerly during build,
|
|
23
|
+
* but the parent's `width` may still be 0 at that point (the mark is
|
|
24
|
+
* retained for retry). The final reflow pass
|
|
25
|
+
* (`reflowDeferredAbsolutePositioningTree`) used to call only the
|
|
26
|
+
* positioning resolvers — not the aspect-ratio retry — so the parent
|
|
27
|
+
* stayed at height=0, `applyDeferredPercentPositioning` bailed
|
|
28
|
+
* (`ph <= 0`), and percent-positioned children all stacked at y=0.
|
|
29
|
+
* The triangle of icons (Next.js top-left, Figma top-right, Storybook
|
|
30
|
+
* bottom-center) collapsed into a horizontal row.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
type StubChild = {
|
|
34
|
+
type: 'FRAME';
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
layoutPositioning: 'AUTO' | 'ABSOLUTE';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type StubFrame = {
|
|
43
|
+
type: 'FRAME';
|
|
44
|
+
layoutMode: 'NONE' | 'HORIZONTAL' | 'VERTICAL';
|
|
45
|
+
primaryAxisSizingMode: 'AUTO' | 'FIXED';
|
|
46
|
+
counterAxisSizingMode: 'AUTO' | 'FIXED';
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
children: StubChild[];
|
|
50
|
+
resize(w: number, h: number): void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function makeChild(name: string): StubChild & { name: string } {
|
|
54
|
+
return {
|
|
55
|
+
type: 'FRAME',
|
|
56
|
+
name,
|
|
57
|
+
width: 40,
|
|
58
|
+
height: 40,
|
|
59
|
+
x: 0,
|
|
60
|
+
y: 0,
|
|
61
|
+
layoutPositioning: 'ABSOLUTE',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function makeAspectFrame(width: number): StubFrame {
|
|
66
|
+
const frame: StubFrame = {
|
|
67
|
+
type: 'FRAME',
|
|
68
|
+
layoutMode: 'VERTICAL',
|
|
69
|
+
primaryAxisSizingMode: 'AUTO',
|
|
70
|
+
counterAxisSizingMode: 'AUTO',
|
|
71
|
+
width,
|
|
72
|
+
height: 0, // hug-content with all-absolute children = 0
|
|
73
|
+
children: [],
|
|
74
|
+
resize(w, h) {
|
|
75
|
+
this.width = w;
|
|
76
|
+
this.height = h;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return frame;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Scenario A — direct simulation of the retry path.
|
|
84
|
+
// applyAspectRatioIfPossible(frame) runs once with width=0 (early in build);
|
|
85
|
+
// width settles afterwards; the reflow must retry and resolve to height=360.
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
{
|
|
88
|
+
const frame = makeAspectFrame(0);
|
|
89
|
+
markAspectRatio(frame as unknown as SceneNode, 5 / 3);
|
|
90
|
+
|
|
91
|
+
// First attempt: width=0, mark retained for retry.
|
|
92
|
+
applyAspectRatioIfPossible(frame as unknown as SceneNode);
|
|
93
|
+
assert.equal(frame.height, 0, 'first attempt with width=0 leaves height untouched');
|
|
94
|
+
|
|
95
|
+
// Width settles after some intermediate pass.
|
|
96
|
+
frame.resize(600, 0);
|
|
97
|
+
|
|
98
|
+
// Reflow must re-attempt the aspect-ratio resolution.
|
|
99
|
+
reflowDeferredAbsolutePositioningTree(frame as unknown as SceneNode);
|
|
100
|
+
|
|
101
|
+
assert.equal(frame.height, 360, 'reflow retry resolves height = 600 / (5/3) = 360');
|
|
102
|
+
assert.equal(frame.primaryAxisSizingMode, 'FIXED', 'reflow locks primary axis');
|
|
103
|
+
assert.equal(frame.counterAxisSizingMode, 'FIXED', 'reflow locks counter axis');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Scenario B — the actual round-trip-section pattern: aspect-5/3 parent with
|
|
108
|
+
// three absolute children at percent positions. Before the fix, percent
|
|
109
|
+
// positioning bailed (parent.height was 0); icons stacked at y=0.
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
{
|
|
112
|
+
const frame = makeAspectFrame(600);
|
|
113
|
+
markAspectRatio(frame as unknown as SceneNode, 5 / 3);
|
|
114
|
+
|
|
115
|
+
// Children mirror the NODES array: Next.js, Figma, Storybook.
|
|
116
|
+
const nextNode = makeChild('Next.js');
|
|
117
|
+
const figmaNode = makeChild('Figma');
|
|
118
|
+
const storybookNode = makeChild('Storybook');
|
|
119
|
+
frame.children.push(nextNode, figmaNode, storybookNode);
|
|
120
|
+
|
|
121
|
+
markPositionInfo(nextNode as unknown as SceneNode, {
|
|
122
|
+
leftPercent: 0.1833,
|
|
123
|
+
topPercent: 0.3056,
|
|
124
|
+
});
|
|
125
|
+
markPositionInfo(figmaNode as unknown as SceneNode, {
|
|
126
|
+
leftPercent: 0.8167,
|
|
127
|
+
topPercent: 0.3056,
|
|
128
|
+
});
|
|
129
|
+
markPositionInfo(storybookNode as unknown as SceneNode, {
|
|
130
|
+
leftPercent: 0.5,
|
|
131
|
+
topPercent: 0.8056,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Simulate the "applyAspectRatioIfPossible ran early but width was 0" path
|
|
135
|
+
// by NOT calling it before reflow — the reflow must own the retry.
|
|
136
|
+
// (If we DID call it now it would resolve immediately because width is 600,
|
|
137
|
+
// which doesn't exercise the bug. The test in Scenario A pins that path;
|
|
138
|
+
// here we want to confirm reflow alone is sufficient.)
|
|
139
|
+
reflowDeferredAbsolutePositioningTree(frame as unknown as SceneNode);
|
|
140
|
+
|
|
141
|
+
assert.equal(frame.height, 360, 'reflow resolves aspect ratio to 360');
|
|
142
|
+
|
|
143
|
+
// Percent positions resolve against the now-final height.
|
|
144
|
+
assert.equal(Math.round(nextNode.x), Math.round(600 * 0.1833), 'Next.js x');
|
|
145
|
+
assert.equal(Math.round(nextNode.y), Math.round(360 * 0.3056), 'Next.js y');
|
|
146
|
+
assert.equal(Math.round(figmaNode.x), Math.round(600 * 0.8167), 'Figma x');
|
|
147
|
+
assert.equal(Math.round(figmaNode.y), Math.round(360 * 0.3056), 'Figma y');
|
|
148
|
+
assert.equal(Math.round(storybookNode.x), Math.round(600 * 0.5), 'Storybook x');
|
|
149
|
+
assert.equal(Math.round(storybookNode.y), Math.round(360 * 0.8056), 'Storybook y');
|
|
150
|
+
|
|
151
|
+
// Critical invariant: Storybook must sit BELOW the other two — that's the
|
|
152
|
+
// visual signal of a working triangle layout.
|
|
153
|
+
assert.ok(
|
|
154
|
+
storybookNode.y > nextNode.y && storybookNode.y > figmaNode.y,
|
|
155
|
+
`Storybook (y=${storybookNode.y}) must be below Next.js (y=${nextNode.y}) and Figma (y=${figmaNode.y})`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Scenario C — sanity: a frame with a NON-zero height should still resolve
|
|
161
|
+
// the same way (aspect-ratio takes precedence and overrides intermediate
|
|
162
|
+
// heights, matching CSS `aspect-ratio: 5/3`).
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
{
|
|
165
|
+
const frame = makeAspectFrame(600);
|
|
166
|
+
frame.height = 200; // intermediate value some pass may have set
|
|
167
|
+
markAspectRatio(frame as unknown as SceneNode, 5 / 3);
|
|
168
|
+
|
|
169
|
+
applyAspectRatioIfPossible(frame as unknown as SceneNode);
|
|
170
|
+
assert.equal(frame.height, 360, 'aspect-ratio overrides intermediate height');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Scenario D — `-translate-x-1/2 -translate-y-1/2` centers the child on the
|
|
175
|
+
// percent point (CSS `transform: translate(-50%, -50%)`). Without this,
|
|
176
|
+
// an icon at `left-[50%] top-[50%]` is anchored top-left at the centre
|
|
177
|
+
// instead of visually centred.
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
{
|
|
180
|
+
const frame = makeAspectFrame(600);
|
|
181
|
+
markAspectRatio(frame as unknown as SceneNode, 5 / 3);
|
|
182
|
+
|
|
183
|
+
const node = makeChild('CenteredIcon');
|
|
184
|
+
node.width = 64;
|
|
185
|
+
node.height = 80;
|
|
186
|
+
frame.children.push(node);
|
|
187
|
+
|
|
188
|
+
markPositionInfo(node as unknown as SceneNode, {
|
|
189
|
+
leftPercent: 0.5,
|
|
190
|
+
topPercent: 0.5,
|
|
191
|
+
translateXFraction: -0.5,
|
|
192
|
+
translateYFraction: -0.5,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
reflowDeferredAbsolutePositioningTree(frame as unknown as SceneNode);
|
|
196
|
+
|
|
197
|
+
// Parent: 600 × 360. Percent point: (300, 180). After -translate(-50%, -50%):
|
|
198
|
+
// x = 300 - 64/2 = 268; y = 180 - 80/2 = 140.
|
|
199
|
+
assert.equal(node.x, 268, 'translate-x-1/2 centers child on percent X');
|
|
200
|
+
assert.equal(node.y, 140, 'translate-y-1/2 centers child on percent Y');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Scenario E — position-info marks must NOT be deleted after a single
|
|
205
|
+
// application. The reflow re-runs after dimensions change (aspect-ratio
|
|
206
|
+
// retry), and the percent positioning must re-resolve against the new
|
|
207
|
+
// dimensions instead of staying frozen at intermediate values.
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
{
|
|
210
|
+
const frame = makeAspectFrame(600);
|
|
211
|
+
markAspectRatio(frame as unknown as SceneNode, 5 / 3);
|
|
212
|
+
|
|
213
|
+
const node = makeChild('PercentNode');
|
|
214
|
+
frame.children.push(node);
|
|
215
|
+
markPositionInfo(node as unknown as SceneNode, {
|
|
216
|
+
leftPercent: 0.5,
|
|
217
|
+
topPercent: 0.5,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// First reflow — resolves against full dimensions.
|
|
221
|
+
reflowDeferredAbsolutePositioningTree(frame as unknown as SceneNode);
|
|
222
|
+
const firstX = node.x;
|
|
223
|
+
const firstY = node.y;
|
|
224
|
+
assert.ok(firstX > 0 && firstY > 0, 'first reflow resolves percent positions');
|
|
225
|
+
|
|
226
|
+
// Simulate parent dimensions changing (e.g. a later layout pass adjusts
|
|
227
|
+
// the parent). Re-run reflow — position must update to match the new
|
|
228
|
+
// dimensions, proving the mark wasn't deleted after first application.
|
|
229
|
+
frame.resize(800, 0);
|
|
230
|
+
markAspectRatio(frame as unknown as SceneNode, 5 / 3);
|
|
231
|
+
reflowDeferredAbsolutePositioningTree(frame as unknown as SceneNode);
|
|
232
|
+
assert.equal(frame.height, 480, 'second reflow re-resolves aspect ratio');
|
|
233
|
+
assert.equal(node.x, 400, 'position re-resolved against new width');
|
|
234
|
+
assert.equal(node.y, 240, 'position re-resolved against new height');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log('aspect-percent-position-regression: PASS');
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
|
|
3
|
+
// Stub `figma` global before importing plugin internals — these modules
|
|
4
|
+
// reference `figma.*` at import time even though our resolver only uses
|
|
5
|
+
// instance methods on real nodes at runtime. The stubs are sufficient for
|
|
6
|
+
// the parser/marker side of the test.
|
|
7
|
+
(globalThis as unknown as { figma: unknown }).figma = {
|
|
8
|
+
notify: () => undefined,
|
|
9
|
+
showUI: () => undefined,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
import { applyAspectRatioIfPossible, hasAspectRatio, markAspectRatio } from '../src/layout/deferred-layout';
|
|
13
|
+
|
|
14
|
+
// Minimal SceneNode-shaped stub: only the bits the resolver inspects.
|
|
15
|
+
type StubNode = {
|
|
16
|
+
type: 'FRAME';
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
primaryAxisSizingMode: 'AUTO' | 'FIXED';
|
|
20
|
+
counterAxisSizingMode: 'AUTO' | 'FIXED';
|
|
21
|
+
resize(w: number, h: number): void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function makeStub(width: number, height: number): StubNode {
|
|
25
|
+
const node: StubNode = {
|
|
26
|
+
type: 'FRAME',
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
primaryAxisSizingMode: 'AUTO',
|
|
30
|
+
counterAxisSizingMode: 'AUTO',
|
|
31
|
+
resize(w, h) {
|
|
32
|
+
this.width = w;
|
|
33
|
+
this.height = h;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return node;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function runRegression(): void {
|
|
40
|
+
// ---- aspect-5/3 with width 600 → height 360 -----------------------------
|
|
41
|
+
const node = makeStub(600, 0);
|
|
42
|
+
markAspectRatio(node as unknown as SceneNode, 5 / 3);
|
|
43
|
+
assert.equal(hasAspectRatio(node as unknown as SceneNode), true, 'mark should register');
|
|
44
|
+
|
|
45
|
+
applyAspectRatioIfPossible(node as unknown as SceneNode);
|
|
46
|
+
assert.equal(node.width, 600, 'width preserved');
|
|
47
|
+
assert.equal(node.height, 360, 'height = 600 / (5/3) = 360 (5:3)');
|
|
48
|
+
assert.equal(node.primaryAxisSizingMode, 'FIXED', 'primary axis locked');
|
|
49
|
+
assert.equal(node.counterAxisSizingMode, 'FIXED', 'counter axis locked');
|
|
50
|
+
assert.equal(hasAspectRatio(node as unknown as SceneNode), false, 'mark cleared after apply');
|
|
51
|
+
|
|
52
|
+
// ---- aspect-square with width 240 → height 240 --------------------------
|
|
53
|
+
const square = makeStub(240, 0);
|
|
54
|
+
markAspectRatio(square as unknown as SceneNode, 1);
|
|
55
|
+
applyAspectRatioIfPossible(square as unknown as SceneNode);
|
|
56
|
+
assert.equal(square.height, 240, 'square ratio = width');
|
|
57
|
+
|
|
58
|
+
// ---- aspect-6/1 with width 1200 → height 200 ----------------------------
|
|
59
|
+
const wide = makeStub(1200, 0);
|
|
60
|
+
markAspectRatio(wide as unknown as SceneNode, 6);
|
|
61
|
+
applyAspectRatioIfPossible(wide as unknown as SceneNode);
|
|
62
|
+
assert.equal(wide.height, 200, 'aspect-6/1 ratio = 6 → 1200/6=200');
|
|
63
|
+
|
|
64
|
+
// ---- invalid ratios are ignored (no throw, no mark) ---------------------
|
|
65
|
+
const noop = makeStub(500, 100);
|
|
66
|
+
markAspectRatio(noop as unknown as SceneNode, 0);
|
|
67
|
+
markAspectRatio(noop as unknown as SceneNode, -2);
|
|
68
|
+
markAspectRatio(noop as unknown as SceneNode, Number.NaN);
|
|
69
|
+
assert.equal(hasAspectRatio(noop as unknown as SceneNode), false, 'invalid ratios rejected');
|
|
70
|
+
applyAspectRatioIfPossible(noop as unknown as SceneNode);
|
|
71
|
+
assert.equal(noop.height, 100, 'no resize when no ratio marked');
|
|
72
|
+
|
|
73
|
+
// ---- zero/missing width: skip (don't crash, don't divide by zero) -------
|
|
74
|
+
const collapsed = makeStub(0, 50);
|
|
75
|
+
markAspectRatio(collapsed as unknown as SceneNode, 5 / 3);
|
|
76
|
+
applyAspectRatioIfPossible(collapsed as unknown as SceneNode);
|
|
77
|
+
assert.equal(collapsed.width, 0, 'collapsed width untouched');
|
|
78
|
+
assert.equal(collapsed.height, 50, 'collapsed height untouched');
|
|
79
|
+
// Mark should NOT be cleared so the resolver can be retried after width settles.
|
|
80
|
+
assert.equal(hasAspectRatio(collapsed as unknown as SceneNode), true, 'mark retained for retry');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
runRegression();
|
|
85
|
+
console.log('aspect-ratio-regression: PASS');
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error('aspect-ratio-regression: FAIL');
|
|
88
|
+
console.error(err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { resolveBlobDimensions, resolveBlobPlacement } from '../src/blob-placement';
|
|
3
|
-
import { getClassesForBreakpoint } from '../src/responsive-analyzer';
|
|
2
|
+
import { resolveBlobDimensions, resolveBlobPlacement } from '../src/effects/blob-placement';
|
|
3
|
+
import { getClassesForBreakpoint } from '../src/tailwind/responsive-analyzer';
|
|
4
4
|
|
|
5
5
|
type CaseDef = {
|
|
6
6
|
name: string;
|