@ng-native/fabric 0.7.0 → 0.8.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/background-layers.d.ts +18 -0
- package/dist/background-layers.js +70 -0
- package/dist/background-layers.js.map +1 -0
- package/dist/css.d.ts +16 -0
- package/dist/css.js +135 -22
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +79 -1
- package/dist/engine.js +425 -20
- package/dist/engine.js.map +1 -1
- package/dist/fabric.d.ts +1 -1
- package/dist/fabric.js +1 -0
- package/dist/fabric.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/native-drive.js +9 -0
- package/dist/native-drive.js.map +1 -1
- package/dist/scroll-animation.d.ts +11 -0
- package/dist/scroll-animation.js +30 -1
- package/dist/scroll-animation.js.map +1 -1
- package/dist/transition.d.ts +23 -0
- package/dist/transition.js +32 -9
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `background` a token holds as text, where it is no single colour: the gradients a library
|
|
3
|
+
* works out as the app runs and sets as a custom property, a colour slider's track or a colour
|
|
4
|
+
* area's two fades over a colour. A stylesheet's gradient is compiled when the app is built; one
|
|
5
|
+
* set on an element is read here, into what a view's `experimental_backgroundImage` takes.
|
|
6
|
+
*
|
|
7
|
+
* ponytail: `linear-gradient()` with a side or an angle and colour stops at percentages, which
|
|
8
|
+
* is what such a library writes. A corner, a length or two positions on a stop, `radial-` and
|
|
9
|
+
* `repeating-` are not read, and the whole background is then nothing, as an invalid one is.
|
|
10
|
+
* Native has no conic gradient at all.
|
|
11
|
+
*/
|
|
12
|
+
export interface BackgroundLayers {
|
|
13
|
+
readonly images: readonly object[];
|
|
14
|
+
/** The colour under the images: the last layer, where it is one. */
|
|
15
|
+
readonly color?: string;
|
|
16
|
+
}
|
|
17
|
+
/** The layers a background's text holds, or nothing where any of it is not read. */
|
|
18
|
+
export declare function backgroundLayers(text: string): BackgroundLayers | undefined;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { parseColor } from "./transition.js";
|
|
2
|
+
const SIDES = {
|
|
3
|
+
'to top': 0,
|
|
4
|
+
'to right': 90,
|
|
5
|
+
'to bottom': 180,
|
|
6
|
+
'to left': 270,
|
|
7
|
+
};
|
|
8
|
+
/** Split at the commas that are not inside brackets. */
|
|
9
|
+
function split(text) {
|
|
10
|
+
const parts = [];
|
|
11
|
+
let depth = 0;
|
|
12
|
+
let from = 0;
|
|
13
|
+
for (let at = 0; at < text.length; at++) {
|
|
14
|
+
const char = text[at];
|
|
15
|
+
if (char === '(')
|
|
16
|
+
depth++;
|
|
17
|
+
else if (char === ')')
|
|
18
|
+
depth--;
|
|
19
|
+
else if (char === ',' && depth === 0) {
|
|
20
|
+
parts.push(text.slice(from, at).trim());
|
|
21
|
+
from = at + 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
parts.push(text.slice(from).trim());
|
|
25
|
+
return parts;
|
|
26
|
+
}
|
|
27
|
+
const rgba = (text) => {
|
|
28
|
+
const parsed = parseColor(text);
|
|
29
|
+
return parsed ? `rgba(${parsed[0]}, ${parsed[1]}, ${parsed[2]}, ${parsed[3]})` : undefined;
|
|
30
|
+
};
|
|
31
|
+
/** A direction in degrees, or nothing for a first argument that is a colour stop. */
|
|
32
|
+
function direction(first) {
|
|
33
|
+
const side = SIDES[first.toLowerCase()];
|
|
34
|
+
if (side !== undefined)
|
|
35
|
+
return side;
|
|
36
|
+
const angle = /^(-?[\d.]+)deg$/i.exec(first);
|
|
37
|
+
return angle ? Number(angle[1]) : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** `rgba(…) 40%` as a stop, or nothing for one this does not read. */
|
|
40
|
+
function stop(text) {
|
|
41
|
+
const placed = /^(.*\S)\s+(-?[\d.]+%)$/.exec(text);
|
|
42
|
+
const color = rgba(placed ? placed[1] : text);
|
|
43
|
+
return color === undefined ? undefined : { color, position: placed ? placed[2] : null };
|
|
44
|
+
}
|
|
45
|
+
function linear(layer) {
|
|
46
|
+
const inside = /^linear-gradient\((.*)\)$/is.exec(layer)?.[1];
|
|
47
|
+
if (inside === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
const parts = split(inside);
|
|
50
|
+
const angle = direction(parts[0]);
|
|
51
|
+
const stops = (angle === undefined ? parts : parts.slice(1)).map(stop);
|
|
52
|
+
if (stops.length < 2 || stops.some((each) => each === undefined))
|
|
53
|
+
return undefined;
|
|
54
|
+
return {
|
|
55
|
+
type: 'linear-gradient',
|
|
56
|
+
direction: { type: 'angle', value: angle ?? 180 },
|
|
57
|
+
colorStops: stops,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** The layers a background's text holds, or nothing where any of it is not read. */
|
|
61
|
+
export function backgroundLayers(text) {
|
|
62
|
+
const layers = split(text.trim());
|
|
63
|
+
const last = layers.at(-1);
|
|
64
|
+
const color = parseColor(last) ? last : undefined;
|
|
65
|
+
const images = (color === undefined ? layers : layers.slice(0, -1)).map(linear);
|
|
66
|
+
if (!images.length || images.some((each) => each === undefined))
|
|
67
|
+
return undefined;
|
|
68
|
+
return { images: images, color };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=background-layers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-layers.js","sourceRoot":"","sources":["../src/background-layers.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseColor } from './transition.ts';\n\n/**\n * A `background` a token holds as text, where it is no single colour: the gradients a library\n * works out as the app runs and sets as a custom property, a colour slider's track or a colour\n * area's two fades over a colour. A stylesheet's gradient is compiled when the app is built; one\n * set on an element is read here, into what a view's `experimental_backgroundImage` takes.\n *\n * ponytail: `linear-gradient()` with a side or an angle and colour stops at percentages, which\n * is what such a library writes. A corner, a length or two positions on a stop, `radial-` and\n * `repeating-` are not read, and the whole background is then nothing, as an invalid one is.\n * Native has no conic gradient at all.\n */\nexport interface BackgroundLayers {\n readonly images: readonly object[];\n /** The colour under the images: the last layer, where it is one. */\n readonly color?: string;\n}\n\nconst SIDES: Readonly<Record<string, number>> = {\n 'to top': 0,\n 'to right': 90,\n 'to bottom': 180,\n 'to left': 270,\n};\n\n/** Split at the commas that are not inside brackets. */\nfunction split(text: string): string[] {\n const parts: string[] = [];\n let depth = 0;\n let from = 0;\n for (let at = 0; at < text.length; at++) {\n const char = text[at];\n if (char === '(') depth++;\n else if (char === ')') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(text.slice(from, at).trim());\n from = at + 1;\n }\n }\n parts.push(text.slice(from).trim());\n return parts;\n}\n\nconst rgba = (text: string): string | undefined => {\n const parsed = parseColor(text);\n return parsed ? `rgba(${parsed[0]}, ${parsed[1]}, ${parsed[2]}, ${parsed[3]})` : undefined;\n};\n\n/** A direction in degrees, or nothing for a first argument that is a colour stop. */\nfunction direction(first: string): number | undefined {\n const side = SIDES[first.toLowerCase()];\n if (side !== undefined) return side;\n const angle = /^(-?[\\d.]+)deg$/i.exec(first);\n return angle ? Number(angle[1]) : undefined;\n}\n\n/** `rgba(…) 40%` as a stop, or nothing for one this does not read. */\nfunction stop(text: string): { color: string; position: string | null } | undefined {\n const placed = /^(.*\\S)\\s+(-?[\\d.]+%)$/.exec(text);\n const color = rgba(placed ? placed[1]! : text);\n return color === undefined ? undefined : { color, position: placed ? placed[2]! : null };\n}\n\nfunction linear(layer: string): object | undefined {\n const inside = /^linear-gradient\\((.*)\\)$/is.exec(layer)?.[1];\n if (inside === undefined) return undefined;\n const parts = split(inside);\n const angle = direction(parts[0]!);\n const stops = (angle === undefined ? parts : parts.slice(1)).map(stop);\n if (stops.length < 2 || stops.some((each) => each === undefined)) return undefined;\n return {\n type: 'linear-gradient',\n direction: { type: 'angle', value: angle ?? 180 },\n colorStops: stops,\n };\n}\n\n/** The layers a background's text holds, or nothing where any of it is not read. */\nexport function backgroundLayers(text: string): BackgroundLayers | undefined {\n const layers = split(text.trim());\n const last = layers.at(-1)!;\n const color = parseColor(last) ? last : undefined;\n const images = (color === undefined ? layers : layers.slice(0, -1)).map(linear);\n if (!images.length || images.some((each) => each === undefined)) return undefined;\n return { images: images as object[], color };\n}\n"]}
|
package/dist/css.d.ts
CHANGED
|
@@ -222,6 +222,11 @@ export interface DeferredDeclaration {
|
|
|
222
222
|
* unambiguous given a single-valued token, so `padding: var(--gap)` writes all four.
|
|
223
223
|
*/
|
|
224
224
|
readonly props: readonly string[];
|
|
225
|
+
/**
|
|
226
|
+
* `background: var()`: the token may hold gradients over a colour, so `props` names the image
|
|
227
|
+
* beside the colour and each is written from its own part of the token. See `layersOf`.
|
|
228
|
+
*/
|
|
229
|
+
readonly layers?: true;
|
|
225
230
|
/**
|
|
226
231
|
* `inherit`, written for these props: each is the parent's value for it, or nothing where the
|
|
227
232
|
* parent has none.
|
|
@@ -698,6 +703,10 @@ interface IndexedEntry {
|
|
|
698
703
|
export interface RuleIndex {
|
|
699
704
|
readonly buckets: Map<string, IndexedEntry[]>;
|
|
700
705
|
readonly universal: IndexedEntry[];
|
|
706
|
+
/** Whether any rule is filed under an attribute, which a node's props are then looked up for. */
|
|
707
|
+
attributes?: boolean;
|
|
708
|
+
/** Whether any is filed under the class of an element its own is inside. */
|
|
709
|
+
under?: boolean;
|
|
701
710
|
}
|
|
702
711
|
/**
|
|
703
712
|
* File rules by key selector, each at `from` and on: its place among every rule there is, which
|
|
@@ -848,6 +857,13 @@ export declare class StyleResolver {
|
|
|
848
857
|
private readonly noted;
|
|
849
858
|
/** Read the classes a sheet names, once, as soon as any node is given it. */
|
|
850
859
|
noteSheet(sheet: StyleSheet | null | undefined): void;
|
|
860
|
+
/** The props a selector of some sheet asks about: an id, and each attribute by its name. */
|
|
861
|
+
private readonly named;
|
|
862
|
+
/**
|
|
863
|
+
* Whether a prop changing can change what any element matches: only where a selector of a
|
|
864
|
+
* sheet that was ever loaded names it. A sheet loaded later has every element matched again.
|
|
865
|
+
*/
|
|
866
|
+
reads(prop: string): boolean;
|
|
851
867
|
private noteReach;
|
|
852
868
|
/**
|
|
853
869
|
* What classes coming to an element or going from it can restyle: `true` for it and all
|
package/dist/css.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* framework-agnostic and knows nothing about Angular.
|
|
7
7
|
*/
|
|
8
8
|
import { mixChannels, mixColours, } from "./color-mix.js";
|
|
9
|
+
import { backgroundLayers } from "./background-layers.js";
|
|
9
10
|
import { ELEMENT_STYLES } from "./element-styles.js";
|
|
10
11
|
import { relativeColour } from "./relative-colour.js";
|
|
11
12
|
/** A run of CSS whitespace: spaces, tabs, newlines, carriage returns and form feeds, and no other. */
|
|
@@ -229,22 +230,30 @@ function matchesAttribute(node, test) {
|
|
|
229
230
|
return false;
|
|
230
231
|
if (test.operator === undefined)
|
|
231
232
|
return true;
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
// `^=`, `$=`, `*=` and `~=` with an empty value match nothing, as Selectors 4 says.
|
|
234
|
+
if (test.value === '' && !EMPTY_MATCHES.has(test.operator))
|
|
235
|
+
return false;
|
|
236
|
+
return matchesValue(attributeText(actual, test), test.operator, test.value);
|
|
237
|
+
}
|
|
238
|
+
/** The operators an empty value can still match by: `=` and `|=`. */
|
|
239
|
+
const EMPTY_MATCHES = new Set(['equal', 'dash-match']);
|
|
240
|
+
/** Whether an attribute's value, as `attributeText` gives it, passes an operator's test. */
|
|
241
|
+
function matchesValue(value, operator, expected) {
|
|
242
|
+
switch (operator) {
|
|
234
243
|
case 'equal':
|
|
235
|
-
return value ===
|
|
244
|
+
return value === expected;
|
|
236
245
|
case 'prefix':
|
|
237
|
-
return value.startsWith(
|
|
246
|
+
return value.startsWith(expected);
|
|
238
247
|
case 'suffix':
|
|
239
|
-
return value.endsWith(
|
|
248
|
+
return value.endsWith(expected);
|
|
240
249
|
case 'substring':
|
|
241
|
-
return value.includes(
|
|
250
|
+
return value.includes(expected);
|
|
242
251
|
case 'includes':
|
|
243
252
|
// `~=` matches one whole word of a space-separated list.
|
|
244
|
-
return value.split(/\s+/).includes(
|
|
253
|
+
return value.split(/\s+/).includes(expected);
|
|
245
254
|
case 'dash-match':
|
|
246
255
|
// `|=` matches the value itself or the value followed by a hyphen, as `lang` uses.
|
|
247
|
-
return value ===
|
|
256
|
+
return value === expected || value.startsWith(`${expected}-`);
|
|
248
257
|
}
|
|
249
258
|
}
|
|
250
259
|
/*
|
|
@@ -905,7 +914,34 @@ export function ruleKeys(rule) {
|
|
|
905
914
|
const held = parentClass(rule, key);
|
|
906
915
|
if (held !== undefined)
|
|
907
916
|
return [`in:${held}`];
|
|
908
|
-
return alternativeKeys(key) ?? EVERY_NODE;
|
|
917
|
+
return alternativeKeys(key) ?? attributeKey(key) ?? ancestorKeys(rule, key) ?? EVERY_NODE;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* The buckets of a rule for anything inside an element with a class, `.item:focus *`: under the
|
|
921
|
+
* class, or each class the element may have instead. Offered to what has such an element
|
|
922
|
+
* somewhere over it, where the universal bucket has every element walk to the root to find none.
|
|
923
|
+
*/
|
|
924
|
+
function ancestorKeys(rule, key) {
|
|
925
|
+
const joined = rule.combinators[rule.combinators.length - 1];
|
|
926
|
+
const above = key.ancestors?.[0] ??
|
|
927
|
+
(joined === 'descendant' ? rule.compounds[rule.compounds.length - 2] : undefined);
|
|
928
|
+
if (!above)
|
|
929
|
+
return undefined;
|
|
930
|
+
if (above.classes.length)
|
|
931
|
+
return [`under:${above.classes[0]}`];
|
|
932
|
+
const any = above.is?.[0];
|
|
933
|
+
if (!any?.length || any.some((one) => !one.classes.length))
|
|
934
|
+
return undefined;
|
|
935
|
+
return any.map((one) => `under:${one.classes[0]}`);
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* The bucket of a rule that names its element by an attribute and nothing else, `[ngpButton]`:
|
|
939
|
+
* a prop the element has to have, which is how a headless library names every element it
|
|
940
|
+
* styles. Not `class`, which is no prop of the element's to find the bucket by.
|
|
941
|
+
*/
|
|
942
|
+
function attributeKey(key) {
|
|
943
|
+
const named = key.attributes?.find((test) => test.name !== 'class');
|
|
944
|
+
return named ? [`attr:${named.name}`] : undefined;
|
|
909
945
|
}
|
|
910
946
|
/** A class the node's parent has to have for the rule to match, where its selector says one. */
|
|
911
947
|
function parentClass(rule, key) {
|
|
@@ -958,6 +994,10 @@ export function indexRules(entries, into = { buckets: new Map(), universal: [] }
|
|
|
958
994
|
into.universal.push(indexed);
|
|
959
995
|
continue;
|
|
960
996
|
}
|
|
997
|
+
if (key.startsWith('attr:'))
|
|
998
|
+
into.attributes = true;
|
|
999
|
+
else if (key.startsWith('under:'))
|
|
1000
|
+
into.under = true;
|
|
961
1001
|
const bucket = into.buckets.get(key);
|
|
962
1002
|
if (bucket)
|
|
963
1003
|
bucket.push(indexed);
|
|
@@ -990,6 +1030,27 @@ function reach(node, index, found) {
|
|
|
990
1030
|
if (around)
|
|
991
1031
|
for (const name of around)
|
|
992
1032
|
take(`in:${name}`);
|
|
1033
|
+
if (index.attributes)
|
|
1034
|
+
reachByAttribute(node, take);
|
|
1035
|
+
if (index.under)
|
|
1036
|
+
reachUnder(node, take);
|
|
1037
|
+
}
|
|
1038
|
+
/** The buckets of the classes of every element over a node. */
|
|
1039
|
+
function reachUnder(node, take) {
|
|
1040
|
+
for (let above = node.parent; above; above = above.parent) {
|
|
1041
|
+
if (above.classes)
|
|
1042
|
+
for (const name of above.classes)
|
|
1043
|
+
take(`under:${name}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
/** The buckets of the attributes a node has: a prop of its own that is set. */
|
|
1047
|
+
function reachByAttribute(node, take) {
|
|
1048
|
+
for (const name in node.props) {
|
|
1049
|
+
const value = node.props[name];
|
|
1050
|
+
// As `matchesAttribute` has it: a prop that is false is an attribute that is not there.
|
|
1051
|
+
if (value !== undefined && value !== null && value !== false)
|
|
1052
|
+
take(`attr:${name}`);
|
|
1053
|
+
}
|
|
993
1054
|
}
|
|
994
1055
|
/**
|
|
995
1056
|
* The rules worth trying against a node, in cascade order: `order` says which of two comes
|
|
@@ -1082,9 +1143,10 @@ function classReach(sheet) {
|
|
|
1082
1143
|
return found;
|
|
1083
1144
|
const classes = new Map();
|
|
1084
1145
|
const reads = [];
|
|
1146
|
+
const named = new Set();
|
|
1085
1147
|
for (const rule of sheet.rules) {
|
|
1086
1148
|
const every = new Set();
|
|
1087
|
-
collectClasses(rule, every, reads);
|
|
1149
|
+
collectClasses(rule, every, reads, named);
|
|
1088
1150
|
const inside = classesInside(rule);
|
|
1089
1151
|
for (const name of every) {
|
|
1090
1152
|
if (!inside.has(name))
|
|
@@ -1093,7 +1155,7 @@ function classReach(sheet) {
|
|
|
1093
1155
|
noteInside(classes, name, rule);
|
|
1094
1156
|
}
|
|
1095
1157
|
}
|
|
1096
|
-
reaches.set(sheet, (found = { classes, reads }));
|
|
1158
|
+
reaches.set(sheet, (found = { classes, reads, named }));
|
|
1097
1159
|
return found;
|
|
1098
1160
|
}
|
|
1099
1161
|
/** Note a rule as one that names a class as what its element is inside. */
|
|
@@ -1155,24 +1217,30 @@ function classesInside(rule) {
|
|
|
1155
1217
|
}
|
|
1156
1218
|
/** What a rule declares, which names no element. */
|
|
1157
1219
|
const DECLARED = new Set(['declarations', 'important', 'tokens']);
|
|
1158
|
-
/**
|
|
1159
|
-
|
|
1220
|
+
/**
|
|
1221
|
+
* Add each class under `value` to `into`, each test of the `class` attribute to `reads`, and the
|
|
1222
|
+
* name of every attribute asked about to `named`.
|
|
1223
|
+
*/
|
|
1224
|
+
function collectClasses(value, into, reads, named) {
|
|
1160
1225
|
if (value === null || typeof value !== 'object')
|
|
1161
1226
|
return;
|
|
1162
|
-
if (Array.isArray(value))
|
|
1163
|
-
return value.forEach((inner) => collectClasses(inner, into, reads));
|
|
1227
|
+
if (Array.isArray(value)) {
|
|
1228
|
+
return value.forEach((inner) => collectClasses(inner, into, reads, named));
|
|
1229
|
+
}
|
|
1164
1230
|
const part = value;
|
|
1165
1231
|
if (Array.isArray(part.classes))
|
|
1166
1232
|
for (const name of part.classes)
|
|
1167
1233
|
into.add(String(name));
|
|
1168
1234
|
if (Array.isArray(part.attributes)) {
|
|
1169
|
-
for (const test of part.attributes)
|
|
1235
|
+
for (const test of part.attributes) {
|
|
1236
|
+
named?.add(test.name);
|
|
1170
1237
|
if (test.name === 'class')
|
|
1171
1238
|
reads.push(test);
|
|
1239
|
+
}
|
|
1172
1240
|
}
|
|
1173
1241
|
for (const [key, inner] of Object.entries(value)) {
|
|
1174
1242
|
if (!DECLARED.has(key))
|
|
1175
|
-
collectClasses(inner, into, reads);
|
|
1243
|
+
collectClasses(inner, into, reads, named);
|
|
1176
1244
|
}
|
|
1177
1245
|
}
|
|
1178
1246
|
/**
|
|
@@ -1491,10 +1559,21 @@ export class StyleResolver {
|
|
|
1491
1559
|
if (!sheet || this.noted.has(sheet))
|
|
1492
1560
|
return;
|
|
1493
1561
|
this.noted.add(sheet);
|
|
1494
|
-
const { classes, reads } = classReach(sheet);
|
|
1562
|
+
const { classes, reads, named } = classReach(sheet);
|
|
1495
1563
|
for (const [name, how] of classes)
|
|
1496
1564
|
this.noteReach(name, how);
|
|
1497
1565
|
this.classTests.push(...reads);
|
|
1566
|
+
for (const name of named)
|
|
1567
|
+
this.named.add(name);
|
|
1568
|
+
}
|
|
1569
|
+
/** The props a selector of some sheet asks about: an id, and each attribute by its name. */
|
|
1570
|
+
named = new Set(['nativeID']);
|
|
1571
|
+
/**
|
|
1572
|
+
* Whether a prop changing can change what any element matches: only where a selector of a
|
|
1573
|
+
* sheet that was ever loaded names it. A sheet loaded later has every element matched again.
|
|
1574
|
+
*/
|
|
1575
|
+
reads(prop) {
|
|
1576
|
+
return this.named.has(prop);
|
|
1498
1577
|
}
|
|
1499
1578
|
noteReach(name, how) {
|
|
1500
1579
|
const had = this.reach.get(name);
|
|
@@ -2087,11 +2166,10 @@ export class StyleResolver {
|
|
|
2087
2166
|
if (unread && this.onUnreadDisplay && !outranked('display')) {
|
|
2088
2167
|
this.reportDisplay(declaration, tokens);
|
|
2089
2168
|
}
|
|
2090
|
-
const
|
|
2169
|
+
const layers = settled === undefined ? layersOf(declaration, tokens) : undefined;
|
|
2091
2170
|
for (const prop of declaration.props) {
|
|
2092
|
-
if (outranked(prop))
|
|
2093
|
-
|
|
2094
|
-
write(own, prop, value, declaration.line !== undefined);
|
|
2171
|
+
if (!outranked(prop))
|
|
2172
|
+
writeSettled(own, prop, declaration, settled, layers);
|
|
2095
2173
|
}
|
|
2096
2174
|
}
|
|
2097
2175
|
}
|
|
@@ -2759,6 +2837,41 @@ function tokenForm(token, kind) {
|
|
|
2759
2837
|
const value = formOf(token, kind);
|
|
2760
2838
|
return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
|
|
2761
2839
|
}
|
|
2840
|
+
/** What a deferred declaration settled to, written to one of its props. */
|
|
2841
|
+
function writeSettled(own, prop, declaration, settled, layers) {
|
|
2842
|
+
const value = settled ?? declaration.unset;
|
|
2843
|
+
if (declaration.layers)
|
|
2844
|
+
writeLayer(own, prop, value, layers);
|
|
2845
|
+
else
|
|
2846
|
+
write(own, prop, value, declaration.line !== undefined);
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* One prop of a `background: var()`, whose token may hold the images as well as the colour. The
|
|
2850
|
+
* image is written only where the token holds one: a token that is a colour leaves an image
|
|
2851
|
+
* another rule declared as it is. Each prop is ranked in the cascade by itself, so an image a
|
|
2852
|
+
* later rule declares, or an important one, is not written over by the token's.
|
|
2853
|
+
*/
|
|
2854
|
+
function writeLayer(own, prop, value, layers) {
|
|
2855
|
+
if (prop !== BACKGROUND_IMAGE)
|
|
2856
|
+
write(own, prop, layers?.color ?? value, false);
|
|
2857
|
+
else if (layers)
|
|
2858
|
+
own[prop] = layers.images;
|
|
2859
|
+
}
|
|
2860
|
+
const BACKGROUND_IMAGE = 'experimental_backgroundImage';
|
|
2861
|
+
/**
|
|
2862
|
+
* The gradients a `background: var()` reads where its token is no colour: see `backgroundLayers`.
|
|
2863
|
+
* Only the shorthand, which the compiler marks: `background-color: var()` is a colour or nothing.
|
|
2864
|
+
* And only where the token is set as text on an element, which is where a library sets one as the
|
|
2865
|
+
* app runs; a stylesheet's is refused where it is written. The token is the one the declaration
|
|
2866
|
+
* settles on, the first that is set.
|
|
2867
|
+
*/
|
|
2868
|
+
function layersOf(declaration, tokens) {
|
|
2869
|
+
if (!declaration.layers)
|
|
2870
|
+
return undefined;
|
|
2871
|
+
const names = [declaration.reference, ...(declaration.alternatives ?? [])];
|
|
2872
|
+
const text = firstSet(names, tokens)?.keyword;
|
|
2873
|
+
return text && /gradient\(/i.test(text) ? backgroundLayers(text) : undefined;
|
|
2874
|
+
}
|
|
2762
2875
|
const BOUND_NUMBERS = new Set([
|
|
2763
2876
|
'opacity',
|
|
2764
2877
|
'flex',
|