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
@@ -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
- }
@@ -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
- }
@@ -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
- }