@seed-design/figma 0.0.6 → 0.0.17
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/lib/index.cjs +5548 -4901
- package/lib/index.d.ts +489 -189
- package/lib/index.js +5535 -4888
- package/package.json +3 -2
- package/src/codegen/core/codegen.ts +65 -0
- package/src/codegen/core/component.ts +15 -27
- package/src/codegen/core/component.types.ts +29 -0
- package/src/codegen/core/element.ts +13 -0
- package/src/codegen/core/index.ts +13 -8
- package/src/codegen/core/infer-layout.test.ts +285 -0
- package/src/codegen/core/infer-layout.ts +416 -0
- package/src/codegen/core/jsx.ts +12 -0
- package/src/codegen/core/props.ts +81 -0
- package/src/codegen/core/value.ts +289 -0
- package/src/codegen/index.ts +39 -6
- package/src/codegen/targets/figma/context.ts +139 -0
- package/src/codegen/targets/figma/frame.ts +37 -0
- package/src/codegen/targets/figma/index.ts +6 -0
- package/src/codegen/targets/figma/instance.ts +16 -0
- package/src/codegen/targets/figma/props.ts +244 -0
- package/src/codegen/targets/figma/shape.ts +62 -0
- package/src/codegen/targets/figma/text.ts +33 -0
- package/src/codegen/targets/index.ts +2 -0
- package/src/codegen/{domain/seed-component → targets/react/component}/deps.interface.ts +2 -2
- package/src/codegen/{domain/seed-component → targets/react/component}/index.ts +36 -34
- package/src/codegen/{domain/seed-component → targets/react/component}/properties.type.ts +2 -2
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/action-button.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/action-chip.ts +3 -4
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/action-sheet.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/app-bar.ts +5 -6
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/avatar-stack.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/avatar.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/badge.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/callout.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/checkbox.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/chip-tabs.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/control-chip.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/error-state.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/extended-action-sheet.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/extended-fab.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/fab.ts +2 -2
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/help-bubble.ts +2 -2
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/identity-placeholder.ts +2 -2
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/inline-banner.ts +5 -6
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/manner-temp-badge.ts +3 -4
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/multiline-text-field.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/progress-circle.ts +3 -4
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/reaction-button.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/segmented-control.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/select-box.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/skeleton.ts +3 -4
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/snackbar.ts +3 -4
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/switch.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/tabs.ts +5 -6
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/text-button.ts +6 -7
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/text-field.ts +4 -5
- package/src/codegen/{domain/seed-component → targets/react/component}/transformers/toggle-button.ts +4 -5
- package/src/codegen/targets/react/context.ts +170 -0
- package/src/codegen/targets/react/frame.ts +75 -0
- package/src/codegen/targets/react/index.ts +7 -0
- package/src/codegen/{domain/instance.service.ts → targets/react/instance.ts} +20 -33
- package/src/codegen/targets/react/props.ts +361 -0
- package/src/codegen/targets/react/shape.ts +36 -0
- package/src/codegen/targets/react/text.ts +33 -0
- package/src/{codegen → entities}/data/icons.ts +1 -1
- package/src/{codegen → entities}/data/styles.ts +1 -1
- package/src/{codegen → entities}/data/variable-collections.ts +1 -1
- package/src/{codegen → entities}/data/variables.ts +1 -1
- package/src/entities/index.ts +41 -0
- package/src/{codegen/domain → entities}/style.repository.ts +6 -2
- package/src/{codegen/domain → entities}/style.service.ts +1 -1
- package/src/{codegen/domain → entities}/variable.repository.ts +17 -4
- package/src/{codegen/domain → entities}/variable.service.ts +47 -9
- package/src/index.ts +1 -0
- package/src/normalizer/from-plugin.ts +3 -0
- package/src/normalizer/types.ts +28 -24
- package/src/utils/common.ts +4 -0
- package/src/utils/css.ts +10 -4
- package/src/utils/figma-node.ts +42 -2
- package/src/codegen/context.ts +0 -148
- package/src/codegen/core/transformer.ts +0 -40
- package/src/codegen/domain/codegen.service.ts +0 -69
- package/src/codegen/domain/figma-component.service.ts +0 -21
- package/src/codegen/domain/frame.service.ts +0 -108
- package/src/codegen/domain/index.ts +0 -22
- package/src/codegen/domain/props/container-layout-props.service.ts +0 -248
- package/src/codegen/domain/props/fill-props.service.ts +0 -75
- package/src/codegen/domain/props/radius-props.service.ts +0 -105
- package/src/codegen/domain/props/self-layout-props.service.ts +0 -127
- package/src/codegen/domain/props/stroke-props.service.ts +0 -45
- package/src/codegen/domain/props/type-style-props.service.ts +0 -31
- package/src/codegen/domain/rectangle.service.ts +0 -31
- package/src/codegen/domain/text.service.ts +0 -62
- /package/src/codegen/{domain/seed-component → targets/react/component}/size.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/action-button.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/action-button.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/action-chip.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/action-chip.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/action-sheet.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/action-sheet.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/avatar-stack.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/avatar-stack.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/avatar.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/avatar.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/badge.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/badge.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/bottom-navigation-global.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/bottom-navigation-global.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/bottom-navigation-kr.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/bottom-navigation-kr.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/bottom-sheet.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/bottom-sheet.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/callout.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/callout.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/checkbox.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/checkbox.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/chip-tablist.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/chip-tablist.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/control-chip.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/control-chip.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/divider.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/divider.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/error-state.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/error-state.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/extended-action-sheet.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/extended-action-sheet.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/extended-floating-action-button.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/extended-floating-action-button.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/floating-action-button.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/floating-action-button.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/help-bubble.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/help-bubble.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/identity-placeholder.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/identity-placeholder.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/index.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/index.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/inline-banner.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/inline-banner.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/main-tab-navigation-global.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/main-tab-navigation-global.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/main-tab-navigation-kr.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/main-tab-navigation-kr.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/manner-temp-badge.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/manner-temp-badge.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/manner-temp-bar.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/manner-temp-bar.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/manner-temp.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/manner-temp.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/multiline-text-field.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/multiline-text-field.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/progress-circle.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/progress-circle.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/radio.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/radio.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/range-slider.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/range-slider.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/reaction-button.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/reaction-button.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/segmented-control.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/segmented-control.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/select-box.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/select-box.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/skeleton.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/skeleton.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/slider.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/slider.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/snackbar.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/snackbar.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/standard-navigation.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/standard-navigation.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/switch.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/switch.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/tablist.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/tablist.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-bottom-fixed-bar.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-bottom-fixed-bar.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-button-group.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-button-group.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-chip-group.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-chip-group.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-select-box-group.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-select-box-group.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-top-navigation.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/template-top-navigation.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/text-button.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/text-button.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/text-field.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/text-field.mjs +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/toggle-button.d.ts +0 -0
- /package/src/{codegen → entities}/data/__generated__/component-sets/toggle-button.mjs +0 -0
- /package/src/{codegen/domain → entities}/icon.interface.ts +0 -0
- /package/src/{codegen/domain → entities}/icon.repository.ts +0 -0
- /package/src/{codegen/domain → entities}/icon.service.ts +0 -0
- /package/src/{codegen/domain → entities}/style.interface.ts +0 -0
- /package/src/{codegen/domain → entities}/variable.interface.ts +0 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NormalizedHasChildrenTrait,
|
|
3
|
+
NormalizedHasFramePropertiesTrait,
|
|
4
|
+
NormalizedHasLayoutTrait,
|
|
5
|
+
NormalizedIsLayerTrait,
|
|
6
|
+
} from "@/normalizer";
|
|
7
|
+
|
|
8
|
+
interface BoundingBox {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LayoutProperties {
|
|
16
|
+
layoutMode?: "NONE" | "HORIZONTAL" | "VERTICAL";
|
|
17
|
+
primaryAxisSizingMode?: "FIXED" | "AUTO";
|
|
18
|
+
counterAxisSizingMode?: "FIXED" | "AUTO";
|
|
19
|
+
primaryAxisAlignItems?: "MIN" | "CENTER" | "MAX" | "SPACE_BETWEEN";
|
|
20
|
+
counterAxisAlignItems?: "MIN" | "CENTER" | "MAX"; // 'BASELINE' requires more info
|
|
21
|
+
paddingLeft?: number;
|
|
22
|
+
paddingRight?: number;
|
|
23
|
+
paddingTop?: number;
|
|
24
|
+
paddingBottom?: number;
|
|
25
|
+
itemSpacing?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface InferResult {
|
|
29
|
+
properties: LayoutProperties;
|
|
30
|
+
childProperties: {
|
|
31
|
+
[childId: string]: {
|
|
32
|
+
layoutAlign?: "MIN" | "STRETCH";
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type LayoutNode = NormalizedIsLayerTrait &
|
|
38
|
+
NormalizedHasFramePropertiesTrait &
|
|
39
|
+
NormalizedHasChildrenTrait &
|
|
40
|
+
NormalizedHasLayoutTrait;
|
|
41
|
+
|
|
42
|
+
// --- Helper Functions ---
|
|
43
|
+
|
|
44
|
+
function getCollectiveBoundingBox(nodes: LayoutNode[]): BoundingBox | null {
|
|
45
|
+
if (nodes.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
50
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
51
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
52
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
53
|
+
|
|
54
|
+
nodes.forEach((node) => {
|
|
55
|
+
const box = node.absoluteBoundingBox!;
|
|
56
|
+
minX = Math.min(minX, box.x);
|
|
57
|
+
minY = Math.min(minY, box.y);
|
|
58
|
+
maxX = Math.max(maxX, box.x + box.width);
|
|
59
|
+
maxY = Math.max(maxY, box.y + box.height);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
x: minX,
|
|
64
|
+
y: minY,
|
|
65
|
+
width: maxX - minX,
|
|
66
|
+
height: maxY - minY,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function calculateMean(arr: number[]): number {
|
|
71
|
+
if (arr.length === 0) return 0;
|
|
72
|
+
return arr.reduce((sum, val) => sum + val, 0) / arr.length;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function calculateVariance(arr: number[]): number {
|
|
76
|
+
if (arr.length < 2) return 0;
|
|
77
|
+
const mean = calculateMean(arr);
|
|
78
|
+
return arr.reduce((sum, val) => sum + (val - mean) ** 2, 0) / arr.length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function calculateMedian(arr: number[]): number {
|
|
82
|
+
if (arr.length === 0) return 0;
|
|
83
|
+
const sortedArr = [...arr].sort((a, b) => a - b);
|
|
84
|
+
const mid = Math.floor(sortedArr.length / 2);
|
|
85
|
+
if (sortedArr.length % 2 === 0) {
|
|
86
|
+
return (sortedArr[mid - 1] + sortedArr[mid]) / 2;
|
|
87
|
+
}
|
|
88
|
+
return sortedArr[mid];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Tolerance for floating point comparisons and alignment checks
|
|
92
|
+
const EPSILON = 1; // 1 pixel tolerance
|
|
93
|
+
|
|
94
|
+
// --- Main Inference Function ---
|
|
95
|
+
|
|
96
|
+
export function inferLayout(parentNode: LayoutNode): InferResult {
|
|
97
|
+
if (parentNode.layoutMode !== "NONE") {
|
|
98
|
+
return {
|
|
99
|
+
properties: {},
|
|
100
|
+
childProperties: {},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const children = (parentNode.children || []) as LayoutNode[];
|
|
105
|
+
const parentBox = parentNode.absoluteBoundingBox!;
|
|
106
|
+
const result: LayoutProperties = { layoutMode: "NONE" };
|
|
107
|
+
|
|
108
|
+
if (children.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
properties: {},
|
|
111
|
+
childProperties: {},
|
|
112
|
+
}; // Cannot infer layout for no children
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (children.length === 1) {
|
|
116
|
+
// Default for single child: Horizontal, Hug contents, No spacing, Calculate padding
|
|
117
|
+
result.layoutMode = "HORIZONTAL";
|
|
118
|
+
result.primaryAxisSizingMode = "AUTO";
|
|
119
|
+
result.counterAxisSizingMode = "AUTO";
|
|
120
|
+
result.itemSpacing = 0;
|
|
121
|
+
result.primaryAxisAlignItems = "MIN"; // Doesn't matter for one item
|
|
122
|
+
result.counterAxisAlignItems = "MIN"; // Doesn't matter for one item
|
|
123
|
+
|
|
124
|
+
const childBox = children[0].absoluteBoundingBox!;
|
|
125
|
+
result.paddingLeft = Math.max(0, childBox.x - parentBox.x);
|
|
126
|
+
result.paddingRight = Math.max(
|
|
127
|
+
0,
|
|
128
|
+
parentBox.x + parentBox.width - (childBox.x + childBox.width),
|
|
129
|
+
);
|
|
130
|
+
result.paddingTop = Math.max(0, childBox.y - parentBox.y);
|
|
131
|
+
result.paddingBottom = Math.max(
|
|
132
|
+
0,
|
|
133
|
+
parentBox.y + parentBox.height - (childBox.y + childBox.height),
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
properties: result,
|
|
137
|
+
childProperties: {},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- 1. Determine Layout Direction ---
|
|
142
|
+
const sortedByX = [...children].sort(
|
|
143
|
+
(a, b) => a.absoluteBoundingBox!.x - b.absoluteBoundingBox!.x,
|
|
144
|
+
);
|
|
145
|
+
const sortedByY = [...children].sort(
|
|
146
|
+
(a, b) => a.absoluteBoundingBox!.y - b.absoluteBoundingBox!.y,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const horizontalGaps: number[] = [];
|
|
150
|
+
for (let i = 0; i < sortedByX.length - 1; i++) {
|
|
151
|
+
const current = sortedByX[i].absoluteBoundingBox!;
|
|
152
|
+
const next = sortedByX[i + 1].absoluteBoundingBox!;
|
|
153
|
+
// Ensure items don't significantly overlap vertically for horizontal check
|
|
154
|
+
if (Math.max(current.y, next.y) < Math.min(current.y + current.height, next.y + next.height)) {
|
|
155
|
+
horizontalGaps.push(next.x - (current.x + current.width));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const verticalGaps: number[] = [];
|
|
160
|
+
for (let i = 0; i < sortedByY.length - 1; i++) {
|
|
161
|
+
const current = sortedByY[i].absoluteBoundingBox!;
|
|
162
|
+
const next = sortedByY[i + 1].absoluteBoundingBox!;
|
|
163
|
+
// Ensure items don't significantly overlap horizontally for vertical check
|
|
164
|
+
if (Math.max(current.x, next.x) < Math.min(current.x + current.width, next.x + next.width)) {
|
|
165
|
+
verticalGaps.push(next.y - (current.y + current.height));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Heuristic: Prefer axis with more non-negative gaps and lower variance
|
|
170
|
+
const hVariance = calculateVariance(horizontalGaps.filter((g) => g >= -EPSILON));
|
|
171
|
+
const vVariance = calculateVariance(verticalGaps.filter((g) => g >= -EPSILON));
|
|
172
|
+
const hCount = horizontalGaps.filter((g) => g >= -EPSILON).length;
|
|
173
|
+
const vCount = verticalGaps.filter((g) => g >= -EPSILON).length;
|
|
174
|
+
|
|
175
|
+
let primaryAxisSortedNodes = sortedByX; // Default guess
|
|
176
|
+
|
|
177
|
+
// Basic variance check (lower is better). Add slight bias for horizontal if equal.
|
|
178
|
+
if (
|
|
179
|
+
vCount > 0 &&
|
|
180
|
+
(hCount === 0 ||
|
|
181
|
+
(vVariance < hVariance - EPSILON && vCount >= hCount) ||
|
|
182
|
+
(vVariance <= hVariance && vCount > hCount))
|
|
183
|
+
) {
|
|
184
|
+
result.layoutMode = "VERTICAL";
|
|
185
|
+
primaryAxisSortedNodes = sortedByY;
|
|
186
|
+
} else if (hCount > 0) {
|
|
187
|
+
result.layoutMode = "HORIZONTAL";
|
|
188
|
+
primaryAxisSortedNodes = sortedByX;
|
|
189
|
+
} else {
|
|
190
|
+
// Ambiguous case based on gaps, fall back to bounding box aspect ratio
|
|
191
|
+
const collectiveBox = getCollectiveBoundingBox(children);
|
|
192
|
+
if (collectiveBox) {
|
|
193
|
+
if (collectiveBox.height > collectiveBox.width) {
|
|
194
|
+
result.layoutMode = "VERTICAL";
|
|
195
|
+
primaryAxisSortedNodes = sortedByY;
|
|
196
|
+
} else {
|
|
197
|
+
result.layoutMode = "HORIZONTAL";
|
|
198
|
+
primaryAxisSortedNodes = sortedByX;
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
// Still nothing? Default to Horizontal
|
|
202
|
+
result.layoutMode = "HORIZONTAL";
|
|
203
|
+
primaryAxisSortedNodes = sortedByX;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const primaryGaps = result.layoutMode === "HORIZONTAL" ? horizontalGaps : verticalGaps;
|
|
208
|
+
const validGaps = primaryGaps.filter((g) => g >= -EPSILON); // Allow slight overlap
|
|
209
|
+
|
|
210
|
+
// --- 2. Calculate Spacing & Primary Alignment ---
|
|
211
|
+
let isSpaceBetween = false;
|
|
212
|
+
const collectiveBox = getCollectiveBoundingBox(children);
|
|
213
|
+
|
|
214
|
+
if (collectiveBox && children.length >= 2) {
|
|
215
|
+
// Check for Space Between potential
|
|
216
|
+
const first = primaryAxisSortedNodes[0].absoluteBoundingBox!;
|
|
217
|
+
const last = primaryAxisSortedNodes[primaryAxisSortedNodes.length - 1].absoluteBoundingBox!;
|
|
218
|
+
let firstStart: number;
|
|
219
|
+
let lastEnd: number;
|
|
220
|
+
let parentSize: number;
|
|
221
|
+
|
|
222
|
+
if (result.layoutMode === "HORIZONTAL") {
|
|
223
|
+
firstStart = first.x;
|
|
224
|
+
lastEnd = last.x + last.width;
|
|
225
|
+
parentSize = parentBox.width;
|
|
226
|
+
} else {
|
|
227
|
+
firstStart = first.y;
|
|
228
|
+
lastEnd = last.y + last.height;
|
|
229
|
+
parentSize = parentBox.height;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const contentSpan = lastEnd - firstStart;
|
|
233
|
+
|
|
234
|
+
// Heuristic for Space Between: Content spans most of the parent & average gap is large
|
|
235
|
+
const averageGap = calculateMean(validGaps);
|
|
236
|
+
// Example threshold: Content fills > 85% AND average gap is > 20% of average item size? Or just large?
|
|
237
|
+
if (contentSpan > parentSize * 0.8 && validGaps.length > 0 && averageGap > 10) {
|
|
238
|
+
// Additional check: are first/last items close to parent edges (considering padding)?
|
|
239
|
+
const startPadding =
|
|
240
|
+
result.layoutMode === "HORIZONTAL" ? first.x - parentBox.x : first.y - parentBox.y;
|
|
241
|
+
const endPadding =
|
|
242
|
+
result.layoutMode === "HORIZONTAL"
|
|
243
|
+
? parentBox.x + parentBox.width - (last.x + last.width)
|
|
244
|
+
: parentBox.y + parentBox.height - (last.y + last.height);
|
|
245
|
+
|
|
246
|
+
// If start/end items are reasonably close to edges (e.g., < 2 * average gap?)
|
|
247
|
+
if (
|
|
248
|
+
Math.abs(startPadding) < Math.max(20, averageGap * 1.5) &&
|
|
249
|
+
Math.abs(endPadding) < Math.max(20, averageGap * 1.5)
|
|
250
|
+
) {
|
|
251
|
+
isSpaceBetween = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (isSpaceBetween) {
|
|
257
|
+
result.primaryAxisAlignItems = "SPACE_BETWEEN";
|
|
258
|
+
result.itemSpacing = 0; // Spacing is implicit
|
|
259
|
+
result.primaryAxisSizingMode = "FIXED"; // Usually fixed when using space between
|
|
260
|
+
} else {
|
|
261
|
+
result.primaryAxisAlignItems = "MIN"; // Default to MIN for packed, could refine later
|
|
262
|
+
if (validGaps.length > 0) {
|
|
263
|
+
// Use median spacing for robustness against outliers
|
|
264
|
+
result.itemSpacing = calculateMedian(validGaps);
|
|
265
|
+
// Clamp negative spacing if it's very small (likely float error)
|
|
266
|
+
if (result.itemSpacing < 0 && result.itemSpacing > -EPSILON) {
|
|
267
|
+
result.itemSpacing = 0;
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
result.itemSpacing = 0; // No valid gaps found
|
|
271
|
+
}
|
|
272
|
+
result.primaryAxisSizingMode = "AUTO"; // Default to hug content for packed
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// --- 3. Calculate Padding ---
|
|
276
|
+
if (collectiveBox) {
|
|
277
|
+
result.paddingLeft = Math.max(0, collectiveBox.x - parentBox.x);
|
|
278
|
+
result.paddingRight = Math.max(
|
|
279
|
+
0,
|
|
280
|
+
parentBox.x + parentBox.width - (collectiveBox.x + collectiveBox.width),
|
|
281
|
+
);
|
|
282
|
+
result.paddingTop = Math.max(0, collectiveBox.y - parentBox.y);
|
|
283
|
+
result.paddingBottom = Math.max(
|
|
284
|
+
0,
|
|
285
|
+
parentBox.y + parentBox.height - (collectiveBox.y + collectiveBox.height),
|
|
286
|
+
);
|
|
287
|
+
} else {
|
|
288
|
+
result.paddingLeft = 0;
|
|
289
|
+
result.paddingRight = 0;
|
|
290
|
+
result.paddingTop = 0;
|
|
291
|
+
result.paddingBottom = 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// --- 4. Determine Counter Axis Alignment ---
|
|
295
|
+
const counterCoordsMin: number[] = [];
|
|
296
|
+
const counterCoordsCenter: number[] = [];
|
|
297
|
+
const counterCoordsMax: number[] = [];
|
|
298
|
+
|
|
299
|
+
if (result.layoutMode === "HORIZONTAL") {
|
|
300
|
+
// Check vertical alignment (Y)
|
|
301
|
+
children.forEach((node) => {
|
|
302
|
+
const box = node.absoluteBoundingBox!;
|
|
303
|
+
counterCoordsMin.push(box.y);
|
|
304
|
+
counterCoordsCenter.push(box.y + box.height / 2);
|
|
305
|
+
counterCoordsMax.push(box.y + box.height);
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
// VERTICAL layout
|
|
309
|
+
// Check horizontal alignment (X)
|
|
310
|
+
children.forEach((node) => {
|
|
311
|
+
const box = node.absoluteBoundingBox!;
|
|
312
|
+
counterCoordsMin.push(box.x);
|
|
313
|
+
counterCoordsCenter.push(box.x + box.width / 2);
|
|
314
|
+
counterCoordsMax.push(box.x + box.width);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const minVariance = calculateVariance(counterCoordsMin);
|
|
319
|
+
const centerVariance = calculateVariance(counterCoordsCenter);
|
|
320
|
+
const maxVariance = calculateVariance(counterCoordsMax);
|
|
321
|
+
|
|
322
|
+
const alignmentTolerance = EPSILON * EPSILON * 4; // Allow slightly more variance for alignment match
|
|
323
|
+
if (
|
|
324
|
+
minVariance <= centerVariance &&
|
|
325
|
+
minVariance <= maxVariance &&
|
|
326
|
+
minVariance < alignmentTolerance
|
|
327
|
+
) {
|
|
328
|
+
result.counterAxisAlignItems = "MIN";
|
|
329
|
+
} else if (
|
|
330
|
+
centerVariance <= minVariance &&
|
|
331
|
+
centerVariance <= maxVariance &&
|
|
332
|
+
centerVariance < alignmentTolerance
|
|
333
|
+
) {
|
|
334
|
+
result.counterAxisAlignItems = "CENTER";
|
|
335
|
+
} else if (
|
|
336
|
+
maxVariance <= minVariance &&
|
|
337
|
+
maxVariance <= centerVariance &&
|
|
338
|
+
maxVariance < alignmentTolerance
|
|
339
|
+
) {
|
|
340
|
+
result.counterAxisAlignItems = "MAX";
|
|
341
|
+
} else {
|
|
342
|
+
// Default if variances are high or similar
|
|
343
|
+
result.counterAxisAlignItems = "CENTER";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// --- 5. Determine Counter Axis Sizing Mode ---
|
|
347
|
+
// Default to AUTO unless children perfectly fill the parent counter dimension
|
|
348
|
+
result.counterAxisSizingMode = "AUTO";
|
|
349
|
+
if (collectiveBox) {
|
|
350
|
+
let collectiveCounterSize: number;
|
|
351
|
+
let parentCounterSize: number;
|
|
352
|
+
if (result.layoutMode === "HORIZONTAL") {
|
|
353
|
+
collectiveCounterSize = collectiveBox.height;
|
|
354
|
+
parentCounterSize = parentBox.height - (result.paddingTop ?? 0) - (result.paddingBottom ?? 0);
|
|
355
|
+
} else {
|
|
356
|
+
collectiveCounterSize = collectiveBox.width;
|
|
357
|
+
parentCounterSize = parentBox.width - (result.paddingLeft ?? 0) - (result.paddingRight ?? 0);
|
|
358
|
+
}
|
|
359
|
+
// If collective size is very close to parent size on counter axis
|
|
360
|
+
if (Math.abs(collectiveCounterSize - parentCounterSize) < EPSILON) {
|
|
361
|
+
result.counterAxisSizingMode = "FIXED";
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 6. Infer layoutAlign for each child
|
|
366
|
+
const childProperties: InferResult["childProperties"] = {};
|
|
367
|
+
const availableWidth = parentBox.width - (result.paddingLeft ?? 0) - (result.paddingRight ?? 0);
|
|
368
|
+
const availableHeight = parentBox.height - (result.paddingTop ?? 0) - (result.paddingBottom ?? 0);
|
|
369
|
+
|
|
370
|
+
children.forEach((child) => {
|
|
371
|
+
const childBox = child.absoluteBoundingBox!;
|
|
372
|
+
let inferredChildAlign: "INHERIT" | "STRETCH" | undefined = undefined;
|
|
373
|
+
|
|
374
|
+
// Check STRETCH
|
|
375
|
+
if (result.layoutMode === "HORIZONTAL") {
|
|
376
|
+
// Counter: Vertical
|
|
377
|
+
if (Math.abs(childBox.height - availableHeight) < EPSILON && availableHeight > 0) {
|
|
378
|
+
inferredChildAlign = "STRETCH";
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
// Counter: Horizontal
|
|
382
|
+
if (Math.abs(childBox.width - availableWidth) < EPSILON && availableWidth > 0) {
|
|
383
|
+
inferredChildAlign = "STRETCH";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (inferredChildAlign) {
|
|
388
|
+
childProperties[child.id] = { layoutAlign: inferredChildAlign };
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
properties: result,
|
|
394
|
+
childProperties,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function applyInferredLayout<T extends LayoutNode>(parentNode: T, result: InferResult): T {
|
|
399
|
+
const { properties, childProperties } = result;
|
|
400
|
+
|
|
401
|
+
if (properties.layoutMode === "NONE") {
|
|
402
|
+
return parentNode;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
...parentNode,
|
|
407
|
+
...properties,
|
|
408
|
+
children: parentNode.children.map((child) => {
|
|
409
|
+
const props = childProperties[child.id];
|
|
410
|
+
if (props) {
|
|
411
|
+
return { ...child, ...props };
|
|
412
|
+
}
|
|
413
|
+
return child;
|
|
414
|
+
}),
|
|
415
|
+
};
|
|
416
|
+
}
|
package/src/codegen/core/jsx.ts
CHANGED
|
@@ -24,6 +24,18 @@ export function createElement(
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export function cloneElement(
|
|
28
|
+
element: ElementNode,
|
|
29
|
+
props: Record<string, string | number | boolean | object | undefined> = {},
|
|
30
|
+
children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
...element,
|
|
34
|
+
props: { ...element.props, ...props },
|
|
35
|
+
children: children ? ensureArray(children).filter(exists) : element.children,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
export function appendSource(element: ElementNode, source: string) {
|
|
28
40
|
return {
|
|
29
41
|
...element,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { VariableValueResolved } from "@/entities";
|
|
2
|
+
import type { NormalizedSceneNode } from "@/normalizer";
|
|
3
|
+
import { objectEntries } from "@/utils/common";
|
|
4
|
+
import type { ElementNode } from "./jsx";
|
|
5
|
+
|
|
6
|
+
export type PropsTransformer<
|
|
7
|
+
T extends Record<string, any> = Record<string, any>,
|
|
8
|
+
R extends Record<string, any> = Record<string, any>,
|
|
9
|
+
> = (node: T, traverse: (node: NormalizedSceneNode) => ElementNode | undefined) => R;
|
|
10
|
+
|
|
11
|
+
export function definePropsTransformer<
|
|
12
|
+
T extends Record<string, any>,
|
|
13
|
+
R extends Record<string, any>,
|
|
14
|
+
>(transformer: PropsTransformer<T, R>) {
|
|
15
|
+
return transformer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Handlers<
|
|
19
|
+
TTrait extends Record<string, VariableValueResolved>,
|
|
20
|
+
TProps extends Record<string, any>,
|
|
21
|
+
HandlerKeys extends keyof TProps = keyof TProps,
|
|
22
|
+
> = {
|
|
23
|
+
[K in HandlerKeys]: (node: TTrait) => TProps[K];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<
|
|
27
|
+
Exclude<keyof TProps, HandlerKeys>,
|
|
28
|
+
HandlerKeys[]
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
export interface PropsTransformerConfig<
|
|
32
|
+
TTrait extends Record<string, any>,
|
|
33
|
+
TProps extends Record<string, any>,
|
|
34
|
+
HandlerKeys extends keyof TProps,
|
|
35
|
+
> {
|
|
36
|
+
_types: {
|
|
37
|
+
trait: TTrait;
|
|
38
|
+
props: TProps;
|
|
39
|
+
};
|
|
40
|
+
handlers: Handlers<TTrait, TProps, HandlerKeys>;
|
|
41
|
+
shorthands?: Shorthands<TProps, HandlerKeys>;
|
|
42
|
+
defaults?: Partial<TProps>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createPropsTransformer<
|
|
46
|
+
TTrait extends Record<string, any>,
|
|
47
|
+
TProps extends Record<string, any>,
|
|
48
|
+
HandlerKeys extends keyof TProps,
|
|
49
|
+
>({
|
|
50
|
+
handlers,
|
|
51
|
+
shorthands,
|
|
52
|
+
defaults,
|
|
53
|
+
}: PropsTransformerConfig<TTrait, TProps, HandlerKeys>): PropsTransformer<TTrait, TProps> {
|
|
54
|
+
return definePropsTransformer((node: TTrait) => {
|
|
55
|
+
const result = {} as TProps;
|
|
56
|
+
|
|
57
|
+
for (const [prop, handler] of objectEntries(handlers)) {
|
|
58
|
+
const value = handler(node);
|
|
59
|
+
if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {
|
|
60
|
+
result[prop as keyof TProps] = value as any;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (shorthands) {
|
|
65
|
+
for (const [shorthand, props] of objectEntries(shorthands)) {
|
|
66
|
+
const values = props.map((prop) => result[prop as keyof TProps]);
|
|
67
|
+
const allDefined = values.every((value) => value !== undefined);
|
|
68
|
+
const allEqual = allDefined && values.every((value) => value === values[0]);
|
|
69
|
+
|
|
70
|
+
if (allEqual && values[0] !== undefined) {
|
|
71
|
+
result[shorthand as keyof TProps] = values[0] as any;
|
|
72
|
+
for (const prop of props) {
|
|
73
|
+
delete result[prop as keyof TProps];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
});
|
|
81
|
+
}
|