@ng-native/fabric 0.7.0 → 0.9.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 +67 -3
- package/dist/css.js +329 -62
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +137 -7
- package/dist/engine.js +641 -48
- 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 +34 -0
- package/dist/transition.js +32 -9
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
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. */
|
|
@@ -221,30 +222,41 @@ const attributeText = (actual, test) => test.insensitive ? String(actual).toLowe
|
|
|
221
222
|
*
|
|
222
223
|
* A prop set to `false` or `null` counts as absent, matching the web, where an attribute is either
|
|
223
224
|
* present or not and a `disabled` binding that evaluates false removes it. Everything else is
|
|
224
|
-
* compared as a string, so `[tabIndex="0"]` works against a numeric prop.
|
|
225
|
+
* compared as a string, so `[tabIndex="0"]` works against a numeric prop. `class` is no prop: it
|
|
226
|
+
* is the element's class list, as the attribute would spell it.
|
|
225
227
|
*/
|
|
226
228
|
function matchesAttribute(node, test) {
|
|
227
|
-
const actual = node.props[test.name];
|
|
229
|
+
const actual = test.name === 'class' ? classAttribute(node) : node.props[test.name];
|
|
228
230
|
if (actual === undefined || actual === null || actual === false)
|
|
229
231
|
return false;
|
|
230
232
|
if (test.operator === undefined)
|
|
231
233
|
return true;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
// `^=`, `$=`, `*=` and `~=` with an empty value match nothing, as Selectors 4 says.
|
|
235
|
+
if (test.value === '' && !EMPTY_MATCHES.has(test.operator))
|
|
236
|
+
return false;
|
|
237
|
+
return matchesValue(attributeText(actual, test), test.operator, test.value);
|
|
238
|
+
}
|
|
239
|
+
/** The classes of a node in the order they were set, one space between each; none is absent. */
|
|
240
|
+
const classAttribute = (node) => node.classes?.size ? [...node.classes].join(' ') : undefined;
|
|
241
|
+
/** The operators an empty value can still match by: `=` and `|=`. */
|
|
242
|
+
const EMPTY_MATCHES = new Set(['equal', 'dash-match']);
|
|
243
|
+
/** Whether an attribute's value, as `attributeText` gives it, passes an operator's test. */
|
|
244
|
+
function matchesValue(value, operator, expected) {
|
|
245
|
+
switch (operator) {
|
|
234
246
|
case 'equal':
|
|
235
|
-
return value ===
|
|
247
|
+
return value === expected;
|
|
236
248
|
case 'prefix':
|
|
237
|
-
return value.startsWith(
|
|
249
|
+
return value.startsWith(expected);
|
|
238
250
|
case 'suffix':
|
|
239
|
-
return value.endsWith(
|
|
251
|
+
return value.endsWith(expected);
|
|
240
252
|
case 'substring':
|
|
241
|
-
return value.includes(
|
|
253
|
+
return value.includes(expected);
|
|
242
254
|
case 'includes':
|
|
243
255
|
// `~=` matches one whole word of a space-separated list.
|
|
244
|
-
return value.split(/\s+/).includes(
|
|
256
|
+
return value.split(/\s+/).includes(expected);
|
|
245
257
|
case 'dash-match':
|
|
246
258
|
// `|=` matches the value itself or the value followed by a hyphen, as `lang` uses.
|
|
247
|
-
return value ===
|
|
259
|
+
return value === expected || value.startsWith(`${expected}-`);
|
|
248
260
|
}
|
|
249
261
|
}
|
|
250
262
|
/*
|
|
@@ -327,13 +339,15 @@ function matchesCompound(node, compound, sheet) {
|
|
|
327
339
|
}
|
|
328
340
|
}
|
|
329
341
|
if (compound.hostContext) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
342
|
+
for (const context of compound.hostContext) {
|
|
343
|
+
// The host itself counts, as it does on the web.
|
|
344
|
+
let found = false;
|
|
345
|
+
for (let n = node; n && !found; n = n.parent) {
|
|
346
|
+
found = matchesCompound(n, context, sheet);
|
|
347
|
+
}
|
|
348
|
+
if (!found)
|
|
349
|
+
return false;
|
|
334
350
|
}
|
|
335
|
-
if (!found)
|
|
336
|
-
return false;
|
|
337
351
|
}
|
|
338
352
|
return true;
|
|
339
353
|
}
|
|
@@ -905,7 +919,34 @@ export function ruleKeys(rule) {
|
|
|
905
919
|
const held = parentClass(rule, key);
|
|
906
920
|
if (held !== undefined)
|
|
907
921
|
return [`in:${held}`];
|
|
908
|
-
return alternativeKeys(key) ?? EVERY_NODE;
|
|
922
|
+
return alternativeKeys(key) ?? attributeKey(key) ?? ancestorKeys(rule, key) ?? EVERY_NODE;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* The buckets of a rule for anything inside an element with a class, `.item:focus *`: under the
|
|
926
|
+
* class, or each class the element may have instead. Offered to what has such an element
|
|
927
|
+
* somewhere over it, where the universal bucket has every element walk to the root to find none.
|
|
928
|
+
*/
|
|
929
|
+
function ancestorKeys(rule, key) {
|
|
930
|
+
const joined = rule.combinators[rule.combinators.length - 1];
|
|
931
|
+
const above = key.ancestors?.[0] ??
|
|
932
|
+
(joined === 'descendant' ? rule.compounds[rule.compounds.length - 2] : undefined);
|
|
933
|
+
if (!above)
|
|
934
|
+
return undefined;
|
|
935
|
+
if (above.classes.length)
|
|
936
|
+
return [`under:${above.classes[0]}`];
|
|
937
|
+
const any = above.is?.[0];
|
|
938
|
+
if (!any?.length || any.some((one) => !one.classes.length))
|
|
939
|
+
return undefined;
|
|
940
|
+
return any.map((one) => `under:${one.classes[0]}`);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* The bucket of a rule that names its element by an attribute and nothing else, `[ngpButton]`:
|
|
944
|
+
* a prop the element has to have, which is how a headless library names every element it
|
|
945
|
+
* styles. Not `class`, which is no prop of the element's to find the bucket by.
|
|
946
|
+
*/
|
|
947
|
+
function attributeKey(key) {
|
|
948
|
+
const named = key.attributes?.find((test) => test.name !== 'class');
|
|
949
|
+
return named ? [`attr:${named.name}`] : undefined;
|
|
909
950
|
}
|
|
910
951
|
/** A class the node's parent has to have for the rule to match, where its selector says one. */
|
|
911
952
|
function parentClass(rule, key) {
|
|
@@ -958,6 +999,10 @@ export function indexRules(entries, into = { buckets: new Map(), universal: [] }
|
|
|
958
999
|
into.universal.push(indexed);
|
|
959
1000
|
continue;
|
|
960
1001
|
}
|
|
1002
|
+
if (key.startsWith('attr:'))
|
|
1003
|
+
into.attributes = true;
|
|
1004
|
+
else if (key.startsWith('under:'))
|
|
1005
|
+
into.under = true;
|
|
961
1006
|
const bucket = into.buckets.get(key);
|
|
962
1007
|
if (bucket)
|
|
963
1008
|
bucket.push(indexed);
|
|
@@ -990,6 +1035,27 @@ function reach(node, index, found) {
|
|
|
990
1035
|
if (around)
|
|
991
1036
|
for (const name of around)
|
|
992
1037
|
take(`in:${name}`);
|
|
1038
|
+
if (index.attributes)
|
|
1039
|
+
reachByAttribute(node, take);
|
|
1040
|
+
if (index.under)
|
|
1041
|
+
reachUnder(node, take);
|
|
1042
|
+
}
|
|
1043
|
+
/** The buckets of the classes of every element over a node. */
|
|
1044
|
+
function reachUnder(node, take) {
|
|
1045
|
+
for (let above = node.parent; above; above = above.parent) {
|
|
1046
|
+
if (above.classes)
|
|
1047
|
+
for (const name of above.classes)
|
|
1048
|
+
take(`under:${name}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/** The buckets of the attributes a node has: a prop of its own that is set. */
|
|
1052
|
+
function reachByAttribute(node, take) {
|
|
1053
|
+
for (const name in node.props) {
|
|
1054
|
+
const value = node.props[name];
|
|
1055
|
+
// As `matchesAttribute` has it: a prop that is false is an attribute that is not there.
|
|
1056
|
+
if (value !== undefined && value !== null && value !== false)
|
|
1057
|
+
take(`attr:${name}`);
|
|
1058
|
+
}
|
|
993
1059
|
}
|
|
994
1060
|
/**
|
|
995
1061
|
* The rules worth trying against a node, in cascade order: `order` says which of two comes
|
|
@@ -1033,6 +1099,13 @@ const DEFAULT_FONT_SIZE = 16;
|
|
|
1033
1099
|
const EMPTY = {};
|
|
1034
1100
|
/** Stands in for the parent of a root node, so the comparison needs no null case. */
|
|
1035
1101
|
const ROOT_CONTEXT = {};
|
|
1102
|
+
/** The `scope` over a node with no parent. */
|
|
1103
|
+
const ROOT_SCOPE = {};
|
|
1104
|
+
/**
|
|
1105
|
+
* The `scope` of the one cache every node with nothing to style shares. It is no one node's, so
|
|
1106
|
+
* nothing styled under it, or holding it, is taken to match what it did.
|
|
1107
|
+
*/
|
|
1108
|
+
const SHARED_SCOPE = {};
|
|
1036
1109
|
/** Shared empty token map, for the overwhelmingly common case of a subtree defining none. */
|
|
1037
1110
|
const NO_TOKENS = {};
|
|
1038
1111
|
/**
|
|
@@ -1082,20 +1155,33 @@ function classReach(sheet) {
|
|
|
1082
1155
|
return found;
|
|
1083
1156
|
const classes = new Map();
|
|
1084
1157
|
const reads = [];
|
|
1158
|
+
const named = new Set();
|
|
1085
1159
|
for (const rule of sheet.rules) {
|
|
1086
1160
|
const every = new Set();
|
|
1087
|
-
collectClasses(rule, every, reads);
|
|
1088
|
-
const inside =
|
|
1161
|
+
collectClasses(rule, every, reads, named);
|
|
1162
|
+
const { own, inside } = classPlaces(rule);
|
|
1089
1163
|
for (const name of every) {
|
|
1090
|
-
if (!inside.has(name))
|
|
1164
|
+
if (!own.has(name) && !inside.has(name))
|
|
1091
1165
|
classes.set(name, true);
|
|
1092
|
-
|
|
1166
|
+
if (own.has(name))
|
|
1167
|
+
noteOwn(classes, name);
|
|
1168
|
+
if (inside.has(name))
|
|
1093
1169
|
noteInside(classes, name, rule);
|
|
1094
1170
|
}
|
|
1095
1171
|
}
|
|
1096
|
-
reaches.set(sheet, (found = { classes, reads }));
|
|
1172
|
+
reaches.set(sheet, (found = { classes, reads, named }));
|
|
1097
1173
|
return found;
|
|
1098
1174
|
}
|
|
1175
|
+
/** Note a rule as one for the element that has a class. */
|
|
1176
|
+
function noteOwn(found, name) {
|
|
1177
|
+
const subjects = found.get(name);
|
|
1178
|
+
if (subjects === true)
|
|
1179
|
+
return;
|
|
1180
|
+
if (subjects)
|
|
1181
|
+
subjects.own = true;
|
|
1182
|
+
else
|
|
1183
|
+
found.set(name, { ...noSubjects(), own: true });
|
|
1184
|
+
}
|
|
1099
1185
|
/** Note a rule as one that names a class as what its element is inside. */
|
|
1100
1186
|
function noteInside(found, name, rule) {
|
|
1101
1187
|
let subjects = found.get(name);
|
|
@@ -1119,6 +1205,8 @@ const noSubjects = () => ({
|
|
|
1119
1205
|
inside: new Set(),
|
|
1120
1206
|
any: false,
|
|
1121
1207
|
});
|
|
1208
|
+
/** The reach of classes that rules are for the element of, and for nothing under it. */
|
|
1209
|
+
const OWN_ALONE = { ...noSubjects(), own: true };
|
|
1122
1210
|
/** Add to `into` what `from` is for. */
|
|
1123
1211
|
function addSubjects(into, from) {
|
|
1124
1212
|
for (const name of from.classes)
|
|
@@ -1128,51 +1216,94 @@ function addSubjects(into, from) {
|
|
|
1128
1216
|
for (const name of from.inside)
|
|
1129
1217
|
into.inside.add(name);
|
|
1130
1218
|
into.any ||= from.any;
|
|
1219
|
+
if (from.own)
|
|
1220
|
+
into.own = true;
|
|
1131
1221
|
}
|
|
1132
1222
|
/**
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1223
|
+
* Where a rule names each class it names, for the classes it names in no other place than these:
|
|
1224
|
+
*
|
|
1225
|
+
* - `own`, on the element the rule is for: its own classes, and those in `:is()` and `:not()` of
|
|
1226
|
+
* it, which are asked of that element;
|
|
1227
|
+
* - `inside`, as what that element is inside: the classes of a compound before the last, with
|
|
1228
|
+
* nothing but `>` and a space between there and the element, and those of what `:is(.box *)`
|
|
1229
|
+
* and `:is(.box > *)` put over it.
|
|
1230
|
+
*
|
|
1231
|
+
* A class named anywhere else in the rule is in neither, whatever else names it: beside the
|
|
1232
|
+
* element, inside `:has()` or `:host-context()`, or in `:not()` of a box the element is in.
|
|
1135
1233
|
*/
|
|
1136
|
-
function
|
|
1234
|
+
function classPlaces(rule) {
|
|
1235
|
+
const own = new Set();
|
|
1137
1236
|
const inside = new Set();
|
|
1138
1237
|
const elsewhere = new Set();
|
|
1139
1238
|
const last = rule.compounds.length - 1;
|
|
1140
1239
|
let under = true;
|
|
1141
1240
|
for (let index = last; index >= 0; index--) {
|
|
1142
1241
|
const compound = rule.compounds[index];
|
|
1143
|
-
if (index
|
|
1144
|
-
|
|
1145
|
-
|
|
1242
|
+
if (index === last) {
|
|
1243
|
+
placeClasses(compound, own, inside, elsewhere);
|
|
1244
|
+
continue;
|
|
1146
1245
|
}
|
|
1246
|
+
const joined = rule.combinators[index];
|
|
1247
|
+
under &&= joined === 'child' || joined === 'descendant';
|
|
1147
1248
|
const { classes, ...rest } = compound;
|
|
1148
1249
|
collectClasses(rest, elsewhere, []);
|
|
1149
1250
|
for (const name of classes)
|
|
1150
|
-
(
|
|
1251
|
+
(under ? inside : elsewhere).add(name);
|
|
1151
1252
|
}
|
|
1152
|
-
for (const name of elsewhere)
|
|
1253
|
+
for (const name of elsewhere) {
|
|
1254
|
+
own.delete(name);
|
|
1153
1255
|
inside.delete(name);
|
|
1154
|
-
|
|
1256
|
+
}
|
|
1257
|
+
return { own, inside };
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* The classes of a compound asked of the element a rule is for, into `at`, and of the compounds
|
|
1261
|
+
* it puts over that element, into `inside`. What any other part of it names goes `elsewhere`:
|
|
1262
|
+
* a part read here by name is one this knows the reach of, and one added later is not.
|
|
1263
|
+
*/
|
|
1264
|
+
function placeClasses(compound, at, inside, elsewhere) {
|
|
1265
|
+
const { classes, is, not, ancestors, parents, type: _t, id: _i, root: _r, pseudo: _p, ...rest } = compound;
|
|
1266
|
+
for (const name of classes)
|
|
1267
|
+
at.add(name);
|
|
1268
|
+
for (const alternatives of is ?? []) {
|
|
1269
|
+
for (const option of alternatives)
|
|
1270
|
+
placeClasses(option, at, inside, elsewhere);
|
|
1271
|
+
}
|
|
1272
|
+
for (const excluded of not ?? [])
|
|
1273
|
+
placeClasses(excluded, at, inside, elsewhere);
|
|
1274
|
+
// What is over the element is over it whichever of these said so, and what is over that is too.
|
|
1275
|
+
for (const above of [...(ancestors ?? []), ...(parents ?? [])]) {
|
|
1276
|
+
placeClasses(above, inside, inside, elsewhere);
|
|
1277
|
+
}
|
|
1278
|
+
// `attributes` among them: a test of the `class` attribute is answered through `classTests`.
|
|
1279
|
+
collectClasses(rest, elsewhere, []);
|
|
1155
1280
|
}
|
|
1156
1281
|
/** What a rule declares, which names no element. */
|
|
1157
1282
|
const DECLARED = new Set(['declarations', 'important', 'tokens']);
|
|
1158
|
-
/**
|
|
1159
|
-
|
|
1283
|
+
/**
|
|
1284
|
+
* Add each class under `value` to `into`, each test of the `class` attribute to `reads`, and the
|
|
1285
|
+
* name of every attribute asked about to `named`.
|
|
1286
|
+
*/
|
|
1287
|
+
function collectClasses(value, into, reads, named) {
|
|
1160
1288
|
if (value === null || typeof value !== 'object')
|
|
1161
1289
|
return;
|
|
1162
|
-
if (Array.isArray(value))
|
|
1163
|
-
return value.forEach((inner) => collectClasses(inner, into, reads));
|
|
1290
|
+
if (Array.isArray(value)) {
|
|
1291
|
+
return value.forEach((inner) => collectClasses(inner, into, reads, named));
|
|
1292
|
+
}
|
|
1164
1293
|
const part = value;
|
|
1165
1294
|
if (Array.isArray(part.classes))
|
|
1166
1295
|
for (const name of part.classes)
|
|
1167
1296
|
into.add(String(name));
|
|
1168
1297
|
if (Array.isArray(part.attributes)) {
|
|
1169
|
-
for (const test of part.attributes)
|
|
1298
|
+
for (const test of part.attributes) {
|
|
1299
|
+
named?.add(test.name);
|
|
1170
1300
|
if (test.name === 'class')
|
|
1171
1301
|
reads.push(test);
|
|
1302
|
+
}
|
|
1172
1303
|
}
|
|
1173
1304
|
for (const [key, inner] of Object.entries(value)) {
|
|
1174
1305
|
if (!DECLARED.has(key))
|
|
1175
|
-
collectClasses(inner, into, reads);
|
|
1306
|
+
collectClasses(inner, into, reads, named);
|
|
1176
1307
|
}
|
|
1177
1308
|
}
|
|
1178
1309
|
/**
|
|
@@ -1215,12 +1346,22 @@ function writtenFor(of, node) {
|
|
|
1215
1346
|
}
|
|
1216
1347
|
return false;
|
|
1217
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Whether a node has its answer for this commit already. Not one marked since it was given the
|
|
1351
|
+
* one cache every box with nothing to style shares, see `unstyled`: another of them resolved in
|
|
1352
|
+
* this commit has set that cache's epoch, and it is not this node's answer.
|
|
1353
|
+
*/
|
|
1354
|
+
function resolvedIn(epoch, cache, node) {
|
|
1355
|
+
return (cache !== null && cache.epoch === epoch && (!node.styleDirty || cache.context !== ROOT_CONTEXT));
|
|
1356
|
+
}
|
|
1218
1357
|
function emptyCacheFor(epoch, generation) {
|
|
1219
1358
|
return {
|
|
1220
1359
|
epoch,
|
|
1221
1360
|
generation,
|
|
1222
1361
|
context: ROOT_CONTEXT,
|
|
1223
1362
|
parentContext: ROOT_CONTEXT,
|
|
1363
|
+
scope: SHARED_SCOPE,
|
|
1364
|
+
parentScope: SHARED_SCOPE,
|
|
1224
1365
|
style: EMPTY,
|
|
1225
1366
|
inherited: EMPTY,
|
|
1226
1367
|
tokens: NO_TOKENS,
|
|
@@ -1491,10 +1632,21 @@ export class StyleResolver {
|
|
|
1491
1632
|
if (!sheet || this.noted.has(sheet))
|
|
1492
1633
|
return;
|
|
1493
1634
|
this.noted.add(sheet);
|
|
1494
|
-
const { classes, reads } = classReach(sheet);
|
|
1635
|
+
const { classes, reads, named } = classReach(sheet);
|
|
1495
1636
|
for (const [name, how] of classes)
|
|
1496
1637
|
this.noteReach(name, how);
|
|
1497
1638
|
this.classTests.push(...reads);
|
|
1639
|
+
for (const name of named)
|
|
1640
|
+
this.named.add(name);
|
|
1641
|
+
}
|
|
1642
|
+
/** The props a selector of some sheet asks about: an id, and each attribute by its name. */
|
|
1643
|
+
named = new Set(['nativeID']);
|
|
1644
|
+
/**
|
|
1645
|
+
* Whether a prop changing can change what any element matches: only where a selector of a
|
|
1646
|
+
* sheet that was ever loaded names it. A sheet loaded later has every element matched again.
|
|
1647
|
+
*/
|
|
1648
|
+
reads(prop) {
|
|
1649
|
+
return this.named.has(prop);
|
|
1498
1650
|
}
|
|
1499
1651
|
noteReach(name, how) {
|
|
1500
1652
|
const had = this.reach.get(name);
|
|
@@ -1505,20 +1657,38 @@ export class StyleResolver {
|
|
|
1505
1657
|
else
|
|
1506
1658
|
addSubjects(had ?? this.reach.set(name, noSubjects()).get(name), how);
|
|
1507
1659
|
}
|
|
1660
|
+
/** Whether a class's reach names any element under the one that has it. */
|
|
1661
|
+
reachesUnder(subjects) {
|
|
1662
|
+
return (subjects.any ||
|
|
1663
|
+
subjects.classes.size > 0 ||
|
|
1664
|
+
subjects.types.size > 0 ||
|
|
1665
|
+
subjects.inside.size > 0);
|
|
1666
|
+
}
|
|
1508
1667
|
/**
|
|
1509
1668
|
* What classes coming to an element or going from it can restyle: `true` for it and all
|
|
1510
|
-
* under it and after it, as any change to it does; the
|
|
1511
|
-
* rules are for; or null, where no rule names any
|
|
1669
|
+
* under it and after it, as any change to it does; the element itself, where `own`, and the
|
|
1670
|
+
* elements under it to match again, by what rules are for; or null, where no rule names any
|
|
1671
|
+
* of them and nothing is styled again.
|
|
1512
1672
|
*/
|
|
1513
1673
|
reachOf(classes) {
|
|
1514
1674
|
let under = null;
|
|
1675
|
+
let own = false;
|
|
1515
1676
|
for (const name of classes) {
|
|
1516
1677
|
const how = this.reach.get(name);
|
|
1517
1678
|
if (how === true || this.classTests.some((test) => answers(test, name)))
|
|
1518
1679
|
return true;
|
|
1519
|
-
if (how)
|
|
1680
|
+
if (!how)
|
|
1681
|
+
continue;
|
|
1682
|
+
own ||= how.own === true;
|
|
1683
|
+
// Made only for a class that reaches under: most are a rule's own, and this is asked for
|
|
1684
|
+
// every element as it is given its classes.
|
|
1685
|
+
if (this.reachesUnder(how))
|
|
1520
1686
|
addSubjects((under ??= noSubjects()), how);
|
|
1521
1687
|
}
|
|
1688
|
+
if (!under)
|
|
1689
|
+
return own ? OWN_ALONE : null;
|
|
1690
|
+
if (own)
|
|
1691
|
+
under.own = true;
|
|
1522
1692
|
return under;
|
|
1523
1693
|
}
|
|
1524
1694
|
/** Whether one of the rules `subjects` stands for could be for a node. */
|
|
@@ -1579,7 +1749,7 @@ export class StyleResolver {
|
|
|
1579
1749
|
const cached = node.styleCache;
|
|
1580
1750
|
// Within one commit a node is asked for its inherited map once per descendant, so this is the
|
|
1581
1751
|
// hot path and it must not walk anywhere.
|
|
1582
|
-
if (
|
|
1752
|
+
if (resolvedIn(epoch, cached, node))
|
|
1583
1753
|
return cached;
|
|
1584
1754
|
const parent = node.parent ? this.resolve(node.parent, epoch) : null;
|
|
1585
1755
|
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
@@ -1597,33 +1767,66 @@ export class StyleResolver {
|
|
|
1597
1767
|
this.justMatched = null;
|
|
1598
1768
|
if (kept)
|
|
1599
1769
|
return kept;
|
|
1770
|
+
return this.styledAgain(node, epoch, parent, found);
|
|
1771
|
+
}
|
|
1772
|
+
/** A node styled now, from the rules found for it or from those it kept: see `matchesStand`. */
|
|
1773
|
+
styledAgain(node, epoch, parent, found) {
|
|
1774
|
+
const cached = node.styleCache;
|
|
1775
|
+
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
1600
1776
|
const parentInherited = parent ? parent.inherited : EMPTY;
|
|
1777
|
+
const parentScope = parent ? parent.scope : ROOT_SCOPE;
|
|
1778
|
+
const stands = this.matchesStand(cached, node, parentScope);
|
|
1779
|
+
// The scope it keeps, or none: it then has a new one, which its new `context` serves as.
|
|
1780
|
+
const scope = stands ? cached.scope : null;
|
|
1601
1781
|
// A box the engine made around loose text is in no template, so no rule matches it.
|
|
1602
1782
|
if (node.anonymous || this.hasNoRules(node)) {
|
|
1603
|
-
return this.unstyled(node, epoch, parent,
|
|
1783
|
+
return this.unstyled(node, epoch, parent, parentInherited, scope, parentScope);
|
|
1604
1784
|
}
|
|
1605
1785
|
styleStats.nodesResolved++;
|
|
1606
|
-
|
|
1786
|
+
// Its own rules are found again for a change of its own, and not for one above it.
|
|
1787
|
+
const rules = stands && !node.ownDirty ? cached.rules : undefined;
|
|
1788
|
+
const matched = found ?? rules ?? this.matchedBy(node);
|
|
1607
1789
|
const parentTokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
1608
1790
|
this.heir(node, parent, matched);
|
|
1609
1791
|
const styled = this.styled(node, matched, parentTokens, parentInherited);
|
|
1610
|
-
const cache = this.cached(epoch, parentContext, styled, matched);
|
|
1792
|
+
const cache = this.cached(epoch, parentContext, styled, matched, scope, parentScope);
|
|
1611
1793
|
node.styleCache = cache;
|
|
1612
1794
|
node.styleDirty = false;
|
|
1795
|
+
node.ownDirty = false;
|
|
1613
1796
|
node.hasDirty = false;
|
|
1614
1797
|
node.stateDirty = false;
|
|
1615
1798
|
return cache;
|
|
1616
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Whether what an element could match is as it was when it was last styled: nothing that can
|
|
1802
|
+
* change it was marked on the element, its parent's `scope` is the one it was styled under,
|
|
1803
|
+
* and no sheet that could match it has come since. It is being styled again all the same, for
|
|
1804
|
+
* a change of its own that alters nothing under it, or for what it inherits.
|
|
1805
|
+
*/
|
|
1806
|
+
matchesStand(cached, node, parentScope) {
|
|
1807
|
+
return (cached !== null &&
|
|
1808
|
+
!node.styleDirty &&
|
|
1809
|
+
!node.hasDirty &&
|
|
1810
|
+
!node.stateDirty &&
|
|
1811
|
+
cached.scope !== SHARED_SCOPE &&
|
|
1812
|
+
parentScope !== SHARED_SCOPE &&
|
|
1813
|
+
cached.parentScope === parentScope &&
|
|
1814
|
+
this.current(cached, node));
|
|
1815
|
+
}
|
|
1617
1816
|
/**
|
|
1618
1817
|
* A cache of a node just styled, with a context of its own. It keeps the rules the node
|
|
1619
1818
|
* matched, for the node to be compared with, once a sheet has a use for them: see `tracksHas`.
|
|
1620
1819
|
*/
|
|
1621
|
-
cached(epoch, parentContext, styled, matched) {
|
|
1820
|
+
cached(epoch, parentContext, styled, matched, scope, parentScope) {
|
|
1821
|
+
const context = {};
|
|
1622
1822
|
const cache = {
|
|
1623
1823
|
epoch,
|
|
1624
1824
|
generation: this.generation,
|
|
1625
|
-
context
|
|
1825
|
+
context,
|
|
1626
1826
|
parentContext,
|
|
1827
|
+
scope: scope ?? context,
|
|
1828
|
+
parentScope,
|
|
1829
|
+
rules: matched,
|
|
1627
1830
|
...styled,
|
|
1628
1831
|
};
|
|
1629
1832
|
if (this.tracksHas)
|
|
@@ -1646,6 +1849,7 @@ export class StyleResolver {
|
|
|
1646
1849
|
const stands = (node.hasDirty === true || node.stateDirty === true) &&
|
|
1647
1850
|
cached?.matched !== undefined &&
|
|
1648
1851
|
!node.styleDirty &&
|
|
1852
|
+
!node.ownDirty &&
|
|
1649
1853
|
cached.parentContext === parentContext &&
|
|
1650
1854
|
this.current(cached, node);
|
|
1651
1855
|
if (!stands)
|
|
@@ -1685,6 +1889,7 @@ export class StyleResolver {
|
|
|
1685
1889
|
const stands = node.stateDirty === true &&
|
|
1686
1890
|
cached !== null &&
|
|
1687
1891
|
!node.styleDirty &&
|
|
1892
|
+
!node.ownDirty &&
|
|
1688
1893
|
!node.hasDirty &&
|
|
1689
1894
|
cached.parentContext === parentContext &&
|
|
1690
1895
|
this.current(cached, node) &&
|
|
@@ -1702,6 +1907,14 @@ export class StyleResolver {
|
|
|
1702
1907
|
holds(node, parentContext) {
|
|
1703
1908
|
return this.reusable(node.styleCache, node, parentContext);
|
|
1704
1909
|
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Whether a node was last styled under a box holding the one cache shared by every box with
|
|
1912
|
+
* nothing to style. Such boxes hand the same `context` down, so the node cannot tell one from
|
|
1913
|
+
* another by it: moved under another box, it has to be told.
|
|
1914
|
+
*/
|
|
1915
|
+
styledUnderShared(node) {
|
|
1916
|
+
return node.styleCache !== null && node.styleCache.parentContext === ROOT_CONTEXT;
|
|
1917
|
+
}
|
|
1705
1918
|
/**
|
|
1706
1919
|
* Whether a previous result still stands: nothing about the node changed, nothing above it
|
|
1707
1920
|
* moved, and the conditions have not.
|
|
@@ -1709,6 +1922,7 @@ export class StyleResolver {
|
|
|
1709
1922
|
reusable(cached, node, parentContext) {
|
|
1710
1923
|
return (cached !== null &&
|
|
1711
1924
|
!node.styleDirty &&
|
|
1925
|
+
!node.ownDirty &&
|
|
1712
1926
|
!node.hasDirty &&
|
|
1713
1927
|
!node.stateDirty &&
|
|
1714
1928
|
cached.parentContext === parentContext &&
|
|
@@ -1736,35 +1950,43 @@ export class StyleResolver {
|
|
|
1736
1950
|
* carry: the shared object holds none, and the surface root answering with it dropped every
|
|
1737
1951
|
* token the device had seeded.
|
|
1738
1952
|
*/
|
|
1739
|
-
unstyled(node, epoch, parent,
|
|
1953
|
+
unstyled(node, epoch, parent, parentInherited, scope, parentScope) {
|
|
1740
1954
|
const tokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
1955
|
+
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
1741
1956
|
// What inline style sets is inherited too, with no rule to read it against.
|
|
1742
1957
|
const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
|
|
1743
|
-
|
|
1958
|
+
const nothing = !inline &&
|
|
1744
1959
|
parentInherited === EMPTY &&
|
|
1745
1960
|
parentContext === ROOT_CONTEXT &&
|
|
1746
|
-
tokens === NO_TOKENS
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
}
|
|
1751
|
-
node.styleCache = this.emptyCache;
|
|
1752
|
-
return this.emptyCache;
|
|
1753
|
-
}
|
|
1961
|
+
tokens === NO_TOKENS;
|
|
1962
|
+
if (nothing && !node.styleDirty && !node.ownDirty)
|
|
1963
|
+
return this.sharedEmpty(node, epoch);
|
|
1964
|
+
const context = {};
|
|
1754
1965
|
const passthrough = {
|
|
1755
1966
|
epoch,
|
|
1756
1967
|
generation: this.generation,
|
|
1757
|
-
context
|
|
1968
|
+
context,
|
|
1758
1969
|
parentContext,
|
|
1970
|
+
scope: scope ?? context,
|
|
1971
|
+
parentScope,
|
|
1759
1972
|
style: unstyledStyle(parentInherited, inline),
|
|
1760
1973
|
inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
|
|
1761
1974
|
tokens,
|
|
1762
1975
|
};
|
|
1763
1976
|
node.styleCache = passthrough;
|
|
1764
1977
|
node.styleDirty = false;
|
|
1978
|
+
node.ownDirty = false;
|
|
1765
1979
|
node.stateDirty = false;
|
|
1766
1980
|
return passthrough;
|
|
1767
1981
|
}
|
|
1982
|
+
/** The one cache of every node with nothing to style and nothing to hand down, given to one more. */
|
|
1983
|
+
sharedEmpty(node, epoch) {
|
|
1984
|
+
if (this.emptyCache.epoch !== epoch || this.emptyCache.generation !== this.generation) {
|
|
1985
|
+
this.emptyCache = emptyCacheFor(epoch, this.generation);
|
|
1986
|
+
}
|
|
1987
|
+
node.styleCache = this.emptyCache;
|
|
1988
|
+
return this.emptyCache;
|
|
1989
|
+
}
|
|
1768
1990
|
/**
|
|
1769
1991
|
* The rules worth trying against a node, in cascade order.
|
|
1770
1992
|
*
|
|
@@ -2071,6 +2293,17 @@ export class StyleResolver {
|
|
|
2071
2293
|
* A declaration with no token and no usable fallback is dropped rather than written as
|
|
2072
2294
|
* undefined, which is what a browser does with a value it cannot resolve.
|
|
2073
2295
|
*/
|
|
2296
|
+
/**
|
|
2297
|
+
* What declarations that wait for a node's tokens come to on it, where no rule of the node's
|
|
2298
|
+
* carries them: a keyframe's, which is settled against the element that plays it.
|
|
2299
|
+
*/
|
|
2300
|
+
settleFor(node, deferred, epoch) {
|
|
2301
|
+
const { tokens } = this.resolve(node, epoch);
|
|
2302
|
+
const above = node.parent ? this.resolve(node.parent, epoch).inherited : EMPTY;
|
|
2303
|
+
const own = {};
|
|
2304
|
+
this.applyDeferred(deferred, own, tokens, above, null);
|
|
2305
|
+
return own;
|
|
2306
|
+
}
|
|
2074
2307
|
applyDeferred(deferred, own, tokens, parentInherited, important) {
|
|
2075
2308
|
for (const declaration of settlingOrder(deferred)) {
|
|
2076
2309
|
if (declaration.inherit) {
|
|
@@ -2087,11 +2320,10 @@ export class StyleResolver {
|
|
|
2087
2320
|
if (unread && this.onUnreadDisplay && !outranked('display')) {
|
|
2088
2321
|
this.reportDisplay(declaration, tokens);
|
|
2089
2322
|
}
|
|
2090
|
-
const
|
|
2323
|
+
const layers = settled === undefined ? layersOf(declaration, tokens) : undefined;
|
|
2091
2324
|
for (const prop of declaration.props) {
|
|
2092
|
-
if (outranked(prop))
|
|
2093
|
-
|
|
2094
|
-
write(own, prop, value, declaration.line !== undefined);
|
|
2325
|
+
if (!outranked(prop))
|
|
2326
|
+
writeSettled(own, prop, declaration, settled, layers);
|
|
2095
2327
|
}
|
|
2096
2328
|
}
|
|
2097
2329
|
}
|
|
@@ -2759,6 +2991,41 @@ function tokenForm(token, kind) {
|
|
|
2759
2991
|
const value = formOf(token, kind);
|
|
2760
2992
|
return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
|
|
2761
2993
|
}
|
|
2994
|
+
/** What a deferred declaration settled to, written to one of its props. */
|
|
2995
|
+
function writeSettled(own, prop, declaration, settled, layers) {
|
|
2996
|
+
const value = settled ?? declaration.unset;
|
|
2997
|
+
if (declaration.layers)
|
|
2998
|
+
writeLayer(own, prop, value, layers);
|
|
2999
|
+
else
|
|
3000
|
+
write(own, prop, value, declaration.line !== undefined);
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* One prop of a `background: var()`, whose token may hold the images as well as the colour. The
|
|
3004
|
+
* image is written only where the token holds one: a token that is a colour leaves an image
|
|
3005
|
+
* another rule declared as it is. Each prop is ranked in the cascade by itself, so an image a
|
|
3006
|
+
* later rule declares, or an important one, is not written over by the token's.
|
|
3007
|
+
*/
|
|
3008
|
+
function writeLayer(own, prop, value, layers) {
|
|
3009
|
+
if (prop !== BACKGROUND_IMAGE)
|
|
3010
|
+
write(own, prop, layers?.color ?? value, false);
|
|
3011
|
+
else if (layers)
|
|
3012
|
+
own[prop] = layers.images;
|
|
3013
|
+
}
|
|
3014
|
+
const BACKGROUND_IMAGE = 'experimental_backgroundImage';
|
|
3015
|
+
/**
|
|
3016
|
+
* The gradients a `background: var()` reads where its token is no colour: see `backgroundLayers`.
|
|
3017
|
+
* Only the shorthand, which the compiler marks: `background-color: var()` is a colour or nothing.
|
|
3018
|
+
* And only where the token is set as text on an element, which is where a library sets one as the
|
|
3019
|
+
* app runs; a stylesheet's is refused where it is written. The token is the one the declaration
|
|
3020
|
+
* settles on, the first that is set.
|
|
3021
|
+
*/
|
|
3022
|
+
function layersOf(declaration, tokens) {
|
|
3023
|
+
if (!declaration.layers)
|
|
3024
|
+
return undefined;
|
|
3025
|
+
const names = [declaration.reference, ...(declaration.alternatives ?? [])];
|
|
3026
|
+
const text = firstSet(names, tokens)?.keyword;
|
|
3027
|
+
return text && /gradient\(/i.test(text) ? backgroundLayers(text) : undefined;
|
|
3028
|
+
}
|
|
2762
3029
|
const BOUND_NUMBERS = new Set([
|
|
2763
3030
|
'opacity',
|
|
2764
3031
|
'flex',
|