@hypen-space/web 0.4.36 → 0.4.38
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 +7 -6
- package/dist/canvas/accessibility.js +174 -174
- package/dist/canvas/accessibility.js.map +1 -10
- package/dist/canvas/events.js +264 -360
- package/dist/canvas/events.js.map +1 -11
- package/dist/canvas/index.js +11 -47
- package/dist/canvas/index.js.map +1 -9
- package/dist/canvas/input.js +178 -289
- package/dist/canvas/input.js.map +1 -11
- package/dist/canvas/layout.js +349 -547
- package/dist/canvas/layout.js.map +1 -12
- package/dist/canvas/paint.js +1122 -1146
- package/dist/canvas/paint.js.map +1 -12
- package/dist/canvas/renderer.js +352 -2259
- package/dist/canvas/renderer.js.map +1 -17
- package/dist/canvas/text.js +129 -264
- package/dist/canvas/text.js.map +1 -11
- package/dist/canvas/types.js +7 -2
- package/dist/canvas/types.js.map +1 -9
- package/dist/canvas/utils.js +158 -161
- package/dist/canvas/utils.js.map +1 -10
- package/dist/dom/applicators/advanced-layout.js +105 -138
- package/dist/dom/applicators/advanced-layout.js.map +1 -10
- package/dist/dom/applicators/background.js +36 -76
- package/dist/dom/applicators/background.js.map +1 -10
- package/dist/dom/applicators/border.js +82 -106
- package/dist/dom/applicators/border.js.map +1 -10
- package/dist/dom/applicators/color.js +17 -58
- package/dist/dom/applicators/color.js.map +1 -10
- package/dist/dom/applicators/display.js +39 -79
- package/dist/dom/applicators/display.js.map +1 -10
- package/dist/dom/applicators/effects.js +121 -147
- package/dist/dom/applicators/effects.js.map +1 -10
- package/dist/dom/applicators/events.js +413 -450
- package/dist/dom/applicators/events.js.map +1 -11
- package/dist/dom/applicators/font.js +119 -140
- package/dist/dom/applicators/font.js.map +1 -10
- package/dist/dom/applicators/index.js +386 -1770
- package/dist/dom/applicators/index.js.map +1 -26
- package/dist/dom/applicators/layout.js +143 -149
- package/dist/dom/applicators/layout.js.map +1 -10
- package/dist/dom/applicators/margin.js +64 -105
- package/dist/dom/applicators/margin.js.map +1 -10
- package/dist/dom/applicators/padding.js +64 -105
- package/dist/dom/applicators/padding.js.map +1 -10
- package/dist/dom/applicators/size.js +169 -172
- package/dist/dom/applicators/size.js.map +1 -10
- package/dist/dom/applicators/transform.js +73 -114
- package/dist/dom/applicators/transform.js.map +1 -10
- package/dist/dom/applicators/transition.js +47 -88
- package/dist/dom/applicators/transition.js.map +1 -10
- package/dist/dom/applicators/types.js +7 -2
- package/dist/dom/applicators/types.js.map +1 -9
- package/dist/dom/applicators/typography.js +68 -109
- package/dist/dom/applicators/typography.js.map +1 -10
- package/dist/dom/canvas/index.js +49 -83
- package/dist/dom/canvas/index.js.map +1 -10
- package/dist/dom/components/app.js +16 -63
- package/dist/dom/components/app.js.map +1 -10
- package/dist/dom/components/audio.js +31 -76
- package/dist/dom/components/audio.js.map +1 -10
- package/dist/dom/components/avatar.js +41 -87
- package/dist/dom/components/avatar.js.map +1 -10
- package/dist/dom/components/badge.js +35 -83
- package/dist/dom/components/badge.js.map +1 -10
- package/dist/dom/components/button.js +26 -69
- package/dist/dom/components/button.js.map +1 -10
- package/dist/dom/components/card.js +13 -63
- package/dist/dom/components/card.js.map +1 -10
- package/dist/dom/components/center.js +21 -65
- package/dist/dom/components/center.js.map +1 -10
- package/dist/dom/components/checkbox.js +37 -82
- package/dist/dom/components/checkbox.js.map +1 -10
- package/dist/dom/components/column.js +16 -62
- package/dist/dom/components/column.js.map +1 -10
- package/dist/dom/components/container.js +11 -59
- package/dist/dom/components/container.js.map +1 -10
- package/dist/dom/components/divider.js +27 -73
- package/dist/dom/components/divider.js.map +1 -10
- package/dist/dom/components/grid.js +41 -80
- package/dist/dom/components/grid.js.map +1 -10
- package/dist/dom/components/heading.js +28 -74
- package/dist/dom/components/heading.js.map +1 -10
- package/dist/dom/components/hypenapp.js +291 -288
- package/dist/dom/components/hypenapp.js.map +1 -10
- package/dist/dom/components/image.js +18 -67
- package/dist/dom/components/image.js.map +1 -10
- package/dist/dom/components/index.js +109 -1427
- package/dist/dom/components/index.js.map +1 -42
- package/dist/dom/components/input.js +19 -69
- package/dist/dom/components/input.js.map +1 -10
- package/dist/dom/components/link.js +23 -70
- package/dist/dom/components/link.js.map +1 -10
- package/dist/dom/components/list.js +26 -72
- package/dist/dom/components/list.js.map +1 -10
- package/dist/dom/components/paragraph.js +14 -63
- package/dist/dom/components/paragraph.js.map +1 -10
- package/dist/dom/components/progressbar.js +38 -85
- package/dist/dom/components/progressbar.js.map +1 -10
- package/dist/dom/components/route.js +27 -73
- package/dist/dom/components/route.js.map +1 -10
- package/dist/dom/components/router.js +15 -62
- package/dist/dom/components/router.js.map +1 -10
- package/dist/dom/components/row.js +25 -70
- package/dist/dom/components/row.js.map +1 -10
- package/dist/dom/components/select.js +42 -85
- package/dist/dom/components/select.js.map +1 -10
- package/dist/dom/components/slider.js +31 -76
- package/dist/dom/components/slider.js.map +1 -10
- package/dist/dom/components/spacer.js +10 -60
- package/dist/dom/components/spacer.js.map +1 -10
- package/dist/dom/components/spinner.js +41 -88
- package/dist/dom/components/spinner.js.map +1 -10
- package/dist/dom/components/stack.js +19 -67
- package/dist/dom/components/stack.js.map +1 -10
- package/dist/dom/components/switch.js +50 -93
- package/dist/dom/components/switch.js.map +1 -10
- package/dist/dom/components/text.js +24 -69
- package/dist/dom/components/text.js.map +1 -10
- package/dist/dom/components/textarea.js +35 -79
- package/dist/dom/components/textarea.js.map +1 -10
- package/dist/dom/components/video.js +35 -79
- package/dist/dom/components/video.js.map +1 -10
- package/dist/dom/debug.js +159 -160
- package/dist/dom/debug.js.map +1 -10
- package/dist/dom/element-data.js +181 -143
- package/dist/dom/element-data.js.map +1 -10
- package/dist/dom/events.js +133 -142
- package/dist/dom/events.js.map +1 -10
- package/dist/dom/index.js +11 -48
- package/dist/dom/index.js.map +1 -9
- package/dist/dom/renderer.js +311 -3596
- package/dist/dom/renderer.js.map +1 -62
- package/dist/index.d.ts +2 -2
- package/dist/index.js +36 -53
- package/dist/index.js.map +1 -9
- package/package.json +2 -16
- package/src/canvas/QUICKSTART.md +4 -3
- package/src/canvas/README.md +3 -2
- package/src/dom/README.md +4 -3
- package/src/index.ts +2 -2
- package/dist/client.d.ts +0 -19
- package/dist/client.js +0 -45
- package/dist/client.js.map +0 -9
- package/dist/hypen.d.ts +0 -140
- package/dist/hypen.js +0 -3909
- package/dist/hypen.js.map +0 -63
- package/src/client.ts +0 -20
- package/src/hypen.ts +0 -556
package/dist/canvas/layout.js
CHANGED
|
@@ -1,571 +1,373 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Layout Engine
|
|
3
|
+
*
|
|
4
|
+
* Flexbox-like layout system for positioning virtual nodes
|
|
5
|
+
*/
|
|
6
|
+
import { parseSpacing, parseSize } from "./utils.js";
|
|
7
|
+
import { measureText } from "./text.js";
|
|
8
|
+
/**
|
|
9
|
+
* Compute layout for a virtual node tree
|
|
10
|
+
*/
|
|
11
|
+
export function computeLayout(ctx, node, availableWidth, availableHeight, x = 0, y = 0) {
|
|
12
|
+
// Parse props
|
|
13
|
+
const props = node.props;
|
|
14
|
+
// Support individual margins (marginTop, marginRight, etc.)
|
|
15
|
+
let margin = parseSpacing(props.margin || 0);
|
|
16
|
+
if (props.marginTop !== undefined)
|
|
17
|
+
margin.top = parseFloat(props.marginTop) || 0;
|
|
18
|
+
if (props.marginRight !== undefined)
|
|
19
|
+
margin.right = parseFloat(props.marginRight) || 0;
|
|
20
|
+
if (props.marginBottom !== undefined)
|
|
21
|
+
margin.bottom = parseFloat(props.marginBottom) || 0;
|
|
22
|
+
if (props.marginLeft !== undefined)
|
|
23
|
+
margin.left = parseFloat(props.marginLeft) || 0;
|
|
24
|
+
// Support individual padding (paddingTop, paddingRight, etc.)
|
|
25
|
+
let padding = parseSpacing(props.padding || 0);
|
|
26
|
+
if (props.paddingTop !== undefined)
|
|
27
|
+
padding.top = parseFloat(props.paddingTop) || 0;
|
|
28
|
+
if (props.paddingRight !== undefined)
|
|
29
|
+
padding.right = parseFloat(props.paddingRight) || 0;
|
|
30
|
+
if (props.paddingBottom !== undefined)
|
|
31
|
+
padding.bottom = parseFloat(props.paddingBottom) || 0;
|
|
32
|
+
if (props.paddingLeft !== undefined)
|
|
33
|
+
padding.left = parseFloat(props.paddingLeft) || 0;
|
|
34
|
+
const borderWidth = parseFloat(props.borderWidth) || 0;
|
|
35
|
+
const borderColor = props.borderColor || "transparent";
|
|
36
|
+
const borderRadius = parseFloat(props.borderRadius) || 0;
|
|
37
|
+
// Calculate available space after margin
|
|
38
|
+
const availableAfterMargin = {
|
|
39
|
+
width: availableWidth - margin.left - margin.right,
|
|
40
|
+
height: availableHeight - margin.top - margin.bottom,
|
|
41
|
+
};
|
|
42
|
+
// Determine size
|
|
43
|
+
let width = parseSize(props.width);
|
|
44
|
+
let height = parseSize(props.height);
|
|
45
|
+
// Default sizing for specific component types
|
|
46
|
+
const type = node.type.toLowerCase();
|
|
47
|
+
if (type === "app") {
|
|
48
|
+
// App fills the full available space by default
|
|
49
|
+
if (width === null)
|
|
50
|
+
width = availableAfterMargin.width;
|
|
51
|
+
if (height === null)
|
|
52
|
+
height = availableAfterMargin.height;
|
|
49
53
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
54
|
+
else if (type === "spacer") {
|
|
55
|
+
// Spacer fills available space by default
|
|
56
|
+
if (width === null)
|
|
57
|
+
width = availableAfterMargin.width;
|
|
58
|
+
if (height === null)
|
|
59
|
+
height = availableAfterMargin.height;
|
|
52
60
|
}
|
|
53
|
-
if (
|
|
54
|
-
|
|
61
|
+
else if (type === "divider" || type === "separator") {
|
|
62
|
+
const orientation = props.orientation || "horizontal";
|
|
63
|
+
const thickness = parseFloat(props.thickness) || 1;
|
|
64
|
+
if (orientation === "vertical") {
|
|
65
|
+
if (width === null)
|
|
66
|
+
width = thickness;
|
|
67
|
+
if (height === null)
|
|
68
|
+
height = availableAfterMargin.height;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (width === null)
|
|
72
|
+
width = availableAfterMargin.width;
|
|
73
|
+
if (height === null)
|
|
74
|
+
height = thickness;
|
|
75
|
+
}
|
|
55
76
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
left: parseFloat(value.left) || 0
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
66
|
-
}
|
|
67
|
-
function parseSize(value) {
|
|
68
|
-
if (typeof value === "number")
|
|
69
|
-
return value;
|
|
70
|
-
if (typeof value === "string") {
|
|
71
|
-
if (value === "auto")
|
|
72
|
-
return null;
|
|
73
|
-
const num = parseFloat(value);
|
|
74
|
-
return isNaN(num) ? null : num;
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
function isPointInRect(point, rect) {
|
|
79
|
-
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
80
|
-
}
|
|
81
|
-
function isPointInRoundedRect(point, rect, radius) {
|
|
82
|
-
const { x, y, width, height } = rect;
|
|
83
|
-
if (!isPointInRect(point, rect))
|
|
84
|
-
return false;
|
|
85
|
-
if (radius <= 0)
|
|
86
|
-
return true;
|
|
87
|
-
const px = point.x;
|
|
88
|
-
const py = point.y;
|
|
89
|
-
if (px < x + radius && py < y + radius) {
|
|
90
|
-
return Math.pow(px - (x + radius), 2) + Math.pow(py - (y + radius), 2) <= Math.pow(radius, 2);
|
|
91
|
-
}
|
|
92
|
-
if (px > x + width - radius && py < y + radius) {
|
|
93
|
-
return Math.pow(px - (x + width - radius), 2) + Math.pow(py - (y + radius), 2) <= Math.pow(radius, 2);
|
|
94
|
-
}
|
|
95
|
-
if (px < x + radius && py > y + height - radius) {
|
|
96
|
-
return Math.pow(px - (x + radius), 2) + Math.pow(py - (y + height - radius), 2) <= Math.pow(radius, 2);
|
|
97
|
-
}
|
|
98
|
-
if (px > x + width - radius && py > y + height - radius) {
|
|
99
|
-
return Math.pow(px - (x + width - radius), 2) + Math.pow(py - (y + height - radius), 2) <= Math.pow(radius, 2);
|
|
100
|
-
}
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
function mergeRects(rects) {
|
|
104
|
-
if (rects.length === 0)
|
|
105
|
-
return null;
|
|
106
|
-
if (rects.length === 1)
|
|
107
|
-
return rects[0];
|
|
108
|
-
let minX = Infinity;
|
|
109
|
-
let minY = Infinity;
|
|
110
|
-
let maxX = -Infinity;
|
|
111
|
-
let maxY = -Infinity;
|
|
112
|
-
for (const rect of rects) {
|
|
113
|
-
minX = Math.min(minX, rect.x);
|
|
114
|
-
minY = Math.min(minY, rect.y);
|
|
115
|
-
maxX = Math.max(maxX, rect.x + rect.width);
|
|
116
|
-
maxY = Math.max(maxY, rect.y + rect.height);
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
x: minX,
|
|
120
|
-
y: minY,
|
|
121
|
-
width: maxX - minX,
|
|
122
|
-
height: maxY - minY
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function createFontString(fontSize, fontWeight, fontFamily) {
|
|
126
|
-
return `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
127
|
-
}
|
|
128
|
-
function walkTree(node, callback) {
|
|
129
|
-
callback(node);
|
|
130
|
-
for (const child of node.children) {
|
|
131
|
-
walkTree(child, callback);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function findNodeById(root, id) {
|
|
135
|
-
if (root.id === id)
|
|
136
|
-
return root;
|
|
137
|
-
for (const child of root.children) {
|
|
138
|
-
const found = findNodeById(child, id);
|
|
139
|
-
if (found)
|
|
140
|
-
return found;
|
|
141
|
-
}
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
function getAbsoluteBounds(node) {
|
|
145
|
-
if (!node.layout)
|
|
146
|
-
return null;
|
|
147
|
-
let x = node.layout.x;
|
|
148
|
-
let y = node.layout.y;
|
|
149
|
-
let current = node.parent;
|
|
150
|
-
while (current && current.layout) {
|
|
151
|
-
x += current.layout.contentX;
|
|
152
|
-
y += current.layout.contentY;
|
|
153
|
-
current = current.parent;
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
x,
|
|
157
|
-
y,
|
|
158
|
-
width: node.layout.width,
|
|
159
|
-
height: node.layout.height
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// src/canvas/text.ts
|
|
164
|
-
import { frameworkLoggers } from "@hypen-space/core/logger";
|
|
165
|
-
var log = frameworkLoggers.canvas;
|
|
166
|
-
var textMetricsCache = new Map;
|
|
167
|
-
function getCacheKey(text, fontStyle, maxWidth) {
|
|
168
|
-
return `${text}|${fontStyle.fontSize}|${fontStyle.fontWeight}|${fontStyle.fontFamily}|${maxWidth || "auto"}`;
|
|
169
|
-
}
|
|
170
|
-
function measureText(ctx, text, fontStyle, maxWidth) {
|
|
171
|
-
const cacheKey = getCacheKey(text, fontStyle, maxWidth);
|
|
172
|
-
const cached = textMetricsCache.get(cacheKey);
|
|
173
|
-
if (cached)
|
|
174
|
-
return cached;
|
|
175
|
-
const font = createFontString(fontStyle.fontSize, fontStyle.fontWeight, fontStyle.fontFamily);
|
|
176
|
-
ctx.save();
|
|
177
|
-
ctx.font = font;
|
|
178
|
-
const lineHeight = fontStyle.lineHeight || fontStyle.fontSize * 1.2;
|
|
179
|
-
if (!maxWidth) {
|
|
180
|
-
const metrics = ctx.measureText(text);
|
|
181
|
-
const result2 = {
|
|
182
|
-
width: metrics.width,
|
|
183
|
-
height: lineHeight,
|
|
184
|
-
lines: [text],
|
|
185
|
-
lineHeight
|
|
186
|
-
};
|
|
187
|
-
ctx.restore();
|
|
188
|
-
textMetricsCache.set(cacheKey, result2);
|
|
189
|
-
return result2;
|
|
190
|
-
}
|
|
191
|
-
const lines = wrapText(ctx, text, maxWidth);
|
|
192
|
-
const width = Math.max(...lines.map((line) => ctx.measureText(line).width));
|
|
193
|
-
const height = lines.length * lineHeight;
|
|
194
|
-
const result = {
|
|
195
|
-
width,
|
|
196
|
-
height,
|
|
197
|
-
lines,
|
|
198
|
-
lineHeight
|
|
199
|
-
};
|
|
200
|
-
ctx.restore();
|
|
201
|
-
textMetricsCache.set(cacheKey, result);
|
|
202
|
-
return result;
|
|
203
|
-
}
|
|
204
|
-
function wrapText(ctx, text, maxWidth) {
|
|
205
|
-
const lines = [];
|
|
206
|
-
const paragraphs = text.split(`
|
|
207
|
-
`);
|
|
208
|
-
for (const paragraph of paragraphs) {
|
|
209
|
-
const words = paragraph.split(" ");
|
|
210
|
-
let currentLine = "";
|
|
211
|
-
for (const word of words) {
|
|
212
|
-
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
213
|
-
const metrics = ctx.measureText(testLine);
|
|
214
|
-
if (metrics.width > maxWidth && currentLine) {
|
|
215
|
-
lines.push(currentLine);
|
|
216
|
-
currentLine = word;
|
|
217
|
-
} else {
|
|
218
|
-
currentLine = testLine;
|
|
219
|
-
}
|
|
77
|
+
else if (type === "checkbox" || type === "radio") {
|
|
78
|
+
const size = parseFloat(props.size) || 20;
|
|
79
|
+
if (width === null)
|
|
80
|
+
width = size;
|
|
81
|
+
if (height === null)
|
|
82
|
+
height = size;
|
|
220
83
|
}
|
|
221
|
-
if (
|
|
222
|
-
|
|
84
|
+
else if (type === "switch" || type === "toggle") {
|
|
85
|
+
if (width === null)
|
|
86
|
+
width = 44;
|
|
87
|
+
if (height === null)
|
|
88
|
+
height = 24;
|
|
223
89
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
ctx.save();
|
|
230
|
-
ctx.font = font;
|
|
231
|
-
ctx.fillStyle = style.color;
|
|
232
|
-
ctx.textBaseline = "top";
|
|
233
|
-
const metrics = measureText(ctx, text, style, width);
|
|
234
|
-
let startY = y;
|
|
235
|
-
if (style.verticalAlign === "middle") {
|
|
236
|
-
startY = y + (height - metrics.height) / 2;
|
|
237
|
-
} else if (style.verticalAlign === "bottom") {
|
|
238
|
-
startY = y + height - metrics.height;
|
|
239
|
-
}
|
|
240
|
-
for (let i = 0;i < metrics.lines.length; i++) {
|
|
241
|
-
const line = metrics.lines[i];
|
|
242
|
-
const lineY = startY + i * metrics.lineHeight;
|
|
243
|
-
let lineX = x;
|
|
244
|
-
if (style.textAlign === "center") {
|
|
245
|
-
const lineWidth = ctx.measureText(line).width;
|
|
246
|
-
lineX = x + (width - lineWidth) / 2;
|
|
247
|
-
} else if (style.textAlign === "right") {
|
|
248
|
-
const lineWidth = ctx.measureText(line).width;
|
|
249
|
-
lineX = x + width - lineWidth;
|
|
90
|
+
else if (type === "slider") {
|
|
91
|
+
if (width === null)
|
|
92
|
+
width = 200;
|
|
93
|
+
if (height === null)
|
|
94
|
+
height = 20;
|
|
250
95
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const type = node.type.toLowerCase();
|
|
300
|
-
if (type === "app") {
|
|
301
|
-
if (width === null)
|
|
302
|
-
width = availableAfterMargin.width;
|
|
303
|
-
if (height === null)
|
|
304
|
-
height = availableAfterMargin.height;
|
|
305
|
-
} else if (type === "spacer") {
|
|
96
|
+
else if (type === "progress" || type === "progressbar") {
|
|
97
|
+
if (width === null)
|
|
98
|
+
width = 200;
|
|
99
|
+
if (height === null)
|
|
100
|
+
height = 8;
|
|
101
|
+
}
|
|
102
|
+
else if (type === "spinner" || type === "loading") {
|
|
103
|
+
const size = parseFloat(props.size) || 24;
|
|
104
|
+
if (width === null)
|
|
105
|
+
width = size;
|
|
106
|
+
if (height === null)
|
|
107
|
+
height = size;
|
|
108
|
+
}
|
|
109
|
+
else if (type === "badge") {
|
|
110
|
+
if (width === null)
|
|
111
|
+
width = 20;
|
|
112
|
+
if (height === null)
|
|
113
|
+
height = 20;
|
|
114
|
+
}
|
|
115
|
+
else if (type === "avatar") {
|
|
116
|
+
const size = parseFloat(props.size) || 40;
|
|
117
|
+
if (width === null)
|
|
118
|
+
width = size;
|
|
119
|
+
if (height === null)
|
|
120
|
+
height = size;
|
|
121
|
+
}
|
|
122
|
+
else if (type === "icon") {
|
|
123
|
+
const size = parseFloat(props.size) || 24;
|
|
124
|
+
if (width === null)
|
|
125
|
+
width = size;
|
|
126
|
+
if (height === null)
|
|
127
|
+
height = size;
|
|
128
|
+
}
|
|
129
|
+
// Intrinsic sizing for text nodes
|
|
130
|
+
if (node.type === "text" && node.props[0]) {
|
|
131
|
+
const text = String(node.props[0] || "");
|
|
132
|
+
const fontSize = parseFloat(props.fontSize) || 16;
|
|
133
|
+
const fontWeight = props.fontWeight || "normal";
|
|
134
|
+
const fontFamily = props.fontFamily || "system-ui, sans-serif";
|
|
135
|
+
const lineHeight = parseFloat(props.lineHeight) || fontSize * 1.2;
|
|
136
|
+
const maxWidth = width || availableAfterMargin.width - padding.left - padding.right;
|
|
137
|
+
const metrics = measureText(ctx, text, { fontSize, fontWeight, fontFamily, lineHeight }, maxWidth);
|
|
138
|
+
if (!width)
|
|
139
|
+
width = metrics.width + padding.left + padding.right;
|
|
140
|
+
if (!height)
|
|
141
|
+
height = metrics.height + padding.top + padding.bottom;
|
|
142
|
+
}
|
|
143
|
+
// Default to available size if not specified
|
|
306
144
|
if (width === null)
|
|
307
|
-
|
|
145
|
+
width = availableAfterMargin.width;
|
|
308
146
|
if (height === null)
|
|
309
|
-
height = availableAfterMargin.height;
|
|
310
|
-
} else if (type === "divider" || type === "separator") {
|
|
311
|
-
const orientation = props.orientation || "horizontal";
|
|
312
|
-
const thickness = parseFloat(props.thickness) || 1;
|
|
313
|
-
if (orientation === "vertical") {
|
|
314
|
-
if (width === null)
|
|
315
|
-
width = thickness;
|
|
316
|
-
if (height === null)
|
|
317
147
|
height = availableAfterMargin.height;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
148
|
+
// Apply constraints
|
|
149
|
+
const minWidth = parseSize(props.minWidth);
|
|
150
|
+
const maxWidth = parseSize(props.maxWidth);
|
|
151
|
+
const minHeight = parseSize(props.minHeight);
|
|
152
|
+
const maxHeight = parseSize(props.maxHeight);
|
|
153
|
+
if (minWidth !== null)
|
|
154
|
+
width = Math.max(width, minWidth);
|
|
155
|
+
if (maxWidth !== null)
|
|
156
|
+
width = Math.min(width, maxWidth);
|
|
157
|
+
if (minHeight !== null)
|
|
158
|
+
height = Math.max(height, minHeight);
|
|
159
|
+
if (maxHeight !== null)
|
|
160
|
+
height = Math.min(height, maxHeight);
|
|
161
|
+
// Create layout object
|
|
162
|
+
const layout = {
|
|
163
|
+
x: x + margin.left,
|
|
164
|
+
y: y + margin.top,
|
|
165
|
+
width,
|
|
166
|
+
height,
|
|
167
|
+
margin,
|
|
168
|
+
padding,
|
|
169
|
+
border: {
|
|
170
|
+
width: borderWidth,
|
|
171
|
+
color: borderColor,
|
|
172
|
+
radius: borderRadius,
|
|
173
|
+
},
|
|
174
|
+
contentX: padding.left + borderWidth,
|
|
175
|
+
contentY: padding.top + borderWidth,
|
|
176
|
+
contentWidth: width - padding.left - padding.right - borderWidth * 2,
|
|
177
|
+
contentHeight: height - padding.top - padding.bottom - borderWidth * 2,
|
|
178
|
+
};
|
|
179
|
+
node.layout = layout;
|
|
180
|
+
// Layout children if container
|
|
181
|
+
if (node.children.length > 0) {
|
|
182
|
+
layoutChildren(ctx, node);
|
|
323
183
|
}
|
|
324
|
-
} else if (type === "checkbox" || type === "radio") {
|
|
325
|
-
const size = parseFloat(props.size) || 20;
|
|
326
|
-
if (width === null)
|
|
327
|
-
width = size;
|
|
328
|
-
if (height === null)
|
|
329
|
-
height = size;
|
|
330
|
-
} else if (type === "switch" || type === "toggle") {
|
|
331
|
-
if (width === null)
|
|
332
|
-
width = 44;
|
|
333
|
-
if (height === null)
|
|
334
|
-
height = 24;
|
|
335
|
-
} else if (type === "slider") {
|
|
336
|
-
if (width === null)
|
|
337
|
-
width = 200;
|
|
338
|
-
if (height === null)
|
|
339
|
-
height = 20;
|
|
340
|
-
} else if (type === "progress" || type === "progressbar") {
|
|
341
|
-
if (width === null)
|
|
342
|
-
width = 200;
|
|
343
|
-
if (height === null)
|
|
344
|
-
height = 8;
|
|
345
|
-
} else if (type === "spinner" || type === "loading") {
|
|
346
|
-
const size = parseFloat(props.size) || 24;
|
|
347
|
-
if (width === null)
|
|
348
|
-
width = size;
|
|
349
|
-
if (height === null)
|
|
350
|
-
height = size;
|
|
351
|
-
} else if (type === "badge") {
|
|
352
|
-
if (width === null)
|
|
353
|
-
width = 20;
|
|
354
|
-
if (height === null)
|
|
355
|
-
height = 20;
|
|
356
|
-
} else if (type === "avatar") {
|
|
357
|
-
const size = parseFloat(props.size) || 40;
|
|
358
|
-
if (width === null)
|
|
359
|
-
width = size;
|
|
360
|
-
if (height === null)
|
|
361
|
-
height = size;
|
|
362
|
-
} else if (type === "icon") {
|
|
363
|
-
const size = parseFloat(props.size) || 24;
|
|
364
|
-
if (width === null)
|
|
365
|
-
width = size;
|
|
366
|
-
if (height === null)
|
|
367
|
-
height = size;
|
|
368
|
-
}
|
|
369
|
-
if (node.type === "text" && node.props[0]) {
|
|
370
|
-
const text = String(node.props[0] || "");
|
|
371
|
-
const fontSize = parseFloat(props.fontSize) || 16;
|
|
372
|
-
const fontWeight = props.fontWeight || "normal";
|
|
373
|
-
const fontFamily = props.fontFamily || "system-ui, sans-serif";
|
|
374
|
-
const lineHeight = parseFloat(props.lineHeight) || fontSize * 1.2;
|
|
375
|
-
const maxWidth2 = width || availableAfterMargin.width - padding.left - padding.right;
|
|
376
|
-
const metrics = measureText(ctx, text, { fontSize, fontWeight, fontFamily, lineHeight }, maxWidth2);
|
|
377
|
-
if (!width)
|
|
378
|
-
width = metrics.width + padding.left + padding.right;
|
|
379
|
-
if (!height)
|
|
380
|
-
height = metrics.height + padding.top + padding.bottom;
|
|
381
|
-
}
|
|
382
|
-
if (width === null)
|
|
383
|
-
width = availableAfterMargin.width;
|
|
384
|
-
if (height === null)
|
|
385
|
-
height = availableAfterMargin.height;
|
|
386
|
-
const minWidth = parseSize(props.minWidth);
|
|
387
|
-
const maxWidth = parseSize(props.maxWidth);
|
|
388
|
-
const minHeight = parseSize(props.minHeight);
|
|
389
|
-
const maxHeight = parseSize(props.maxHeight);
|
|
390
|
-
if (minWidth !== null)
|
|
391
|
-
width = Math.max(width, minWidth);
|
|
392
|
-
if (maxWidth !== null)
|
|
393
|
-
width = Math.min(width, maxWidth);
|
|
394
|
-
if (minHeight !== null)
|
|
395
|
-
height = Math.max(height, minHeight);
|
|
396
|
-
if (maxHeight !== null)
|
|
397
|
-
height = Math.min(height, maxHeight);
|
|
398
|
-
const layout = {
|
|
399
|
-
x: x + margin.left,
|
|
400
|
-
y: y + margin.top,
|
|
401
|
-
width,
|
|
402
|
-
height,
|
|
403
|
-
margin,
|
|
404
|
-
padding,
|
|
405
|
-
border: {
|
|
406
|
-
width: borderWidth,
|
|
407
|
-
color: borderColor,
|
|
408
|
-
radius: borderRadius
|
|
409
|
-
},
|
|
410
|
-
contentX: padding.left + borderWidth,
|
|
411
|
-
contentY: padding.top + borderWidth,
|
|
412
|
-
contentWidth: width - padding.left - padding.right - borderWidth * 2,
|
|
413
|
-
contentHeight: height - padding.top - padding.bottom - borderWidth * 2
|
|
414
|
-
};
|
|
415
|
-
node.layout = layout;
|
|
416
|
-
if (node.children.length > 0) {
|
|
417
|
-
layoutChildren(ctx, node);
|
|
418
|
-
}
|
|
419
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Layout children using flexbox-like rules
|
|
187
|
+
*/
|
|
420
188
|
function layoutChildren(ctx, parent) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const flexDirection = props.flexDirection || (parent.type === "column" ? "column" : "row");
|
|
428
|
-
const isColumn = flexDirection === "column";
|
|
429
|
-
const justifyContent = isColumn ? props.verticalAlignment || "flex-start" : props.horizontalAlignment || "flex-start";
|
|
430
|
-
const alignItems = isColumn ? props.horizontalAlignment || "flex-start" : props.verticalAlignment || "flex-start";
|
|
431
|
-
const gap = parseFloat(props.gap) || 0;
|
|
432
|
-
const availableWidth = layout.contentWidth;
|
|
433
|
-
const availableHeight = layout.contentHeight;
|
|
434
|
-
const childInfo = [];
|
|
435
|
-
let totalMainSize = 0;
|
|
436
|
-
let totalFlexGrow = 0;
|
|
437
|
-
let totalFlexShrink = 0;
|
|
438
|
-
for (const child of parent.children) {
|
|
439
|
-
const flexGrow = parseFloat(child.props.flexGrow) || parseFloat(child.props.flex) || 0;
|
|
440
|
-
const flexShrink = parseFloat(child.props.flexShrink) || 1;
|
|
441
|
-
const flexBasis = parseSize(child.props.flexBasis);
|
|
442
|
-
computeLayout(ctx, child, availableWidth, availableHeight, 0, 0);
|
|
443
|
-
const childLayout = child.layout;
|
|
444
|
-
let mainSize = isColumn ? childLayout.height : childLayout.width;
|
|
445
|
-
if (flexBasis !== null) {
|
|
446
|
-
mainSize = flexBasis;
|
|
447
|
-
if (isColumn) {
|
|
448
|
-
childLayout.height = flexBasis;
|
|
449
|
-
} else {
|
|
450
|
-
childLayout.width = flexBasis;
|
|
451
|
-
}
|
|
189
|
+
const layout = parent.layout;
|
|
190
|
+
const props = parent.props;
|
|
191
|
+
// Stack components overlay children on top of each other
|
|
192
|
+
if (parent.type.toLowerCase() === "stack") {
|
|
193
|
+
layoutStackChildren(ctx, parent);
|
|
194
|
+
return;
|
|
452
195
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
for (
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
196
|
+
const flexDirection = props.flexDirection || (parent.type === "column" ? "column" : "row");
|
|
197
|
+
const isColumn = flexDirection === "column";
|
|
198
|
+
// Resolve verticalAlignment/horizontalAlignment to main/cross axis based on direction
|
|
199
|
+
const justifyContent = isColumn
|
|
200
|
+
? (props.verticalAlignment || "flex-start")
|
|
201
|
+
: (props.horizontalAlignment || "flex-start");
|
|
202
|
+
const alignItems = isColumn
|
|
203
|
+
? (props.horizontalAlignment || "flex-start")
|
|
204
|
+
: (props.verticalAlignment || "flex-start");
|
|
205
|
+
const gap = parseFloat(props.gap) || 0;
|
|
206
|
+
const availableWidth = layout.contentWidth;
|
|
207
|
+
const availableHeight = layout.contentHeight;
|
|
208
|
+
// First pass: compute intrinsic sizes and collect flex info
|
|
209
|
+
const childInfo = [];
|
|
210
|
+
let totalMainSize = 0;
|
|
211
|
+
let totalFlexGrow = 0;
|
|
212
|
+
let totalFlexShrink = 0;
|
|
213
|
+
for (const child of parent.children) {
|
|
214
|
+
const flexGrow = parseFloat(child.props.flexGrow) || parseFloat(child.props.flex) || 0;
|
|
215
|
+
const flexShrink = parseFloat(child.props.flexShrink) || 1;
|
|
216
|
+
const flexBasis = parseSize(child.props.flexBasis);
|
|
217
|
+
// Compute child layout with available space
|
|
218
|
+
computeLayout(ctx, child, availableWidth, availableHeight, 0, 0);
|
|
219
|
+
const childLayout = child.layout;
|
|
220
|
+
let mainSize = isColumn ? childLayout.height : childLayout.width;
|
|
221
|
+
// Use flexBasis if specified
|
|
222
|
+
if (flexBasis !== null) {
|
|
223
|
+
mainSize = flexBasis;
|
|
224
|
+
if (isColumn) {
|
|
225
|
+
childLayout.height = flexBasis;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
childLayout.width = flexBasis;
|
|
229
|
+
}
|
|
478
230
|
}
|
|
479
|
-
|
|
480
|
-
|
|
231
|
+
childInfo.push({
|
|
232
|
+
width: childLayout.width,
|
|
233
|
+
height: childLayout.height,
|
|
234
|
+
flexGrow,
|
|
235
|
+
flexShrink,
|
|
236
|
+
flexBasis,
|
|
237
|
+
});
|
|
238
|
+
totalMainSize += mainSize;
|
|
239
|
+
totalFlexGrow += flexGrow;
|
|
240
|
+
totalFlexShrink += flexShrink;
|
|
481
241
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
242
|
+
// Add gaps
|
|
243
|
+
const totalGap = gap * (parent.children.length - 1);
|
|
244
|
+
totalMainSize += totalGap;
|
|
245
|
+
// Apply flex grow/shrink
|
|
246
|
+
const availableMain = isColumn ? availableHeight : availableWidth;
|
|
247
|
+
let remainingSpace = availableMain - totalMainSize;
|
|
248
|
+
// Distribute remaining space to flex-grow children
|
|
249
|
+
if (remainingSpace > 0 && totalFlexGrow > 0) {
|
|
250
|
+
const spacePerFlex = remainingSpace / totalFlexGrow;
|
|
251
|
+
for (let i = 0; i < parent.children.length; i++) {
|
|
252
|
+
const info = childInfo[i];
|
|
253
|
+
if (info.flexGrow > 0) {
|
|
254
|
+
const extraSpace = spacePerFlex * info.flexGrow;
|
|
255
|
+
if (isColumn) {
|
|
256
|
+
info.height += extraSpace;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
info.width += extraSpace;
|
|
260
|
+
}
|
|
261
|
+
totalMainSize += extraSpace;
|
|
262
|
+
}
|
|
494
263
|
}
|
|
495
|
-
|
|
496
|
-
|
|
264
|
+
remainingSpace = 0;
|
|
265
|
+
}
|
|
266
|
+
// Apply flex shrink if needed (content is too large)
|
|
267
|
+
if (remainingSpace < 0 && totalFlexShrink > 0) {
|
|
268
|
+
const shrinkPerFlex = Math.abs(remainingSpace) / totalFlexShrink;
|
|
269
|
+
for (let i = 0; i < parent.children.length; i++) {
|
|
270
|
+
const info = childInfo[i];
|
|
271
|
+
if (info.flexShrink > 0) {
|
|
272
|
+
const shrinkSpace = Math.min(shrinkPerFlex * info.flexShrink, isColumn ? info.height : info.width);
|
|
273
|
+
if (isColumn) {
|
|
274
|
+
info.height = Math.max(0, info.height - shrinkSpace);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
info.width = Math.max(0, info.width - shrinkSpace);
|
|
278
|
+
}
|
|
279
|
+
totalMainSize -= shrinkSpace;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
remainingSpace = availableMain - totalMainSize;
|
|
283
|
+
}
|
|
284
|
+
// Calculate starting position based on justifyContent
|
|
285
|
+
let mainStart = 0;
|
|
286
|
+
let spacing = 0;
|
|
287
|
+
if (justifyContent === "center") {
|
|
288
|
+
mainStart = Math.max(0, remainingSpace / 2);
|
|
497
289
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
let mainStart = 0;
|
|
501
|
-
let spacing = 0;
|
|
502
|
-
if (justifyContent === "center") {
|
|
503
|
-
mainStart = Math.max(0, remainingSpace / 2);
|
|
504
|
-
} else if (justifyContent === "flex-end") {
|
|
505
|
-
mainStart = Math.max(0, remainingSpace);
|
|
506
|
-
} else if (justifyContent === "space-between") {
|
|
507
|
-
spacing = remainingSpace / Math.max(1, parent.children.length - 1);
|
|
508
|
-
} else if (justifyContent === "space-around") {
|
|
509
|
-
spacing = remainingSpace / parent.children.length;
|
|
510
|
-
mainStart = spacing / 2;
|
|
511
|
-
}
|
|
512
|
-
let currentMain = mainStart;
|
|
513
|
-
for (let i = 0;i < parent.children.length; i++) {
|
|
514
|
-
const child = parent.children[i];
|
|
515
|
-
const childLayout = child.layout;
|
|
516
|
-
const info = childInfo[i];
|
|
517
|
-
childLayout.width = info.width;
|
|
518
|
-
childLayout.height = info.height;
|
|
519
|
-
let crossStart = 0;
|
|
520
|
-
const availableCross = isColumn ? availableWidth : availableHeight;
|
|
521
|
-
const childCross = isColumn ? info.width : info.height;
|
|
522
|
-
if (alignItems === "center") {
|
|
523
|
-
crossStart = (availableCross - childCross) / 2;
|
|
524
|
-
} else if (alignItems === "flex-end") {
|
|
525
|
-
crossStart = availableCross - childCross;
|
|
290
|
+
else if (justifyContent === "flex-end") {
|
|
291
|
+
mainStart = Math.max(0, remainingSpace);
|
|
526
292
|
}
|
|
527
|
-
if (
|
|
528
|
-
|
|
529
|
-
childLayout.y = layout.y + layout.contentY + currentMain;
|
|
530
|
-
currentMain += info.height + gap;
|
|
531
|
-
} else {
|
|
532
|
-
childLayout.x = layout.x + layout.contentX + currentMain;
|
|
533
|
-
childLayout.y = layout.y + layout.contentY + crossStart;
|
|
534
|
-
currentMain += info.width + gap;
|
|
293
|
+
else if (justifyContent === "space-between") {
|
|
294
|
+
spacing = remainingSpace / Math.max(1, parent.children.length - 1);
|
|
535
295
|
}
|
|
536
|
-
if (justifyContent === "space-
|
|
537
|
-
|
|
296
|
+
else if (justifyContent === "space-around") {
|
|
297
|
+
spacing = remainingSpace / parent.children.length;
|
|
298
|
+
mainStart = spacing / 2;
|
|
299
|
+
}
|
|
300
|
+
// Position children
|
|
301
|
+
let currentMain = mainStart;
|
|
302
|
+
for (let i = 0; i < parent.children.length; i++) {
|
|
303
|
+
const child = parent.children[i];
|
|
304
|
+
const childLayout = child.layout;
|
|
305
|
+
const info = childInfo[i];
|
|
306
|
+
// Update child layout with flex-adjusted size
|
|
307
|
+
childLayout.width = info.width;
|
|
308
|
+
childLayout.height = info.height;
|
|
309
|
+
// Calculate cross position based on alignItems
|
|
310
|
+
let crossStart = 0;
|
|
311
|
+
const availableCross = isColumn ? availableWidth : availableHeight;
|
|
312
|
+
const childCross = isColumn ? info.width : info.height;
|
|
313
|
+
if (alignItems === "center") {
|
|
314
|
+
crossStart = (availableCross - childCross) / 2;
|
|
315
|
+
}
|
|
316
|
+
else if (alignItems === "flex-end") {
|
|
317
|
+
crossStart = availableCross - childCross;
|
|
318
|
+
}
|
|
319
|
+
// Update child position relative to parent content area
|
|
320
|
+
if (isColumn) {
|
|
321
|
+
childLayout.x = layout.x + layout.contentX + crossStart;
|
|
322
|
+
childLayout.y = layout.y + layout.contentY + currentMain;
|
|
323
|
+
currentMain += info.height + gap;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
childLayout.x = layout.x + layout.contentX + currentMain;
|
|
327
|
+
childLayout.y = layout.y + layout.contentY + crossStart;
|
|
328
|
+
currentMain += info.width + gap;
|
|
329
|
+
}
|
|
330
|
+
if (justifyContent === "space-between" || justifyContent === "space-around") {
|
|
331
|
+
currentMain += spacing;
|
|
332
|
+
}
|
|
538
333
|
}
|
|
539
|
-
}
|
|
540
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Layout Stack children - overlays children on top of each other
|
|
337
|
+
*/
|
|
541
338
|
function layoutStackChildren(ctx, parent) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
339
|
+
const layout = parent.layout;
|
|
340
|
+
const props = parent.props;
|
|
341
|
+
// Stack uses horizontalAlignment/verticalAlignment directly
|
|
342
|
+
const horizontalAlignment = props.horizontalAlignment || "flex-start";
|
|
343
|
+
const verticalAlignment = props.verticalAlignment || "flex-start";
|
|
344
|
+
const availableWidth = layout.contentWidth;
|
|
345
|
+
const availableHeight = layout.contentHeight;
|
|
346
|
+
// Layout all children at the same position (stacked on top of each other)
|
|
347
|
+
for (const child of parent.children) {
|
|
348
|
+
// Compute child layout
|
|
349
|
+
computeLayout(ctx, child, availableWidth, availableHeight, 0, 0);
|
|
350
|
+
const childLayout = child.layout;
|
|
351
|
+
// Calculate position based on alignment
|
|
352
|
+
let x = 0;
|
|
353
|
+
let y = 0;
|
|
354
|
+
// Horizontal alignment
|
|
355
|
+
if (horizontalAlignment === "center") {
|
|
356
|
+
x = (availableWidth - childLayout.width) / 2;
|
|
357
|
+
}
|
|
358
|
+
else if (horizontalAlignment === "flex-end") {
|
|
359
|
+
x = availableWidth - childLayout.width;
|
|
360
|
+
}
|
|
361
|
+
// Vertical alignment
|
|
362
|
+
if (verticalAlignment === "center") {
|
|
363
|
+
y = (availableHeight - childLayout.height) / 2;
|
|
364
|
+
}
|
|
365
|
+
else if (verticalAlignment === "flex-end") {
|
|
366
|
+
y = availableHeight - childLayout.height;
|
|
367
|
+
}
|
|
368
|
+
// Update child position relative to parent content area
|
|
369
|
+
childLayout.x = layout.x + layout.contentX + x;
|
|
370
|
+
childLayout.y = layout.y + layout.contentY + y;
|
|
562
371
|
}
|
|
563
|
-
childLayout.x = layout.x + layout.contentX + x;
|
|
564
|
-
childLayout.y = layout.y + layout.contentY + y;
|
|
565
|
-
}
|
|
566
372
|
}
|
|
567
|
-
|
|
568
|
-
computeLayout
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
//# debugId=107090D4899BFD6E64756E2164756E21
|
|
373
|
+
//# sourceMappingURL=layout.js.map
|