@ng-native/fabric 0.9.0 → 0.11.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 CHANGED
@@ -51,6 +51,11 @@ export interface Compound {
51
51
  readonly nth?: readonly NthTest[];
52
52
  /** `:empty`: no children at all, text included. */
53
53
  readonly empty?: true;
54
+ /**
55
+ * What a `::ng-deep` is written after: an element of the component the sheet is of, or its
56
+ * host. A rule with one is for what is under it, in other components' views too.
57
+ */
58
+ readonly own?: true;
54
59
  /** `:has()`: every one of these must find a match beneath the node. */
55
60
  readonly has?: readonly HasTest[];
56
61
  }
@@ -65,6 +70,8 @@ export interface NthTest {
65
70
  readonly a: number;
66
71
  readonly b: number;
67
72
  readonly fromEnd?: true;
73
+ /** Counted among the siblings with the node's own element name: `:nth-of-type()`. */
74
+ readonly ofType?: true;
68
75
  }
69
76
  export type Combinator = 'descendant' | 'child' | 'next-sibling' | 'later-sibling';
70
77
  export interface StyleRule {
@@ -72,6 +79,8 @@ export interface StyleRule {
72
79
  /** Between compounds; length is `compounds.length - 1`. */
73
80
  readonly combinators: readonly Combinator[];
74
81
  readonly specificity: number;
82
+ /** Written with `::ng-deep`: for what is under its component, in other components' views too. */
83
+ readonly deep?: true;
75
84
  /**
76
85
  * The cascade layer the rule is in, as its place in its sheet's `layers`, or nothing for a rule
77
86
  * in no layer, which beats every layered one whatever their specificity.
@@ -162,6 +171,16 @@ export interface TokenValue {
162
171
  readonly lineHeight?: {
163
172
  readonly __defer: DeferredDeclaration['compute'];
164
173
  };
174
+ /**
175
+ * Two to four lengths, which a shorthand of sides or corners takes a value each from:
176
+ * `padding: var(--list-padding)` of `--list-padding: 0.25rem 0.5rem`.
177
+ */
178
+ readonly lengths?: readonly (number | string)[];
179
+ /**
180
+ * The same where a part is a `var()`, each such part as the alias a token that is one is:
181
+ * `0 var(--modal-padding) var(--modal-padding)`. Worked out into `lengths` where it is defined.
182
+ */
183
+ readonly deferredLengths?: readonly (number | string | TokenValue)[];
165
184
  /** A whole shadow list, in the processed shape `boxShadow` takes. */
166
185
  readonly shadow?: readonly unknown[];
167
186
  /** One filter function, as the one-entry list `filter` takes: a slot of Tailwind's filters. */
@@ -674,6 +693,15 @@ export interface SiblingReach {
674
693
  * Where no sheet reaches either way, only the elements at the two ends can match differently.
675
694
  */
676
695
  export declare function siblingReach(sheet: StyleSheet): SiblingReach;
696
+ /**
697
+ * A compound whose element's place is read from another element, with the classes the rule asks
698
+ * of what that element is in: its parent's where `child`, and otherwise of anything above it.
699
+ */
700
+ export interface PlaceReader {
701
+ readonly compound: Compound;
702
+ readonly above?: readonly string[];
703
+ readonly child?: boolean;
704
+ }
677
705
  /**
678
706
  * Each compound in a sheet whose element's place among its siblings is read from another
679
707
  * element: one with something under it in the rule, `.row:nth-child(odd) .label` and
@@ -687,7 +715,7 @@ export declare function siblingReach(sheet: StyleSheet): SiblingReach;
687
715
  * A place read by the element a rule styles, or by the elements before it that the rule steps
688
716
  * from (`.a:first-child + .b`), is that element's own: the rules it matches say.
689
717
  */
690
- export declare function placeReadElsewhere(sheet: StyleSheet): readonly Compound[];
718
+ export declare function placeReadElsewhere(sheet: StyleSheet): readonly PlaceReader[];
691
719
  /** One rule in a cascade: the sheet it came from, which `:host` needs, and its weight here. */
692
720
  interface RuleEntry {
693
721
  readonly rule: StyleRule;
@@ -757,6 +785,17 @@ export interface Subjects {
757
785
  */
758
786
  own?: boolean;
759
787
  }
788
+ /**
789
+ * A box a rule asks `:has()` of that is not the node it styles, `.card:has(.x) .title`, by the
790
+ * classes the rule asks of that box, with what the rule is for: the nodes under such a box to
791
+ * match again when what is in it changes.
792
+ */
793
+ export interface HasAbove {
794
+ readonly classes: readonly string[];
795
+ readonly subjects: Subjects;
796
+ }
797
+ /** Each box a sheet's rules ask `:has()` of above the node they style. */
798
+ export declare function hasAbove(sheet: StyleSheet): readonly HasAbove[];
760
799
  /** Whether an inline style sets a property children inherit. Allocates nothing: it runs per set. */
761
800
  export declare function setsInherited(style: unknown): boolean;
762
801
  /**
@@ -878,6 +917,12 @@ export declare class StyleResolver {
878
917
  /** The tests of the `class` attribute itself in those sheets, which any class may answer. */
879
918
  private readonly classTests;
880
919
  private readonly noted;
920
+ /**
921
+ * The sheets with a rule written with `::ng-deep`, which is for what is under the component
922
+ * in the views of the components it holds. None in most apps, and nothing is asked then.
923
+ */
924
+ private readonly deepSheets;
925
+ private readonly deepIndexes;
881
926
  /** Read the classes a sheet names, once, as soon as any node is given it. */
882
927
  noteSheet(sheet: StyleSheet | null | undefined): void;
883
928
  /** The props a selector of some sheet asks about: an id, and each attribute by its name. */
@@ -1009,6 +1054,13 @@ export declare class StyleResolver {
1009
1054
  * it, so it loses to their extra attribute and wins a tie with them.
1010
1055
  */
1011
1056
  private rulesFor;
1057
+ /**
1058
+ * Add the `::ng-deep` rules of each component a node is in whose sheet is not the node's own:
1059
+ * a node is tried against its own component's rules already, those among them.
1060
+ */
1061
+ private reachedFromAbove;
1062
+ /** A sheet's `::ng-deep` rules alone, filed as a component's own are. */
1063
+ private deepIndex;
1012
1064
  /** Weakest first: by layer where any sheet has one, then by weight, then as they were filed. */
1013
1065
  private readonly cascadeOrder;
1014
1066
  /** A component's sheet as the rules of a host or of a creator, which differ only in their place. */
@@ -1049,6 +1101,8 @@ export declare class StyleResolver {
1049
1101
  * edge the inline style sets takes the other form of it out, as a later rule would.
1050
1102
  */
1051
1103
  overOtherForms(inline: Readonly<Record<string, unknown>>, style: Record<string, unknown>): void;
1104
+ /** Whether a settled style is of an element read right to left: by its own, or the app's. */
1105
+ readsRightToLeft(style: Readonly<Record<string, unknown>>): boolean;
1052
1106
  /**
1053
1107
  * The direction an element is laid out in: the last `direction` its own rules give it, the one
1054
1108
  * it inherits, or the app's. A logical corner is a different physical one in each.
package/dist/css.js CHANGED
@@ -259,6 +259,16 @@ function matchesValue(value, operator, expected) {
259
259
  return value === expected || value.startsWith(`${expected}-`);
260
260
  }
261
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
+ }
262
272
  /*
263
273
  * Exempt from the complexity limit deliberately. This is a flat conjunction of independent
264
274
  * guards, not nested logic: cyclomatic complexity scores the two the same, and they are not the
@@ -280,6 +290,8 @@ function matchesCompound(node, compound, sheet) {
280
290
  }
281
291
  if (compound.type !== undefined && compound.type !== node.name)
282
292
  return false;
293
+ if (compound.own !== undefined && !ownedBy(node, compound, sheet))
294
+ return false;
283
295
  if (compound.id !== undefined && node.props['nativeID'] !== compound.id)
284
296
  return false;
285
297
  for (const className of compound.classes) {
@@ -433,6 +445,82 @@ function matchAny(first, next, rule, index, compound, sheet) {
433
445
  }
434
446
  return false;
435
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;
436
524
  const TAKES_PARENTS = new WeakMap();
437
525
  /** Whether a rule has an `inherit` in it, which is settled from the parent's own style. */
438
526
  function takesParents(rule) {
@@ -496,8 +584,9 @@ function stepsAlong(combinators) {
496
584
  * matched again by other means, and which way it reaches is not worked out.
497
585
  */
498
586
  function countSiblings(compound, reach, withinHas) {
499
- // Which is first and which is last is the ends, and no count.
500
- const counts = (compound.nth ?? []).filter((test) => test.a !== 0 || test.b !== 1);
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);
501
590
  if (counts.some((test) => withinHas || !test.fromEnd))
502
591
  reach.after = Infinity;
503
592
  if (counts.some((test) => withinHas || test.fromEnd))
@@ -537,10 +626,13 @@ export function placeReadElsewhere(sheet) {
537
626
  if (joined === 'next-sibling' || joined === 'later-sibling')
538
627
  return;
539
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';
540
632
  start = at + 1;
541
633
  const above = at < rule.compounds.length - 1;
542
634
  if (above && (run.length > 1 || run.some(readsPlace)))
543
- elsewhere.push(compound);
635
+ elsewhere.push({ compound, above: list?.classes, child });
544
636
  });
545
637
  }
546
638
  known = elsewhere;
@@ -567,7 +659,7 @@ function placeReadAbove(compound, elsewhere) {
567
659
  ];
568
660
  for (const inner of above)
569
661
  if (readsPlace(inner))
570
- elsewhere.push(inner);
662
+ elsewhere.push({ compound: inner });
571
663
  for (const inner of nestedCompounds(compound))
572
664
  placeReadAbove(inner, elsewhere);
573
665
  }
@@ -601,10 +693,10 @@ function matchesNth(node, test) {
601
693
  const siblings = node.parent?.children;
602
694
  if (!siblings)
603
695
  return false;
604
- if (test.a === 0 && test.b === 1) {
696
+ if (test.a === 0 && test.b === 1 && !test.ofType) {
605
697
  return (test.fromEnd ? lastElement(siblings) : firstElement(siblings)) === node;
606
698
  }
607
- const from = countPosition(node, siblings, test.fromEnd === true);
699
+ const from = countPosition(node, siblings, test.fromEnd === true, test.ofType === true);
608
700
  if (from === 0)
609
701
  return false;
610
702
  // `an + b` for some whole number of steps, which is what CSS counts from.
@@ -822,11 +914,11 @@ function byImportance(matched, places) {
822
914
  }));
823
915
  return [...plain, ...important];
824
916
  }
825
- function countPosition(node, siblings, fromEnd) {
917
+ function countPosition(node, siblings, fromEnd, ofType) {
826
918
  let position = 0;
827
919
  let seen = 0;
828
920
  for (const sibling of siblings) {
829
- if (!isElement(sibling))
921
+ if (!isElement(sibling) || (ofType && sibling.name !== node.name))
830
922
  continue;
831
923
  seen++;
832
924
  if (sibling === node)
@@ -1128,6 +1220,42 @@ function subjectsOf(sheet) {
1128
1220
  subjects.set(sheet, (found = all));
1129
1221
  return found;
1130
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
+ }
1131
1259
  /** Note in `into` what a rule is for. */
1132
1260
  function noteSubject(into, rule) {
1133
1261
  const subject = rule.compounds.at(-1);
@@ -1245,10 +1373,10 @@ function classPlaces(rule) {
1245
1373
  }
1246
1374
  const joined = rule.combinators[index];
1247
1375
  under &&= joined === 'child' || joined === 'descendant';
1248
- const { classes, ...rest } = compound;
1249
- collectClasses(rest, elsewhere, []);
1250
- for (const name of classes)
1251
- (under ? inside : elsewhere).add(name);
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);
1252
1380
  }
1253
1381
  for (const name of elsewhere) {
1254
1382
  own.delete(name);
@@ -1627,11 +1755,19 @@ export class StyleResolver {
1627
1755
  /** The tests of the `class` attribute itself in those sheets, which any class may answer. */
1628
1756
  classTests = [];
1629
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();
1630
1764
  /** Read the classes a sheet names, once, as soon as any node is given it. */
1631
1765
  noteSheet(sheet) {
1632
1766
  if (!sheet || this.noted.has(sheet))
1633
1767
  return;
1634
1768
  this.noted.add(sheet);
1769
+ if (sheet.rules.some((rule) => rule.deep))
1770
+ this.deepSheets.add(sheet);
1635
1771
  const { classes, reads, named } = classReach(sheet);
1636
1772
  for (const [name, how] of classes)
1637
1773
  this.noteReach(name, how);
@@ -1935,6 +2071,7 @@ export class StyleResolver {
1935
2071
  hasNoRules(node) {
1936
2072
  return (node.sheet === null &&
1937
2073
  node.hostSheet === null &&
2074
+ this.deepSheets.size === 0 &&
1938
2075
  this.globalSheet === null &&
1939
2076
  this.addedSheets.length === 0 &&
1940
2077
  !node.customProperties &&
@@ -2015,9 +2152,33 @@ export class StyleResolver {
2015
2152
  indexes.push(this.ownIndex(node.hostSheet, HOSTS));
2016
2153
  if (node.sheet)
2017
2154
  indexes.push(this.ownIndex(node.sheet, CREATED));
2155
+ if (this.deepSheets.size)
2156
+ this.reachedFromAbove(node, indexes);
2018
2157
  indexes.push(this.sharedIndex());
2019
2158
  return candidateRules(node, indexes, this.cascadeOrder);
2020
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
+ }
2021
2182
  /** Weakest first: by layer where any sheet has one, then by weight, then as they were filed. */
2022
2183
  cascadeOrder = (a, b) => {
2023
2184
  const layers = this.layered
@@ -2157,8 +2318,15 @@ export class StyleResolver {
2157
2318
  /** The rules that apply to a node, weakest first. */
2158
2319
  matched(node, entries) {
2159
2320
  const out = [];
2321
+ // What the node's ancestors are, gathered for the first rule that names one.
2322
+ let above;
2160
2323
  for (const { rule, sheet } of entries) {
2161
- if (this.conditionHolds(rule) && matches(node, rule, sheet))
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))
2162
2330
  out.push(rule);
2163
2331
  }
2164
2332
  return out;
@@ -2208,13 +2376,17 @@ export class StyleResolver {
2208
2376
  for (const [at, key] of written.entries()) {
2209
2377
  if (!(key in TWIN.ltr))
2210
2378
  continue;
2211
- twin ??= TWIN[(style['direction'] ?? this.conditions.direction) === 'rtl' ? 'rtl' : 'ltr'];
2379
+ twin ??= TWIN[this.readsRightToLeft(style) ? 'rtl' : 'ltr'];
2212
2380
  const other = twin[key];
2213
2381
  // Where the inline style sets both, the one it set later stands.
2214
2382
  if (written.indexOf(other) < at)
2215
2383
  delete style[other];
2216
2384
  }
2217
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
+ }
2218
2390
  /**
2219
2391
  * The direction an element is laid out in: the last `direction` its own rules give it, the one
2220
2392
  * it inherits, or the app's. A logical corner is a different physical one in each.
@@ -2682,6 +2854,9 @@ function substitutable(token, tokens) {
2682
2854
  return hslSubstitutable(token.hsl, tokens);
2683
2855
  if (token.deferredColour)
2684
2856
  return colourSubstitutable(token.deferredColour, tokens);
2857
+ if (token.deferredLengths) {
2858
+ return token.deferredLengths.every((part) => typeof part !== 'object' || substitutedIn(part, tokens) !== undefined);
2859
+ }
2685
2860
  // Each marker is one reading of the same text, and a fallback one cannot read is dropped from it.
2686
2861
  return token.deferredCalc.some((marker) => calcSubstitutable(marker.expression, tokens));
2687
2862
  }
@@ -2760,7 +2935,7 @@ function inCycles(names, waitingOn) {
2760
2935
  }
2761
2936
  /** Whether a token is made of others, and so worked out where it is defined. */
2762
2937
  function isDerived(token) {
2763
- return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc);
2938
+ return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc || token?.deferredLengths);
2764
2939
  }
2765
2940
  /** A token made of others, worked out from the tokens in scope. */
2766
2941
  function derived(token, tokens) {
@@ -2768,8 +2943,23 @@ function derived(token, tokens) {
2768
2943
  return colourToken(resolveHsl(token.hsl, tokens));
2769
2944
  if (token.deferredColour)
2770
2945
  return colourToken(resolveColour(token.deferredColour, tokens));
2946
+ if (token.deferredLengths)
2947
+ return lengthsToken(token, tokens);
2771
2948
  return calcToken(token.deferredCalc, tokens);
2772
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
+ }
2773
2963
  /**
2774
2964
  * Each alias settled to what it names. `follow` writes back every one that resolves; one in a
2775
2965
  * cycle is removed only once all are followed, as removed any sooner, the next one round the
@@ -2943,7 +3133,7 @@ function referenced(declaration, tokens) {
2943
3133
  // property unset rather than trying the next, or the fallback.
2944
3134
  const names = [declaration.reference, ...(declaration.alternatives ?? [])];
2945
3135
  const token = firstSet(names, tokens);
2946
- let value = token ? tokenForm(token, declaration.kind) : fallbackOf(declaration, tokens);
3136
+ let value = token ? formFor(token, declaration) : fallbackOf(declaration, tokens);
2947
3137
  // `calc(var(--n) * 1px)`: the arithmetic gives a unitless token its unit, which is the usual
2948
3138
  // way to turn a count into a length. So it reads the bare number, and a length is no such thing.
2949
3139
  if (token && declaration.adjust?.number && declaration.kind === 'length') {
@@ -2991,9 +3181,52 @@ function tokenForm(token, kind) {
2991
3181
  const value = formOf(token, kind);
2992
3182
  return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
2993
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
+ }
2994
3226
  /** What a deferred declaration settled to, written to one of its props. */
2995
3227
  function writeSettled(own, prop, declaration, settled, layers) {
2996
- const value = settled ?? declaration.unset;
3228
+ const one = settled instanceof EachSide ? settled.at(declaration.props, prop) : settled;
3229
+ const value = one ?? declaration.unset;
2997
3230
  if (declaration.layers)
2998
3231
  writeLayer(own, prop, value, layers);
2999
3232
  else