@ng-native/fabric 0.16.0 → 0.17.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/dist/grid.js ADDED
@@ -0,0 +1,934 @@
1
+ /**
2
+ * CSS Grid, as a function from a container's style and its items to where each item goes.
3
+ *
4
+ * It follows CSS Grid Layout Level 1: placement (8.5), track sizing (11.3 to 11.8) and alignment
5
+ * (10). It knows nothing about Yoga or a device. Whatever lays the items out says how big each
6
+ * one's content is through `measure`, and gets back a rectangle for each against the container's
7
+ * content box.
8
+ *
9
+ * Not here: subgrid, last-baseline alignment (read as start), and right-to-left columns.
10
+ */
11
+ const AUTO = { kind: 'auto' };
12
+ /** The most tracks a repeat makes and the furthest line an item names: a grid stays finite. */
13
+ const CAP = 1000;
14
+ const px = (value) => Number.parseFloat(value ?? '') || 0;
15
+ const isAuto = (value) => value === undefined || value === 'auto';
16
+ /**
17
+ * A length, or a percentage of `whole`, or nothing when it is `auto` or cannot be known yet. A
18
+ * percentage may have points added, `50% + 20px`, as a content box's share has its frame.
19
+ */
20
+ function length(value, whole) {
21
+ if (isAuto(value))
22
+ return undefined;
23
+ const share = /^(-?[\d.]+)%(?: \+ (-?[\d.]+)px)?$/.exec(value);
24
+ if (share)
25
+ return whole === undefined ? undefined : (px(share[1]) / 100) * whole + px(share[2]);
26
+ return /^-?[\d.]+(px)?$/.test(value) ? px(value) : undefined;
27
+ }
28
+ // Track lists
29
+ /** A track list's words and its `[line names]`, each kept together, as is a function's arguments. */
30
+ function tokens(list, separator = ' ') {
31
+ const out = [];
32
+ let depth = 0;
33
+ let word = '';
34
+ for (const char of list) {
35
+ if (char === '(' || char === '[')
36
+ depth += 1;
37
+ if (char === ')' || char === ']')
38
+ depth -= 1;
39
+ if (char === separator && depth === 0) {
40
+ out.push(word);
41
+ word = '';
42
+ }
43
+ else {
44
+ word += char;
45
+ }
46
+ }
47
+ out.push(word);
48
+ return out.map((each) => each.trim()).filter(Boolean);
49
+ }
50
+ /** A track list's words, with its line names dropped. */
51
+ const words = (list, separator = ' ') => tokens(list, separator).filter((each) => !each.startsWith('['));
52
+ function breadth(word, whole) {
53
+ if (word === 'min-content' || word === 'max-content')
54
+ return { kind: word };
55
+ if (word.endsWith('fr'))
56
+ return { kind: 'fr', factor: px(word) };
57
+ const size = length(word, whole);
58
+ return size === undefined ? AUTO : { kind: 'length', px: size };
59
+ }
60
+ function track(word, whole, autoFit = false) {
61
+ const args = /^([a-z-]+)\((.*)\)$/.exec(word);
62
+ let min = breadth(word, whole);
63
+ let max = min;
64
+ if (args?.[1] === 'minmax') {
65
+ const [low, high] = words(args[2], ',');
66
+ min = breadth(low, whole);
67
+ max = breadth(high, whole);
68
+ }
69
+ else if (args?.[1] === 'fit-content') {
70
+ min = AUTO;
71
+ max = { kind: 'fit-content', limit: length(args[2], whole) ?? Infinity };
72
+ }
73
+ // A flexible minimum is no minimum: the track is as small as its content allows.
74
+ if (min.kind === 'fr')
75
+ min = AUTO;
76
+ return { min, max, base: 0, limit: 0, growable: false, autoFit };
77
+ }
78
+ /** What a track is sure to take: its fixed maximum, or failing that its fixed minimum. */
79
+ const fixedSize = (each) => each.max.kind === 'length' ? each.max.px : each.min.kind === 'length' ? each.min.px : 0;
80
+ /** How many times an `auto-fill` or `auto-fit` list repeats in `whole` without overflowing. */
81
+ function repetitions(repeated, others, otherSize, whole, gap) {
82
+ if (whole === undefined)
83
+ return 1;
84
+ // Each repeat a point at the least, as CSS Grid 7.2.3.2 has it, so one of tracks with no fixed
85
+ // size, `minmax(0, 1fr)`, comes to an end: a point wide each, as many as fit.
86
+ const size = Math.max(repeated.reduce((sum, each) => sum + fixedSize(each), 0), 1);
87
+ let count = 1;
88
+ const total = (n) => otherSize + n * size + (others + n * repeated.length - 1) * gap;
89
+ while (count < CAP && total(count + 1) <= whole)
90
+ count += 1;
91
+ return count;
92
+ }
93
+ const nameLine = (names, name, at) => names.set(name, [...(names.get(name) ?? []), at]);
94
+ const cells = (list, whole, autoFit = false) => tokens(list).map((word) => word.startsWith('[') ? words(word.slice(1, -1)) : track(word, whole, autoFit));
95
+ const tracksOf = (all) => all.filter((each) => !Array.isArray(each));
96
+ /** A `grid-template-columns` or `grid-template-rows` value as tracks, and the names of its lines. */
97
+ function template(list, whole, gap) {
98
+ const names = new Map();
99
+ const tracks = [];
100
+ if (!list || list === 'none')
101
+ return { tracks, names };
102
+ const parts = tokens(list).map((word) => {
103
+ const repeat = /^repeat\(\s*([^,]+),(.*)\)$/.exec(word);
104
+ if (!repeat)
105
+ return { count: 1, cells: cells(word, whole) };
106
+ const count = repeat[1].trim();
107
+ const auto = count === 'auto-fill' || count === 'auto-fit';
108
+ return {
109
+ count: auto ? 'auto' : Math.min(Number(count), CAP),
110
+ cells: cells(repeat[2], whole, count === 'auto-fit'),
111
+ };
112
+ });
113
+ const fixed = parts.filter((part) => part.count !== 'auto');
114
+ const size = (part) => tracksOf(part.cells).reduce((sum, each) => sum + fixedSize(each), 0);
115
+ const others = fixed.reduce((sum, part) => sum + part.count * tracksOf(part.cells).length, 0);
116
+ const otherSize = fixed.reduce((sum, part) => sum + part.count * size(part), 0);
117
+ for (const part of parts) {
118
+ const count = part.count === 'auto'
119
+ ? repetitions(tracksOf(part.cells), others, otherSize, whole, gap)
120
+ : part.count;
121
+ for (let i = 0; i < count; i++) {
122
+ for (const cell of part.cells) {
123
+ if (Array.isArray(cell))
124
+ for (const name of cell)
125
+ nameLine(names, name, tracks.length);
126
+ else
127
+ tracks.push({ ...cell });
128
+ }
129
+ }
130
+ }
131
+ return { tracks, names };
132
+ }
133
+ /**
134
+ * `grid-template-areas`: how many tracks its rows of names make on each axis, and the lines
135
+ * each area gives a name to, `head-start` and `head-end`, which is what an item is placed by.
136
+ */
137
+ function templateAreas(text) {
138
+ const columns = new Map();
139
+ const rows = new Map();
140
+ const grid = [...(text ?? '').matchAll(/(["'])(.*?)\1/g)].map((row) => words(row[2]));
141
+ const edges = new Map();
142
+ grid.forEach((row, y) => row.forEach((name, x) => {
143
+ if (name.startsWith('.'))
144
+ return;
145
+ const at = edges.get(name) ?? { left: x, right: x, top: y, bottom: y };
146
+ edges.set(name, {
147
+ left: Math.min(at.left, x),
148
+ right: Math.max(at.right, x),
149
+ top: Math.min(at.top, y),
150
+ bottom: Math.max(at.bottom, y),
151
+ });
152
+ }));
153
+ for (const [name, at] of edges) {
154
+ nameLine(columns, `${name}-start`, at.left);
155
+ nameLine(columns, `${name}-end`, at.right + 1);
156
+ nameLine(rows, `${name}-start`, at.top);
157
+ nameLine(rows, `${name}-end`, at.bottom + 1);
158
+ }
159
+ return {
160
+ columns,
161
+ rows,
162
+ across: Math.max(0, ...grid.map((row) => row.length)),
163
+ down: grid.length,
164
+ };
165
+ }
166
+ /** The names of a track list's lines and of the areas over it, as one. */
167
+ function allNames(lines, areas) {
168
+ const out = new Map(lines);
169
+ for (const [name, at] of areas)
170
+ out.set(name, [...(out.get(name) ?? []), ...at]);
171
+ return out;
172
+ }
173
+ /**
174
+ * One side of a placement. A name is the line an area starts or ends at on that side, `head`
175
+ * for `head-start`, or failing that a line of that name: the first, or the one counted to. A
176
+ * name no line has is the first line past the `explicit` tracks, as every line out there has it.
177
+ */
178
+ function line(value, names, edge, explicit) {
179
+ const text = (value ?? 'auto').trim();
180
+ const span = /^span\s+(\d+)$/.exec(text);
181
+ if (span)
182
+ return { span: Math.min(Number(span[1]), CAP) };
183
+ if (/^-?\d+$/.test(text)) {
184
+ return Number(text) === 0 ? {} : { line: Math.max(Math.min(Number(text), CAP), -CAP) };
185
+ }
186
+ const named = /^([A-Za-z_][\w-]*)(?:\s+(-?\d+))?$/.exec(text);
187
+ if (!named || text === 'auto')
188
+ return {};
189
+ const at = namedLine(names, named[1], named[2], edge);
190
+ if (at !== undefined)
191
+ return { line: at + 1 };
192
+ return { line: Number(named[2] ?? 1) > 0 ? explicit + 2 : -(explicit + 2) };
193
+ }
194
+ /** The line a name says: an area's edge on that side, or the line of that name counted to. */
195
+ function namedLine(names, name, count, edge) {
196
+ const lines = (count === undefined ? names.get(`${name}-${edge}`) : undefined) ?? names.get(name);
197
+ const nth = Number(count ?? 1);
198
+ return nth > 0 ? lines?.[nth - 1] : lines?.at(nth);
199
+ }
200
+ const isName = (value) => /^[A-Za-z_]/.test(value ?? '') && value !== 'auto';
201
+ /**
202
+ * `grid-area` as its four sides, each `auto` unless written: a name alone is the area of that
203
+ * name, so it stands for the sides left out too.
204
+ */
205
+ function areaSides(style) {
206
+ const [rowStart, columnStart, rowEnd, columnEnd] = (style['grid-area'] ?? '')
207
+ .split('/')
208
+ .map((each) => each.trim() || undefined);
209
+ const column = columnStart ?? (isName(rowStart) ? rowStart : undefined);
210
+ return {
211
+ row: [rowStart, rowEnd ?? (isName(rowStart) ? rowStart : undefined)],
212
+ column: [column, columnEnd ?? (isName(column) ? column : undefined)],
213
+ };
214
+ }
215
+ /** The two sides an item's `grid-column` or `grid-row` is written as, each with its longhand. */
216
+ function sides(style, axis) {
217
+ const [first, second] = style[`grid-${axis}`]?.split('/') ?? areaSides(style)[axis];
218
+ // One name is both sides of it, `content` for `content-start / content-end`.
219
+ const both = isName(first?.trim()) ? first : undefined;
220
+ return [style[`grid-${axis}-start`] ?? first, style[`grid-${axis}-end`] ?? second ?? both];
221
+ }
222
+ /** An item's `grid-column` or `grid-row`, and its `-start` and `-end`, as a span of tracks. */
223
+ function span(style, axis, explicit, names) {
224
+ const [first, last] = sides(style, axis);
225
+ const start = line(first, names, 'start', explicit);
226
+ const end = line(last, names, 'end', explicit);
227
+ const index = (n) => (n > 0 ? n - 1 : explicit + 1 + n);
228
+ if (start.line !== undefined && end.line !== undefined) {
229
+ const [a, b] = [index(start.line), index(end.line)].sort((x, y) => x - y);
230
+ return { start: a, span: Math.max(b - a, 1) };
231
+ }
232
+ if (start.line !== undefined)
233
+ return { start: index(start.line), span: end.span ?? 1 };
234
+ if (end.line !== undefined) {
235
+ const size = start.span ?? 1;
236
+ return { start: index(end.line) - size, span: size };
237
+ }
238
+ return { span: start.span ?? end.span ?? 1 };
239
+ }
240
+ /**
241
+ * The tracks an out-of-flow child's placement names on one axis: the one its start line is
242
+ * before and the one its end line is after, each nothing where it is `auto` or names no line
243
+ * the grid has. A span is counted from the line on the other side, and is `auto` without one.
244
+ */
245
+ function namedTracks(style, axis, grid) {
246
+ const [first, last] = sides(style, axis);
247
+ const start = line(first, grid.names, 'start', grid.explicit);
248
+ const end = line(last, grid.names, 'end', grid.explicit);
249
+ const index = (n) => (n > 0 ? n - 1 : grid.explicit + 1 + n) + grid.before;
250
+ let [from, to] = spanned(start.line === undefined ? undefined : index(start.line), end.line === undefined ? undefined : index(end.line), start.span, end.span);
251
+ // Written the wrong way round they are swapped, and the same line twice is only a start.
252
+ if (from !== undefined && to !== undefined && from > to)
253
+ [from, to] = [to, from];
254
+ if (from === to)
255
+ to = undefined;
256
+ const known = (at) => at !== undefined && at >= 0 && at <= grid.count ? at : undefined;
257
+ return { start: known(from), end: known(to) };
258
+ }
259
+ /** Two lines, each counted from the other where it is a span and the other is a line. */
260
+ function spanned(from, to, before, after) {
261
+ if (from === undefined && to !== undefined && before)
262
+ return [to - before, to];
263
+ if (to === undefined && from !== undefined && after)
264
+ return [from, from + after];
265
+ return [from, to];
266
+ }
267
+ /**
268
+ * The auto-placement algorithm, along whichever axis the grid fills first. `minor` is the axis
269
+ * with a fixed number of tracks, columns in a row flow, and `major` the one that grows.
270
+ */
271
+ function autoPlace(spans, explicitMinor, dense) {
272
+ let minorCount = Math.max(explicitMinor, 1, ...spans.map(({ minor }) => (minor.start ?? 0) + minor.span));
273
+ const taken = new Set();
274
+ const free = (major, minor, each) => {
275
+ for (let i = major; i < major + each.major.span; i++) {
276
+ for (let j = minor; j < minor + each.minor.span; j++)
277
+ if (taken.has(`${i},${j}`))
278
+ return false;
279
+ }
280
+ return true;
281
+ };
282
+ const out = [];
283
+ const take = (index, major, minor) => {
284
+ const each = spans[index];
285
+ for (let i = major; i < major + each.major.span; i++) {
286
+ for (let j = minor; j < minor + each.minor.span; j++)
287
+ taken.add(`${i},${j}`);
288
+ }
289
+ minorCount = Math.max(minorCount, minor + each.minor.span);
290
+ out[index] = { major, minor };
291
+ };
292
+ spans.forEach((each, i) => {
293
+ if (each.major.start !== undefined && each.minor.start !== undefined) {
294
+ take(i, each.major.start, each.minor.start);
295
+ }
296
+ });
297
+ // Locked to a line of the major axis: the first room along it, after its earlier items.
298
+ const along = new Map();
299
+ spans.forEach((each, i) => {
300
+ if (each.major.start === undefined || each.minor.start !== undefined)
301
+ return;
302
+ let minor = dense ? 0 : (along.get(each.major.start) ?? 0);
303
+ while (!free(each.major.start, minor, each))
304
+ minor += 1;
305
+ along.set(each.major.start, minor + each.minor.span);
306
+ take(i, each.major.start, minor);
307
+ });
308
+ const cursor = { major: 0, minor: 0 };
309
+ spans.forEach((each, i) => {
310
+ if (each.major.start !== undefined)
311
+ return;
312
+ if (dense)
313
+ Object.assign(cursor, { major: 0, minor: 0 });
314
+ if (each.minor.start !== undefined) {
315
+ if (each.minor.start < cursor.minor)
316
+ cursor.major += 1;
317
+ cursor.minor = each.minor.start;
318
+ while (!free(cursor.major, cursor.minor, each))
319
+ cursor.major += 1;
320
+ }
321
+ else {
322
+ while (cursor.minor + each.minor.span > minorCount ||
323
+ !free(cursor.major, cursor.minor, each)) {
324
+ cursor.minor += 1;
325
+ if (cursor.minor + each.minor.span > minorCount) {
326
+ cursor.major += 1;
327
+ cursor.minor = 0;
328
+ }
329
+ }
330
+ }
331
+ take(i, cursor.major, cursor.minor);
332
+ });
333
+ return out;
334
+ }
335
+ function place(styles, container, explicit, names) {
336
+ const flow = container['grid-auto-flow'] ?? 'row';
337
+ const byColumn = flow.includes('column');
338
+ const all = styles.map((style) => ({
339
+ column: span(style, 'column', explicit.columns, names.columns),
340
+ row: span(style, 'row', explicit.rows, names.rows),
341
+ }));
342
+ // A line before the first explicit one starts an implicit track there: count from it instead.
343
+ const before = {
344
+ columns: Math.max(0, ...all.map(({ column }) => -(column.start ?? 0))),
345
+ rows: Math.max(0, ...all.map(({ row }) => -(row.start ?? 0))),
346
+ };
347
+ for (const each of all) {
348
+ if (each.column.start !== undefined)
349
+ each.column.start += before.columns;
350
+ if (each.row.start !== undefined)
351
+ each.row.start += before.rows;
352
+ }
353
+ const spans = all.map((each) => byColumn ? { major: each.column, minor: each.row } : { major: each.row, minor: each.column });
354
+ const explicitMinor = byColumn ? explicit.rows + before.rows : explicit.columns + before.columns;
355
+ const cells = autoPlace(spans, explicitMinor, flow.includes('dense'));
356
+ const items = cells.map((cell, i) => ({
357
+ column: byColumn ? cell.major : cell.minor,
358
+ row: byColumn ? cell.minor : cell.major,
359
+ columns: all[i].column.span,
360
+ rows: all[i].row.span,
361
+ }));
362
+ return {
363
+ items,
364
+ before,
365
+ columns: Math.max(explicit.columns + before.columns, ...items.map((i) => i.column + i.columns)),
366
+ rows: Math.max(explicit.rows + before.rows, ...items.map((i) => i.row + i.rows)),
367
+ };
368
+ }
369
+ /** The explicit tracks, with implicit ones from `grid-auto-*` before and after them. */
370
+ function withImplicit(explicit, auto, whole, before, count) {
371
+ const pattern = words(auto ?? 'auto');
372
+ const implicit = (index) => track(pattern.at(index % pattern.length), whole);
373
+ const leading = Array.from({ length: before }, (_, i) => implicit(-(before - i)));
374
+ const after = Math.max(count - before - explicit.length, 0);
375
+ return [...leading, ...explicit, ...Array.from({ length: after }, (_, i) => implicit(i))];
376
+ }
377
+ const intrinsic = (each) => each.kind !== 'length' && each.kind !== 'fr';
378
+ const flexible = (each) => each.max.kind === 'fr';
379
+ const sum = (values) => values.reduce((a, b) => a + b, 0);
380
+ function initialise(tracks) {
381
+ for (const each of tracks) {
382
+ each.base = each.min.kind === 'length' ? each.min.px : 0;
383
+ each.limit = each.max.kind === 'length' ? Math.max(each.max.px, each.base) : Infinity;
384
+ each.growable = false;
385
+ }
386
+ }
387
+ /** An item in one track sets that track's size by itself. */
388
+ function sizeFromSingle(each, item) {
389
+ if (each.min.kind === 'min-content')
390
+ each.base = Math.max(each.base, item.min);
391
+ else if (each.min.kind === 'max-content')
392
+ each.base = Math.max(each.base, item.max);
393
+ else if (each.min.kind === 'auto')
394
+ each.base = Math.max(each.base, item.minimum);
395
+ const limit = each.limit === Infinity ? 0 : each.limit;
396
+ if (each.max.kind === 'min-content')
397
+ each.limit = Math.max(limit, item.min);
398
+ else if (each.max.kind === 'fit-content') {
399
+ each.limit = Math.max(limit, Math.min(item.max, Math.max(each.max.limit, item.minimum)));
400
+ }
401
+ else if (intrinsic(each.max))
402
+ each.limit = Math.max(limit, item.max);
403
+ each.limit = Math.max(each.limit, each.base);
404
+ }
405
+ /**
406
+ * Share `space` over `tracks`, each up to its `room`, an equal part at a time (11.5.1), and
407
+ * answer how much each takes. What is left once all are full goes to `beyond`. Flexible tracks
408
+ * share by their flex factors instead, when those come to something.
409
+ */
410
+ function share(space, tracks, room, beyond) {
411
+ const factors = tracks.every(flexible)
412
+ ? tracks.map((each) => each.max.factor)
413
+ : [];
414
+ const weighted = sum(factors) > 0;
415
+ const weight = (each) => (weighted ? factors[tracks.indexOf(each)] : 1);
416
+ const taken = new Map(tracks.map((each) => [each, 0]));
417
+ let open = tracks.filter((each) => room(each) > 0 && weight(each) > 0);
418
+ let left = space;
419
+ while (left > 1e-9 && open.length) {
420
+ const whole = sum(open.map(weight));
421
+ const parts = open.map((each) => (left * weight(each)) / whole);
422
+ open.forEach((each, i) => {
423
+ const take = Math.min(parts[i], room(each) - taken.get(each));
424
+ taken.set(each, taken.get(each) + take);
425
+ left -= take;
426
+ });
427
+ open = open.filter((each) => room(each) - taken.get(each) > 1e-9);
428
+ }
429
+ if (left > 1e-9 && beyond.length) {
430
+ for (const each of beyond)
431
+ taken.set(each, (taken.get(each) ?? 0) + left / beyond.length);
432
+ }
433
+ return taken;
434
+ }
435
+ const PHASES = [
436
+ {
437
+ affects: (t) => intrinsic(t.min),
438
+ size: (item) => item.minimum,
439
+ target: 'base',
440
+ beyond: (t) => intrinsic(t.max),
441
+ },
442
+ {
443
+ affects: (t) => t.min.kind === 'min-content' || t.min.kind === 'max-content',
444
+ size: (item) => item.min,
445
+ target: 'base',
446
+ beyond: (t) => t.max.kind === 'min-content' || t.max.kind === 'max-content',
447
+ },
448
+ {
449
+ affects: (t) => t.min.kind === 'max-content',
450
+ size: (item) => item.max,
451
+ target: 'base',
452
+ beyond: (t) => t.max.kind === 'max-content',
453
+ },
454
+ {
455
+ affects: (t) => intrinsic(t.max),
456
+ size: (item) => item.min,
457
+ target: 'limit',
458
+ beyond: () => true,
459
+ },
460
+ {
461
+ affects: (t) => ['auto', 'max-content', 'fit-content'].includes(t.max.kind),
462
+ size: (item) => item.max,
463
+ target: 'limit',
464
+ beyond: () => true,
465
+ },
466
+ ];
467
+ /** One phase of 11.5 for a group of items: grow the affected tracks to fit each item. */
468
+ function grow(tracks, items, gap, phase, only) {
469
+ const planned = new Map();
470
+ const current = (each) => phase.target === 'base' ? each.base : each.limit === Infinity ? each.base : each.limit;
471
+ for (const item of items) {
472
+ const spanned = tracks.slice(item.start, item.start + item.span);
473
+ const affected = spanned.filter((each) => phase.affects(each) && (only?.(each) ?? true));
474
+ if (!affected.length)
475
+ continue;
476
+ const space = phase.size(item) - sum(spanned.map(current)) - gap * (item.span - 1);
477
+ if (space <= 0)
478
+ continue;
479
+ const room = (each) => {
480
+ if (phase.target === 'base')
481
+ return each.limit - each.base;
482
+ return each.limit === Infinity || each.growable ? Infinity : 0;
483
+ };
484
+ const beyond = affected.filter(phase.beyond);
485
+ const taken = share(space, affected, room, beyond.length ? beyond : affected);
486
+ for (const [each, amount] of taken)
487
+ planned.set(each, Math.max(planned.get(each) ?? 0, amount));
488
+ }
489
+ for (const [each, amount] of planned) {
490
+ if (phase.target === 'base') {
491
+ each.base += amount;
492
+ }
493
+ else {
494
+ each.growable = each.limit === Infinity;
495
+ each.limit = (each.limit === Infinity ? each.base : each.limit) + amount;
496
+ }
497
+ }
498
+ }
499
+ function growAll(tracks, items, gap, only) {
500
+ for (const phase of PHASES) {
501
+ if (only && phase.target === 'limit')
502
+ continue;
503
+ grow(tracks, items, gap, phase, only);
504
+ if (phase.target === 'base')
505
+ for (const each of tracks)
506
+ each.limit = Math.max(each.limit, each.base);
507
+ }
508
+ }
509
+ /** 11.5: size each track to the items in it, the items in one track first. */
510
+ function resolveIntrinsic(tracks, items, gap) {
511
+ const crossesFlexible = (item) => tracks.slice(item.start, item.start + item.span).some(flexible);
512
+ const rigid = items.filter((item) => !crossesFlexible(item));
513
+ for (const item of rigid.filter((each) => each.span === 1))
514
+ sizeFromSingle(tracks[item.start], item);
515
+ const sizes = [...new Set(rigid.map((item) => item.span))]
516
+ .filter((n) => n > 1)
517
+ .sort((a, b) => a - b);
518
+ for (const size of sizes) {
519
+ growAll(tracks, rigid.filter((item) => item.span === size), gap);
520
+ }
521
+ growAll(tracks, items.filter(crossesFlexible), gap, flexible);
522
+ for (const each of tracks) {
523
+ if (each.max.kind === 'fit-content') {
524
+ each.limit = Math.min(each.limit, Math.max(each.max.limit, each.base));
525
+ }
526
+ if (each.limit === Infinity)
527
+ each.limit = each.base;
528
+ }
529
+ }
530
+ /** 11.7.1: the size of one `fr` that fills `space` over these tracks. */
531
+ function fraction(tracks, space) {
532
+ const rigid = new Set(tracks.filter((each) => !flexible(each)));
533
+ for (;;) {
534
+ const left = space - sum([...rigid].map((each) => each.base));
535
+ const factors = sum(tracks
536
+ .filter((each) => !rigid.has(each))
537
+ .map((each) => each.max.factor));
538
+ const size = left / Math.max(factors, 1);
539
+ const short = tracks.find((each) => !rigid.has(each) && size * each.max.factor < each.base);
540
+ if (!short)
541
+ return size;
542
+ rigid.add(short);
543
+ }
544
+ }
545
+ function expandFlexible(tracks, items, gap, available) {
546
+ const flex = tracks.filter(flexible);
547
+ if (!flex.length)
548
+ return;
549
+ const factor = (each) => each.max.factor;
550
+ const gaps = gap * (tracks.length - 1);
551
+ let size;
552
+ if (available !== undefined) {
553
+ size = fraction(tracks, available - gaps);
554
+ }
555
+ else {
556
+ size = Math.max(0, ...flex.map((each) => each.base / Math.max(factor(each), 1)));
557
+ for (const item of items) {
558
+ const spanned = tracks.slice(item.start, item.start + item.span);
559
+ if (spanned.some(flexible)) {
560
+ size = Math.max(size, fraction(spanned, item.max - gap * (item.span - 1)));
561
+ }
562
+ }
563
+ }
564
+ for (const each of flex)
565
+ each.base = Math.max(each.base, size * factor(each));
566
+ }
567
+ /** Sizes on `tracks` for these items, in `available` room or in as much as the content wants. */
568
+ function sizeTracks(tracks, items, gap, available, stretch) {
569
+ initialise(tracks);
570
+ resolveIntrinsic(tracks, items, gap);
571
+ const free = () => (available ?? Infinity) - sum(tracks.map((each) => each.base)) - gap * (tracks.length - 1);
572
+ // 11.6: grow every track towards its growth limit with what is free.
573
+ if (available === undefined) {
574
+ for (const each of tracks)
575
+ each.base = each.limit;
576
+ }
577
+ else if (free() > 0) {
578
+ const taken = share(free(), tracks, (each) => each.limit - each.base, []);
579
+ for (const [each, amount] of taken)
580
+ each.base += amount;
581
+ }
582
+ expandFlexible(tracks, items, gap, available);
583
+ // 11.8: `auto` tracks take what is still free.
584
+ const autos = tracks.filter((each) => each.max.kind === 'auto');
585
+ if (stretch && available !== undefined && free() > 0 && autos.length) {
586
+ const part = free() / autos.length;
587
+ for (const each of autos)
588
+ each.base += part;
589
+ }
590
+ }
591
+ // Alignment
592
+ /** The last word of an alignment value, with `flex-` and `self-` read as the plain keyword. */
593
+ function alignment(value, fallback) {
594
+ const word = (value ?? fallback)
595
+ .trim()
596
+ .split(/\s+/)
597
+ .at(-1)
598
+ .replace(/^(flex|self)-/, '');
599
+ return word === 'normal' || word === 'auto' ? fallback : word;
600
+ }
601
+ /** `safe` keeps what overflows at the start, where it can still be scrolled to. */
602
+ const isSafe = (value) => /^\s*safe\b/.test(value ?? '');
603
+ /** What is free around and between `count` tracks, for a content-distribution keyword. */
604
+ function distribute(align, free, count) {
605
+ if (align === 'end')
606
+ return { offset: free, between: 0 };
607
+ if (align === 'center')
608
+ return { offset: free / 2, between: 0 };
609
+ if (free <= 0 || !count)
610
+ return { offset: 0, between: 0 };
611
+ if (align === 'space-between' && count > 1)
612
+ return { offset: 0, between: free / (count - 1) };
613
+ if (align === 'space-around')
614
+ return { offset: free / count / 2, between: free / count };
615
+ if (align === 'space-evenly')
616
+ return { offset: free / (count + 1), between: free / (count + 1) };
617
+ return { offset: 0, between: 0 };
618
+ }
619
+ /** Where each track starts, with what is free shared out as `justify-content` or `align-content` say. */
620
+ function positions(tracks, gap, available, value) {
621
+ const live = tracks.filter((each) => !each.collapsed);
622
+ const used = sum(live.map((each) => each.base)) + gap * Math.max(live.length - 1, 0);
623
+ const free = available === undefined ? 0 : available - used;
624
+ const align = isSafe(value) && free < 0 ? 'start' : alignment(value, 'start');
625
+ const { offset, between } = distribute(align, free, live.length);
626
+ const starts = [];
627
+ let at = offset;
628
+ for (const each of tracks) {
629
+ starts.push(at);
630
+ if (!each.collapsed)
631
+ at += each.base + gap + between;
632
+ }
633
+ return { starts, used, between };
634
+ }
635
+ /** An `auto-fit` track nothing is placed in takes no room, and neither does its gap. */
636
+ function collapseEmpty(tracks, items) {
637
+ tracks.forEach((each, i) => {
638
+ each.collapsed =
639
+ each.autoFit && !items.some((item) => item.start <= i && i < item.start + item.span);
640
+ if (each.collapsed)
641
+ Object.assign(each, { min: { kind: 'length', px: 0 }, max: { kind: 'length', px: 0 } });
642
+ });
643
+ }
644
+ /** How far into its room an item starts: pushed by an `auto` margin, or by its alignment. */
645
+ function leading(free, self, autoStart, autoEnd) {
646
+ if (autoStart)
647
+ return Math.max(free, 0) / (autoEnd ? 2 : 1);
648
+ if (autoEnd || (self.safe && free < 0))
649
+ return 0;
650
+ if (self.align === 'end')
651
+ return free;
652
+ return self.align === 'center' ? free / 2 : 0;
653
+ }
654
+ /** One axis of an item in its area: its size, and where its border box starts. */
655
+ function inArea(style, axis, area, self, natural) {
656
+ const marginStart = style[`margin-${axis.start}`];
657
+ const marginEnd = style[`margin-${axis.end}`];
658
+ // A percentage margin is of the area's width, on either axis.
659
+ const before = length(marginStart, area.inline) ?? 0;
660
+ const margins = before + (length(marginEnd, area.inline) ?? 0);
661
+ const autoMargin = marginStart === 'auto' || marginEnd === 'auto';
662
+ const room = area.size - margins;
663
+ let size = length(style[axis.size], area.size);
664
+ if (size === undefined)
665
+ size = self.align === 'stretch' && !autoMargin ? room : natural(room);
666
+ size = Math.min(size, length(style[`max-${axis.size}`], area.size) ?? Infinity);
667
+ size = Math.max(size, length(style[`min-${axis.size}`], area.size) ?? 0, 0);
668
+ const lead = leading(room - size, self, marginStart === 'auto', marginEnd === 'auto');
669
+ return { start: area.start + before + lead, size };
670
+ }
671
+ // The whole grid
672
+ const X = { size: 'width', start: 'left', end: 'right' };
673
+ const Y = { size: 'height', start: 'top', end: 'bottom' };
674
+ /**
675
+ * What an item's content asks of the tracks on one axis, margins included. A percentage margin
676
+ * is of the width of the item's area, which `inline` is once the columns are sized: down the
677
+ * rows. Across the columns it is not known yet, and is none.
678
+ */
679
+ function contribution(style, axis, at, content, inline) {
680
+ const fixed = length(style[axis.size], undefined);
681
+ const low = length(style[`min-${axis.size}`], undefined);
682
+ const high = length(style[`max-${axis.size}`], undefined) ?? Infinity;
683
+ // The content is measured only when a track asks for it, and never for a size already set.
684
+ const clamp = (value) => Math.max(Math.min(fixed ?? value(), high), low ?? 0);
685
+ const margins = (length(style[`margin-${axis.start}`], inline) ?? 0) +
686
+ (length(style[`margin-${axis.end}`], inline) ?? 0);
687
+ // The least it can be: its own size where it has one, and otherwise its minimum, which is 0
688
+ // and not `auto` for a view (as `@ng-native/web`'s reset makes it in a browser), so an item
689
+ // with no size of its own is never held to its content's.
690
+ const minimum = fixed === undefined ? Math.min(low ?? 0, high) : clamp(() => fixed);
691
+ return {
692
+ ...at,
693
+ get min() {
694
+ return clamp(content.min) + margins;
695
+ },
696
+ get max() {
697
+ return clamp(content.max) + margins;
698
+ },
699
+ minimum: minimum + margins,
700
+ };
701
+ }
702
+ /**
703
+ * How far down each item aligned to a baseline is moved so that the baselines of those that
704
+ * start in one row line up: under the one whose baseline is furthest down. The move is added to
705
+ * the item's top margin, so its row is as tall as the moved items need.
706
+ *
707
+ * ponytail: `last baseline` sits at the start, and so does an item with a percentage or `auto`
708
+ * top margin. An item spanning rows is aligned in the row it starts in.
709
+ */
710
+ function baselineShifts(container, items, rows, widthOf) {
711
+ const groups = new Map();
712
+ items.forEach((item, i) => {
713
+ const own = item.style['align-self'];
714
+ const value = isAuto(own) || own === 'normal' ? container['align-items'] : own;
715
+ const margin = item.style['margin-top'];
716
+ if (!item.baseline || !/^(first\s+)?baseline$/.test(value?.trim() ?? ''))
717
+ return;
718
+ if (margin !== undefined && length(margin, undefined) === undefined)
719
+ return;
720
+ groups.set(rows[i].start, [...(groups.get(rows[i].start) ?? []), i]);
721
+ });
722
+ const shifts = items.map(() => 0);
723
+ for (const group of groups.values()) {
724
+ const above = group.map((i) => px(items[i].style['margin-top']) + items[i].baseline(widthOf(i)));
725
+ const lowest = Math.max(...above);
726
+ group.forEach((i, at) => (shifts[i] = lowest - above[at]));
727
+ }
728
+ return shifts;
729
+ }
730
+ /** `aspect-ratio` as width over height: `2`, or `16 / 9`. Nothing for `auto` or none. */
731
+ function ratioOf(value) {
732
+ const [width, height = '1'] = (value ?? '').split('/').map((each) => Number.parseFloat(each));
733
+ const ratio = Number(width) / Number(height);
734
+ return Number.isFinite(ratio) && ratio > 0 ? ratio : undefined;
735
+ }
736
+ /** The width an item's ratio and its own height make, where it has that height and no width. */
737
+ function ratioWidth(style) {
738
+ const ratio = ratioOf(style['aspect-ratio']);
739
+ const height = length(style['height'], undefined);
740
+ return ratio && height !== undefined && isAuto(style['width']) ? height * ratio : undefined;
741
+ }
742
+ /**
743
+ * An item with every size in its style that of its border box, as the layout reads them: one
744
+ * written of its content has its padding and border added, and the least it may be is no less
745
+ * than those, as a box with `min-width: 0` is in a browser.
746
+ * A percentage of the content box is the share of the area with the frame added: see `length`.
747
+ */
748
+ function asBorderBox(item) {
749
+ const frame = item.frame;
750
+ if (!frame || (!frame.width && !frame.height))
751
+ return item;
752
+ const style = { ...item.style };
753
+ const content = style['box-sizing'] === 'content-box';
754
+ framed(style, 'width', frame.width, content);
755
+ framed(style, 'height', frame.height, content);
756
+ return { ...item, style };
757
+ }
758
+ /** One axis of `asBorderBox`: `add` is the item's padding and border along it. */
759
+ function framed(style, axis, add, content) {
760
+ const least = style[`min-${axis}`];
761
+ // A share of the area is known once the area is: a floor is left to the frame added to it.
762
+ if (least === undefined || !least.endsWith('%')) {
763
+ const own = length(least, undefined) ?? 0;
764
+ style[`min-${axis}`] = `${Math.max(content ? own + add : own, add)}px`;
765
+ }
766
+ for (const key of [axis, `max-${axis}`, ...(least?.endsWith('%') ? [`min-${axis}`] : [])]) {
767
+ const value = style[key];
768
+ if (!content || value === undefined)
769
+ continue;
770
+ if (value.endsWith('%'))
771
+ style[key] = `${value} + ${add}px`;
772
+ else {
773
+ const points = length(value, undefined);
774
+ if (points !== undefined)
775
+ style[key] = `${points + add}px`;
776
+ }
777
+ }
778
+ }
779
+ function gapOf(style, axis, whole) {
780
+ const [row, column = row] = (style['gap'] ?? '').split(/\s+/);
781
+ return length(style[`${axis}-gap`] ?? (axis === 'row' ? row : column), whole) ?? 0;
782
+ }
783
+ const usesPercent = (style, axis) => [`grid-template-${axis}s`, `grid-auto-${axis}s`, `${axis}-gap`, 'gap'].some((property) => style[property]?.includes('%'));
784
+ function solve(style, given, width, height) {
785
+ // An item with a ratio and a height of its own is as wide as the two make it.
786
+ const items = given.map(asBorderBox).map((item) => {
787
+ const wide = ratioWidth(item.style);
788
+ return wide === undefined ? item : { ...item, style: { ...item.style, width: `${wide}px` } };
789
+ });
790
+ const columnGap = gapOf(style, 'column', width);
791
+ const rowGap = gapOf(style, 'row', height);
792
+ const listedColumns = template(style['grid-template-columns'], width, columnGap);
793
+ const listedRows = template(style['grid-template-rows'], height, rowGap);
794
+ const explicitColumns = listedColumns.tracks;
795
+ const explicitRows = listedRows.tracks;
796
+ // Areas make tracks of their own where the lists have fewer, sized as implicit ones are.
797
+ const areas = templateAreas(style['grid-template-areas']);
798
+ const explicit = {
799
+ columns: Math.max(explicitColumns.length, areas.across),
800
+ rows: Math.max(explicitRows.length, areas.down),
801
+ };
802
+ const names = {
803
+ columns: allNames(listedColumns.names, areas.columns),
804
+ rows: allNames(listedRows.names, areas.rows),
805
+ };
806
+ const placement = place(items.map((item) => item.style), style, explicit, names);
807
+ const { before } = placement;
808
+ const columns = withImplicit(explicitColumns, style['grid-auto-columns'], width, before.columns, placement.columns);
809
+ const rows = withImplicit(explicitRows, style['grid-auto-rows'], height, before.rows, placement.rows);
810
+ const across = placement.items.map((each) => ({ start: each.column, span: each.columns }));
811
+ const down = placement.items.map((each) => ({ start: each.row, span: each.rows }));
812
+ collapseEmpty(columns, across);
813
+ collapseEmpty(rows, down);
814
+ const justifyContent = alignment(style['justify-content'], 'stretch');
815
+ sizeTracks(columns, items.map((item, i) => contribution(item.style, X, across[i], {
816
+ min: () => item.measure('min').width,
817
+ max: () => item.measure('max').width,
818
+ })), columnGap, width, justifyContent === 'stretch');
819
+ const x = positions(columns, columnGap, width, style['justify-content']);
820
+ const area = (starts, tracks, at, gap) => {
821
+ const live = tracks.slice(at.start, at.start + at.span).filter((each) => !each.collapsed);
822
+ return {
823
+ start: starts[at.start],
824
+ size: sum(live.map((each) => each.base)) + gap * Math.max(live.length - 1, 0),
825
+ };
826
+ };
827
+ const selfAlign = (item, axis) => {
828
+ const value = item.style[`${axis}-self`];
829
+ const own = isAuto(value) || value === 'normal' ? style[`${axis}-items`] : value;
830
+ return { align: alignment(own, 'stretch'), safe: isSafe(own) };
831
+ };
832
+ const columnAreas = items.map((_, i) => area(x.starts, columns, across[i], columnGap + x.between));
833
+ const horizontal = items.map((item, i) => inArea(item.style, X, { ...columnAreas[i], inline: columnAreas[i].size }, selfAlign(item, 'justify'), (room) => Math.min(item.measure('max').width, Math.max(item.measure('min').width, room))));
834
+ // An item with a ratio and no height of its own is as tall as its width makes it, and is not
835
+ // stretched: held to a minimum or a maximum height, its width follows where it had none.
836
+ const tall = items.map((item, i) => {
837
+ const ratio = ratioOf(item.style['aspect-ratio']);
838
+ if (!ratio || !isAuto(item.style['height']))
839
+ return undefined;
840
+ const wanted = horizontal[i].size / ratio;
841
+ const height = Math.max(Math.min(wanted, length(item.style['max-height'], undefined) ?? Infinity), length(item.style['min-height'], undefined) ?? 0);
842
+ if (height !== wanted && isAuto(item.style['width']))
843
+ horizontal[i].size = height * ratio;
844
+ return height;
845
+ });
846
+ const lifted = baselineShifts(style, items, down, (i) => horizontal[i].size);
847
+ const upright = items.map((item, i) => ({
848
+ ...item.style,
849
+ ...(tall[i] === undefined ? {} : { height: `${tall[i]}px` }),
850
+ ...(lifted[i] ? { 'margin-top': `${px(item.style['margin-top']) + lifted[i]}px` } : {}),
851
+ }));
852
+ const heights = items.map((item, i) => () => tall[i] ?? item.measure(horizontal[i].size).height);
853
+ sizeTracks(rows, items.map((_, i) => contribution(upright[i], Y, down[i], { min: heights[i], max: heights[i] }, columnAreas[i].size)), rowGap, height, alignment(style['align-content'], 'stretch') === 'stretch');
854
+ const y = positions(rows, rowGap, height, style['align-content']);
855
+ const vertical = items.map((item, i) => inArea(upright[i], Y, { ...area(y.starts, rows, down[i], rowGap + y.between), inline: columnAreas[i].size }, selfAlign(item, 'align'), heights[i]));
856
+ /** Where a line is: the start of the track after it, or the end of the one before it. */
857
+ const edge = (starts, tracks, at, side) => {
858
+ if (at === undefined)
859
+ return undefined;
860
+ if (side === 'start' && at < tracks.length)
861
+ return starts[at];
862
+ const last = tracks[at - 1];
863
+ // A grid with no tracks still has its first line, at the content box's edge.
864
+ return last ? starts[at - 1] + (last.collapsed ? 0 : last.base) : 0;
865
+ };
866
+ return {
867
+ width: width ?? x.used,
868
+ height: height ?? y.used,
869
+ usedWidth: x.used,
870
+ usedHeight: y.used,
871
+ items: items.map((_, i) => ({
872
+ left: horizontal[i].start,
873
+ top: vertical[i].start,
874
+ width: horizontal[i].size,
875
+ height: vertical[i].size,
876
+ })),
877
+ area(child) {
878
+ const across = namedTracks(child, 'column', {
879
+ explicit: explicit.columns,
880
+ before: before.columns,
881
+ count: columns.length,
882
+ names: names.columns,
883
+ });
884
+ const down = namedTracks(child, 'row', {
885
+ explicit: explicit.rows,
886
+ before: before.rows,
887
+ count: rows.length,
888
+ names: names.rows,
889
+ });
890
+ return {
891
+ left: edge(x.starts, columns, across.start, 'start'),
892
+ right: edge(x.starts, columns, across.end, 'end'),
893
+ top: edge(y.starts, rows, down.start, 'start'),
894
+ bottom: edge(y.starts, rows, down.end, 'end'),
895
+ };
896
+ },
897
+ };
898
+ }
899
+ /**
900
+ * Lay a grid out in a content box of `width` by `height`. A side left out is as big as the tracks
901
+ * come to, and a percentage along it is of that size, resolved by a second pass.
902
+ */
903
+ export function layOutGrid(style, items, available = {}) {
904
+ if (style['direction'] !== 'rtl')
905
+ return leftToRight(style, items, available);
906
+ // Right to left is left to right in a mirror: swap each item's sides, and flip what comes out.
907
+ const flip = (value) => value?.replace(/\b(left|right)\b/, (side) => (side === 'left' ? 'right' : 'left'));
908
+ const mirrored = items.map((item) => ({
909
+ ...item,
910
+ style: Object.fromEntries(Object.entries(item.style).map(([property, value]) => [flip(property), value])),
911
+ }));
912
+ const layout = leftToRight(style, mirrored, available);
913
+ for (const rect of layout.items)
914
+ rect.left = layout.width - rect.left - rect.width;
915
+ const mirror = (at) => (at === undefined ? undefined : layout.width - at);
916
+ return {
917
+ ...layout,
918
+ area(child) {
919
+ const { left, right, top, bottom } = layout.area(child);
920
+ return { left: mirror(right), right: mirror(left), top, bottom };
921
+ },
922
+ };
923
+ }
924
+ function leftToRight(style, items, available) {
925
+ const first = solve(style, items, available.width, available.height);
926
+ const again = {
927
+ width: available.width ?? (usesPercent(style, 'column') ? first.width : undefined),
928
+ height: available.height ?? (usesPercent(style, 'row') ? first.height : undefined),
929
+ };
930
+ if (again.width === available.width && again.height === available.height)
931
+ return first;
932
+ return solve(style, items, again.width, again.height);
933
+ }
934
+ //# sourceMappingURL=grid.js.map