@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.
Files changed (149) hide show
  1. package/README.md +7 -6
  2. package/dist/canvas/accessibility.js +174 -174
  3. package/dist/canvas/accessibility.js.map +1 -10
  4. package/dist/canvas/events.js +264 -360
  5. package/dist/canvas/events.js.map +1 -11
  6. package/dist/canvas/index.js +11 -47
  7. package/dist/canvas/index.js.map +1 -9
  8. package/dist/canvas/input.js +178 -289
  9. package/dist/canvas/input.js.map +1 -11
  10. package/dist/canvas/layout.js +349 -547
  11. package/dist/canvas/layout.js.map +1 -12
  12. package/dist/canvas/paint.js +1122 -1146
  13. package/dist/canvas/paint.js.map +1 -12
  14. package/dist/canvas/renderer.js +352 -2259
  15. package/dist/canvas/renderer.js.map +1 -17
  16. package/dist/canvas/text.js +129 -264
  17. package/dist/canvas/text.js.map +1 -11
  18. package/dist/canvas/types.js +7 -2
  19. package/dist/canvas/types.js.map +1 -9
  20. package/dist/canvas/utils.js +158 -161
  21. package/dist/canvas/utils.js.map +1 -10
  22. package/dist/dom/applicators/advanced-layout.js +105 -138
  23. package/dist/dom/applicators/advanced-layout.js.map +1 -10
  24. package/dist/dom/applicators/background.js +36 -76
  25. package/dist/dom/applicators/background.js.map +1 -10
  26. package/dist/dom/applicators/border.js +82 -106
  27. package/dist/dom/applicators/border.js.map +1 -10
  28. package/dist/dom/applicators/color.js +17 -58
  29. package/dist/dom/applicators/color.js.map +1 -10
  30. package/dist/dom/applicators/display.js +39 -79
  31. package/dist/dom/applicators/display.js.map +1 -10
  32. package/dist/dom/applicators/effects.js +121 -147
  33. package/dist/dom/applicators/effects.js.map +1 -10
  34. package/dist/dom/applicators/events.js +413 -450
  35. package/dist/dom/applicators/events.js.map +1 -11
  36. package/dist/dom/applicators/font.js +119 -140
  37. package/dist/dom/applicators/font.js.map +1 -10
  38. package/dist/dom/applicators/index.js +386 -1770
  39. package/dist/dom/applicators/index.js.map +1 -26
  40. package/dist/dom/applicators/layout.js +143 -149
  41. package/dist/dom/applicators/layout.js.map +1 -10
  42. package/dist/dom/applicators/margin.js +64 -105
  43. package/dist/dom/applicators/margin.js.map +1 -10
  44. package/dist/dom/applicators/padding.js +64 -105
  45. package/dist/dom/applicators/padding.js.map +1 -10
  46. package/dist/dom/applicators/size.js +169 -172
  47. package/dist/dom/applicators/size.js.map +1 -10
  48. package/dist/dom/applicators/transform.js +73 -114
  49. package/dist/dom/applicators/transform.js.map +1 -10
  50. package/dist/dom/applicators/transition.js +47 -88
  51. package/dist/dom/applicators/transition.js.map +1 -10
  52. package/dist/dom/applicators/types.js +7 -2
  53. package/dist/dom/applicators/types.js.map +1 -9
  54. package/dist/dom/applicators/typography.js +68 -109
  55. package/dist/dom/applicators/typography.js.map +1 -10
  56. package/dist/dom/canvas/index.js +49 -83
  57. package/dist/dom/canvas/index.js.map +1 -10
  58. package/dist/dom/components/app.js +16 -63
  59. package/dist/dom/components/app.js.map +1 -10
  60. package/dist/dom/components/audio.js +31 -76
  61. package/dist/dom/components/audio.js.map +1 -10
  62. package/dist/dom/components/avatar.js +41 -87
  63. package/dist/dom/components/avatar.js.map +1 -10
  64. package/dist/dom/components/badge.js +35 -83
  65. package/dist/dom/components/badge.js.map +1 -10
  66. package/dist/dom/components/button.js +26 -69
  67. package/dist/dom/components/button.js.map +1 -10
  68. package/dist/dom/components/card.js +13 -63
  69. package/dist/dom/components/card.js.map +1 -10
  70. package/dist/dom/components/center.js +21 -65
  71. package/dist/dom/components/center.js.map +1 -10
  72. package/dist/dom/components/checkbox.js +37 -82
  73. package/dist/dom/components/checkbox.js.map +1 -10
  74. package/dist/dom/components/column.js +16 -62
  75. package/dist/dom/components/column.js.map +1 -10
  76. package/dist/dom/components/container.js +11 -59
  77. package/dist/dom/components/container.js.map +1 -10
  78. package/dist/dom/components/divider.js +27 -73
  79. package/dist/dom/components/divider.js.map +1 -10
  80. package/dist/dom/components/grid.js +41 -80
  81. package/dist/dom/components/grid.js.map +1 -10
  82. package/dist/dom/components/heading.js +28 -74
  83. package/dist/dom/components/heading.js.map +1 -10
  84. package/dist/dom/components/hypenapp.js +291 -288
  85. package/dist/dom/components/hypenapp.js.map +1 -10
  86. package/dist/dom/components/image.js +18 -67
  87. package/dist/dom/components/image.js.map +1 -10
  88. package/dist/dom/components/index.js +109 -1427
  89. package/dist/dom/components/index.js.map +1 -42
  90. package/dist/dom/components/input.js +19 -69
  91. package/dist/dom/components/input.js.map +1 -10
  92. package/dist/dom/components/link.js +23 -70
  93. package/dist/dom/components/link.js.map +1 -10
  94. package/dist/dom/components/list.js +26 -72
  95. package/dist/dom/components/list.js.map +1 -10
  96. package/dist/dom/components/paragraph.js +14 -63
  97. package/dist/dom/components/paragraph.js.map +1 -10
  98. package/dist/dom/components/progressbar.js +38 -85
  99. package/dist/dom/components/progressbar.js.map +1 -10
  100. package/dist/dom/components/route.js +27 -73
  101. package/dist/dom/components/route.js.map +1 -10
  102. package/dist/dom/components/router.js +15 -62
  103. package/dist/dom/components/router.js.map +1 -10
  104. package/dist/dom/components/row.js +25 -70
  105. package/dist/dom/components/row.js.map +1 -10
  106. package/dist/dom/components/select.js +42 -85
  107. package/dist/dom/components/select.js.map +1 -10
  108. package/dist/dom/components/slider.js +31 -76
  109. package/dist/dom/components/slider.js.map +1 -10
  110. package/dist/dom/components/spacer.js +10 -60
  111. package/dist/dom/components/spacer.js.map +1 -10
  112. package/dist/dom/components/spinner.js +41 -88
  113. package/dist/dom/components/spinner.js.map +1 -10
  114. package/dist/dom/components/stack.js +19 -67
  115. package/dist/dom/components/stack.js.map +1 -10
  116. package/dist/dom/components/switch.js +50 -93
  117. package/dist/dom/components/switch.js.map +1 -10
  118. package/dist/dom/components/text.js +24 -69
  119. package/dist/dom/components/text.js.map +1 -10
  120. package/dist/dom/components/textarea.js +35 -79
  121. package/dist/dom/components/textarea.js.map +1 -10
  122. package/dist/dom/components/video.js +35 -79
  123. package/dist/dom/components/video.js.map +1 -10
  124. package/dist/dom/debug.js +159 -160
  125. package/dist/dom/debug.js.map +1 -10
  126. package/dist/dom/element-data.js +181 -143
  127. package/dist/dom/element-data.js.map +1 -10
  128. package/dist/dom/events.js +133 -142
  129. package/dist/dom/events.js.map +1 -10
  130. package/dist/dom/index.js +11 -48
  131. package/dist/dom/index.js.map +1 -9
  132. package/dist/dom/renderer.js +311 -3596
  133. package/dist/dom/renderer.js.map +1 -62
  134. package/dist/index.d.ts +2 -2
  135. package/dist/index.js +36 -53
  136. package/dist/index.js.map +1 -9
  137. package/package.json +2 -16
  138. package/src/canvas/QUICKSTART.md +4 -3
  139. package/src/canvas/README.md +3 -2
  140. package/src/dom/README.md +4 -3
  141. package/src/index.ts +2 -2
  142. package/dist/client.d.ts +0 -19
  143. package/dist/client.js +0 -45
  144. package/dist/client.js.map +0 -9
  145. package/dist/hypen.d.ts +0 -140
  146. package/dist/hypen.js +0 -3909
  147. package/dist/hypen.js.map +0 -63
  148. package/src/client.ts +0 -20
  149. package/src/hypen.ts +0 -556
