@ng-native/fabric 0.17.1 → 0.18.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/css.d.ts CHANGED
@@ -186,13 +186,28 @@ export interface Conditions {
186
186
  /** A run of CSS whitespace: spaces, tabs, newlines, carriage returns and form feeds, and no other. */
187
187
  export declare const CSS_SPACE: RegExp;
188
188
  /** A custom property's value, in each form it can legally take. Absent forms are unusable. */
189
+ /**
190
+ * A colour only the platform resolves, `platform-color(link)` or a system colour, held by its
191
+ * names, which the engine turns into the shape each platform reads (`Engine.color`). It has no
192
+ * channels here, so nothing can mix, fade or shade it, or draw it in a gradient or a shadow.
193
+ */
194
+ export interface PlatformColour {
195
+ readonly platformColor: readonly string[];
196
+ }
197
+ export declare const isPlatformColour: (value: unknown) => value is PlatformColour;
189
198
  export interface TokenValue {
190
199
  readonly length?: number | string;
191
200
  /** An angle, in degrees whatever unit it was written in. */
192
201
  readonly angle?: number;
193
202
  /** A time, in milliseconds whatever unit it was written in. */
194
203
  readonly time?: number;
195
- readonly color?: string;
204
+ /** A colour, or one only the platform resolves, by name: `platform-color(link)`, `LinkText`. */
205
+ readonly color?: string | PlatformColour;
206
+ /**
207
+ * The token holding a platform colour that one made of others, `color-mix(in srgb, var(--x)
208
+ * 50%, white)`, was worked out of, which leaves it with no value: what a warning names.
209
+ */
210
+ readonly platformColourIn?: string;
196
211
  readonly keyword?: string;
197
212
  readonly number?: number;
198
213
  /** A font weight, as the string `fontWeight` takes. */
@@ -329,6 +344,11 @@ export interface DeferredDeclaration {
329
344
  * gradient is not the class that supplies its colours.
330
345
  */
331
346
  readonly gradient?: GradientTemplate | readonly GradientTemplate[];
347
+ /**
348
+ * A band of the box from side to side whose edges are tokens, `clipBand` as a library's sheet
349
+ * writes it for a slider: each edge a percentage, or a token with the percentage beside it.
350
+ */
351
+ readonly band?: readonly BandEdge[];
332
352
  /**
333
353
  * Arithmetic around the reference, settled at build time. `value * scale + offset`, then no
334
354
  * smaller than `floor`. What `calc(var(--x) + 12px)` and `max(var(--x), 16px)` become.
@@ -690,6 +710,14 @@ export interface StyleTarget {
690
710
  }
691
711
  /** Whether a prop of a shape in a drawing is a paint it hands down: see `inheritFrom`. */
692
712
  export declare const handsDownPaint: (key: string) => boolean;
713
+ /**
714
+ * A border's unsided width and colour, written beside the four sides once the cascade and every
715
+ * token have settled them, where they agree. Android strokes a rounded border, the only way it
716
+ * draws one dashed or dotted, from `borderWidth` and `borderColor` alone, and only when every
717
+ * side equals them; given the sides alone it fills each in solid. A border with no width strokes
718
+ * nothing, so preflight's `border: 0 solid` adds nothing to every element.
719
+ */
720
+ export declare function settleUnsidedBorder(own: Record<string, unknown>): void;
693
721
  /**
694
722
  * Deterministic work counters for the matcher.
695
723
  *
@@ -1030,6 +1058,13 @@ export declare class StyleResolver {
1030
1058
  * compiler refuses the same value written out, but a token is only known here.
1031
1059
  */
1032
1060
  onUnreadDisplay: ((name: string, value: string | undefined) => void) | null;
1061
+ /**
1062
+ * Told of a declaration dropped for working a colour out of a token that holds a platform
1063
+ * colour, which has no channels: a `color-mix()`, a relative colour, a gradient or a shadow.
1064
+ * Set only in development: the compiler says so of a sheet that holds both, but only here is it
1065
+ * known for a token one sheet sets and another reads.
1066
+ */
1067
+ onPlatformColour: ((name: string, props: readonly string[], on?: string) => void) | null;
1033
1068
  constructor(globalSheet: StyleSheet | null, conditions: Conditions);
1034
1069
  /**
1035
1070
  * Match `sheet` against every node from now on, as a global sheet, after the application's;
@@ -1317,8 +1352,12 @@ export declare class StyleResolver {
1317
1352
  private settled;
1318
1353
  /** A line's longhands from its tokens, with a width in `em` worked out against the font size. */
1319
1354
  private settledLine;
1355
+ /** Why a declaration came to nothing, where something is told: in development. */
1356
+ private reportUnsettled;
1320
1357
  /** A reference with nothing defined for it and nothing to fall back to. */
1321
1358
  private reportUndefined;
1359
+ /** A colour worked out of a token holding a platform colour, which cannot be. */
1360
+ private reportPlatformColour;
1322
1361
  /** A display token set to a value native has no display for. */
1323
1362
  private reportDisplay;
1324
1363
  /**
@@ -1326,7 +1365,10 @@ export declare class StyleResolver {
1326
1365
  * a colour in it cannot be: a shadow with no colour is not a shadow, as CSS reads it.
1327
1366
  */
1328
1367
  private settledWithin;
1329
- /** A structured value with every deferred length and colour inside it settled. */
1368
+ /**
1369
+ * A structured value with every deferred length and colour inside it settled. A platform colour
1370
+ * is settled only as the `whole` value: inside a shadow or a filter native draws from channels.
1371
+ */
1330
1372
  private filledIn;
1331
1373
  /**
1332
1374
  * A list's parts filled in, a whole shadow or filter token standing for however many entries it
@@ -1361,6 +1403,11 @@ export declare const isNegativeLineHeight: (prop: string, text: string) => boole
1361
1403
  export declare function boundRelativeLength(prop: string, text: string, on?: string): DeferredDeclaration | null;
1362
1404
  /** `hsl()` reads a channels token as hue, saturation and lightness; `rgb()` as sRGB. */
1363
1405
  type ChannelSpace = 'rgb' | 'hsl';
1406
+ /** An edge of a band: a percentage, or a token with the percentage beside it where one is. */
1407
+ type BandEdge = string | {
1408
+ readonly reference: string;
1409
+ readonly fallback?: string;
1410
+ };
1364
1411
  /**
1365
1412
  * Arithmetic with tokens in it, as the compiler leaves it: numbers, token references and
1366
1413
  * `[op, a, b]`, in points, degrees or plain numbers as the slot it fills counts. A percentage
package/dist/css.js CHANGED
@@ -11,6 +11,9 @@ import { ELEMENT_STYLES } from "./element-styles.js";
11
11
  import { relativeColour } from "./relative-colour.js";
12
12
  /** A run of CSS whitespace: spaces, tabs, newlines, carriage returns and form feeds, and no other. */
13
13
  export const CSS_SPACE = /[ \t\n\r\f]+/;
14
+ export const isPlatformColour = (value) => typeof value === 'object' &&
15
+ value !== null &&
16
+ Array.isArray(value.platformColor);
14
17
  /**
15
18
  * Where a form a token does not have can be read from instead. A length is already a
16
19
  * line-height and a single word is already a family, so neither is stored twice in every token
@@ -70,6 +73,9 @@ const INHERITED = new Set([
70
73
  'textShadowOffset',
71
74
  'textShadowRadius',
72
75
  'textShadowColor',
76
+ // `text-wrap` and `word-break`, which CSS inherits, as the paragraph props they are.
77
+ 'lineBreakStrategyIOS',
78
+ 'textBreakStrategy',
73
79
  'selectable',
74
80
  // CSS inherits it, and its `none` is the element alone: a descendant's `auto` takes touches
75
81
  // again. The engine commits a computed `none` as native's `box-none`, which means that.
@@ -82,10 +88,48 @@ const INHERITED = new Set([
82
88
  const INHERITED_POINTER_EVENTS = new Set(['none', 'auto']);
83
89
  /**
84
90
  * A shape's paint, which SVG hands down: a path under an `<svg>` with a `fill` has that fill.
85
- * Inherited only in a library's drawing (`StyleTarget.namespace`). An icon's shapes are left to
86
- * react-native-svg, which hands a group's paint down itself, from what the shape set as its own.
91
+ * Handed down through any element, as a browser does, `.btn { fill: currentColor }` to the
92
+ * icon in the button, and taken only by a library's drawing (`StyleTarget.namespace`). An icon's
93
+ * shapes are left to react-native-svg, which hands a group's paint down itself, from what the
94
+ * shape set as its own.
95
+ *
96
+ * ponytail: an inline style's paint is handed down by a drawing's node alone (`setsInherited`).
97
+ * Hand it down from any node as a rule's is if `[style.fill]` on a box around a drawing is seen.
87
98
  */
88
99
  const SHAPE_PAINT_INHERITED = new Set(['fill', 'stroke', 'strokeWidth']);
100
+ /**
101
+ * Whether a node takes the shape's paint it is handed: a node of a library's drawing, or one a
102
+ * package makes in it, as web-compat makes the span a text's words are drawn by.
103
+ */
104
+ const takesPaint = (node) => node.namespace !== undefined || node.parent?.namespace !== undefined;
105
+ /** Whether a node is handed a shape's paint: see `SHAPE_PAINT_INHERITED`. */
106
+ const handedPaint = (inherited) => inherited['fill'] !== undefined ||
107
+ inherited['stroke'] !== undefined ||
108
+ inherited['strokeWidth'] !== undefined;
109
+ /** The styles of nodes outside a drawing with no rules, by the map they inherit: see `unpainted`. */
110
+ const unpaintedStyles = new WeakMap();
111
+ /**
112
+ * A style without the shape's paint it was handed, for a node outside a drawing: it hands the
113
+ * paint on and takes none, as an icon's shape commits only the paint it was given itself.
114
+ */
115
+ function unpainted(style, own) {
116
+ if (!handedPaint(style))
117
+ return style;
118
+ if (own) {
119
+ for (const key of SHAPE_PAINT_INHERITED)
120
+ if (!(key in own))
121
+ delete style[key];
122
+ return style;
123
+ }
124
+ let bare = unpaintedStyles.get(style);
125
+ if (!bare) {
126
+ bare = { ...style };
127
+ for (const key of SHAPE_PAINT_INHERITED)
128
+ delete bare[key];
129
+ unpaintedStyles.set(style, bare);
130
+ }
131
+ return bare;
132
+ }
89
133
  /** Whether a prop of a shape in a drawing is a paint it hands down: see `inheritFrom`. */
90
134
  export const handsDownPaint = (key) => key === 'propList' || SHAPE_PAINT_INHERITED.has(key);
91
135
  /**
@@ -126,6 +170,47 @@ function settleBorders(own) {
126
170
  else
127
171
  own['borderStyle'] = drawn;
128
172
  }
173
+ /** A border's four sides and its logical ones, by the part of it they hold. */
174
+ const BORDER_SIDES = Object.fromEntries(['Width', 'Color'].map((part) => [
175
+ part,
176
+ {
177
+ physical: ['Top', 'Right', 'Bottom', 'Left'].map((side) => `border${side}${part}`),
178
+ logical: ['Start', 'End', 'Block', 'BlockStart', 'BlockEnd'].map((side) => `border${side}${part}`),
179
+ },
180
+ ]));
181
+ /**
182
+ * A border's unsided width and colour, written beside the four sides once the cascade and every
183
+ * token have settled them, where they agree. Android strokes a rounded border, the only way it
184
+ * draws one dashed or dotted, from `borderWidth` and `borderColor` alone, and only when every
185
+ * side equals them; given the sides alone it fills each in solid. A border with no width strokes
186
+ * nothing, so preflight's `border: 0 solid` adds nothing to every element.
187
+ */
188
+ export function settleUnsidedBorder(own) {
189
+ // No top width, its own or unsided: no four sides that agree. Most views have no border.
190
+ if (own['borderTopWidth'] === undefined && own['borderWidth'] === undefined)
191
+ return;
192
+ const width = agreedSides(own, 'Width');
193
+ if (width === undefined || width === 0)
194
+ return;
195
+ own['borderWidth'] = width;
196
+ const colour = agreedSides(own, 'Color');
197
+ if (colour !== undefined)
198
+ own['borderColor'] = colour;
199
+ }
200
+ /**
201
+ * What the four sides of a border are, each read as native reads it, where they are the same,
202
+ * a colour held in an object by what is in it. A logical side, which a direction makes left or
203
+ * right, leaves it unsaid.
204
+ */
205
+ function agreedSides(own, part) {
206
+ const { physical, logical } = BORDER_SIDES[part];
207
+ if (logical.some((key) => own[key] !== undefined))
208
+ return undefined;
209
+ const unsided = own[`border${part}`];
210
+ const [first, ...rest] = physical.map((key) => own[key] ?? unsided);
211
+ const same = (one) => one === first || JSON.stringify(one) === JSON.stringify(first);
212
+ return rest.every(same) ? first : undefined;
213
+ }
129
214
  /**
130
215
  * Deterministic work counters for the matcher.
131
216
  *
@@ -2226,7 +2311,7 @@ function inheritFrom(parentInherited, own, node) {
2226
2311
  const drawing = node?.namespace !== undefined;
2227
2312
  let inherited = drawing ? ownPaint(node, parentInherited, own) : parentInherited;
2228
2313
  for (const key of Object.keys(own)) {
2229
- if (!(INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))))
2314
+ if (!(INHERITED.has(key) || SHAPE_PAINT_INHERITED.has(key)))
2230
2315
  continue;
2231
2316
  // `pointer-events: inherit`, which only an inline style on an element no rule matches still
2232
2317
  // holds here: what the parent hands down stands.
@@ -2430,6 +2515,13 @@ export class StyleResolver {
2430
2515
  * compiler refuses the same value written out, but a token is only known here.
2431
2516
  */
2432
2517
  onUnreadDisplay = null;
2518
+ /**
2519
+ * Told of a declaration dropped for working a colour out of a token that holds a platform
2520
+ * colour, which has no channels: a `color-mix()`, a relative colour, a gradient or a shadow.
2521
+ * Set only in development: the compiler says so of a sheet that holds both, but only here is it
2522
+ * known for a token one sheet sets and another reads.
2523
+ */
2524
+ onPlatformColour = null;
2433
2525
  constructor(globalSheet, conditions) {
2434
2526
  this.globalSheet = globalSheet;
2435
2527
  this.conditions = conditions;
@@ -2884,7 +2976,9 @@ export class StyleResolver {
2884
2976
  parentContext,
2885
2977
  scope: scope ?? context,
2886
2978
  parentScope,
2887
- style: unstyledStyle(parentInherited, inline),
2979
+ style: takesPaint(node)
2980
+ ? unstyledStyle(parentInherited, inline)
2981
+ : unpainted(unstyledStyle(parentInherited, inline), null),
2888
2982
  inherited: handedOn(node, parentInherited, inline, drawing),
2889
2983
  tokens,
2890
2984
  };
@@ -3054,11 +3148,11 @@ export class StyleResolver {
3054
3148
  const drawing = node.namespace !== undefined;
3055
3149
  const inline = node.inlineInherits ? inlineInherited(node.props['style'], drawing) : null;
3056
3150
  // Nor one that takes a value from its parent's own style, which two parents differ in, nor
3057
- // a node of a drawing, which hands down paint another node with its rules does not.
3151
+ // one that takes a drawing's paint, which another node with its rules does not.
3058
3152
  if (node.customProperties ||
3059
3153
  inline ||
3060
3154
  node.boundStyle ||
3061
- drawing ||
3155
+ takesPaint(node) ||
3062
3156
  matched.some(takesParents)) {
3063
3157
  return this.style(node, matched, parentTokens, parentInherited, inline);
3064
3158
  }
@@ -3108,10 +3202,13 @@ export class StyleResolver {
3108
3202
  this.applyDeferred(result.deferred, own, tokens, parentInherited, result.important);
3109
3203
  }
3110
3204
  settleBorders(own);
3205
+ settleUnsidedBorder(own);
3111
3206
  // `pointer-events: inherit` won the cascade: what the parent hands down stands.
3112
3207
  if (own['pointerEvents'] === 'inherit')
3113
3208
  delete own['pointerEvents'];
3114
3209
  const style = { ...parentInherited, ...own };
3210
+ if (!takesPaint(node))
3211
+ unpainted(style, own);
3115
3212
  settleLineHeight(style);
3116
3213
  const inherited = decorate(style, inheritFrom(parentInherited, own, node), own);
3117
3214
  const rtl = (style['direction'] ?? this.conditions.direction) === 'rtl';
@@ -3286,9 +3383,8 @@ export class StyleResolver {
3286
3383
  continue;
3287
3384
  }
3288
3385
  const settled = this.settled(declaration, own, parentInherited, tokens);
3289
- if (settled === undefined && this.onUndefinedToken) {
3290
- this.reportUndefined(declaration, tokens);
3291
- }
3386
+ if (settled === undefined)
3387
+ this.reportUnsettled(declaration, tokens);
3292
3388
  const outranked = (prop) => !declaration.important && important !== null && prop in important;
3293
3389
  // Said only of a display that is written: an important one wins over the token's.
3294
3390
  const unread = settled === undefined && declaration.kind === 'display';
@@ -3390,6 +3486,13 @@ export class StyleResolver {
3390
3486
  values[prop] = Math.max(points, 0);
3391
3487
  return values;
3392
3488
  }
3489
+ /** Why a declaration came to nothing, where something is told: in development. */
3490
+ reportUnsettled(declaration, tokens) {
3491
+ if (this.onUndefinedToken)
3492
+ this.reportUndefined(declaration, tokens);
3493
+ if (this.onPlatformColour)
3494
+ this.reportPlatformColour(declaration, tokens);
3495
+ }
3393
3496
  /** A reference with nothing defined for it and nothing to fall back to. */
3394
3497
  reportUndefined(declaration, tokens) {
3395
3498
  const name = declaration.reference;
@@ -3401,6 +3504,14 @@ export class StyleResolver {
3401
3504
  return;
3402
3505
  this.onUndefinedToken(name, declaration.props, declaration.on);
3403
3506
  }
3507
+ /** A colour worked out of a token holding a platform colour, which cannot be. */
3508
+ reportPlatformColour(declaration, tokens) {
3509
+ if (declaration.kind !== undefined && !COLOURED_KINDS.has(declaration.kind))
3510
+ return;
3511
+ const name = platformColourRead(declaration, tokens, new Set());
3512
+ if (name)
3513
+ this.onPlatformColour(name, declaration.props, declaration.on);
3514
+ }
3404
3515
  /** A display token set to a value native has no display for. */
3405
3516
  reportDisplay(declaration, tokens) {
3406
3517
  const names = [declaration.reference, ...(declaration.alternatives ?? [])];
@@ -3413,7 +3524,7 @@ export class StyleResolver {
3413
3524
  * a colour in it cannot be: a shadow with no colour is not a shadow, as CSS reads it.
3414
3525
  */
3415
3526
  settledWithin(value, declaration, own, parentInherited, tokens) {
3416
- const filled = this.filledIn(value, declaration, own, parentInherited, tokens);
3527
+ const filled = this.filledIn(value, declaration, own, parentInherited, tokens, true);
3417
3528
  if (filled === UNSETTLED)
3418
3529
  return undefined;
3419
3530
  // As the compiler drops a shadow nobody can see: Tailwind composes five, four of them nothing.
@@ -3421,8 +3532,11 @@ export class StyleResolver {
3421
3532
  ? filled.filter((shadow) => !invisible(shadow.color))
3422
3533
  : filled;
3423
3534
  }
3424
- /** A structured value with every deferred length and colour inside it settled. */
3425
- filledIn(value, declaration, own, parentInherited, tokens) {
3535
+ /**
3536
+ * A structured value with every deferred length and colour inside it settled. A platform colour
3537
+ * is settled only as the `whole` value: inside a shadow or a filter native draws from channels.
3538
+ */
3539
+ filledIn(value, declaration, own, parentInherited, tokens, whole = false) {
3426
3540
  if (!needsFilling(value))
3427
3541
  return value;
3428
3542
  const fill = (part) => this.filledIn(part, declaration, own, parentInherited, tokens);
@@ -3435,10 +3549,11 @@ export class StyleResolver {
3435
3549
  return (ownColour ?? parentInherited['color'] ?? 'black');
3436
3550
  };
3437
3551
  const marked = settledMarker(value, tokens, current);
3438
- if (marked === 'currentcolor')
3439
- return current();
3552
+ const settled = marked === 'currentcolor' ? current() : marked;
3553
+ if (isPlatformColour(settled) && !whole)
3554
+ return UNSETTLED;
3440
3555
  if (marked !== NOT_A_MARKER)
3441
- return marked ?? UNSETTLED;
3556
+ return settled ?? UNSETTLED;
3442
3557
  const pending = value.__defer;
3443
3558
  if (pending)
3444
3559
  return this.computed({ ...declaration, compute: pending }, own, parentInherited);
@@ -3649,7 +3764,14 @@ const INVALID = Object.freeze({});
3649
3764
  * percentage hue does, and unset only when a `var()` in it has nothing to be substituted with.
3650
3765
  */
3651
3766
  function workedOut(token, tokens) {
3652
- return derived(token, tokens) ?? (substitutable(token, tokens) ? INVALID : undefined);
3767
+ const value = derived(token, tokens);
3768
+ if (value)
3769
+ return value;
3770
+ if (!substitutable(token, tokens))
3771
+ return undefined;
3772
+ // Made of a platform colour, which has no channels: invalid, and kept to say why.
3773
+ const from = platformColourRead(token, tokens, new Set());
3774
+ return from ? { platformColourIn: from } : INVALID;
3653
3775
  }
3654
3776
  /** Whether each `var()` in a token made of others names a token that is set, or has a fallback. */
3655
3777
  function substitutable(token, tokens) {
@@ -3932,6 +4054,8 @@ function substituted(value, tokens, path, depth) {
3932
4054
  function referenced(declaration, tokens) {
3933
4055
  if (declaration.gradient)
3934
4056
  return paintLayers(declaration.gradient, tokens);
4057
+ if (declaration.band)
4058
+ return settledBand(declaration.band, tokens);
3935
4059
  // The first token set is substituted, whatever it holds: one of the wrong kind leaves the
3936
4060
  // property unset rather than trying the next, or the fallback.
3937
4061
  const names = [declaration.reference, ...(declaration.alternatives ?? [])];
@@ -4325,6 +4449,23 @@ function fromChannels(value, alpha, tokens, space) {
4325
4449
  ? `rgb(${r}, ${g}, ${b})`
4326
4450
  : `rgba(${r}, ${g}, ${b}, ${Math.round(opacity * 1000) / 1000})`;
4327
4451
  }
4452
+ /**
4453
+ * A band's clip with its edges settled, as `clipBand` is written: none where an edge is a token
4454
+ * that is no percentage of the box, or unset with nothing beside it, as CSS then has no clip.
4455
+ */
4456
+ function settledBand(band, tokens) {
4457
+ const [from, to] = band.map((edge) => percentOf(typeof edge === 'string' ? edge : substitute(edge, tokens, (token) => token.length)));
4458
+ if (from === undefined || to === undefined)
4459
+ return undefined;
4460
+ return `polygon(${from}% 0%, ${to}% 0%, ${to}% 100%, ${from}% 100%)`;
4461
+ }
4462
+ /** A length as a percentage, 0 and one below it included, or undefined for any other. */
4463
+ function percentOf(length) {
4464
+ if (length === 0)
4465
+ return '0';
4466
+ const share = typeof length === 'string' ? /^(-?\d*\.?\d+)%$/.exec(length) : null;
4467
+ return share ? String(Number(share[1])) : undefined;
4468
+ }
4328
4469
  /** One template, or a layered background of several: every layer painted, or, as CSS would, none. */
4329
4470
  function paintLayers(gradient, tokens) {
4330
4471
  if (!Array.isArray(gradient))
@@ -4364,6 +4505,9 @@ function paintedStops(template, tokens) {
4364
4505
  for (const stop of template.colorStops) {
4365
4506
  const token = stop.colour ? undefined : tokens[stop.reference];
4366
4507
  const color = stop.colour ? resolveColour(stop.colour, tokens) : token?.color;
4508
+ // A platform colour is no stop: a gradient is drawn from channels, which it has none of.
4509
+ if (isPlatformColour(color))
4510
+ return undefined;
4367
4511
  // `none` is how the Tailwind flattening writes `via-none`: the stop left out, not invalid.
4368
4512
  if (color === undefined && token && token.keyword !== 'none')
4369
4513
  return undefined;
@@ -4433,6 +4577,14 @@ function resolveColour(expression, tokens, current) {
4433
4577
  }
4434
4578
  /** A colour inside another: `currentColor` there, written or in a token, is the node's colour. */
4435
4579
  function innerColour(expression, tokens, current) {
4580
+ return withChannels(colourWithin(expression, tokens, current));
4581
+ }
4582
+ /**
4583
+ * A colour something works another out of, which a platform colour cannot be: it has no channels.
4584
+ * Undefined for one, so what it was in is not settled either, as for a token that is not set.
4585
+ */
4586
+ const withChannels = (colour) => isPlatformColour(colour) ? undefined : colour;
4587
+ function colourWithin(expression, tokens, current) {
4436
4588
  if (!current)
4437
4589
  return resolveColour(expression, tokens);
4438
4590
  if ('reference' in expression) {
@@ -4441,7 +4593,7 @@ function innerColour(expression, tokens, current) {
4441
4593
  return current();
4442
4594
  }
4443
4595
  const colour = resolveColour(expression, tokens, current);
4444
- return colour?.toLowerCase() === 'currentcolor' ? current() : colour;
4596
+ return typeof colour === 'string' && colour.toLowerCase() === 'currentcolor' ? current() : colour;
4445
4597
  }
4446
4598
  /** A mix's two sides, a mix inside it unrounded, as CSS rounds only the colour at the end. */
4447
4599
  function mixSides(mix, tokens, current) {
@@ -4696,7 +4848,8 @@ function tokenColour(expression, tokens) {
4696
4848
  if (set)
4697
4849
  return formOf(set, 'color');
4698
4850
  const token = expression.fallbackToken;
4699
- return expression.fallback ?? (token && formOf(derived(token, tokens), 'color'));
4851
+ return (expression.fallback ??
4852
+ (token && formOf(derived(token, tokens), 'color')));
4700
4853
  }
4701
4854
  /**
4702
4855
  * A shadow's colour from a token that is the colour or the word `inset`: currentcolor, which the
@@ -4707,6 +4860,39 @@ function insetOrColour(token) {
4707
4860
  return 'currentcolor';
4708
4861
  return formOf(token, 'color');
4709
4862
  }
4863
+ /** The kinds of token a colour is part of. */
4864
+ const COLOURED_KINDS = new Set(['color', 'shadow', 'filter']);
4865
+ /**
4866
+ * The first token a declaration or a token reads, at any depth and through the tokens those read,
4867
+ * that holds a platform colour. Asked only of one that came to nothing, in development.
4868
+ */
4869
+ function platformColourRead(value, tokens, seen) {
4870
+ if (value === null || typeof value !== 'object')
4871
+ return undefined;
4872
+ const record = value;
4873
+ const names = [record['reference'], record['alias'], ...[record['alternatives']].flat()];
4874
+ for (const name of names) {
4875
+ const found = typeof name === 'string' ? platformColourNamed(name, tokens, seen) : undefined;
4876
+ if (found)
4877
+ return found;
4878
+ }
4879
+ for (const part of Object.values(record)) {
4880
+ const found = platformColourRead(part, tokens, seen);
4881
+ if (found)
4882
+ return found;
4883
+ }
4884
+ return undefined;
4885
+ }
4886
+ /** `platformColourRead` of the token `name`, which is the one when it holds a platform colour. */
4887
+ function platformColourNamed(name, tokens, seen) {
4888
+ if (seen.has(name))
4889
+ return undefined;
4890
+ seen.add(name);
4891
+ const token = tokens[name];
4892
+ if (isPlatformColour(token?.color))
4893
+ return name;
4894
+ return token?.platformColourIn ?? platformColourRead(token, tokens, seen);
4895
+ }
4710
4896
  /** A marker the compiler leaves in a structured value for a colour only the device can settle. */
4711
4897
  function colourMarker(value) {
4712
4898
  return value?.__colour;
@@ -4737,8 +4923,9 @@ function withPercent(value, adjust) {
4737
4923
  * the app meant, and turning it into NaN would be a silent layout rather than a visible one.
4738
4924
  */
4739
4925
  function adjusted(value, adjust) {
4926
+ // A platform colour has no alpha to fade: unset, where it was painted as it stood.
4740
4927
  if (adjust.alpha !== undefined)
4741
- return faded(value, adjust.alpha);
4928
+ return isPlatformColour(value) ? undefined : faded(value, adjust.alpha);
4742
4929
  const em = value?.__defer;
4743
4930
  if (adjust.percent !== undefined)
4744
4931
  return withPercent(value, adjust);