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.
- package/dist/chunk-CBBoxR_p.mjs +26 -0
- package/dist/constants-BNURa6H7.mjs +65 -0
- package/dist/constants-BNURa6H7.mjs.map +1 -0
- package/dist/constants-D7ythAJC.d.mts +64 -0
- package/dist/constants-D7ythAJC.d.mts.map +1 -0
- package/{src/classic/node.ts → dist/index-classic.d.mts} +118 -619
- package/dist/index-classic.d.mts.map +1 -0
- package/dist/index-classic.mjs +1909 -0
- package/dist/index-classic.mjs.map +1 -0
- package/dist/index.d.mts +195 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3279 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node-zero-75maLs2s.d.mts +762 -0
- package/dist/node-zero-75maLs2s.d.mts.map +1 -0
- package/dist/src-BWyhokNZ.mjs +692 -0
- package/dist/src-BWyhokNZ.mjs.map +1 -0
- package/dist/src-DdSLylRA.mjs +816 -0
- package/dist/src-DdSLylRA.mjs.map +1 -0
- package/dist/testing.d.mts +55 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +154 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types--IozHd4V.mjs +283 -0
- package/dist/types--IozHd4V.mjs.map +1 -0
- package/dist/types-DG1H4DVR.d.mts +157 -0
- package/dist/types-DG1H4DVR.d.mts.map +1 -0
- package/package.json +33 -24
- package/src/CLAUDE.md +0 -527
- package/src/classic/layout.ts +0 -1843
- package/src/constants.ts +0 -82
- package/src/create-flexily.ts +0 -153
- package/src/index-classic.ts +0 -110
- package/src/index.ts +0 -133
- package/src/layout-flex-lines.ts +0 -413
- package/src/layout-helpers.ts +0 -160
- package/src/layout-measure.ts +0 -259
- package/src/layout-stats.ts +0 -41
- package/src/layout-traversal.ts +0 -70
- package/src/layout-zero.ts +0 -2219
- package/src/logger.ts +0 -68
- package/src/monospace-measurer.ts +0 -68
- package/src/node-zero.ts +0 -1508
- package/src/pretext-measurer.ts +0 -86
- package/src/test-measurer.ts +0 -219
- package/src/testing.ts +0 -215
- package/src/text-layout.ts +0 -75
- package/src/trace.ts +0 -252
- package/src/types.ts +0 -236
- package/src/utils.ts +0 -243
package/src/layout-measure.ts
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node Measurement (Intrinsic Sizing)
|
|
3
|
-
*
|
|
4
|
-
* measureNode() computes a node's width and height without calculating positions.
|
|
5
|
-
* It's a lightweight alternative to layoutNode() used during Phase 5 for
|
|
6
|
-
* intrinsic sizing of auto-sized container children.
|
|
7
|
-
*
|
|
8
|
-
* IMPORTANT: measureNode() overwrites layout.width/layout.height as a side effect.
|
|
9
|
-
* Callers MUST save/restore these fields around calls to avoid corrupting
|
|
10
|
-
* the fingerprint cache (see "Bug 1: measureNode corruption" in CLAUDE.md).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import * as C from "./constants.js"
|
|
14
|
-
import type { Node } from "./node-zero.js"
|
|
15
|
-
import { resolveValue, applyMinMax } from "./utils.js"
|
|
16
|
-
import { resolveEdgeValue, resolveEdgeBorderValue, isRowDirection } from "./layout-helpers.js"
|
|
17
|
-
import { incMeasureNodeCalls, incLayoutCacheHits } from "./layout-stats.js"
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Measure a node to get its intrinsic size without computing positions.
|
|
21
|
-
* This is a lightweight alternative to layoutNode for sizing-only passes.
|
|
22
|
-
*
|
|
23
|
-
* Sets layout.width and layout.height but NOT layout.left/top.
|
|
24
|
-
*
|
|
25
|
-
* @param node - The node to measure
|
|
26
|
-
* @param availableWidth - Available width (NaN for unconstrained)
|
|
27
|
-
* @param availableHeight - Available height (NaN for unconstrained)
|
|
28
|
-
* @param direction - Layout direction (LTR or RTL)
|
|
29
|
-
*/
|
|
30
|
-
export function measureNode(
|
|
31
|
-
node: Node,
|
|
32
|
-
availableWidth: number,
|
|
33
|
-
availableHeight: number,
|
|
34
|
-
direction: number = C.DIRECTION_LTR,
|
|
35
|
-
): void {
|
|
36
|
-
incMeasureNodeCalls()
|
|
37
|
-
const style = node.style
|
|
38
|
-
const layout = node.layout
|
|
39
|
-
|
|
40
|
-
// Handle display: none
|
|
41
|
-
if (style.display === C.DISPLAY_NONE) {
|
|
42
|
-
layout.width = 0
|
|
43
|
-
layout.height = 0
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Calculate spacing
|
|
48
|
-
const marginLeft = resolveEdgeValue(style.margin, 0, style.flexDirection, availableWidth, direction)
|
|
49
|
-
const marginTop = resolveEdgeValue(style.margin, 1, style.flexDirection, availableWidth, direction)
|
|
50
|
-
const marginRight = resolveEdgeValue(style.margin, 2, style.flexDirection, availableWidth, direction)
|
|
51
|
-
const marginBottom = resolveEdgeValue(style.margin, 3, style.flexDirection, availableWidth, direction)
|
|
52
|
-
|
|
53
|
-
const paddingLeft = resolveEdgeValue(style.padding, 0, style.flexDirection, availableWidth, direction)
|
|
54
|
-
const paddingTop = resolveEdgeValue(style.padding, 1, style.flexDirection, availableWidth, direction)
|
|
55
|
-
const paddingRight = resolveEdgeValue(style.padding, 2, style.flexDirection, availableWidth, direction)
|
|
56
|
-
const paddingBottom = resolveEdgeValue(style.padding, 3, style.flexDirection, availableWidth, direction)
|
|
57
|
-
|
|
58
|
-
const borderLeft = resolveEdgeBorderValue(style.border, 0, style.flexDirection, direction)
|
|
59
|
-
const borderTop = resolveEdgeBorderValue(style.border, 1, style.flexDirection, direction)
|
|
60
|
-
const borderRight = resolveEdgeBorderValue(style.border, 2, style.flexDirection, direction)
|
|
61
|
-
const borderBottom = resolveEdgeBorderValue(style.border, 3, style.flexDirection, direction)
|
|
62
|
-
|
|
63
|
-
// Calculate node dimensions
|
|
64
|
-
let nodeWidth: number
|
|
65
|
-
if (style.width.unit === C.UNIT_POINT) {
|
|
66
|
-
nodeWidth = style.width.value
|
|
67
|
-
} else if (style.width.unit === C.UNIT_PERCENT) {
|
|
68
|
-
nodeWidth = resolveValue(style.width, availableWidth)
|
|
69
|
-
} else if (Number.isNaN(availableWidth)) {
|
|
70
|
-
nodeWidth = NaN
|
|
71
|
-
} else {
|
|
72
|
-
nodeWidth = availableWidth - marginLeft - marginRight
|
|
73
|
-
}
|
|
74
|
-
nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth)
|
|
75
|
-
|
|
76
|
-
let nodeHeight: number
|
|
77
|
-
if (style.height.unit === C.UNIT_POINT) {
|
|
78
|
-
nodeHeight = style.height.value
|
|
79
|
-
} else if (style.height.unit === C.UNIT_PERCENT) {
|
|
80
|
-
nodeHeight = resolveValue(style.height, availableHeight)
|
|
81
|
-
} else if (Number.isNaN(availableHeight)) {
|
|
82
|
-
nodeHeight = NaN
|
|
83
|
-
} else {
|
|
84
|
-
nodeHeight = availableHeight - marginTop - marginBottom
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Apply aspect ratio (CSS aspect-ratio spec)
|
|
88
|
-
// Re-apply min/max on the derived dimension to respect CSS box model.
|
|
89
|
-
const aspectRatio = style.aspectRatio
|
|
90
|
-
if (!Number.isNaN(aspectRatio) && aspectRatio > 0) {
|
|
91
|
-
const widthIsAuto = Number.isNaN(nodeWidth) || style.width.unit === C.UNIT_AUTO
|
|
92
|
-
const heightIsAuto = Number.isNaN(nodeHeight) || style.height.unit === C.UNIT_AUTO
|
|
93
|
-
if (widthIsAuto && !heightIsAuto && !Number.isNaN(nodeHeight)) {
|
|
94
|
-
nodeWidth = nodeHeight * aspectRatio
|
|
95
|
-
nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth)
|
|
96
|
-
} else if (heightIsAuto && !widthIsAuto && !Number.isNaN(nodeWidth)) {
|
|
97
|
-
nodeHeight = nodeWidth / aspectRatio
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
nodeHeight = applyMinMax(nodeHeight, style.minHeight, style.maxHeight, availableHeight)
|
|
102
|
-
|
|
103
|
-
// Content area
|
|
104
|
-
const innerLeft = borderLeft + paddingLeft
|
|
105
|
-
const innerTop = borderTop + paddingTop
|
|
106
|
-
const innerRight = borderRight + paddingRight
|
|
107
|
-
const innerBottom = borderBottom + paddingBottom
|
|
108
|
-
|
|
109
|
-
const minInnerWidth = innerLeft + innerRight
|
|
110
|
-
const minInnerHeight = innerTop + innerBottom
|
|
111
|
-
if (!Number.isNaN(nodeWidth) && nodeWidth < minInnerWidth) {
|
|
112
|
-
nodeWidth = minInnerWidth
|
|
113
|
-
}
|
|
114
|
-
if (!Number.isNaN(nodeHeight) && nodeHeight < minInnerHeight) {
|
|
115
|
-
nodeHeight = minInnerHeight
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const contentWidth = Number.isNaN(nodeWidth) ? NaN : Math.max(0, nodeWidth - innerLeft - innerRight)
|
|
119
|
-
const contentHeight = Number.isNaN(nodeHeight) ? NaN : Math.max(0, nodeHeight - innerTop - innerBottom)
|
|
120
|
-
|
|
121
|
-
// Handle measure function (text nodes)
|
|
122
|
-
if (node.hasMeasureFunc() && node.children.length === 0) {
|
|
123
|
-
const widthIsAuto =
|
|
124
|
-
style.width.unit === C.UNIT_AUTO || style.width.unit === C.UNIT_UNDEFINED || Number.isNaN(nodeWidth)
|
|
125
|
-
const heightIsAuto =
|
|
126
|
-
style.height.unit === C.UNIT_AUTO || style.height.unit === C.UNIT_UNDEFINED || Number.isNaN(nodeHeight)
|
|
127
|
-
const widthMode = widthIsAuto ? C.MEASURE_MODE_AT_MOST : C.MEASURE_MODE_EXACTLY
|
|
128
|
-
const heightMode = heightIsAuto ? C.MEASURE_MODE_UNDEFINED : C.MEASURE_MODE_EXACTLY
|
|
129
|
-
const measureWidth = Number.isNaN(contentWidth) ? Infinity : contentWidth
|
|
130
|
-
const measureHeight = Number.isNaN(contentHeight) ? Infinity : contentHeight
|
|
131
|
-
|
|
132
|
-
const measured = node.cachedMeasure(measureWidth, widthMode, measureHeight, heightMode)!
|
|
133
|
-
|
|
134
|
-
if (widthIsAuto) {
|
|
135
|
-
nodeWidth = measured.width + innerLeft + innerRight
|
|
136
|
-
}
|
|
137
|
-
if (heightIsAuto) {
|
|
138
|
-
nodeHeight = measured.height + innerTop + innerBottom
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
layout.width = Math.round(nodeWidth)
|
|
142
|
-
layout.height = Math.round(nodeHeight)
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Handle leaf nodes without measureFunc
|
|
147
|
-
if (node.children.length === 0) {
|
|
148
|
-
if (Number.isNaN(nodeWidth)) {
|
|
149
|
-
nodeWidth = innerLeft + innerRight
|
|
150
|
-
}
|
|
151
|
-
if (Number.isNaN(nodeHeight)) {
|
|
152
|
-
nodeHeight = innerTop + innerBottom
|
|
153
|
-
}
|
|
154
|
-
layout.width = Math.round(nodeWidth)
|
|
155
|
-
layout.height = Math.round(nodeHeight)
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// For container nodes, we need to measure children to compute intrinsic size
|
|
160
|
-
// Zero-alloc: iterate children directly without collecting into temporary array
|
|
161
|
-
|
|
162
|
-
// First pass: count relative children (skip absolute/hidden)
|
|
163
|
-
let relativeChildCount = 0
|
|
164
|
-
for (const c of node.children) {
|
|
165
|
-
if (c.style.display === C.DISPLAY_NONE) continue
|
|
166
|
-
if (c.style.positionType !== C.POSITION_TYPE_ABSOLUTE) {
|
|
167
|
-
relativeChildCount++
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (relativeChildCount === 0) {
|
|
172
|
-
// No relative children - size is just padding+border
|
|
173
|
-
if (Number.isNaN(nodeWidth)) nodeWidth = minInnerWidth
|
|
174
|
-
if (Number.isNaN(nodeHeight)) nodeHeight = minInnerHeight
|
|
175
|
-
layout.width = Math.round(nodeWidth)
|
|
176
|
-
layout.height = Math.round(nodeHeight)
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const isRow = isRowDirection(style.flexDirection)
|
|
181
|
-
const mainAxisSize = isRow ? contentWidth : contentHeight
|
|
182
|
-
const crossAxisSize = isRow ? contentHeight : contentWidth
|
|
183
|
-
const mainGap = isRow ? style.gap[0] : style.gap[1]
|
|
184
|
-
|
|
185
|
-
// Second pass: measure each child and sum for intrinsic size
|
|
186
|
-
let totalMainSize = 0
|
|
187
|
-
let maxCrossSize = 0
|
|
188
|
-
let itemCount = 0
|
|
189
|
-
|
|
190
|
-
for (const child of node.children) {
|
|
191
|
-
// Skip absolute/hidden children (same filter as count pass)
|
|
192
|
-
if (child.style.display === C.DISPLAY_NONE) continue
|
|
193
|
-
if (child.style.positionType === C.POSITION_TYPE_ABSOLUTE) continue
|
|
194
|
-
|
|
195
|
-
const childStyle = child.style
|
|
196
|
-
|
|
197
|
-
// Get child margins
|
|
198
|
-
const childMarginMain = isRow
|
|
199
|
-
? resolveEdgeValue(childStyle.margin, 0, style.flexDirection, contentWidth, direction) +
|
|
200
|
-
resolveEdgeValue(childStyle.margin, 2, style.flexDirection, contentWidth, direction)
|
|
201
|
-
: resolveEdgeValue(childStyle.margin, 1, style.flexDirection, contentWidth, direction) +
|
|
202
|
-
resolveEdgeValue(childStyle.margin, 3, style.flexDirection, contentWidth, direction)
|
|
203
|
-
const childMarginCross = isRow
|
|
204
|
-
? resolveEdgeValue(childStyle.margin, 1, style.flexDirection, contentWidth, direction) +
|
|
205
|
-
resolveEdgeValue(childStyle.margin, 3, style.flexDirection, contentWidth, direction)
|
|
206
|
-
: resolveEdgeValue(childStyle.margin, 0, style.flexDirection, contentWidth, direction) +
|
|
207
|
-
resolveEdgeValue(childStyle.margin, 2, style.flexDirection, contentWidth, direction)
|
|
208
|
-
|
|
209
|
-
// Measure child with appropriate constraints
|
|
210
|
-
// For shrink-wrap: pass NaN for main axis, cross axis constraint for cross
|
|
211
|
-
const childAvailW = isRow ? NaN : crossAxisSize
|
|
212
|
-
const childAvailH = isRow ? crossAxisSize : NaN
|
|
213
|
-
|
|
214
|
-
// Check cache first
|
|
215
|
-
let measuredW = 0
|
|
216
|
-
let measuredH = 0
|
|
217
|
-
const cached = child.getCachedLayout(childAvailW, childAvailH)
|
|
218
|
-
if (cached) {
|
|
219
|
-
incLayoutCacheHits()
|
|
220
|
-
} else {
|
|
221
|
-
// Save/restore layout around measureNode — it overwrites node.layout
|
|
222
|
-
const savedW = child.layout.width
|
|
223
|
-
const savedH = child.layout.height
|
|
224
|
-
measureNode(child, childAvailW, childAvailH, direction)
|
|
225
|
-
measuredW = child.layout.width
|
|
226
|
-
measuredH = child.layout.height
|
|
227
|
-
child.layout.width = savedW
|
|
228
|
-
child.layout.height = savedH
|
|
229
|
-
child.setCachedLayout(childAvailW, childAvailH, measuredW, measuredH)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const childMainSize = cached ? (isRow ? cached.width : cached.height) : isRow ? measuredW : measuredH
|
|
233
|
-
const childCrossSize = cached ? (isRow ? cached.height : cached.width) : isRow ? measuredH : measuredW
|
|
234
|
-
|
|
235
|
-
totalMainSize += childMainSize + childMarginMain
|
|
236
|
-
maxCrossSize = Math.max(maxCrossSize, childCrossSize + childMarginCross)
|
|
237
|
-
itemCount++
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Add gaps
|
|
241
|
-
if (itemCount > 1) {
|
|
242
|
-
totalMainSize += mainGap * (itemCount - 1)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Compute final node size
|
|
246
|
-
if (Number.isNaN(nodeWidth)) {
|
|
247
|
-
nodeWidth = (isRow ? totalMainSize : maxCrossSize) + innerLeft + innerRight
|
|
248
|
-
}
|
|
249
|
-
if (Number.isNaN(nodeHeight)) {
|
|
250
|
-
nodeHeight = (isRow ? maxCrossSize : totalMainSize) + innerTop + innerBottom
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Apply min/max again after shrink-wrap
|
|
254
|
-
nodeWidth = applyMinMax(nodeWidth, style.minWidth, style.maxWidth, availableWidth)
|
|
255
|
-
nodeHeight = applyMinMax(nodeHeight, style.minHeight, style.maxHeight, availableHeight)
|
|
256
|
-
|
|
257
|
-
layout.width = Math.round(nodeWidth)
|
|
258
|
-
layout.height = Math.round(nodeHeight)
|
|
259
|
-
}
|
package/src/layout-stats.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layout Statistics Counters
|
|
3
|
-
*
|
|
4
|
-
* Mutable counters for debugging and benchmarking.
|
|
5
|
-
* Separated to avoid circular dependencies between layout modules.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Layout statistics for debugging
|
|
9
|
-
export let layoutNodeCalls = 0
|
|
10
|
-
export let measureNodeCalls = 0
|
|
11
|
-
export let layoutSizingCalls = 0 // Calls for intrinsic sizing (offset=0,0)
|
|
12
|
-
export let layoutPositioningCalls = 0 // Calls for final positioning
|
|
13
|
-
export let layoutCacheHits = 0
|
|
14
|
-
|
|
15
|
-
export function resetLayoutStats(): void {
|
|
16
|
-
layoutNodeCalls = 0
|
|
17
|
-
measureNodeCalls = 0
|
|
18
|
-
layoutSizingCalls = 0
|
|
19
|
-
layoutPositioningCalls = 0
|
|
20
|
-
layoutCacheHits = 0
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function incLayoutNodeCalls(): void {
|
|
24
|
-
layoutNodeCalls++
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function incMeasureNodeCalls(): void {
|
|
28
|
-
measureNodeCalls++
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function incLayoutSizingCalls(): void {
|
|
32
|
-
layoutSizingCalls++
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function incLayoutPositioningCalls(): void {
|
|
36
|
-
layoutPositioningCalls++
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function incLayoutCacheHits(): void {
|
|
40
|
-
layoutCacheHits++
|
|
41
|
-
}
|
package/src/layout-traversal.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layout Tree Traversal Utilities
|
|
3
|
-
*
|
|
4
|
-
* Iterative tree traversal functions that avoid recursion
|
|
5
|
-
* (prevents stack overflow on deep trees).
|
|
6
|
-
* Uses shared traversalStack from utils.ts for zero-allocation.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Node } from "./node-zero.js"
|
|
10
|
-
import { traversalStack } from "./utils.js"
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Mark subtree as having new layout and clear dirty flags (iterative to avoid stack overflow).
|
|
14
|
-
* This is called after layout completes to reset dirty tracking for all nodes.
|
|
15
|
-
*/
|
|
16
|
-
export function markSubtreeLayoutSeen(node: Node): void {
|
|
17
|
-
traversalStack.length = 0
|
|
18
|
-
traversalStack.push(node)
|
|
19
|
-
while (traversalStack.length > 0) {
|
|
20
|
-
const current = traversalStack.pop() as Node
|
|
21
|
-
// Clear dirty flag - layout is now complete
|
|
22
|
-
;(current as Node)["_isDirty"] = false
|
|
23
|
-
;(current as Node)["_hasNewLayout"] = true
|
|
24
|
-
for (const child of current.children) {
|
|
25
|
-
traversalStack.push(child)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Count total nodes in tree (iterative to avoid stack overflow).
|
|
32
|
-
*/
|
|
33
|
-
export function countNodes(node: Node): number {
|
|
34
|
-
let count = 0
|
|
35
|
-
traversalStack.length = 0
|
|
36
|
-
traversalStack.push(node)
|
|
37
|
-
while (traversalStack.length > 0) {
|
|
38
|
-
const current = traversalStack.pop() as Node
|
|
39
|
-
count++
|
|
40
|
-
for (const child of current.children) {
|
|
41
|
-
traversalStack.push(child)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return count
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Propagate position delta to all descendants (iterative to avoid stack overflow).
|
|
49
|
-
* Used when parent position changes but layout is cached.
|
|
50
|
-
*
|
|
51
|
-
* Only updates the constraint fingerprint's lastOffset values, NOT layout.top/left.
|
|
52
|
-
* layout.top/left store RELATIVE positions (relative to parent's border box),
|
|
53
|
-
* so they don't change when an ancestor moves — only the absolute offset
|
|
54
|
-
* (tracked via lastOffsetX/Y) changes.
|
|
55
|
-
*/
|
|
56
|
-
export function propagatePositionDelta(node: Node, deltaX: number, deltaY: number): void {
|
|
57
|
-
traversalStack.length = 0
|
|
58
|
-
// Start with all children of the node
|
|
59
|
-
for (const child of node.children) {
|
|
60
|
-
traversalStack.push(child)
|
|
61
|
-
}
|
|
62
|
-
while (traversalStack.length > 0) {
|
|
63
|
-
const current = traversalStack.pop() as Node
|
|
64
|
-
current.flex.lastOffsetX += deltaX
|
|
65
|
-
current.flex.lastOffsetY += deltaY
|
|
66
|
-
for (const child of current.children) {
|
|
67
|
-
traversalStack.push(child)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|