flexily 0.5.2 → 0.6.0

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 (50) hide show
  1. package/dist/chunk-CBBoxR_p.mjs +26 -0
  2. package/dist/constants-BNURa6H7.mjs +65 -0
  3. package/dist/constants-BNURa6H7.mjs.map +1 -0
  4. package/dist/constants-D7ythAJC.d.mts +64 -0
  5. package/dist/constants-D7ythAJC.d.mts.map +1 -0
  6. package/{src/classic/node.ts → dist/index-classic.d.mts} +118 -619
  7. package/dist/index-classic.d.mts.map +1 -0
  8. package/dist/index-classic.mjs +1909 -0
  9. package/dist/index-classic.mjs.map +1 -0
  10. package/dist/index.d.mts +195 -0
  11. package/dist/index.d.mts.map +1 -0
  12. package/dist/index.mjs +3279 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/node-zero-75maLs2s.d.mts +762 -0
  15. package/dist/node-zero-75maLs2s.d.mts.map +1 -0
  16. package/dist/src-BWyhokNZ.mjs +692 -0
  17. package/dist/src-BWyhokNZ.mjs.map +1 -0
  18. package/dist/src-DdSLylRA.mjs +816 -0
  19. package/dist/src-DdSLylRA.mjs.map +1 -0
  20. package/dist/testing.d.mts +55 -0
  21. package/dist/testing.d.mts.map +1 -0
  22. package/dist/testing.mjs +154 -0
  23. package/dist/testing.mjs.map +1 -0
  24. package/dist/types--IozHd4V.mjs +283 -0
  25. package/dist/types--IozHd4V.mjs.map +1 -0
  26. package/dist/types-DG1H4DVR.d.mts +157 -0
  27. package/dist/types-DG1H4DVR.d.mts.map +1 -0
  28. package/package.json +33 -24
  29. package/src/CLAUDE.md +0 -527
  30. package/src/classic/layout.ts +0 -1843
  31. package/src/constants.ts +0 -82
  32. package/src/create-flexily.ts +0 -153
  33. package/src/index-classic.ts +0 -110
  34. package/src/index.ts +0 -133
  35. package/src/layout-flex-lines.ts +0 -413
  36. package/src/layout-helpers.ts +0 -160
  37. package/src/layout-measure.ts +0 -259
  38. package/src/layout-stats.ts +0 -41
  39. package/src/layout-traversal.ts +0 -70
  40. package/src/layout-zero.ts +0 -2219
  41. package/src/logger.ts +0 -68
  42. package/src/monospace-measurer.ts +0 -68
  43. package/src/node-zero.ts +0 -1508
  44. package/src/pretext-measurer.ts +0 -86
  45. package/src/test-measurer.ts +0 -219
  46. package/src/testing.ts +0 -215
  47. package/src/text-layout.ts +0 -75
  48. package/src/trace.ts +0 -252
  49. package/src/types.ts +0 -236
  50. package/src/utils.ts +0 -243
