@pilates/core 1.0.0-rc.1
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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/algorithm/axis.d.ts +41 -0
- package/dist/algorithm/axis.d.ts.map +1 -0
- package/dist/algorithm/axis.js +72 -0
- package/dist/algorithm/axis.js.map +1 -0
- package/dist/algorithm/index.d.ts +12 -0
- package/dist/algorithm/index.d.ts.map +1 -0
- package/dist/algorithm/index.js +26 -0
- package/dist/algorithm/index.js.map +1 -0
- package/dist/algorithm/main-axis.d.ts +59 -0
- package/dist/algorithm/main-axis.d.ts.map +1 -0
- package/dist/algorithm/main-axis.js +704 -0
- package/dist/algorithm/main-axis.js.map +1 -0
- package/dist/algorithm/round.d.ts +20 -0
- package/dist/algorithm/round.d.ts.map +1 -0
- package/dist/algorithm/round.js +45 -0
- package/dist/algorithm/round.js.map +1 -0
- package/dist/edge.d.ts +23 -0
- package/dist/edge.d.ts.map +1 -0
- package/dist/edge.js +22 -0
- package/dist/edge.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +15 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +4 -0
- package/dist/layout.js.map +1 -0
- package/dist/measure/ansi.d.ts +15 -0
- package/dist/measure/ansi.d.ts.map +1 -0
- package/dist/measure/ansi.js +118 -0
- package/dist/measure/ansi.js.map +1 -0
- package/dist/measure/grapheme.d.ts +43 -0
- package/dist/measure/grapheme.d.ts.map +1 -0
- package/dist/measure/grapheme.js +169 -0
- package/dist/measure/grapheme.js.map +1 -0
- package/dist/measure/index.d.ts +4 -0
- package/dist/measure/index.d.ts.map +1 -0
- package/dist/measure/index.js +4 -0
- package/dist/measure/index.js.map +1 -0
- package/dist/measure/range-search.d.ts +7 -0
- package/dist/measure/range-search.d.ts.map +1 -0
- package/dist/measure/range-search.js +22 -0
- package/dist/measure/range-search.js.map +1 -0
- package/dist/measure/tables.d.ts +16 -0
- package/dist/measure/tables.d.ts.map +1 -0
- package/dist/measure/tables.js +262 -0
- package/dist/measure/tables.js.map +1 -0
- package/dist/measure/width.d.ts +37 -0
- package/dist/measure/width.d.ts.map +1 -0
- package/dist/measure/width.js +96 -0
- package/dist/measure/width.js.map +1 -0
- package/dist/measure-func.d.ts +24 -0
- package/dist/measure-func.d.ts.map +1 -0
- package/dist/measure-func.js +18 -0
- package/dist/measure-func.js.map +1 -0
- package/dist/node.d.ts +96 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +332 -0
- package/dist/node.js.map +1 -0
- package/dist/style.d.ts +53 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +40 -0
- package/dist/style.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flex layout algorithm — Milestones 4 + 5.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline (one container at a time):
|
|
5
|
+
*
|
|
6
|
+
* 1. Build per-child `FlexItem` records: hypothetical main size (basis or
|
|
7
|
+
* style or measure-func or 0), main and cross margins, "natural" cross
|
|
8
|
+
* size (used when alignment is not stretch).
|
|
9
|
+
* 2. Pack items into `FlexLine`s. With `flex-wrap: nowrap` everything goes
|
|
10
|
+
* on one line; with `wrap` items spill onto a new line whenever the
|
|
11
|
+
* next addition would exceed the container's inner main size.
|
|
12
|
+
* 3. For each line, distribute slack via flex-grow / flex-shrink with the
|
|
13
|
+
* CSS freeze loop (items hitting min/max are frozen, their slack is
|
|
14
|
+
* redistributed among unfrozen siblings). M4 work, unchanged.
|
|
15
|
+
* 4. Compute each line's cross size: max of items' (natural cross +
|
|
16
|
+
* cross-margins). Single-line containers just use the inner cross.
|
|
17
|
+
* 5. Stack lines along the cross axis with `align-content`. Multi-line
|
|
18
|
+
* stacks distribute leftover space; single-line stacks just place the
|
|
19
|
+
* one line at the start.
|
|
20
|
+
* 6. Inside each line, position items along the main axis with
|
|
21
|
+
* `justify-content`. Leftover space is distributed via flex-start /
|
|
22
|
+
* flex-end / center / space-between / space-around / space-evenly.
|
|
23
|
+
* 7. Inside each line, cross-align each item: alignSelf takes precedence,
|
|
24
|
+
* falling back to the container's alignItems for items with
|
|
25
|
+
* alignSelf: 'auto'. Stretch fills the line cross size.
|
|
26
|
+
* 8. Reverse-direction containers flip main positions; wrap-reverse
|
|
27
|
+
* reverses the line stack on the cross axis.
|
|
28
|
+
* 9. Recurse into each visible child.
|
|
29
|
+
*
|
|
30
|
+
* Position rounding lives in `round.ts` and runs once at the very end so
|
|
31
|
+
* sibling boxes butt cleanly on integer cell boundaries.
|
|
32
|
+
*/
|
|
33
|
+
import { MeasureMode } from '../measure-func.js';
|
|
34
|
+
import { clampSize, crossAxis, gapAlong, isReverse, mainAxis, maxSize, minSize, preferredSize, readEnd, readStart, } from './axis.js';
|
|
35
|
+
/**
|
|
36
|
+
* Lay out a node's children. The container's own `layout.width` and
|
|
37
|
+
* `layout.height` must already be set when this is called.
|
|
38
|
+
*
|
|
39
|
+
* Two child populations are handled here:
|
|
40
|
+
* - In-flow flex children (positionType !== 'absolute', display !== 'none'):
|
|
41
|
+
* run through the 8-step flex pipeline.
|
|
42
|
+
* - Out-of-flow absolute children (positionType === 'absolute',
|
|
43
|
+
* display !== 'none'): positioned independently against the parent's
|
|
44
|
+
* content box. They do not contribute to the flex pipeline.
|
|
45
|
+
*
|
|
46
|
+
* After both populations are positioned, the function recurses into every
|
|
47
|
+
* non-hidden child so their own descendants are laid out within the box
|
|
48
|
+
* we just assigned them.
|
|
49
|
+
*/
|
|
50
|
+
export function layoutChildren(node) {
|
|
51
|
+
const flowChildren = visibleChildrenOf(node);
|
|
52
|
+
const absoluteList = absoluteChildrenOf(node);
|
|
53
|
+
if (flowChildren.length === 0 && absoluteList.length === 0) {
|
|
54
|
+
measureLeafIfNeeded(node);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (flowChildren.length > 0) {
|
|
58
|
+
layoutFlexFlow(node, flowChildren);
|
|
59
|
+
}
|
|
60
|
+
if (absoluteList.length > 0) {
|
|
61
|
+
layoutAbsoluteChildren(node, absoluteList);
|
|
62
|
+
}
|
|
63
|
+
// Recurse into every non-hidden child so descendants are laid out within
|
|
64
|
+
// the box we just assigned them. (Absolute children may have their own
|
|
65
|
+
// flex subtrees.)
|
|
66
|
+
for (let i = 0; i < node.getChildCount(); i++) {
|
|
67
|
+
const c = node.getChild(i);
|
|
68
|
+
if (c.style.display === 'none')
|
|
69
|
+
continue;
|
|
70
|
+
layoutChildren(c);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The 8-step flex pipeline for in-flow children. */
|
|
74
|
+
function layoutFlexFlow(node, visible) {
|
|
75
|
+
const main = mainAxis(node.style.flexDirection);
|
|
76
|
+
const cross = crossAxis(node.style.flexDirection);
|
|
77
|
+
const containerMain = sizeOnAxis(node, main);
|
|
78
|
+
const containerCross = sizeOnAxis(node, cross);
|
|
79
|
+
const padMainStart = readStart(node.style.padding, main);
|
|
80
|
+
const padMainEnd = readEnd(node.style.padding, main);
|
|
81
|
+
const padCrossStart = readStart(node.style.padding, cross);
|
|
82
|
+
const padCrossEnd = readEnd(node.style.padding, cross);
|
|
83
|
+
const innerMain = Math.max(0, containerMain - padMainStart - padMainEnd);
|
|
84
|
+
const innerCross = Math.max(0, containerCross - padCrossStart - padCrossEnd);
|
|
85
|
+
const gapMain = gapAlong(node.style, main);
|
|
86
|
+
const gapCross = gapAlong(node.style, cross);
|
|
87
|
+
// Step 1: build per-child FlexItem records.
|
|
88
|
+
const items = visible.map((child) => buildItem(child, main, cross, innerMain, innerCross));
|
|
89
|
+
// Step 2: pack into lines.
|
|
90
|
+
const lines = packIntoLines(items, innerMain, gapMain, node.style.flexWrap);
|
|
91
|
+
// Step 3: distribute slack within each line.
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
distributeFlexInLine(line, innerMain, gapMain, main);
|
|
94
|
+
}
|
|
95
|
+
// Step 4: each line's cross size.
|
|
96
|
+
computeLineCrossSizes(lines, innerCross, lines.length === 1);
|
|
97
|
+
// Step 5: stack lines on the cross axis with align-content.
|
|
98
|
+
positionLinesOnCross(lines, node.style.alignContent, innerCross, gapCross);
|
|
99
|
+
if (node.style.flexWrap === 'wrap-reverse') {
|
|
100
|
+
reverseLineStack(lines, innerCross);
|
|
101
|
+
}
|
|
102
|
+
// Step 6 & 7: per-line item positioning and cross-alignment.
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
positionItemsInLine(line, node.style.justifyContent, innerMain, gapMain);
|
|
105
|
+
crossAlignItemsInLine(line, node.style.alignItems);
|
|
106
|
+
}
|
|
107
|
+
// Step 8: write to layout boxes (translating line + item to absolute).
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
for (const item of line.items) {
|
|
110
|
+
const mainAbs = padMainStart + item.mainPos;
|
|
111
|
+
const crossAbs = padCrossStart + line.crossPos + item.crossPos;
|
|
112
|
+
writeMainPos(item.node, main, mainAbs);
|
|
113
|
+
writeMainSize(item.node, main, item.finalMain);
|
|
114
|
+
writeCrossPos(item.node, cross, crossAbs);
|
|
115
|
+
writeCrossSize(item.node, cross, item.finalCross);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Reverse main-axis positions for *-reverse directions.
|
|
119
|
+
if (isReverse(node.style.flexDirection)) {
|
|
120
|
+
flipMainAxis(visible, main, containerMain);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Lay out absolutely-positioned children against the parent's outer box.
|
|
125
|
+
*
|
|
126
|
+
* Position offsets are measured from the parent's outer edges, NOT its
|
|
127
|
+
* content (post-padding) edges. This matches Yoga / React Native semantics,
|
|
128
|
+
* which differs from CSS — under CSS, absolute children are positioned
|
|
129
|
+
* against the padding edge. We keep Yoga's choice so consumers porting from
|
|
130
|
+
* Yoga / Ink see consistent results.
|
|
131
|
+
*
|
|
132
|
+
* Sizing rules per axis:
|
|
133
|
+
* - explicit `width`/`height` style → use it (clamped).
|
|
134
|
+
* - both opposite edges set, no explicit size → derive from edges.
|
|
135
|
+
* - measure function on a leaf → ask it.
|
|
136
|
+
* - otherwise → 0.
|
|
137
|
+
*
|
|
138
|
+
* Position rules per axis:
|
|
139
|
+
* - start edge set → anchor `start_offset + margin_start` from parent's outer edge.
|
|
140
|
+
* - else end edge set → anchor relative to parent's opposite outer edge.
|
|
141
|
+
* - else → 0 from parent's outer edge (no anchor; v1 simplification —
|
|
142
|
+
* Yoga falls back to justify/align here, which we may revisit).
|
|
143
|
+
*/
|
|
144
|
+
function layoutAbsoluteChildren(parent, absolutes) {
|
|
145
|
+
const outerW = parent.layout.width;
|
|
146
|
+
const outerH = parent.layout.height;
|
|
147
|
+
for (const child of absolutes) {
|
|
148
|
+
layoutAbsoluteChild(child, outerW, outerH);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const POS_TOP = 0;
|
|
152
|
+
const POS_RIGHT = 1;
|
|
153
|
+
const POS_BOTTOM = 2;
|
|
154
|
+
const POS_LEFT = 3;
|
|
155
|
+
function layoutAbsoluteChild(child, parentOuterW, parentOuterH) {
|
|
156
|
+
const pos = child.style.position;
|
|
157
|
+
const margin = child.style.margin;
|
|
158
|
+
// Resolve width.
|
|
159
|
+
let width;
|
|
160
|
+
const wStyle = child.style.width;
|
|
161
|
+
if (typeof wStyle === 'number') {
|
|
162
|
+
width = clampSize(child.style, 'row', wStyle);
|
|
163
|
+
}
|
|
164
|
+
else if (pos[POS_LEFT] !== undefined && pos[POS_RIGHT] !== undefined) {
|
|
165
|
+
const candidate = parentOuterW - pos[POS_LEFT] - pos[POS_RIGHT] - margin[POS_LEFT] - margin[POS_RIGHT];
|
|
166
|
+
width = clampSize(child.style, 'row', Math.max(0, candidate));
|
|
167
|
+
}
|
|
168
|
+
else if (child.getMeasureFunc() !== null && child.getChildCount() === 0) {
|
|
169
|
+
const result = child.getMeasureFunc()(parentOuterW, MeasureMode.AtMost, parentOuterH, MeasureMode.AtMost);
|
|
170
|
+
width = clampSize(child.style, 'row', result.width);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
width = clampSize(child.style, 'row', 0);
|
|
174
|
+
}
|
|
175
|
+
// Resolve height.
|
|
176
|
+
let height;
|
|
177
|
+
const hStyle = child.style.height;
|
|
178
|
+
if (typeof hStyle === 'number') {
|
|
179
|
+
height = clampSize(child.style, 'column', hStyle);
|
|
180
|
+
}
|
|
181
|
+
else if (pos[POS_TOP] !== undefined && pos[POS_BOTTOM] !== undefined) {
|
|
182
|
+
const candidate = parentOuterH - pos[POS_TOP] - pos[POS_BOTTOM] - margin[POS_TOP] - margin[POS_BOTTOM];
|
|
183
|
+
height = clampSize(child.style, 'column', Math.max(0, candidate));
|
|
184
|
+
}
|
|
185
|
+
else if (child.getMeasureFunc() !== null && child.getChildCount() === 0) {
|
|
186
|
+
const result = child.getMeasureFunc()(width, MeasureMode.Exactly, parentOuterH, MeasureMode.AtMost);
|
|
187
|
+
height = clampSize(child.style, 'column', result.height);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
height = clampSize(child.style, 'column', 0);
|
|
191
|
+
}
|
|
192
|
+
// Resolve left.
|
|
193
|
+
let left;
|
|
194
|
+
if (pos[POS_LEFT] !== undefined) {
|
|
195
|
+
left = pos[POS_LEFT] + margin[POS_LEFT];
|
|
196
|
+
}
|
|
197
|
+
else if (pos[POS_RIGHT] !== undefined) {
|
|
198
|
+
left = parentOuterW - width - pos[POS_RIGHT] - margin[POS_RIGHT];
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
left = margin[POS_LEFT];
|
|
202
|
+
}
|
|
203
|
+
// Resolve top.
|
|
204
|
+
let top;
|
|
205
|
+
if (pos[POS_TOP] !== undefined) {
|
|
206
|
+
top = pos[POS_TOP] + margin[POS_TOP];
|
|
207
|
+
}
|
|
208
|
+
else if (pos[POS_BOTTOM] !== undefined) {
|
|
209
|
+
top = parentOuterH - height - pos[POS_BOTTOM] - margin[POS_BOTTOM];
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
top = margin[POS_TOP];
|
|
213
|
+
}
|
|
214
|
+
child.layout.left = left;
|
|
215
|
+
child.layout.top = top;
|
|
216
|
+
child.layout.width = width;
|
|
217
|
+
child.layout.height = height;
|
|
218
|
+
}
|
|
219
|
+
function absoluteChildrenOf(node) {
|
|
220
|
+
const out = [];
|
|
221
|
+
for (let i = 0; i < node.getChildCount(); i++) {
|
|
222
|
+
const c = node.getChild(i);
|
|
223
|
+
if (c.style.display === 'none')
|
|
224
|
+
continue;
|
|
225
|
+
if (c.style.positionType === 'absolute')
|
|
226
|
+
out.push(c);
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
// ─── step 1: build items ────────────────────────────────────────────────
|
|
231
|
+
function buildItem(node, main, cross, innerMain, innerCross) {
|
|
232
|
+
const hypothetical = clampSize(node.style, main, resolveHypotheticalMainSize(node, main, innerMain, innerCross));
|
|
233
|
+
const naturalCross = naturalCrossSize(node, cross, innerCross);
|
|
234
|
+
return {
|
|
235
|
+
node,
|
|
236
|
+
hypothetical,
|
|
237
|
+
finalMain: hypothetical,
|
|
238
|
+
naturalCross,
|
|
239
|
+
finalCross: naturalCross,
|
|
240
|
+
mainPos: 0,
|
|
241
|
+
crossPos: 0,
|
|
242
|
+
marginMainStart: readStart(node.style.margin, main),
|
|
243
|
+
marginMainEnd: readEnd(node.style.margin, main),
|
|
244
|
+
marginCrossStart: readStart(node.style.margin, cross),
|
|
245
|
+
marginCrossEnd: readEnd(node.style.margin, cross),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// ─── step 2: pack items into lines ──────────────────────────────────────
|
|
249
|
+
function packIntoLines(items, innerMain, gapMain, wrap) {
|
|
250
|
+
if (wrap === 'nowrap' || items.length <= 1) {
|
|
251
|
+
return [singleLine(items, gapMain)];
|
|
252
|
+
}
|
|
253
|
+
const lines = [];
|
|
254
|
+
let current = [];
|
|
255
|
+
let currentMain = 0;
|
|
256
|
+
for (const item of items) {
|
|
257
|
+
const itemMain = item.hypothetical + item.marginMainStart + item.marginMainEnd;
|
|
258
|
+
const wouldUse = currentMain + (current.length > 0 ? gapMain : 0) + itemMain;
|
|
259
|
+
if (current.length > 0 && wouldUse > innerMain) {
|
|
260
|
+
lines.push(makeLine(current, currentMain));
|
|
261
|
+
current = [item];
|
|
262
|
+
currentMain = itemMain;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
if (current.length > 0)
|
|
266
|
+
currentMain += gapMain;
|
|
267
|
+
current.push(item);
|
|
268
|
+
currentMain += itemMain;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (current.length > 0)
|
|
272
|
+
lines.push(makeLine(current, currentMain));
|
|
273
|
+
return lines;
|
|
274
|
+
}
|
|
275
|
+
function singleLine(items, gapMain) {
|
|
276
|
+
let hypotheticalMain = 0;
|
|
277
|
+
for (let i = 0; i < items.length; i++) {
|
|
278
|
+
const it = items[i];
|
|
279
|
+
hypotheticalMain += it.hypothetical + it.marginMainStart + it.marginMainEnd;
|
|
280
|
+
if (i < items.length - 1)
|
|
281
|
+
hypotheticalMain += gapMain;
|
|
282
|
+
}
|
|
283
|
+
return { items, hypotheticalMain, crossSize: 0, crossPos: 0 };
|
|
284
|
+
}
|
|
285
|
+
function makeLine(items, hypotheticalMain) {
|
|
286
|
+
return { items, hypotheticalMain, crossSize: 0, crossPos: 0 };
|
|
287
|
+
}
|
|
288
|
+
// ─── step 3: distribute flex-grow / flex-shrink within a line ───────────
|
|
289
|
+
function distributeFlexInLine(line, innerMain, gapMain, main) {
|
|
290
|
+
const remaining = innerMain -
|
|
291
|
+
line.hypotheticalMain -
|
|
292
|
+
Math.max(0, line.items.length - 1) * (line.items.length > 1 ? 0 : 0);
|
|
293
|
+
// Note: gapMain is already included in hypotheticalMain via singleLine /
|
|
294
|
+
// makeLine accounting (for wrap case) — wait, actually gapMain is added
|
|
295
|
+
// for singleLine. Let me handle this consistently:
|
|
296
|
+
void gapMain;
|
|
297
|
+
if (remaining > 0) {
|
|
298
|
+
distributeGrow(line.items, remaining, main);
|
|
299
|
+
}
|
|
300
|
+
else if (remaining < 0) {
|
|
301
|
+
distributeShrink(line.items, -remaining, main);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function distributeGrow(items, slack, main) {
|
|
305
|
+
const n = items.length;
|
|
306
|
+
const frozen = new Array(n).fill(false);
|
|
307
|
+
for (let i = 0; i < n; i++) {
|
|
308
|
+
if (items[i].node.style.flexGrow <= 0)
|
|
309
|
+
frozen[i] = true;
|
|
310
|
+
}
|
|
311
|
+
for (let iter = 0; iter < n + 1; iter++) {
|
|
312
|
+
let totalGrow = 0;
|
|
313
|
+
let frozenContribution = 0;
|
|
314
|
+
for (let i = 0; i < n; i++) {
|
|
315
|
+
if (frozen[i])
|
|
316
|
+
frozenContribution += items[i].finalMain - items[i].hypothetical;
|
|
317
|
+
else
|
|
318
|
+
totalGrow += items[i].node.style.flexGrow;
|
|
319
|
+
}
|
|
320
|
+
if (totalGrow <= 0)
|
|
321
|
+
return;
|
|
322
|
+
const remaining = slack - frozenContribution;
|
|
323
|
+
if (remaining <= 0)
|
|
324
|
+
return;
|
|
325
|
+
let frozeAny = false;
|
|
326
|
+
for (let i = 0; i < n; i++) {
|
|
327
|
+
if (frozen[i])
|
|
328
|
+
continue;
|
|
329
|
+
const item = items[i];
|
|
330
|
+
const grow = item.node.style.flexGrow;
|
|
331
|
+
const target = item.hypothetical + (remaining * grow) / totalGrow;
|
|
332
|
+
const clamped = clampSize(item.node.style, main, target);
|
|
333
|
+
item.finalMain = clamped;
|
|
334
|
+
if (clamped !== target) {
|
|
335
|
+
frozen[i] = true;
|
|
336
|
+
frozeAny = true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (!frozeAny)
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function distributeShrink(items, overflow, main) {
|
|
344
|
+
const n = items.length;
|
|
345
|
+
const frozen = new Array(n).fill(false);
|
|
346
|
+
for (let i = 0; i < n; i++) {
|
|
347
|
+
if (items[i].node.style.flexShrink <= 0)
|
|
348
|
+
frozen[i] = true;
|
|
349
|
+
}
|
|
350
|
+
for (let iter = 0; iter < n + 1; iter++) {
|
|
351
|
+
let totalScaled = 0;
|
|
352
|
+
let frozenContribution = 0;
|
|
353
|
+
for (let i = 0; i < n; i++) {
|
|
354
|
+
if (frozen[i])
|
|
355
|
+
frozenContribution += items[i].hypothetical - items[i].finalMain;
|
|
356
|
+
else
|
|
357
|
+
totalScaled += items[i].node.style.flexShrink * items[i].hypothetical;
|
|
358
|
+
}
|
|
359
|
+
if (totalScaled <= 0)
|
|
360
|
+
return;
|
|
361
|
+
const remaining = overflow - frozenContribution;
|
|
362
|
+
if (remaining <= 0)
|
|
363
|
+
return;
|
|
364
|
+
let frozeAny = false;
|
|
365
|
+
for (let i = 0; i < n; i++) {
|
|
366
|
+
if (frozen[i])
|
|
367
|
+
continue;
|
|
368
|
+
const item = items[i];
|
|
369
|
+
const scaled = item.node.style.flexShrink * item.hypothetical;
|
|
370
|
+
if (scaled <= 0) {
|
|
371
|
+
frozen[i] = true;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const reduction = (remaining * scaled) / totalScaled;
|
|
375
|
+
const target = item.hypothetical - reduction;
|
|
376
|
+
const clamped = clampSize(item.node.style, main, target);
|
|
377
|
+
item.finalMain = clamped;
|
|
378
|
+
if (clamped !== target) {
|
|
379
|
+
frozen[i] = true;
|
|
380
|
+
frozeAny = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!frozeAny)
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// ─── step 4: per-line cross size ────────────────────────────────────────
|
|
388
|
+
function computeLineCrossSizes(lines, innerCross, singleLineMode) {
|
|
389
|
+
if (singleLineMode) {
|
|
390
|
+
// CSS: a single-line flex container's only line takes the container's
|
|
391
|
+
// inner cross size.
|
|
392
|
+
if (lines.length > 0)
|
|
393
|
+
lines[0].crossSize = innerCross;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
for (const line of lines) {
|
|
397
|
+
let max = 0;
|
|
398
|
+
for (const it of line.items) {
|
|
399
|
+
const candidate = it.naturalCross + it.marginCrossStart + it.marginCrossEnd;
|
|
400
|
+
if (candidate > max)
|
|
401
|
+
max = candidate;
|
|
402
|
+
}
|
|
403
|
+
line.crossSize = max;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// ─── step 5: position lines on the cross axis (align-content) ───────────
|
|
407
|
+
function positionLinesOnCross(lines, alignContent, innerCross, gapCross) {
|
|
408
|
+
if (lines.length === 1) {
|
|
409
|
+
lines[0].crossPos = 0;
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let used = 0;
|
|
413
|
+
for (const line of lines)
|
|
414
|
+
used += line.crossSize;
|
|
415
|
+
used += (lines.length - 1) * gapCross;
|
|
416
|
+
const leftover = innerCross - used;
|
|
417
|
+
// 'auto' on align-content means stretch. We treat any value not in the
|
|
418
|
+
// distribution set as flex-start.
|
|
419
|
+
let cursor = 0;
|
|
420
|
+
let extraGap = 0;
|
|
421
|
+
let lineSizeBoost = 0;
|
|
422
|
+
switch (alignContent) {
|
|
423
|
+
case 'flex-end':
|
|
424
|
+
cursor = leftover;
|
|
425
|
+
break;
|
|
426
|
+
case 'center':
|
|
427
|
+
cursor = leftover / 2;
|
|
428
|
+
break;
|
|
429
|
+
case 'space-between':
|
|
430
|
+
if (lines.length > 1 && leftover > 0)
|
|
431
|
+
extraGap = leftover / (lines.length - 1);
|
|
432
|
+
break;
|
|
433
|
+
case 'space-around':
|
|
434
|
+
if (leftover > 0) {
|
|
435
|
+
const slot = leftover / lines.length;
|
|
436
|
+
cursor = slot / 2;
|
|
437
|
+
extraGap = slot;
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
case 'stretch':
|
|
441
|
+
case 'auto':
|
|
442
|
+
if (leftover > 0)
|
|
443
|
+
lineSizeBoost = leftover / lines.length;
|
|
444
|
+
break;
|
|
445
|
+
default:
|
|
446
|
+
// flex-start: cursor = 0, no extra.
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
for (let i = 0; i < lines.length; i++) {
|
|
450
|
+
const line = lines[i];
|
|
451
|
+
if (lineSizeBoost > 0)
|
|
452
|
+
line.crossSize += lineSizeBoost;
|
|
453
|
+
line.crossPos = cursor;
|
|
454
|
+
cursor += line.crossSize + gapCross + extraGap;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function reverseLineStack(lines, innerCross) {
|
|
458
|
+
for (const line of lines) {
|
|
459
|
+
line.crossPos = innerCross - line.crossPos - line.crossSize;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// ─── step 6: justify-content per line ───────────────────────────────────
|
|
463
|
+
function positionItemsInLine(line, justify, innerMain, gapMain) {
|
|
464
|
+
const items = line.items;
|
|
465
|
+
const n = items.length;
|
|
466
|
+
if (n === 0)
|
|
467
|
+
return;
|
|
468
|
+
let usedMain = 0;
|
|
469
|
+
for (let i = 0; i < n; i++) {
|
|
470
|
+
const it = items[i];
|
|
471
|
+
usedMain += it.finalMain + it.marginMainStart + it.marginMainEnd;
|
|
472
|
+
if (i < n - 1)
|
|
473
|
+
usedMain += gapMain;
|
|
474
|
+
}
|
|
475
|
+
const leftover = Math.max(0, innerMain - usedMain);
|
|
476
|
+
let cursor = 0;
|
|
477
|
+
let extraGap = 0;
|
|
478
|
+
switch (justify) {
|
|
479
|
+
case 'flex-end':
|
|
480
|
+
cursor = leftover;
|
|
481
|
+
break;
|
|
482
|
+
case 'center':
|
|
483
|
+
cursor = leftover / 2;
|
|
484
|
+
break;
|
|
485
|
+
case 'space-between':
|
|
486
|
+
if (n > 1)
|
|
487
|
+
extraGap = leftover / (n - 1);
|
|
488
|
+
break;
|
|
489
|
+
case 'space-around': {
|
|
490
|
+
const slot = leftover / n;
|
|
491
|
+
cursor = slot / 2;
|
|
492
|
+
extraGap = slot;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case 'space-evenly': {
|
|
496
|
+
const slot = leftover / (n + 1);
|
|
497
|
+
cursor = slot;
|
|
498
|
+
extraGap = slot;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
default:
|
|
502
|
+
// flex-start: cursor = 0, extra = 0.
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
for (let i = 0; i < n; i++) {
|
|
506
|
+
const it = items[i];
|
|
507
|
+
cursor += it.marginMainStart;
|
|
508
|
+
it.mainPos = cursor;
|
|
509
|
+
cursor += it.finalMain + it.marginMainEnd;
|
|
510
|
+
if (i < n - 1)
|
|
511
|
+
cursor += gapMain + extraGap;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// ─── step 7: align-items / align-self per item ──────────────────────────
|
|
515
|
+
function crossAlignItemsInLine(line, alignItems) {
|
|
516
|
+
for (const item of line.items) {
|
|
517
|
+
const align = effectiveAlign(item.node.style.alignSelf, alignItems);
|
|
518
|
+
const innerLine = line.crossSize - item.marginCrossStart - item.marginCrossEnd;
|
|
519
|
+
const cross = inferCrossAxisFromContext(item, line);
|
|
520
|
+
if (align === 'stretch') {
|
|
521
|
+
const explicit = preferredSize(item.node.style, cross);
|
|
522
|
+
if (typeof explicit === 'number') {
|
|
523
|
+
item.finalCross = clampSize(item.node.style, cross, explicit);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
item.finalCross = clampSize(item.node.style, cross, Math.max(0, innerLine));
|
|
527
|
+
}
|
|
528
|
+
item.crossPos = item.marginCrossStart;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
// Non-stretch: use the natural cross size, clamped.
|
|
532
|
+
const naturalClamped = clampSize(item.node.style, cross, item.naturalCross);
|
|
533
|
+
item.finalCross = naturalClamped;
|
|
534
|
+
switch (align) {
|
|
535
|
+
case 'flex-end':
|
|
536
|
+
item.crossPos = line.crossSize - naturalClamped - item.marginCrossEnd;
|
|
537
|
+
break;
|
|
538
|
+
case 'center':
|
|
539
|
+
item.crossPos = item.marginCrossStart + Math.max(0, (innerLine - naturalClamped) / 2);
|
|
540
|
+
break;
|
|
541
|
+
default:
|
|
542
|
+
// flex-start, baseline (treated as flex-start in v1), space-* on
|
|
543
|
+
// an item (degenerate — treat as flex-start).
|
|
544
|
+
item.crossPos = item.marginCrossStart;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function effectiveAlign(self, items) {
|
|
550
|
+
return self === 'auto' ? items : self;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* The cross axis we're working on within `crossAlignItemsInLine`. We look at
|
|
554
|
+
* the item's parent direction via the FlexItem context; since cross/main are
|
|
555
|
+
* fixed per layoutChildren call, we infer from the item's own layout-time
|
|
556
|
+
* cross axis. Helper kept private so the hot path doesn't recompute it.
|
|
557
|
+
*/
|
|
558
|
+
function inferCrossAxisFromContext(item, _line) {
|
|
559
|
+
// Cross axis is the perpendicular of the parent's flex direction. The
|
|
560
|
+
// parent is the only place flexDirection lives, so we look it up via the
|
|
561
|
+
// node's parent.
|
|
562
|
+
const parent = item.node.getParent();
|
|
563
|
+
if (parent === null)
|
|
564
|
+
return 'column';
|
|
565
|
+
return crossAxis(parent.style.flexDirection);
|
|
566
|
+
}
|
|
567
|
+
// ─── helpers ────────────────────────────────────────────────────────────
|
|
568
|
+
function visibleChildrenOf(node) {
|
|
569
|
+
const out = [];
|
|
570
|
+
for (let i = 0; i < node.getChildCount(); i++) {
|
|
571
|
+
const c = node.getChild(i);
|
|
572
|
+
if (c.style.display === 'none')
|
|
573
|
+
continue;
|
|
574
|
+
if (c.style.positionType === 'absolute')
|
|
575
|
+
continue; // M6 handles absolute
|
|
576
|
+
out.push(c);
|
|
577
|
+
}
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
function measureLeafIfNeeded(node) {
|
|
581
|
+
const fn = node.getMeasureFunc();
|
|
582
|
+
if (fn === null)
|
|
583
|
+
return;
|
|
584
|
+
const padW = (node.style.padding[1] ?? 0) + (node.style.padding[3] ?? 0);
|
|
585
|
+
const padH = (node.style.padding[0] ?? 0) + (node.style.padding[2] ?? 0);
|
|
586
|
+
fn(Math.max(0, node.layout.width - padW), MeasureMode.AtMost, Math.max(0, node.layout.height - padH), MeasureMode.AtMost);
|
|
587
|
+
}
|
|
588
|
+
function sizeOnAxis(node, axis) {
|
|
589
|
+
return axis === 'row' ? node.layout.width : node.layout.height;
|
|
590
|
+
}
|
|
591
|
+
function writeMainPos(node, main, value) {
|
|
592
|
+
if (main === 'row')
|
|
593
|
+
node.layout.left = value;
|
|
594
|
+
else
|
|
595
|
+
node.layout.top = value;
|
|
596
|
+
}
|
|
597
|
+
function writeMainSize(node, main, value) {
|
|
598
|
+
if (main === 'row')
|
|
599
|
+
node.layout.width = value;
|
|
600
|
+
else
|
|
601
|
+
node.layout.height = value;
|
|
602
|
+
}
|
|
603
|
+
function writeCrossPos(node, cross, value) {
|
|
604
|
+
if (cross === 'row')
|
|
605
|
+
node.layout.left = value;
|
|
606
|
+
else
|
|
607
|
+
node.layout.top = value;
|
|
608
|
+
}
|
|
609
|
+
function writeCrossSize(node, cross, value) {
|
|
610
|
+
if (cross === 'row')
|
|
611
|
+
node.layout.width = value;
|
|
612
|
+
else
|
|
613
|
+
node.layout.height = value;
|
|
614
|
+
}
|
|
615
|
+
function resolveHypotheticalMainSize(child, main, _innerMain, innerCross) {
|
|
616
|
+
const basis = child.style.flexBasis;
|
|
617
|
+
if (typeof basis === 'number')
|
|
618
|
+
return basis;
|
|
619
|
+
const styleMain = preferredSize(child.style, main);
|
|
620
|
+
if (typeof styleMain === 'number')
|
|
621
|
+
return styleMain;
|
|
622
|
+
const measure = child.getMeasureFunc();
|
|
623
|
+
if (measure !== null && child.getChildCount() === 0) {
|
|
624
|
+
// Ask the leaf for its natural main-axis size: cross axis is
|
|
625
|
+
// constrained (AtMost the available cross), main axis is free
|
|
626
|
+
// (Undefined). The measure function reports `{ width, height }` as
|
|
627
|
+
// dimensions of the node, so we map main/cross to width/height before
|
|
628
|
+
// calling.
|
|
629
|
+
const cross = main === 'row' ? 'column' : 'row';
|
|
630
|
+
const cs = preferredSize(child.style, cross);
|
|
631
|
+
const crossConstraint = typeof cs === 'number' ? cs : innerCross;
|
|
632
|
+
let widthArg;
|
|
633
|
+
let heightArg;
|
|
634
|
+
let widthMode;
|
|
635
|
+
let heightMode;
|
|
636
|
+
if (main === 'row') {
|
|
637
|
+
// main = width (free), cross = height (constrained)
|
|
638
|
+
widthArg = 0;
|
|
639
|
+
widthMode = MeasureMode.Undefined;
|
|
640
|
+
heightArg = crossConstraint;
|
|
641
|
+
heightMode = MeasureMode.AtMost;
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
// main = height (free), cross = width (constrained)
|
|
645
|
+
widthArg = crossConstraint;
|
|
646
|
+
widthMode = MeasureMode.AtMost;
|
|
647
|
+
heightArg = 0;
|
|
648
|
+
heightMode = MeasureMode.Undefined;
|
|
649
|
+
}
|
|
650
|
+
const result = measure(widthArg, widthMode, heightArg, heightMode);
|
|
651
|
+
return main === 'row' ? result.width : result.height;
|
|
652
|
+
}
|
|
653
|
+
return 0;
|
|
654
|
+
}
|
|
655
|
+
function naturalCrossSize(child, cross, innerCross) {
|
|
656
|
+
const explicit = preferredSize(child.style, cross);
|
|
657
|
+
if (typeof explicit === 'number')
|
|
658
|
+
return explicit;
|
|
659
|
+
const measure = child.getMeasureFunc();
|
|
660
|
+
if (measure !== null && child.getChildCount() === 0) {
|
|
661
|
+
const main = cross === 'row' ? 'column' : 'row';
|
|
662
|
+
const ms = preferredSize(child.style, main);
|
|
663
|
+
const mainHint = typeof ms === 'number' ? ms : innerCross;
|
|
664
|
+
const result = measure(cross === 'row' ? innerCross : mainHint, cross === 'row' ? MeasureMode.AtMost : MeasureMode.Undefined, cross === 'column' ? innerCross : mainHint, cross === 'column' ? MeasureMode.AtMost : MeasureMode.Undefined);
|
|
665
|
+
return cross === 'row' ? result.width : result.height;
|
|
666
|
+
}
|
|
667
|
+
return 0;
|
|
668
|
+
}
|
|
669
|
+
function flipMainAxis(children, main, containerMain) {
|
|
670
|
+
for (const child of children) {
|
|
671
|
+
const childMain = main === 'row' ? child.layout.width : child.layout.height;
|
|
672
|
+
const childPos = main === 'row' ? child.layout.left : child.layout.top;
|
|
673
|
+
const newPos = containerMain - childPos - childMain;
|
|
674
|
+
if (main === 'row')
|
|
675
|
+
child.layout.left = newPos;
|
|
676
|
+
else
|
|
677
|
+
child.layout.top = newPos;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Resolve the root node's own size from style + caller-supplied availability.
|
|
682
|
+
*
|
|
683
|
+
* - explicit number → use it (clamped to min/max).
|
|
684
|
+
* - 'auto' + an `available` value → use available, clamped.
|
|
685
|
+
* - 'auto' with neither → 0.
|
|
686
|
+
*/
|
|
687
|
+
export function resolveRootAxisSize(node, axis, available) {
|
|
688
|
+
const style = preferredSize(node.style, axis);
|
|
689
|
+
if (typeof style === 'number') {
|
|
690
|
+
return clampSize(node.style, axis, style);
|
|
691
|
+
}
|
|
692
|
+
if (available !== undefined) {
|
|
693
|
+
let v = available;
|
|
694
|
+
const mn = minSize(node.style, axis);
|
|
695
|
+
const mx = maxSize(node.style, axis);
|
|
696
|
+
if (v < mn)
|
|
697
|
+
v = mn;
|
|
698
|
+
if (mx !== undefined && v > mx)
|
|
699
|
+
v = mx;
|
|
700
|
+
return v;
|
|
701
|
+
}
|
|
702
|
+
return 0;
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=main-axis.js.map
|