flexily 0.5.1 → 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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-CBBoxR_p.mjs +26 -0
  3. package/dist/constants-BNURa6H7.mjs +65 -0
  4. package/dist/constants-BNURa6H7.mjs.map +1 -0
  5. package/dist/constants-D7ythAJC.d.mts +64 -0
  6. package/dist/constants-D7ythAJC.d.mts.map +1 -0
  7. package/{src/classic/node.ts → dist/index-classic.d.mts} +118 -619
  8. package/dist/index-classic.d.mts.map +1 -0
  9. package/dist/index-classic.mjs +1909 -0
  10. package/dist/index-classic.mjs.map +1 -0
  11. package/dist/index.d.mts +195 -0
  12. package/dist/index.d.mts.map +1 -0
  13. package/dist/index.mjs +3279 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/node-zero-75maLs2s.d.mts +762 -0
  16. package/dist/node-zero-75maLs2s.d.mts.map +1 -0
  17. package/dist/src-BWyhokNZ.mjs +692 -0
  18. package/dist/src-BWyhokNZ.mjs.map +1 -0
  19. package/dist/src-DdSLylRA.mjs +816 -0
  20. package/dist/src-DdSLylRA.mjs.map +1 -0
  21. package/dist/testing.d.mts +55 -0
  22. package/dist/testing.d.mts.map +1 -0
  23. package/dist/testing.mjs +154 -0
  24. package/dist/testing.mjs.map +1 -0
  25. package/dist/types--IozHd4V.mjs +283 -0
  26. package/dist/types--IozHd4V.mjs.map +1 -0
  27. package/dist/types-DG1H4DVR.d.mts +157 -0
  28. package/dist/types-DG1H4DVR.d.mts.map +1 -0
  29. package/package.json +34 -24
  30. package/src/CLAUDE.md +0 -527
  31. package/src/classic/layout.ts +0 -1843
  32. package/src/constants.ts +0 -82
  33. package/src/create-flexily.ts +0 -153
  34. package/src/index-classic.ts +0 -110
  35. package/src/index.ts +0 -133
  36. package/src/layout-flex-lines.ts +0 -413
  37. package/src/layout-helpers.ts +0 -160
  38. package/src/layout-measure.ts +0 -259
  39. package/src/layout-stats.ts +0 -41
  40. package/src/layout-traversal.ts +0 -70
  41. package/src/layout-zero.ts +0 -2219
  42. package/src/logger.ts +0 -67
  43. package/src/monospace-measurer.ts +0 -68
  44. package/src/node-zero.ts +0 -1508
  45. package/src/pretext-measurer.ts +0 -86
  46. package/src/test-measurer.ts +0 -219
  47. package/src/testing.ts +0 -215
  48. package/src/text-layout.ts +0 -75
  49. package/src/trace.ts +0 -252
  50. package/src/types.ts +0 -236
  51. package/src/utils.ts +0 -243
