@ng-native/fabric 0.2.0 → 0.3.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/color-mix.d.ts +12 -2
- package/dist/color-mix.js +16 -5
- package/dist/color-mix.js.map +1 -1
- package/dist/css.d.ts +87 -4
- package/dist/css.js +507 -66
- package/dist/css.js.map +1 -1
- package/dist/defer-triggers.js +3 -1
- package/dist/defer-triggers.js.map +1 -1
- package/dist/engine.d.ts +142 -14
- package/dist/engine.js +451 -60
- package/dist/engine.js.map +1 -1
- package/dist/host.d.ts +7 -0
- package/dist/host.js +7 -0
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inline-derived-token.d.ts +3 -12
- package/dist/inline-derived-token.js +241 -6
- package/dist/inline-derived-token.js.map +1 -1
- package/dist/inline-token.d.ts +5 -4
- package/dist/inline-token.js +28 -3
- package/dist/inline-token.js.map +1 -1
- package/dist/transition.d.ts +15 -4
- package/dist/transition.js +21 -3
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
package/dist/css.js
CHANGED
|
@@ -5,17 +5,26 @@
|
|
|
5
5
|
* specificity maths, sorting), so this only walks a pre-sorted rule list and merges. It is
|
|
6
6
|
* framework-agnostic and knows nothing about Angular.
|
|
7
7
|
*/
|
|
8
|
-
import { mixColours } from "./color-mix.js";
|
|
8
|
+
import { mixChannels, mixColours, } from "./color-mix.js";
|
|
9
9
|
import { relativeColour } from "./relative-colour.js";
|
|
10
|
+
/** A run of CSS whitespace: spaces, tabs, newlines, carriage returns and form feeds, and no other. */
|
|
11
|
+
export const CSS_SPACE = /[ \t\n\r\f]+/;
|
|
10
12
|
/**
|
|
11
13
|
* Where a form a token does not have can be read from instead. A length is already a
|
|
12
14
|
* line-height and a single word is already a family, so neither is stored twice in every token
|
|
13
15
|
* that happens to be one. The compiler reads fallbacks with the same table.
|
|
14
16
|
*/
|
|
15
|
-
const STAND_IN = {
|
|
17
|
+
const STAND_IN = {
|
|
18
|
+
lineHeight: 'length',
|
|
19
|
+
family: 'keyword',
|
|
20
|
+
};
|
|
16
21
|
function formOf(token, kind) {
|
|
17
22
|
if (!token)
|
|
18
23
|
return undefined;
|
|
24
|
+
// A display is a word, read as one native has where it is used: see `displayOf`. Two words,
|
|
25
|
+
// `inline flex`, are a family when the stylesheet wrote them, as a font stack's first name is.
|
|
26
|
+
if (kind === 'display')
|
|
27
|
+
return token.keyword ?? token.family;
|
|
19
28
|
const stand = STAND_IN[kind];
|
|
20
29
|
return token[kind] ?? (stand ? token[stand] : undefined);
|
|
21
30
|
}
|
|
@@ -538,6 +547,12 @@ export class StyleResolver {
|
|
|
538
547
|
* component created it, which is what resets, utility classes and `:root` tokens all need.
|
|
539
548
|
*/
|
|
540
549
|
globalSheet;
|
|
550
|
+
/**
|
|
551
|
+
* Global sheets added once the engine is running, in the order they came: a
|
|
552
|
+
* `ViewEncapsulation.None` component's, which a browser adds to the document unscoped when the
|
|
553
|
+
* component first renders, after the application's own. See `addGlobalSheet`.
|
|
554
|
+
*/
|
|
555
|
+
addedSheets = [];
|
|
541
556
|
/**
|
|
542
557
|
* Custom properties the host supplies rather than the stylesheet: the safe-area insets, and
|
|
543
558
|
* whatever else is a property of the device instead of the design.
|
|
@@ -556,10 +571,46 @@ export class StyleResolver {
|
|
|
556
571
|
* which is dropped. Set only in development, where it is how the engine says so.
|
|
557
572
|
*/
|
|
558
573
|
onUndefinedToken = null;
|
|
574
|
+
/**
|
|
575
|
+
* Told of a `display: var()` whose token holds no display native has, `grid` or `table`, which
|
|
576
|
+
* leaves display unset. Set only in development, where it is how the engine says so: the
|
|
577
|
+
* compiler refuses the same value written out, but a token is only known here.
|
|
578
|
+
*/
|
|
579
|
+
onUnreadDisplay = null;
|
|
559
580
|
constructor(globalSheet, conditions) {
|
|
560
581
|
this.globalSheet = globalSheet;
|
|
561
582
|
this.conditions = conditions;
|
|
562
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Match `sheet` against every node from now on, as a global sheet, after the application's;
|
|
586
|
+
* false when it already is. Every cached result goes, as on a theme switch: rules that were not
|
|
587
|
+
* there can now match anything. `:host` in it matches nothing, since no node hosts it.
|
|
588
|
+
*
|
|
589
|
+
* With `replacing`, a sheet added before, it takes that one's place, and so its order: a hot
|
|
590
|
+
* swap of the component's CSS edits the sheet where it is.
|
|
591
|
+
*/
|
|
592
|
+
addGlobalSheet(sheet, replacing) {
|
|
593
|
+
if (sheet === this.globalSheet || this.addedSheets.includes(sheet))
|
|
594
|
+
return false;
|
|
595
|
+
const at = replacing ? this.addedSheets.indexOf(replacing) : -1;
|
|
596
|
+
if (at === -1)
|
|
597
|
+
this.addedSheets.push(sheet);
|
|
598
|
+
else
|
|
599
|
+
this.addedSheets[at] = sheet;
|
|
600
|
+
this.merged = new WeakMap();
|
|
601
|
+
this.generation = ++generations;
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
/** Stop matching a sheet `addGlobalSheet` added; false when it is not one. */
|
|
605
|
+
removeGlobalSheet(sheet) {
|
|
606
|
+
const at = this.addedSheets.indexOf(sheet);
|
|
607
|
+
if (at === -1)
|
|
608
|
+
return false;
|
|
609
|
+
this.addedSheets.splice(at, 1);
|
|
610
|
+
this.merged = new WeakMap();
|
|
611
|
+
this.generation = ++generations;
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
563
614
|
setConditions(next) {
|
|
564
615
|
this.conditions = next;
|
|
565
616
|
this.generation = ++generations;
|
|
@@ -633,6 +684,7 @@ export class StyleResolver {
|
|
|
633
684
|
return (node.sheet === null &&
|
|
634
685
|
node.hostSheet === null &&
|
|
635
686
|
this.globalSheet === null &&
|
|
687
|
+
this.addedSheets.length === 0 &&
|
|
636
688
|
!node.customProperties);
|
|
637
689
|
}
|
|
638
690
|
/**
|
|
@@ -640,10 +692,16 @@ export class StyleResolver {
|
|
|
640
692
|
*
|
|
641
693
|
* It still mints a context, because a rule further down may match on one of *its* classes.
|
|
642
694
|
* When there is nothing to inherit either, one shared cache object serves every such node, so
|
|
643
|
-
* an application that writes no CSS allocates nothing per node.
|
|
695
|
+
* an application that writes no CSS allocates nothing per node. Not when there are tokens to
|
|
696
|
+
* carry: the shared object holds none, and the surface root answering with it dropped every
|
|
697
|
+
* token the device had seeded.
|
|
644
698
|
*/
|
|
645
699
|
unstyled(node, epoch, parent, parentContext, parentInherited) {
|
|
646
|
-
|
|
700
|
+
const tokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
701
|
+
if (parentInherited === EMPTY &&
|
|
702
|
+
parentContext === ROOT_CONTEXT &&
|
|
703
|
+
tokens === NO_TOKENS &&
|
|
704
|
+
!node.styleDirty) {
|
|
647
705
|
if (this.emptyCache.epoch !== epoch || this.emptyCache.generation !== this.generation) {
|
|
648
706
|
this.emptyCache = emptyCacheFor(epoch, this.generation);
|
|
649
707
|
}
|
|
@@ -657,7 +715,7 @@ export class StyleResolver {
|
|
|
657
715
|
parentContext,
|
|
658
716
|
style: parentInherited,
|
|
659
717
|
inherited: parentInherited,
|
|
660
|
-
tokens
|
|
718
|
+
tokens,
|
|
661
719
|
};
|
|
662
720
|
node.styleCache = passthrough;
|
|
663
721
|
node.styleDirty = false;
|
|
@@ -688,6 +746,10 @@ export class StyleResolver {
|
|
|
688
746
|
* created it. Both carry the bump; the creator comes later and so wins a tie, as the outer
|
|
689
747
|
* document does over `:host` on the web. The sort is stable, so within one sheet source order
|
|
690
748
|
* survives and a later sheet wins equal weight.
|
|
749
|
+
*
|
|
750
|
+
* A sheet added with `addGlobalSheet` comes last, with no bump: a browser adds a None
|
|
751
|
+
* component's CSS when the component first renders, after the styles of the components around
|
|
752
|
+
* it, so it loses to their extra attribute and wins a tie with them.
|
|
691
753
|
*/
|
|
692
754
|
merge(sheet, hostSheet) {
|
|
693
755
|
const entries = [];
|
|
@@ -701,6 +763,8 @@ export class StyleResolver {
|
|
|
701
763
|
add(this.globalSheet, 0);
|
|
702
764
|
add(hostSheet, COMPONENT_SPECIFICITY_BUMP);
|
|
703
765
|
add(sheet, COMPONENT_SPECIFICITY_BUMP);
|
|
766
|
+
for (const added of this.addedSheets)
|
|
767
|
+
add(added, 0);
|
|
704
768
|
return entries.sort((a, b) => a.weight - b.weight);
|
|
705
769
|
}
|
|
706
770
|
/**
|
|
@@ -862,13 +926,17 @@ export class StyleResolver {
|
|
|
862
926
|
if (settled === undefined && this.onUndefinedToken) {
|
|
863
927
|
this.reportUndefined(declaration, tokens);
|
|
864
928
|
}
|
|
929
|
+
const outranked = (prop) => !declaration.important && important !== null && prop in important;
|
|
930
|
+
// Said only of a display that is written: an important one wins over the token's.
|
|
931
|
+
const unread = settled === undefined && declaration.kind === 'display';
|
|
932
|
+
if (unread && this.onUnreadDisplay && !outranked('display')) {
|
|
933
|
+
this.reportDisplay(declaration, tokens);
|
|
934
|
+
}
|
|
865
935
|
const value = settled ?? declaration.unset;
|
|
866
|
-
if (value === undefined)
|
|
867
|
-
continue;
|
|
868
936
|
for (const prop of declaration.props) {
|
|
869
|
-
if (
|
|
937
|
+
if (outranked(prop))
|
|
870
938
|
continue;
|
|
871
|
-
own
|
|
939
|
+
write(own, prop, value, declaration.line !== undefined);
|
|
872
940
|
}
|
|
873
941
|
}
|
|
874
942
|
}
|
|
@@ -879,6 +947,8 @@ export class StyleResolver {
|
|
|
879
947
|
}
|
|
880
948
|
if (declaration.compute)
|
|
881
949
|
return this.computed(declaration, own, parentInherited);
|
|
950
|
+
if (declaration.line)
|
|
951
|
+
return this.settledLine(declaration, own, parentInherited, tokens);
|
|
882
952
|
const value = referenced(declaration, tokens);
|
|
883
953
|
if (declaration.whenSet !== undefined)
|
|
884
954
|
return value === undefined ? undefined : declaration.whenSet;
|
|
@@ -893,6 +963,27 @@ export class StyleResolver {
|
|
|
893
963
|
? this.settledWithin(value, declaration, own, parentInherited, tokens)
|
|
894
964
|
: value;
|
|
895
965
|
}
|
|
966
|
+
/** A line's longhands from its tokens, with a width in `em` worked out against the font size. */
|
|
967
|
+
settledLine(declaration, own, parentInherited, tokens) {
|
|
968
|
+
const line = declaration.line;
|
|
969
|
+
const values = lineValues(line, tokens);
|
|
970
|
+
if (!values)
|
|
971
|
+
return undefined;
|
|
972
|
+
// `currentcolor` is the colour in scope, which only the node knows.
|
|
973
|
+
if (values[line.colors[0]] === 'currentcolor') {
|
|
974
|
+
const colour = own['color'] ?? parentInherited['color'] ?? 'black';
|
|
975
|
+
for (const prop of line.colors)
|
|
976
|
+
values[prop] = colour;
|
|
977
|
+
}
|
|
978
|
+
const width = values[line.widths[0]];
|
|
979
|
+
if (!width?.__defer)
|
|
980
|
+
return values;
|
|
981
|
+
// No less than zero, as CSS clamps a line's width: `calc(var(--w) - 2px)` with an em token.
|
|
982
|
+
const points = this.computed({ ...declaration, compute: width.__defer }, own, parentInherited);
|
|
983
|
+
for (const prop of line.widths)
|
|
984
|
+
values[prop] = Math.max(points, 0);
|
|
985
|
+
return values;
|
|
986
|
+
}
|
|
896
987
|
/** A reference with nothing defined for it and nothing to fall back to. */
|
|
897
988
|
reportUndefined(declaration, tokens) {
|
|
898
989
|
const name = declaration.reference;
|
|
@@ -904,6 +995,13 @@ export class StyleResolver {
|
|
|
904
995
|
return;
|
|
905
996
|
this.onUndefinedToken(name, declaration.props);
|
|
906
997
|
}
|
|
998
|
+
/** A display token set to a value native has no display for. */
|
|
999
|
+
reportDisplay(declaration, tokens) {
|
|
1000
|
+
const names = [declaration.reference, ...(declaration.alternatives ?? [])];
|
|
1001
|
+
const name = names.find((candidate) => tokens[candidate] !== undefined);
|
|
1002
|
+
if (name)
|
|
1003
|
+
this.onUnreadDisplay(name, formOf(tokens[name], 'display'));
|
|
1004
|
+
}
|
|
907
1005
|
/**
|
|
908
1006
|
* A structured value with every deferred length and colour inside it settled, or undefined when
|
|
909
1007
|
* a colour in it cannot be: a shadow with no colour is not a shadow, as CSS reads it.
|
|
@@ -926,8 +1024,11 @@ export class StyleResolver {
|
|
|
926
1024
|
return this.filledList(value, fill, tokens);
|
|
927
1025
|
const marked = settledMarker(value, tokens);
|
|
928
1026
|
// `currentcolor` is the colour in scope, which only the node knows: Tailwind's ring default.
|
|
929
|
-
|
|
930
|
-
|
|
1027
|
+
// On `color` itself that is the inherited colour, as CSS reads it.
|
|
1028
|
+
if (marked === 'currentcolor') {
|
|
1029
|
+
const ownColour = declaration.props.includes('color') ? undefined : own['color'];
|
|
1030
|
+
return ownColour ?? parentInherited['color'] ?? 'black';
|
|
1031
|
+
}
|
|
931
1032
|
if (marked !== NOT_A_MARKER)
|
|
932
1033
|
return marked ?? UNSETTLED;
|
|
933
1034
|
const pending = value.__defer;
|
|
@@ -957,7 +1058,7 @@ export class StyleResolver {
|
|
|
957
1058
|
parts.push(filled);
|
|
958
1059
|
continue;
|
|
959
1060
|
}
|
|
960
|
-
const filled = fill(
|
|
1061
|
+
const filled = fill(substitute(slot.marker, tokens, (token) => slotEntries(token, slot.kind)));
|
|
961
1062
|
if (filled === UNSETTLED || !Array.isArray(filled))
|
|
962
1063
|
return UNSETTLED;
|
|
963
1064
|
parts.push(...filled);
|
|
@@ -1013,10 +1114,15 @@ function settlingOrder(deferred) {
|
|
|
1013
1114
|
*/
|
|
1014
1115
|
function tokensInScope(parentTokens, ruleTokens, custom) {
|
|
1015
1116
|
const own = custom ? { ...ruleTokens, ...custom } : ruleTokens;
|
|
1016
|
-
return own ? resolveAliases(own, { ...parentTokens, ...own }) : parentTokens;
|
|
1117
|
+
return own ? resolveAliases(own, { ...parentTokens, ...own }, parentTokens) : parentTokens;
|
|
1017
1118
|
}
|
|
1018
|
-
function resolveAliases(own, merged) {
|
|
1119
|
+
function resolveAliases(own, merged, parentTokens) {
|
|
1019
1120
|
const names = Object.keys(own);
|
|
1121
|
+
for (const name of names) {
|
|
1122
|
+
const word = own[name].keyword;
|
|
1123
|
+
if (word !== undefined && CSS_WIDE.has(word))
|
|
1124
|
+
cssWide(merged, name, word, parentTokens);
|
|
1125
|
+
}
|
|
1020
1126
|
const aliases = names.filter((name) => own[name].alias);
|
|
1021
1127
|
followAliases(aliases, merged);
|
|
1022
1128
|
// Left to work out: each token made of others and each alias that came to one, by a link or a
|
|
@@ -1026,6 +1132,25 @@ function resolveAliases(own, merged) {
|
|
|
1026
1132
|
settleDerived(own, merged, pending);
|
|
1027
1133
|
return merged;
|
|
1028
1134
|
}
|
|
1135
|
+
const CSS_WIDE = new Set([
|
|
1136
|
+
'initial',
|
|
1137
|
+
'inherit',
|
|
1138
|
+
'unset',
|
|
1139
|
+
'revert',
|
|
1140
|
+
'revert-layer',
|
|
1141
|
+
]);
|
|
1142
|
+
/**
|
|
1143
|
+
* A token set to a CSS-wide keyword, as CSS has one: its parent's value for `inherit` and `unset`,
|
|
1144
|
+
* since a custom property inherits, and otherwise its initial value, which is to be unset. Tailwind
|
|
1145
|
+
* writes `--tw-blur: initial` for each filter slot a node has not filled.
|
|
1146
|
+
*/
|
|
1147
|
+
function cssWide(merged, name, word, parentTokens) {
|
|
1148
|
+
const inherited = word === 'inherit' || word === 'unset' ? parentTokens[name] : undefined;
|
|
1149
|
+
if (inherited)
|
|
1150
|
+
merged[name] = inherited;
|
|
1151
|
+
else
|
|
1152
|
+
delete merged[name];
|
|
1153
|
+
}
|
|
1029
1154
|
/** What a token not yet worked out reads as while another is: set, with no form of any kind. */
|
|
1030
1155
|
const PENDING = Object.freeze({});
|
|
1031
1156
|
/**
|
|
@@ -1051,7 +1176,7 @@ function settleDerived(own, merged, pending) {
|
|
|
1051
1176
|
for (const name of pending) {
|
|
1052
1177
|
read = new Set();
|
|
1053
1178
|
const token = own[name];
|
|
1054
|
-
const value = token.alias ? substitutedIn(token, view) :
|
|
1179
|
+
const value = token.alias ? substitutedIn(token, view) : workedOut(token, view);
|
|
1055
1180
|
if (read.size) {
|
|
1056
1181
|
waitingOn.set(name, read);
|
|
1057
1182
|
continue;
|
|
@@ -1077,7 +1202,87 @@ function substitutedIn(token, tokens) {
|
|
|
1077
1202
|
return target;
|
|
1078
1203
|
link = link.fallback;
|
|
1079
1204
|
}
|
|
1080
|
-
return link && isDerived(link) ?
|
|
1205
|
+
return link && isDerived(link) ? workedOut(link, tokens) : link;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* A token that is set but makes no valid value. A use site finds it set, so takes no fallback, and
|
|
1209
|
+
* in no form, so its property is unset, as CSS substitutes such a token and then unsets it.
|
|
1210
|
+
*/
|
|
1211
|
+
const INVALID = Object.freeze({});
|
|
1212
|
+
/**
|
|
1213
|
+
* A token made of others, worked out: invalid when it makes nothing valid, as `hsl()` of a
|
|
1214
|
+
* percentage hue does, and unset only when a `var()` in it has nothing to be substituted with.
|
|
1215
|
+
*/
|
|
1216
|
+
function workedOut(token, tokens) {
|
|
1217
|
+
return derived(token, tokens) ?? (substitutable(token, tokens) ? INVALID : undefined);
|
|
1218
|
+
}
|
|
1219
|
+
/** Whether each `var()` in a token made of others names a token that is set, or has a fallback. */
|
|
1220
|
+
function substitutable(token, tokens) {
|
|
1221
|
+
if (token.hsl)
|
|
1222
|
+
return hslSubstitutable(token.hsl, tokens);
|
|
1223
|
+
if (token.deferredColour)
|
|
1224
|
+
return colourSubstitutable(token.deferredColour, tokens);
|
|
1225
|
+
// Each marker is one reading of the same text, and a fallback one cannot read is dropped from it.
|
|
1226
|
+
return token.deferredCalc.some((marker) => calcSubstitutable(marker.expression, tokens));
|
|
1227
|
+
}
|
|
1228
|
+
function hslSubstitutable(hsl, tokens) {
|
|
1229
|
+
return [hsl.h, hsl.s, hsl.l, hsl.alpha].every((channel) => typeof channel !== 'object' ||
|
|
1230
|
+
tokens[channel.reference] !== undefined ||
|
|
1231
|
+
channel.fallback !== undefined);
|
|
1232
|
+
}
|
|
1233
|
+
function calcSubstitutable(expression, tokens) {
|
|
1234
|
+
if (Array.isArray(expression)) {
|
|
1235
|
+
return expression.slice(1).every((side) => calcSubstitutable(side, tokens));
|
|
1236
|
+
}
|
|
1237
|
+
if (typeof expression !== 'object' || 'percentage' in expression)
|
|
1238
|
+
return true;
|
|
1239
|
+
const leaf = expression;
|
|
1240
|
+
if (tokens[leaf.reference] !== undefined)
|
|
1241
|
+
return true;
|
|
1242
|
+
return leaf.fallback !== undefined && calcSubstitutable(leaf.fallback, tokens);
|
|
1243
|
+
}
|
|
1244
|
+
function colourSubstitutable(expression, tokens) {
|
|
1245
|
+
if ('color' in expression)
|
|
1246
|
+
return true;
|
|
1247
|
+
if ('hsl' in expression)
|
|
1248
|
+
return hslSubstitutable(expression.hsl, tokens);
|
|
1249
|
+
if ('relative' in expression)
|
|
1250
|
+
return colourSubstitutable(expression.relative.from, tokens);
|
|
1251
|
+
if ('mix' in expression) {
|
|
1252
|
+
const { a, b } = expression.mix;
|
|
1253
|
+
return colourSubstitutable(a, tokens) && colourSubstitutable(b, tokens);
|
|
1254
|
+
}
|
|
1255
|
+
if ('channels' in expression) {
|
|
1256
|
+
const { channels, alpha } = expression;
|
|
1257
|
+
const set = (one) => tokens[one.reference] !== undefined || one.fallback !== undefined;
|
|
1258
|
+
return set(channels) && (typeof alpha !== 'object' || set(alpha));
|
|
1259
|
+
}
|
|
1260
|
+
return referenceSubstitutable(expression, tokens);
|
|
1261
|
+
}
|
|
1262
|
+
/** A colour's `var()`: a token or alternative that is set, or a fallback that can be worked out. */
|
|
1263
|
+
function referenceSubstitutable(expression, tokens) {
|
|
1264
|
+
const names = [expression.reference, ...(expression.alternatives ?? [])];
|
|
1265
|
+
if (names.some((name) => tokens[name] !== undefined))
|
|
1266
|
+
return true;
|
|
1267
|
+
if (expression.fallback !== undefined)
|
|
1268
|
+
return true;
|
|
1269
|
+
const token = expression.fallbackToken;
|
|
1270
|
+
return token !== undefined && (!isDerived(token) || substitutable(token, tokens));
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* A reference read from its token when that is set, in whatever form `read` finds, and its
|
|
1274
|
+
* fallback only when it is not.
|
|
1275
|
+
*/
|
|
1276
|
+
function substitute(marker, tokens, read) {
|
|
1277
|
+
const token = tokens[marker.reference];
|
|
1278
|
+
return token === undefined ? marker.fallback : read(token);
|
|
1279
|
+
}
|
|
1280
|
+
/** The first of the names that is set: the one a `var()` and its `var()` fallbacks substitute. */
|
|
1281
|
+
function firstSet(names, tokens) {
|
|
1282
|
+
for (const name of names)
|
|
1283
|
+
if (tokens[name] !== undefined)
|
|
1284
|
+
return tokens[name];
|
|
1285
|
+
return undefined;
|
|
1081
1286
|
}
|
|
1082
1287
|
/** The names among `names` that reach themselves by what each waits on. */
|
|
1083
1288
|
function inCycles(names, waitingOn) {
|
|
@@ -1134,36 +1339,78 @@ function calcToken(markers, tokens) {
|
|
|
1134
1339
|
token = { ...token, length: value };
|
|
1135
1340
|
else if (typeof value === 'number')
|
|
1136
1341
|
token = { ...token, number: value };
|
|
1342
|
+
// Arithmetic of a percentage, `calc(var(--half) * 2)`, is a percentage, as a token written
|
|
1343
|
+
// `100%` is, so an hsl() does not read it as a bare number of percent.
|
|
1344
|
+
const percentage = marker.kind === 'number' && calcType(marker.expression, tokens) === '%';
|
|
1345
|
+
if (percentage && typeof value === 'number' && token?.length === undefined) {
|
|
1346
|
+
token = { ...token, length: `${Math.round(value * 100000) / 1000}%` };
|
|
1347
|
+
}
|
|
1137
1348
|
}
|
|
1138
1349
|
return token;
|
|
1139
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* What a calc() of numbers and percentage tokens makes, as CSS types it: a number, `''`, a
|
|
1353
|
+
* percentage, `'%'`, or neither, as a percentage added to a number is, or one percentage
|
|
1354
|
+
* multiplied by another.
|
|
1355
|
+
*/
|
|
1356
|
+
function calcType(expression, tokens) {
|
|
1357
|
+
if (typeof expression === 'number')
|
|
1358
|
+
return '';
|
|
1359
|
+
if (!Array.isArray(expression))
|
|
1360
|
+
return leafType(expression, tokens);
|
|
1361
|
+
const [op, a, b] = expression;
|
|
1362
|
+
const left = calcType(a, tokens);
|
|
1363
|
+
const right = calcType(b, tokens);
|
|
1364
|
+
if (left === undefined || right === undefined)
|
|
1365
|
+
return undefined;
|
|
1366
|
+
if (op === '*')
|
|
1367
|
+
return left && right ? undefined : left || right;
|
|
1368
|
+
if (op === '/')
|
|
1369
|
+
return right ? undefined : left;
|
|
1370
|
+
return left === right ? left : undefined;
|
|
1371
|
+
}
|
|
1372
|
+
/** What one token in a calc() is, as `calcType` reads it: its fallback's type when it is unset. */
|
|
1373
|
+
function leafType(leaf, tokens) {
|
|
1374
|
+
if ('percentage' in leaf)
|
|
1375
|
+
return '%';
|
|
1376
|
+
const token = tokens[leaf.reference];
|
|
1377
|
+
if (!token)
|
|
1378
|
+
return leaf.fallback === undefined ? undefined : calcType(leaf.fallback, tokens);
|
|
1379
|
+
return typeof token.length === 'string' && token.length.endsWith('%') ? '%' : '';
|
|
1380
|
+
}
|
|
1140
1381
|
/** An `hsl()` token's channels, read from the tokens in scope, as the colour they make. */
|
|
1141
1382
|
function resolveHsl(hsl, tokens) {
|
|
1142
|
-
const h = hslChannel(hsl.h, tokens,
|
|
1143
|
-
const s = hslChannel(hsl.s, tokens);
|
|
1144
|
-
const l = hslChannel(hsl.l, tokens);
|
|
1145
|
-
const alpha = hsl.alpha === undefined ? 1 : hslChannel(hsl.alpha, tokens);
|
|
1383
|
+
const h = hslChannel(hsl.h, tokens, 'hue');
|
|
1384
|
+
const s = hslChannel(hsl.s, tokens, hsl.legacy ? 'legacy' : 'percentage');
|
|
1385
|
+
const l = hslChannel(hsl.l, tokens, hsl.legacy ? 'legacy' : 'percentage');
|
|
1386
|
+
const alpha = hsl.alpha === undefined ? 1 : hslChannel(hsl.alpha, tokens, 'alpha');
|
|
1146
1387
|
if (h === undefined || s === undefined || l === undefined || alpha === undefined) {
|
|
1147
1388
|
return undefined;
|
|
1148
1389
|
}
|
|
1149
1390
|
return hslToRgb(h, s, l, alpha);
|
|
1150
1391
|
}
|
|
1151
1392
|
/**
|
|
1152
|
-
* A channel's number, or undefined when its token is the wrong kind for the slot
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1393
|
+
* A channel's number, or undefined when its token is the wrong kind for the slot, as the token's
|
|
1394
|
+
* text substituted into the hsl() would read: a hue is a number or an angle, read in degrees, as
|
|
1395
|
+
* `0.5turn` is 180 and not 0.5, and never a percentage; a saturation or a lightness is a
|
|
1396
|
+
* percentage, or a bare number of percent, `--s: 100`, though not in the legacy comma syntax; an
|
|
1397
|
+
* alpha is a number or a percentage. The fallback is only for a token that is not set, as a set
|
|
1398
|
+
* one is substituted, whatever it holds.
|
|
1156
1399
|
*/
|
|
1157
|
-
function hslChannel(channel, tokens,
|
|
1400
|
+
function hslChannel(channel, tokens, slot) {
|
|
1158
1401
|
if (typeof channel === 'number')
|
|
1159
1402
|
return channel;
|
|
1160
1403
|
const token = tokens[channel.reference];
|
|
1161
1404
|
if (!token)
|
|
1162
1405
|
return channel.fallback;
|
|
1163
|
-
if (!hue)
|
|
1164
|
-
return token.angle === undefined ? token.number : undefined;
|
|
1165
1406
|
const percentage = typeof token.length === 'string' && token.length.endsWith('%');
|
|
1166
|
-
|
|
1407
|
+
if (slot === 'hue')
|
|
1408
|
+
return percentage ? undefined : (token.angle ?? token.number);
|
|
1409
|
+
if (token.angle !== undefined || token.number === undefined)
|
|
1410
|
+
return undefined;
|
|
1411
|
+
if (percentage || slot === 'alpha')
|
|
1412
|
+
return token.number;
|
|
1413
|
+
return slot === 'legacy' ? undefined : token.number / 100;
|
|
1167
1414
|
}
|
|
1168
1415
|
/**
|
|
1169
1416
|
* `hsl(h, s, l)` converted to sRGB, printed the same way `fromChannels` prints an rgba() built
|
|
@@ -1232,28 +1479,167 @@ function substituted(value, tokens, path, depth) {
|
|
|
1232
1479
|
function referenced(declaration, tokens) {
|
|
1233
1480
|
if (declaration.gradient)
|
|
1234
1481
|
return paintLayers(declaration.gradient, tokens);
|
|
1235
|
-
|
|
1482
|
+
// The first token set is substituted, whatever it holds: one of the wrong kind leaves the
|
|
1483
|
+
// property unset rather than trying the next, or the fallback.
|
|
1484
|
+
const names = [declaration.reference, ...(declaration.alternatives ?? [])];
|
|
1485
|
+
const token = firstSet(names, tokens);
|
|
1486
|
+
let value = token ? tokenForm(token, declaration.kind) : fallbackOf(declaration, tokens);
|
|
1236
1487
|
// `calc(var(--n) * 1px)`: the arithmetic gives a unitless token its unit, which is the usual
|
|
1237
|
-
// way to turn a count into a length. So
|
|
1238
|
-
if (
|
|
1239
|
-
value =
|
|
1488
|
+
// way to turn a count into a length. So it reads the bare number, and a length is no such thing.
|
|
1489
|
+
if (token && declaration.adjust?.number && declaration.kind === 'length') {
|
|
1490
|
+
value = token.number;
|
|
1240
1491
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
break;
|
|
1244
|
-
value = formOf(tokens[alternative], declaration.kind);
|
|
1245
|
-
}
|
|
1246
|
-
value ??= fallbackOf(declaration, tokens);
|
|
1492
|
+
if (declaration.kind === 'display')
|
|
1493
|
+
return displayOf(value);
|
|
1247
1494
|
const base = CHANNEL_KINDS.has(declaration.kind)
|
|
1248
1495
|
? fromChannels(value, declaration.alpha, tokens, declaration.space)
|
|
1249
1496
|
: value;
|
|
1250
1497
|
return declaration.adjust ? adjusted(base, declaration.adjust) : base;
|
|
1251
1498
|
}
|
|
1499
|
+
/**
|
|
1500
|
+
* What native displays for a keyword: flex, none or contents. A block, inline, inline-block,
|
|
1501
|
+
* flow-root or inline-flex box is flex, as the compiler reads one written out, and so is `flow`,
|
|
1502
|
+
* which is a block. Anything else, a grid or a table, is no display native has, so the property
|
|
1503
|
+
* is unset.
|
|
1504
|
+
*/
|
|
1505
|
+
const DISPLAYS = new Map([
|
|
1506
|
+
...['flex', 'block', 'inline', 'inline-block', 'flow-root', 'inline-flex', 'flow'].map((word) => [word, 'flex']),
|
|
1507
|
+
['none', 'none'],
|
|
1508
|
+
['contents', 'contents'],
|
|
1509
|
+
]);
|
|
1510
|
+
/**
|
|
1511
|
+
* The two-keyword form, an outer display and an inner one in either order: `inline flex` is
|
|
1512
|
+
* `inline-flex` and `block flow` is `block`, as Chrome computes them, so each is flex here.
|
|
1513
|
+
*/
|
|
1514
|
+
const OUTER = new Set(['block', 'inline']);
|
|
1515
|
+
const INNER = new Set(['flow', 'flow-root', 'flex']);
|
|
1516
|
+
function displayOf(value) {
|
|
1517
|
+
if (typeof value !== 'string')
|
|
1518
|
+
return undefined;
|
|
1519
|
+
const words = value.toLowerCase().split(CSS_SPACE);
|
|
1520
|
+
if (words.length === 1)
|
|
1521
|
+
return DISPLAYS.get(words[0]);
|
|
1522
|
+
const [first, second] = words;
|
|
1523
|
+
const pair = (OUTER.has(first) && INNER.has(second)) || (INNER.has(first) && OUTER.has(second));
|
|
1524
|
+
return words.length === 2 && pair ? 'flex' : undefined;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* A set token's value in the form wanted. A token of `currentColor` is the colour in scope where
|
|
1528
|
+
* it is used, not where it is set, so it is the marker the node fills in.
|
|
1529
|
+
*/
|
|
1530
|
+
function tokenForm(token, kind) {
|
|
1531
|
+
const value = formOf(token, kind);
|
|
1532
|
+
return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
|
|
1533
|
+
}
|
|
1252
1534
|
/** The fallback written, or one made of other tokens, worked out from the tokens where it is used. */
|
|
1253
1535
|
function fallbackOf(declaration, tokens) {
|
|
1254
1536
|
const token = declaration.fallbackToken;
|
|
1255
1537
|
return declaration.fallback ?? (token && formOf(derived(token, tokens), declaration.kind));
|
|
1256
1538
|
}
|
|
1539
|
+
/**
|
|
1540
|
+
* One longhand of a deferred declaration written to the node's own style. Unset when the value is
|
|
1541
|
+
* nothing, as CSS has a declaration invalid once its tokens are known: it still wins the cascade,
|
|
1542
|
+
* so what a weaker rule set goes, and the property inherits or starts over. A line's value is one
|
|
1543
|
+
* per longhand, and leaves the ones it was written with alone.
|
|
1544
|
+
*/
|
|
1545
|
+
function write(own, prop, value, perProp) {
|
|
1546
|
+
if (value === undefined)
|
|
1547
|
+
delete own[prop];
|
|
1548
|
+
else if (!perProp)
|
|
1549
|
+
own[prop] = value;
|
|
1550
|
+
else if (prop in value) {
|
|
1551
|
+
own[prop] = value[prop];
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
/** The styles a line's token can be, as the compiler reads a written one. */
|
|
1555
|
+
const LINE_STYLES = new Set([
|
|
1556
|
+
'solid',
|
|
1557
|
+
'dashed',
|
|
1558
|
+
'dotted',
|
|
1559
|
+
'double',
|
|
1560
|
+
'groove',
|
|
1561
|
+
'ridge',
|
|
1562
|
+
'inset',
|
|
1563
|
+
'none',
|
|
1564
|
+
'hidden',
|
|
1565
|
+
]);
|
|
1566
|
+
const LINE_WIDTHS = { thin: 1, medium: 3, thick: 5 };
|
|
1567
|
+
/** What a line's token substitutes to: a token, or nothing at all, `var(--x,)` or `--x: ;`. */
|
|
1568
|
+
const NOTHING = Symbol('nothing');
|
|
1569
|
+
/**
|
|
1570
|
+
* A line's longhands, each token given the one role its form says and the written parts did not
|
|
1571
|
+
* take. Undefined when a token is unset with nothing to fall back to, fits no role left, or would
|
|
1572
|
+
* take a role twice: CSS makes the whole shorthand invalid then.
|
|
1573
|
+
*/
|
|
1574
|
+
function lineValues(line, tokens) {
|
|
1575
|
+
const open = new Set(line.roles);
|
|
1576
|
+
const values = {};
|
|
1577
|
+
for (const reference of line.references) {
|
|
1578
|
+
const token = lineToken(reference, tokens);
|
|
1579
|
+
if (token === undefined)
|
|
1580
|
+
return undefined;
|
|
1581
|
+
if (token === NOTHING)
|
|
1582
|
+
continue;
|
|
1583
|
+
const found = reference.adjust
|
|
1584
|
+
? computedWidth(token, open, reference.adjust)
|
|
1585
|
+
: lineRole(token, open);
|
|
1586
|
+
if (!found)
|
|
1587
|
+
return undefined;
|
|
1588
|
+
open.delete(found.role);
|
|
1589
|
+
fillRole(line, values, found.role, found.value);
|
|
1590
|
+
}
|
|
1591
|
+
// A part no token filled takes its initial value, as one left out of a written line does.
|
|
1592
|
+
for (const role of open)
|
|
1593
|
+
fillRole(line, values, role, LINE_INITIAL[role]);
|
|
1594
|
+
return values;
|
|
1595
|
+
}
|
|
1596
|
+
/** A token of `currentColor`, in any case. */
|
|
1597
|
+
const isCurrentColour = (token) => token?.keyword?.toLowerCase() === 'currentcolor';
|
|
1598
|
+
/** The marker the compiler writes for `currentColor`, which the node fills in. */
|
|
1599
|
+
const CURRENT_COLOUR = { __colour: { color: 'currentcolor' } };
|
|
1600
|
+
/** What a line's width, style and colour are when it leaves them out. */
|
|
1601
|
+
const LINE_INITIAL = {
|
|
1602
|
+
width: LINE_WIDTHS['medium'],
|
|
1603
|
+
style: 'none',
|
|
1604
|
+
color: 'currentcolor',
|
|
1605
|
+
};
|
|
1606
|
+
function fillRole(line, values, role, value) {
|
|
1607
|
+
const props = { width: line.widths, color: line.colors, style: [line.style] }[role];
|
|
1608
|
+
for (const prop of props)
|
|
1609
|
+
values[prop] = value;
|
|
1610
|
+
}
|
|
1611
|
+
function lineToken(reference, tokens) {
|
|
1612
|
+
const set = firstSet([reference.reference, ...(reference.alternatives ?? [])], tokens);
|
|
1613
|
+
if (set)
|
|
1614
|
+
return set.keyword?.trim() === '' ? NOTHING : set;
|
|
1615
|
+
if (reference.empty)
|
|
1616
|
+
return NOTHING;
|
|
1617
|
+
const { fallback } = reference;
|
|
1618
|
+
return fallback && isDerived(fallback) ? derived(fallback, tokens) : fallback;
|
|
1619
|
+
}
|
|
1620
|
+
/** The role a token's form says it has, among those still open. */
|
|
1621
|
+
function lineRole(token, open) {
|
|
1622
|
+
const word = token.keyword;
|
|
1623
|
+
const width = word && word in LINE_WIDTHS ? LINE_WIDTHS[word] : token.length;
|
|
1624
|
+
if (open.has('width') && (typeof width === 'number' || isDeferredLength(width))) {
|
|
1625
|
+
return { role: 'width', value: width };
|
|
1626
|
+
}
|
|
1627
|
+
if (open.has('style') && word && LINE_STYLES.has(word))
|
|
1628
|
+
return { role: 'style', value: word };
|
|
1629
|
+
const color = isCurrentColour(token) ? 'currentcolor' : formOf(token, 'color');
|
|
1630
|
+
return open.has('color') && color !== undefined ? { role: 'color', value: color } : undefined;
|
|
1631
|
+
}
|
|
1632
|
+
/** A `calc()` of a token, which is a width, and no less than zero, as CSS clamps one. */
|
|
1633
|
+
function computedWidth(token, open, adjust) {
|
|
1634
|
+
// A bare number only when the calc() gives it its unit: `calc(var(--n) * 1px)`.
|
|
1635
|
+
const length = adjust.number ? token.number : token.length;
|
|
1636
|
+
if (!open.has('width') || (typeof length !== 'number' && !isDeferredLength(length))) {
|
|
1637
|
+
return undefined;
|
|
1638
|
+
}
|
|
1639
|
+
const value = adjusted(length, adjust);
|
|
1640
|
+
return { role: 'width', value: typeof value === 'number' ? Math.max(value, 0) : value };
|
|
1641
|
+
}
|
|
1642
|
+
const isDeferredLength = (value) => typeof value === 'object' && value !== null && '__defer' in value;
|
|
1257
1643
|
const CHANNEL_KINDS = new Set(['channels', 'hslChannels']);
|
|
1258
1644
|
/**
|
|
1259
1645
|
* `13, 110, 253` as the colour it is, at the alpha the token beside it says, if one does: the
|
|
@@ -1263,8 +1649,8 @@ function fromChannels(value, alpha, tokens, space) {
|
|
|
1263
1649
|
if (!Array.isArray(value) || value.length !== 3)
|
|
1264
1650
|
return undefined;
|
|
1265
1651
|
// An alpha naming a token nothing set, with no fallback, is no colour: CSS drops it.
|
|
1266
|
-
const opacity = alpha ? (
|
|
1267
|
-
if (opacity
|
|
1652
|
+
const opacity = alpha ? substitute(alpha, tokens, (token) => token.number) : 1;
|
|
1653
|
+
if (typeof opacity !== 'number')
|
|
1268
1654
|
return undefined;
|
|
1269
1655
|
if (space === 'hsl')
|
|
1270
1656
|
return hslToRgb(value[0], value[1], value[2], opacity);
|
|
@@ -1295,29 +1681,47 @@ function paintLayers(gradient, tokens) {
|
|
|
1295
1681
|
*/
|
|
1296
1682
|
function paintGradient(template, tokens) {
|
|
1297
1683
|
const colorStops = paintedStops(template, tokens);
|
|
1298
|
-
if (colorStops.length < 2)
|
|
1684
|
+
if (!colorStops || colorStops.length < 2)
|
|
1299
1685
|
return undefined;
|
|
1300
1686
|
const position = template.position && placed(template.position, tokens);
|
|
1301
1687
|
if (position === null)
|
|
1302
1688
|
return undefined;
|
|
1303
1689
|
return [{ ...template, colorStops, ...(position ? { position } : {}) }];
|
|
1304
1690
|
}
|
|
1305
|
-
/**
|
|
1691
|
+
/**
|
|
1692
|
+
* A template's stops with their tokens read, leaving out any whose colour is not set. Undefined
|
|
1693
|
+
* when a stop's colour or position token is set but no such thing, which makes the gradient
|
|
1694
|
+
* invalid, as CSS substitutes a set token rather than falling back.
|
|
1695
|
+
*/
|
|
1306
1696
|
function paintedStops(template, tokens) {
|
|
1307
1697
|
const colorStops = [];
|
|
1308
1698
|
for (const stop of template.colorStops) {
|
|
1309
|
-
const
|
|
1699
|
+
const token = stop.colour ? undefined : tokens[stop.reference];
|
|
1700
|
+
const color = stop.colour ? resolveColour(stop.colour, tokens) : token?.color;
|
|
1701
|
+
// `none` is how the Tailwind flattening writes `via-none`: the stop left out, not invalid.
|
|
1702
|
+
if (color === undefined && token && token.keyword !== 'none')
|
|
1703
|
+
return undefined;
|
|
1310
1704
|
if (color === undefined)
|
|
1311
1705
|
continue;
|
|
1312
|
-
const position = stop
|
|
1313
|
-
|
|
1314
|
-
|
|
1706
|
+
const position = stopPosition(stop, tokens);
|
|
1707
|
+
if (position === INVALID_POSITION)
|
|
1708
|
+
return undefined;
|
|
1315
1709
|
// Written even when there is none: Fabric looks the key up before it looks at the value, and
|
|
1316
1710
|
// silently skips a stop that has not got one.
|
|
1317
1711
|
colorStops.push({ color, position: position ?? null });
|
|
1318
1712
|
}
|
|
1319
1713
|
return colorStops;
|
|
1320
1714
|
}
|
|
1715
|
+
const INVALID_POSITION = Symbol('invalid position');
|
|
1716
|
+
/** A stop's position: its token's when that is set, the one written otherwise. */
|
|
1717
|
+
function stopPosition(stop, tokens) {
|
|
1718
|
+
if (!stop.positionReference)
|
|
1719
|
+
return stop.position;
|
|
1720
|
+
const token = tokens[stop.positionReference];
|
|
1721
|
+
if (!token)
|
|
1722
|
+
return stop.position;
|
|
1723
|
+
return token.length ?? INVALID_POSITION;
|
|
1724
|
+
}
|
|
1321
1725
|
/**
|
|
1322
1726
|
* A radial gradient's centre with any token in it read, or null when a token it needs is not set
|
|
1323
1727
|
* and has nothing written beside it: CSS would not paint the gradient then, and neither does this.
|
|
@@ -1329,8 +1733,8 @@ function placed(position, tokens) {
|
|
|
1329
1733
|
out[edge] = offset;
|
|
1330
1734
|
continue;
|
|
1331
1735
|
}
|
|
1332
|
-
const
|
|
1333
|
-
const value = tokens
|
|
1736
|
+
const marker = offset;
|
|
1737
|
+
const value = substitute(marker, tokens, (token) => token.length);
|
|
1334
1738
|
if (value === undefined || typeof value === 'object')
|
|
1335
1739
|
return null;
|
|
1336
1740
|
out[edge] = value;
|
|
@@ -1355,12 +1759,24 @@ function resolveColour(expression, tokens) {
|
|
|
1355
1759
|
const origin = resolveColour(from, tokens);
|
|
1356
1760
|
return origin === undefined ? undefined : relativeColour(space, origin, channels, alpha);
|
|
1357
1761
|
}
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1762
|
+
const sides = mixSides(expression.mix, tokens);
|
|
1763
|
+
return sides && mixColours(expression.mix.space, ...sides, expression.mix.hue);
|
|
1764
|
+
}
|
|
1765
|
+
/** A mix's two sides, a mix inside it unrounded, as CSS rounds only the colour at the end. */
|
|
1766
|
+
function mixSides(mix, tokens) {
|
|
1767
|
+
const colour = (side) => 'mix' in side ? unroundedMix(side.mix, tokens) : resolveColour(side, tokens);
|
|
1768
|
+
const first = colour(mix.a);
|
|
1769
|
+
const second = colour(mix.b);
|
|
1361
1770
|
if (first === undefined || second === undefined)
|
|
1362
1771
|
return undefined;
|
|
1363
|
-
return
|
|
1772
|
+
return [
|
|
1773
|
+
{ colour: first, percentage: mix.aPercentage },
|
|
1774
|
+
{ colour: second, percentage: mix.bPercentage },
|
|
1775
|
+
];
|
|
1776
|
+
}
|
|
1777
|
+
function unroundedMix(mix, tokens) {
|
|
1778
|
+
const sides = mixSides(mix, tokens);
|
|
1779
|
+
return sides && mixChannels(mix.space, ...sides, mix.hue);
|
|
1364
1780
|
}
|
|
1365
1781
|
/** A calc marker worked out, as its slot takes it; undefined when a token in it is not set. */
|
|
1366
1782
|
function resolveCalc(marker, tokens) {
|
|
@@ -1369,6 +1785,9 @@ function resolveCalc(marker, tokens) {
|
|
|
1369
1785
|
return percentage;
|
|
1370
1786
|
if (bareNumberAsLength(marker, tokens) || notAnAngle(marker, tokens))
|
|
1371
1787
|
return undefined;
|
|
1788
|
+
// A percentage and a number added together, `calc(var(--n) + 10%)`, are neither, and invalid.
|
|
1789
|
+
if (marker.kind === 'number' && calcType(marker.expression, tokens) === undefined)
|
|
1790
|
+
return undefined;
|
|
1372
1791
|
const value = calculated(marker.expression, marker.kind, tokens);
|
|
1373
1792
|
if (value === undefined || !Number.isFinite(value))
|
|
1374
1793
|
return undefined;
|
|
@@ -1422,11 +1841,8 @@ function wholePercentage(marker, tokens) {
|
|
|
1422
1841
|
function calculated(expression, kind, tokens) {
|
|
1423
1842
|
if (typeof expression === 'number')
|
|
1424
1843
|
return expression;
|
|
1425
|
-
if (!Array.isArray(expression))
|
|
1426
|
-
|
|
1427
|
-
const own = tokenNumber(tokens[leaf.reference], kind);
|
|
1428
|
-
return (own ?? (leaf.fallback === undefined ? undefined : calculated(leaf.fallback, kind, tokens)));
|
|
1429
|
-
}
|
|
1844
|
+
if (!Array.isArray(expression))
|
|
1845
|
+
return leafValue(expression, kind, tokens);
|
|
1430
1846
|
const [op, a, b] = expression;
|
|
1431
1847
|
const left = calculated(a, kind, tokens);
|
|
1432
1848
|
const right = calculated(b, kind, tokens);
|
|
@@ -1444,6 +1860,15 @@ function calculated(expression, kind, tokens) {
|
|
|
1444
1860
|
return Math.min(left, right);
|
|
1445
1861
|
return left / right;
|
|
1446
1862
|
}
|
|
1863
|
+
/** A written percentage, or a token, as `calculated` reads it: its fallback when it is unset. */
|
|
1864
|
+
function leafValue(leaf, kind, tokens) {
|
|
1865
|
+
if ('percentage' in leaf)
|
|
1866
|
+
return leaf.percentage;
|
|
1867
|
+
const token = tokens[leaf.reference];
|
|
1868
|
+
if (token)
|
|
1869
|
+
return tokenNumber(token, kind);
|
|
1870
|
+
return leaf.fallback === undefined ? undefined : calculated(leaf.fallback, kind, tokens);
|
|
1871
|
+
}
|
|
1447
1872
|
/** Where each kind of slot reads a token from, before its bare number. */
|
|
1448
1873
|
const FORM_OF_KIND = {
|
|
1449
1874
|
length: (token) => token.length,
|
|
@@ -1468,6 +1893,10 @@ function tokenNumber(token, kind) {
|
|
|
1468
1893
|
return undefined;
|
|
1469
1894
|
return token.number;
|
|
1470
1895
|
}
|
|
1896
|
+
/** What a token set in a slot stands for: its entries, or none for one set to nothing, `--tw-blur: ;`. */
|
|
1897
|
+
function slotEntries(token, kind) {
|
|
1898
|
+
return formOf(token, kind) ?? (token.keyword?.trim() === '' ? [] : undefined);
|
|
1899
|
+
}
|
|
1471
1900
|
/** The slot a list entry can be instead of an entry: one per list property that has them. */
|
|
1472
1901
|
const SLOT_KINDS = {
|
|
1473
1902
|
__shadows: 'shadow',
|
|
@@ -1488,7 +1917,9 @@ function slotOf(part) {
|
|
|
1488
1917
|
return undefined;
|
|
1489
1918
|
}
|
|
1490
1919
|
function resolveLength(marker, tokens) {
|
|
1491
|
-
|
|
1920
|
+
// A number when the arithmetic gives it its unit, as `referenced` reads one.
|
|
1921
|
+
const form = marker.adjust?.number ? 'number' : 'length';
|
|
1922
|
+
const value = substitute(marker, tokens, (token) => formOf(token, form));
|
|
1492
1923
|
if (typeof value !== 'number')
|
|
1493
1924
|
return undefined;
|
|
1494
1925
|
return marker.adjust ? adjusted(value, marker.adjust) : value;
|
|
@@ -1557,7 +1988,7 @@ const UNSETTLED = Symbol('unsettled');
|
|
|
1557
1988
|
function channelsColour(expression, tokens) {
|
|
1558
1989
|
const { space } = expression.channels;
|
|
1559
1990
|
const kind = space === 'hsl' ? 'hslChannels' : 'channels';
|
|
1560
|
-
const value =
|
|
1991
|
+
const value = substitute(expression.channels, tokens, (token) => formOf(token, kind));
|
|
1561
1992
|
const { alpha } = expression;
|
|
1562
1993
|
if (typeof alpha !== 'number')
|
|
1563
1994
|
return fromChannels(value, alpha, tokens, space);
|
|
@@ -1566,13 +1997,23 @@ function channelsColour(expression, tokens) {
|
|
|
1566
1997
|
}
|
|
1567
1998
|
/** A token's colour, or the first of its alternatives that is defined, or its fallback. */
|
|
1568
1999
|
function tokenColour(expression, tokens) {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
2000
|
+
// The first token set is substituted, whatever it holds, as `referenced` reads one.
|
|
2001
|
+
const set = firstSet([expression.reference, ...(expression.alternatives ?? [])], tokens);
|
|
2002
|
+
if (expression.orInset)
|
|
2003
|
+
return insetOrColour(set);
|
|
2004
|
+
if (set)
|
|
2005
|
+
return formOf(set, 'color');
|
|
2006
|
+
const token = expression.fallbackToken;
|
|
2007
|
+
return expression.fallback ?? (token && formOf(derived(token, tokens), 'color'));
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* A shadow's colour from a token that is the colour or the word `inset`: currentcolor, which the
|
|
2011
|
+
* node fills in, when it is `inset` or unset, and nothing, so no shadow, when it is anything else.
|
|
2012
|
+
*/
|
|
2013
|
+
function insetOrColour(token) {
|
|
2014
|
+
if (!token || token.keyword === 'inset')
|
|
2015
|
+
return 'currentcolor';
|
|
2016
|
+
return formOf(token, 'color');
|
|
1576
2017
|
}
|
|
1577
2018
|
/** A marker the compiler leaves in a structured value for a colour only the device can settle. */
|
|
1578
2019
|
function colourMarker(value) {
|