@@ -1,571 +1,373 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- function __accessProp(key) {
6
- return this[key];
7
- }
8
- var __toCommonJS = (from) => {
9
- var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
- if (entry)
11
- return entry;
12
- entry = __defProp({}, "__esModule", { value: true });
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (var key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(entry, key))
16
- __defProp(entry, key, {
17
- get: __accessProp.bind(from, key),
18
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
- });
20
- }
21
- __moduleCache.set(from, entry);
22
- return entry;
23
- };
24
- var __moduleCache;
25
- var __returnValue = (v) => v;
26
- function __exportSetter(name, newValue) {
27
- this[name] = __returnValue.bind(null, newValue);
28
- }
29
- var __export = (target, all) => {
30
- for (var name in all)
31
- __defProp(target, name, {
32
- get: all[name],
33
- enumerable: true,
34
- configurable: true,
35
- set: __exportSetter.bind(all, name)
36
- });
37
- };
38
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
39
-
40
- // src/canvas/utils.ts
41
- function parseSpacing(value) {
42
- if (typeof value === "number") {
43
- return { top: value, right: value, bottom: value, left: value };
44
- }
45
- if (typeof value === "string") {
46
- const parts = value.split(/\s+/).map((v) => parseFloat(v) || 0);
47
- if (parts.length === 1) {
48
- return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };
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 (parts.length === 2) {
51
- return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };
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 (parts.length === 4) {
54
- return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };
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
- if (typeof value === "object" && value !== null) {
58
- return {
59
- top: parseFloat(value.top) || 0,
60
- right: parseFloat(value.right) || 0,
61
- bottom: parseFloat(value.bottom) || 0,
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 (currentLine) {
222
- lines.push(currentLine);
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
- return lines.length > 0 ? lines : [""];
226
- }
227
- function renderText(ctx, text, x, y, width, height, style) {
228
- const font = createFontString(style.fontSize, style.fontWeight, style.fontFamily);
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
- ctx.fillText(line, lineX, lineY);
252
- }
253
- ctx.restore();
254
- }
255
- function clearTextCache() {
256
- textMetricsCache.clear();
257
- }
258
- async function loadFont(fontFamily, fontWeight = "normal") {
259
- if (!("fonts" in document))
260
- return;
261
- const font = `${fontWeight} 16px ${fontFamily}`;
262
- try {
263
- await document.fonts.load(font);
264
- } catch (error) {
265
- log.warn(`Failed to load font: ${font}`, error);
266
- }
267
- }
268
-
269
- // src/canvas/layout.ts
270
- function computeLayout(ctx, node, availableWidth, availableHeight, x = 0, y = 0) {
271
- const props = node.props;
272
- let margin = parseSpacing(props.margin || 0);
273
- if (props.marginTop !== undefined)
274
- margin.top = parseFloat(props.marginTop) || 0;
275
- if (props.marginRight !== undefined)
276
- margin.right = parseFloat(props.marginRight) || 0;
277
- if (props.marginBottom !== undefined)
278
- margin.bottom = parseFloat(props.marginBottom) || 0;
279
- if (props.marginLeft !== undefined)
280
- margin.left = parseFloat(props.marginLeft) || 0;
281
- let padding = parseSpacing(props.padding || 0);
282
- if (props.paddingTop !== undefined)
283
- padding.top = parseFloat(props.paddingTop) || 0;
284
- if (props.paddingRight !== undefined)
285
- padding.right = parseFloat(props.paddingRight) || 0;
286
- if (props.paddingBottom !== undefined)
287
- padding.bottom = parseFloat(props.paddingBottom) || 0;
288
- if (props.paddingLeft !== undefined)
289
- padding.left = parseFloat(props.paddingLeft) || 0;
290
- const borderWidth = parseFloat(props.borderWidth) || 0;
291
- const borderColor = props.borderColor || "transparent";
292
- const borderRadius = parseFloat(props.borderRadius) || 0;
293
- const availableAfterMargin = {
294
- width: availableWidth - margin.left - margin.right,
295
- height: availableHeight - margin.top - margin.bottom
296
- };
297
- let width = parseSize(props.width);
298
- let height = parseSize(props.height);
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
- width = availableAfterMargin.width;
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
- } else {
319
- if (width === null)
320
- width = availableAfterMargin.width;
321
- if (height === null)
322
- height = thickness;
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
- const layout = parent.layout;
422
- const props = parent.props;
423
- if (parent.type.toLowerCase() === "stack") {
424
- layoutStackChildren(ctx, parent);
425
- return;
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
- childInfo.push({
454
- width: childLayout.width,
455
- height: childLayout.height,
456
- flexGrow,
457
- flexShrink,
458
- flexBasis
459
- });
460
- totalMainSize += mainSize;
461
- totalFlexGrow += flexGrow;
462
- totalFlexShrink += flexShrink;
463
- }
464
- const totalGap = gap * (parent.children.length - 1);
465
- totalMainSize += totalGap;
466
- const availableMain = isColumn ? availableHeight : availableWidth;
467
- let remainingSpace = availableMain - totalMainSize;
468
- if (remainingSpace > 0 && totalFlexGrow > 0) {
469
- const spacePerFlex = remainingSpace / totalFlexGrow;
470
- for (let i = 0;i < parent.children.length; i++) {
471
- const info = childInfo[i];
472
- if (info.flexGrow > 0) {
473
- const extraSpace = spacePerFlex * info.flexGrow;
474
- if (isColumn) {
475
- info.height += extraSpace;
476
- } else {
477
- info.width += extraSpace;
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
- totalMainSize += extraSpace;
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
- remainingSpace = 0;
483
- }
484
- if (remainingSpace < 0 && totalFlexShrink > 0) {
485
- const shrinkPerFlex = Math.abs(remainingSpace) / totalFlexShrink;
486
- for (let i = 0;i < parent.children.length; i++) {
487
- const info = childInfo[i];
488
- if (info.flexShrink > 0) {
489
- const shrinkSpace = Math.min(shrinkPerFlex * info.flexShrink, isColumn ? info.height : info.width);
490
- if (isColumn) {
491
- info.height = Math.max(0, info.height - shrinkSpace);
492
- } else {
493
- info.width = Math.max(0, info.width - shrinkSpace);
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
- totalMainSize -= shrinkSpace;
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
- remainingSpace = availableMain - totalMainSize;
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 (isColumn) {
528
- childLayout.x = layout.x + layout.contentX + crossStart;
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-between" || justifyContent === "space-around") {
537
- currentMain += spacing;
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
- const layout = parent.layout;
543
- const props = parent.props;
544
- const horizontalAlignment = props.horizontalAlignment || "flex-start";
545
- const verticalAlignment = props.verticalAlignment || "flex-start";
546
- const availableWidth = layout.contentWidth;
547
- const availableHeight = layout.contentHeight;
548
- for (const child of parent.children) {
549
- computeLayout(ctx, child, availableWidth, availableHeight, 0, 0);
550
- const childLayout = child.layout;
551
- let x = 0;
552
- let y = 0;
553
- if (horizontalAlignment === "center") {
554
- x = (availableWidth - childLayout.width) / 2;
555
- } else if (horizontalAlignment === "flex-end") {
556
- x = availableWidth - childLayout.width;
557
- }
558
- if (verticalAlignment === "center") {
559
- y = (availableHeight - childLayout.height) / 2;
560
- } else if (verticalAlignment === "flex-end") {
561
- y = availableHeight - childLayout.height;
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
- export {
568
- computeLayout
569
- };
570
-
571
- //# debugId=107090D4899BFD6E64756E2164756E21
373
+ //# sourceMappingURL=layout.js.map