@@ -1,413 +0,0 @@
1
- /**
2
- * Flex Line Breaking and Space Distribution
3
- *
4
- * Pre-allocated arrays for zero-allocation flex-wrap layout,
5
- * plus the line-breaking and flex-distribution algorithms.
6
- *
7
- * Re-entrancy: A user measureFunc or baselineFunc may synchronously call
8
- * calculateLayout() on a separate tree. saveLineState/restoreLineState
9
- * bracket such nested calls to protect the outer pass's scratch arrays.
10
- */
11
-
12
- import * as C from "./constants.js"
13
- import type { Node } from "./node-zero.js"
14
-
15
- // ============================================================================
16
- // Pre-allocated Arrays for Zero-Allocation Layout
17
- // ============================================================================
18
- //
19
- // These module-level typed arrays enable zero-allocation flex-wrap layout.
20
- // They store per-line metrics that would otherwise require FlexLine[] allocation.
21
- //
22
- // Trade-offs:
23
- // - Pro: No heap allocation during layout passes (eliminates GC pressure)
24
- // - Pro: Cache-friendly contiguous memory access
25
- // - Con: Fixed maximum lines (grows dynamically if exceeded, rare allocation)
26
- // - Con: Not reentrant (single layout calculation at a time)
27
- //
28
- // Memory usage: ~768 bytes total (32 lines x 8 bytes x 2 + 32 x 2 bytes)
29
-
30
- /**
31
- * Maximum number of flex lines supported without dynamic allocation.
32
- * If a layout exceeds this, arrays grow automatically (rare edge case).
33
- * 32 lines covers virtually all real-world layouts while using minimal memory.
34
- */
35
- export let MAX_FLEX_LINES = 32
36
-
37
- /**
38
- * Pre-allocated array for line cross sizes (reused across layout passes).
39
- * Stores the computed cross-axis size of each flex line.
40
- */
41
- export let _lineCrossSizes = new Float64Array(MAX_FLEX_LINES)
42
-
43
- /**
44
- * Pre-allocated array for line cross offsets (reused across layout passes).
45
- * Stores the cross-axis position offset for each flex line.
46
- */
47
- export let _lineCrossOffsets = new Float64Array(MAX_FLEX_LINES)
48
-
49
- /**
50
- * Pre-allocated array for line lengths (number of children per line).
51
- * Uint16 supports up to 65535 children per line (more than sufficient).
52
- */
53
- export let _lineLengths = new Uint16Array(MAX_FLEX_LINES)
54
-
55
- /**
56
- * Pre-allocated 2D array for children per line.
57
- * Avoids O(n*m) iteration when processing multi-line flex layouts.
58
- * Each slot holds array of Node references for that line.
59
- */
60
- export let _lineChildren: Node[][] = Array.from({ length: MAX_FLEX_LINES }, () => [])
61
-
62
- /**
63
- * Pre-allocated array for per-line justify-content start offsets.
64
- * Stores the main-axis starting position for each flex line.
65
- */
66
- export let _lineJustifyStarts = new Float64Array(MAX_FLEX_LINES)
67
-
68
- /**
69
- * Pre-allocated array for per-line item spacing (justify-content gaps).
70
- * Stores the spacing between items for each flex line.
71
- */
72
- export let _lineItemSpacings = new Float64Array(MAX_FLEX_LINES)
73
-
74
- /**
75
- * Grow pre-allocated line arrays if needed.
76
- * Called when a layout has more lines than current capacity.
77
- * This is rare (>32 lines) and acceptable as a one-time allocation.
78
- */
79
- export function growLineArrays(needed: number): void {
80
- const newSize = Math.max(needed, MAX_FLEX_LINES * 2)
81
- MAX_FLEX_LINES = newSize
82
- _lineCrossSizes = new Float64Array(newSize)
83
- _lineCrossOffsets = new Float64Array(newSize)
84
- _lineLengths = new Uint16Array(newSize)
85
- _lineJustifyStarts = new Float64Array(newSize)
86
- _lineItemSpacings = new Float64Array(newSize)
87
- // Grow _lineChildren array
88
- while (_lineChildren.length < newSize) {
89
- _lineChildren.push([])
90
- }
91
- }
92
-
93
- // ============================================================================
94
- // Re-entrancy Support
95
- // ============================================================================
96
- // When a measureFunc/baselineFunc synchronously calls calculateLayout() on
97
- // another tree, we must save and restore the module-level scratch arrays so
98
- // the outer pass resumes with its own data intact.
99
-
100
- /**
101
- * Saved snapshot of all module-level line arrays.
102
- * Allocated only on re-entrant calls (depth > 0).
103
- */
104
- export interface LineStateSave {
105
- crossSizes: Float64Array<ArrayBuffer>
106
- crossOffsets: Float64Array<ArrayBuffer>
107
- lengths: Uint16Array<ArrayBuffer>
108
- justifyStarts: Float64Array<ArrayBuffer>
109
- itemSpacings: Float64Array<ArrayBuffer>
110
- children: Node[][]
111
- maxLines: number
112
- }
113
-
114
- /** Current re-entrancy depth. 0 = outermost (no save needed). */
115
- let _layoutDepth = 0
116
-
117
- /**
118
- * Enter a layout pass. If re-entrant (depth > 0), saves current line state.
119
- * @returns The saved state (to pass to restoreLineState), or null at depth 0.
120
- */
121
- export function enterLayout(): LineStateSave | null {
122
- const depth = _layoutDepth++
123
- if (depth === 0) return null // Outermost — no save needed
124
-
125
- // Save current state (allocates only on re-entrant calls — rare)
126
- const saved: LineStateSave = {
127
- crossSizes: _lineCrossSizes.slice(),
128
- crossOffsets: _lineCrossOffsets.slice(),
129
- lengths: _lineLengths.slice(),
130
- justifyStarts: _lineJustifyStarts.slice(),
131
- itemSpacings: _lineItemSpacings.slice(),
132
- children: _lineChildren.map((arr) => arr.slice()),
133
- maxLines: MAX_FLEX_LINES,
134
- }
135
- return saved
136
- }
137
-
138
- /**
139
- * Exit a layout pass. If re-entrant, restores saved line state.
140
- * @param saved - The state returned by enterLayout (null at depth 0).
141
- */
142
- export function exitLayout(saved: LineStateSave | null): void {
143
- _layoutDepth--
144
- if (!saved) return // Outermost — nothing to restore
145
-
146
- // Restore saved state
147
- MAX_FLEX_LINES = saved.maxLines
148
- _lineCrossSizes = saved.crossSizes
149
- _lineCrossOffsets = saved.crossOffsets
150
- _lineLengths = saved.lengths
151
- _lineJustifyStarts = saved.justifyStarts
152
- _lineItemSpacings = saved.itemSpacings
153
- _lineChildren = saved.children
154
- }
155
-
156
- /**
157
- * Epsilon value for floating point comparisons in flex distribution.
158
- * Used to determine when remaining space is negligible and iteration should stop.
159
- */
160
- const EPSILON_FLOAT = 0.001
161
-
162
- /**
163
- * Break children into flex lines based on available main-axis space.
164
- * Zero-allocation: Sets child.flex.lineIndex directly, uses pre-allocated _lineStarts/_lineLengths.
165
- *
166
- * @param parent - Parent node whose children to wrap
167
- * @param relativeCount - Number of relative children (those with flex.relativeIndex >= 0)
168
- * @param mainAxisSize - Available main-axis space (NaN for unconstrained)
169
- * @param mainGap - Gap between items on main axis
170
- * @param wrap - Wrap mode (WRAP_NO_WRAP, WRAP_WRAP, WRAP_WRAP_REVERSE)
171
- * @returns Number of lines created
172
- */
173
- export function breakIntoLines(
174
- parent: Node,
175
- relativeCount: number,
176
- mainAxisSize: number,
177
- mainGap: number,
178
- wrap: number,
179
- ): number {
180
- // No wrapping or unconstrained - all children on one line
181
- if (wrap === C.WRAP_NO_WRAP || Number.isNaN(mainAxisSize) || relativeCount === 0) {
182
- // All relative children on line 0, populate _lineChildren for O(n) access
183
- // Use index-based assignment to avoid push() backing store growth
184
- const lineArr = _lineChildren[0]!
185
- let idx = 0
186
- for (const child of parent.children) {
187
- if (child.flex.relativeIndex >= 0) {
188
- child.flex.lineIndex = 0
189
- lineArr[idx++] = child
190
- }
191
- }
192
- lineArr.length = idx // Trim to actual size
193
- _lineLengths[0] = relativeCount
194
- _lineCrossSizes[0] = 0
195
- _lineCrossOffsets[0] = 0
196
- return 1
197
- }
198
-
199
- let lineIndex = 0
200
- let lineMainSize = 0
201
- let lineChildCount = 0
202
- let lineChildIdx = 0 // Index within current line array
203
-
204
- for (const child of parent.children) {
205
- if (child.flex.relativeIndex < 0) continue
206
-
207
- const flex = child.flex
208
- // CSS spec 9.3.4: line breaking uses the "hypothetical main size" which is
209
- // the flex base size clamped to min/max, not the unclamped base size.
210
- const hypotheticalMainSize = Math.max(flex.minMain, Math.min(flex.maxMain, flex.baseSize))
211
- const childMainSize = hypotheticalMainSize + flex.mainMargin
212
- const gapIfNotFirst = lineChildCount > 0 ? mainGap : 0
213
-
214
- // Check if child fits on current line
215
- if (lineChildCount > 0 && lineMainSize + gapIfNotFirst + childMainSize > mainAxisSize) {
216
- // Finalize current line: trim array to actual size
217
- _lineChildren[lineIndex]!.length = lineChildIdx
218
- _lineLengths[lineIndex] = lineChildCount
219
- lineIndex++
220
- if (lineIndex >= MAX_FLEX_LINES) {
221
- // Grow arrays dynamically (rare - only for >32 line layouts)
222
- growLineArrays(lineIndex + 16)
223
- }
224
- lineChildIdx = 0 // Reset index for new line
225
- lineMainSize = childMainSize
226
- lineChildCount = 1
227
- } else {
228
- lineMainSize += gapIfNotFirst + childMainSize
229
- lineChildCount++
230
- }
231
- flex.lineIndex = lineIndex
232
- // Use index-based assignment to avoid push() backing store growth
233
- _lineChildren[lineIndex]![lineChildIdx++] = child
234
- }
235
-
236
- // Finalize the last line
237
- if (lineChildCount > 0) {
238
- _lineChildren[lineIndex]!.length = lineChildIdx // Trim to actual size
239
- _lineLengths[lineIndex] = lineChildCount
240
- lineIndex++
241
- }
242
-
243
- const numLines = lineIndex
244
-
245
- // Initialize cross sizes/offsets
246
- for (let i = 0; i < numLines; i++) {
247
- _lineCrossSizes[i] = 0
248
- _lineCrossOffsets[i] = 0
249
- }
250
-
251
- // Handle wrap-reverse by reversing line CONTENTS (not references) to maintain pool stability
252
- // Swapping array references would corrupt the global _lineChildren pool across layout calls
253
- if (wrap === C.WRAP_WRAP_REVERSE && numLines > 1) {
254
- // Swap contents between symmetric line pairs (element-by-element, zero allocation)
255
- for (let i = 0; i < Math.floor(numLines / 2); i++) {
256
- const j = numLines - 1 - i
257
- const lineI = _lineChildren[i]!
258
- const lineJ = _lineChildren[j]!
259
- const lenI = lineI.length
260
- const lenJ = lineJ.length
261
-
262
- // Swap elements up to max length (JS arrays auto-extend on assignment)
263
- const maxLen = Math.max(lenI, lenJ)
264
- for (let k = 0; k < maxLen; k++) {
265
- const hasI = k < lenI
266
- const hasJ = k < lenJ
267
- const tmpI = hasI ? lineI[k] : null
268
- const tmpJ = hasJ ? lineJ[k] : null
269
- if (hasJ) lineI[k] = tmpJ!
270
- if (hasI) lineJ[k] = tmpI!
271
- }
272
-
273
- // Set correct lengths (truncates or maintains as needed)
274
- lineI.length = lenJ
275
- lineJ.length = lenI
276
- }
277
- // Update lineIndex on each child to match new positions
278
- for (let i = 0; i < numLines; i++) {
279
- const lc = _lineChildren[i]!
280
- for (let c = 0; c < lc.length; c++) {
281
- lc[c]!.flex.lineIndex = i
282
- }
283
- }
284
- }
285
-
286
- return numLines
287
- }
288
-
289
- /**
290
- * Distribute flex space for a single line of children.
291
- * Implements CSS Flexbox section 9.7: Resolving Flexible Lengths.
292
- *
293
- * Takes pre-collected children array to avoid O(n*m) iteration pattern.
294
- * Previously iterated through ALL parent.children 8 times per line.
295
- *
296
- * @param lineChildren - Pre-collected children for this line (from _lineChildren)
297
- * @param initialFreeSpace - Free space to distribute (positive=grow, negative=shrink)
298
- */
299
- export function distributeFlexSpaceForLine(lineChildren: Node[], initialFreeSpace: number): void {
300
- const isGrowing = initialFreeSpace > 0
301
- if (initialFreeSpace === 0) return
302
-
303
- const childCount = lineChildren.length
304
- if (childCount === 0) return
305
-
306
- // Single-child fast path: skip iteration, direct assignment
307
- if (childCount === 1) {
308
- const flex = lineChildren[0]!.flex
309
- const canFlex = isGrowing ? flex.flexGrow > 0 : flex.flexShrink > 0
310
- if (canFlex) {
311
- // Target = base + all free space, clamped by min/max
312
- const target = flex.baseSize + initialFreeSpace
313
- flex.mainSize = Math.max(flex.minMain, Math.min(flex.maxMain, target))
314
- }
315
- // If can't flex, mainSize stays at baseSize (already set)
316
- return
317
- }
318
-
319
- // Calculate container inner size
320
- let totalBase = 0
321
- for (let i = 0; i < childCount; i++) {
322
- totalBase += lineChildren[i]!.flex.baseSize
323
- }
324
-
325
- const containerInner = initialFreeSpace + totalBase
326
-
327
- // Initialize: all items start unfrozen
328
- for (let i = 0; i < childCount; i++) {
329
- lineChildren[i]!.flex.frozen = false
330
- }
331
-
332
- let freeSpace = initialFreeSpace
333
- let iterations = 0
334
- const maxIterations = childCount + 1
335
-
336
- while (iterations++ < maxIterations) {
337
- let totalFlex = 0
338
- for (let i = 0; i < childCount; i++) {
339
- const flex = lineChildren[i]!.flex
340
- if (flex.frozen) continue
341
- if (isGrowing) {
342
- totalFlex += flex.flexGrow
343
- } else {
344
- totalFlex += flex.flexShrink * flex.baseSize
345
- }
346
- }
347
-
348
- if (totalFlex === 0) break
349
-
350
- let effectiveFreeSpace = freeSpace
351
- if (isGrowing && totalFlex < 1) {
352
- effectiveFreeSpace = freeSpace * totalFlex
353
- }
354
-
355
- let totalViolation = 0
356
- for (let i = 0; i < childCount; i++) {
357
- const flex = lineChildren[i]!.flex
358
- if (flex.frozen) continue
359
-
360
- const flexFactor = isGrowing ? flex.flexGrow : flex.flexShrink * flex.baseSize
361
- const ratio = totalFlex > 0 ? flexFactor / totalFlex : 0
362
- const target = flex.baseSize + effectiveFreeSpace * ratio
363
- const clamped = Math.max(flex.minMain, Math.min(flex.maxMain, target))
364
- totalViolation += clamped - target
365
- flex.mainSize = clamped
366
- }
367
-
368
- let anyFrozen = false
369
- if (Math.abs(totalViolation) < EPSILON_FLOAT) {
370
- for (let i = 0; i < childCount; i++) {
371
- lineChildren[i]!.flex.frozen = true
372
- }
373
- break
374
- } else if (totalViolation > 0) {
375
- for (let i = 0; i < childCount; i++) {
376
- const flex = lineChildren[i]!.flex
377
- if (flex.frozen) continue
378
- const target =
379
- flex.baseSize +
380
- ((isGrowing ? flex.flexGrow : flex.flexShrink * flex.baseSize) / totalFlex) * effectiveFreeSpace
381
- if (flex.mainSize > target + EPSILON_FLOAT) {
382
- flex.frozen = true
383
- anyFrozen = true
384
- }
385
- }
386
- } else {
387
- for (let i = 0; i < childCount; i++) {
388
- const flex = lineChildren[i]!.flex
389
- if (flex.frozen) continue
390
- const flexFactor = isGrowing ? flex.flexGrow : flex.flexShrink * flex.baseSize
391
- const target = flex.baseSize + (flexFactor / totalFlex) * effectiveFreeSpace
392
- if (flex.mainSize < target - EPSILON_FLOAT) {
393
- flex.frozen = true
394
- anyFrozen = true
395
- }
396
- }
397
- }
398
-
399
- if (!anyFrozen) break
400
-
401
- let frozenSpace = 0
402
- let unfrozenBase = 0
403
- for (let i = 0; i < childCount; i++) {
404
- const flex = lineChildren[i]!.flex
405
- if (flex.frozen) {
406
- frozenSpace += flex.mainSize
407
- } else {
408
- unfrozenBase += flex.baseSize
409
- }
410
- }
411
- freeSpace = containerInner - frozenSpace - unfrozenBase
412
- }
413
- }
@@ -1,160 +0,0 @@
1
- /**
2
- * Layout Helper Functions
3
- *
4
- * Edge resolution helpers for margins, padding, borders.
5
- * These are pure functions with no state — safe to extract.
6
- */
7
-
8
- import * as C from "./constants.js"
9
- import type { Value } from "./types.js"
10
- import { resolveValue } from "./utils.js"
11
-
12
- // Re-export edge constants (canonical definitions in constants.ts)
13
- export { EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM } from "./constants.js"
14
-
15
- // ============================================================================
16
- // Helper Functions
17
- // ============================================================================
18
-
19
- /**
20
- * Check if flex direction is row-oriented (horizontal main axis).
21
- */
22
- export function isRowDirection(flexDirection: number): boolean {
23
- return flexDirection === C.FLEX_DIRECTION_ROW || flexDirection === C.FLEX_DIRECTION_ROW_REVERSE
24
- }
25
-
26
- /**
27
- * Check if flex direction is reversed.
28
- */
29
- export function isReverseDirection(flexDirection: number): boolean {
30
- return flexDirection === C.FLEX_DIRECTION_ROW_REVERSE || flexDirection === C.FLEX_DIRECTION_COLUMN_REVERSE
31
- }
32
-
33
- /**
34
- * Get the logical edge value (START/END) for a given physical index.
35
- * Returns undefined if no logical value applies to this physical edge.
36
- *
37
- * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis,
38
- * regardless of flex direction. Direction (LTR/RTL) determines the mapping:
39
- * - LTR: START->left, END->right
40
- * - RTL: START->right, END->left
41
- */
42
- function getLogicalEdgeValue(
43
- arr: [Value, Value, Value, Value, Value, Value],
44
- physicalIndex: number,
45
- _flexDirection: number,
46
- direction: number = C.DIRECTION_LTR,
47
- ): Value | undefined {
48
- const isRTL = direction === C.DIRECTION_RTL
49
-
50
- // START/END always map to left/right (inline direction)
51
- if (physicalIndex === 0) {
52
- return isRTL ? arr[5] : arr[4] // Left: START (LTR) or END (RTL)
53
- } else if (physicalIndex === 2) {
54
- return isRTL ? arr[4] : arr[5] // Right: END (LTR) or START (RTL)
55
- }
56
- return undefined
57
- }
58
-
59
- /**
60
- * Resolve logical (START/END) margins/padding to physical values.
61
- * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis:
62
- * - LTR: START->left, END->right
63
- * - RTL: START->right, END->left
64
- *
65
- * Physical edges (LEFT/RIGHT/TOP/BOTTOM) are used directly.
66
- * When both physical and logical are set, logical takes precedence.
67
- */
68
- export function resolveEdgeValue(
69
- arr: [Value, Value, Value, Value, Value, Value],
70
- physicalIndex: number, // 0=left, 1=top, 2=right, 3=bottom
71
- flexDirection: number,
72
- availableSize: number,
73
- direction: number = C.DIRECTION_LTR,
74
- ): number {
75
- const logicalValue = getLogicalEdgeValue(arr, physicalIndex, flexDirection, direction)
76
-
77
- // Logical takes precedence if defined
78
- if (logicalValue && logicalValue.unit !== C.UNIT_UNDEFINED) {
79
- return resolveValue(logicalValue, availableSize)
80
- }
81
-
82
- // Fall back to physical
83
- return resolveValue(arr[physicalIndex]!, availableSize)
84
- }
85
-
86
- /**
87
- * Check if a logical edge margin is set to auto.
88
- */
89
- export function isEdgeAuto(
90
- arr: [Value, Value, Value, Value, Value, Value],
91
- physicalIndex: number,
92
- flexDirection: number,
93
- direction: number = C.DIRECTION_LTR,
94
- ): boolean {
95
- const logicalValue = getLogicalEdgeValue(arr, physicalIndex, flexDirection, direction)
96
-
97
- // Check logical first
98
- if (logicalValue && logicalValue.unit !== C.UNIT_UNDEFINED) {
99
- return logicalValue.unit === C.UNIT_AUTO
100
- }
101
-
102
- // Fall back to physical
103
- return arr[physicalIndex]!.unit === C.UNIT_AUTO
104
- }
105
-
106
- /**
107
- * Resolve logical (START/END) border widths to physical values.
108
- * Border values are plain numbers (always points), so resolution is simpler
109
- * than for margin/padding. Uses NaN as the "not set" sentinel for logical slots.
110
- * When both physical and logical are set, logical takes precedence.
111
- *
112
- * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis,
113
- * regardless of flex direction. Direction (LTR/RTL) determines the mapping:
114
- * - LTR: START->left, END->right
115
- * - RTL: START->right, END->left
116
- */
117
- /**
118
- * Resolve logical (START/END) position edges to physical values.
119
- * Returns the resolved Value for a physical position index, considering
120
- * logical EDGE_START/EDGE_END. Logical takes precedence over physical.
121
- *
122
- * EDGE_START/EDGE_END always resolve along the inline (horizontal) axis:
123
- * - LTR: START->left, END->right
124
- * - RTL: START->right, END->left
125
- */
126
- export function resolvePositionEdge(
127
- arr: [Value, Value, Value, Value, Value, Value],
128
- physicalIndex: number, // 0=left, 1=top, 2=right, 3=bottom
129
- direction: number = C.DIRECTION_LTR,
130
- ): Value {
131
- const logicalValue = getLogicalEdgeValue(arr, physicalIndex, 0 /* unused */, direction)
132
-
133
- // Logical takes precedence if defined
134
- if (logicalValue && logicalValue.unit !== C.UNIT_UNDEFINED) {
135
- return logicalValue
136
- }
137
-
138
- // Fall back to physical
139
- return arr[physicalIndex]!
140
- }
141
-
142
- export function resolveEdgeBorderValue(
143
- arr: [number, number, number, number, number, number],
144
- physicalIndex: number, // 0=left, 1=top, 2=right, 3=bottom
145
- _flexDirection: number,
146
- direction: number = C.DIRECTION_LTR,
147
- ): number {
148
- const isRTL = direction === C.DIRECTION_RTL
149
-
150
- // START/END always map to left/right (inline direction)
151
- let logicalSlot: number | undefined
152
- if (physicalIndex === 0) logicalSlot = isRTL ? 5 : 4
153
- else if (physicalIndex === 2) logicalSlot = isRTL ? 4 : 5
154
-
155
- // Logical takes precedence if set (NaN = not set)
156
- if (logicalSlot !== undefined && !Number.isNaN(arr[logicalSlot])) {
157
- return arr[logicalSlot]!
158
- }
159
- return arr[physicalIndex]!
160
- }