package/dist/index.mjs ADDED
@@ -0,0 +1,3279 @@
1
+ import { $ as WRAP_WRAP, A as GUTTER_ROW, B as OVERFLOW_HIDDEN, C as EDGE_VERTICAL, D as FLEX_DIRECTION_ROW_REVERSE, E as FLEX_DIRECTION_ROW, F as JUSTIFY_SPACE_BETWEEN, G as POSITION_TYPE_STATIC, H as OVERFLOW_VISIBLE, I as JUSTIFY_SPACE_EVENLY, J as UNIT_PERCENT, K as UNIT_AUTO, L as MEASURE_MODE_AT_MOST, M as JUSTIFY_FLEX_END, N as JUSTIFY_FLEX_START, O as GUTTER_ALL, P as JUSTIFY_SPACE_AROUND, Q as WRAP_NO_WRAP, R as MEASURE_MODE_EXACTLY, S as EDGE_TOP, T as FLEX_DIRECTION_COLUMN_REVERSE, U as POSITION_TYPE_ABSOLUTE, V as OVERFLOW_SCROLL, W as POSITION_TYPE_RELATIVE, X as UNIT_SNUG_CONTENT, Y as UNIT_POINT, Z as UNIT_UNDEFINED, _ as EDGE_END, a as ALIGN_FLEX_START, b as EDGE_RIGHT, c as ALIGN_SPACE_EVENLY, d as DIRECTION_LTR, et as WRAP_WRAP_REVERSE, f as DIRECTION_RTL, g as EDGE_BOTTOM, h as EDGE_ALL, i as ALIGN_FLEX_END, j as JUSTIFY_CENTER, k as GUTTER_COLUMN, l as ALIGN_STRETCH, m as DISPLAY_NONE, n as ALIGN_BASELINE, o as ALIGN_SPACE_AROUND, p as DISPLAY_FLEX, q as UNIT_FIT_CONTENT, r as ALIGN_CENTER, s as ALIGN_SPACE_BETWEEN, t as ALIGN_AUTO, u as DIRECTION_INHERIT, v as EDGE_HORIZONTAL, w as FLEX_DIRECTION_COLUMN, x as EDGE_START, y as EDGE_LEFT, z as MEASURE_MODE_UNDEFINED } from "./constants-BNURa6H7.mjs";
2
+ import { a as getEdgeBorderValue, c as setEdgeBorder, i as applyMinMax, l as setEdgeValue, n as createValue, o as getEdgeValue, r as log, s as resolveValue, t as createDefaultStyle, u as traversalStack } from "./types--IozHd4V.mjs";
3
+ //#region src/trace.ts
4
+ let _trace = null;
5
+ /** Get the current trace recorder (null when tracing is disabled). */
6
+ function getTrace() {
7
+ return _trace;
8
+ }
9
+ //#endregion
10
+ //#region src/layout-helpers.ts
11
+ /**
12
+ * Layout Helper Functions
13
+ *
14
+ * Edge resolution helpers for margins, padding, borders.
15
+ * These are pure functions with no state — safe to extract.
16
+ */
17
+ /**
18
+ * Check if flex direction is row-oriented (horizontal main axis).
19
+ */
20
+ function isRowDirection(flexDirection) {
21
+ return flexDirection === 2 || flexDirection === 3;
22
+ }
23
+ /**
24
+ * Check if flex direction is reversed.
25
+ */
26
+ function isReverseDirection(flexDirection) {
27
+ return flexDirection === 3 || flexDirection === 1;
28
+ }
29
+ /**
30
+ * Get the logical edge value (START/END) for a given physical index.
31
+ * Returns undefined if no logical value applies to this physical edge.
32
+ *
33
+ * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis,
34
+ * regardless of flex direction. Direction (LTR/RTL) determines the mapping:
35
+ * - LTR: START->left, END->right
36
+ * - RTL: START->right, END->left
37
+ */
38
+ function getLogicalEdgeValue(arr, physicalIndex, _flexDirection, direction = 1) {
39
+ const isRTL = direction === 2;
40
+ if (physicalIndex === 0) return isRTL ? arr[5] : arr[4];
41
+ else if (physicalIndex === 2) return isRTL ? arr[4] : arr[5];
42
+ }
43
+ /**
44
+ * Resolve logical (START/END) margins/padding to physical values.
45
+ * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis:
46
+ * - LTR: START->left, END->right
47
+ * - RTL: START->right, END->left
48
+ *
49
+ * Physical edges (LEFT/RIGHT/TOP/BOTTOM) are used directly.
50
+ * When both physical and logical are set, logical takes precedence.
51
+ */
52
+ function resolveEdgeValue(arr, physicalIndex, flexDirection, availableSize, direction = 1) {
53
+ const logicalValue = getLogicalEdgeValue(arr, physicalIndex, flexDirection, direction);
54
+ if (logicalValue && logicalValue.unit !== 0) return resolveValue(logicalValue, availableSize);
55
+ return resolveValue(arr[physicalIndex], availableSize);
56
+ }
57
+ /**
58
+ * Check if a logical edge margin is set to auto.
59
+ */
60
+ function isEdgeAuto(arr, physicalIndex, flexDirection, direction = 1) {
61
+ const logicalValue = getLogicalEdgeValue(arr, physicalIndex, flexDirection, direction);
62
+ if (logicalValue && logicalValue.unit !== 0) return logicalValue.unit === 3;
63
+ return arr[physicalIndex].unit === 3;
64
+ }
65
+ /**
66
+ * Resolve logical (START/END) border widths to physical values.
67
+ * Border values are plain numbers (always points), so resolution is simpler
68
+ * than for margin/padding. Uses NaN as the "not set" sentinel for logical slots.
69
+ * When both physical and logical are set, logical takes precedence.
70
+ *
71
+ * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis,
72
+ * regardless of flex direction. Direction (LTR/RTL) determines the mapping:
73
+ * - LTR: START->left, END->right
74
+ * - RTL: START->right, END->left
75
+ */
76
+ /**
77
+ * Resolve logical (START/END) position edges to physical values.
78
+ * Returns the resolved Value for a physical position index, considering
79
+ * logical EDGE_START/EDGE_END. Logical takes precedence over physical.
80
+ *
81
+ * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis:
82
+ * - LTR: START->left, END->right
83
+ * - RTL: START->right, END->left
84
+ */
85
+ function resolvePositionEdge(arr, physicalIndex, direction = 1) {
86
+ const logicalValue = getLogicalEdgeValue(arr, physicalIndex, 0, direction);
87
+ if (logicalValue && logicalValue.unit !== 0) return logicalValue;
88
+ return arr[physicalIndex];
89
+ }
90
+ function resolveEdgeBorderValue(arr, physicalIndex, _flexDirection, direction = 1) {
91
+ const isRTL = direction === 2;
92
+ let logicalSlot;
93
+ if (physicalIndex === 0) logicalSlot = isRTL ? 5 : 4;
94
+ else if (physicalIndex === 2) logicalSlot = isRTL ? 4 : 5;
95
+ if (logicalSlot !== void 0 && !Number.isNaN(arr[logicalSlot])) return arr[logicalSlot];
96
+ return arr[physicalIndex];
97
+ }
98
+ //#endregion
99
+ //#region src/layout-traversal.ts
100
+ /**
101
+ * Mark subtree as having new layout and clear dirty flags (iterative to avoid stack overflow).
102
+ * This is called after layout completes to reset dirty tracking for all nodes.
103
+ */
104
+ function markSubtreeLayoutSeen(node) {
105
+ traversalStack.length = 0;
106
+ traversalStack.push(node);
107
+ while (traversalStack.length > 0) {
108
+ const current = traversalStack.pop();
109
+ current["_isDirty"] = false;
110
+ current["_hasNewLayout"] = true;
111
+ for (const child of current.children) traversalStack.push(child);
112
+ }
113
+ }
114
+ /**
115
+ * Count total nodes in tree (iterative to avoid stack overflow).
116
+ */
117
+ function countNodes(node) {
118
+ let count = 0;
119
+ traversalStack.length = 0;
120
+ traversalStack.push(node);
121
+ while (traversalStack.length > 0) {
122
+ const current = traversalStack.pop();
123
+ count++;
124
+ for (const child of current.children) traversalStack.push(child);
125
+ }
126
+ return count;
127
+ }
128
+ /**
129
+ * Propagate position delta to all descendants (iterative to avoid stack overflow).
130
+ * Used when parent position changes but layout is cached.
131
+ *
132
+ * Only updates the constraint fingerprint's lastOffset values, NOT layout.top/left.
133
+ * layout.top/left store RELATIVE positions (relative to parent's border box),
134
+ * so they don't change when an ancestor moves — only the absolute offset
135
+ * (tracked via lastOffsetX/Y) changes.
136
+ */
137
+ function propagatePositionDelta(node, deltaX, deltaY) {
138
+ traversalStack.length = 0;
139
+ for (const child of node.children) traversalStack.push(child);
140
+ while (traversalStack.length > 0) {
141
+ const current = traversalStack.pop();
142
+ current.flex.lastOffsetX += deltaX;
143
+ current.flex.lastOffsetY += deltaY;
144
+ for (const child of current.children) traversalStack.push(child);
145
+ }
146
+ }
147
+ //#endregion
148
+ //#region src/layout-stats.ts
149
+ /**
150
+ * Layout Statistics Counters
151
+ *
152
+ * Mutable counters for debugging and benchmarking.
153
+ * Separated to avoid circular dependencies between layout modules.
154
+ */
155
+ let layoutNodeCalls = 0;
156
+ let measureNodeCalls = 0;
157
+ let layoutSizingCalls = 0;
158
+ let layoutPositioningCalls = 0;
159
+ let layoutCacheHits = 0;
160
+ function resetLayoutStats() {
161
+ layoutNodeCalls = 0;
162
+ measureNodeCalls = 0;
163
+ layoutSizingCalls = 0;
164
+ layoutPositioningCalls = 0;
165
+ layoutCacheHits = 0;
166
+ }
167
+ function incLayoutNodeCalls() {
168
+ layoutNodeCalls++;
169
+ }
170
+ function incMeasureNodeCalls() {
171
+ measureNodeCalls++;
172
+ }
173
+ function incLayoutSizingCalls() {
174
+ layoutSizingCalls++;
175
+ }
176
+ function incLayoutPositioningCalls() {
177
+ layoutPositioningCalls++;
178
+ }
179
+ function incLayoutCacheHits() {
180
+ layoutCacheHits++;
181
+ }
182
+ //#endregion
183
+ //#region src/layout-measure.ts
184
+ /**
185
+ * Node Measurement (Intrinsic Sizing)
186
+ *
187
+ * measureNode() computes a node's width and height without calculating positions.
188
+ * It's a lightweight alternative to layoutNode() used during Phase 5 for
189
+ * intrinsic sizing of auto-sized container children.
190
+ *
191
+ * IMPORTANT: measureNode() overwrites layout.width/layout.height as a side effect.
192
+ * Callers MUST save/restore these fields around calls to avoid corrupting
193
+ * the fingerprint cache (see "Bug 1: measureNode corruption" in CLAUDE.md).
194
+ */
195
+ /**
196
+ * Measure a node to get its intrinsic size without computing positions.
197
+ * This is a lightweight alternative to layoutNode for sizing-only passes.
198
+ *
199
+ * Sets layout.width and layout.height but NOT layout.left/top.
200
+ *
201
+ * @param node - The node to measure
202
+ * @param availableWidth - Available width (NaN for unconstrained)
203
+ * @param availableHeight - Available height (NaN for unconstrained)
204
+ * @param direction - Layout direction (LTR or RTL)
205
+ */
206
+ function measureNode(node, availableWidth, availableHeight, direction = 1) {
207
+ incMeasureNodeCalls();
208
+ const style = node.style;
209
+ const layout = node.layout;
210
+ if (style.display === 1) {
211
+ layout.width = 0;
212
+ layout.height = 0;
213
+ return;
214
+ }
215
+ const marginLeft = resolveEdgeValue(style.margin, 0, style.flexDirection, availableWidth, direction);
216
+ const marginTop = resolveEdgeValue(style.margin, 1, style.flexDirection, availableWidth, direction);
217
+ const marginRight = resolveEdgeValue(style.margin, 2, style.flexDirection, availableWidth, direction);
218
+ const marginBottom = resolveEdgeValue(style.margin, 3, style.flexDirection, availableWidth, direction);
219
+ const paddingLeft = resolveEdgeValue(style.padding, 0, style.flexDirection, availableWidth, direction);
220
+ const paddingTop = resolveEdgeValue(style.padding, 1, style.flexDirection, availableWidth, direction);
221
+ const paddingRight = resolveEdgeValue(style.padding, 2, style.flexDirection, availableWidth, direction);
222
+ const paddingBottom = resolveEdgeValue(style.padding, 3, style.flexDirection, availableWidth, direction);
223
+ const borderLeft = resolveEdgeBorderValue(style.border, 0, style.flexDirection, direction);
224
+ const borderTop = resolveEdgeBorderValue(style.border, 1, style.flexDirection, direction);
225
+ const borderRight = resolveEdgeBorderValue(style.border, 2, style.flexDirection, direction);
226
+ const borderBottom = resolveEdgeBorderValue(style.border, 3, style.flexDirection, direction);
227
+ let nodeWidth;
228
+ if (style.width.unit === 1) nodeWidth = style.width.value;
229
+ else if (style.width.unit === 2) nodeWidth = resolveValue(style.width, availableWidth);
230
+ else if (Number.isNaN(availableWidth)) nodeWidth = NaN;
231
+ else nodeWidth = availableWidth - marginLeft - marginRight;
232
+ nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth);
233
+ let nodeHeight;
234
+ if (style.height.unit === 1) nodeHeight = style.height.value;
235
+ else if (style.height.unit === 2) nodeHeight = resolveValue(style.height, availableHeight);
236
+ else if (Number.isNaN(availableHeight)) nodeHeight = NaN;
237
+ else nodeHeight = availableHeight - marginTop - marginBottom;
238
+ const aspectRatio = style.aspectRatio;
239
+ if (!Number.isNaN(aspectRatio) && aspectRatio > 0) {
240
+ const widthIsAuto = Number.isNaN(nodeWidth) || style.width.unit === 3;
241
+ const heightIsAuto = Number.isNaN(nodeHeight) || style.height.unit === 3;
242
+ if (widthIsAuto && !heightIsAuto && !Number.isNaN(nodeHeight)) {
243
+ nodeWidth = nodeHeight * aspectRatio;
244
+ nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth);
245
+ } else if (heightIsAuto && !widthIsAuto && !Number.isNaN(nodeWidth)) nodeHeight = nodeWidth / aspectRatio;
246
+ }
247
+ nodeHeight = applyMinMax(nodeHeight, style.minHeight, style.maxHeight, availableHeight);
248
+ const innerLeft = borderLeft + paddingLeft;
249
+ const innerTop = borderTop + paddingTop;
250
+ const innerRight = borderRight + paddingRight;
251
+ const innerBottom = borderBottom + paddingBottom;
252
+ const minInnerWidth = innerLeft + innerRight;
253
+ const minInnerHeight = innerTop + innerBottom;
254
+ if (!Number.isNaN(nodeWidth) && nodeWidth < minInnerWidth) nodeWidth = minInnerWidth;
255
+ if (!Number.isNaN(nodeHeight) && nodeHeight < minInnerHeight) nodeHeight = minInnerHeight;
256
+ const contentWidth = Number.isNaN(nodeWidth) ? NaN : Math.max(0, nodeWidth - innerLeft - innerRight);
257
+ const contentHeight = Number.isNaN(nodeHeight) ? NaN : Math.max(0, nodeHeight - innerTop - innerBottom);
258
+ if (node.hasMeasureFunc() && node.children.length === 0) {
259
+ const widthIsAuto = style.width.unit === 3 || style.width.unit === 0 || Number.isNaN(nodeWidth);
260
+ const heightIsAuto = style.height.unit === 3 || style.height.unit === 0 || Number.isNaN(nodeHeight);
261
+ const widthMode = widthIsAuto ? 2 : 1;
262
+ const heightMode = heightIsAuto ? 0 : 1;
263
+ const measureWidth = Number.isNaN(contentWidth) ? Infinity : contentWidth;
264
+ const measureHeight = Number.isNaN(contentHeight) ? Infinity : contentHeight;
265
+ const measured = node.cachedMeasure(measureWidth, widthMode, measureHeight, heightMode);
266
+ if (widthIsAuto) nodeWidth = measured.width + innerLeft + innerRight;
267
+ if (heightIsAuto) nodeHeight = measured.height + innerTop + innerBottom;
268
+ layout.width = Math.round(nodeWidth);
269
+ layout.height = Math.round(nodeHeight);
270
+ return;
271
+ }
272
+ if (node.children.length === 0) {
273
+ if (Number.isNaN(nodeWidth)) nodeWidth = innerLeft + innerRight;
274
+ if (Number.isNaN(nodeHeight)) nodeHeight = innerTop + innerBottom;
275
+ layout.width = Math.round(nodeWidth);
276
+ layout.height = Math.round(nodeHeight);
277
+ return;
278
+ }
279
+ let relativeChildCount = 0;
280
+ for (const c of node.children) {
281
+ if (c.style.display === 1) continue;
282
+ if (c.style.positionType !== 2) relativeChildCount++;
283
+ }
284
+ if (relativeChildCount === 0) {
285
+ if (Number.isNaN(nodeWidth)) nodeWidth = minInnerWidth;
286
+ if (Number.isNaN(nodeHeight)) nodeHeight = minInnerHeight;
287
+ layout.width = Math.round(nodeWidth);
288
+ layout.height = Math.round(nodeHeight);
289
+ return;
290
+ }
291
+ const isRow = isRowDirection(style.flexDirection);
292
+ const crossAxisSize = isRow ? contentHeight : contentWidth;
293
+ const mainGap = isRow ? style.gap[0] : style.gap[1];
294
+ let totalMainSize = 0;
295
+ let maxCrossSize = 0;
296
+ let itemCount = 0;
297
+ for (const child of node.children) {
298
+ if (child.style.display === 1) continue;
299
+ if (child.style.positionType === 2) continue;
300
+ const childStyle = child.style;
301
+ const childMarginMain = isRow ? resolveEdgeValue(childStyle.margin, 0, style.flexDirection, contentWidth, direction) + resolveEdgeValue(childStyle.margin, 2, style.flexDirection, contentWidth, direction) : resolveEdgeValue(childStyle.margin, 1, style.flexDirection, contentWidth, direction) + resolveEdgeValue(childStyle.margin, 3, style.flexDirection, contentWidth, direction);
302
+ const childMarginCross = isRow ? resolveEdgeValue(childStyle.margin, 1, style.flexDirection, contentWidth, direction) + resolveEdgeValue(childStyle.margin, 3, style.flexDirection, contentWidth, direction) : resolveEdgeValue(childStyle.margin, 0, style.flexDirection, contentWidth, direction) + resolveEdgeValue(childStyle.margin, 2, style.flexDirection, contentWidth, direction);
303
+ const childAvailW = isRow ? NaN : crossAxisSize;
304
+ const childAvailH = isRow ? crossAxisSize : NaN;
305
+ let measuredW = 0;
306
+ let measuredH = 0;
307
+ const cached = child.getCachedLayout(childAvailW, childAvailH);
308
+ if (cached) incLayoutCacheHits();
309
+ else {
310
+ const savedW = child.layout.width;
311
+ const savedH = child.layout.height;
312
+ measureNode(child, childAvailW, childAvailH, direction);
313
+ measuredW = child.layout.width;
314
+ measuredH = child.layout.height;
315
+ child.layout.width = savedW;
316
+ child.layout.height = savedH;
317
+ child.setCachedLayout(childAvailW, childAvailH, measuredW, measuredH);
318
+ }
319
+ const childMainSize = cached ? isRow ? cached.width : cached.height : isRow ? measuredW : measuredH;
320
+ const childCrossSize = cached ? isRow ? cached.height : cached.width : isRow ? measuredH : measuredW;
321
+ totalMainSize += childMainSize + childMarginMain;
322
+ maxCrossSize = Math.max(maxCrossSize, childCrossSize + childMarginCross);
323
+ itemCount++;
324
+ }
325
+ if (itemCount > 1) totalMainSize += mainGap * (itemCount - 1);
326
+ if (Number.isNaN(nodeWidth)) nodeWidth = (isRow ? totalMainSize : maxCrossSize) + innerLeft + innerRight;
327
+ if (Number.isNaN(nodeHeight)) nodeHeight = (isRow ? maxCrossSize : totalMainSize) + innerTop + innerBottom;
328
+ nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth);
329
+ nodeHeight = applyMinMax(nodeHeight, style.minHeight, style.maxHeight, availableHeight);
330
+ layout.width = Math.round(nodeWidth);
331
+ layout.height = Math.round(nodeHeight);
332
+ }
333
+ //#endregion
334
+ //#region src/layout-flex-lines.ts
335
+ /**
336
+ * Flex Line Breaking and Space Distribution
337
+ *
338
+ * Pre-allocated arrays for zero-allocation flex-wrap layout,
339
+ * plus the line-breaking and flex-distribution algorithms.
340
+ *
341
+ * Re-entrancy: A user measureFunc or baselineFunc may synchronously call
342
+ * calculateLayout() on a separate tree. saveLineState/restoreLineState
343
+ * bracket such nested calls to protect the outer pass's scratch arrays.
344
+ */
345
+ /**
346
+ * Maximum number of flex lines supported without dynamic allocation.
347
+ * If a layout exceeds this, arrays grow automatically (rare edge case).
348
+ * 32 lines covers virtually all real-world layouts while using minimal memory.
349
+ */
350
+ let MAX_FLEX_LINES = 32;
351
+ /**
352
+ * Pre-allocated array for line cross sizes (reused across layout passes).
353
+ * Stores the computed cross-axis size of each flex line.
354
+ */
355
+ let _lineCrossSizes = new Float64Array(MAX_FLEX_LINES);
356
+ /**
357
+ * Pre-allocated array for line cross offsets (reused across layout passes).
358
+ * Stores the cross-axis position offset for each flex line.
359
+ */
360
+ let _lineCrossOffsets = new Float64Array(MAX_FLEX_LINES);
361
+ /**
362
+ * Pre-allocated array for line lengths (number of children per line).
363
+ * Uint16 supports up to 65535 children per line (more than sufficient).
364
+ */
365
+ let _lineLengths = new Uint16Array(MAX_FLEX_LINES);
366
+ /**
367
+ * Pre-allocated 2D array for children per line.
368
+ * Avoids O(n*m) iteration when processing multi-line flex layouts.
369
+ * Each slot holds array of Node references for that line.
370
+ */
371
+ let _lineChildren = Array.from({ length: MAX_FLEX_LINES }, () => []);
372
+ /**
373
+ * Pre-allocated array for per-line justify-content start offsets.
374
+ * Stores the main-axis starting position for each flex line.
375
+ */
376
+ let _lineJustifyStarts = new Float64Array(MAX_FLEX_LINES);
377
+ /**
378
+ * Pre-allocated array for per-line item spacing (justify-content gaps).
379
+ * Stores the spacing between items for each flex line.
380
+ */
381
+ let _lineItemSpacings = new Float64Array(MAX_FLEX_LINES);
382
+ /**
383
+ * Grow pre-allocated line arrays if needed.
384
+ * Called when a layout has more lines than current capacity.
385
+ * This is rare (>32 lines) and acceptable as a one-time allocation.
386
+ */
387
+ function growLineArrays(needed) {
388
+ const newSize = Math.max(needed, MAX_FLEX_LINES * 2);
389
+ MAX_FLEX_LINES = newSize;
390
+ _lineCrossSizes = new Float64Array(newSize);
391
+ _lineCrossOffsets = new Float64Array(newSize);
392
+ _lineLengths = new Uint16Array(newSize);
393
+ _lineJustifyStarts = new Float64Array(newSize);
394
+ _lineItemSpacings = new Float64Array(newSize);
395
+ while (_lineChildren.length < newSize) _lineChildren.push([]);
396
+ }
397
+ /** Current re-entrancy depth. 0 = outermost (no save needed). */
398
+ let _layoutDepth = 0;
399
+ /**
400
+ * Enter a layout pass. If re-entrant (depth > 0), saves current line state.
401
+ * @returns The saved state (to pass to restoreLineState), or null at depth 0.
402
+ */
403
+ function enterLayout() {
404
+ if (_layoutDepth++ === 0) return null;
405
+ return {
406
+ crossSizes: _lineCrossSizes.slice(),
407
+ crossOffsets: _lineCrossOffsets.slice(),
408
+ lengths: _lineLengths.slice(),
409
+ justifyStarts: _lineJustifyStarts.slice(),
410
+ itemSpacings: _lineItemSpacings.slice(),
411
+ children: _lineChildren.map((arr) => arr.slice()),
412
+ maxLines: MAX_FLEX_LINES
413
+ };
414
+ }
415
+ /**
416
+ * Exit a layout pass. If re-entrant, restores saved line state.
417
+ * @param saved - The state returned by enterLayout (null at depth 0).
418
+ */
419
+ function exitLayout(saved) {
420
+ _layoutDepth--;
421
+ if (!saved) return;
422
+ MAX_FLEX_LINES = saved.maxLines;
423
+ _lineCrossSizes = saved.crossSizes;
424
+ _lineCrossOffsets = saved.crossOffsets;
425
+ _lineLengths = saved.lengths;
426
+ _lineJustifyStarts = saved.justifyStarts;
427
+ _lineItemSpacings = saved.itemSpacings;
428
+ _lineChildren = saved.children;
429
+ }
430
+ /**
431
+ * Epsilon value for floating point comparisons in flex distribution.
432
+ * Used to determine when remaining space is negligible and iteration should stop.
433
+ */
434
+ const EPSILON_FLOAT = .001;
435
+ /**
436
+ * Break children into flex lines based on available main-axis space.
437
+ * Zero-allocation: Sets child.flex.lineIndex directly, uses pre-allocated _lineStarts/_lineLengths.
438
+ *
439
+ * @param parent - Parent node whose children to wrap
440
+ * @param relativeCount - Number of relative children (those with flex.relativeIndex >= 0)
441
+ * @param mainAxisSize - Available main-axis space (NaN for unconstrained)
442
+ * @param mainGap - Gap between items on main axis
443
+ * @param wrap - Wrap mode (WRAP_NO_WRAP, WRAP_WRAP, WRAP_WRAP_REVERSE)
444
+ * @returns Number of lines created
445
+ */
446
+ function breakIntoLines(parent, relativeCount, mainAxisSize, mainGap, wrap) {
447
+ if (wrap === 0 || Number.isNaN(mainAxisSize) || relativeCount === 0) {
448
+ const lineArr = _lineChildren[0];
449
+ let idx = 0;
450
+ for (const child of parent.children) if (child.flex.relativeIndex >= 0) {
451
+ child.flex.lineIndex = 0;
452
+ lineArr[idx++] = child;
453
+ }
454
+ lineArr.length = idx;
455
+ _lineLengths[0] = relativeCount;
456
+ _lineCrossSizes[0] = 0;
457
+ _lineCrossOffsets[0] = 0;
458
+ return 1;
459
+ }
460
+ let lineIndex = 0;
461
+ let lineMainSize = 0;
462
+ let lineChildCount = 0;
463
+ let lineChildIdx = 0;
464
+ for (const child of parent.children) {
465
+ if (child.flex.relativeIndex < 0) continue;
466
+ const flex = child.flex;
467
+ const childMainSize = Math.max(flex.minMain, Math.min(flex.maxMain, flex.baseSize)) + flex.mainMargin;
468
+ const gapIfNotFirst = lineChildCount > 0 ? mainGap : 0;
469
+ if (lineChildCount > 0 && lineMainSize + gapIfNotFirst + childMainSize > mainAxisSize) {
470
+ _lineChildren[lineIndex].length = lineChildIdx;
471
+ _lineLengths[lineIndex] = lineChildCount;
472
+ lineIndex++;
473
+ if (lineIndex >= MAX_FLEX_LINES) growLineArrays(lineIndex + 16);
474
+ lineChildIdx = 0;
475
+ lineMainSize = childMainSize;
476
+ lineChildCount = 1;
477
+ } else {
478
+ lineMainSize += gapIfNotFirst + childMainSize;
479
+ lineChildCount++;
480
+ }
481
+ flex.lineIndex = lineIndex;
482
+ _lineChildren[lineIndex][lineChildIdx++] = child;
483
+ }
484
+ if (lineChildCount > 0) {
485
+ _lineChildren[lineIndex].length = lineChildIdx;
486
+ _lineLengths[lineIndex] = lineChildCount;
487
+ lineIndex++;
488
+ }
489
+ const numLines = lineIndex;
490
+ for (let i = 0; i < numLines; i++) {
491
+ _lineCrossSizes[i] = 0;
492
+ _lineCrossOffsets[i] = 0;
493
+ }
494
+ if (wrap === 2 && numLines > 1) {
495
+ for (let i = 0; i < Math.floor(numLines / 2); i++) {
496
+ const j = numLines - 1 - i;
497
+ const lineI = _lineChildren[i];
498
+ const lineJ = _lineChildren[j];
499
+ const lenI = lineI.length;
500
+ const lenJ = lineJ.length;
501
+ const maxLen = Math.max(lenI, lenJ);
502
+ for (let k = 0; k < maxLen; k++) {
503
+ const hasI = k < lenI;
504
+ const hasJ = k < lenJ;
505
+ const tmpI = hasI ? lineI[k] : null;
506
+ const tmpJ = hasJ ? lineJ[k] : null;
507
+ if (hasJ) lineI[k] = tmpJ;
508
+ if (hasI) lineJ[k] = tmpI;
509
+ }
510
+ lineI.length = lenJ;
511
+ lineJ.length = lenI;
512
+ }
513
+ for (let i = 0; i < numLines; i++) {
514
+ const lc = _lineChildren[i];
515
+ for (let c = 0; c < lc.length; c++) lc[c].flex.lineIndex = i;
516
+ }
517
+ }
518
+ return numLines;
519
+ }
520
+ /**
521
+ * Distribute flex space for a single line of children.
522
+ * Implements CSS Flexbox section 9.7: Resolving Flexible Lengths.
523
+ *
524
+ * Takes pre-collected children array to avoid O(n*m) iteration pattern.
525
+ * Previously iterated through ALL parent.children 8 times per line.
526
+ *
527
+ * @param lineChildren - Pre-collected children for this line (from _lineChildren)
528
+ * @param initialFreeSpace - Free space to distribute (positive=grow, negative=shrink)
529
+ */
530
+ function distributeFlexSpaceForLine(lineChildren, initialFreeSpace) {
531
+ const isGrowing = initialFreeSpace > 0;
532
+ if (initialFreeSpace === 0) return;
533
+ const childCount = lineChildren.length;
534
+ if (childCount === 0) return;
535
+ if (childCount === 1) {
536
+ const flex = lineChildren[0].flex;
537
+ if (isGrowing ? flex.flexGrow > 0 : flex.flexShrink > 0) {
538
+ const target = flex.baseSize + initialFreeSpace;
539
+ flex.mainSize = Math.max(flex.minMain, Math.min(flex.maxMain, target));
540
+ }
541
+ return;
542
+ }
543
+ let totalBase = 0;
544
+ for (let i = 0; i < childCount; i++) totalBase += lineChildren[i].flex.baseSize;
545
+ const containerInner = initialFreeSpace + totalBase;
546
+ for (let i = 0; i < childCount; i++) lineChildren[i].flex.frozen = false;
547
+ let freeSpace = initialFreeSpace;
548
+ let iterations = 0;
549
+ const maxIterations = childCount + 1;
550
+ while (iterations++ < maxIterations) {
551
+ let totalFlex = 0;
552
+ for (let i = 0; i < childCount; i++) {
553
+ const flex = lineChildren[i].flex;
554
+ if (flex.frozen) continue;
555
+ if (isGrowing) totalFlex += flex.flexGrow;
556
+ else totalFlex += flex.flexShrink * flex.baseSize;
557
+ }
558
+ if (totalFlex === 0) break;
559
+ let effectiveFreeSpace = freeSpace;
560
+ if (isGrowing && totalFlex < 1) effectiveFreeSpace = freeSpace * totalFlex;
561
+ let totalViolation = 0;
562
+ for (let i = 0; i < childCount; i++) {
563
+ const flex = lineChildren[i].flex;
564
+ if (flex.frozen) continue;
565
+ const flexFactor = isGrowing ? flex.flexGrow : flex.flexShrink * flex.baseSize;
566
+ const ratio = totalFlex > 0 ? flexFactor / totalFlex : 0;
567
+ const target = flex.baseSize + effectiveFreeSpace * ratio;
568
+ const clamped = Math.max(flex.minMain, Math.min(flex.maxMain, target));
569
+ totalViolation += clamped - target;
570
+ flex.mainSize = clamped;
571
+ }
572
+ let anyFrozen = false;
573
+ if (Math.abs(totalViolation) < EPSILON_FLOAT) {
574
+ for (let i = 0; i < childCount; i++) lineChildren[i].flex.frozen = true;
575
+ break;
576
+ } else if (totalViolation > 0) for (let i = 0; i < childCount; i++) {
577
+ const flex = lineChildren[i].flex;
578
+ if (flex.frozen) continue;
579
+ const target = flex.baseSize + (isGrowing ? flex.flexGrow : flex.flexShrink * flex.baseSize) / totalFlex * effectiveFreeSpace;
580
+ if (flex.mainSize > target + EPSILON_FLOAT) {
581
+ flex.frozen = true;
582
+ anyFrozen = true;
583
+ }
584
+ }
585
+ else for (let i = 0; i < childCount; i++) {
586
+ const flex = lineChildren[i].flex;
587
+ if (flex.frozen) continue;
588
+ const flexFactor = isGrowing ? flex.flexGrow : flex.flexShrink * flex.baseSize;
589
+ const target = flex.baseSize + flexFactor / totalFlex * effectiveFreeSpace;
590
+ if (flex.mainSize < target - EPSILON_FLOAT) {
591
+ flex.frozen = true;
592
+ anyFrozen = true;
593
+ }
594
+ }
595
+ if (!anyFrozen) break;
596
+ let frozenSpace = 0;
597
+ let unfrozenBase = 0;
598
+ for (let i = 0; i < childCount; i++) {
599
+ const flex = lineChildren[i].flex;
600
+ if (flex.frozen) frozenSpace += flex.mainSize;
601
+ else unfrozenBase += flex.baseSize;
602
+ }
603
+ freeSpace = containerInner - frozenSpace - unfrozenBase;
604
+ }
605
+ }
606
+ //#endregion
607
+ //#region src/layout-zero.ts
608
+ /**
609
+ * Flexily Layout Algorithm — Main Entry Point
610
+ *
611
+ * Core flexbox layout computation. This file contains:
612
+ * - computeLayout(): top-level entry point
613
+ * - layoutNode(): recursive layout algorithm (11 phases)
614
+ *
615
+ * Helper modules (split for maintainability, zero-allocation preserved):
616
+ * - layout-helpers.ts: Edge resolution (margins, padding, borders)
617
+ * - layout-traversal.ts: Tree traversal (markSubtreeLayoutSeen, countNodes)
618
+ * - layout-flex-lines.ts: Pre-allocated arrays, line breaking, flex distribution
619
+ * - layout-measure.ts: Intrinsic sizing (measureNode)
620
+ * - layout-stats.ts: Debug/benchmark counters
621
+ *
622
+ * Based on Planning-nl/flexbox.js reference implementation.
623
+ */
624
+ /**
625
+ * Compute layout for a node tree.
626
+ */
627
+ function computeLayout(root, availableWidth, availableHeight, direction = 1) {
628
+ const saved = enterLayout();
629
+ try {
630
+ resetLayoutStats();
631
+ getTrace()?.resetCounter();
632
+ root.resetLayoutCache();
633
+ layoutNode(root, availableWidth, availableHeight, 0, 0, 0, 0, direction);
634
+ } finally {
635
+ exitLayout(saved);
636
+ }
637
+ }
638
+ /**
639
+ * Layout a node and its children.
640
+ *
641
+ * @param absX - Absolute X position from document root (for Yoga-compatible edge rounding)
642
+ * @param absY - Absolute Y position from document root (for Yoga-compatible edge rounding)
643
+ */
644
+ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, absX, absY, direction = 1) {
645
+ incLayoutNodeCalls();
646
+ if (offsetX === 0 && offsetY === 0 && absX === 0 && absY === 0 && node.children.length > 0) incLayoutSizingCalls();
647
+ else incLayoutPositioningCalls();
648
+ log.debug?.("layoutNode called: availW=%d, availH=%d, offsetX=%d, offsetY=%d, absX=%d, absY=%d, children=%d", availableWidth, availableHeight, offsetX, offsetY, absX, absY, node.children.length);
649
+ const _t = getTrace();
650
+ const _tn = _t?.nextNode() ?? 0;
651
+ _t?.layoutEnter(_tn, availableWidth, availableHeight, node.isDirty(), node.children.length);
652
+ const style = node.style;
653
+ const layout = node.layout;
654
+ if (style.display === 1) {
655
+ layout.left = 0;
656
+ layout.top = 0;
657
+ layout.width = 0;
658
+ layout.height = 0;
659
+ return;
660
+ }
661
+ const flex = node.flex;
662
+ if (flex.layoutValid && !node.isDirty() && Object.is(flex.lastAvailW, availableWidth) && Object.is(flex.lastAvailH, availableHeight) && flex.lastDir === direction && flex.lastAbsX === absX && flex.lastAbsY === absY) {
663
+ _t?.fingerprintHit(_tn, availableWidth, availableHeight);
664
+ const deltaX = offsetX - flex.lastOffsetX;
665
+ const deltaY = offsetY - flex.lastOffsetY;
666
+ if (deltaX !== 0 || deltaY !== 0) {
667
+ layout.left += deltaX;
668
+ layout.top += deltaY;
669
+ flex.lastOffsetX = offsetX;
670
+ flex.lastOffsetY = offsetY;
671
+ propagatePositionDelta(node, deltaX, deltaY);
672
+ }
673
+ return;
674
+ }
675
+ _t?.fingerprintMiss(_tn, availableWidth, availableHeight, {
676
+ layoutValid: flex.layoutValid,
677
+ isDirty: node.isDirty(),
678
+ sameW: Object.is(flex.lastAvailW, availableWidth),
679
+ sameH: Object.is(flex.lastAvailH, availableHeight),
680
+ sameDir: flex.lastDir === direction,
681
+ sameAbsX: flex.lastAbsX === absX,
682
+ sameAbsY: flex.lastAbsY === absY
683
+ });
684
+ const marginLeft = resolveEdgeValue(style.margin, 0, style.flexDirection, availableWidth, direction);
685
+ const marginTop = resolveEdgeValue(style.margin, 1, style.flexDirection, availableWidth, direction);
686
+ const marginRight = resolveEdgeValue(style.margin, 2, style.flexDirection, availableWidth, direction);
687
+ const marginBottom = resolveEdgeValue(style.margin, 3, style.flexDirection, availableWidth, direction);
688
+ const paddingLeft = resolveEdgeValue(style.padding, 0, style.flexDirection, availableWidth, direction);
689
+ const paddingTop = resolveEdgeValue(style.padding, 1, style.flexDirection, availableWidth, direction);
690
+ const paddingRight = resolveEdgeValue(style.padding, 2, style.flexDirection, availableWidth, direction);
691
+ const paddingBottom = resolveEdgeValue(style.padding, 3, style.flexDirection, availableWidth, direction);
692
+ const borderLeft = resolveEdgeBorderValue(style.border, 0, style.flexDirection, direction);
693
+ const borderTop = resolveEdgeBorderValue(style.border, 1, style.flexDirection, direction);
694
+ const borderRight = resolveEdgeBorderValue(style.border, 2, style.flexDirection, direction);
695
+ const borderBottom = resolveEdgeBorderValue(style.border, 3, style.flexDirection, direction);
696
+ let nodeWidth;
697
+ const isFitContentWidth = style.width.unit === 4 || style.width.unit === 5;
698
+ if (style.width.unit === 1) nodeWidth = style.width.value;
699
+ else if (style.width.unit === 2) nodeWidth = resolveValue(style.width, availableWidth);
700
+ else if (Number.isNaN(availableWidth)) nodeWidth = NaN;
701
+ else nodeWidth = availableWidth - marginLeft - marginRight;
702
+ nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth);
703
+ let nodeHeight;
704
+ if (style.height.unit === 1) nodeHeight = style.height.value;
705
+ else if (style.height.unit === 2) nodeHeight = resolveValue(style.height, availableHeight);
706
+ else if (Number.isNaN(availableHeight)) nodeHeight = NaN;
707
+ else nodeHeight = availableHeight - marginTop - marginBottom;
708
+ const aspectRatio = style.aspectRatio;
709
+ if (!Number.isNaN(aspectRatio) && aspectRatio > 0) {
710
+ const widthIsAuto = Number.isNaN(nodeWidth) || style.width.unit === 3;
711
+ const heightIsAuto = Number.isNaN(nodeHeight) || style.height.unit === 3;
712
+ if (widthIsAuto && !heightIsAuto && !Number.isNaN(nodeHeight)) {
713
+ nodeWidth = nodeHeight * aspectRatio;
714
+ nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth);
715
+ } else if (heightIsAuto && !widthIsAuto && !Number.isNaN(nodeWidth)) nodeHeight = nodeWidth / aspectRatio;
716
+ }
717
+ nodeHeight = applyMinMax(nodeHeight, style.minHeight, style.maxHeight, availableHeight);
718
+ const innerLeft = borderLeft + paddingLeft;
719
+ const innerTop = borderTop + paddingTop;
720
+ const innerRight = borderRight + paddingRight;
721
+ const innerBottom = borderBottom + paddingBottom;
722
+ const minInnerWidth = innerLeft + innerRight;
723
+ const minInnerHeight = innerTop + innerBottom;
724
+ if (!Number.isNaN(nodeWidth) && nodeWidth < minInnerWidth) nodeWidth = minInnerWidth;
725
+ if (!Number.isNaN(nodeHeight) && nodeHeight < minInnerHeight) nodeHeight = minInnerHeight;
726
+ const contentWidth = Number.isNaN(nodeWidth) ? NaN : Math.max(0, nodeWidth - innerLeft - innerRight);
727
+ const contentHeight = Number.isNaN(nodeHeight) ? NaN : Math.max(0, nodeHeight - innerTop - innerBottom);
728
+ let parentPosOffsetX = 0;
729
+ let parentPosOffsetY = 0;
730
+ if (style.positionType === 1) {
731
+ const leftPos = resolvePositionEdge(style.position, 0, direction);
732
+ const topPos = style.position[1];
733
+ const rightPos = resolvePositionEdge(style.position, 2, direction);
734
+ const bottomPos = style.position[3];
735
+ if (leftPos.unit !== 0) parentPosOffsetX = resolveValue(leftPos, availableWidth);
736
+ else if (rightPos.unit !== 0) parentPosOffsetX = -resolveValue(rightPos, availableWidth);
737
+ if (topPos.unit !== 0) parentPosOffsetY = resolveValue(topPos, availableHeight);
738
+ else if (bottomPos.unit !== 0) parentPosOffsetY = -resolveValue(bottomPos, availableHeight);
739
+ }
740
+ if (node.hasMeasureFunc() && node.children.length === 0) {
741
+ const widthIsAuto = style.width.unit === 3 || style.width.unit === 0 || Number.isNaN(nodeWidth);
742
+ const widthIsFitContent = isFitContentWidth;
743
+ const heightIsAuto = style.height.unit === 3 || style.height.unit === 0 || Number.isNaN(nodeHeight);
744
+ const widthMode = widthIsAuto || widthIsFitContent ? 2 : 1;
745
+ const heightMode = heightIsAuto ? 0 : 1;
746
+ const measureWidth = Number.isNaN(contentWidth) ? Infinity : contentWidth;
747
+ const measureHeight = Number.isNaN(contentHeight) ? Infinity : contentHeight;
748
+ const measured = node.cachedMeasure(measureWidth, widthMode, measureHeight, heightMode);
749
+ if (widthIsAuto || widthIsFitContent) nodeWidth = measured.width + innerLeft + innerRight;
750
+ if (heightIsAuto) nodeHeight = measured.height + innerTop + innerBottom;
751
+ layout.width = Math.round(nodeWidth);
752
+ layout.height = Math.round(nodeHeight);
753
+ layout.left = Math.round(offsetX + marginLeft);
754
+ layout.top = Math.round(offsetY + marginTop);
755
+ return;
756
+ }
757
+ if (node.children.length === 0) {
758
+ if (Number.isNaN(nodeWidth) || isFitContentWidth) nodeWidth = innerLeft + innerRight;
759
+ if (Number.isNaN(nodeHeight)) nodeHeight = innerTop + innerBottom;
760
+ layout.width = Math.round(nodeWidth);
761
+ layout.height = Math.round(nodeHeight);
762
+ layout.left = Math.round(offsetX + marginLeft);
763
+ layout.top = Math.round(offsetY + marginTop);
764
+ return;
765
+ }
766
+ const isRow = isRowDirection(style.flexDirection);
767
+ const isReverse = isReverseDirection(style.flexDirection);
768
+ const effectiveReverse = isRow ? direction === 2 !== isReverse : isReverse;
769
+ const mainAxisSize = isRow ? contentWidth : contentHeight;
770
+ const crossAxisSize = isRow ? contentHeight : contentWidth;
771
+ const mainGap = isRow ? style.gap[0] : style.gap[1];
772
+ let totalBaseMain = 0;
773
+ let relativeCount = 0;
774
+ let totalAutoMargins = 0;
775
+ let hasBaselineAlignment = style.alignItems === 5;
776
+ for (const child of node.children) {
777
+ if (child.style.display === 1 || child.style.positionType === 2) {
778
+ child.flex.relativeIndex = -1;
779
+ continue;
780
+ }
781
+ child.flex.relativeIndex = relativeCount++;
782
+ const childStyle = child.style;
783
+ const cflex = child.flex;
784
+ const mainStartIndex = isRow ? effectiveReverse ? 2 : 0 : isReverse ? 3 : 1;
785
+ const mainEndIndex = isRow ? effectiveReverse ? 0 : 2 : isReverse ? 1 : 3;
786
+ cflex.mainStartMarginAuto = isEdgeAuto(childStyle.margin, mainStartIndex, style.flexDirection, direction);
787
+ cflex.mainEndMarginAuto = isEdgeAuto(childStyle.margin, mainEndIndex, style.flexDirection, direction);
788
+ cflex.marginL = resolveEdgeValue(childStyle.margin, 0, style.flexDirection, contentWidth, direction);
789
+ cflex.marginT = resolveEdgeValue(childStyle.margin, 1, style.flexDirection, contentWidth, direction);
790
+ cflex.marginR = resolveEdgeValue(childStyle.margin, 2, style.flexDirection, contentWidth, direction);
791
+ cflex.marginB = resolveEdgeValue(childStyle.margin, 3, style.flexDirection, contentWidth, direction);
792
+ cflex.mainStartMarginValue = cflex.mainStartMarginAuto ? 0 : isRow ? effectiveReverse ? cflex.marginR : cflex.marginL : isReverse ? cflex.marginB : cflex.marginT;
793
+ cflex.mainEndMarginValue = cflex.mainEndMarginAuto ? 0 : isRow ? effectiveReverse ? cflex.marginL : cflex.marginR : isReverse ? cflex.marginT : cflex.marginB;
794
+ cflex.mainMargin = cflex.mainStartMarginValue + cflex.mainEndMarginValue;
795
+ let baseSize = 0;
796
+ if (childStyle.flexBasis.unit === 1) baseSize = childStyle.flexBasis.value;
797
+ else if (childStyle.flexBasis.unit === 2) baseSize = Number.isNaN(mainAxisSize) ? 0 : mainAxisSize * (childStyle.flexBasis.value / 100);
798
+ else {
799
+ const sizeVal = isRow ? childStyle.width : childStyle.height;
800
+ if (sizeVal.unit === 1) baseSize = sizeVal.value;
801
+ else if (sizeVal.unit === 2) baseSize = Number.isNaN(mainAxisSize) ? 0 : mainAxisSize * (sizeVal.value / 100);
802
+ else if (child.hasMeasureFunc()) {
803
+ const availCross = crossAxisSize - (isRow ? cflex.marginT + cflex.marginB : cflex.marginL + cflex.marginR);
804
+ const wantMaxContent = childStyle.flexGrow > 0;
805
+ const mW = isRow ? wantMaxContent ? Infinity : Number.isNaN(mainAxisSize) ? Infinity : mainAxisSize : Number.isNaN(availCross) ? Infinity : availCross;
806
+ const mH = isRow ? Number.isNaN(availCross) ? Infinity : availCross : wantMaxContent ? Infinity : Number.isNaN(mainAxisSize) ? Infinity : mainAxisSize;
807
+ const mWMode = isRow ? wantMaxContent ? 0 : 2 : Number.isNaN(availCross) ? 0 : 2;
808
+ const mHMode = isRow ? 0 : wantMaxContent ? 0 : 2;
809
+ const measured = child.cachedMeasure(mW, mWMode, mH, mHMode);
810
+ baseSize = isRow ? measured.width : measured.height;
811
+ } else if (child.children.length > 0) {
812
+ const sizingW = isRow ? NaN : crossAxisSize;
813
+ const sizingH = isRow ? crossAxisSize : NaN;
814
+ const cached = child.getCachedLayout(sizingW, sizingH);
815
+ if (cached) {
816
+ incLayoutCacheHits();
817
+ _t?.cacheHit(_tn, sizingW, sizingH, cached.width, cached.height);
818
+ baseSize = isRow ? cached.width : cached.height;
819
+ } else {
820
+ _t?.cacheMiss(_tn, sizingW, sizingH);
821
+ const savedW = child.layout.width;
822
+ const savedH = child.layout.height;
823
+ measureNode(child, sizingW, sizingH, direction);
824
+ const measuredW = child.layout.width;
825
+ const measuredH = child.layout.height;
826
+ child.layout.width = savedW;
827
+ child.layout.height = savedH;
828
+ _t?.measureSaveRestore(_tn, savedW, savedH, measuredW, measuredH);
829
+ baseSize = isRow ? measuredW : measuredH;
830
+ child.setCachedLayout(sizingW, sizingH, measuredW, measuredH);
831
+ }
832
+ } else {
833
+ const parentWidth = isRow ? mainAxisSize : crossAxisSize;
834
+ baseSize = (isRow ? resolveEdgeValue(childStyle.padding, 0, childStyle.flexDirection, parentWidth, direction) + resolveEdgeValue(childStyle.padding, 2, childStyle.flexDirection, parentWidth, direction) : resolveEdgeValue(childStyle.padding, 1, childStyle.flexDirection, parentWidth, direction) + resolveEdgeValue(childStyle.padding, 3, childStyle.flexDirection, parentWidth, direction)) + (isRow ? resolveEdgeBorderValue(childStyle.border, 0, childStyle.flexDirection, direction) + resolveEdgeBorderValue(childStyle.border, 2, childStyle.flexDirection, direction) : resolveEdgeBorderValue(childStyle.border, 1, childStyle.flexDirection, direction) + resolveEdgeBorderValue(childStyle.border, 3, childStyle.flexDirection, direction));
835
+ }
836
+ }
837
+ const minVal = isRow ? childStyle.minWidth : childStyle.minHeight;
838
+ const maxVal = isRow ? childStyle.maxWidth : childStyle.maxHeight;
839
+ cflex.minMain = minVal.unit !== 0 ? resolveValue(minVal, mainAxisSize) : 0;
840
+ cflex.maxMain = maxVal.unit !== 0 ? resolveValue(maxVal, mainAxisSize) : Infinity;
841
+ cflex.flexGrow = childStyle.flexGrow;
842
+ let shrink = childStyle.flexShrink;
843
+ if (childStyle.overflow !== 0) shrink = Math.max(shrink, 1);
844
+ if (child.hasMeasureFunc() && childStyle.flexGrow > 0) shrink = Math.max(shrink, 1);
845
+ const mainDim = isRow ? childStyle.width : childStyle.height;
846
+ if (mainDim.unit === 4 || mainDim.unit === 5) shrink = Math.max(shrink, 1);
847
+ cflex.flexShrink = shrink;
848
+ cflex.baseSize = baseSize;
849
+ cflex.mainSize = baseSize;
850
+ cflex.frozen = false;
851
+ totalBaseMain += baseSize + cflex.mainMargin;
852
+ if (cflex.mainStartMarginAuto) totalAutoMargins++;
853
+ if (cflex.mainEndMarginAuto) totalAutoMargins++;
854
+ if (!hasBaselineAlignment && childStyle.alignSelf === 5) hasBaselineAlignment = true;
855
+ }
856
+ log.debug?.("layoutNode: node.children=%d, relativeCount=%d", node.children.length, relativeCount);
857
+ if (relativeCount > 0) {
858
+ const numLines = breakIntoLines(node, relativeCount, mainAxisSize, mainGap, style.flexWrap);
859
+ const crossGap = isRow ? style.gap[1] : style.gap[0];
860
+ for (let lineIdx = 0; lineIdx < numLines; lineIdx++) {
861
+ const lineChildren = _lineChildren[lineIdx];
862
+ const lineLength = lineChildren.length;
863
+ if (lineLength === 0) continue;
864
+ let lineTotalBaseMain = 0;
865
+ for (let i = 0; i < lineLength; i++) {
866
+ const c = lineChildren[i];
867
+ lineTotalBaseMain += c.flex.baseSize + c.flex.mainMargin;
868
+ }
869
+ const lineTotalGaps = lineLength > 1 ? mainGap * (lineLength - 1) : 0;
870
+ let effectiveMainSize = mainAxisSize;
871
+ if (Number.isNaN(mainAxisSize)) {
872
+ const maxMainVal = isRow ? style.maxWidth : style.maxHeight;
873
+ if (maxMainVal.unit !== 0) {
874
+ const maxMain = resolveValue(maxMainVal, isRow ? availableWidth : availableHeight);
875
+ if (!Number.isNaN(maxMain) && lineTotalBaseMain + lineTotalGaps > maxMain) effectiveMainSize = maxMain - (isRow ? innerLeft + innerRight : innerTop + innerBottom);
876
+ }
877
+ }
878
+ if (!Number.isNaN(effectiveMainSize)) distributeFlexSpaceForLine(lineChildren, effectiveMainSize - lineTotalBaseMain - lineTotalGaps);
879
+ for (let i = 0; i < lineLength; i++) {
880
+ const f = lineChildren[i].flex;
881
+ f.mainSize = Math.max(f.minMain, Math.min(f.maxMain, f.mainSize));
882
+ }
883
+ }
884
+ for (let lineIdx = 0; lineIdx < numLines; lineIdx++) {
885
+ const lineChildren = _lineChildren[lineIdx];
886
+ const lineLength = lineChildren.length;
887
+ if (lineLength === 0) {
888
+ _lineJustifyStarts[lineIdx] = 0;
889
+ _lineItemSpacings[lineIdx] = mainGap;
890
+ continue;
891
+ }
892
+ let lineUsedMain = 0;
893
+ let lineAutoMargins = 0;
894
+ for (let i = 0; i < lineLength; i++) {
895
+ const c = lineChildren[i];
896
+ lineUsedMain += c.flex.mainSize + c.flex.mainMargin;
897
+ if (c.flex.mainStartMarginAuto) lineAutoMargins++;
898
+ if (c.flex.mainEndMarginAuto) lineAutoMargins++;
899
+ }
900
+ const lineGaps = lineLength > 1 ? mainGap * (lineLength - 1) : 0;
901
+ lineUsedMain += lineGaps;
902
+ const lineRemainingSpace = Number.isNaN(mainAxisSize) ? 0 : mainAxisSize - lineUsedMain;
903
+ const lineHasAutoMargins = lineAutoMargins > 0;
904
+ if (lineHasAutoMargins) {
905
+ const autoMarginValue = Math.max(0, lineRemainingSpace) / lineAutoMargins;
906
+ for (let i = 0; i < lineLength; i++) {
907
+ const child = lineChildren[i];
908
+ if (child.flex.mainStartMarginAuto) child.flex.mainStartMarginValue = autoMarginValue;
909
+ if (child.flex.mainEndMarginAuto) child.flex.mainEndMarginValue = autoMarginValue;
910
+ }
911
+ }
912
+ let lineStartOffset = 0;
913
+ let lineItemSpacing = mainGap;
914
+ if (!lineHasAutoMargins) switch (style.justifyContent) {
915
+ case 2:
916
+ lineStartOffset = lineRemainingSpace;
917
+ break;
918
+ case 1:
919
+ lineStartOffset = lineRemainingSpace / 2;
920
+ break;
921
+ case 3:
922
+ if (lineLength > 1 && lineRemainingSpace > 0) lineItemSpacing = mainGap + lineRemainingSpace / (lineLength - 1);
923
+ break;
924
+ case 4:
925
+ if (lineLength > 0 && lineRemainingSpace > 0) {
926
+ const extraSpace = lineRemainingSpace / lineLength;
927
+ lineStartOffset = extraSpace / 2;
928
+ lineItemSpacing = mainGap + extraSpace;
929
+ }
930
+ break;
931
+ case 5:
932
+ if (lineLength > 0 && lineRemainingSpace > 0) {
933
+ const extraSpace = lineRemainingSpace / (lineLength + 1);
934
+ lineStartOffset = extraSpace;
935
+ lineItemSpacing = mainGap + extraSpace;
936
+ }
937
+ break;
938
+ }
939
+ _lineJustifyStarts[lineIdx] = lineStartOffset;
940
+ _lineItemSpacings[lineIdx] = lineItemSpacing;
941
+ }
942
+ const startOffset = _lineJustifyStarts[0];
943
+ const itemSpacing = _lineItemSpacings[0];
944
+ let maxBaseline = 0;
945
+ let baselineZoneHeight = 0;
946
+ const alignItemsIsBaseline = style.alignItems === 5;
947
+ if (hasBaselineAlignment && isRow) {
948
+ let maxChildHeight = 0;
949
+ for (const child of node.children) {
950
+ if (child.flex.relativeIndex < 0) continue;
951
+ const childStyle = child.style;
952
+ const topMargin = child.flex.marginT;
953
+ let childWidth;
954
+ let childHeight;
955
+ const widthDim = childStyle.width;
956
+ const heightDim = childStyle.height;
957
+ if (widthDim.unit === 1) childWidth = widthDim.value;
958
+ else if (widthDim.unit === 2 && !Number.isNaN(mainAxisSize)) childWidth = mainAxisSize * (widthDim.value / 100);
959
+ else childWidth = child.flex.mainSize;
960
+ if (heightDim.unit === 1) childHeight = heightDim.value;
961
+ else if (heightDim.unit === 2 && !Number.isNaN(crossAxisSize)) childHeight = crossAxisSize * (heightDim.value / 100);
962
+ else {
963
+ const cached = child.getCachedLayout(child.flex.mainSize, NaN);
964
+ if (cached) {
965
+ incLayoutCacheHits();
966
+ _t?.cacheHit(_tn, child.flex.mainSize, NaN, cached.width, cached.height);
967
+ childWidth = cached.width;
968
+ childHeight = cached.height;
969
+ } else {
970
+ _t?.cacheMiss(_tn, child.flex.mainSize, NaN);
971
+ const savedW = child.layout.width;
972
+ const savedH = child.layout.height;
973
+ measureNode(child, child.flex.mainSize, NaN, direction);
974
+ childWidth = child.layout.width;
975
+ childHeight = child.layout.height;
976
+ child.layout.width = savedW;
977
+ child.layout.height = savedH;
978
+ _t?.measureSaveRestore(_tn, savedW, savedH, childWidth, childHeight);
979
+ child.setCachedLayout(child.flex.mainSize, NaN, childWidth, childHeight);
980
+ }
981
+ }
982
+ if (child.baselineFunc !== null) child.flex.baseline = topMargin + child.baselineFunc(childWidth, childHeight);
983
+ else child.flex.baseline = topMargin + childHeight;
984
+ maxChildHeight = Math.max(maxChildHeight, topMargin + childHeight + child.flex.marginB);
985
+ if (alignItemsIsBaseline || childStyle.alignSelf === 5) maxBaseline = Math.max(maxBaseline, child.flex.baseline);
986
+ }
987
+ baselineZoneHeight = Math.max(maxBaseline, maxChildHeight);
988
+ }
989
+ let cumulativeCrossOffset = 0;
990
+ const isWrapReverse = style.flexWrap === 2;
991
+ for (let lineIdx = 0; lineIdx < numLines; lineIdx++) {
992
+ _lineCrossOffsets[lineIdx] = cumulativeCrossOffset;
993
+ const lineChildren = _lineChildren[lineIdx];
994
+ const lineLength = lineChildren.length;
995
+ let maxLineCross = 0;
996
+ for (let i = 0; i < lineLength; i++) {
997
+ const child = lineChildren[i];
998
+ const childStyle = child.style;
999
+ const crossDim = isRow ? childStyle.height : childStyle.width;
1000
+ const crossMarginStart = isRow ? child.flex.marginT : child.flex.marginL;
1001
+ const crossMarginEnd = isRow ? child.flex.marginB : child.flex.marginR;
1002
+ let childCross = 0;
1003
+ if (crossDim.unit === 1) childCross = crossDim.value;
1004
+ else if (crossDim.unit === 2 && !Number.isNaN(crossAxisSize)) childCross = crossAxisSize * (crossDim.value / 100);
1005
+ else if (child.hasMeasureFunc()) {
1006
+ const crossMargin = crossMarginStart + crossMarginEnd;
1007
+ const availCross = Number.isNaN(crossAxisSize) ? Infinity : crossAxisSize - crossMargin;
1008
+ const childMainSize = child.flex.mainSize;
1009
+ const mW = isRow ? childMainSize : availCross;
1010
+ const mH = isRow ? availCross : childMainSize;
1011
+ const mWMode = Number.isNaN(mW) ? 0 : 2;
1012
+ const mHMode = Number.isNaN(mH) ? 0 : 2;
1013
+ const measured = child.cachedMeasure(Number.isNaN(mW) ? Infinity : mW, mWMode, Number.isNaN(mH) ? Infinity : mH, mHMode);
1014
+ if (measured) childCross = isRow ? measured.height : measured.width;
1015
+ } else if (child.children.length > 0) {
1016
+ const savedW = child.layout.width;
1017
+ const savedH = child.layout.height;
1018
+ measureNode(child, NaN, NaN, direction);
1019
+ childCross = isRow ? child.layout.height : child.layout.width;
1020
+ child.layout.width = savedW;
1021
+ child.layout.height = savedH;
1022
+ }
1023
+ maxLineCross = Math.max(maxLineCross, childCross + crossMarginStart + crossMarginEnd);
1024
+ }
1025
+ const lineCrossSize = maxLineCross;
1026
+ _lineCrossSizes[lineIdx] = lineCrossSize;
1027
+ cumulativeCrossOffset += lineCrossSize + crossGap;
1028
+ }
1029
+ if (!Number.isNaN(crossAxisSize) && numLines > 0) {
1030
+ const freeSpace = crossAxisSize - (cumulativeCrossOffset - crossGap);
1031
+ switch (style.alignContent) {
1032
+ case 3:
1033
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += freeSpace;
1034
+ break;
1035
+ case 2:
1036
+ {
1037
+ const centerOffset = freeSpace / 2;
1038
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += centerOffset;
1039
+ }
1040
+ break;
1041
+ case 6:
1042
+ if (freeSpace > 0 && numLines > 1) {
1043
+ const gap = freeSpace / (numLines - 1);
1044
+ for (let i = 1; i < numLines; i++) _lineCrossOffsets[i] += gap * i;
1045
+ }
1046
+ break;
1047
+ case 7:
1048
+ if (freeSpace > 0) {
1049
+ const halfGap = freeSpace / (numLines * 2);
1050
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += halfGap + halfGap * 2 * i;
1051
+ } else {
1052
+ const centerOffset = freeSpace / 2;
1053
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += centerOffset;
1054
+ }
1055
+ break;
1056
+ case 8:
1057
+ if (freeSpace > 0 && numLines > 0) {
1058
+ const gap = freeSpace / (numLines + 1);
1059
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += gap * (i + 1);
1060
+ } else if (freeSpace < 0) {
1061
+ const centerOffset = freeSpace / 2;
1062
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += centerOffset;
1063
+ }
1064
+ break;
1065
+ case 4:
1066
+ if (freeSpace > 0 && numLines > 0) {
1067
+ const extraPerLine = freeSpace / numLines;
1068
+ for (let i = 0; i < numLines; i++) {
1069
+ _lineCrossSizes[i] += extraPerLine;
1070
+ if (i > 0) _lineCrossOffsets[i] = _lineCrossOffsets[i - 1] + _lineCrossSizes[i - 1] + crossGap;
1071
+ }
1072
+ }
1073
+ break;
1074
+ }
1075
+ if (isWrapReverse) {
1076
+ let totalLineCrossSize = 0;
1077
+ for (let i = 0; i < numLines; i++) totalLineCrossSize += _lineCrossSizes[i];
1078
+ totalLineCrossSize += crossGap * (numLines - 1);
1079
+ const crossStartOffset = crossAxisSize - totalLineCrossSize;
1080
+ for (let i = 0; i < numLines; i++) _lineCrossOffsets[i] += crossStartOffset;
1081
+ }
1082
+ }
1083
+ let savedLineCrossSizes = null;
1084
+ let savedLineCrossOffsets = null;
1085
+ let savedLineJustifyStarts = null;
1086
+ let savedLineItemSpacings = null;
1087
+ if (numLines > 1) {
1088
+ savedLineCrossSizes = new Float64Array(numLines);
1089
+ savedLineCrossOffsets = new Float64Array(numLines);
1090
+ savedLineJustifyStarts = new Float64Array(numLines);
1091
+ savedLineItemSpacings = new Float64Array(numLines);
1092
+ for (let i = 0; i < numLines; i++) {
1093
+ savedLineCrossSizes[i] = _lineCrossSizes[i];
1094
+ savedLineCrossOffsets[i] = _lineCrossOffsets[i];
1095
+ savedLineJustifyStarts[i] = _lineJustifyStarts[i];
1096
+ savedLineItemSpacings[i] = _lineItemSpacings[i];
1097
+ }
1098
+ }
1099
+ let effectiveMainAxisSize = mainAxisSize;
1100
+ const mainIsAuto = isRow ? style.width.unit !== 1 && style.width.unit !== 2 : style.height.unit !== 1 && style.height.unit !== 2;
1101
+ const totalGaps = relativeCount > 1 ? mainGap * (relativeCount - 1) : 0;
1102
+ if (effectiveReverse && mainIsAuto) {
1103
+ let totalContent = 0;
1104
+ for (const child of node.children) {
1105
+ if (child.flex.relativeIndex < 0) continue;
1106
+ totalContent += child.flex.mainSize + child.flex.mainStartMarginValue + child.flex.mainEndMarginValue;
1107
+ }
1108
+ totalContent += totalGaps;
1109
+ effectiveMainAxisSize = totalContent;
1110
+ }
1111
+ let mainPos = effectiveReverse ? effectiveMainAxisSize - startOffset : startOffset;
1112
+ let currentLineIdx = -1;
1113
+ let relIdx = 0;
1114
+ let lineChildIdx = 0;
1115
+ let currentLineLength = 0;
1116
+ let currentItemSpacing = itemSpacing;
1117
+ log.debug?.("positioning children: isRow=%s, startOffset=%d, relativeCount=%d, effectiveReverse=%s, numLines=%d", isRow, startOffset, relativeCount, effectiveReverse, numLines);
1118
+ for (const child of node.children) {
1119
+ if (child.flex.relativeIndex < 0) continue;
1120
+ const cflex = child.flex;
1121
+ const childStyle = child.style;
1122
+ const childLineIdx = cflex.lineIndex;
1123
+ if (childLineIdx !== currentLineIdx) {
1124
+ currentLineIdx = childLineIdx;
1125
+ lineChildIdx = 0;
1126
+ currentLineLength = _lineChildren[childLineIdx].length;
1127
+ const lineOffset = savedLineJustifyStarts ? savedLineJustifyStarts[childLineIdx] : _lineJustifyStarts[childLineIdx];
1128
+ currentItemSpacing = savedLineItemSpacings ? savedLineItemSpacings[childLineIdx] : _lineItemSpacings[childLineIdx];
1129
+ mainPos = effectiveReverse ? effectiveMainAxisSize - lineOffset : lineOffset;
1130
+ }
1131
+ const lineCrossOffset = savedLineCrossOffsets ? savedLineCrossOffsets[childLineIdx] : childLineIdx < MAX_FLEX_LINES ? _lineCrossOffsets[childLineIdx] : 0;
1132
+ let childMarginLeft;
1133
+ let childMarginTop;
1134
+ let childMarginRight;
1135
+ let childMarginBottom;
1136
+ if (isRow) {
1137
+ childMarginLeft = cflex.mainStartMarginAuto && !effectiveReverse ? cflex.mainStartMarginValue : cflex.mainEndMarginAuto && effectiveReverse ? cflex.mainEndMarginValue : cflex.marginL;
1138
+ childMarginRight = cflex.mainEndMarginAuto && !effectiveReverse ? cflex.mainEndMarginValue : cflex.mainStartMarginAuto && effectiveReverse ? cflex.mainStartMarginValue : cflex.marginR;
1139
+ childMarginTop = cflex.marginT;
1140
+ childMarginBottom = cflex.marginB;
1141
+ } else {
1142
+ childMarginTop = cflex.mainStartMarginAuto && !isReverse ? cflex.mainStartMarginValue : cflex.mainEndMarginAuto && isReverse ? cflex.mainEndMarginValue : cflex.marginT;
1143
+ childMarginBottom = cflex.mainEndMarginAuto && !isReverse ? cflex.mainEndMarginValue : cflex.mainStartMarginAuto && isReverse ? cflex.mainStartMarginValue : cflex.marginB;
1144
+ childMarginLeft = cflex.marginL;
1145
+ childMarginRight = cflex.marginR;
1146
+ }
1147
+ const childMainSize = cflex.mainSize;
1148
+ let alignment = style.alignItems;
1149
+ if (childStyle.alignSelf !== 0) alignment = childStyle.alignSelf;
1150
+ const childCrossDimForAR = isRow ? childStyle.height : childStyle.width;
1151
+ const childCrossIsAutoForAR = childCrossDimForAR.unit === 3 || childCrossDimForAR.unit === 0;
1152
+ if (alignment === 4 && childStyle.alignSelf === 0 && !Number.isNaN(childStyle.aspectRatio) && childStyle.aspectRatio > 0 && childCrossIsAutoForAR) alignment = 1;
1153
+ let childCrossSize;
1154
+ const crossDim = isRow ? childStyle.height : childStyle.width;
1155
+ const crossMargin = isRow ? childMarginTop + childMarginBottom : childMarginLeft + childMarginRight;
1156
+ const parentCrossDim = isRow ? style.height : style.width;
1157
+ const parentHasDefiniteCross = parentCrossDim.unit === 1 || parentCrossDim.unit === 2 || !Number.isNaN(crossAxisSize);
1158
+ if (crossDim.unit === 1) childCrossSize = crossDim.value;
1159
+ else if (crossDim.unit === 2) childCrossSize = resolveValue(crossDim, crossAxisSize);
1160
+ else if (crossDim.unit === 4 || crossDim.unit === 5) childCrossSize = NaN;
1161
+ else if (parentHasDefiniteCross && alignment === 4) childCrossSize = (numLines > 1 ? savedLineCrossSizes ? savedLineCrossSizes[childLineIdx] : _lineCrossSizes[childLineIdx] : crossAxisSize) - crossMargin;
1162
+ else childCrossSize = NaN;
1163
+ const crossMinVal = isRow ? childStyle.minHeight : childStyle.minWidth;
1164
+ const crossMaxVal = isRow ? childStyle.maxHeight : childStyle.maxWidth;
1165
+ const crossMin = crossMinVal.unit !== 0 ? resolveValue(crossMinVal, crossAxisSize) : 0;
1166
+ const crossMax = crossMaxVal.unit !== 0 ? resolveValue(crossMaxVal, crossAxisSize) : Infinity;
1167
+ if (Number.isNaN(childCrossSize)) {
1168
+ if (crossMin > 0) childCrossSize = crossMin;
1169
+ } else childCrossSize = Math.max(crossMin, Math.min(crossMax, childCrossSize));
1170
+ const mainDim = isRow ? childStyle.width : childStyle.height;
1171
+ const hasDefiniteFlexBasis = childStyle.flexBasis.unit === 1 || childStyle.flexBasis.unit === 2;
1172
+ const mainIsAutoChild = (mainDim.unit === 3 || mainDim.unit === 0) && !hasDefiniteFlexBasis;
1173
+ const hasFlexGrow = cflex.flexGrow > 0;
1174
+ const effectiveMainSize = childMainSize;
1175
+ let childWidth = isRow ? effectiveMainSize : childCrossSize;
1176
+ let childHeight = isRow ? childCrossSize : effectiveMainSize;
1177
+ const shouldMeasure = child.hasMeasureFunc() && child.children.length === 0 && !hasFlexGrow;
1178
+ if (shouldMeasure) {
1179
+ const widthAuto = childStyle.width.unit === 3 || childStyle.width.unit === 0;
1180
+ const heightAuto = childStyle.height.unit === 3 || childStyle.height.unit === 0;
1181
+ if (widthAuto || heightAuto) {
1182
+ const widthMode = widthAuto ? 2 : 1;
1183
+ const heightMode = heightAuto ? 0 : 1;
1184
+ const rawAvailW = widthAuto ? isRow ? mainAxisSize - mainPos : crossAxisSize - crossMargin : childStyle.width.value;
1185
+ const rawAvailH = heightAuto ? isRow ? crossAxisSize - crossMargin : mainAxisSize - mainPos : childStyle.height.value;
1186
+ const availW = Number.isNaN(rawAvailW) ? Infinity : rawAvailW;
1187
+ const availH = Number.isNaN(rawAvailH) ? Infinity : rawAvailH;
1188
+ const measured = child.cachedMeasure(availW, widthMode, availH, heightMode);
1189
+ if (widthAuto) childWidth = measured.width;
1190
+ if (heightAuto) childHeight = measured.height;
1191
+ }
1192
+ }
1193
+ let childX;
1194
+ let childY;
1195
+ if (effectiveReverse) if (isRow) {
1196
+ childX = mainPos - childMainSize - childMarginRight;
1197
+ childY = lineCrossOffset + childMarginTop;
1198
+ } else {
1199
+ childX = lineCrossOffset + childMarginLeft;
1200
+ childY = mainPos - childMainSize - childMarginTop;
1201
+ }
1202
+ else {
1203
+ childX = isRow ? mainPos + childMarginLeft : lineCrossOffset + childMarginLeft;
1204
+ childY = isRow ? lineCrossOffset + childMarginTop : mainPos + childMarginTop;
1205
+ }
1206
+ const fractionalLeft = innerLeft + childX;
1207
+ const fractionalTop = innerTop + childY;
1208
+ let posOffsetX = 0;
1209
+ let posOffsetY = 0;
1210
+ if (childStyle.positionType === 1) {
1211
+ const relLeftPos = resolvePositionEdge(childStyle.position, 0, direction);
1212
+ const relTopPos = childStyle.position[1];
1213
+ const relRightPos = resolvePositionEdge(childStyle.position, 2, direction);
1214
+ const relBottomPos = childStyle.position[3];
1215
+ if (relLeftPos.unit !== 0) posOffsetX = resolveValue(relLeftPos, contentWidth);
1216
+ else if (relRightPos.unit !== 0) posOffsetX = -resolveValue(relRightPos, contentWidth);
1217
+ if (relTopPos.unit !== 0) posOffsetY = resolveValue(relTopPos, contentHeight);
1218
+ else if (relBottomPos.unit !== 0) posOffsetY = -resolveValue(relBottomPos, contentHeight);
1219
+ }
1220
+ const absChildLeft = absX + marginLeft + parentPosOffsetX + fractionalLeft + posOffsetX;
1221
+ const absChildTop = absY + marginTop + parentPosOffsetY + fractionalTop + posOffsetY;
1222
+ let roundedAbsMainStart;
1223
+ let roundedAbsMainEnd;
1224
+ let edgeBasedMainSize;
1225
+ const useEdgeBasedRounding = childMainSize > 0;
1226
+ const childPaddingL = resolveEdgeValue(childStyle.padding, 0, childStyle.flexDirection, contentWidth, direction);
1227
+ const childPaddingT = resolveEdgeValue(childStyle.padding, 1, childStyle.flexDirection, contentWidth, direction);
1228
+ const childPaddingR = resolveEdgeValue(childStyle.padding, 2, childStyle.flexDirection, contentWidth, direction);
1229
+ const childPaddingB = resolveEdgeValue(childStyle.padding, 3, childStyle.flexDirection, contentWidth, direction);
1230
+ const childBorderL = resolveEdgeBorderValue(childStyle.border, 0, childStyle.flexDirection, direction);
1231
+ const childBorderT = resolveEdgeBorderValue(childStyle.border, 1, childStyle.flexDirection, direction);
1232
+ const childBorderR = resolveEdgeBorderValue(childStyle.border, 2, childStyle.flexDirection, direction);
1233
+ const childBorderB = resolveEdgeBorderValue(childStyle.border, 3, childStyle.flexDirection, direction);
1234
+ const childMinW = childPaddingL + childPaddingR + childBorderL + childBorderR;
1235
+ const childMinH = childPaddingT + childPaddingB + childBorderT + childBorderB;
1236
+ const childMinMain = isRow ? childMinW : childMinH;
1237
+ const constrainedMainSize = Math.max(childMainSize, childMinMain);
1238
+ if (useEdgeBasedRounding) if (isRow) {
1239
+ roundedAbsMainStart = Math.round(absChildLeft);
1240
+ roundedAbsMainEnd = Math.round(absChildLeft + constrainedMainSize);
1241
+ edgeBasedMainSize = roundedAbsMainEnd - roundedAbsMainStart;
1242
+ } else {
1243
+ roundedAbsMainStart = Math.round(absChildTop);
1244
+ roundedAbsMainEnd = Math.round(absChildTop + constrainedMainSize);
1245
+ edgeBasedMainSize = roundedAbsMainEnd - roundedAbsMainStart;
1246
+ }
1247
+ else {
1248
+ roundedAbsMainStart = isRow ? Math.round(absChildLeft) : Math.round(absChildTop);
1249
+ edgeBasedMainSize = childMinMain;
1250
+ }
1251
+ const posRound = shouldMeasure ? Math.floor : Math.round;
1252
+ const childLeft = posRound(fractionalLeft + posOffsetX);
1253
+ const childTop = posRound(fractionalTop + posOffsetY);
1254
+ const crossDimForLayoutCall = isRow ? childStyle.height : childStyle.width;
1255
+ const crossIsAutoForLayoutCall = crossDimForLayoutCall.unit === 3 || crossDimForLayoutCall.unit === 0 || crossDimForLayoutCall.unit === 4 || crossDimForLayoutCall.unit === 5;
1256
+ const mainIsPercentForLayoutCall = (isRow ? childStyle.width : childStyle.height).unit === 2;
1257
+ const crossIsPercentForLayoutCall = crossDimForLayoutCall.unit === 2;
1258
+ const flexDistChanged = child.flex.mainSize !== child.flex.baseSize;
1259
+ const hasMeasureLeaf = child.hasMeasureFunc() && child.children.length === 0;
1260
+ const crossIsFitContent = crossDimForLayoutCall.unit === 4 || crossDimForLayoutCall.unit === 5;
1261
+ layoutNode(child, isRow && mainIsAutoChild && !hasFlexGrow && !flexDistChanged && !hasMeasureLeaf ? NaN : !isRow && crossIsAutoForLayoutCall && !parentHasDefiniteCross && !crossIsFitContent ? NaN : isRow && mainIsPercentForLayoutCall ? mainAxisSize : !isRow && crossIsPercentForLayoutCall ? crossAxisSize : !isRow && crossIsFitContent ? crossAxisSize : childWidth, !isRow && mainIsAutoChild && !hasFlexGrow && !flexDistChanged && !hasMeasureLeaf ? NaN : isRow && crossIsAutoForLayoutCall && !parentHasDefiniteCross && !crossIsFitContent ? NaN : !isRow && mainIsPercentForLayoutCall ? mainAxisSize : isRow && crossIsPercentForLayoutCall ? crossAxisSize : isRow && crossIsFitContent ? crossAxisSize : childHeight, childLeft, childTop, absChildLeft - childMarginLeft, absChildTop - childMarginTop, direction);
1262
+ if (childWidth < childMinW) childWidth = childMinW;
1263
+ if (childHeight < childMinH) childHeight = childMinH;
1264
+ const hasMeasure = child.hasMeasureFunc() && child.children.length === 0;
1265
+ const flexDistributionChangedSize = child.flex.mainSize !== child.flex.baseSize;
1266
+ if (!mainIsAuto && !mainIsAutoChild || hasFlexGrow || hasMeasure || flexDistributionChangedSize) if (isRow) {
1267
+ _t?.parentOverride(_tn, "main", child.layout.width, edgeBasedMainSize);
1268
+ child.layout.width = edgeBasedMainSize;
1269
+ } else {
1270
+ _t?.parentOverride(_tn, "main", child.layout.height, edgeBasedMainSize);
1271
+ child.layout.height = edgeBasedMainSize;
1272
+ }
1273
+ const crossDimForCheck = isRow ? childStyle.height : childStyle.width;
1274
+ const crossDimIsFitContent = crossDimForCheck.unit === 4 || crossDimForCheck.unit === 5;
1275
+ const crossIsAuto = crossDimForCheck.unit === 3 || crossDimForCheck.unit === 0 || crossDimIsFitContent;
1276
+ const parentCrossIsAuto = !parentHasDefiniteCross;
1277
+ const hasCrossMinMax = crossMinVal.unit !== 0 || crossMaxVal.unit !== 0;
1278
+ if (!crossIsAuto || !crossDimIsFitContent && !parentCrossIsAuto && alignment === 4 || hasCrossMinMax && !Number.isNaN(childCrossSize)) if (isRow) child.layout.height = Math.round(childHeight);
1279
+ else child.layout.width = Math.round(childWidth);
1280
+ child.layout.left = childLeft;
1281
+ child.layout.top = childTop;
1282
+ childWidth = child.layout.width;
1283
+ childHeight = child.layout.height;
1284
+ const finalCrossSize = isRow ? child.layout.height : child.layout.width;
1285
+ let crossOffset = 0;
1286
+ const crossStartIndex = isRow ? 1 : 0;
1287
+ const crossEndIndex = isRow ? 3 : 2;
1288
+ const hasAutoStartMargin = isEdgeAuto(childStyle.margin, crossStartIndex, style.flexDirection, direction);
1289
+ const hasAutoEndMargin = isEdgeAuto(childStyle.margin, crossEndIndex, style.flexDirection, direction);
1290
+ const availableCrossSpace = (hasBaselineAlignment && isRow && !alignItemsIsBaseline && alignment !== 5 && baselineZoneHeight > 0 ? baselineZoneHeight : crossAxisSize) - finalCrossSize - crossMargin;
1291
+ if (hasAutoStartMargin && hasAutoEndMargin) crossOffset = Math.max(0, availableCrossSpace) / 2;
1292
+ else if (hasAutoStartMargin) crossOffset = Math.max(0, availableCrossSpace);
1293
+ else if (hasAutoEndMargin) crossOffset = 0;
1294
+ else switch (alignment) {
1295
+ case 3:
1296
+ crossOffset = availableCrossSpace;
1297
+ break;
1298
+ case 2:
1299
+ crossOffset = availableCrossSpace / 2;
1300
+ break;
1301
+ case 5:
1302
+ if (isRow && hasBaselineAlignment) crossOffset = maxBaseline - child.flex.baseline;
1303
+ break;
1304
+ }
1305
+ if (crossOffset !== 0) {
1306
+ const crossRound = shouldMeasure ? Math.floor : Math.round;
1307
+ if (isRow) child.layout.top += crossRound(crossOffset);
1308
+ else child.layout.left += crossRound(crossOffset);
1309
+ }
1310
+ const fractionalMainSize = !mainIsAuto && !mainIsAutoChild || hasFlexGrow || hasMeasure || flexDistributionChangedSize ? constrainedMainSize : isRow ? child.layout.width : child.layout.height;
1311
+ const totalMainMargin = cflex.mainStartMarginValue + cflex.mainEndMarginValue;
1312
+ log.debug?.(" child %d: mainPos=%d -> top=%d (fractionalMainSize=%d, totalMainMargin=%d)", relIdx, mainPos, child.layout.top, fractionalMainSize, totalMainMargin);
1313
+ if (effectiveReverse) {
1314
+ mainPos -= fractionalMainSize + totalMainMargin;
1315
+ if (lineChildIdx < currentLineLength - 1) mainPos -= currentItemSpacing;
1316
+ } else {
1317
+ mainPos += fractionalMainSize + totalMainMargin;
1318
+ if (lineChildIdx < currentLineLength - 1) mainPos += currentItemSpacing;
1319
+ }
1320
+ relIdx++;
1321
+ lineChildIdx++;
1322
+ }
1323
+ let actualUsedMain = 0;
1324
+ for (const child of node.children) {
1325
+ if (child.flex.relativeIndex < 0) continue;
1326
+ const childMainSize = isRow ? child.layout.width : child.layout.height;
1327
+ const totalMainMargin = child.flex.mainStartMarginValue + child.flex.mainEndMarginValue;
1328
+ actualUsedMain += childMainSize + totalMainMargin;
1329
+ }
1330
+ actualUsedMain += totalGaps;
1331
+ const hasAR = !Number.isNaN(aspectRatio) && aspectRatio > 0;
1332
+ if (isRow && style.width.unit !== 1 && style.width.unit !== 2 && !hasAR) nodeWidth = actualUsedMain + innerLeft + innerRight;
1333
+ if (!isRow && style.height.unit !== 1 && style.height.unit !== 2 && !hasAR) nodeHeight = actualUsedMain + innerTop + innerBottom;
1334
+ let totalCrossSize = 0;
1335
+ if (numLines > 1) {
1336
+ for (let i = 0; i < numLines; i++) totalCrossSize += savedLineCrossSizes ? savedLineCrossSizes[i] : _lineCrossSizes[i];
1337
+ totalCrossSize += crossGap * (numLines - 1);
1338
+ } else for (const child of node.children) {
1339
+ if (child.flex.relativeIndex < 0) continue;
1340
+ const childCross = isRow ? child.layout.height : child.layout.width;
1341
+ const childMargin = isRow ? resolveEdgeValue(child.style.margin, 1, style.flexDirection, contentWidth, direction) + resolveEdgeValue(child.style.margin, 3, style.flexDirection, contentWidth, direction) : resolveEdgeValue(child.style.margin, 0, style.flexDirection, contentWidth, direction) + resolveEdgeValue(child.style.margin, 2, style.flexDirection, contentWidth, direction);
1342
+ totalCrossSize = Math.max(totalCrossSize, childCross + childMargin);
1343
+ }
1344
+ if (isRow && style.height.unit !== 1 && style.height.unit !== 2 && Number.isNaN(availableHeight) && !hasAR) nodeHeight = totalCrossSize + innerTop + innerBottom;
1345
+ if (!isRow && style.width.unit !== 1 && style.width.unit !== 2 && Number.isNaN(availableWidth) && !hasAR) nodeWidth = totalCrossSize + innerLeft + innerRight;
1346
+ }
1347
+ if (isFitContentWidth && !Number.isNaN(nodeWidth) && !Number.isNaN(availableWidth)) {
1348
+ const availForNode = availableWidth - marginLeft - marginRight;
1349
+ if (availForNode >= 0 && nodeWidth > availForNode) nodeWidth = availForNode;
1350
+ }
1351
+ nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth);
1352
+ nodeHeight = applyMinMax(nodeHeight, style.minHeight, style.maxHeight, availableHeight);
1353
+ if (!Number.isNaN(nodeWidth) && nodeWidth < minInnerWidth) nodeWidth = minInnerWidth;
1354
+ if (!Number.isNaN(nodeHeight) && nodeHeight < minInnerHeight) nodeHeight = minInnerHeight;
1355
+ if (Number.isNaN(crossAxisSize) && relativeCount > 0) {
1356
+ const finalCross = isRow ? nodeHeight - innerTop - innerBottom : nodeWidth - innerLeft - innerRight;
1357
+ if (!Number.isNaN(finalCross) && finalCross > 0) {
1358
+ for (const child of node.children) {
1359
+ if (child.flex.relativeIndex < 0) continue;
1360
+ const cstyle = child.style;
1361
+ let childAlign = style.alignItems;
1362
+ if (cstyle.alignSelf !== 0) childAlign = cstyle.alignSelf;
1363
+ const cCrossDim = isRow ? cstyle.height : cstyle.width;
1364
+ const cCrossIsAuto = cCrossDim.unit === 3 || cCrossDim.unit === 0;
1365
+ if (childAlign === 4 && cstyle.alignSelf === 0 && !Number.isNaN(cstyle.aspectRatio) && cstyle.aspectRatio > 0 && cCrossIsAuto) childAlign = 1;
1366
+ if (childAlign !== 4) continue;
1367
+ if (!cCrossIsAuto) continue;
1368
+ const stretchedCross = finalCross - (isRow ? resolveEdgeValue(cstyle.margin, 1, style.flexDirection, contentWidth, direction) + resolveEdgeValue(cstyle.margin, 3, style.flexDirection, contentWidth, direction) : resolveEdgeValue(cstyle.margin, 0, style.flexDirection, contentWidth, direction) + resolveEdgeValue(cstyle.margin, 2, style.flexDirection, contentWidth, direction));
1369
+ const currentCross = isRow ? child.layout.height : child.layout.width;
1370
+ if (Math.round(stretchedCross) <= currentCross) continue;
1371
+ const savedLeft = child.layout.left;
1372
+ const savedTop = child.layout.top;
1373
+ const cMarginL = resolveEdgeValue(cstyle.margin, 0, style.flexDirection, contentWidth, direction);
1374
+ const cMarginT = resolveEdgeValue(cstyle.margin, 1, style.flexDirection, contentWidth, direction);
1375
+ const cAbsX = absX + innerLeft + savedLeft - cMarginL;
1376
+ const cAbsY = absY + innerTop + savedTop - cMarginT;
1377
+ layoutNode(child, isRow ? child.layout.width : stretchedCross, isRow ? stretchedCross : child.layout.height, savedLeft, savedTop, cAbsX, cAbsY, direction);
1378
+ child.layout.left = savedLeft;
1379
+ child.layout.top = savedTop;
1380
+ if (isRow) child.layout.height = Math.round(stretchedCross);
1381
+ else child.layout.width = Math.round(stretchedCross);
1382
+ }
1383
+ if (Number.isNaN(crossAxisSize) && relativeCount > 0) {
1384
+ const finalCross9c = isRow ? nodeHeight - innerTop - innerBottom : nodeWidth - innerLeft - innerRight;
1385
+ if (!Number.isNaN(finalCross9c) && finalCross9c > 0) for (const child of node.children) {
1386
+ if (child.flex.relativeIndex < 0) continue;
1387
+ const cstyle = child.style;
1388
+ let childAlign = style.alignItems;
1389
+ if (cstyle.alignSelf !== 0) childAlign = cstyle.alignSelf;
1390
+ const cCrossDim = isRow ? cstyle.height : cstyle.width;
1391
+ const cCrossIsAuto = cCrossDim.unit === 3 || cCrossDim.unit === 0;
1392
+ if (childAlign === 4 && cstyle.alignSelf === 0 && !Number.isNaN(cstyle.aspectRatio) && cstyle.aspectRatio > 0 && cCrossIsAuto) childAlign = 1;
1393
+ const crossStartIdx = isRow ? 1 : 0;
1394
+ const crossEndIdx = isRow ? 3 : 2;
1395
+ const hasAutoStart = isEdgeAuto(cstyle.margin, crossStartIdx, style.flexDirection, direction);
1396
+ const hasAutoEnd = isEdgeAuto(cstyle.margin, crossEndIdx, style.flexDirection, direction);
1397
+ if (!(hasAutoStart || hasAutoEnd || childAlign === 2 || childAlign === 3)) continue;
1398
+ const childCrossSize = isRow ? child.layout.height : child.layout.width;
1399
+ const cCrossMargin = isRow ? resolveEdgeValue(cstyle.margin, 1, style.flexDirection, contentWidth, direction) + resolveEdgeValue(cstyle.margin, 3, style.flexDirection, contentWidth, direction) : resolveEdgeValue(cstyle.margin, 0, style.flexDirection, contentWidth, direction) + resolveEdgeValue(cstyle.margin, 2, style.flexDirection, contentWidth, direction);
1400
+ const availSpace = finalCross9c - childCrossSize - cCrossMargin;
1401
+ let crossOffset = 0;
1402
+ if (hasAutoStart && hasAutoEnd) crossOffset = Math.max(0, availSpace) / 2;
1403
+ else if (hasAutoStart) crossOffset = Math.max(0, availSpace);
1404
+ else if (hasAutoEnd) crossOffset = 0;
1405
+ else switch (childAlign) {
1406
+ case 3:
1407
+ crossOffset = availSpace;
1408
+ break;
1409
+ case 2:
1410
+ crossOffset = availSpace / 2;
1411
+ break;
1412
+ }
1413
+ if (isRow) {
1414
+ if (Number.isNaN(child.layout.top)) {
1415
+ const cMarginT = resolveEdgeValue(cstyle.margin, 1, style.flexDirection, contentWidth, direction);
1416
+ child.layout.top = Math.round(cMarginT + crossOffset);
1417
+ } else if (crossOffset !== 0) child.layout.top += Math.round(crossOffset);
1418
+ } else if (Number.isNaN(child.layout.left)) {
1419
+ const cMarginL = resolveEdgeValue(cstyle.margin, 0, style.flexDirection, contentWidth, direction);
1420
+ child.layout.left = Math.round(cMarginL + crossOffset);
1421
+ } else if (crossOffset !== 0) child.layout.left += Math.round(crossOffset);
1422
+ }
1423
+ }
1424
+ }
1425
+ }
1426
+ const absNodeLeft = absX + marginLeft + parentPosOffsetX;
1427
+ const absNodeTop = absY + marginTop + parentPosOffsetY;
1428
+ const absNodeRight = absNodeLeft + nodeWidth;
1429
+ const absNodeBottom = absNodeTop + nodeHeight;
1430
+ const roundedAbsLeft = Math.round(absNodeLeft);
1431
+ const roundedAbsTop = Math.round(absNodeTop);
1432
+ const roundedAbsRight = Math.round(absNodeRight);
1433
+ const roundedAbsBottom = Math.round(absNodeBottom);
1434
+ layout.width = roundedAbsRight - roundedAbsLeft;
1435
+ layout.height = roundedAbsBottom - roundedAbsTop;
1436
+ const roundedAbsParentLeft = Math.round(absX);
1437
+ const roundedAbsParentTop = Math.round(absY);
1438
+ layout.left = roundedAbsLeft - roundedAbsParentLeft;
1439
+ layout.top = roundedAbsTop - roundedAbsParentTop;
1440
+ const absInnerLeft = borderLeft;
1441
+ const absInnerTop = borderTop;
1442
+ const absInnerRight = borderRight;
1443
+ const absInnerBottom = borderBottom;
1444
+ const absPaddingBoxW = nodeWidth - absInnerLeft - absInnerRight;
1445
+ const absPaddingBoxH = nodeHeight - absInnerTop - absInnerBottom;
1446
+ const absContentBoxW = absPaddingBoxW - paddingLeft - paddingRight;
1447
+ const absContentBoxH = absPaddingBoxH - paddingTop - paddingBottom;
1448
+ for (const child of node.children) {
1449
+ if (child.style.display === 1) continue;
1450
+ if (child.style.positionType !== 2) continue;
1451
+ const childStyle = child.style;
1452
+ const childMarginLeft = resolveEdgeValue(childStyle.margin, 0, style.flexDirection, nodeWidth, direction);
1453
+ const childMarginTop = resolveEdgeValue(childStyle.margin, 1, style.flexDirection, nodeWidth, direction);
1454
+ const childMarginRight = resolveEdgeValue(childStyle.margin, 2, style.flexDirection, nodeWidth, direction);
1455
+ const childMarginBottom = resolveEdgeValue(childStyle.margin, 3, style.flexDirection, nodeWidth, direction);
1456
+ const hasAutoMarginLeft = isEdgeAuto(childStyle.margin, 0, style.flexDirection, direction);
1457
+ const hasAutoMarginRight = isEdgeAuto(childStyle.margin, 2, style.flexDirection, direction);
1458
+ const hasAutoMarginTop = isEdgeAuto(childStyle.margin, 1, style.flexDirection, direction);
1459
+ const hasAutoMarginBottom = isEdgeAuto(childStyle.margin, 3, style.flexDirection, direction);
1460
+ const leftPos = resolvePositionEdge(childStyle.position, 0, direction);
1461
+ const topPos = childStyle.position[1];
1462
+ const rightPos = resolvePositionEdge(childStyle.position, 2, direction);
1463
+ const bottomPos = childStyle.position[3];
1464
+ const hasLeft = leftPos.unit !== 0;
1465
+ const hasRight = rightPos.unit !== 0;
1466
+ const hasTop = topPos.unit !== 0;
1467
+ const hasBottom = bottomPos.unit !== 0;
1468
+ const leftOffset = resolveValue(leftPos, absContentBoxW);
1469
+ const topOffset = resolveValue(topPos, absContentBoxH);
1470
+ const rightOffset = resolveValue(rightPos, absContentBoxW);
1471
+ const bottomOffset = resolveValue(bottomPos, absContentBoxH);
1472
+ const contentW = absPaddingBoxW;
1473
+ const contentH = absPaddingBoxH;
1474
+ let childAvailWidth;
1475
+ const widthIsAuto = childStyle.width.unit === 3 || childStyle.width.unit === 0;
1476
+ const widthIsPercent = childStyle.width.unit === 2;
1477
+ if (widthIsAuto && hasLeft && hasRight) childAvailWidth = contentW - leftOffset - rightOffset - childMarginLeft - childMarginRight;
1478
+ else if (widthIsAuto) childAvailWidth = NaN;
1479
+ else if (widthIsPercent) childAvailWidth = absContentBoxW;
1480
+ else childAvailWidth = contentW;
1481
+ let childAvailHeight;
1482
+ const heightIsAuto = childStyle.height.unit === 3 || childStyle.height.unit === 0;
1483
+ const heightIsPercent = childStyle.height.unit === 2;
1484
+ if (heightIsAuto && hasTop && hasBottom) childAvailHeight = contentH - topOffset - bottomOffset - childMarginTop - childMarginBottom;
1485
+ else if (heightIsAuto) childAvailHeight = NaN;
1486
+ else if (heightIsPercent) childAvailHeight = absContentBoxH;
1487
+ else childAvailHeight = contentH;
1488
+ let childX = childMarginLeft + leftOffset;
1489
+ let childY = childMarginTop + topOffset;
1490
+ const childAbsX = absX + marginLeft + absInnerLeft + leftOffset;
1491
+ const childAbsY = absY + marginTop + absInnerTop + topOffset;
1492
+ const clampIfNumber = (v) => Number.isNaN(v) ? NaN : Math.max(0, v);
1493
+ layoutNode(child, clampIfNumber(childAvailWidth), clampIfNumber(childAvailHeight), layout.left + absInnerLeft + childX, layout.top + absInnerTop + childY, childAbsX, childAbsY, direction);
1494
+ const childWidth = child.layout.width;
1495
+ const childHeight = child.layout.height;
1496
+ if (!hasLeft && !hasRight) if (isRow) {
1497
+ const freeSpaceX = contentW - childWidth - childMarginLeft - childMarginRight;
1498
+ switch (style.justifyContent) {
1499
+ case 1:
1500
+ childX = childMarginLeft + freeSpaceX / 2;
1501
+ break;
1502
+ case 2:
1503
+ childX = childMarginLeft + freeSpaceX;
1504
+ break;
1505
+ default:
1506
+ childX = childMarginLeft;
1507
+ break;
1508
+ }
1509
+ } else {
1510
+ let alignment = style.alignItems;
1511
+ if (childStyle.alignSelf !== 0) alignment = childStyle.alignSelf;
1512
+ const freeSpaceX = contentW - childWidth - childMarginLeft - childMarginRight;
1513
+ switch (alignment) {
1514
+ case 2:
1515
+ childX = childMarginLeft + freeSpaceX / 2;
1516
+ break;
1517
+ case 3:
1518
+ childX = childMarginLeft + freeSpaceX;
1519
+ break;
1520
+ case 4: break;
1521
+ default:
1522
+ childX = childMarginLeft;
1523
+ break;
1524
+ }
1525
+ }
1526
+ else if (!hasLeft && hasRight) childX = contentW - rightOffset - childMarginRight - childWidth;
1527
+ else if (hasLeft && hasRight) {
1528
+ if (widthIsAuto) child.layout.width = Math.round(childAvailWidth);
1529
+ else if (hasAutoMarginLeft || hasAutoMarginRight) {
1530
+ const freeSpace = Math.max(0, contentW - leftOffset - rightOffset - childWidth);
1531
+ if (hasAutoMarginLeft && hasAutoMarginRight) childX = leftOffset + freeSpace / 2;
1532
+ else if (hasAutoMarginLeft) childX = leftOffset + freeSpace;
1533
+ }
1534
+ }
1535
+ if (!hasTop && !hasBottom) if (isRow) {
1536
+ let alignment = style.alignItems;
1537
+ if (childStyle.alignSelf !== 0) alignment = childStyle.alignSelf;
1538
+ const freeSpaceY = contentH - childHeight - childMarginTop - childMarginBottom;
1539
+ switch (alignment) {
1540
+ case 2:
1541
+ childY = childMarginTop + freeSpaceY / 2;
1542
+ break;
1543
+ case 3:
1544
+ childY = childMarginTop + freeSpaceY;
1545
+ break;
1546
+ case 4: break;
1547
+ default:
1548
+ childY = childMarginTop;
1549
+ break;
1550
+ }
1551
+ } else {
1552
+ const freeSpaceY = contentH - childHeight - childMarginTop - childMarginBottom;
1553
+ switch (style.justifyContent) {
1554
+ case 1:
1555
+ childY = childMarginTop + freeSpaceY / 2;
1556
+ break;
1557
+ case 2:
1558
+ childY = childMarginTop + freeSpaceY;
1559
+ break;
1560
+ default:
1561
+ childY = childMarginTop;
1562
+ break;
1563
+ }
1564
+ }
1565
+ else if (!hasTop && hasBottom) childY = contentH - bottomOffset - childMarginBottom - childHeight;
1566
+ else if (hasTop && hasBottom) {
1567
+ if (heightIsAuto) child.layout.height = Math.round(childAvailHeight);
1568
+ else if (hasAutoMarginTop || hasAutoMarginBottom) {
1569
+ const freeSpace = Math.max(0, contentH - topOffset - bottomOffset - childHeight);
1570
+ if (hasAutoMarginTop && hasAutoMarginBottom) childY = topOffset + freeSpace / 2;
1571
+ else if (hasAutoMarginTop) childY = topOffset + freeSpace;
1572
+ }
1573
+ }
1574
+ child.layout.left = Math.round(absInnerLeft + childX);
1575
+ child.layout.top = Math.round(absInnerTop + childY);
1576
+ }
1577
+ flex.lastAvailW = availableWidth;
1578
+ flex.lastAvailH = availableHeight;
1579
+ flex.lastOffsetX = offsetX;
1580
+ flex.lastOffsetY = offsetY;
1581
+ flex.lastAbsX = absX;
1582
+ flex.lastAbsY = absY;
1583
+ flex.lastDir = direction;
1584
+ flex.layoutValid = true;
1585
+ _t?.layoutExit(_tn, layout.width, layout.height);
1586
+ }
1587
+ //#endregion
1588
+ //#region src/node-zero.ts
1589
+ /**
1590
+ * Flexily Node
1591
+ *
1592
+ * Yoga-compatible Node class for flexbox layout.
1593
+ */
1594
+ /**
1595
+ * A layout node in the flexbox tree.
1596
+ */
1597
+ var Node = class Node {
1598
+ _parent = null;
1599
+ _children = [];
1600
+ _style = createDefaultStyle();
1601
+ _measureFunc = null;
1602
+ _baselineFunc = null;
1603
+ _m0;
1604
+ _m1;
1605
+ _m2;
1606
+ _m3;
1607
+ _lc0;
1608
+ _lc1;
1609
+ _measureResult = {
1610
+ width: 0,
1611
+ height: 0
1612
+ };
1613
+ _layoutResult = {
1614
+ width: 0,
1615
+ height: 0
1616
+ };
1617
+ static measureCalls = 0;
1618
+ static measureCacheHits = 0;
1619
+ /**
1620
+ * Reset measure statistics (call before calculateLayout).
1621
+ */
1622
+ static resetMeasureStats() {
1623
+ Node.measureCalls = 0;
1624
+ Node.measureCacheHits = 0;
1625
+ }
1626
+ _layout = {
1627
+ left: 0,
1628
+ top: 0,
1629
+ width: 0,
1630
+ height: 0
1631
+ };
1632
+ _flex = {
1633
+ mainSize: 0,
1634
+ baseSize: 0,
1635
+ mainMargin: 0,
1636
+ flexGrow: 0,
1637
+ flexShrink: 0,
1638
+ minMain: 0,
1639
+ maxMain: Infinity,
1640
+ mainStartMarginAuto: false,
1641
+ mainEndMarginAuto: false,
1642
+ mainStartMarginValue: 0,
1643
+ mainEndMarginValue: 0,
1644
+ marginL: 0,
1645
+ marginT: 0,
1646
+ marginR: 0,
1647
+ marginB: 0,
1648
+ frozen: false,
1649
+ lineIndex: 0,
1650
+ relativeIndex: -1,
1651
+ baseline: 0,
1652
+ lastAvailW: NaN,
1653
+ lastAvailH: NaN,
1654
+ lastOffsetX: NaN,
1655
+ lastOffsetY: NaN,
1656
+ lastAbsX: NaN,
1657
+ lastAbsY: NaN,
1658
+ layoutValid: false,
1659
+ lastDir: 0
1660
+ };
1661
+ _isDirty = true;
1662
+ _hasNewLayout = false;
1663
+ _lastCalcW = NaN;
1664
+ _lastCalcH = NaN;
1665
+ _lastCalcDir = 0;
1666
+ /**
1667
+ * Create a new layout node.
1668
+ *
1669
+ * @returns A new Node instance
1670
+ * @example
1671
+ * ```typescript
1672
+ * const root = Node.create();
1673
+ * root.setWidth(100);
1674
+ * root.setHeight(200);
1675
+ * ```
1676
+ */
1677
+ static create() {
1678
+ return new Node();
1679
+ }
1680
+ /**
1681
+ * Get the number of child nodes.
1682
+ *
1683
+ * @returns The number of children
1684
+ */
1685
+ getChildCount() {
1686
+ return this._children.length;
1687
+ }
1688
+ /**
1689
+ * Get a child node by index.
1690
+ *
1691
+ * @param index - Zero-based child index
1692
+ * @returns The child node at the given index, or undefined if index is out of bounds
1693
+ */
1694
+ getChild(index) {
1695
+ return this._children[index];
1696
+ }
1697
+ /**
1698
+ * Get the parent node.
1699
+ *
1700
+ * @returns The parent node, or null if this is a root node
1701
+ */
1702
+ getParent() {
1703
+ return this._parent;
1704
+ }
1705
+ /**
1706
+ * Insert a child node at the specified index.
1707
+ * If the child already has a parent, it will be removed from that parent first.
1708
+ * Marks the node as dirty to trigger layout recalculation.
1709
+ *
1710
+ * @param child - The child node to insert
1711
+ * @param index - The index at which to insert the child
1712
+ * @example
1713
+ * ```typescript
1714
+ * const parent = Node.create();
1715
+ * const child1 = Node.create();
1716
+ * const child2 = Node.create();
1717
+ * parent.insertChild(child1, 0);
1718
+ * parent.insertChild(child2, 1);
1719
+ * ```
1720
+ */
1721
+ insertChild(child, index) {
1722
+ if (child === this) throw new Error("Cannot insert a node as a child of itself");
1723
+ let ancestor = this._parent;
1724
+ while (ancestor !== null) {
1725
+ if (ancestor === child) throw new Error("Cannot insert an ancestor as a child (would create a cycle)");
1726
+ ancestor = ancestor._parent;
1727
+ }
1728
+ if (child._parent !== null) child._parent.removeChild(child);
1729
+ child._parent = this;
1730
+ const clampedIndex = Math.max(0, Math.min(index, this._children.length));
1731
+ this._children.splice(clampedIndex, 0, child);
1732
+ for (let i = clampedIndex + 1; i < this._children.length; i++) this._children[i]._flex.layoutValid = false;
1733
+ this.markDirty();
1734
+ }
1735
+ /**
1736
+ * Remove a child node from this node.
1737
+ * The child's parent reference will be cleared.
1738
+ * Marks the node as dirty to trigger layout recalculation.
1739
+ * Invalidates layout validity of remaining siblings whose positions may change.
1740
+ *
1741
+ * @param child - The child node to remove
1742
+ */
1743
+ removeChild(child) {
1744
+ const index = this._children.indexOf(child);
1745
+ if (index !== -1) {
1746
+ this._children.splice(index, 1);
1747
+ child._parent = null;
1748
+ for (let i = index; i < this._children.length; i++) this._children[i]._flex.layoutValid = false;
1749
+ this.markDirty();
1750
+ }
1751
+ }
1752
+ /**
1753
+ * Free this node and clean up all references.
1754
+ * Removes the node from its parent, clears all children, and removes the measure function.
1755
+ * This does not recursively free child nodes.
1756
+ */
1757
+ free() {
1758
+ if (this._parent !== null) this._parent.removeChild(this);
1759
+ for (const child of this._children) child._parent = null;
1760
+ this._children = [];
1761
+ this._measureFunc = null;
1762
+ this._baselineFunc = null;
1763
+ }
1764
+ /**
1765
+ * Free this node and all descendants recursively.
1766
+ * Each node is detached from its parent and cleaned up.
1767
+ * Uses iterative traversal to avoid stack overflow on deep trees.
1768
+ */
1769
+ freeRecursive() {
1770
+ const nodes = [];
1771
+ traversalStack.length = 0;
1772
+ traversalStack.push(this);
1773
+ while (traversalStack.length > 0) {
1774
+ const current = traversalStack.pop();
1775
+ nodes.push(current);
1776
+ for (const child of current._children) traversalStack.push(child);
1777
+ }
1778
+ for (let i = nodes.length - 1; i >= 0; i--) nodes[i].free();
1779
+ }
1780
+ /**
1781
+ * Dispose the node (calls free)
1782
+ */
1783
+ [Symbol.dispose]() {
1784
+ this.free();
1785
+ }
1786
+ /**
1787
+ * Set a measure function for intrinsic sizing.
1788
+ * The measure function is called during layout to determine the node's natural size.
1789
+ * Typically used for text nodes or other content that has an intrinsic size.
1790
+ * Marks the node as dirty to trigger layout recalculation.
1791
+ *
1792
+ * @param measureFunc - Function that returns width and height given available space and constraints
1793
+ * @example
1794
+ * ```typescript
1795
+ * const textNode = Node.create();
1796
+ * textNode.setMeasureFunc((width, widthMode, height, heightMode) => {
1797
+ * // Measure text and return dimensions
1798
+ * return { width: 50, height: 20 };
1799
+ * });
1800
+ * ```
1801
+ */
1802
+ setMeasureFunc(measureFunc) {
1803
+ this._measureFunc = measureFunc;
1804
+ this.markDirty();
1805
+ }
1806
+ /**
1807
+ * Remove the measure function from this node.
1808
+ * Marks the node as dirty to trigger layout recalculation.
1809
+ */
1810
+ unsetMeasureFunc() {
1811
+ this._measureFunc = null;
1812
+ this.markDirty();
1813
+ }
1814
+ /**
1815
+ * Check if this node has a measure function.
1816
+ *
1817
+ * @returns True if a measure function is set
1818
+ */
1819
+ hasMeasureFunc() {
1820
+ return this._measureFunc !== null;
1821
+ }
1822
+ /**
1823
+ * Set a baseline function to determine where this node's text baseline is.
1824
+ * Used for ALIGN_BASELINE to align text across siblings with different heights.
1825
+ *
1826
+ * @param baselineFunc - Function that returns baseline offset from top given width and height
1827
+ * @example
1828
+ * ```typescript
1829
+ * textNode.setBaselineFunc((width, height) => {
1830
+ * // For a text node, baseline might be at 80% of height
1831
+ * return height * 0.8;
1832
+ * });
1833
+ * ```
1834
+ */
1835
+ setBaselineFunc(baselineFunc) {
1836
+ this._baselineFunc = baselineFunc;
1837
+ this.markDirty();
1838
+ }
1839
+ /**
1840
+ * Remove the baseline function from this node.
1841
+ * Marks the node as dirty to trigger layout recalculation.
1842
+ */
1843
+ unsetBaselineFunc() {
1844
+ this._baselineFunc = null;
1845
+ this.markDirty();
1846
+ }
1847
+ /**
1848
+ * Check if this node has a baseline function.
1849
+ *
1850
+ * @returns True if a baseline function is set
1851
+ */
1852
+ hasBaselineFunc() {
1853
+ return this._baselineFunc !== null;
1854
+ }
1855
+ /**
1856
+ * Call the measure function with caching.
1857
+ * Uses a 4-entry numeric cache for fast lookup without allocations.
1858
+ * Cache is cleared when markDirty() is called.
1859
+ *
1860
+ * @returns Measured dimensions or null if no measure function
1861
+ */
1862
+ cachedMeasure(w, wm, h, hm) {
1863
+ if (!this._measureFunc) return null;
1864
+ Node.measureCalls++;
1865
+ const m0 = this._m0;
1866
+ if (m0 && m0.w === w && m0.wm === wm && m0.h === h && m0.hm === hm) {
1867
+ Node.measureCacheHits++;
1868
+ this._measureResult.width = m0.rw;
1869
+ this._measureResult.height = m0.rh;
1870
+ getTrace()?.measureCacheHit(0, w, h, m0.rw, m0.rh);
1871
+ return this._measureResult;
1872
+ }
1873
+ const m1 = this._m1;
1874
+ if (m1 && m1.w === w && m1.wm === wm && m1.h === h && m1.hm === hm) {
1875
+ Node.measureCacheHits++;
1876
+ this._measureResult.width = m1.rw;
1877
+ this._measureResult.height = m1.rh;
1878
+ getTrace()?.measureCacheHit(0, w, h, m1.rw, m1.rh);
1879
+ return this._measureResult;
1880
+ }
1881
+ const m2 = this._m2;
1882
+ if (m2 && m2.w === w && m2.wm === wm && m2.h === h && m2.hm === hm) {
1883
+ Node.measureCacheHits++;
1884
+ this._measureResult.width = m2.rw;
1885
+ this._measureResult.height = m2.rh;
1886
+ getTrace()?.measureCacheHit(0, w, h, m2.rw, m2.rh);
1887
+ return this._measureResult;
1888
+ }
1889
+ const m3 = this._m3;
1890
+ if (m3 && m3.w === w && m3.wm === wm && m3.h === h && m3.hm === hm) {
1891
+ Node.measureCacheHits++;
1892
+ this._measureResult.width = m3.rw;
1893
+ this._measureResult.height = m3.rh;
1894
+ getTrace()?.measureCacheHit(0, w, h, m3.rw, m3.rh);
1895
+ return this._measureResult;
1896
+ }
1897
+ getTrace()?.measureCacheMiss(0, w, h);
1898
+ const result = this._measureFunc(w, wm, h, hm);
1899
+ if (this._m2) {
1900
+ if (!this._m3) this._m3 = {
1901
+ w: 0,
1902
+ wm: 0,
1903
+ h: 0,
1904
+ hm: 0,
1905
+ rw: 0,
1906
+ rh: 0
1907
+ };
1908
+ this._m3.w = this._m2.w;
1909
+ this._m3.wm = this._m2.wm;
1910
+ this._m3.h = this._m2.h;
1911
+ this._m3.hm = this._m2.hm;
1912
+ this._m3.rw = this._m2.rw;
1913
+ this._m3.rh = this._m2.rh;
1914
+ }
1915
+ if (this._m1) {
1916
+ if (!this._m2) this._m2 = {
1917
+ w: 0,
1918
+ wm: 0,
1919
+ h: 0,
1920
+ hm: 0,
1921
+ rw: 0,
1922
+ rh: 0
1923
+ };
1924
+ this._m2.w = this._m1.w;
1925
+ this._m2.wm = this._m1.wm;
1926
+ this._m2.h = this._m1.h;
1927
+ this._m2.hm = this._m1.hm;
1928
+ this._m2.rw = this._m1.rw;
1929
+ this._m2.rh = this._m1.rh;
1930
+ }
1931
+ if (this._m0) {
1932
+ if (!this._m1) this._m1 = {
1933
+ w: 0,
1934
+ wm: 0,
1935
+ h: 0,
1936
+ hm: 0,
1937
+ rw: 0,
1938
+ rh: 0
1939
+ };
1940
+ this._m1.w = this._m0.w;
1941
+ this._m1.wm = this._m0.wm;
1942
+ this._m1.h = this._m0.h;
1943
+ this._m1.hm = this._m0.hm;
1944
+ this._m1.rw = this._m0.rw;
1945
+ this._m1.rh = this._m0.rh;
1946
+ }
1947
+ if (!this._m0) this._m0 = {
1948
+ w: 0,
1949
+ wm: 0,
1950
+ h: 0,
1951
+ hm: 0,
1952
+ rw: 0,
1953
+ rh: 0
1954
+ };
1955
+ this._m0.w = w;
1956
+ this._m0.wm = wm;
1957
+ this._m0.h = h;
1958
+ this._m0.hm = hm;
1959
+ this._m0.rw = result.width;
1960
+ this._m0.rh = result.height;
1961
+ this._measureResult.width = result.width;
1962
+ this._measureResult.height = result.height;
1963
+ return this._measureResult;
1964
+ }
1965
+ /**
1966
+ * Check layout cache for a previously computed size with same available dimensions.
1967
+ * Returns cached (width, height) or null if not found.
1968
+ *
1969
+ * NaN dimensions are handled specially via Object.is (NaN === NaN is false, but Object.is(NaN, NaN) is true).
1970
+ */
1971
+ getCachedLayout(availW, availH) {
1972
+ if (this._isDirty) return null;
1973
+ const lc0 = this._lc0;
1974
+ if (lc0 && Object.is(lc0.availW, availW) && Object.is(lc0.availH, availH)) {
1975
+ this._layoutResult.width = lc0.computedW;
1976
+ this._layoutResult.height = lc0.computedH;
1977
+ return this._layoutResult;
1978
+ }
1979
+ const lc1 = this._lc1;
1980
+ if (lc1 && Object.is(lc1.availW, availW) && Object.is(lc1.availH, availH)) {
1981
+ this._layoutResult.width = lc1.computedW;
1982
+ this._layoutResult.height = lc1.computedH;
1983
+ return this._layoutResult;
1984
+ }
1985
+ return null;
1986
+ }
1987
+ /**
1988
+ * Cache a computed layout result for the given available dimensions.
1989
+ * Zero-allocation: lazily allocates cache entries once, then reuses.
1990
+ */
1991
+ setCachedLayout(availW, availH, computedW, computedH) {
1992
+ if (this._lc0) {
1993
+ if (!this._lc1) this._lc1 = {
1994
+ availW: NaN,
1995
+ availH: NaN,
1996
+ computedW: 0,
1997
+ computedH: 0
1998
+ };
1999
+ this._lc1.availW = this._lc0.availW;
2000
+ this._lc1.availH = this._lc0.availH;
2001
+ this._lc1.computedW = this._lc0.computedW;
2002
+ this._lc1.computedH = this._lc0.computedH;
2003
+ }
2004
+ if (!this._lc0) this._lc0 = {
2005
+ availW: 0,
2006
+ availH: 0,
2007
+ computedW: 0,
2008
+ computedH: 0
2009
+ };
2010
+ this._lc0.availW = availW;
2011
+ this._lc0.availH = availH;
2012
+ this._lc0.computedW = computedW;
2013
+ this._lc0.computedH = computedH;
2014
+ }
2015
+ /**
2016
+ * Clear layout cache for this node and all descendants.
2017
+ * Called at the start of each calculateLayout pass.
2018
+ * Zero-allocation: invalidates entries (availW = NaN) rather than deallocating.
2019
+ * Uses iterative traversal to avoid stack overflow on deep trees.
2020
+ */
2021
+ resetLayoutCache() {
2022
+ traversalStack.length = 0;
2023
+ traversalStack.push(this);
2024
+ while (traversalStack.length > 0) {
2025
+ const node = traversalStack.pop();
2026
+ if (node._lc0) node._lc0.availW = -1;
2027
+ if (node._lc1) node._lc1.availW = -1;
2028
+ for (const child of node._children) traversalStack.push(child);
2029
+ }
2030
+ }
2031
+ /**
2032
+ * Check if this node needs layout recalculation.
2033
+ *
2034
+ * @returns True if the node is dirty and needs layout
2035
+ */
2036
+ isDirty() {
2037
+ return this._isDirty;
2038
+ }
2039
+ /**
2040
+ * Mark this node and all ancestors as dirty.
2041
+ * A dirty node needs layout recalculation.
2042
+ * This is automatically called by all style setters and tree operations.
2043
+ * Uses iterative approach to avoid stack overflow on deep trees.
2044
+ */
2045
+ markDirty() {
2046
+ let current = this;
2047
+ while (current !== null) {
2048
+ current._m0 = current._m1 = current._m2 = current._m3 = void 0;
2049
+ current._lc0 = current._lc1 = void 0;
2050
+ if (current._isDirty) break;
2051
+ current._isDirty = true;
2052
+ current._flex.layoutValid = false;
2053
+ current = current._parent;
2054
+ }
2055
+ }
2056
+ /**
2057
+ * Check if this node has new layout results since the last check.
2058
+ *
2059
+ * @returns True if layout was recalculated since the last call to markLayoutSeen
2060
+ */
2061
+ hasNewLayout() {
2062
+ return this._hasNewLayout;
2063
+ }
2064
+ /**
2065
+ * Mark that the current layout has been seen/processed.
2066
+ * Clears the hasNewLayout flag.
2067
+ */
2068
+ markLayoutSeen() {
2069
+ this._hasNewLayout = false;
2070
+ }
2071
+ /**
2072
+ * Calculate layout for this node and all descendants.
2073
+ * This runs the flexbox layout algorithm to compute positions and sizes.
2074
+ * Only recalculates if the node is marked as dirty.
2075
+ *
2076
+ * @param width - Available width for layout
2077
+ * @param height - Available height for layout
2078
+ * @param _direction - Text direction (LTR or RTL), defaults to LTR
2079
+ * @example
2080
+ * ```typescript
2081
+ * const root = Node.create();
2082
+ * root.setFlexDirection(FLEX_DIRECTION_ROW);
2083
+ * root.setWidth(100);
2084
+ * root.setHeight(50);
2085
+ *
2086
+ * const child = Node.create();
2087
+ * child.setFlexGrow(1);
2088
+ * root.insertChild(child, 0);
2089
+ *
2090
+ * root.calculateLayout(100, 50, DIRECTION_LTR);
2091
+ *
2092
+ * // Now you can read computed layout
2093
+ * console.log(child.getComputedWidth());
2094
+ * ```
2095
+ */
2096
+ calculateLayout(width, height, direction = 1) {
2097
+ const availableWidth = width ?? NaN;
2098
+ const availableHeight = height ?? NaN;
2099
+ if (!this._isDirty && Object.is(this._lastCalcW, availableWidth) && Object.is(this._lastCalcH, availableHeight) && this._lastCalcDir === direction) {
2100
+ log.debug?.("layout skip (not dirty, constraints unchanged)");
2101
+ return;
2102
+ }
2103
+ this._lastCalcW = availableWidth;
2104
+ this._lastCalcH = availableHeight;
2105
+ this._lastCalcDir = direction;
2106
+ const start = log.debug ? Date.now() : 0;
2107
+ const nodeCount = log.debug ? countNodes(this) : 0;
2108
+ Node.resetMeasureStats();
2109
+ computeLayout(this, availableWidth, availableHeight, direction);
2110
+ this._isDirty = false;
2111
+ this._hasNewLayout = true;
2112
+ markSubtreeLayoutSeen(this);
2113
+ log.debug?.("layout: %dx%d, %d nodes in %dms (measure: calls=%d hits=%d)", width, height, nodeCount, Date.now() - start, Node.measureCalls, Node.measureCacheHits);
2114
+ }
2115
+ /**
2116
+ * Get the computed left position after layout.
2117
+ *
2118
+ * @returns The left position in points
2119
+ */
2120
+ getComputedLeft() {
2121
+ return this._layout.left;
2122
+ }
2123
+ /**
2124
+ * Get the computed top position after layout.
2125
+ *
2126
+ * @returns The top position in points
2127
+ */
2128
+ getComputedTop() {
2129
+ return this._layout.top;
2130
+ }
2131
+ /**
2132
+ * Get the computed width after layout.
2133
+ *
2134
+ * @returns The width in points
2135
+ */
2136
+ getComputedWidth() {
2137
+ return this._layout.width;
2138
+ }
2139
+ /**
2140
+ * Get the computed height after layout.
2141
+ *
2142
+ * @returns The height in points
2143
+ */
2144
+ getComputedHeight() {
2145
+ return this._layout.height;
2146
+ }
2147
+ /**
2148
+ * Get the computed right edge position after layout (left + width).
2149
+ *
2150
+ * @returns The right edge position in points
2151
+ */
2152
+ getComputedRight() {
2153
+ return this._layout.left + this._layout.width;
2154
+ }
2155
+ /**
2156
+ * Get the computed bottom edge position after layout (top + height).
2157
+ *
2158
+ * @returns The bottom edge position in points
2159
+ */
2160
+ getComputedBottom() {
2161
+ return this._layout.top + this._layout.height;
2162
+ }
2163
+ /**
2164
+ * Get the computed padding for a specific edge after layout.
2165
+ * Returns the resolved padding value (percentage and logical edges resolved).
2166
+ *
2167
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2168
+ * @returns Padding value in points
2169
+ */
2170
+ getComputedPadding(edge) {
2171
+ return getEdgeValue(this._style.padding, edge).value;
2172
+ }
2173
+ /**
2174
+ * Get the computed margin for a specific edge after layout.
2175
+ * Returns the resolved margin value (percentage and logical edges resolved).
2176
+ *
2177
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2178
+ * @returns Margin value in points
2179
+ */
2180
+ getComputedMargin(edge) {
2181
+ return getEdgeValue(this._style.margin, edge).value;
2182
+ }
2183
+ /**
2184
+ * Get the computed border width for a specific edge after layout.
2185
+ *
2186
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2187
+ * @returns Border width in points
2188
+ */
2189
+ getComputedBorder(edge) {
2190
+ return getEdgeBorderValue(this._style.border, edge);
2191
+ }
2192
+ get children() {
2193
+ return this._children;
2194
+ }
2195
+ get style() {
2196
+ return this._style;
2197
+ }
2198
+ get layout() {
2199
+ return this._layout;
2200
+ }
2201
+ get measureFunc() {
2202
+ return this._measureFunc;
2203
+ }
2204
+ get baselineFunc() {
2205
+ return this._baselineFunc;
2206
+ }
2207
+ get flex() {
2208
+ return this._flex;
2209
+ }
2210
+ /**
2211
+ * Set the width to a fixed value in points.
2212
+ *
2213
+ * @param value - Width in points
2214
+ */
2215
+ setWidth(value) {
2216
+ if (Number.isNaN(value)) this._style.width = {
2217
+ value: 0,
2218
+ unit: 3
2219
+ };
2220
+ else this._style.width = {
2221
+ value,
2222
+ unit: 1
2223
+ };
2224
+ this.markDirty();
2225
+ }
2226
+ /**
2227
+ * Set the width as a percentage of the parent's width.
2228
+ *
2229
+ * @param value - Width as a percentage (0-100)
2230
+ */
2231
+ setWidthPercent(value) {
2232
+ this._style.width = {
2233
+ value,
2234
+ unit: 2
2235
+ };
2236
+ this.markDirty();
2237
+ }
2238
+ /**
2239
+ * Set the width to auto (determined by layout algorithm).
2240
+ */
2241
+ setWidthAuto() {
2242
+ this._style.width = {
2243
+ value: 0,
2244
+ unit: 3
2245
+ };
2246
+ this.markDirty();
2247
+ }
2248
+ /**
2249
+ * Set the width to fit-content mode.
2250
+ *
2251
+ * CSS fit-content = min(max-content, max(min-content, available-width)).
2252
+ * For terminals: min(max-content, available-width) since min-content
2253
+ * floor is rarely relevant.
2254
+ *
2255
+ * The layout algorithm measures unconstrained content width (max-content),
2256
+ * then clamps to the available width from the parent.
2257
+ */
2258
+ setWidthFitContent() {
2259
+ this._style.width = {
2260
+ value: 0,
2261
+ unit: 4
2262
+ };
2263
+ this.markDirty();
2264
+ }
2265
+ /**
2266
+ * Set the width to snug-content mode.
2267
+ *
2268
+ * Like fit-content but signals that the consumer wants the tightest
2269
+ * possible width (binary-search shrinkwrap). The layout engine treats
2270
+ * this identically to fit-content for sizing; the consuming framework
2271
+ * (e.g., silvery) can further tighten via its own binary search.
2272
+ */
2273
+ setWidthSnugContent() {
2274
+ this._style.width = {
2275
+ value: 0,
2276
+ unit: 5
2277
+ };
2278
+ this.markDirty();
2279
+ }
2280
+ /**
2281
+ * Set the height to a fixed value in points.
2282
+ *
2283
+ * @param value - Height in points
2284
+ */
2285
+ setHeight(value) {
2286
+ if (Number.isNaN(value)) this._style.height = {
2287
+ value: 0,
2288
+ unit: 3
2289
+ };
2290
+ else this._style.height = {
2291
+ value,
2292
+ unit: 1
2293
+ };
2294
+ this.markDirty();
2295
+ }
2296
+ /**
2297
+ * Set the height as a percentage of the parent's height.
2298
+ *
2299
+ * @param value - Height as a percentage (0-100)
2300
+ */
2301
+ setHeightPercent(value) {
2302
+ this._style.height = {
2303
+ value,
2304
+ unit: 2
2305
+ };
2306
+ this.markDirty();
2307
+ }
2308
+ /**
2309
+ * Set the height to auto (determined by layout algorithm).
2310
+ */
2311
+ setHeightAuto() {
2312
+ this._style.height = {
2313
+ value: 0,
2314
+ unit: 3
2315
+ };
2316
+ this.markDirty();
2317
+ }
2318
+ /**
2319
+ * Set the minimum width in points.
2320
+ *
2321
+ * @param value - Minimum width in points
2322
+ */
2323
+ setMinWidth(value) {
2324
+ this._style.minWidth = {
2325
+ value,
2326
+ unit: 1
2327
+ };
2328
+ this.markDirty();
2329
+ }
2330
+ /**
2331
+ * Set the minimum width as a percentage of the parent's width.
2332
+ *
2333
+ * @param value - Minimum width as a percentage (0-100)
2334
+ */
2335
+ setMinWidthPercent(value) {
2336
+ this._style.minWidth = {
2337
+ value,
2338
+ unit: 2
2339
+ };
2340
+ this.markDirty();
2341
+ }
2342
+ /**
2343
+ * Set the minimum height in points.
2344
+ *
2345
+ * @param value - Minimum height in points
2346
+ */
2347
+ setMinHeight(value) {
2348
+ this._style.minHeight = {
2349
+ value,
2350
+ unit: 1
2351
+ };
2352
+ this.markDirty();
2353
+ }
2354
+ /**
2355
+ * Set the minimum height as a percentage of the parent's height.
2356
+ *
2357
+ * @param value - Minimum height as a percentage (0-100)
2358
+ */
2359
+ setMinHeightPercent(value) {
2360
+ this._style.minHeight = {
2361
+ value,
2362
+ unit: 2
2363
+ };
2364
+ this.markDirty();
2365
+ }
2366
+ /**
2367
+ * Set the maximum width in points.
2368
+ *
2369
+ * @param value - Maximum width in points
2370
+ */
2371
+ setMaxWidth(value) {
2372
+ this._style.maxWidth = {
2373
+ value,
2374
+ unit: 1
2375
+ };
2376
+ this.markDirty();
2377
+ }
2378
+ /**
2379
+ * Set the maximum width as a percentage of the parent's width.
2380
+ *
2381
+ * @param value - Maximum width as a percentage (0-100)
2382
+ */
2383
+ setMaxWidthPercent(value) {
2384
+ this._style.maxWidth = {
2385
+ value,
2386
+ unit: 2
2387
+ };
2388
+ this.markDirty();
2389
+ }
2390
+ /**
2391
+ * Set the maximum height in points.
2392
+ *
2393
+ * @param value - Maximum height in points
2394
+ */
2395
+ setMaxHeight(value) {
2396
+ this._style.maxHeight = {
2397
+ value,
2398
+ unit: 1
2399
+ };
2400
+ this.markDirty();
2401
+ }
2402
+ /**
2403
+ * Set the maximum height as a percentage of the parent's height.
2404
+ *
2405
+ * @param value - Maximum height as a percentage (0-100)
2406
+ */
2407
+ setMaxHeightPercent(value) {
2408
+ this._style.maxHeight = {
2409
+ value,
2410
+ unit: 2
2411
+ };
2412
+ this.markDirty();
2413
+ }
2414
+ /**
2415
+ * Set the aspect ratio of the node.
2416
+ * When set, the node's width/height relationship is constrained.
2417
+ * If width is defined, height = width / aspectRatio.
2418
+ * If height is defined, width = height * aspectRatio.
2419
+ *
2420
+ * @param value - Aspect ratio (width/height). Use NaN to unset.
2421
+ */
2422
+ setAspectRatio(value) {
2423
+ this._style.aspectRatio = value;
2424
+ this.markDirty();
2425
+ }
2426
+ /**
2427
+ * Set the flex grow factor.
2428
+ * Determines how much the node will grow relative to siblings when there is extra space.
2429
+ *
2430
+ * @param value - Flex grow factor (typically 0 or 1+)
2431
+ * @example
2432
+ * ```typescript
2433
+ * const child = Node.create();
2434
+ * child.setFlexGrow(1); // Will grow to fill available space
2435
+ * ```
2436
+ */
2437
+ setFlexGrow(value) {
2438
+ this._style.flexGrow = value;
2439
+ this.markDirty();
2440
+ }
2441
+ /**
2442
+ * Set the flex shrink factor.
2443
+ * Determines how much the node will shrink relative to siblings when there is insufficient space.
2444
+ *
2445
+ * @param value - Flex shrink factor (default is 1)
2446
+ */
2447
+ setFlexShrink(value) {
2448
+ this._style.flexShrink = value;
2449
+ this.markDirty();
2450
+ }
2451
+ /**
2452
+ * Set the flex basis to a fixed value in points.
2453
+ * The initial size of the node before flex grow/shrink is applied.
2454
+ *
2455
+ * @param value - Flex basis in points
2456
+ */
2457
+ setFlexBasis(value) {
2458
+ this._style.flexBasis = {
2459
+ value,
2460
+ unit: 1
2461
+ };
2462
+ this.markDirty();
2463
+ }
2464
+ /**
2465
+ * Set the flex basis as a percentage of the parent's size.
2466
+ *
2467
+ * @param value - Flex basis as a percentage (0-100)
2468
+ */
2469
+ setFlexBasisPercent(value) {
2470
+ this._style.flexBasis = {
2471
+ value,
2472
+ unit: 2
2473
+ };
2474
+ this.markDirty();
2475
+ }
2476
+ /**
2477
+ * Set the flex basis to auto (based on the node's width/height).
2478
+ */
2479
+ setFlexBasisAuto() {
2480
+ this._style.flexBasis = {
2481
+ value: 0,
2482
+ unit: 3
2483
+ };
2484
+ this.markDirty();
2485
+ }
2486
+ /**
2487
+ * Set the flex direction (main axis direction).
2488
+ *
2489
+ * @param direction - FLEX_DIRECTION_ROW, FLEX_DIRECTION_COLUMN, FLEX_DIRECTION_ROW_REVERSE, or FLEX_DIRECTION_COLUMN_REVERSE
2490
+ * @example
2491
+ * ```typescript
2492
+ * const container = Node.create();
2493
+ * container.setFlexDirection(FLEX_DIRECTION_ROW); // Lay out children horizontally
2494
+ * ```
2495
+ */
2496
+ setFlexDirection(direction) {
2497
+ this._style.flexDirection = direction;
2498
+ this.markDirty();
2499
+ }
2500
+ /**
2501
+ * Set the flex wrap behavior.
2502
+ *
2503
+ * @param wrap - WRAP_NO_WRAP, WRAP_WRAP, or WRAP_WRAP_REVERSE
2504
+ */
2505
+ setFlexWrap(wrap) {
2506
+ this._style.flexWrap = wrap;
2507
+ this.markDirty();
2508
+ }
2509
+ /**
2510
+ * Set how children are aligned along the cross axis.
2511
+ *
2512
+ * @param align - ALIGN_FLEX_START, ALIGN_CENTER, ALIGN_FLEX_END, ALIGN_STRETCH, or ALIGN_BASELINE
2513
+ * @example
2514
+ * ```typescript
2515
+ * const container = Node.create();
2516
+ * container.setFlexDirection(FLEX_DIRECTION_ROW);
2517
+ * container.setAlignItems(ALIGN_CENTER); // Center children vertically
2518
+ * ```
2519
+ */
2520
+ setAlignItems(align) {
2521
+ this._style.alignItems = align;
2522
+ this.markDirty();
2523
+ }
2524
+ /**
2525
+ * Set how this node is aligned along the parent's cross axis.
2526
+ * Overrides the parent's alignItems for this specific child.
2527
+ *
2528
+ * @param align - ALIGN_AUTO, ALIGN_FLEX_START, ALIGN_CENTER, ALIGN_FLEX_END, ALIGN_STRETCH, or ALIGN_BASELINE
2529
+ */
2530
+ setAlignSelf(align) {
2531
+ this._style.alignSelf = align;
2532
+ this.markDirty();
2533
+ }
2534
+ /**
2535
+ * Set how lines are aligned in a multi-line flex container.
2536
+ * Only affects containers with wrap enabled and multiple lines.
2537
+ *
2538
+ * @param align - ALIGN_FLEX_START, ALIGN_CENTER, ALIGN_FLEX_END, ALIGN_STRETCH, ALIGN_SPACE_BETWEEN, or ALIGN_SPACE_AROUND
2539
+ */
2540
+ setAlignContent(align) {
2541
+ this._style.alignContent = align;
2542
+ this.markDirty();
2543
+ }
2544
+ /**
2545
+ * Set how children are distributed along the main axis.
2546
+ *
2547
+ * @param justify - JUSTIFY_FLEX_START, JUSTIFY_CENTER, JUSTIFY_FLEX_END, JUSTIFY_SPACE_BETWEEN, JUSTIFY_SPACE_AROUND, or JUSTIFY_SPACE_EVENLY
2548
+ * @example
2549
+ * ```typescript
2550
+ * const container = Node.create();
2551
+ * container.setJustifyContent(JUSTIFY_SPACE_BETWEEN); // Space children evenly with edges at start/end
2552
+ * ```
2553
+ */
2554
+ setJustifyContent(justify) {
2555
+ this._style.justifyContent = justify;
2556
+ this.markDirty();
2557
+ }
2558
+ /**
2559
+ * Set padding for one or more edges.
2560
+ *
2561
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2562
+ * @param value - Padding in points
2563
+ * @example
2564
+ * ```typescript
2565
+ * node.setPadding(EDGE_ALL, 10); // Set 10pt padding on all edges
2566
+ * node.setPadding(EDGE_HORIZONTAL, 5); // Set 5pt padding on left and right
2567
+ * ```
2568
+ */
2569
+ setPadding(edge, value) {
2570
+ setEdgeValue(this._style.padding, edge, value, 1);
2571
+ this.markDirty();
2572
+ }
2573
+ /**
2574
+ * Set padding as a percentage of the parent's width.
2575
+ * Per CSS spec, percentage padding always resolves against the containing block's width.
2576
+ *
2577
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2578
+ * @param value - Padding as a percentage (0-100)
2579
+ */
2580
+ setPaddingPercent(edge, value) {
2581
+ setEdgeValue(this._style.padding, edge, value, 2);
2582
+ this.markDirty();
2583
+ }
2584
+ /**
2585
+ * Set margin for one or more edges.
2586
+ *
2587
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2588
+ * @param value - Margin in points
2589
+ * @example
2590
+ * ```typescript
2591
+ * node.setMargin(EDGE_ALL, 5); // Set 5pt margin on all edges
2592
+ * node.setMargin(EDGE_TOP, 10); // Set 10pt margin on top only
2593
+ * ```
2594
+ */
2595
+ setMargin(edge, value) {
2596
+ setEdgeValue(this._style.margin, edge, value, 1);
2597
+ this.markDirty();
2598
+ }
2599
+ /**
2600
+ * Set margin as a percentage of the parent's size.
2601
+ *
2602
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2603
+ * @param value - Margin as a percentage (0-100)
2604
+ */
2605
+ setMarginPercent(edge, value) {
2606
+ setEdgeValue(this._style.margin, edge, value, 2);
2607
+ this.markDirty();
2608
+ }
2609
+ /**
2610
+ * Set margin to auto (for centering items with margin: auto).
2611
+ *
2612
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2613
+ */
2614
+ setMarginAuto(edge) {
2615
+ setEdgeValue(this._style.margin, edge, 0, 3);
2616
+ this.markDirty();
2617
+ }
2618
+ /**
2619
+ * Set border width for one or more edges.
2620
+ *
2621
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2622
+ * @param value - Border width in points
2623
+ */
2624
+ setBorder(edge, value) {
2625
+ setEdgeBorder(this._style.border, edge, value);
2626
+ this.markDirty();
2627
+ }
2628
+ /**
2629
+ * Set gap between flex items.
2630
+ *
2631
+ * @param gutter - GUTTER_COLUMN (horizontal gap), GUTTER_ROW (vertical gap), or GUTTER_ALL (both)
2632
+ * @param value - Gap size in points
2633
+ * @example
2634
+ * ```typescript
2635
+ * container.setGap(GUTTER_ALL, 8); // Set 8pt gap between all items
2636
+ * container.setGap(GUTTER_COLUMN, 10); // Set 10pt horizontal gap only
2637
+ * ```
2638
+ */
2639
+ setGap(gutter, value) {
2640
+ if (gutter === 0) this._style.gap[0] = value;
2641
+ else if (gutter === 1) this._style.gap[1] = value;
2642
+ else if (gutter === 2) {
2643
+ this._style.gap[0] = value;
2644
+ this._style.gap[1] = value;
2645
+ }
2646
+ this.markDirty();
2647
+ }
2648
+ /**
2649
+ * Set the position type.
2650
+ *
2651
+ * @param positionType - POSITION_TYPE_STATIC, POSITION_TYPE_RELATIVE, or POSITION_TYPE_ABSOLUTE
2652
+ * @example
2653
+ * ```typescript
2654
+ * node.setPositionType(POSITION_TYPE_ABSOLUTE);
2655
+ * node.setPosition(EDGE_LEFT, 10);
2656
+ * node.setPosition(EDGE_TOP, 20);
2657
+ * ```
2658
+ */
2659
+ setPositionType(positionType) {
2660
+ this._style.positionType = positionType;
2661
+ this.markDirty();
2662
+ }
2663
+ /**
2664
+ * Set position offset for one or more edges.
2665
+ * Only applies when position type is ABSOLUTE or RELATIVE.
2666
+ *
2667
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2668
+ * @param value - Position offset in points
2669
+ */
2670
+ setPosition(edge, value) {
2671
+ if (Number.isNaN(value)) setEdgeValue(this._style.position, edge, 0, 0);
2672
+ else setEdgeValue(this._style.position, edge, value, 1);
2673
+ this.markDirty();
2674
+ }
2675
+ /**
2676
+ * Set position offset as a percentage.
2677
+ *
2678
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_HORIZONTAL, EDGE_VERTICAL, or EDGE_ALL
2679
+ * @param value - Position offset as a percentage of parent's corresponding dimension
2680
+ */
2681
+ setPositionPercent(edge, value) {
2682
+ setEdgeValue(this._style.position, edge, value, 2);
2683
+ this.markDirty();
2684
+ }
2685
+ /**
2686
+ * Set the display type.
2687
+ *
2688
+ * @param display - DISPLAY_FLEX or DISPLAY_NONE
2689
+ */
2690
+ setDisplay(display) {
2691
+ this._style.display = display;
2692
+ this.markDirty();
2693
+ }
2694
+ /**
2695
+ * Set the overflow behavior.
2696
+ *
2697
+ * @param overflow - OVERFLOW_VISIBLE, OVERFLOW_HIDDEN, or OVERFLOW_SCROLL
2698
+ */
2699
+ setOverflow(overflow) {
2700
+ this._style.overflow = overflow;
2701
+ this.markDirty();
2702
+ }
2703
+ /**
2704
+ * Get the width style value.
2705
+ *
2706
+ * @returns Width value with unit (points, percent, or auto)
2707
+ */
2708
+ getWidth() {
2709
+ return this._style.width;
2710
+ }
2711
+ /**
2712
+ * Get the height style value.
2713
+ *
2714
+ * @returns Height value with unit (points, percent, or auto)
2715
+ */
2716
+ getHeight() {
2717
+ return this._style.height;
2718
+ }
2719
+ /**
2720
+ * Get the minimum width style value.
2721
+ *
2722
+ * @returns Minimum width value with unit
2723
+ */
2724
+ getMinWidth() {
2725
+ return this._style.minWidth;
2726
+ }
2727
+ /**
2728
+ * Get the minimum height style value.
2729
+ *
2730
+ * @returns Minimum height value with unit
2731
+ */
2732
+ getMinHeight() {
2733
+ return this._style.minHeight;
2734
+ }
2735
+ /**
2736
+ * Get the maximum width style value.
2737
+ *
2738
+ * @returns Maximum width value with unit
2739
+ */
2740
+ getMaxWidth() {
2741
+ return this._style.maxWidth;
2742
+ }
2743
+ /**
2744
+ * Get the maximum height style value.
2745
+ *
2746
+ * @returns Maximum height value with unit
2747
+ */
2748
+ getMaxHeight() {
2749
+ return this._style.maxHeight;
2750
+ }
2751
+ /**
2752
+ * Get the aspect ratio.
2753
+ *
2754
+ * @returns Aspect ratio value (NaN if not set)
2755
+ */
2756
+ getAspectRatio() {
2757
+ return this._style.aspectRatio;
2758
+ }
2759
+ /**
2760
+ * Get the flex grow factor.
2761
+ *
2762
+ * @returns Flex grow value
2763
+ */
2764
+ getFlexGrow() {
2765
+ return this._style.flexGrow;
2766
+ }
2767
+ /**
2768
+ * Get the flex shrink factor.
2769
+ *
2770
+ * @returns Flex shrink value
2771
+ */
2772
+ getFlexShrink() {
2773
+ return this._style.flexShrink;
2774
+ }
2775
+ /**
2776
+ * Get the flex basis style value.
2777
+ *
2778
+ * @returns Flex basis value with unit
2779
+ */
2780
+ getFlexBasis() {
2781
+ return this._style.flexBasis;
2782
+ }
2783
+ /**
2784
+ * Get the flex direction.
2785
+ *
2786
+ * @returns Flex direction constant
2787
+ */
2788
+ getFlexDirection() {
2789
+ return this._style.flexDirection;
2790
+ }
2791
+ /**
2792
+ * Get the flex wrap setting.
2793
+ *
2794
+ * @returns Flex wrap constant
2795
+ */
2796
+ getFlexWrap() {
2797
+ return this._style.flexWrap;
2798
+ }
2799
+ /**
2800
+ * Get the align items setting.
2801
+ *
2802
+ * @returns Align items constant
2803
+ */
2804
+ getAlignItems() {
2805
+ return this._style.alignItems;
2806
+ }
2807
+ /**
2808
+ * Get the align self setting.
2809
+ *
2810
+ * @returns Align self constant
2811
+ */
2812
+ getAlignSelf() {
2813
+ return this._style.alignSelf;
2814
+ }
2815
+ /**
2816
+ * Get the align content setting.
2817
+ *
2818
+ * @returns Align content constant
2819
+ */
2820
+ getAlignContent() {
2821
+ return this._style.alignContent;
2822
+ }
2823
+ /**
2824
+ * Get the justify content setting.
2825
+ *
2826
+ * @returns Justify content constant
2827
+ */
2828
+ getJustifyContent() {
2829
+ return this._style.justifyContent;
2830
+ }
2831
+ /**
2832
+ * Get the padding for a specific edge.
2833
+ *
2834
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2835
+ * @returns Padding value with unit
2836
+ */
2837
+ getPadding(edge) {
2838
+ return getEdgeValue(this._style.padding, edge);
2839
+ }
2840
+ /**
2841
+ * Get the margin for a specific edge.
2842
+ *
2843
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2844
+ * @returns Margin value with unit
2845
+ */
2846
+ getMargin(edge) {
2847
+ return getEdgeValue(this._style.margin, edge);
2848
+ }
2849
+ /**
2850
+ * Get the border width for a specific edge.
2851
+ *
2852
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2853
+ * @returns Border width in points
2854
+ */
2855
+ getBorder(edge) {
2856
+ return getEdgeBorderValue(this._style.border, edge);
2857
+ }
2858
+ /**
2859
+ * Get the position offset for a specific edge.
2860
+ *
2861
+ * @param edge - EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, or EDGE_BOTTOM
2862
+ * @returns Position value with unit
2863
+ */
2864
+ getPosition(edge) {
2865
+ return getEdgeValue(this._style.position, edge);
2866
+ }
2867
+ /**
2868
+ * Get the position type.
2869
+ *
2870
+ * @returns Position type constant
2871
+ */
2872
+ getPositionType() {
2873
+ return this._style.positionType;
2874
+ }
2875
+ /**
2876
+ * Get the display type.
2877
+ *
2878
+ * @returns Display constant
2879
+ */
2880
+ getDisplay() {
2881
+ return this._style.display;
2882
+ }
2883
+ /**
2884
+ * Get the overflow setting.
2885
+ *
2886
+ * @returns Overflow constant
2887
+ */
2888
+ getOverflow() {
2889
+ return this._style.overflow;
2890
+ }
2891
+ /**
2892
+ * Get the gap for column or row.
2893
+ *
2894
+ * @param gutter - GUTTER_COLUMN or GUTTER_ROW
2895
+ * @returns Gap size in points
2896
+ */
2897
+ getGap(gutter) {
2898
+ if (gutter === 0) return this._style.gap[0];
2899
+ else if (gutter === 1) return this._style.gap[1];
2900
+ return this._style.gap[0];
2901
+ }
2902
+ };
2903
+ //#endregion
2904
+ //#region src/monospace-measurer.ts
2905
+ const segmenter$1 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
2906
+ /**
2907
+ * Create a monospace text measurement service.
2908
+ *
2909
+ * @param charWidth - Width of each character cell (default: 1 for terminal grids)
2910
+ * @param charHeight - Height of each character cell (default: 1 for terminal grids)
2911
+ */
2912
+ function createMonospaceMeasurer(charWidth = 1, charHeight = 1) {
2913
+ return { prepare(input) {
2914
+ const { text } = input;
2915
+ let graphemeCount = 0;
2916
+ for (const _ of segmenter$1.segment(text)) graphemeCount++;
2917
+ const totalWidth = graphemeCount * charWidth;
2918
+ return {
2919
+ intrinsicSizes() {
2920
+ return {
2921
+ minContentWidth: totalWidth,
2922
+ maxContentWidth: totalWidth
2923
+ };
2924
+ },
2925
+ layout(constraints) {
2926
+ const width = constraints.maxWidth !== void 0 ? Math.min(totalWidth, constraints.maxWidth) : totalWidth;
2927
+ return {
2928
+ width,
2929
+ height: charHeight,
2930
+ lineCount: 1,
2931
+ firstBaseline: charHeight,
2932
+ lastBaseline: charHeight,
2933
+ truncated: width < totalWidth
2934
+ };
2935
+ }
2936
+ };
2937
+ } };
2938
+ }
2939
+ /**
2940
+ * Plugin: add monospace text measurement to the engine.
2941
+ *
2942
+ * @param charWidth - Width per character cell (default: 1)
2943
+ * @param charHeight - Height per character cell (default: 1)
2944
+ */
2945
+ function withMonospace(charWidth = 1, charHeight = 1) {
2946
+ return (engine) => {
2947
+ engine.textLayout = createMonospaceMeasurer(charWidth, charHeight);
2948
+ return engine;
2949
+ };
2950
+ }
2951
+ //#endregion
2952
+ //#region src/create-flexily.ts
2953
+ /**
2954
+ * Composable Flexily engine — createFlexily, createBareFlexily, pipe.
2955
+ *
2956
+ * FlexilyNode = Node + { setTextContent, getTextContent }.
2957
+ * No wrapper — text methods are mixed directly onto the Node instance.
2958
+ */
2959
+ const DEFAULT_TEXT_STYLE = {
2960
+ fontShorthand: "1ch monospace",
2961
+ fontFamily: "monospace",
2962
+ fontSize: 1,
2963
+ fontWeight: 400,
2964
+ fontStyle: "normal",
2965
+ lineHeight: 1
2966
+ };
2967
+ /** Mix text content methods onto a Node. Returns the same node typed as FlexilyNode. */
2968
+ function mixTextContent(node, engine) {
2969
+ let textContent = null;
2970
+ let prepared = null;
2971
+ const origSetMeasure = node.setMeasureFunc.bind(node);
2972
+ const origUnsetMeasure = node.unsetMeasureFunc.bind(node);
2973
+ const flexNode = node;
2974
+ flexNode.setTextContent = function(text, style) {
2975
+ if (!engine.textLayout) throw new Error("No TextLayoutService. Add a text plugin (withMonospace, withPretext, withTestMeasurer).");
2976
+ textContent = text;
2977
+ const resolved = {
2978
+ ...DEFAULT_TEXT_STYLE,
2979
+ ...style
2980
+ };
2981
+ prepared = engine.textLayout.prepare({
2982
+ text,
2983
+ style: resolved
2984
+ });
2985
+ const p = prepared;
2986
+ origSetMeasure((width, widthMode) => {
2987
+ if (widthMode === 0) {
2988
+ const sizes = p.intrinsicSizes();
2989
+ const result = p.layout({ maxWidth: sizes.maxContentWidth });
2990
+ return {
2991
+ width: result.width,
2992
+ height: result.height
2993
+ };
2994
+ }
2995
+ const result = p.layout({ maxWidth: width });
2996
+ return {
2997
+ width: widthMode === 2 ? Math.min(width, result.width) : width,
2998
+ height: result.height
2999
+ };
3000
+ });
3001
+ };
3002
+ flexNode.getTextContent = function() {
3003
+ return textContent;
3004
+ };
3005
+ flexNode.setMeasureFunc = function(fn) {
3006
+ textContent = null;
3007
+ prepared = null;
3008
+ origSetMeasure(fn);
3009
+ };
3010
+ flexNode.unsetMeasureFunc = function() {
3011
+ textContent = null;
3012
+ prepared = null;
3013
+ origUnsetMeasure();
3014
+ };
3015
+ return flexNode;
3016
+ }
3017
+ /**
3018
+ * Create a bare Flexily engine — no plugins, just nodes and layout.
3019
+ * Add plugins via pipe() for text measurement.
3020
+ */
3021
+ function createBareFlexily() {
3022
+ const engine = {
3023
+ createNode() {
3024
+ return mixTextContent(Node.create(), engine);
3025
+ },
3026
+ calculateLayout(root, width, height, direction) {
3027
+ root.calculateLayout(width, height, direction ?? 1);
3028
+ }
3029
+ };
3030
+ return engine;
3031
+ }
3032
+ /**
3033
+ * Apply plugins to an engine, left to right.
3034
+ *
3035
+ * @example
3036
+ * ```typescript
3037
+ * const flex = pipe(createBareFlexily(), withMonospace(), withTestMeasurer())
3038
+ * ```
3039
+ */
3040
+ function pipe(engine, ...plugins) {
3041
+ let result = engine;
3042
+ for (const plugin of plugins) result = plugin(result);
3043
+ return result;
3044
+ }
3045
+ /**
3046
+ * Create a batteries-included Flexily engine.
3047
+ *
3048
+ * Includes monospace text measurement (1 char = charWidth units).
3049
+ * For terminal UIs, the default charWidth/charHeight of 1 maps to terminal cells.
3050
+ *
3051
+ * @example
3052
+ * ```typescript
3053
+ * import { createFlexily } from "flexily"
3054
+ * const flex = createFlexily()
3055
+ * const node = flex.createNode()
3056
+ * node.setTextContent("Hello world")
3057
+ * flex.calculateLayout(node, 80, 24)
3058
+ * ```
3059
+ */
3060
+ function createFlexily(options) {
3061
+ const engine = createBareFlexily();
3062
+ engine.textLayout = createMonospaceMeasurer(options?.charWidth ?? 1, options?.charHeight ?? 1);
3063
+ return engine;
3064
+ }
3065
+ //#endregion
3066
+ //#region src/test-measurer.ts
3067
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
3068
+ /** Measure a single grapheme cluster with the deterministic width table. */
3069
+ function graphemeWidth(grapheme, fontSize) {
3070
+ const cp = grapheme.codePointAt(0) ?? 0;
3071
+ if (cp >= 127744 && cp <= 129791 || cp >= 9728 && cp <= 10175 || cp >= 65024 && cp <= 65039 || cp === 8205 || cp >= 129280 && cp <= 129535 || grapheme.length > 2) return fontSize * 1.8;
3072
+ if (cp >= 19968 && cp <= 40959 || cp >= 12288 && cp <= 12351 || cp >= 12352 && cp <= 12447 || cp >= 12448 && cp <= 12543 || cp >= 44032 && cp <= 55215 || cp >= 65280 && cp <= 65519) return fontSize * 1;
3073
+ return fontSize * .8;
3074
+ }
3075
+ function segmentText(text, fontSize) {
3076
+ const segments = [];
3077
+ let current = "";
3078
+ let currentWidth = 0;
3079
+ let isWhitespace = false;
3080
+ for (const { segment } of segmenter.segment(text)) {
3081
+ const isSpace = segment === " " || segment === " ";
3082
+ if (segments.length === 0 && current === "") {
3083
+ isWhitespace = isSpace;
3084
+ current = segment;
3085
+ currentWidth = isSpace ? fontSize * .8 : graphemeWidth(segment, fontSize);
3086
+ continue;
3087
+ }
3088
+ if (isSpace !== isWhitespace) {
3089
+ if (current) segments.push({
3090
+ text: current,
3091
+ width: currentWidth,
3092
+ isWhitespace
3093
+ });
3094
+ current = segment;
3095
+ currentWidth = isSpace ? fontSize * .8 : graphemeWidth(segment, fontSize);
3096
+ isWhitespace = isSpace;
3097
+ } else {
3098
+ current += segment;
3099
+ currentWidth += isSpace ? fontSize * .8 : graphemeWidth(segment, fontSize);
3100
+ }
3101
+ }
3102
+ if (current) segments.push({
3103
+ text: current,
3104
+ width: currentWidth,
3105
+ isWhitespace
3106
+ });
3107
+ return segments;
3108
+ }
3109
+ function measureString(text, fontSize) {
3110
+ let width = 0;
3111
+ for (const { segment } of segmenter.segment(text)) width += graphemeWidth(segment, fontSize);
3112
+ return width;
3113
+ }
3114
+ /**
3115
+ * Create a deterministic text measurement service for testing.
3116
+ *
3117
+ * Uses fixed grapheme widths: Latin 0.8, CJK 1.0, emoji 1.8 (relative to fontSize).
3118
+ */
3119
+ function createTestMeasurer() {
3120
+ return { prepare(input) {
3121
+ const { text, style } = input;
3122
+ const fontSize = style.fontSize;
3123
+ const lineHeight = style.lineHeight || fontSize;
3124
+ const segments = segmentText(text, fontSize);
3125
+ let maxContentWidth = 0;
3126
+ let minContentWidth = 0;
3127
+ for (const seg of segments) {
3128
+ maxContentWidth += seg.width;
3129
+ if (!seg.isWhitespace && seg.width > minContentWidth) minContentWidth = seg.width;
3130
+ }
3131
+ return {
3132
+ intrinsicSizes() {
3133
+ return {
3134
+ minContentWidth,
3135
+ maxContentWidth
3136
+ };
3137
+ },
3138
+ layout(constraints, options) {
3139
+ const maxWidth = constraints.maxWidth ?? Infinity;
3140
+ const maxLines = constraints.maxLines ?? Infinity;
3141
+ const wrap = constraints.wrap ?? "normal";
3142
+ const includeLines = options?.includeLines ?? false;
3143
+ if (wrap === "none" || maxWidth >= maxContentWidth) {
3144
+ const truncated = maxLines < 1;
3145
+ return {
3146
+ width: constraints.shrinkWrap ? maxContentWidth : Math.min(maxWidth, maxContentWidth),
3147
+ height: truncated ? 0 : lineHeight,
3148
+ lineCount: truncated ? 0 : 1,
3149
+ firstBaseline: lineHeight,
3150
+ lastBaseline: lineHeight,
3151
+ truncated: maxWidth < maxContentWidth,
3152
+ lines: includeLines ? [{
3153
+ text,
3154
+ width: maxContentWidth,
3155
+ startIndex: 0,
3156
+ endIndex: text.length
3157
+ }] : void 0
3158
+ };
3159
+ }
3160
+ const lines = [];
3161
+ let lineWidth = 0;
3162
+ let lineText = "";
3163
+ let lineStart = 0;
3164
+ let charIndex = 0;
3165
+ for (const seg of segments) {
3166
+ if (seg.isWhitespace) {
3167
+ if (lineText) {
3168
+ lineWidth += seg.width;
3169
+ lineText += seg.text;
3170
+ }
3171
+ charIndex += seg.text.length;
3172
+ continue;
3173
+ }
3174
+ if (lineWidth + seg.width > maxWidth && lineText) {
3175
+ const trimmed = lineText.trimEnd();
3176
+ lines.push({
3177
+ text: trimmed,
3178
+ width: measureString(trimmed, fontSize),
3179
+ startIndex: lineStart,
3180
+ endIndex: charIndex
3181
+ });
3182
+ if (lines.length >= maxLines) break;
3183
+ lineText = seg.text;
3184
+ lineWidth = seg.width;
3185
+ lineStart = charIndex;
3186
+ } else {
3187
+ lineText += seg.text;
3188
+ lineWidth += seg.width;
3189
+ }
3190
+ charIndex += seg.text.length;
3191
+ }
3192
+ if (lineText && lines.length < maxLines) {
3193
+ const trimmed = lineText.trimEnd();
3194
+ lines.push({
3195
+ text: trimmed,
3196
+ width: measureString(trimmed, fontSize),
3197
+ startIndex: lineStart,
3198
+ endIndex: charIndex
3199
+ });
3200
+ }
3201
+ const lineCount = lines.length;
3202
+ const height = lineCount * lineHeight;
3203
+ const widestLine = lines.reduce((max, l) => Math.max(max, l.width), 0);
3204
+ return {
3205
+ width: constraints.shrinkWrap ? widestLine : Math.min(maxWidth, widestLine),
3206
+ height,
3207
+ lineCount,
3208
+ firstBaseline: lineHeight,
3209
+ lastBaseline: lineCount > 0 ? (lineCount - 1) * lineHeight + lineHeight : lineHeight,
3210
+ truncated: lines.length >= maxLines && charIndex < text.length,
3211
+ lines: includeLines ? lines : void 0
3212
+ };
3213
+ }
3214
+ };
3215
+ } };
3216
+ }
3217
+ /**
3218
+ * Plugin: add deterministic test text measurement to the engine.
3219
+ */
3220
+ function withTestMeasurer() {
3221
+ return (engine) => {
3222
+ engine.textLayout = createTestMeasurer();
3223
+ return engine;
3224
+ };
3225
+ }
3226
+ //#endregion
3227
+ //#region src/pretext-measurer.ts
3228
+ /**
3229
+ * Create a Pretext-based text measurement service.
3230
+ *
3231
+ * @param pretext - The pretext module (import from "@chenglou/pretext")
3232
+ */
3233
+ function createPretextMeasurer(pretext) {
3234
+ return { prepare(input) {
3235
+ const { text, style } = input;
3236
+ const pretextPrepared = pretext.prepare(text, style.fontShorthand);
3237
+ const lineHeight = style.lineHeight || style.fontSize;
3238
+ let cachedIntrinsic = null;
3239
+ return {
3240
+ intrinsicSizes() {
3241
+ if (cachedIntrinsic) return cachedIntrinsic;
3242
+ const unconstrained = pretextPrepared.layout(Infinity, lineHeight);
3243
+ cachedIntrinsic = {
3244
+ minContentWidth: pretextPrepared.layout(0, lineHeight).width,
3245
+ maxContentWidth: unconstrained.width
3246
+ };
3247
+ return cachedIntrinsic;
3248
+ },
3249
+ layout(constraints) {
3250
+ const maxWidth = constraints.maxWidth ?? Infinity;
3251
+ const result = pretextPrepared.layout(maxWidth, lineHeight);
3252
+ const lineCount = result.lines?.length ?? 1;
3253
+ return {
3254
+ width: constraints.shrinkWrap ? result.width : Math.min(maxWidth, result.width),
3255
+ height: result.height,
3256
+ lineCount,
3257
+ firstBaseline: lineHeight,
3258
+ lastBaseline: lineCount > 0 ? (lineCount - 1) * lineHeight + lineHeight : lineHeight,
3259
+ truncated: false
3260
+ };
3261
+ }
3262
+ };
3263
+ } };
3264
+ }
3265
+ /**
3266
+ * Plugin: add Pretext-based proportional text measurement.
3267
+ *
3268
+ * @param pretext - The pretext module (import from "@chenglou/pretext")
3269
+ */
3270
+ function withPretext(pretext) {
3271
+ return (engine) => {
3272
+ engine.textLayout = createPretextMeasurer(pretext);
3273
+ return engine;
3274
+ };
3275
+ }
3276
+ //#endregion
3277
+ export { ALIGN_AUTO, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_FLEX_END, ALIGN_FLEX_START, ALIGN_SPACE_AROUND, ALIGN_SPACE_BETWEEN, ALIGN_SPACE_EVENLY, ALIGN_STRETCH, DIRECTION_INHERIT, DIRECTION_LTR, DIRECTION_RTL, DISPLAY_FLEX, DISPLAY_NONE, EDGE_ALL, EDGE_BOTTOM, EDGE_END, EDGE_HORIZONTAL, EDGE_LEFT, EDGE_RIGHT, EDGE_START, EDGE_TOP, EDGE_VERTICAL, FLEX_DIRECTION_COLUMN, FLEX_DIRECTION_COLUMN_REVERSE, FLEX_DIRECTION_ROW, FLEX_DIRECTION_ROW_REVERSE, GUTTER_ALL, GUTTER_COLUMN, GUTTER_ROW, JUSTIFY_CENTER, JUSTIFY_FLEX_END, JUSTIFY_FLEX_START, JUSTIFY_SPACE_AROUND, JUSTIFY_SPACE_BETWEEN, JUSTIFY_SPACE_EVENLY, MEASURE_MODE_AT_MOST, MEASURE_MODE_EXACTLY, MEASURE_MODE_UNDEFINED, Node, OVERFLOW_HIDDEN, OVERFLOW_SCROLL, OVERFLOW_VISIBLE, POSITION_TYPE_ABSOLUTE, POSITION_TYPE_RELATIVE, POSITION_TYPE_STATIC, UNIT_AUTO, UNIT_FIT_CONTENT, UNIT_PERCENT, UNIT_POINT, UNIT_SNUG_CONTENT, UNIT_UNDEFINED, WRAP_NO_WRAP, WRAP_WRAP, WRAP_WRAP_REVERSE, createBareFlexily, createDefaultStyle, createFlexily, createMonospaceMeasurer, createPretextMeasurer, createTestMeasurer, createValue, layoutCacheHits, layoutNodeCalls, layoutPositioningCalls, layoutSizingCalls, pipe, resetLayoutStats, withMonospace, withPretext, withTestMeasurer };
3278
+
3279
+ //# sourceMappingURL=index.mjs.map