@ng-native/fabric 0.1.2 → 0.1.3
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/README.md +1 -1
- package/dist/css.d.ts +64 -13
- package/dist/css.js +209 -46
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +56 -3
- package/dist/engine.js +144 -9
- package/dist/engine.js.map +1 -1
- package/dist/font-faces.d.ts +42 -0
- package/dist/font-faces.js +112 -0
- package/dist/font-faces.js.map +1 -0
- package/dist/forms-inputs.js +1 -1
- package/dist/forms-inputs.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/inline-token.d.ts +3 -2
- package/dist/inline-token.js +73 -1
- package/dist/inline-token.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ const platform = nativePlatform(); // 'ios' | 'android'
|
|
|
49
49
|
[what CSS reaches a device](https://ng-native.com/packages/fabric/supported-css) and
|
|
50
50
|
[animation](https://ng-native.com/packages/fabric/animation)
|
|
51
51
|
- [Root README](https://github.com/ng-native/ng-native/blob/main/README.md) and
|
|
52
|
-
[ARCHITECTURE.md](https://github.com/ng-native/ng-native/blob/main/ARCHITECTURE.md)
|
|
52
|
+
[ARCHITECTURE.md](https://github.com/ng-native/ng-native/blob/main/docs/ARCHITECTURE.md)
|
|
53
53
|
|
|
54
54
|
## License
|
|
55
55
|
|
package/dist/css.d.ts
CHANGED
|
@@ -125,15 +125,24 @@ export interface TokenValue {
|
|
|
125
125
|
readonly transform?: readonly unknown[];
|
|
126
126
|
/** One font variant, as the one-entry list `fontVariant` takes: a numeric variant's slot. */
|
|
127
127
|
readonly fontVariant?: readonly string[];
|
|
128
|
-
/** Bare colour channels, `13, 110, 253
|
|
128
|
+
/** Bare colour channels as `rgb()` reads them, 0 to 255: `13, 110, 253` or `100% 0% 0%`. */
|
|
129
129
|
readonly channels?: readonly number[];
|
|
130
|
+
/** The same as `hsl()` reads them: degrees, then saturation and lightness as fractions. */
|
|
131
|
+
readonly hslChannels?: readonly number[];
|
|
132
|
+
/** A transition's curve, as the cubic-bezier control points a timing keyword stands for. */
|
|
133
|
+
readonly easing?: readonly number[];
|
|
134
|
+
/** A whole animation, as the spec the engine plays: `--animate-spin`. */
|
|
135
|
+
readonly animation?: Readonly<Record<string, unknown>>;
|
|
130
136
|
/**
|
|
131
|
-
* Defined as `rgba(var(--channels), <alpha>)
|
|
132
|
-
*
|
|
137
|
+
* Defined as a colour made of other tokens: `rgba(var(--channels), <alpha>)`, or a `color-mix()`
|
|
138
|
+
* of one. Settled on the node that defines it, as an `hsl` one is.
|
|
133
139
|
*/
|
|
134
|
-
readonly deferredColour?:
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
readonly deferredColour?: ColourExpression;
|
|
141
|
+
/**
|
|
142
|
+
* Defined as arithmetic of other tokens, `calc(var(--spacing) * 4)`, read as a length and as a
|
|
143
|
+
* number. Settled on the node that defines it, as an `hsl` one is.
|
|
144
|
+
*/
|
|
145
|
+
readonly deferredCalc?: readonly CalcMarker[];
|
|
137
146
|
/**
|
|
138
147
|
* Defined as another token, `var(--name)`. Resolved on the node that defines it, against the
|
|
139
148
|
* tokens in scope there, and passed down resolved - which is what a browser does.
|
|
@@ -159,7 +168,7 @@ export type HslChannel = number | {
|
|
|
159
168
|
readonly fallback?: number;
|
|
160
169
|
};
|
|
161
170
|
/** The forms a token can be read in: one per kind of value a use site can need. */
|
|
162
|
-
export type TokenKind = 'length' | 'color' | 'keyword' | 'number' | 'weight' | 'family' | 'lineHeight' | 'shadow' | 'filter' | 'transform' | 'fontVariant' | 'channels';
|
|
171
|
+
export type TokenKind = 'length' | 'color' | 'keyword' | 'number' | 'weight' | 'family' | 'lineHeight' | 'shadow' | 'filter' | 'transform' | 'fontVariant' | 'channels' | 'hslChannels' | 'easing' | 'time' | 'animation';
|
|
163
172
|
export interface DeferredDeclaration {
|
|
164
173
|
/**
|
|
165
174
|
* The React Native style properties this lands in. Usually one, but a four-sided shorthand is
|
|
@@ -194,6 +203,8 @@ export interface DeferredDeclaration {
|
|
|
194
203
|
readonly reference: string;
|
|
195
204
|
readonly fallback?: number;
|
|
196
205
|
};
|
|
206
|
+
/** For a colour built from `channels`, read through `hsl()` rather than `rgb()`. */
|
|
207
|
+
readonly space?: ChannelSpace;
|
|
197
208
|
/**
|
|
198
209
|
* A gradient whose stops are custom properties, filled in once a node's tokens are known. What
|
|
199
210
|
* a utility framework's `from-*` and `to-*` classes compile to, since the class that paints the
|
|
@@ -247,6 +258,7 @@ export type ColourExpression = {
|
|
|
247
258
|
readonly channels: {
|
|
248
259
|
readonly reference: string;
|
|
249
260
|
readonly fallback?: readonly number[];
|
|
261
|
+
readonly space?: ChannelSpace;
|
|
250
262
|
};
|
|
251
263
|
readonly alpha?: number | {
|
|
252
264
|
readonly reference: string;
|
|
@@ -314,12 +326,18 @@ export interface StyleSheet {
|
|
|
314
326
|
* has to re-resolve a whole child list when it changes. It only does that where a sheet asks.
|
|
315
327
|
*/
|
|
316
328
|
readonly structural?: true;
|
|
329
|
+
/**
|
|
330
|
+
* The `@font-face` rules the sheet declares, each `source` the bundler's `require` of the file.
|
|
331
|
+
* The engine does not read them: `loadFonts()` in `@ng-native/expo/fonts` registers them with
|
|
332
|
+
* the platform before mount.
|
|
333
|
+
*/
|
|
334
|
+
readonly fonts?: readonly {
|
|
335
|
+
readonly family: string;
|
|
336
|
+
readonly source: unknown;
|
|
337
|
+
readonly weight?: number;
|
|
338
|
+
readonly style?: string;
|
|
339
|
+
}[];
|
|
317
340
|
}
|
|
318
|
-
/**
|
|
319
|
-
* What one node resolved to. `style` is what it renders with; `inherited` is what its children
|
|
320
|
-
* start from, which is the parent's own `inherited` plus whatever inheritable values this node
|
|
321
|
-
* set. Keeping the two apart is what lets resolution go downwards.
|
|
322
|
-
*/
|
|
323
341
|
export interface StyleCache {
|
|
324
342
|
epoch: number;
|
|
325
343
|
/**
|
|
@@ -560,7 +578,26 @@ export declare class StyleResolver {
|
|
|
560
578
|
* survives and a later sheet wins equal weight.
|
|
561
579
|
*/
|
|
562
580
|
private merge;
|
|
563
|
-
/**
|
|
581
|
+
/**
|
|
582
|
+
* A node's style from the rules it matched, shared with every other node that matched the same
|
|
583
|
+
* rules under the same inherited values and tokens: a list's rows, and what is inside each.
|
|
584
|
+
*
|
|
585
|
+
* Everything past matching depends on those alone, and it is most of the work where a sheet
|
|
586
|
+
* leaves values to the device: settling each `var()`, mixing colours, filling a shadow's slots.
|
|
587
|
+
* A node that sets custom properties of its own is worked out alone. What is shared is never
|
|
588
|
+
* written to after, so sharing it is safe; each node still has a cache and a context of its own.
|
|
589
|
+
*/
|
|
590
|
+
private styled;
|
|
591
|
+
private readonly shared;
|
|
592
|
+
private sharedGeneration;
|
|
593
|
+
private readonly ruleIds;
|
|
594
|
+
private nextRuleId;
|
|
595
|
+
private ruleId;
|
|
596
|
+
/** A node's style from the rules it matched, worked out. */
|
|
597
|
+
private style;
|
|
598
|
+
/** The rules that apply to a node, weakest first. */
|
|
599
|
+
private matched;
|
|
600
|
+
/** The declarations of the rules a node matched. The list is sorted, so later simply wins. */
|
|
564
601
|
private cascade;
|
|
565
602
|
/**
|
|
566
603
|
* Whether a rule's media query holds, memoised per rule.
|
|
@@ -601,6 +638,20 @@ export declare class StyleResolver {
|
|
|
601
638
|
/** A length relative to the viewport or to the font size in scope. */
|
|
602
639
|
private computed;
|
|
603
640
|
}
|
|
641
|
+
/** `hsl()` reads a channels token as hue, saturation and lightness; `rgb()` as sRGB. */
|
|
642
|
+
type ChannelSpace = 'rgb' | 'hsl';
|
|
643
|
+
/**
|
|
644
|
+
* Arithmetic with tokens in it, as the compiler leaves it: numbers, token references and
|
|
645
|
+
* `[op, a, b]`, in points, degrees or plain numbers as the slot it fills counts.
|
|
646
|
+
*/
|
|
647
|
+
type CalcExpression = number | {
|
|
648
|
+
readonly reference: string;
|
|
649
|
+
readonly fallback?: number;
|
|
650
|
+
} | readonly ['+' | '-' | '*' | '/' | 'max' | 'min', CalcExpression, CalcExpression];
|
|
651
|
+
interface CalcMarker {
|
|
652
|
+
readonly expression: CalcExpression;
|
|
653
|
+
readonly kind: 'length' | 'angle' | 'time' | 'number';
|
|
654
|
+
}
|
|
604
655
|
/**
|
|
605
656
|
* Where a component's compiled sheet lives: a static on the component class, written by the
|
|
606
657
|
* Metro transformer at build time.
|
package/dist/css.js
CHANGED
|
@@ -594,25 +594,15 @@ export class StyleResolver {
|
|
|
594
594
|
return this.unstyled(node, epoch, parent, parentContext, parentInherited);
|
|
595
595
|
}
|
|
596
596
|
styleStats.nodesResolved++;
|
|
597
|
-
const
|
|
598
|
-
// Tokens are in scope for this node's own declarations as well as its descendants', so they
|
|
599
|
-
// are merged before any `var()` here is resolved.
|
|
597
|
+
const matched = this.matched(node, this.rulesFor(node));
|
|
600
598
|
const parentTokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
601
|
-
const
|
|
602
|
-
const own = result.declarations;
|
|
603
|
-
if (result.deferred) {
|
|
604
|
-
this.applyDeferred(result.deferred, own, tokens, parentInherited, result.important);
|
|
605
|
-
}
|
|
606
|
-
if (own['borderStyle'] === 'none')
|
|
607
|
-
drawNoBorder(own);
|
|
599
|
+
const styled = this.styled(node, matched, parentTokens, parentInherited);
|
|
608
600
|
const cache = {
|
|
609
601
|
epoch,
|
|
610
602
|
generation: this.generation,
|
|
611
603
|
context: {},
|
|
612
604
|
parentContext,
|
|
613
|
-
|
|
614
|
-
inherited: inheritFrom(parentInherited, own),
|
|
615
|
-
tokens,
|
|
605
|
+
...styled,
|
|
616
606
|
};
|
|
617
607
|
node.styleCache = cache;
|
|
618
608
|
node.styleDirty = false;
|
|
@@ -713,16 +703,81 @@ export class StyleResolver {
|
|
|
713
703
|
add(sheet, COMPONENT_SPECIFICITY_BUMP);
|
|
714
704
|
return entries.sort((a, b) => a.weight - b.weight);
|
|
715
705
|
}
|
|
716
|
-
/**
|
|
717
|
-
|
|
706
|
+
/**
|
|
707
|
+
* A node's style from the rules it matched, shared with every other node that matched the same
|
|
708
|
+
* rules under the same inherited values and tokens: a list's rows, and what is inside each.
|
|
709
|
+
*
|
|
710
|
+
* Everything past matching depends on those alone, and it is most of the work where a sheet
|
|
711
|
+
* leaves values to the device: settling each `var()`, mixing colours, filling a shadow's slots.
|
|
712
|
+
* A node that sets custom properties of its own is worked out alone. What is shared is never
|
|
713
|
+
* written to after, so sharing it is safe; each node still has a cache and a context of its own.
|
|
714
|
+
*/
|
|
715
|
+
styled(node, matched, parentTokens, parentInherited) {
|
|
716
|
+
if (node.customProperties)
|
|
717
|
+
return this.style(node, matched, parentTokens, parentInherited);
|
|
718
|
+
if (this.sharedGeneration !== this.generation) {
|
|
719
|
+
this.shared.clear();
|
|
720
|
+
this.sharedGeneration = this.generation;
|
|
721
|
+
}
|
|
722
|
+
const key = matched.map((rule) => this.ruleId(rule)).join(',');
|
|
723
|
+
let byTokens = this.shared.get(key);
|
|
724
|
+
if (!byTokens)
|
|
725
|
+
this.shared.set(key, (byTokens = new WeakMap()));
|
|
726
|
+
let byInherited = byTokens.get(parentTokens);
|
|
727
|
+
if (!byInherited)
|
|
728
|
+
byTokens.set(parentTokens, (byInherited = new WeakMap()));
|
|
729
|
+
let styled = byInherited.get(parentInherited);
|
|
730
|
+
if (!styled) {
|
|
731
|
+
styled = this.style(node, matched, parentTokens, parentInherited);
|
|
732
|
+
byInherited.set(parentInherited, styled);
|
|
733
|
+
}
|
|
734
|
+
return styled;
|
|
735
|
+
}
|
|
736
|
+
shared = new Map();
|
|
737
|
+
sharedGeneration = 0;
|
|
738
|
+
ruleIds = new WeakMap();
|
|
739
|
+
nextRuleId = 0;
|
|
740
|
+
ruleId(rule) {
|
|
741
|
+
let id = this.ruleIds.get(rule);
|
|
742
|
+
if (id === undefined)
|
|
743
|
+
this.ruleIds.set(rule, (id = this.nextRuleId++));
|
|
744
|
+
return id;
|
|
745
|
+
}
|
|
746
|
+
/** A node's style from the rules it matched, worked out. */
|
|
747
|
+
style(node, matched, parentTokens, parentInherited) {
|
|
748
|
+
const result = this.cascade(matched);
|
|
749
|
+
// Tokens are in scope for this node's own declarations as well as its descendants', so they
|
|
750
|
+
// are merged before any `var()` here is resolved.
|
|
751
|
+
const tokens = tokensInScope(parentTokens, result.tokens, node.customProperties);
|
|
752
|
+
const own = result.declarations;
|
|
753
|
+
if (result.deferred) {
|
|
754
|
+
this.applyDeferred(result.deferred, own, tokens, parentInherited, result.important);
|
|
755
|
+
}
|
|
756
|
+
if (own['borderStyle'] === 'none')
|
|
757
|
+
drawNoBorder(own);
|
|
758
|
+
return {
|
|
759
|
+
style: { ...parentInherited, ...own },
|
|
760
|
+
inherited: inheritFrom(parentInherited, own),
|
|
761
|
+
tokens,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
/** The rules that apply to a node, weakest first. */
|
|
765
|
+
matched(node, entries) {
|
|
766
|
+
const out = [];
|
|
767
|
+
for (const { rule, sheet } of entries) {
|
|
768
|
+
if (this.conditionHolds(rule) && matches(node, rule, sheet))
|
|
769
|
+
out.push(rule);
|
|
770
|
+
}
|
|
771
|
+
return out;
|
|
772
|
+
}
|
|
773
|
+
/** The declarations of the rules a node matched. The list is sorted, so later simply wins. */
|
|
774
|
+
cascade(matched) {
|
|
718
775
|
const normal = {};
|
|
719
776
|
const important = {};
|
|
720
777
|
let hasImportant = false;
|
|
721
778
|
let tokens = null;
|
|
722
779
|
let deferred = null;
|
|
723
|
-
for (const
|
|
724
|
-
if (!this.conditionHolds(rule) || !matches(node, rule, sheet))
|
|
725
|
-
continue;
|
|
780
|
+
for (const rule of matched) {
|
|
726
781
|
Object.assign(normal, rule.declarations);
|
|
727
782
|
if (rule.important) {
|
|
728
783
|
Object.assign(important, rule.important);
|
|
@@ -917,7 +972,10 @@ export class StyleResolver {
|
|
|
917
972
|
const base = declaration.props.includes('fontSize')
|
|
918
973
|
? parentInherited['fontSize']
|
|
919
974
|
: (own['fontSize'] ?? parentInherited['fontSize']);
|
|
920
|
-
|
|
975
|
+
// To a thousandth of a point: a factor rounded at build time, times a font size, is otherwise
|
|
976
|
+
// 19.99998 where the web draws 20.
|
|
977
|
+
const points = factor * (typeof base === 'number' ? base : DEFAULT_FONT_SIZE) + offset;
|
|
978
|
+
return Math.round(points * 1000) / 1000;
|
|
921
979
|
}
|
|
922
980
|
}
|
|
923
981
|
/**
|
|
@@ -957,26 +1015,57 @@ function tokensInScope(parentTokens, ruleTokens, custom) {
|
|
|
957
1015
|
}
|
|
958
1016
|
function resolveAliases(own, merged) {
|
|
959
1017
|
const names = Object.keys(own);
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1018
|
+
const aliases = names.filter((name) => own[name].alias);
|
|
1019
|
+
followAliases(aliases, merged);
|
|
1020
|
+
// Until a pass settles nothing more: one made of another defined after it, or of one not yet
|
|
1021
|
+
// settled, cannot be worked out until that one is. What is left then is a cycle, or reads a
|
|
1022
|
+
// token that is not there, and is unset.
|
|
1023
|
+
const derivedNames = names.filter((name) => isDerived(own[name]));
|
|
1024
|
+
let pending = derivedNames;
|
|
1025
|
+
for (let settled = true; settled && pending.length;) {
|
|
1026
|
+
settled = false;
|
|
1027
|
+
pending = pending.filter((name) => {
|
|
1028
|
+
const value = derived(own[name], merged);
|
|
1029
|
+
if (!value)
|
|
1030
|
+
return true;
|
|
1031
|
+
merged[name] = value;
|
|
1032
|
+
settled = true;
|
|
1033
|
+
return false;
|
|
1034
|
+
});
|
|
970
1035
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1036
|
+
for (const name of pending)
|
|
1037
|
+
delete merged[name];
|
|
1038
|
+
// A chain that ends at one of those, by a link or a fallback, copied it unsettled above into
|
|
1039
|
+
// every alias on the way: follow them all again, from what they were defined as, now it is.
|
|
1040
|
+
if (derivedNames.length && aliases.length) {
|
|
1041
|
+
for (const name of aliases)
|
|
1042
|
+
merged[name] = own[name];
|
|
1043
|
+
followAliases(aliases, merged);
|
|
977
1044
|
}
|
|
978
1045
|
return merged;
|
|
979
1046
|
}
|
|
1047
|
+
/** Whether a token is made of others, and so worked out where it is defined. */
|
|
1048
|
+
function isDerived(token) {
|
|
1049
|
+
return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc);
|
|
1050
|
+
}
|
|
1051
|
+
/** A token made of others, worked out from the tokens in scope. */
|
|
1052
|
+
function derived(token, tokens) {
|
|
1053
|
+
if (token.hsl)
|
|
1054
|
+
return colourToken(resolveHsl(token.hsl, tokens));
|
|
1055
|
+
if (token.deferredColour)
|
|
1056
|
+
return colourToken(resolveColour(token.deferredColour, tokens));
|
|
1057
|
+
return calcToken(token.deferredCalc, tokens);
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Each alias settled to what it names. `follow` writes back every one that resolves; one in a
|
|
1061
|
+
* cycle is removed only once all are followed, as removed any sooner, the next one round the
|
|
1062
|
+
* cycle would find it unset and take its own fallback.
|
|
1063
|
+
*/
|
|
1064
|
+
function followAliases(aliases, merged) {
|
|
1065
|
+
const cyclic = aliases.filter((name) => typeof follow(name, merged, []) === 'number');
|
|
1066
|
+
for (const name of cyclic)
|
|
1067
|
+
delete merged[name];
|
|
1068
|
+
}
|
|
980
1069
|
/** A token set to what it resolved to, or removed when it resolved to nothing, as CSS unsets it. */
|
|
981
1070
|
function settle(merged, name, value) {
|
|
982
1071
|
if (value)
|
|
@@ -985,6 +1074,20 @@ function settle(merged, name, value) {
|
|
|
985
1074
|
delete merged[name];
|
|
986
1075
|
}
|
|
987
1076
|
const colourToken = (color) => color === undefined ? undefined : { color };
|
|
1077
|
+
/** A `calc()` token's forms, from the tokens in scope: undefined when it makes neither. */
|
|
1078
|
+
function calcToken(markers, tokens) {
|
|
1079
|
+
let token;
|
|
1080
|
+
for (const marker of markers) {
|
|
1081
|
+
const value = resolveCalc(marker, tokens);
|
|
1082
|
+
if (value === undefined)
|
|
1083
|
+
continue;
|
|
1084
|
+
if (marker.kind === 'length')
|
|
1085
|
+
token = { ...token, length: value };
|
|
1086
|
+
else if (typeof value === 'number')
|
|
1087
|
+
token = { ...token, number: value };
|
|
1088
|
+
}
|
|
1089
|
+
return token;
|
|
1090
|
+
}
|
|
988
1091
|
/** An `hsl()` token's channels, read from the tokens in scope, as the colour they make. */
|
|
989
1092
|
function resolveHsl(hsl, tokens) {
|
|
990
1093
|
const h = hslChannel(hsl.h, tokens);
|
|
@@ -1031,14 +1134,36 @@ function hslToRgb(h, s, l, alpha) {
|
|
|
1031
1134
|
? `rgb(${r}, ${g}, ${b})`
|
|
1032
1135
|
: `rgba(${r}, ${g}, ${b}, ${Math.round(alpha * 1000) / 1000})`;
|
|
1033
1136
|
}
|
|
1034
|
-
|
|
1137
|
+
/**
|
|
1138
|
+
* A token with its `var()` substituted, through as many aliases and fallbacks as it takes.
|
|
1139
|
+
*
|
|
1140
|
+
* `path` is the aliases being followed on the way here. A number is a cycle, and the index in
|
|
1141
|
+
* `path` where it starts: every property from there on is in it and invalid at computed-value
|
|
1142
|
+
* time, so none of them takes its fallback, while one before it names an invalid property and
|
|
1143
|
+
* does. Anything else is final wherever the chain started, so it is written back to `tokens`, and
|
|
1144
|
+
* no alias is followed twice.
|
|
1145
|
+
*/
|
|
1146
|
+
function follow(name, tokens, path) {
|
|
1035
1147
|
const value = tokens[name];
|
|
1036
1148
|
if (!value?.alias)
|
|
1037
1149
|
return value;
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1150
|
+
const start = path.indexOf(name);
|
|
1151
|
+
if (start >= 0)
|
|
1152
|
+
return start;
|
|
1153
|
+
const depth = path.push(name) - 1;
|
|
1154
|
+
const result = substituted(value, tokens, path, depth);
|
|
1155
|
+
path.pop();
|
|
1156
|
+
if (typeof result !== 'number')
|
|
1157
|
+
settle(tokens, name, result);
|
|
1158
|
+
return result;
|
|
1159
|
+
}
|
|
1160
|
+
/** An alias's target, or its fallback, which may be another `var()`, when the target is unset. */
|
|
1161
|
+
function substituted(value, tokens, path, depth) {
|
|
1162
|
+
const target = follow(value.alias, tokens, path);
|
|
1163
|
+
if (typeof target === 'object' || (typeof target === 'number' && target <= depth))
|
|
1164
|
+
return target;
|
|
1165
|
+
const { fallback } = value;
|
|
1166
|
+
return fallback?.alias ? substituted(fallback, tokens, path, depth) : fallback;
|
|
1042
1167
|
}
|
|
1043
1168
|
/** What a `var()` resolves to: a gradient's stops, or one value with its arithmetic applied. */
|
|
1044
1169
|
function referenced(declaration, tokens) {
|
|
@@ -1056,17 +1181,25 @@ function referenced(declaration, tokens) {
|
|
|
1056
1181
|
value = formOf(tokens[alternative], declaration.kind);
|
|
1057
1182
|
}
|
|
1058
1183
|
value ??= declaration.fallback;
|
|
1059
|
-
const base =
|
|
1184
|
+
const base = CHANNEL_KINDS.has(declaration.kind)
|
|
1185
|
+
? fromChannels(value, declaration.alpha, tokens, declaration.space)
|
|
1186
|
+
: value;
|
|
1060
1187
|
return declaration.adjust ? adjusted(base, declaration.adjust) : base;
|
|
1061
1188
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1189
|
+
const CHANNEL_KINDS = new Set(['channels', 'hslChannels']);
|
|
1190
|
+
/**
|
|
1191
|
+
* `13, 110, 253` as the colour it is, at the alpha the token beside it says, if one does: the
|
|
1192
|
+
* channels form for `rgb()`, the hslChannels form for `hsl()`.
|
|
1193
|
+
*/
|
|
1194
|
+
function fromChannels(value, alpha, tokens, space) {
|
|
1064
1195
|
if (!Array.isArray(value) || value.length !== 3)
|
|
1065
1196
|
return undefined;
|
|
1066
1197
|
// An alpha naming a token nothing set, with no fallback, is no colour: CSS drops it.
|
|
1067
1198
|
const opacity = alpha ? (tokens[alpha.reference]?.number ?? alpha.fallback) : 1;
|
|
1068
1199
|
if (opacity === undefined)
|
|
1069
1200
|
return undefined;
|
|
1201
|
+
if (space === 'hsl')
|
|
1202
|
+
return hslToRgb(value[0], value[1], value[2], opacity);
|
|
1070
1203
|
const [r, g, b] = value;
|
|
1071
1204
|
return opacity >= 1
|
|
1072
1205
|
? `rgb(${r}, ${g}, ${b})`
|
|
@@ -1236,6 +1369,10 @@ function calculated(expression, kind, tokens) {
|
|
|
1236
1369
|
return left - right;
|
|
1237
1370
|
if (op === '*')
|
|
1238
1371
|
return left * right;
|
|
1372
|
+
if (op === 'max')
|
|
1373
|
+
return Math.max(left, right);
|
|
1374
|
+
if (op === 'min')
|
|
1375
|
+
return Math.min(left, right);
|
|
1239
1376
|
return left / right;
|
|
1240
1377
|
}
|
|
1241
1378
|
/** Where each kind of slot reads a token from, before its bare number. */
|
|
@@ -1328,17 +1465,34 @@ function settledMarker(value, tokens) {
|
|
|
1328
1465
|
const sum = value.__calc;
|
|
1329
1466
|
if (sum)
|
|
1330
1467
|
return resolveCalc(sum, tokens);
|
|
1468
|
+
const shadow = value.__dropShadow;
|
|
1469
|
+
if (shadow)
|
|
1470
|
+
return dropShadowOf(tokens[shadow.reference]?.shadow);
|
|
1331
1471
|
return NOT_A_MARKER;
|
|
1332
1472
|
}
|
|
1473
|
+
/**
|
|
1474
|
+
* A shadow token as the drop shadow a filter draws: `drop-shadow(var(--drop-shadow-xs))`. One
|
|
1475
|
+
* shadow, never inset and with no spread, which is all a drop shadow can be; nothing otherwise.
|
|
1476
|
+
*/
|
|
1477
|
+
function dropShadowOf(shadow) {
|
|
1478
|
+
if (!Array.isArray(shadow) || shadow.length !== 1)
|
|
1479
|
+
return undefined;
|
|
1480
|
+
const [{ offsetX, offsetY, blurRadius, spreadDistance, color, inset }] = shadow;
|
|
1481
|
+
if (spreadDistance !== 0 || inset || typeof color !== 'string')
|
|
1482
|
+
return undefined;
|
|
1483
|
+
return { offsetX, offsetY, standardDeviation: blurRadius, color };
|
|
1484
|
+
}
|
|
1333
1485
|
/** What a colour that cannot be settled makes of the whole value it is in. */
|
|
1334
1486
|
const UNSETTLED = Symbol('unsettled');
|
|
1335
1487
|
/** `rgba(var(--channels), <alpha>)`: the token's channels, at the alpha written or tokened. */
|
|
1336
1488
|
function channelsColour(expression, tokens) {
|
|
1337
|
-
const
|
|
1489
|
+
const { space } = expression.channels;
|
|
1490
|
+
const kind = space === 'hsl' ? 'hslChannels' : 'channels';
|
|
1491
|
+
const value = formOf(tokens[expression.channels.reference], kind) ?? expression.channels.fallback;
|
|
1338
1492
|
const { alpha } = expression;
|
|
1339
1493
|
if (typeof alpha !== 'number')
|
|
1340
|
-
return fromChannels(value, alpha, tokens);
|
|
1341
|
-
const opaque = fromChannels(value, undefined, tokens);
|
|
1494
|
+
return fromChannels(value, alpha, tokens, space);
|
|
1495
|
+
const opaque = fromChannels(value, undefined, tokens, space);
|
|
1342
1496
|
return opaque === undefined ? undefined : faded(opaque, alpha);
|
|
1343
1497
|
}
|
|
1344
1498
|
/** A token's colour, or the first of its alternatives that is defined, or its fallback. */
|
|
@@ -1364,11 +1518,20 @@ function colourMarker(value) {
|
|
|
1364
1518
|
function adjusted(value, adjust) {
|
|
1365
1519
|
if (adjust.alpha !== undefined)
|
|
1366
1520
|
return faded(value, adjust.alpha);
|
|
1521
|
+
const em = value?.__defer;
|
|
1522
|
+
if (em && adjust.floor === undefined)
|
|
1523
|
+
return adjustedEm(em, adjust);
|
|
1367
1524
|
if (typeof value !== 'number')
|
|
1368
1525
|
return value;
|
|
1369
1526
|
const scaled = value * (adjust.scale ?? 1) + (adjust.offset ?? 0);
|
|
1370
1527
|
return adjust.floor === undefined ? scaled : Math.max(scaled, adjust.floor);
|
|
1371
1528
|
}
|
|
1529
|
+
/** A length in em, worked out against the font size later, with the arithmetic moved inside it. */
|
|
1530
|
+
function adjustedEm(em, adjust) {
|
|
1531
|
+
const scale = adjust.scale ?? 1;
|
|
1532
|
+
const offset = (em.offset ?? 0) * scale + (adjust.offset ?? 0);
|
|
1533
|
+
return { __defer: { ...em, factor: em.factor * scale, ...(offset ? { offset } : {}) } };
|
|
1534
|
+
}
|
|
1372
1535
|
/** `rgb(r, g, b)` and `rgba(r, g, b, a)`, which is every colour the compiler emits. */
|
|
1373
1536
|
const RGB = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/;
|
|
1374
1537
|
/**
|