@ng-native/fabric 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css.d.ts +106 -4
- package/dist/css.js +444 -57
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +127 -9
- package/dist/engine.js +564 -82
- package/dist/engine.js.map +1 -1
- package/dist/transition.d.ts +11 -0
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
package/dist/css.js
CHANGED
|
@@ -222,10 +222,11 @@ const attributeText = (actual, test) => test.insensitive ? String(actual).toLowe
|
|
|
222
222
|
*
|
|
223
223
|
* A prop set to `false` or `null` counts as absent, matching the web, where an attribute is either
|
|
224
224
|
* present or not and a `disabled` binding that evaluates false removes it. Everything else is
|
|
225
|
-
* compared as a string, so `[tabIndex="0"]` works against a numeric prop.
|
|
225
|
+
* compared as a string, so `[tabIndex="0"]` works against a numeric prop. `class` is no prop: it
|
|
226
|
+
* is the element's class list, as the attribute would spell it.
|
|
226
227
|
*/
|
|
227
228
|
function matchesAttribute(node, test) {
|
|
228
|
-
const actual = node.props[test.name];
|
|
229
|
+
const actual = test.name === 'class' ? classAttribute(node) : node.props[test.name];
|
|
229
230
|
if (actual === undefined || actual === null || actual === false)
|
|
230
231
|
return false;
|
|
231
232
|
if (test.operator === undefined)
|
|
@@ -235,6 +236,8 @@ function matchesAttribute(node, test) {
|
|
|
235
236
|
return false;
|
|
236
237
|
return matchesValue(attributeText(actual, test), test.operator, test.value);
|
|
237
238
|
}
|
|
239
|
+
/** The classes of a node in the order they were set, one space between each; none is absent. */
|
|
240
|
+
const classAttribute = (node) => node.classes?.size ? [...node.classes].join(' ') : undefined;
|
|
238
241
|
/** The operators an empty value can still match by: `=` and `|=`. */
|
|
239
242
|
const EMPTY_MATCHES = new Set(['equal', 'dash-match']);
|
|
240
243
|
/** Whether an attribute's value, as `attributeText` gives it, passes an operator's test. */
|
|
@@ -256,6 +259,16 @@ function matchesValue(value, operator, expected) {
|
|
|
256
259
|
return value === expected || value.startsWith(`${expected}-`);
|
|
257
260
|
}
|
|
258
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Whether a node is one a compound written before `::ng-deep` may be: an element of the view of
|
|
264
|
+
* the component the sheet is of. Its host is one only where the compound says `:host`, which is
|
|
265
|
+
* asked of it already: a class a parent gives the host is no anchor.
|
|
266
|
+
*/
|
|
267
|
+
function ownedBy(node, compound, sheet) {
|
|
268
|
+
if (compound.host !== undefined || compound.hostContext !== undefined)
|
|
269
|
+
return true;
|
|
270
|
+
return node.sheet === sheet;
|
|
271
|
+
}
|
|
259
272
|
/*
|
|
260
273
|
* Exempt from the complexity limit deliberately. This is a flat conjunction of independent
|
|
261
274
|
* guards, not nested logic: cyclomatic complexity scores the two the same, and they are not the
|
|
@@ -277,6 +290,8 @@ function matchesCompound(node, compound, sheet) {
|
|
|
277
290
|
}
|
|
278
291
|
if (compound.type !== undefined && compound.type !== node.name)
|
|
279
292
|
return false;
|
|
293
|
+
if (compound.own !== undefined && !ownedBy(node, compound, sheet))
|
|
294
|
+
return false;
|
|
280
295
|
if (compound.id !== undefined && node.props['nativeID'] !== compound.id)
|
|
281
296
|
return false;
|
|
282
297
|
for (const className of compound.classes) {
|
|
@@ -336,13 +351,15 @@ function matchesCompound(node, compound, sheet) {
|
|
|
336
351
|
}
|
|
337
352
|
}
|
|
338
353
|
if (compound.hostContext) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
354
|
+
for (const context of compound.hostContext) {
|
|
355
|
+
// The host itself counts, as it does on the web.
|
|
356
|
+
let found = false;
|
|
357
|
+
for (let n = node; n && !found; n = n.parent) {
|
|
358
|
+
found = matchesCompound(n, context, sheet);
|
|
359
|
+
}
|
|
360
|
+
if (!found)
|
|
361
|
+
return false;
|
|
343
362
|
}
|
|
344
|
-
if (!found)
|
|
345
|
-
return false;
|
|
346
363
|
}
|
|
347
364
|
return true;
|
|
348
365
|
}
|
|
@@ -428,6 +445,82 @@ function matchAny(first, next, rule, index, compound, sheet) {
|
|
|
428
445
|
}
|
|
429
446
|
return false;
|
|
430
447
|
}
|
|
448
|
+
const BIT_OF = new Map();
|
|
449
|
+
/** Past this many names the bits are worked out again: ids made per row would be kept for ever. */
|
|
450
|
+
const BITS_KEPT = 4096;
|
|
451
|
+
/** The bit a class, a name or an id has among the sixty-four. */
|
|
452
|
+
function bitOf(kind, name) {
|
|
453
|
+
const key = kind + name;
|
|
454
|
+
let bit = BIT_OF.get(key);
|
|
455
|
+
if (bit === undefined) {
|
|
456
|
+
let hash = 0;
|
|
457
|
+
for (let at = 0; at < key.length; at++)
|
|
458
|
+
hash = (Math.imul(hash, 31) + key.charCodeAt(at)) | 0;
|
|
459
|
+
bit = (hash ^ (hash >>> 16)) & 63;
|
|
460
|
+
// A bit is its name's hash, so one forgotten is the same when it is asked for again.
|
|
461
|
+
if (BIT_OF.size >= BITS_KEPT)
|
|
462
|
+
BIT_OF.clear();
|
|
463
|
+
BIT_OF.set(key, bit);
|
|
464
|
+
}
|
|
465
|
+
return bit;
|
|
466
|
+
}
|
|
467
|
+
const NAMED_ABOVE = new WeakMap();
|
|
468
|
+
/**
|
|
469
|
+
* The classes, names and ids a rule names of the node's ancestors: of each part joined to what
|
|
470
|
+
* is on its right by a descendant or a child combinator. One joined by a sibling combinator is
|
|
471
|
+
* no ancestor of the node, though the parts to its left that are ancestors of it are ancestors
|
|
472
|
+
* of the node as well. Null where it names none, a rule of one part among them.
|
|
473
|
+
*/
|
|
474
|
+
function namedAbove(rule) {
|
|
475
|
+
let named = NAMED_ABOVE.get(rule);
|
|
476
|
+
if (named !== undefined)
|
|
477
|
+
return named;
|
|
478
|
+
let [low, high] = [0, 0];
|
|
479
|
+
const add = (bit) => {
|
|
480
|
+
if (bit < 32)
|
|
481
|
+
low |= 1 << bit;
|
|
482
|
+
else
|
|
483
|
+
high |= 1 << (bit - 32);
|
|
484
|
+
};
|
|
485
|
+
for (let at = 0; at < rule.compounds.length - 1; at++) {
|
|
486
|
+
const joined = rule.combinators[at];
|
|
487
|
+
if (joined === 'next-sibling' || joined === 'later-sibling')
|
|
488
|
+
continue;
|
|
489
|
+
const compound = rule.compounds[at];
|
|
490
|
+
for (const className of compound.classes)
|
|
491
|
+
add(bitOf('.', className));
|
|
492
|
+
if (compound.type !== undefined)
|
|
493
|
+
add(bitOf('<', compound.type));
|
|
494
|
+
if (compound.id !== undefined)
|
|
495
|
+
add(bitOf('#', compound.id));
|
|
496
|
+
}
|
|
497
|
+
named = low === 0 && high === 0 ? null : [low, high];
|
|
498
|
+
NAMED_ABOVE.set(rule, named);
|
|
499
|
+
return named;
|
|
500
|
+
}
|
|
501
|
+
/** The classes, names and ids of a node's ancestors, as bits. */
|
|
502
|
+
function bitsAbove(node) {
|
|
503
|
+
let [low, high] = [0, 0];
|
|
504
|
+
const add = (bit) => {
|
|
505
|
+
if (bit < 32)
|
|
506
|
+
low |= 1 << bit;
|
|
507
|
+
else
|
|
508
|
+
high |= 1 << (bit - 32);
|
|
509
|
+
};
|
|
510
|
+
for (let up = node.parent; up; up = up.parent) {
|
|
511
|
+
if (up.classes)
|
|
512
|
+
for (const className of up.classes)
|
|
513
|
+
add(bitOf('.', className));
|
|
514
|
+
if (up.name !== undefined)
|
|
515
|
+
add(bitOf('<', up.name));
|
|
516
|
+
const id = up.props?.['nativeID'];
|
|
517
|
+
if (typeof id === 'string')
|
|
518
|
+
add(bitOf('#', id));
|
|
519
|
+
}
|
|
520
|
+
return [low, high];
|
|
521
|
+
}
|
|
522
|
+
/** Whether what is above a node lacks something a rule names there. */
|
|
523
|
+
const lacks = (above, named) => (named[0] & ~above[0]) !== 0 || (named[1] & ~above[1]) !== 0;
|
|
431
524
|
const TAKES_PARENTS = new WeakMap();
|
|
432
525
|
/** Whether a rule has an `inherit` in it, which is settled from the parent's own style. */
|
|
433
526
|
function takesParents(rule) {
|
|
@@ -491,8 +584,9 @@ function stepsAlong(combinators) {
|
|
|
491
584
|
* matched again by other means, and which way it reaches is not worked out.
|
|
492
585
|
*/
|
|
493
586
|
function countSiblings(compound, reach, withinHas) {
|
|
494
|
-
// Which is first and which is last is the ends, and no count.
|
|
495
|
-
|
|
587
|
+
// Which is first and which is last is the ends, and no count. Not of one name: the first of
|
|
588
|
+
// its name is wherever the others of it are not.
|
|
589
|
+
const counts = (compound.nth ?? []).filter((test) => test.a !== 0 || test.b !== 1 || test.ofType);
|
|
496
590
|
if (counts.some((test) => withinHas || !test.fromEnd))
|
|
497
591
|
reach.after = Infinity;
|
|
498
592
|
if (counts.some((test) => withinHas || test.fromEnd))
|
|
@@ -532,10 +626,13 @@ export function placeReadElsewhere(sheet) {
|
|
|
532
626
|
if (joined === 'next-sibling' || joined === 'later-sibling')
|
|
533
627
|
return;
|
|
534
628
|
const run = rule.compounds.slice(start, at + 1);
|
|
629
|
+
// What the run is in: the compound before it, which every element of it is under.
|
|
630
|
+
const list = rule.compounds[start - 1];
|
|
631
|
+
const child = rule.combinators[start - 1] === 'child';
|
|
535
632
|
start = at + 1;
|
|
536
633
|
const above = at < rule.compounds.length - 1;
|
|
537
634
|
if (above && (run.length > 1 || run.some(readsPlace)))
|
|
538
|
-
elsewhere.push(compound);
|
|
635
|
+
elsewhere.push({ compound, above: list?.classes, child });
|
|
539
636
|
});
|
|
540
637
|
}
|
|
541
638
|
known = elsewhere;
|
|
@@ -562,7 +659,7 @@ function placeReadAbove(compound, elsewhere) {
|
|
|
562
659
|
];
|
|
563
660
|
for (const inner of above)
|
|
564
661
|
if (readsPlace(inner))
|
|
565
|
-
elsewhere.push(inner);
|
|
662
|
+
elsewhere.push({ compound: inner });
|
|
566
663
|
for (const inner of nestedCompounds(compound))
|
|
567
664
|
placeReadAbove(inner, elsewhere);
|
|
568
665
|
}
|
|
@@ -596,10 +693,10 @@ function matchesNth(node, test) {
|
|
|
596
693
|
const siblings = node.parent?.children;
|
|
597
694
|
if (!siblings)
|
|
598
695
|
return false;
|
|
599
|
-
if (test.a === 0 && test.b === 1) {
|
|
696
|
+
if (test.a === 0 && test.b === 1 && !test.ofType) {
|
|
600
697
|
return (test.fromEnd ? lastElement(siblings) : firstElement(siblings)) === node;
|
|
601
698
|
}
|
|
602
|
-
const from = countPosition(node, siblings, test.fromEnd === true);
|
|
699
|
+
const from = countPosition(node, siblings, test.fromEnd === true, test.ofType === true);
|
|
603
700
|
if (from === 0)
|
|
604
701
|
return false;
|
|
605
702
|
// `an + b` for some whole number of steps, which is what CSS counts from.
|
|
@@ -817,11 +914,11 @@ function byImportance(matched, places) {
|
|
|
817
914
|
}));
|
|
818
915
|
return [...plain, ...important];
|
|
819
916
|
}
|
|
820
|
-
function countPosition(node, siblings, fromEnd) {
|
|
917
|
+
function countPosition(node, siblings, fromEnd, ofType) {
|
|
821
918
|
let position = 0;
|
|
822
919
|
let seen = 0;
|
|
823
920
|
for (const sibling of siblings) {
|
|
824
|
-
if (!isElement(sibling))
|
|
921
|
+
if (!isElement(sibling) || (ofType && sibling.name !== node.name))
|
|
825
922
|
continue;
|
|
826
923
|
seen++;
|
|
827
924
|
if (sibling === node)
|
|
@@ -1094,6 +1191,13 @@ const DEFAULT_FONT_SIZE = 16;
|
|
|
1094
1191
|
const EMPTY = {};
|
|
1095
1192
|
/** Stands in for the parent of a root node, so the comparison needs no null case. */
|
|
1096
1193
|
const ROOT_CONTEXT = {};
|
|
1194
|
+
/** The `scope` over a node with no parent. */
|
|
1195
|
+
const ROOT_SCOPE = {};
|
|
1196
|
+
/**
|
|
1197
|
+
* The `scope` of the one cache every node with nothing to style shares. It is no one node's, so
|
|
1198
|
+
* nothing styled under it, or holding it, is taken to match what it did.
|
|
1199
|
+
*/
|
|
1200
|
+
const SHARED_SCOPE = {};
|
|
1097
1201
|
/** Shared empty token map, for the overwhelmingly common case of a subtree defining none. */
|
|
1098
1202
|
const NO_TOKENS = {};
|
|
1099
1203
|
/**
|
|
@@ -1116,6 +1220,42 @@ function subjectsOf(sheet) {
|
|
|
1116
1220
|
subjects.set(sheet, (found = all));
|
|
1117
1221
|
return found;
|
|
1118
1222
|
}
|
|
1223
|
+
const HAS_ABOVE = new WeakMap();
|
|
1224
|
+
/** Whether a compound, or one it asks of the same node, uses `:has()`. */
|
|
1225
|
+
const asksBeneath = (compound) => compound.has !== undefined ||
|
|
1226
|
+
[...(compound.not ?? []), ...(compound.is ?? []).flat()].some(asksBeneath);
|
|
1227
|
+
/**
|
|
1228
|
+
* The boxes above the styled node that a compound asks `:has()` of: the compound's own node,
|
|
1229
|
+
* where that is not the styled one, and each box it is asked to be inside,
|
|
1230
|
+
* `.body:is(.card:has(.x) *) .title`.
|
|
1231
|
+
*/
|
|
1232
|
+
function* boxesAsked(compound, styled) {
|
|
1233
|
+
if (!styled && asksBeneath(compound))
|
|
1234
|
+
yield compound;
|
|
1235
|
+
for (const key of ['ancestors', 'parents', 'hostContext']) {
|
|
1236
|
+
for (const outer of compound[key] ?? [])
|
|
1237
|
+
yield* boxesAsked(outer, false);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
/** Each box a sheet's rules ask `:has()` of above the node they style. */
|
|
1241
|
+
export function hasAbove(sheet) {
|
|
1242
|
+
let known = HAS_ABOVE.get(sheet);
|
|
1243
|
+
if (known === undefined) {
|
|
1244
|
+
const found = [];
|
|
1245
|
+
for (const rule of sheet.rules) {
|
|
1246
|
+
const last = rule.compounds.length - 1;
|
|
1247
|
+
for (const [at, compound] of rule.compounds.entries()) {
|
|
1248
|
+
for (const box of boxesAsked(compound, at === last)) {
|
|
1249
|
+
const subjects = noSubjects();
|
|
1250
|
+
noteSubject(subjects, rule);
|
|
1251
|
+
found.push({ classes: box.classes, subjects });
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
HAS_ABOVE.set(sheet, (known = found));
|
|
1256
|
+
}
|
|
1257
|
+
return known;
|
|
1258
|
+
}
|
|
1119
1259
|
/** Note in `into` what a rule is for. */
|
|
1120
1260
|
function noteSubject(into, rule) {
|
|
1121
1261
|
const subject = rule.compounds.at(-1);
|
|
@@ -1147,17 +1287,29 @@ function classReach(sheet) {
|
|
|
1147
1287
|
for (const rule of sheet.rules) {
|
|
1148
1288
|
const every = new Set();
|
|
1149
1289
|
collectClasses(rule, every, reads, named);
|
|
1150
|
-
const inside =
|
|
1290
|
+
const { own, inside } = classPlaces(rule);
|
|
1151
1291
|
for (const name of every) {
|
|
1152
|
-
if (!inside.has(name))
|
|
1292
|
+
if (!own.has(name) && !inside.has(name))
|
|
1153
1293
|
classes.set(name, true);
|
|
1154
|
-
|
|
1294
|
+
if (own.has(name))
|
|
1295
|
+
noteOwn(classes, name);
|
|
1296
|
+
if (inside.has(name))
|
|
1155
1297
|
noteInside(classes, name, rule);
|
|
1156
1298
|
}
|
|
1157
1299
|
}
|
|
1158
1300
|
reaches.set(sheet, (found = { classes, reads, named }));
|
|
1159
1301
|
return found;
|
|
1160
1302
|
}
|
|
1303
|
+
/** Note a rule as one for the element that has a class. */
|
|
1304
|
+
function noteOwn(found, name) {
|
|
1305
|
+
const subjects = found.get(name);
|
|
1306
|
+
if (subjects === true)
|
|
1307
|
+
return;
|
|
1308
|
+
if (subjects)
|
|
1309
|
+
subjects.own = true;
|
|
1310
|
+
else
|
|
1311
|
+
found.set(name, { ...noSubjects(), own: true });
|
|
1312
|
+
}
|
|
1161
1313
|
/** Note a rule as one that names a class as what its element is inside. */
|
|
1162
1314
|
function noteInside(found, name, rule) {
|
|
1163
1315
|
let subjects = found.get(name);
|
|
@@ -1181,6 +1333,8 @@ const noSubjects = () => ({
|
|
|
1181
1333
|
inside: new Set(),
|
|
1182
1334
|
any: false,
|
|
1183
1335
|
});
|
|
1336
|
+
/** The reach of classes that rules are for the element of, and for nothing under it. */
|
|
1337
|
+
const OWN_ALONE = { ...noSubjects(), own: true };
|
|
1184
1338
|
/** Add to `into` what `from` is for. */
|
|
1185
1339
|
function addSubjects(into, from) {
|
|
1186
1340
|
for (const name of from.classes)
|
|
@@ -1190,30 +1344,67 @@ function addSubjects(into, from) {
|
|
|
1190
1344
|
for (const name of from.inside)
|
|
1191
1345
|
into.inside.add(name);
|
|
1192
1346
|
into.any ||= from.any;
|
|
1347
|
+
if (from.own)
|
|
1348
|
+
into.own = true;
|
|
1193
1349
|
}
|
|
1194
1350
|
/**
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1351
|
+
* Where a rule names each class it names, for the classes it names in no other place than these:
|
|
1352
|
+
*
|
|
1353
|
+
* - `own`, on the element the rule is for: its own classes, and those in `:is()` and `:not()` of
|
|
1354
|
+
* it, which are asked of that element;
|
|
1355
|
+
* - `inside`, as what that element is inside: the classes of a compound before the last, with
|
|
1356
|
+
* nothing but `>` and a space between there and the element, and those of what `:is(.box *)`
|
|
1357
|
+
* and `:is(.box > *)` put over it.
|
|
1358
|
+
*
|
|
1359
|
+
* A class named anywhere else in the rule is in neither, whatever else names it: beside the
|
|
1360
|
+
* element, inside `:has()` or `:host-context()`, or in `:not()` of a box the element is in.
|
|
1197
1361
|
*/
|
|
1198
|
-
function
|
|
1362
|
+
function classPlaces(rule) {
|
|
1363
|
+
const own = new Set();
|
|
1199
1364
|
const inside = new Set();
|
|
1200
1365
|
const elsewhere = new Set();
|
|
1201
1366
|
const last = rule.compounds.length - 1;
|
|
1202
1367
|
let under = true;
|
|
1203
1368
|
for (let index = last; index >= 0; index--) {
|
|
1204
1369
|
const compound = rule.compounds[index];
|
|
1205
|
-
if (index
|
|
1206
|
-
|
|
1207
|
-
|
|
1370
|
+
if (index === last) {
|
|
1371
|
+
placeClasses(compound, own, inside, elsewhere);
|
|
1372
|
+
continue;
|
|
1208
1373
|
}
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1374
|
+
const joined = rule.combinators[index];
|
|
1375
|
+
under &&= joined === 'child' || joined === 'descendant';
|
|
1376
|
+
// A box the element is under, by its classes and by those it is asked to have or not to,
|
|
1377
|
+
// `.box:not(.busy) .row`: each is asked of that box, as its own are.
|
|
1378
|
+
const over = under ? inside : elsewhere;
|
|
1379
|
+
placeClasses(compound, over, over, elsewhere);
|
|
1380
|
+
}
|
|
1381
|
+
for (const name of elsewhere) {
|
|
1382
|
+
own.delete(name);
|
|
1215
1383
|
inside.delete(name);
|
|
1216
|
-
|
|
1384
|
+
}
|
|
1385
|
+
return { own, inside };
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* The classes of a compound asked of the element a rule is for, into `at`, and of the compounds
|
|
1389
|
+
* it puts over that element, into `inside`. What any other part of it names goes `elsewhere`:
|
|
1390
|
+
* a part read here by name is one this knows the reach of, and one added later is not.
|
|
1391
|
+
*/
|
|
1392
|
+
function placeClasses(compound, at, inside, elsewhere) {
|
|
1393
|
+
const { classes, is, not, ancestors, parents, type: _t, id: _i, root: _r, pseudo: _p, ...rest } = compound;
|
|
1394
|
+
for (const name of classes)
|
|
1395
|
+
at.add(name);
|
|
1396
|
+
for (const alternatives of is ?? []) {
|
|
1397
|
+
for (const option of alternatives)
|
|
1398
|
+
placeClasses(option, at, inside, elsewhere);
|
|
1399
|
+
}
|
|
1400
|
+
for (const excluded of not ?? [])
|
|
1401
|
+
placeClasses(excluded, at, inside, elsewhere);
|
|
1402
|
+
// What is over the element is over it whichever of these said so, and what is over that is too.
|
|
1403
|
+
for (const above of [...(ancestors ?? []), ...(parents ?? [])]) {
|
|
1404
|
+
placeClasses(above, inside, inside, elsewhere);
|
|
1405
|
+
}
|
|
1406
|
+
// `attributes` among them: a test of the `class` attribute is answered through `classTests`.
|
|
1407
|
+
collectClasses(rest, elsewhere, []);
|
|
1217
1408
|
}
|
|
1218
1409
|
/** What a rule declares, which names no element. */
|
|
1219
1410
|
const DECLARED = new Set(['declarations', 'important', 'tokens']);
|
|
@@ -1283,12 +1474,22 @@ function writtenFor(of, node) {
|
|
|
1283
1474
|
}
|
|
1284
1475
|
return false;
|
|
1285
1476
|
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Whether a node has its answer for this commit already. Not one marked since it was given the
|
|
1479
|
+
* one cache every box with nothing to style shares, see `unstyled`: another of them resolved in
|
|
1480
|
+
* this commit has set that cache's epoch, and it is not this node's answer.
|
|
1481
|
+
*/
|
|
1482
|
+
function resolvedIn(epoch, cache, node) {
|
|
1483
|
+
return (cache !== null && cache.epoch === epoch && (!node.styleDirty || cache.context !== ROOT_CONTEXT));
|
|
1484
|
+
}
|
|
1286
1485
|
function emptyCacheFor(epoch, generation) {
|
|
1287
1486
|
return {
|
|
1288
1487
|
epoch,
|
|
1289
1488
|
generation,
|
|
1290
1489
|
context: ROOT_CONTEXT,
|
|
1291
1490
|
parentContext: ROOT_CONTEXT,
|
|
1491
|
+
scope: SHARED_SCOPE,
|
|
1492
|
+
parentScope: SHARED_SCOPE,
|
|
1292
1493
|
style: EMPTY,
|
|
1293
1494
|
inherited: EMPTY,
|
|
1294
1495
|
tokens: NO_TOKENS,
|
|
@@ -1554,11 +1755,19 @@ export class StyleResolver {
|
|
|
1554
1755
|
/** The tests of the `class` attribute itself in those sheets, which any class may answer. */
|
|
1555
1756
|
classTests = [];
|
|
1556
1757
|
noted = new WeakSet();
|
|
1758
|
+
/**
|
|
1759
|
+
* The sheets with a rule written with `::ng-deep`, which is for what is under the component
|
|
1760
|
+
* in the views of the components it holds. None in most apps, and nothing is asked then.
|
|
1761
|
+
*/
|
|
1762
|
+
deepSheets = new Set();
|
|
1763
|
+
deepIndexes = new WeakMap();
|
|
1557
1764
|
/** Read the classes a sheet names, once, as soon as any node is given it. */
|
|
1558
1765
|
noteSheet(sheet) {
|
|
1559
1766
|
if (!sheet || this.noted.has(sheet))
|
|
1560
1767
|
return;
|
|
1561
1768
|
this.noted.add(sheet);
|
|
1769
|
+
if (sheet.rules.some((rule) => rule.deep))
|
|
1770
|
+
this.deepSheets.add(sheet);
|
|
1562
1771
|
const { classes, reads, named } = classReach(sheet);
|
|
1563
1772
|
for (const [name, how] of classes)
|
|
1564
1773
|
this.noteReach(name, how);
|
|
@@ -1584,20 +1793,38 @@ export class StyleResolver {
|
|
|
1584
1793
|
else
|
|
1585
1794
|
addSubjects(had ?? this.reach.set(name, noSubjects()).get(name), how);
|
|
1586
1795
|
}
|
|
1796
|
+
/** Whether a class's reach names any element under the one that has it. */
|
|
1797
|
+
reachesUnder(subjects) {
|
|
1798
|
+
return (subjects.any ||
|
|
1799
|
+
subjects.classes.size > 0 ||
|
|
1800
|
+
subjects.types.size > 0 ||
|
|
1801
|
+
subjects.inside.size > 0);
|
|
1802
|
+
}
|
|
1587
1803
|
/**
|
|
1588
1804
|
* What classes coming to an element or going from it can restyle: `true` for it and all
|
|
1589
|
-
* under it and after it, as any change to it does; the
|
|
1590
|
-
* rules are for; or null, where no rule names any
|
|
1805
|
+
* under it and after it, as any change to it does; the element itself, where `own`, and the
|
|
1806
|
+
* elements under it to match again, by what rules are for; or null, where no rule names any
|
|
1807
|
+
* of them and nothing is styled again.
|
|
1591
1808
|
*/
|
|
1592
1809
|
reachOf(classes) {
|
|
1593
1810
|
let under = null;
|
|
1811
|
+
let own = false;
|
|
1594
1812
|
for (const name of classes) {
|
|
1595
1813
|
const how = this.reach.get(name);
|
|
1596
1814
|
if (how === true || this.classTests.some((test) => answers(test, name)))
|
|
1597
1815
|
return true;
|
|
1598
|
-
if (how)
|
|
1816
|
+
if (!how)
|
|
1817
|
+
continue;
|
|
1818
|
+
own ||= how.own === true;
|
|
1819
|
+
// Made only for a class that reaches under: most are a rule's own, and this is asked for
|
|
1820
|
+
// every element as it is given its classes.
|
|
1821
|
+
if (this.reachesUnder(how))
|
|
1599
1822
|
addSubjects((under ??= noSubjects()), how);
|
|
1600
1823
|
}
|
|
1824
|
+
if (!under)
|
|
1825
|
+
return own ? OWN_ALONE : null;
|
|
1826
|
+
if (own)
|
|
1827
|
+
under.own = true;
|
|
1601
1828
|
return under;
|
|
1602
1829
|
}
|
|
1603
1830
|
/** Whether one of the rules `subjects` stands for could be for a node. */
|
|
@@ -1658,7 +1885,7 @@ export class StyleResolver {
|
|
|
1658
1885
|
const cached = node.styleCache;
|
|
1659
1886
|
// Within one commit a node is asked for its inherited map once per descendant, so this is the
|
|
1660
1887
|
// hot path and it must not walk anywhere.
|
|
1661
|
-
if (
|
|
1888
|
+
if (resolvedIn(epoch, cached, node))
|
|
1662
1889
|
return cached;
|
|
1663
1890
|
const parent = node.parent ? this.resolve(node.parent, epoch) : null;
|
|
1664
1891
|
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
@@ -1676,33 +1903,66 @@ export class StyleResolver {
|
|
|
1676
1903
|
this.justMatched = null;
|
|
1677
1904
|
if (kept)
|
|
1678
1905
|
return kept;
|
|
1906
|
+
return this.styledAgain(node, epoch, parent, found);
|
|
1907
|
+
}
|
|
1908
|
+
/** A node styled now, from the rules found for it or from those it kept: see `matchesStand`. */
|
|
1909
|
+
styledAgain(node, epoch, parent, found) {
|
|
1910
|
+
const cached = node.styleCache;
|
|
1911
|
+
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
1679
1912
|
const parentInherited = parent ? parent.inherited : EMPTY;
|
|
1913
|
+
const parentScope = parent ? parent.scope : ROOT_SCOPE;
|
|
1914
|
+
const stands = this.matchesStand(cached, node, parentScope);
|
|
1915
|
+
// The scope it keeps, or none: it then has a new one, which its new `context` serves as.
|
|
1916
|
+
const scope = stands ? cached.scope : null;
|
|
1680
1917
|
// A box the engine made around loose text is in no template, so no rule matches it.
|
|
1681
1918
|
if (node.anonymous || this.hasNoRules(node)) {
|
|
1682
|
-
return this.unstyled(node, epoch, parent,
|
|
1919
|
+
return this.unstyled(node, epoch, parent, parentInherited, scope, parentScope);
|
|
1683
1920
|
}
|
|
1684
1921
|
styleStats.nodesResolved++;
|
|
1685
|
-
|
|
1922
|
+
// Its own rules are found again for a change of its own, and not for one above it.
|
|
1923
|
+
const rules = stands && !node.ownDirty ? cached.rules : undefined;
|
|
1924
|
+
const matched = found ?? rules ?? this.matchedBy(node);
|
|
1686
1925
|
const parentTokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
1687
1926
|
this.heir(node, parent, matched);
|
|
1688
1927
|
const styled = this.styled(node, matched, parentTokens, parentInherited);
|
|
1689
|
-
const cache = this.cached(epoch, parentContext, styled, matched);
|
|
1928
|
+
const cache = this.cached(epoch, parentContext, styled, matched, scope, parentScope);
|
|
1690
1929
|
node.styleCache = cache;
|
|
1691
1930
|
node.styleDirty = false;
|
|
1931
|
+
node.ownDirty = false;
|
|
1692
1932
|
node.hasDirty = false;
|
|
1693
1933
|
node.stateDirty = false;
|
|
1694
1934
|
return cache;
|
|
1695
1935
|
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Whether what an element could match is as it was when it was last styled: nothing that can
|
|
1938
|
+
* change it was marked on the element, its parent's `scope` is the one it was styled under,
|
|
1939
|
+
* and no sheet that could match it has come since. It is being styled again all the same, for
|
|
1940
|
+
* a change of its own that alters nothing under it, or for what it inherits.
|
|
1941
|
+
*/
|
|
1942
|
+
matchesStand(cached, node, parentScope) {
|
|
1943
|
+
return (cached !== null &&
|
|
1944
|
+
!node.styleDirty &&
|
|
1945
|
+
!node.hasDirty &&
|
|
1946
|
+
!node.stateDirty &&
|
|
1947
|
+
cached.scope !== SHARED_SCOPE &&
|
|
1948
|
+
parentScope !== SHARED_SCOPE &&
|
|
1949
|
+
cached.parentScope === parentScope &&
|
|
1950
|
+
this.current(cached, node));
|
|
1951
|
+
}
|
|
1696
1952
|
/**
|
|
1697
1953
|
* A cache of a node just styled, with a context of its own. It keeps the rules the node
|
|
1698
1954
|
* matched, for the node to be compared with, once a sheet has a use for them: see `tracksHas`.
|
|
1699
1955
|
*/
|
|
1700
|
-
cached(epoch, parentContext, styled, matched) {
|
|
1956
|
+
cached(epoch, parentContext, styled, matched, scope, parentScope) {
|
|
1957
|
+
const context = {};
|
|
1701
1958
|
const cache = {
|
|
1702
1959
|
epoch,
|
|
1703
1960
|
generation: this.generation,
|
|
1704
|
-
context
|
|
1961
|
+
context,
|
|
1705
1962
|
parentContext,
|
|
1963
|
+
scope: scope ?? context,
|
|
1964
|
+
parentScope,
|
|
1965
|
+
rules: matched,
|
|
1706
1966
|
...styled,
|
|
1707
1967
|
};
|
|
1708
1968
|
if (this.tracksHas)
|
|
@@ -1725,6 +1985,7 @@ export class StyleResolver {
|
|
|
1725
1985
|
const stands = (node.hasDirty === true || node.stateDirty === true) &&
|
|
1726
1986
|
cached?.matched !== undefined &&
|
|
1727
1987
|
!node.styleDirty &&
|
|
1988
|
+
!node.ownDirty &&
|
|
1728
1989
|
cached.parentContext === parentContext &&
|
|
1729
1990
|
this.current(cached, node);
|
|
1730
1991
|
if (!stands)
|
|
@@ -1764,6 +2025,7 @@ export class StyleResolver {
|
|
|
1764
2025
|
const stands = node.stateDirty === true &&
|
|
1765
2026
|
cached !== null &&
|
|
1766
2027
|
!node.styleDirty &&
|
|
2028
|
+
!node.ownDirty &&
|
|
1767
2029
|
!node.hasDirty &&
|
|
1768
2030
|
cached.parentContext === parentContext &&
|
|
1769
2031
|
this.current(cached, node) &&
|
|
@@ -1781,6 +2043,14 @@ export class StyleResolver {
|
|
|
1781
2043
|
holds(node, parentContext) {
|
|
1782
2044
|
return this.reusable(node.styleCache, node, parentContext);
|
|
1783
2045
|
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Whether a node was last styled under a box holding the one cache shared by every box with
|
|
2048
|
+
* nothing to style. Such boxes hand the same `context` down, so the node cannot tell one from
|
|
2049
|
+
* another by it: moved under another box, it has to be told.
|
|
2050
|
+
*/
|
|
2051
|
+
styledUnderShared(node) {
|
|
2052
|
+
return node.styleCache !== null && node.styleCache.parentContext === ROOT_CONTEXT;
|
|
2053
|
+
}
|
|
1784
2054
|
/**
|
|
1785
2055
|
* Whether a previous result still stands: nothing about the node changed, nothing above it
|
|
1786
2056
|
* moved, and the conditions have not.
|
|
@@ -1788,6 +2058,7 @@ export class StyleResolver {
|
|
|
1788
2058
|
reusable(cached, node, parentContext) {
|
|
1789
2059
|
return (cached !== null &&
|
|
1790
2060
|
!node.styleDirty &&
|
|
2061
|
+
!node.ownDirty &&
|
|
1791
2062
|
!node.hasDirty &&
|
|
1792
2063
|
!node.stateDirty &&
|
|
1793
2064
|
cached.parentContext === parentContext &&
|
|
@@ -1800,6 +2071,7 @@ export class StyleResolver {
|
|
|
1800
2071
|
hasNoRules(node) {
|
|
1801
2072
|
return (node.sheet === null &&
|
|
1802
2073
|
node.hostSheet === null &&
|
|
2074
|
+
this.deepSheets.size === 0 &&
|
|
1803
2075
|
this.globalSheet === null &&
|
|
1804
2076
|
this.addedSheets.length === 0 &&
|
|
1805
2077
|
!node.customProperties &&
|
|
@@ -1815,35 +2087,43 @@ export class StyleResolver {
|
|
|
1815
2087
|
* carry: the shared object holds none, and the surface root answering with it dropped every
|
|
1816
2088
|
* token the device had seeded.
|
|
1817
2089
|
*/
|
|
1818
|
-
unstyled(node, epoch, parent,
|
|
2090
|
+
unstyled(node, epoch, parent, parentInherited, scope, parentScope) {
|
|
1819
2091
|
const tokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
2092
|
+
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
1820
2093
|
// What inline style sets is inherited too, with no rule to read it against.
|
|
1821
2094
|
const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
|
|
1822
|
-
|
|
2095
|
+
const nothing = !inline &&
|
|
1823
2096
|
parentInherited === EMPTY &&
|
|
1824
2097
|
parentContext === ROOT_CONTEXT &&
|
|
1825
|
-
tokens === NO_TOKENS
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
}
|
|
1830
|
-
node.styleCache = this.emptyCache;
|
|
1831
|
-
return this.emptyCache;
|
|
1832
|
-
}
|
|
2098
|
+
tokens === NO_TOKENS;
|
|
2099
|
+
if (nothing && !node.styleDirty && !node.ownDirty)
|
|
2100
|
+
return this.sharedEmpty(node, epoch);
|
|
2101
|
+
const context = {};
|
|
1833
2102
|
const passthrough = {
|
|
1834
2103
|
epoch,
|
|
1835
2104
|
generation: this.generation,
|
|
1836
|
-
context
|
|
2105
|
+
context,
|
|
1837
2106
|
parentContext,
|
|
2107
|
+
scope: scope ?? context,
|
|
2108
|
+
parentScope,
|
|
1838
2109
|
style: unstyledStyle(parentInherited, inline),
|
|
1839
2110
|
inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
|
|
1840
2111
|
tokens,
|
|
1841
2112
|
};
|
|
1842
2113
|
node.styleCache = passthrough;
|
|
1843
2114
|
node.styleDirty = false;
|
|
2115
|
+
node.ownDirty = false;
|
|
1844
2116
|
node.stateDirty = false;
|
|
1845
2117
|
return passthrough;
|
|
1846
2118
|
}
|
|
2119
|
+
/** The one cache of every node with nothing to style and nothing to hand down, given to one more. */
|
|
2120
|
+
sharedEmpty(node, epoch) {
|
|
2121
|
+
if (this.emptyCache.epoch !== epoch || this.emptyCache.generation !== this.generation) {
|
|
2122
|
+
this.emptyCache = emptyCacheFor(epoch, this.generation);
|
|
2123
|
+
}
|
|
2124
|
+
node.styleCache = this.emptyCache;
|
|
2125
|
+
return this.emptyCache;
|
|
2126
|
+
}
|
|
1847
2127
|
/**
|
|
1848
2128
|
* The rules worth trying against a node, in cascade order.
|
|
1849
2129
|
*
|
|
@@ -1872,9 +2152,33 @@ export class StyleResolver {
|
|
|
1872
2152
|
indexes.push(this.ownIndex(node.hostSheet, HOSTS));
|
|
1873
2153
|
if (node.sheet)
|
|
1874
2154
|
indexes.push(this.ownIndex(node.sheet, CREATED));
|
|
2155
|
+
if (this.deepSheets.size)
|
|
2156
|
+
this.reachedFromAbove(node, indexes);
|
|
1875
2157
|
indexes.push(this.sharedIndex());
|
|
1876
2158
|
return candidateRules(node, indexes, this.cascadeOrder);
|
|
1877
2159
|
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Add the `::ng-deep` rules of each component a node is in whose sheet is not the node's own:
|
|
2162
|
+
* a node is tried against its own component's rules already, those among them.
|
|
2163
|
+
*/
|
|
2164
|
+
reachedFromAbove(node, indexes) {
|
|
2165
|
+
for (let up = node.parent; up; up = up.parent) {
|
|
2166
|
+
const sheet = up.hostSheet;
|
|
2167
|
+
if (!sheet || sheet === node.sheet || sheet === node.hostSheet)
|
|
2168
|
+
continue;
|
|
2169
|
+
if (this.deepSheets.has(sheet))
|
|
2170
|
+
indexes.push(this.deepIndex(sheet));
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
/** A sheet's `::ng-deep` rules alone, filed as a component's own are. */
|
|
2174
|
+
deepIndex(sheet) {
|
|
2175
|
+
let index = this.deepIndexes.get(sheet);
|
|
2176
|
+
if (!index) {
|
|
2177
|
+
const deep = this.entries(sheet, COMPONENT_SPECIFICITY_BUMP).filter(({ rule }) => rule.deep);
|
|
2178
|
+
this.deepIndexes.set(sheet, (index = indexRules(deep, undefined, HOSTS * SHEET_ROOM)));
|
|
2179
|
+
}
|
|
2180
|
+
return index;
|
|
2181
|
+
}
|
|
1878
2182
|
/** Weakest first: by layer where any sheet has one, then by weight, then as they were filed. */
|
|
1879
2183
|
cascadeOrder = (a, b) => {
|
|
1880
2184
|
const layers = this.layered
|
|
@@ -2014,8 +2318,15 @@ export class StyleResolver {
|
|
|
2014
2318
|
/** The rules that apply to a node, weakest first. */
|
|
2015
2319
|
matched(node, entries) {
|
|
2016
2320
|
const out = [];
|
|
2321
|
+
// What the node's ancestors are, gathered for the first rule that names one.
|
|
2322
|
+
let above;
|
|
2017
2323
|
for (const { rule, sheet } of entries) {
|
|
2018
|
-
if (this.conditionHolds(rule)
|
|
2324
|
+
if (!this.conditionHolds(rule))
|
|
2325
|
+
continue;
|
|
2326
|
+
const named = namedAbove(rule);
|
|
2327
|
+
if (named && lacks((above ??= bitsAbove(node)), named))
|
|
2328
|
+
continue;
|
|
2329
|
+
if (matches(node, rule, sheet))
|
|
2019
2330
|
out.push(rule);
|
|
2020
2331
|
}
|
|
2021
2332
|
return out;
|
|
@@ -2065,13 +2376,17 @@ export class StyleResolver {
|
|
|
2065
2376
|
for (const [at, key] of written.entries()) {
|
|
2066
2377
|
if (!(key in TWIN.ltr))
|
|
2067
2378
|
continue;
|
|
2068
|
-
twin ??= TWIN[(style
|
|
2379
|
+
twin ??= TWIN[this.readsRightToLeft(style) ? 'rtl' : 'ltr'];
|
|
2069
2380
|
const other = twin[key];
|
|
2070
2381
|
// Where the inline style sets both, the one it set later stands.
|
|
2071
2382
|
if (written.indexOf(other) < at)
|
|
2072
2383
|
delete style[other];
|
|
2073
2384
|
}
|
|
2074
2385
|
}
|
|
2386
|
+
/** Whether a settled style is of an element read right to left: by its own, or the app's. */
|
|
2387
|
+
readsRightToLeft(style) {
|
|
2388
|
+
return (style['direction'] ?? this.conditions.direction) === 'rtl';
|
|
2389
|
+
}
|
|
2075
2390
|
/**
|
|
2076
2391
|
* The direction an element is laid out in: the last `direction` its own rules give it, the one
|
|
2077
2392
|
* it inherits, or the app's. A logical corner is a different physical one in each.
|
|
@@ -2150,6 +2465,17 @@ export class StyleResolver {
|
|
|
2150
2465
|
* A declaration with no token and no usable fallback is dropped rather than written as
|
|
2151
2466
|
* undefined, which is what a browser does with a value it cannot resolve.
|
|
2152
2467
|
*/
|
|
2468
|
+
/**
|
|
2469
|
+
* What declarations that wait for a node's tokens come to on it, where no rule of the node's
|
|
2470
|
+
* carries them: a keyframe's, which is settled against the element that plays it.
|
|
2471
|
+
*/
|
|
2472
|
+
settleFor(node, deferred, epoch) {
|
|
2473
|
+
const { tokens } = this.resolve(node, epoch);
|
|
2474
|
+
const above = node.parent ? this.resolve(node.parent, epoch).inherited : EMPTY;
|
|
2475
|
+
const own = {};
|
|
2476
|
+
this.applyDeferred(deferred, own, tokens, above, null);
|
|
2477
|
+
return own;
|
|
2478
|
+
}
|
|
2153
2479
|
applyDeferred(deferred, own, tokens, parentInherited, important) {
|
|
2154
2480
|
for (const declaration of settlingOrder(deferred)) {
|
|
2155
2481
|
if (declaration.inherit) {
|
|
@@ -2528,6 +2854,9 @@ function substitutable(token, tokens) {
|
|
|
2528
2854
|
return hslSubstitutable(token.hsl, tokens);
|
|
2529
2855
|
if (token.deferredColour)
|
|
2530
2856
|
return colourSubstitutable(token.deferredColour, tokens);
|
|
2857
|
+
if (token.deferredLengths) {
|
|
2858
|
+
return token.deferredLengths.every((part) => typeof part !== 'object' || substitutedIn(part, tokens) !== undefined);
|
|
2859
|
+
}
|
|
2531
2860
|
// Each marker is one reading of the same text, and a fallback one cannot read is dropped from it.
|
|
2532
2861
|
return token.deferredCalc.some((marker) => calcSubstitutable(marker.expression, tokens));
|
|
2533
2862
|
}
|
|
@@ -2606,7 +2935,7 @@ function inCycles(names, waitingOn) {
|
|
|
2606
2935
|
}
|
|
2607
2936
|
/** Whether a token is made of others, and so worked out where it is defined. */
|
|
2608
2937
|
function isDerived(token) {
|
|
2609
|
-
return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc);
|
|
2938
|
+
return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc || token?.deferredLengths);
|
|
2610
2939
|
}
|
|
2611
2940
|
/** A token made of others, worked out from the tokens in scope. */
|
|
2612
2941
|
function derived(token, tokens) {
|
|
@@ -2614,8 +2943,23 @@ function derived(token, tokens) {
|
|
|
2614
2943
|
return colourToken(resolveHsl(token.hsl, tokens));
|
|
2615
2944
|
if (token.deferredColour)
|
|
2616
2945
|
return colourToken(resolveColour(token.deferredColour, tokens));
|
|
2946
|
+
if (token.deferredLengths)
|
|
2947
|
+
return lengthsToken(token, tokens);
|
|
2617
2948
|
return calcToken(token.deferredCalc, tokens);
|
|
2618
2949
|
}
|
|
2950
|
+
/**
|
|
2951
|
+
* A token of several lengths with each part that is a `var()` followed. Where a part comes to
|
|
2952
|
+
* no length the token is what else it reads as, a shadow whose colour is a token being several
|
|
2953
|
+
* lengths and a `var()` too, or nothing where it reads as nothing else.
|
|
2954
|
+
*/
|
|
2955
|
+
function lengthsToken(token, tokens) {
|
|
2956
|
+
const { deferredLengths, ...rest } = token;
|
|
2957
|
+
const lengths = deferredLengths.map((part) => typeof part === 'object' ? substitutedIn(part, tokens)?.length : part);
|
|
2958
|
+
if (lengths.every((length) => length !== undefined)) {
|
|
2959
|
+
return { ...rest, lengths: lengths };
|
|
2960
|
+
}
|
|
2961
|
+
return Object.keys(rest).length ? rest : undefined;
|
|
2962
|
+
}
|
|
2619
2963
|
/**
|
|
2620
2964
|
* Each alias settled to what it names. `follow` writes back every one that resolves; one in a
|
|
2621
2965
|
* cycle is removed only once all are followed, as removed any sooner, the next one round the
|
|
@@ -2789,7 +3133,7 @@ function referenced(declaration, tokens) {
|
|
|
2789
3133
|
// property unset rather than trying the next, or the fallback.
|
|
2790
3134
|
const names = [declaration.reference, ...(declaration.alternatives ?? [])];
|
|
2791
3135
|
const token = firstSet(names, tokens);
|
|
2792
|
-
let value = token ?
|
|
3136
|
+
let value = token ? formFor(token, declaration) : fallbackOf(declaration, tokens);
|
|
2793
3137
|
// `calc(var(--n) * 1px)`: the arithmetic gives a unitless token its unit, which is the usual
|
|
2794
3138
|
// way to turn a count into a length. So it reads the bare number, and a length is no such thing.
|
|
2795
3139
|
if (token && declaration.adjust?.number && declaration.kind === 'length') {
|
|
@@ -2837,9 +3181,52 @@ function tokenForm(token, kind) {
|
|
|
2837
3181
|
const value = formOf(token, kind);
|
|
2838
3182
|
return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
|
|
2839
3183
|
}
|
|
3184
|
+
/** A corner's place among four as CSS writes them, before a side's: `borderTopLeftRadius`. */
|
|
3185
|
+
const PLACES = [
|
|
3186
|
+
[/TopLeft|TopStart|StartStart/, 0],
|
|
3187
|
+
[/TopRight|TopEnd|StartEnd/, 1],
|
|
3188
|
+
[/BottomRight|BottomEnd|EndEnd/, 2],
|
|
3189
|
+
[/BottomLeft|BottomStart|EndStart/, 3],
|
|
3190
|
+
[/Top/, 0],
|
|
3191
|
+
[/Right|End/, 1],
|
|
3192
|
+
[/Bottom/, 2],
|
|
3193
|
+
[/Left|Start/, 3],
|
|
3194
|
+
];
|
|
3195
|
+
/** Where a side or a corner comes in a shorthand of four, whatever order its props are kept in. */
|
|
3196
|
+
const placeOf = (prop) => PLACES.find(([named]) => named.test(prop))?.[1] ?? 0;
|
|
3197
|
+
/** The lengths of a token that holds several, for the props of a shorthand to take one each. */
|
|
3198
|
+
class EachSide {
|
|
3199
|
+
lengths;
|
|
3200
|
+
constructor(lengths) {
|
|
3201
|
+
this.lengths = lengths;
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* The length for one of a shorthand's four props, as CSS gives them: two are a pair repeated,
|
|
3205
|
+
* and of three the left takes the right's. Nothing for a property that is not such a
|
|
3206
|
+
* shorthand: a token of several lengths is no value for it, and it is unset.
|
|
3207
|
+
*/
|
|
3208
|
+
at(props, prop) {
|
|
3209
|
+
if (props.length !== 4)
|
|
3210
|
+
return undefined;
|
|
3211
|
+
const index = placeOf(prop);
|
|
3212
|
+
return this.lengths[index] ?? this.lengths[index - 2] ?? this.lengths[0];
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
/**
|
|
3216
|
+
* A set token's value in the form a declaration wants, or its several lengths where it wants a
|
|
3217
|
+
* length and the token has no one: for a shorthand that takes one a side, see `writeSettled`.
|
|
3218
|
+
* Not with arithmetic on it, which is of one length.
|
|
3219
|
+
*/
|
|
3220
|
+
function formFor(token, declaration) {
|
|
3221
|
+
const value = tokenForm(token, declaration.kind);
|
|
3222
|
+
if (value !== undefined || !token.lengths || declaration.kind !== 'length')
|
|
3223
|
+
return value;
|
|
3224
|
+
return declaration.adjust ? undefined : new EachSide(token.lengths);
|
|
3225
|
+
}
|
|
2840
3226
|
/** What a deferred declaration settled to, written to one of its props. */
|
|
2841
3227
|
function writeSettled(own, prop, declaration, settled, layers) {
|
|
2842
|
-
const
|
|
3228
|
+
const one = settled instanceof EachSide ? settled.at(declaration.props, prop) : settled;
|
|
3229
|
+
const value = one ?? declaration.unset;
|
|
2843
3230
|
if (declaration.layers)
|
|
2844
3231
|
writeLayer(own, prop, value, layers);
|
|
2845
3232
|
else
|