@ng-native/fabric 0.6.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 +21 -0
- package/dist/css.js +174 -24
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +104 -1
- package/dist/engine.js +617 -36
- 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 +48 -1
- package/dist/transition.js +119 -29
- 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.
|
|
@@ -299,6 +304,11 @@ export interface DeferredDeclaration {
|
|
|
299
304
|
readonly unit: 'em' | 'vw' | 'vh' | 'vmin' | 'vmax';
|
|
300
305
|
readonly factor: number;
|
|
301
306
|
readonly offset?: number;
|
|
307
|
+
/**
|
|
308
|
+
* A unitless line-height: the node renders `factor` em, and what it hands down is the number,
|
|
309
|
+
* which each text under it multiplies its own font size by. See `LineHeightMultiple`.
|
|
310
|
+
*/
|
|
311
|
+
readonly multiple?: true;
|
|
302
312
|
};
|
|
303
313
|
/**
|
|
304
314
|
* A structured value - a transform list, a shadow, a filter - with lengths like `compute`'s
|
|
@@ -693,6 +703,10 @@ interface IndexedEntry {
|
|
|
693
703
|
export interface RuleIndex {
|
|
694
704
|
readonly buckets: Map<string, IndexedEntry[]>;
|
|
695
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;
|
|
696
710
|
}
|
|
697
711
|
/**
|
|
698
712
|
* File rules by key selector, each at `from` and on: its place among every rule there is, which
|
|
@@ -843,6 +857,13 @@ export declare class StyleResolver {
|
|
|
843
857
|
private readonly noted;
|
|
844
858
|
/** Read the classes a sheet names, once, as soon as any node is given it. */
|
|
845
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;
|
|
846
867
|
private noteReach;
|
|
847
868
|
/**
|
|
848
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
|
/**
|
|
@@ -1226,6 +1294,40 @@ function emptyCacheFor(epoch, generation) {
|
|
|
1226
1294
|
tokens: NO_TOKENS,
|
|
1227
1295
|
};
|
|
1228
1296
|
}
|
|
1297
|
+
function isMultiple(value) {
|
|
1298
|
+
return typeof value === 'object' && value !== null && 'multiple' in value;
|
|
1299
|
+
}
|
|
1300
|
+
/** A line-height as points, for a node of font size `size`: what an em of it comes to. */
|
|
1301
|
+
function lineHeightPoints(line, size) {
|
|
1302
|
+
const points = line.multiple * (typeof size === 'number' ? size : DEFAULT_FONT_SIZE);
|
|
1303
|
+
return Math.round(points * 1000) / 1000;
|
|
1304
|
+
}
|
|
1305
|
+
/** A node's style with a unitless line-height it set or inherited worked out against its font size. */
|
|
1306
|
+
function settleLineHeight(style) {
|
|
1307
|
+
const line = style['lineHeight'];
|
|
1308
|
+
if (isMultiple(line))
|
|
1309
|
+
style['lineHeight'] = lineHeightPoints(line, style['fontSize']);
|
|
1310
|
+
}
|
|
1311
|
+
/** The styles of nodes with no rules, by the map they inherit, for one that hands down a multiple. */
|
|
1312
|
+
const unstyledStyles = new WeakMap();
|
|
1313
|
+
/**
|
|
1314
|
+
* The style of a node no rule matches: what it inherits, with a unitless line-height worked out
|
|
1315
|
+
* against its font size, which an inline style can set. The same object where there is none.
|
|
1316
|
+
*/
|
|
1317
|
+
function unstyledStyle(inherited, inline) {
|
|
1318
|
+
const line = inherited['lineHeight'];
|
|
1319
|
+
if (!isMultiple(line))
|
|
1320
|
+
return inherited;
|
|
1321
|
+
const size = inline?.['fontSize'];
|
|
1322
|
+
if (typeof size === 'number')
|
|
1323
|
+
return { ...inherited, lineHeight: lineHeightPoints(line, size) };
|
|
1324
|
+
let style = unstyledStyles.get(inherited);
|
|
1325
|
+
if (!style) {
|
|
1326
|
+
style = { ...inherited, lineHeight: lineHeightPoints(line, inherited['fontSize']) };
|
|
1327
|
+
unstyledStyles.set(inherited, style);
|
|
1328
|
+
}
|
|
1329
|
+
return style;
|
|
1330
|
+
}
|
|
1229
1331
|
/**
|
|
1230
1332
|
* What this node's children inherit: the parent's map, overlaid with any inheritable value the
|
|
1231
1333
|
* node set itself. Copied on first write only, so a node that sets none hands the same object on
|
|
@@ -1457,10 +1559,21 @@ export class StyleResolver {
|
|
|
1457
1559
|
if (!sheet || this.noted.has(sheet))
|
|
1458
1560
|
return;
|
|
1459
1561
|
this.noted.add(sheet);
|
|
1460
|
-
const { classes, reads } = classReach(sheet);
|
|
1562
|
+
const { classes, reads, named } = classReach(sheet);
|
|
1461
1563
|
for (const [name, how] of classes)
|
|
1462
1564
|
this.noteReach(name, how);
|
|
1463
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);
|
|
1464
1577
|
}
|
|
1465
1578
|
noteReach(name, how) {
|
|
1466
1579
|
const had = this.reach.get(name);
|
|
@@ -1722,7 +1835,7 @@ export class StyleResolver {
|
|
|
1722
1835
|
generation: this.generation,
|
|
1723
1836
|
context: {},
|
|
1724
1837
|
parentContext,
|
|
1725
|
-
style: parentInherited,
|
|
1838
|
+
style: unstyledStyle(parentInherited, inline),
|
|
1726
1839
|
inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
|
|
1727
1840
|
tokens,
|
|
1728
1841
|
};
|
|
@@ -1892,6 +2005,7 @@ export class StyleResolver {
|
|
|
1892
2005
|
if (own['pointerEvents'] === 'inherit')
|
|
1893
2006
|
delete own['pointerEvents'];
|
|
1894
2007
|
const style = { ...parentInherited, ...own };
|
|
2008
|
+
settleLineHeight(style);
|
|
1895
2009
|
const inherited = decorate(style, inheritFrom(parentInherited, own), own);
|
|
1896
2010
|
const rtl = (style['direction'] ?? this.conditions.direction) === 'rtl';
|
|
1897
2011
|
const important = importantNames(result, () => TWIN[rtl ? 'rtl' : 'ltr']);
|
|
@@ -2052,11 +2166,10 @@ export class StyleResolver {
|
|
|
2052
2166
|
if (unread && this.onUnreadDisplay && !outranked('display')) {
|
|
2053
2167
|
this.reportDisplay(declaration, tokens);
|
|
2054
2168
|
}
|
|
2055
|
-
const
|
|
2169
|
+
const layers = settled === undefined ? layersOf(declaration, tokens) : undefined;
|
|
2056
2170
|
for (const prop of declaration.props) {
|
|
2057
|
-
if (outranked(prop))
|
|
2058
|
-
|
|
2059
|
-
write(own, prop, value, declaration.line !== undefined);
|
|
2171
|
+
if (!outranked(prop))
|
|
2172
|
+
writeSettled(own, prop, declaration, settled, layers);
|
|
2060
2173
|
}
|
|
2061
2174
|
}
|
|
2062
2175
|
}
|
|
@@ -2233,9 +2346,11 @@ export class StyleResolver {
|
|
|
2233
2346
|
}
|
|
2234
2347
|
/** A length relative to the viewport or to the font size in scope. */
|
|
2235
2348
|
computed(declaration, own, parentInherited) {
|
|
2236
|
-
const { unit, factor, offset = 0 } = declaration.compute;
|
|
2349
|
+
const { unit, factor, offset = 0, multiple } = declaration.compute;
|
|
2237
2350
|
if (unit !== 'em')
|
|
2238
2351
|
return this.viewportLength(unit, factor) + offset;
|
|
2352
|
+
if (multiple && !offset)
|
|
2353
|
+
return { multiple: factor };
|
|
2239
2354
|
// The font size in scope. On `font-size` itself that is the inherited size rather than the
|
|
2240
2355
|
// one being computed, which is what makes a nested `1.5em` compound as on the web.
|
|
2241
2356
|
const base = declaration.props.includes('fontSize')
|
|
@@ -2722,6 +2837,41 @@ function tokenForm(token, kind) {
|
|
|
2722
2837
|
const value = formOf(token, kind);
|
|
2723
2838
|
return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
|
|
2724
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
|
+
}
|
|
2725
2875
|
const BOUND_NUMBERS = new Set([
|
|
2726
2876
|
'opacity',
|
|
2727
2877
|
'flex',
|