@ng-native/fabric 0.16.0 → 0.17.1
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/css.d.ts +141 -18
- package/dist/css.js +793 -111
- package/dist/css.js.map +1 -1
- package/dist/defer-triggers.js +4 -4
- package/dist/defer-triggers.js.map +1 -1
- package/dist/engine.d.ts +1408 -81
- package/dist/engine.js +9341 -2831
- 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/flow.d.ts +78 -0
- package/dist/flow.js +270 -0
- package/dist/flow.js.map +1 -0
- package/dist/grid.d.ts +73 -0
- package/dist/grid.js +934 -0
- package/dist/grid.js.map +1 -0
- package/dist/host.d.ts +11 -5
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/native-drive.d.ts +29 -2
- package/dist/native-drive.js +91 -36
- package/dist/native-drive.js.map +1 -1
- package/dist/next-frame.d.ts +2 -0
- package/dist/next-frame.js +10 -0
- package/dist/next-frame.js.map +1 -0
- package/dist/scroll-animation.d.ts +4 -3
- package/dist/scroll-animation.js +64 -4
- package/dist/scroll-animation.js.map +1 -1
- package/package.json +1 -1
package/dist/css.js
CHANGED
|
@@ -32,13 +32,16 @@ function formOf(token, kind) {
|
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Whether a child is one `:empty` sees. An anchor is a comment on the web, which it does not;
|
|
35
|
-
* nor text of no length, which a binding to nothing leaves behind
|
|
35
|
+
* nor text of no length, which a binding to nothing leaves behind; nor a box a rule generated,
|
|
36
|
+
* which is not in the tree a selector is matched against.
|
|
36
37
|
*/
|
|
37
38
|
export function fills(child) {
|
|
38
|
-
if (child.
|
|
39
|
+
if (child.nodeKind === 'anchor' || (boxesDrawn && isGenerated(child.tag)))
|
|
39
40
|
return false;
|
|
40
|
-
return child.
|
|
41
|
+
return child.nodeKind !== 'text' || child.nodeText !== '';
|
|
41
42
|
}
|
|
43
|
+
/** The name a type selector matches a node by. */
|
|
44
|
+
const typeOf = (node) => node.typeName ?? node.tag;
|
|
42
45
|
/**
|
|
43
46
|
* Properties CSS inherits that mean something on native.
|
|
44
47
|
*
|
|
@@ -77,6 +80,14 @@ const INHERITED = new Set([
|
|
|
77
80
|
* children do already, so neither is handed down.
|
|
78
81
|
*/
|
|
79
82
|
const INHERITED_POINTER_EVENTS = new Set(['none', 'auto']);
|
|
83
|
+
/**
|
|
84
|
+
* A shape's paint, which SVG hands down: a path under an `<svg>` with a `fill` has that fill.
|
|
85
|
+
* Inherited only in a library's drawing (`StyleTarget.namespace`). An icon's shapes are left to
|
|
86
|
+
* react-native-svg, which hands a group's paint down itself, from what the shape set as its own.
|
|
87
|
+
*/
|
|
88
|
+
const SHAPE_PAINT_INHERITED = new Set(['fill', 'stroke', 'strokeWidth']);
|
|
89
|
+
/** Whether a prop of a shape in a drawing is a paint it hands down: see `inheritFrom`. */
|
|
90
|
+
export const handsDownPaint = (key) => key === 'propList' || SHAPE_PAINT_INHERITED.has(key);
|
|
80
91
|
/**
|
|
81
92
|
* `border-style: none`, which the compiler writes as a style native does not have. On the web it
|
|
82
93
|
* makes every border width 0, whichever rule set the width, so `border-hidden border-x` draws no
|
|
@@ -210,6 +221,8 @@ export const styleStats = {
|
|
|
210
221
|
rulesFiled: 0,
|
|
211
222
|
/** Nodes asked whether a rule `:has()` was asked for could be for them: see `isFor`. */
|
|
212
223
|
subjectTests: 0,
|
|
224
|
+
/** Children whose place in a list `:has()` noted, to step from one to the next. */
|
|
225
|
+
placesIndexed: 0,
|
|
213
226
|
};
|
|
214
227
|
export function resetStyleStats() {
|
|
215
228
|
styleStats.compoundTests = 0;
|
|
@@ -217,6 +230,7 @@ export function resetStyleStats() {
|
|
|
217
230
|
styleStats.nodesResolved = 0;
|
|
218
231
|
styleStats.rulesFiled = 0;
|
|
219
232
|
styleStats.subjectTests = 0;
|
|
233
|
+
styleStats.placesIndexed = 0;
|
|
220
234
|
}
|
|
221
235
|
/** A prop's value as an attribute test compares it: in lower case, under the i flag. */
|
|
222
236
|
const attributeText = (actual, test) => test.insensitive ? String(actual).toLowerCase() : String(actual);
|
|
@@ -229,7 +243,7 @@ const attributeText = (actual, test) => test.insensitive ? String(actual).toLowe
|
|
|
229
243
|
* is the element's class list, as the attribute would spell it.
|
|
230
244
|
*/
|
|
231
245
|
function matchesAttribute(node, test) {
|
|
232
|
-
const actual = test.name === 'class' ? classAttribute(node) : node
|
|
246
|
+
const actual = test.name === 'class' ? classAttribute(node) : attributeOf(node, test.name);
|
|
233
247
|
if (actual === undefined || actual === null || actual === false)
|
|
234
248
|
return false;
|
|
235
249
|
if (test.operator === undefined)
|
|
@@ -239,6 +253,11 @@ function matchesAttribute(node, test) {
|
|
|
239
253
|
return false;
|
|
240
254
|
return matchesValue(attributeText(actual, test), test.operator, test.value);
|
|
241
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* A node's attribute as a selector reads it: the one a package keeps for it apart from its props,
|
|
258
|
+
* where it keeps one by that name, and otherwise its prop. See `Engine.keepAttribute`.
|
|
259
|
+
*/
|
|
260
|
+
export const attributeOf = (node, name) => node.keptAttributes?.[name] ?? node.props[name];
|
|
242
261
|
/** The classes of a node in the order they were set, one space between each; none is absent. */
|
|
243
262
|
const classAttribute = (node) => node.classes?.size ? [...node.classes].join(' ') : undefined;
|
|
244
263
|
/** The operators an empty value can still match by: `=` and `|=`. */
|
|
@@ -270,7 +289,7 @@ function matchesValue(value, operator, expected) {
|
|
|
270
289
|
function ownedBy(node, compound, sheet) {
|
|
271
290
|
if (compound.host !== undefined || compound.hostContext !== undefined)
|
|
272
291
|
return true;
|
|
273
|
-
return node.
|
|
292
|
+
return node.templateSheet === sheet;
|
|
274
293
|
}
|
|
275
294
|
/*
|
|
276
295
|
* Exempt from the complexity limit deliberately. This is a flat conjunction of independent
|
|
@@ -278,6 +297,20 @@ function ownedBy(node, compound, sheet) {
|
|
|
278
297
|
* same thing. It is also the hottest function in the matcher, called tens of thousands of times
|
|
279
298
|
* per commit, so splitting each guard into its own call would cost more than it reads better.
|
|
280
299
|
*/
|
|
300
|
+
/** The first character of a generated box's tag, `::before` or `::after`. */
|
|
301
|
+
const COLON = 58;
|
|
302
|
+
/**
|
|
303
|
+
* Whether any sheet in the process has drawn a generated box: until one has, no node is one, and
|
|
304
|
+
* the checks for one below cost a boolean read. Set once and left set.
|
|
305
|
+
*
|
|
306
|
+
* ponytail: never turned off again, as the checks are only a cost once it is on; count the
|
|
307
|
+
* engines with boxes if an app that drops its last library sheet needs the matching back.
|
|
308
|
+
*/
|
|
309
|
+
let boxesDrawn = false;
|
|
310
|
+
/** Note that a sheet in play draws generated boxes: see `boxesDrawn`. */
|
|
311
|
+
export function noteGeneratedBoxes() {
|
|
312
|
+
boxesDrawn = true;
|
|
313
|
+
}
|
|
281
314
|
// eslint-disable-next-line complexity
|
|
282
315
|
function matchesCompound(node, compound, sheet) {
|
|
283
316
|
styleStats.compoundTests++;
|
|
@@ -291,7 +324,14 @@ function matchesCompound(node, compound, sheet) {
|
|
|
291
324
|
return false;
|
|
292
325
|
}
|
|
293
326
|
}
|
|
294
|
-
if (compound.type !== undefined
|
|
327
|
+
if (compound.type !== undefined) {
|
|
328
|
+
// `typeOf`, asked here without the call: this is asked of every compound tested.
|
|
329
|
+
if (compound.type !== (node.typeName ?? node.tag))
|
|
330
|
+
return false;
|
|
331
|
+
// A generated box is no element to a compound that names none, `*` or a class alone, as a
|
|
332
|
+
// pseudo-element is none in a browser: one character told from any element's name.
|
|
333
|
+
}
|
|
334
|
+
else if (boxesDrawn && node.tag.charCodeAt(0) === COLON)
|
|
295
335
|
return false;
|
|
296
336
|
if (compound.own !== undefined && !ownedBy(node, compound, sheet))
|
|
297
337
|
return false;
|
|
@@ -307,7 +347,7 @@ function matchesCompound(node, compound, sheet) {
|
|
|
307
347
|
return false;
|
|
308
348
|
}
|
|
309
349
|
}
|
|
310
|
-
if (compound.empty !== undefined && node.
|
|
350
|
+
if (compound.empty !== undefined && node.childList?.some(fills))
|
|
311
351
|
return false;
|
|
312
352
|
if (compound.nth !== undefined) {
|
|
313
353
|
for (const test of compound.nth) {
|
|
@@ -315,17 +355,31 @@ function matchesCompound(node, compound, sheet) {
|
|
|
315
355
|
return false;
|
|
316
356
|
}
|
|
317
357
|
}
|
|
358
|
+
// `:not()`: the node is none of its arguments, a compound or a longer selector.
|
|
318
359
|
if (compound.not) {
|
|
319
360
|
for (const excluded of compound.not) {
|
|
320
361
|
if (matchesCompound(node, excluded, sheet))
|
|
321
362
|
return false;
|
|
322
363
|
}
|
|
364
|
+
if (compound.notComplex) {
|
|
365
|
+
for (const excluded of compound.notComplex) {
|
|
366
|
+
if (matchesComplex(node, excluded, sheet))
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
323
370
|
}
|
|
371
|
+
// `:is()` and `:where()`: the node is one of each list of alternatives.
|
|
324
372
|
if (compound.is) {
|
|
325
373
|
for (const alternatives of compound.is) {
|
|
326
374
|
if (!alternatives.some((option) => matchesCompound(node, option, sheet)))
|
|
327
375
|
return false;
|
|
328
376
|
}
|
|
377
|
+
if (compound.isComplex) {
|
|
378
|
+
for (const alternatives of compound.isComplex) {
|
|
379
|
+
if (!alternatives.some((option) => matchesComplex(node, option, sheet)))
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
329
383
|
}
|
|
330
384
|
if (compound.has) {
|
|
331
385
|
for (const test of compound.has) {
|
|
@@ -366,18 +420,207 @@ function matchesCompound(node, compound, sheet) {
|
|
|
366
420
|
}
|
|
367
421
|
return true;
|
|
368
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* `:has()` answers, by the node asked and the test, while the engine commits: a rule like
|
|
425
|
+
* `main:has(.missing) span` asks the same of `main` for each span in it, and each answer is a
|
|
426
|
+
* walk of everything in `main`. Nothing an answer hangs on changes in a commit, and a change
|
|
427
|
+
* between two drops them all: see `forgetHasAnswers`.
|
|
428
|
+
*/
|
|
429
|
+
let hasAnswers = null;
|
|
430
|
+
/**
|
|
431
|
+
* What `:has()` found of a list while the engine commits, kept and dropped with `hasAnswers`:
|
|
432
|
+
* without it, asking each of a thousand items `:has(~ .x)` looks through the list once each.
|
|
433
|
+
*/
|
|
434
|
+
let relations = null;
|
|
435
|
+
/** Keep `:has()` answers from here, or stop keeping them. */
|
|
436
|
+
export function keepHasAnswers(keep) {
|
|
437
|
+
hasAnswers = keep ? new Map() : null;
|
|
438
|
+
relations = keep ? new Relations() : null;
|
|
439
|
+
}
|
|
440
|
+
/** Drop the `:has()` answers kept, for something beneath a node changed. */
|
|
441
|
+
export function forgetHasAnswers() {
|
|
442
|
+
hasAnswers?.clear();
|
|
443
|
+
if (relations)
|
|
444
|
+
relations = new Relations();
|
|
445
|
+
}
|
|
369
446
|
/** Whether some element beneath `node`, or some child of it for `:has(> x)`, matches the test. */
|
|
370
447
|
function hasBeneath(node, test, sheet) {
|
|
371
|
-
|
|
372
|
-
|
|
448
|
+
const known = hasAnswers?.get(node)?.get(test);
|
|
449
|
+
if (known !== undefined)
|
|
450
|
+
return known;
|
|
451
|
+
const found = test.relative?.some((relative) => hasRelative(node, relative, sheet)) === true ||
|
|
452
|
+
hasCompound(node, test, sheet);
|
|
453
|
+
if (hasAnswers) {
|
|
454
|
+
let answers = hasAnswers.get(node);
|
|
455
|
+
if (!answers)
|
|
456
|
+
hasAnswers.set(node, (answers = new Map()));
|
|
457
|
+
answers.set(test, found);
|
|
458
|
+
}
|
|
459
|
+
return found;
|
|
460
|
+
}
|
|
461
|
+
function hasCompound(node, test, sheet) {
|
|
462
|
+
if (!test.any.length)
|
|
463
|
+
return false;
|
|
464
|
+
for (const child of node.childList ?? []) {
|
|
465
|
+
if (child.nodeKind !== undefined && child.nodeKind !== 'element')
|
|
373
466
|
continue;
|
|
374
467
|
if (test.any.some((option) => matchesCompound(child, option, sheet)))
|
|
375
468
|
return true;
|
|
376
|
-
if (!test.child &&
|
|
469
|
+
if (!test.child && hasCompound(child, test, sheet))
|
|
377
470
|
return true;
|
|
378
471
|
}
|
|
379
472
|
return false;
|
|
380
473
|
}
|
|
474
|
+
/** Whether a node is the last element of a selector: matched leftwards, as a rule is. */
|
|
475
|
+
function matchesComplex(node, selector, sheet) {
|
|
476
|
+
const last = selector.compounds.length - 1;
|
|
477
|
+
return (matchesCompound(node, selector.compounds[last], sheet) &&
|
|
478
|
+
matchPrefix(node, selector, last - 1, sheet));
|
|
479
|
+
}
|
|
480
|
+
const SIBLING_COMBINATORS = new Set(['next-sibling', 'later-sibling']);
|
|
481
|
+
/**
|
|
482
|
+
* Each element's place in its parent's list, found once for a `:has()` as it is asked: a list
|
|
483
|
+
* of thousands asked `:has(.a + .b)` is not searched again for each element in it to find the
|
|
484
|
+
* one before. Nothing changes while a selector is matched, so nothing is out of date.
|
|
485
|
+
*/
|
|
486
|
+
class Places {
|
|
487
|
+
lists = new Map();
|
|
488
|
+
/** Where `node` is in its parent's list, counting what is not an element. */
|
|
489
|
+
place(node) {
|
|
490
|
+
const siblings = node.parent.childList ?? [];
|
|
491
|
+
let places = this.lists.get(node.parent);
|
|
492
|
+
if (!places) {
|
|
493
|
+
places = new Map(siblings.map((child, at) => [child, at]));
|
|
494
|
+
styleStats.placesIndexed += siblings.length;
|
|
495
|
+
this.lists.set(node.parent, places);
|
|
496
|
+
}
|
|
497
|
+
return places.get(node);
|
|
498
|
+
}
|
|
499
|
+
/** The element `step` places along from `node`: before it for -1, after it for 1. */
|
|
500
|
+
element(node, step) {
|
|
501
|
+
const siblings = node.parent?.childList;
|
|
502
|
+
if (!siblings)
|
|
503
|
+
return null;
|
|
504
|
+
for (let at = this.place(node) + step; at >= 0 && at < siblings.length; at += step) {
|
|
505
|
+
if (isElement(siblings[at]))
|
|
506
|
+
return siblings[at];
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/** A `:has()` argument less its first step: `.a .b` of `~ .a .b`, asked of the `.a`. */
|
|
512
|
+
const TAILS = new WeakMap();
|
|
513
|
+
function tailOf(relative) {
|
|
514
|
+
let tail = TAILS.get(relative);
|
|
515
|
+
if (!tail) {
|
|
516
|
+
tail = { compounds: relative.compounds.slice(1), combinators: relative.combinators.slice(1) };
|
|
517
|
+
TAILS.set(relative, tail);
|
|
518
|
+
}
|
|
519
|
+
return tail;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* What a `:has()` argument that starts with `+` or `~` finds of a list: which elements after
|
|
523
|
+
* the node asked it can start from, the rest of it asked of each. That is the same whichever
|
|
524
|
+
* element before them asks, so a list is looked through once for all of them.
|
|
525
|
+
*/
|
|
526
|
+
class Relations {
|
|
527
|
+
places = new Places();
|
|
528
|
+
/** By argument and list, the place of the last element `~` can start from, or -1. */
|
|
529
|
+
lastStarts = new Map();
|
|
530
|
+
/** Whether `relative`, which starts with `+` or `~`, is true of `anchor`. */
|
|
531
|
+
after(anchor, relative, sheet) {
|
|
532
|
+
if (relative.combinators[0] === 'next-sibling') {
|
|
533
|
+
const next = this.places.element(anchor, 1);
|
|
534
|
+
return next !== null && this.startsAt(next, relative, sheet);
|
|
535
|
+
}
|
|
536
|
+
const list = anchor.parent;
|
|
537
|
+
return list !== null && this.places.place(anchor) < this.lastStart(list, relative, sheet);
|
|
538
|
+
}
|
|
539
|
+
lastStart(list, relative, sheet) {
|
|
540
|
+
let byList = this.lastStarts.get(relative);
|
|
541
|
+
if (!byList)
|
|
542
|
+
this.lastStarts.set(relative, (byList = new Map()));
|
|
543
|
+
let last = byList.get(list);
|
|
544
|
+
if (last === undefined) {
|
|
545
|
+
const siblings = list.childList ?? [];
|
|
546
|
+
last = siblings.length - 1;
|
|
547
|
+
while (last >= 0 &&
|
|
548
|
+
!(isElement(siblings[last]) && this.startsAt(siblings[last], relative, sheet))) {
|
|
549
|
+
last--;
|
|
550
|
+
}
|
|
551
|
+
byList.set(list, last);
|
|
552
|
+
}
|
|
553
|
+
return last;
|
|
554
|
+
}
|
|
555
|
+
/** Whether `node` is where the first compound of `relative` is, the rest of it asked of it. */
|
|
556
|
+
startsAt(node, relative, sheet) {
|
|
557
|
+
if (!matchesCompound(node, relative.compounds[0], sheet))
|
|
558
|
+
return false;
|
|
559
|
+
return relative.compounds.length === 1 || hasRelative(node, tailOf(relative), sheet);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Whether some element is the last of a `:has()` argument asked of `anchor`: one beneath it, or
|
|
564
|
+
* after it where the argument starts with `+` or `~`, and beneath one of those where it goes on
|
|
565
|
+
* to a child or a descendant.
|
|
566
|
+
*/
|
|
567
|
+
function hasRelative(anchor, relative, sheet) {
|
|
568
|
+
const known = relations ?? new Relations();
|
|
569
|
+
if (SIBLING_COMBINATORS.has(relative.combinators[0]))
|
|
570
|
+
return known.after(anchor, relative, sheet);
|
|
571
|
+
return someBeneath(anchor, (node) => relativeFits(node, relative, anchor, sheet, known.places));
|
|
572
|
+
}
|
|
573
|
+
/** Whether some element beneath `node` passes `test`. */
|
|
574
|
+
function someBeneath(node, test) {
|
|
575
|
+
for (const child of node.childList ?? []) {
|
|
576
|
+
if (!isElement(child))
|
|
577
|
+
continue;
|
|
578
|
+
if (test(child) || someBeneath(child, test))
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
/** Whether `node` is the last element of a `:has()` argument asked of `anchor`. */
|
|
584
|
+
function relativeFits(node, relative, anchor, sheet, places) {
|
|
585
|
+
const last = relative.compounds.length - 1;
|
|
586
|
+
return (matchesCompound(node, relative.compounds[last], sheet) &&
|
|
587
|
+
relativeLeft(node, last, { relative, anchor, sheet, places }));
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Match what is left of `relative.compounds[at]`, which `matched` is: the compound before it,
|
|
591
|
+
* or the anchor where it is the first, by the combinator between. A descendant or a later
|
|
592
|
+
* sibling tries each candidate, as `matchAny` does.
|
|
593
|
+
*/
|
|
594
|
+
function relativeLeft(matched, at, relating) {
|
|
595
|
+
const { relative, anchor, sheet, places } = relating;
|
|
596
|
+
const compound = at > 0 ? relative.compounds[at - 1] : null;
|
|
597
|
+
const fits = (node) => compound === null
|
|
598
|
+
? node === anchor
|
|
599
|
+
: matchesCompound(node, compound, sheet) && relativeLeft(node, at - 1, relating);
|
|
600
|
+
switch (relative.combinators[at]) {
|
|
601
|
+
case 'child':
|
|
602
|
+
return matched.parent !== null && fits(matched.parent);
|
|
603
|
+
case 'next-sibling': {
|
|
604
|
+
const before = places.element(matched, -1);
|
|
605
|
+
return before !== null && fits(before);
|
|
606
|
+
}
|
|
607
|
+
case 'later-sibling':
|
|
608
|
+
for (let before = places.element(matched, -1); before; before = places.element(before, -1)) {
|
|
609
|
+
if (fits(before))
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
return false;
|
|
613
|
+
default:
|
|
614
|
+
for (let up = matched.parent; up; up = up.parent) {
|
|
615
|
+
if (fits(up))
|
|
616
|
+
return true;
|
|
617
|
+
// Nothing above the anchor is beneath it.
|
|
618
|
+
if (up === anchor)
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
381
624
|
/**
|
|
382
625
|
* Right-to-left matching, as every CSS engine does it: the rightmost compound is the cheapest
|
|
383
626
|
* filter and rejects almost everything immediately.
|
|
@@ -392,7 +635,7 @@ export function matches(node, rule, sheet = null) {
|
|
|
392
635
|
// of the same name.
|
|
393
636
|
if (sheet !== null &&
|
|
394
637
|
sheet === node.hostSheet &&
|
|
395
|
-
sheet !== node.
|
|
638
|
+
sheet !== node.templateSheet &&
|
|
396
639
|
subject.host === undefined) {
|
|
397
640
|
return false;
|
|
398
641
|
}
|
|
@@ -515,7 +758,7 @@ function bitsAbove(node) {
|
|
|
515
758
|
for (const className of up.classes)
|
|
516
759
|
add(bitOf('.', className));
|
|
517
760
|
if (up.tag !== undefined)
|
|
518
|
-
add(bitOf('<', up
|
|
761
|
+
add(bitOf('<', typeOf(up)));
|
|
519
762
|
const id = up.props?.['nativeID'];
|
|
520
763
|
if (typeof id === 'string')
|
|
521
764
|
add(bitOf('#', id));
|
|
@@ -535,7 +778,15 @@ function takesParents(rule) {
|
|
|
535
778
|
return takes;
|
|
536
779
|
}
|
|
537
780
|
/** Whether a node is one of the children CSS counts. Text and anchors are children, not elements. */
|
|
538
|
-
const isElement = (node) =>
|
|
781
|
+
const isElement = (node) =>
|
|
782
|
+
// Asked of each sibling a rule counts: told from a generated box here, without a call.
|
|
783
|
+
(node.nodeKind === undefined || node.nodeKind === 'element') &&
|
|
784
|
+
!(boxesDrawn && node.tag.charCodeAt(0) === COLON);
|
|
785
|
+
/**
|
|
786
|
+
* Whether an element's name is a generated box's, `::before` or `::after`: a child the engine
|
|
787
|
+
* keeps for a rule that draws one, which no selector counts among the element's children.
|
|
788
|
+
*/
|
|
789
|
+
export const isGenerated = (name) => name.startsWith('::');
|
|
539
790
|
/** A node's first `count` elements, or its last, skipping everything that is not one. */
|
|
540
791
|
export function elementsAtEnd(siblings, count, fromEnd) {
|
|
541
792
|
const found = [];
|
|
@@ -594,12 +845,15 @@ function countSiblings(compound, reach, withinHas) {
|
|
|
594
845
|
reach.after = Infinity;
|
|
595
846
|
if (counts.some((test) => withinHas || test.fromEnd))
|
|
596
847
|
reach.before = true;
|
|
848
|
+
// `:is(.a + .b)`: what the node matches hangs on the element before it, as a rule's does.
|
|
849
|
+
for (const selector of nestedSelectors(compound)) {
|
|
850
|
+
if (!withinHas)
|
|
851
|
+
reach.after = Math.max(reach.after, stepsAlong(selector.combinators));
|
|
852
|
+
}
|
|
597
853
|
for (const inner of nestedCompounds(compound))
|
|
598
854
|
countSiblings(inner, reach, withinHas);
|
|
599
|
-
for (const
|
|
600
|
-
|
|
601
|
-
countSiblings(inner, reach, true);
|
|
602
|
-
}
|
|
855
|
+
for (const inner of hasCompounds(compound))
|
|
856
|
+
countSiblings(inner, reach, true);
|
|
603
857
|
}
|
|
604
858
|
const PLACE_ELSEWHERE = new WeakMap();
|
|
605
859
|
/**
|
|
@@ -624,7 +878,7 @@ export function placeReadElsewhere(sheet) {
|
|
|
624
878
|
// above the element styled, and its last compound is the element the rest hangs under.
|
|
625
879
|
let start = 0;
|
|
626
880
|
rule.compounds.forEach((compound, at) => {
|
|
627
|
-
placeReadAbove(compound, elsewhere);
|
|
881
|
+
placeReadAbove(compound, elsewhere, at < rule.compounds.length - 1);
|
|
628
882
|
const joined = rule.combinators[at];
|
|
629
883
|
if (joined === 'next-sibling' || joined === 'later-sibling')
|
|
630
884
|
return;
|
|
@@ -651,51 +905,123 @@ const NAMED_BESIDE = new WeakMap();
|
|
|
651
905
|
export function namedBeside(sheet) {
|
|
652
906
|
let named = NAMED_BESIDE.get(sheet);
|
|
653
907
|
if (named === undefined) {
|
|
654
|
-
|
|
655
|
-
for (const rule of sheet.rules)
|
|
656
|
-
rule
|
|
657
|
-
|
|
658
|
-
propsNamed(rule.compounds[at], named);
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
NAMED_BESIDE.set(sheet, named);
|
|
908
|
+
const found = new Set();
|
|
909
|
+
for (const rule of sheet.rules)
|
|
910
|
+
besideIn(rule, rule, found);
|
|
911
|
+
NAMED_BESIDE.set(sheet, (named = found));
|
|
663
912
|
}
|
|
664
913
|
return named;
|
|
665
914
|
}
|
|
666
|
-
/**
|
|
667
|
-
|
|
668
|
-
|
|
915
|
+
/**
|
|
916
|
+
* Add to `into` what each compound joined to a later one by `+` or `~` asks, in a selector and
|
|
917
|
+
* in each one nested in it. A `:has()` argument starts at the element it is asked of, so one
|
|
918
|
+
* with a sibling in it has every prop the rule names added.
|
|
919
|
+
*/
|
|
920
|
+
function besideIn(selector, rule, into) {
|
|
921
|
+
if (selector === null || typeof selector !== 'object')
|
|
922
|
+
return;
|
|
923
|
+
if (Array.isArray(selector))
|
|
924
|
+
return selector.forEach((inner) => besideIn(inner, rule, into));
|
|
925
|
+
const { compounds, combinators } = selector;
|
|
926
|
+
if (Array.isArray(compounds) && Array.isArray(combinators)) {
|
|
927
|
+
const relative = combinators.length === compounds.length;
|
|
928
|
+
combinators.forEach((joined, at) => {
|
|
929
|
+
if (joined !== 'next-sibling' && joined !== 'later-sibling')
|
|
930
|
+
return;
|
|
931
|
+
if (relative)
|
|
932
|
+
propsNamed(rule, into);
|
|
933
|
+
else
|
|
934
|
+
propsNamed(compounds[at], into);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
for (const [key, inner] of Object.entries(selector)) {
|
|
938
|
+
if (!DECLARED.has(key))
|
|
939
|
+
besideIn(inner, rule, into);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/** Add every prop a part of a selector asks about to `into`: each attribute, and an id. */
|
|
943
|
+
function propsNamed(part, into) {
|
|
944
|
+
if (part === null || typeof part !== 'object')
|
|
945
|
+
return;
|
|
946
|
+
if (Array.isArray(part))
|
|
947
|
+
return part.forEach((inner) => propsNamed(inner, into));
|
|
948
|
+
const { attributes, id } = part;
|
|
949
|
+
if (id !== undefined)
|
|
669
950
|
into.add('nativeID');
|
|
670
|
-
for (const test of
|
|
951
|
+
for (const test of attributes ?? [])
|
|
671
952
|
into.add(test.name);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
953
|
+
for (const [key, inner] of Object.entries(part))
|
|
954
|
+
if (!DECLARED.has(key))
|
|
955
|
+
propsNamed(inner, into);
|
|
675
956
|
}
|
|
676
957
|
/** Whether a compound, or one nested in it at any depth, asks about a place in a child list. */
|
|
677
958
|
function readsPlace(compound) {
|
|
678
959
|
if (compound.nth?.length)
|
|
679
960
|
return true;
|
|
680
|
-
|
|
681
|
-
return [...nestedCompounds(compound), ...within].some(readsPlace);
|
|
961
|
+
return [...nestedCompounds(compound), ...hasCompounds(compound)].some(readsPlace);
|
|
682
962
|
}
|
|
683
963
|
/**
|
|
684
964
|
* Collect the compounds nested in one that are matched against an element above the one it is
|
|
685
|
-
* matched against, and ask about that element's place: `:is(.group:first-child *)`.
|
|
965
|
+
* matched against, and ask about that element's place: `:is(.group:first-child *)`. `above`
|
|
966
|
+
* says the compound is itself matched against an element above the one styled.
|
|
686
967
|
*/
|
|
687
|
-
function placeReadAbove(compound, elsewhere) {
|
|
688
|
-
const
|
|
968
|
+
function placeReadAbove(compound, elsewhere, above) {
|
|
969
|
+
const over = [
|
|
689
970
|
...(compound.ancestors ?? []),
|
|
690
971
|
...(compound.parents ?? []),
|
|
691
972
|
...(compound.hostContext ?? []),
|
|
692
973
|
];
|
|
693
|
-
for (const inner of
|
|
974
|
+
for (const inner of over)
|
|
694
975
|
if (readsPlace(inner))
|
|
695
976
|
elsewhere.push({ compound: inner });
|
|
696
|
-
|
|
697
|
-
|
|
977
|
+
// `:is(.a + .b) .c` or `:not(:first-child .b)`: an element whose place changed can change what
|
|
978
|
+
// one under it matches, which is told from no class here. Only such sheets pay. Of the element
|
|
979
|
+
// styled, `.b:is(.a ~ *)` reads what is before the element alone, as a rule's own `~` does.
|
|
980
|
+
const reads = above ? beside : readFromUnder;
|
|
981
|
+
if (nestedSelectors(compound).some(reads))
|
|
982
|
+
elsewhere.push({ compound: { classes: [] } });
|
|
983
|
+
for (const inner of over)
|
|
984
|
+
placeReadAbove(inner, elsewhere, true);
|
|
985
|
+
placeReadNested(compound, elsewhere, above);
|
|
698
986
|
}
|
|
987
|
+
/**
|
|
988
|
+
* `placeReadAbove` of the compounds nested in one: those of its `:is()` and `:not()` are matched
|
|
989
|
+
* against the element it is, and those of a longer selector in one before its last against others.
|
|
990
|
+
*/
|
|
991
|
+
function placeReadNested(compound, elsewhere, above) {
|
|
992
|
+
for (const inner of [...(compound.not ?? []), ...(compound.is ?? []).flat()]) {
|
|
993
|
+
placeReadAbove(inner, elsewhere, above);
|
|
994
|
+
}
|
|
995
|
+
for (const selector of nestedSelectors(compound)) {
|
|
996
|
+
const last = selector.compounds.length - 1;
|
|
997
|
+
selector.compounds.forEach((inner, at) => placeReadAbove(inner, elsewhere, above || at < last));
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
/** Whether what a selector matches hangs on where an element is among those beside it. */
|
|
1001
|
+
const beside = (one) => one.combinators.some((each) => SIBLING_COMBINATORS.has(each)) ||
|
|
1002
|
+
(one.compounds.length > 1 && one.compounds.some(readsPlace));
|
|
1003
|
+
/**
|
|
1004
|
+
* Whether a selector reads the place of an element above the one it ends at: a run of compounds
|
|
1005
|
+
* joined by `+` and `~` before a descendant or child combinator, `.a + .b .c`, or a compound
|
|
1006
|
+
* there that asks its place, `:first-child > .c`.
|
|
1007
|
+
*/
|
|
1008
|
+
function readFromUnder(one) {
|
|
1009
|
+
let start = 0;
|
|
1010
|
+
for (let at = 0; at < one.combinators.length; at++) {
|
|
1011
|
+
if (SIBLING_COMBINATORS.has(one.combinators[at]))
|
|
1012
|
+
continue;
|
|
1013
|
+
const run = one.compounds.slice(start, at + 1);
|
|
1014
|
+
if (run.length > 1 || run.some(readsPlace))
|
|
1015
|
+
return true;
|
|
1016
|
+
start = at + 1;
|
|
1017
|
+
}
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
/** The selectors of more than a compound nested in one, in its `:is()` and its `:not()`. */
|
|
1021
|
+
const nestedSelectors = (compound) => [
|
|
1022
|
+
...(compound.notComplex ?? []),
|
|
1023
|
+
...(compound.isComplex ?? []).flat(),
|
|
1024
|
+
];
|
|
699
1025
|
/** The compounds nested in one, a `:has()`'s apart. */
|
|
700
1026
|
const nestedCompounds = (compound) => [
|
|
701
1027
|
...(compound.not ?? []),
|
|
@@ -703,10 +1029,16 @@ const nestedCompounds = (compound) => [
|
|
|
703
1029
|
...(compound.ancestors ?? []),
|
|
704
1030
|
...(compound.parents ?? []),
|
|
705
1031
|
...(compound.hostContext ?? []),
|
|
1032
|
+
...nestedSelectors(compound).flatMap((selector) => selector.compounds),
|
|
706
1033
|
];
|
|
1034
|
+
/** The compounds of a compound's `:has()` arguments. */
|
|
1035
|
+
const hasCompounds = (compound) => (compound.has ?? []).flatMap((test) => [
|
|
1036
|
+
...test.any,
|
|
1037
|
+
...(test.relative ?? []).flatMap((relative) => relative.compounds),
|
|
1038
|
+
]);
|
|
707
1039
|
/** The element before this one, skipping everything that is not one. */
|
|
708
1040
|
function previousElement(node) {
|
|
709
|
-
const siblings = node.parent?.
|
|
1041
|
+
const siblings = node.parent?.childList;
|
|
710
1042
|
if (!siblings)
|
|
711
1043
|
return null;
|
|
712
1044
|
for (let i = siblings.indexOf(node) - 1; i >= 0; i--) {
|
|
@@ -723,7 +1055,7 @@ function previousElement(node) {
|
|
|
723
1055
|
* counting, and the rest wait for a list that is actually slow.
|
|
724
1056
|
*/
|
|
725
1057
|
function matchesNth(node, test) {
|
|
726
|
-
const siblings = node.parent?.
|
|
1058
|
+
const siblings = node.parent?.childList;
|
|
727
1059
|
if (!siblings)
|
|
728
1060
|
return false;
|
|
729
1061
|
if (test.a === 0 && test.b === 1 && !test.ofType) {
|
|
@@ -795,6 +1127,148 @@ const TWIN = {
|
|
|
795
1127
|
ltr: twins(SIDES_LTR),
|
|
796
1128
|
rtl: twins(SIDES_LTR.map(([logical, physical]) => [logical, mirrored(physical)])),
|
|
797
1129
|
};
|
|
1130
|
+
/** The props of each edge of a box across and down, in each form Yoga or React Native reads. */
|
|
1131
|
+
const across = (box) => ['Left', 'Right', 'Start', 'End', 'InlineStart', 'InlineEnd'].map((side) => `${box}${side}`);
|
|
1132
|
+
const down = (box) => ['Top', 'Bottom', 'BlockStart', 'BlockEnd'].map((side) => `${box}${side}`);
|
|
1133
|
+
/** A box's shorthand for every edge, across and down, keyed by the name an inline style uses. */
|
|
1134
|
+
function boxShorthands(all, edges, axes) {
|
|
1135
|
+
return [
|
|
1136
|
+
[all, [...edges.across, ...edges.down, ...axes.across, ...axes.down]],
|
|
1137
|
+
// An axis's own name among them is skipped as one the element sets itself.
|
|
1138
|
+
...axes.across.map((axis) => [axis, [...edges.across, ...axes.across]]),
|
|
1139
|
+
...axes.down.map((axis) => [axis, [...edges.down, ...axes.down]]),
|
|
1140
|
+
];
|
|
1141
|
+
}
|
|
1142
|
+
/** Every corner, physical and logical, by the name of its radius. */
|
|
1143
|
+
const CORNERS = [
|
|
1144
|
+
...['TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'TopStart', 'TopEnd'],
|
|
1145
|
+
...['BottomStart', 'BottomEnd', 'StartStart', 'StartEnd', 'EndStart', 'EndEnd'],
|
|
1146
|
+
].map((corner) => `border${corner}Radius`);
|
|
1147
|
+
/**
|
|
1148
|
+
* The props each shorthand an inline style can set covers, in every form a native view reads
|
|
1149
|
+
* them: physical, logical and the aliases Yoga takes. A stylesheet's shorthand is compiled to its
|
|
1150
|
+
* longhands, so only an element's own style commits one as it is written, and there CSS has it
|
|
1151
|
+
* win over a rule's longhand, where Yoga and React Native read the more specific edge or corner.
|
|
1152
|
+
*/
|
|
1153
|
+
const COVERS = new Map([
|
|
1154
|
+
...['margin', 'padding'].flatMap((box) => boxShorthands(box, { across: across(box), down: down(box) }, { across: [`${box}Horizontal`, `${box}Inline`], down: [`${box}Vertical`, `${box}Block`] })),
|
|
1155
|
+
...boxShorthands('inset', {
|
|
1156
|
+
across: ['left', 'right', 'start', 'end', 'insetInlineStart', 'insetInlineEnd'],
|
|
1157
|
+
down: ['top', 'bottom', 'insetBlockStart', 'insetBlockEnd'],
|
|
1158
|
+
}, { across: ['insetInline'], down: ['insetBlock'] }),
|
|
1159
|
+
['gap', ['rowGap', 'columnGap']],
|
|
1160
|
+
['borderWidth', [...across('border'), ...down('border')].map((side) => `${side}Width`)],
|
|
1161
|
+
['borderColor', [...across('border'), ...down('border'), 'borderBlock'].map((s) => `${s}Color`)],
|
|
1162
|
+
['borderBlockColor', down('border').map((side) => `${side}Color`)],
|
|
1163
|
+
['borderRadius', CORNERS],
|
|
1164
|
+
['flex', ['flexGrow', 'flexShrink', 'flexBasis']],
|
|
1165
|
+
]);
|
|
1166
|
+
/**
|
|
1167
|
+
* The longhands Yoga reads `flex` as when none is set (`Node::resolveFlexGrow`,
|
|
1168
|
+
* `resolveFlexShrink`, `processFlexBasis`), so an inline `flex` means the same over a class's.
|
|
1169
|
+
* Null for a value that is no finite number, which covers nothing.
|
|
1170
|
+
*/
|
|
1171
|
+
function flexParts(n) {
|
|
1172
|
+
if (typeof n !== 'number' || !Number.isFinite(n))
|
|
1173
|
+
return null;
|
|
1174
|
+
return { flexGrow: n > 0 ? n : 0, flexShrink: n < 0 ? -n : 0, flexBasis: n > 0 ? 0 : 'auto' };
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Whether a value is several CSS values, `4px 8px` or `4px / 8px`, split on whitespace outside
|
|
1178
|
+
* brackets. Native takes none of them for a side, so a side keeps its own.
|
|
1179
|
+
*
|
|
1180
|
+
* ponytail: a side keeps its own rather than taking its part of the value. The renderer's
|
|
1181
|
+
* `longhands` splits `padding`, `margin`, `inset` and `gap` into their parts; the others can join it.
|
|
1182
|
+
*/
|
|
1183
|
+
function severalValues(value) {
|
|
1184
|
+
if (typeof value !== 'string')
|
|
1185
|
+
return false;
|
|
1186
|
+
let flat = value;
|
|
1187
|
+
for (let inner = flat; (inner = flat.replace(/\([^()]*\)/g, '()')) !== flat;)
|
|
1188
|
+
flat = inner;
|
|
1189
|
+
return flat.split(CSS_SPACE).filter(Boolean).length > 1;
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* What a shorthand's value gives each side it covers: undefined for the value itself, the parts
|
|
1193
|
+
* of a `flex`, or null for nothing, as for several values.
|
|
1194
|
+
*/
|
|
1195
|
+
function partsOf(shorthand, value) {
|
|
1196
|
+
if (severalValues(value))
|
|
1197
|
+
return null;
|
|
1198
|
+
return shorthand === 'flex' && value != null ? flexParts(value) : undefined;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* How late React Native applies a shorthand to an edge it shares with another
|
|
1202
|
+
* (`YogaLayoutableShadowNode`): the box's own name, then its axis, then the logical axis.
|
|
1203
|
+
*/
|
|
1204
|
+
function rankOf(shorthand) {
|
|
1205
|
+
if (/(Inline|Block)$/.test(shorthand))
|
|
1206
|
+
return 2;
|
|
1207
|
+
return /(Horizontal|Vertical|BlockColor)$/.test(shorthand) ? 1 : 0;
|
|
1208
|
+
}
|
|
1209
|
+
/** The shorthands covering each prop, the one React Native reads first ahead of the rest. */
|
|
1210
|
+
const COVERED_BY = (() => {
|
|
1211
|
+
const by = new Map();
|
|
1212
|
+
for (const [shorthand, keys] of COVERS) {
|
|
1213
|
+
for (const key of keys)
|
|
1214
|
+
by.set(key, [...(by.get(key) ?? []), shorthand]);
|
|
1215
|
+
}
|
|
1216
|
+
for (const shorthands of by.values())
|
|
1217
|
+
shorthands.sort((a, b) => rankOf(b) - rankOf(a));
|
|
1218
|
+
return by;
|
|
1219
|
+
})();
|
|
1220
|
+
/** Whether an element sets a shorthand itself, inline or bound from a token. */
|
|
1221
|
+
const setsItself = (shorthand, inline, bound) => inline[shorthand] != null || bound?.has(shorthand) === true;
|
|
1222
|
+
/** Whether an element sets any shorthand that covers a prop, so that there is anything to do. */
|
|
1223
|
+
function setsAnyShorthand(inline, bound) {
|
|
1224
|
+
for (const key in inline)
|
|
1225
|
+
if (inline[key] != null && COVERS.has(key))
|
|
1226
|
+
return true;
|
|
1227
|
+
if (bound)
|
|
1228
|
+
for (const key of bound.keys())
|
|
1229
|
+
if (COVERS.has(key))
|
|
1230
|
+
return true;
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
const UNCOVERED = Symbol('uncovered');
|
|
1234
|
+
/** What `key` takes from the first of its shorthands the element sets and that gives it one. */
|
|
1235
|
+
function coveredValue(key, shorthands, style, inline, bound) {
|
|
1236
|
+
for (const shorthand of shorthands) {
|
|
1237
|
+
if (!setsItself(shorthand, inline, bound))
|
|
1238
|
+
continue;
|
|
1239
|
+
const parts = partsOf(shorthand, style[shorthand]);
|
|
1240
|
+
if (parts !== null)
|
|
1241
|
+
return parts ? parts[key] : style[shorthand];
|
|
1242
|
+
}
|
|
1243
|
+
return UNCOVERED;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Gives each prop in `style` that a shorthand the element sets itself covers the shorthand's
|
|
1247
|
+
* value: one in its inline style, `inline`, or bound from a token, `bound`. The value rather than
|
|
1248
|
+
* nothing, so a transition of the side eases to it, as in a browser. Not a prop the element sets
|
|
1249
|
+
* as well, which Yoga reads over its shorthand as before, nor one a rule marked `!important`,
|
|
1250
|
+
* which beats the element's own declaration. Of two shorthands over one prop, the one React Native
|
|
1251
|
+
* reads: `paddingInline` over `paddingHorizontal`, whichever was written first.
|
|
1252
|
+
*
|
|
1253
|
+
* Per commit for every element with a style of its own, so it walks the props in `style`, a few,
|
|
1254
|
+
* rather than every side and corner a shorthand covers.
|
|
1255
|
+
*/
|
|
1256
|
+
export function underOwnShorthands(style, inline, bound, important) {
|
|
1257
|
+
if (!setsAnyShorthand(inline, bound))
|
|
1258
|
+
return;
|
|
1259
|
+
for (const key in style) {
|
|
1260
|
+
const shorthands = COVERED_BY.get(key);
|
|
1261
|
+
if (!shorthands || key in inline || bound?.has(key) || important?.has(key))
|
|
1262
|
+
continue;
|
|
1263
|
+
const next = coveredValue(key, shorthands, style, inline, bound);
|
|
1264
|
+
if (next === UNCOVERED)
|
|
1265
|
+
continue;
|
|
1266
|
+
if (next == null)
|
|
1267
|
+
delete style[key];
|
|
1268
|
+
else
|
|
1269
|
+
style[key] = next;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
798
1272
|
const sidedOf = new WeakMap();
|
|
799
1273
|
function sided(rule) {
|
|
800
1274
|
let found = sidedOf.get(rule);
|
|
@@ -951,7 +1425,7 @@ function countPosition(node, siblings, fromEnd, ofType) {
|
|
|
951
1425
|
let position = 0;
|
|
952
1426
|
let seen = 0;
|
|
953
1427
|
for (const sibling of siblings) {
|
|
954
|
-
if (!isElement(sibling) || (ofType && sibling
|
|
1428
|
+
if (!isElement(sibling) || (ofType && typeOf(sibling) !== typeOf(node)))
|
|
955
1429
|
continue;
|
|
956
1430
|
seen++;
|
|
957
1431
|
if (sibling === node)
|
|
@@ -1028,8 +1502,11 @@ export function ruleKey(rule) {
|
|
|
1028
1502
|
* `:is()` of compounds that each have a key: it is filed under each of theirs, since a node it
|
|
1029
1503
|
* matches has one of them.
|
|
1030
1504
|
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1505
|
+
* Two kinds of rule say nothing of the node's own name and still say whose child it is: one for
|
|
1506
|
+
* a generated box, `.card::before`, which is `.card > ::before` here, and one for any child,
|
|
1507
|
+
* `.row > *`. Each is filed under the parent's class, and offered to the children of an element
|
|
1508
|
+
* that has it. Filed by the box's own name, every `::before` rule there is would be offered to
|
|
1509
|
+
* every such box.
|
|
1033
1510
|
*/
|
|
1034
1511
|
export function ruleKeys(rule) {
|
|
1035
1512
|
const key = rule.compounds[rule.compounds.length - 1];
|
|
@@ -1039,9 +1516,10 @@ export function ruleKeys(rule) {
|
|
|
1039
1516
|
return [`class:${key.classes[0]}`];
|
|
1040
1517
|
if (key.id !== undefined)
|
|
1041
1518
|
return [`id:${key.id}`];
|
|
1042
|
-
if (key.type !== undefined)
|
|
1043
|
-
return [`type:${key.type}`];
|
|
1044
1519
|
const held = parentClass(rule, key);
|
|
1520
|
+
if (key.type !== undefined && !(held !== undefined && isGenerated(key.type))) {
|
|
1521
|
+
return [`type:${key.type}`];
|
|
1522
|
+
}
|
|
1045
1523
|
if (held !== undefined)
|
|
1046
1524
|
return [`in:${held}`];
|
|
1047
1525
|
return alternativeKeys(key) ?? attributeKey(key) ?? ancestorKeys(rule, key) ?? EVERY_NODE;
|
|
@@ -1148,7 +1626,7 @@ function reach(node, index, found) {
|
|
|
1148
1626
|
};
|
|
1149
1627
|
if (index.universal.length)
|
|
1150
1628
|
found.push(...index.universal);
|
|
1151
|
-
take(`type:${node.tag}`);
|
|
1629
|
+
take(`type:${node.typeName ?? node.tag}`);
|
|
1152
1630
|
const id = node.props['nativeID'];
|
|
1153
1631
|
if (typeof id === 'string')
|
|
1154
1632
|
take(`id:${id}`);
|
|
@@ -1175,12 +1653,20 @@ function reachUnder(node, take) {
|
|
|
1175
1653
|
}
|
|
1176
1654
|
/** The buckets of the attributes a node has: a prop of its own that is set. */
|
|
1177
1655
|
function reachByAttribute(node, take) {
|
|
1656
|
+
const kept = node.keptAttributes;
|
|
1178
1657
|
for (const name in node.props) {
|
|
1179
1658
|
const value = node.props[name];
|
|
1180
1659
|
// As `matchesAttribute` has it: a prop that is false is an attribute that is not there.
|
|
1181
1660
|
if (value !== undefined && value !== null && value !== false)
|
|
1182
1661
|
take(`attr:${name}`);
|
|
1662
|
+
else if (kept?.[name] !== undefined)
|
|
1663
|
+
take(`attr:${name}`);
|
|
1183
1664
|
}
|
|
1665
|
+
// One kept by no prop's name: a prop's is taken above.
|
|
1666
|
+
if (kept)
|
|
1667
|
+
for (const name in kept)
|
|
1668
|
+
if (!(name in node.props))
|
|
1669
|
+
take(`attr:${name}`);
|
|
1184
1670
|
}
|
|
1185
1671
|
/**
|
|
1186
1672
|
* The rules worth trying against a node, in cascade order: `order` says which of two comes
|
|
@@ -1263,21 +1749,33 @@ function subjectsOf(sheet) {
|
|
|
1263
1749
|
const HAS_ABOVE = new WeakMap();
|
|
1264
1750
|
/** Whether a compound, or one it asks of the same node, uses `:has()`. */
|
|
1265
1751
|
const asksBeneath = (compound) => compound.has !== undefined ||
|
|
1266
|
-
[
|
|
1752
|
+
[
|
|
1753
|
+
...(compound.not ?? []),
|
|
1754
|
+
...(compound.is ?? []).flat(),
|
|
1755
|
+
...nestedSelectors(compound).map((selector) => selector.compounds.at(-1)),
|
|
1756
|
+
].some(asksBeneath);
|
|
1267
1757
|
/**
|
|
1268
|
-
* The boxes
|
|
1269
|
-
* where that is not the styled one,
|
|
1270
|
-
* `.body:is(.card:has(.x) *) .title
|
|
1758
|
+
* The boxes other than the styled node that a compound asks `:has()` of: the compound's own
|
|
1759
|
+
* node, where that is not the styled one, each box it is asked to be inside,
|
|
1760
|
+
* `.body:is(.card:has(.x) *) .title`, and each element an `:is()` or `:not()` of more than a
|
|
1761
|
+
* compound asks about, which can be before it as well as above it.
|
|
1271
1762
|
*/
|
|
1272
|
-
function* boxesAsked(compound, styled) {
|
|
1763
|
+
function* boxesAsked(compound, styled, beside) {
|
|
1273
1764
|
if (!styled && asksBeneath(compound))
|
|
1274
|
-
yield compound;
|
|
1765
|
+
yield { box: compound, beside };
|
|
1275
1766
|
for (const key of ['ancestors', 'parents', 'hostContext']) {
|
|
1276
1767
|
for (const outer of compound[key] ?? [])
|
|
1277
|
-
yield* boxesAsked(outer, false);
|
|
1768
|
+
yield* boxesAsked(outer, false, beside);
|
|
1769
|
+
}
|
|
1770
|
+
for (const selector of nestedSelectors(compound)) {
|
|
1771
|
+
const last = selector.compounds.length - 1;
|
|
1772
|
+
// The last is the compound's own node, which `asksBeneath` has asked of already.
|
|
1773
|
+
for (const [at, inner] of selector.compounds.entries()) {
|
|
1774
|
+
yield* boxesAsked(inner, at === last, at === last ? beside : true);
|
|
1775
|
+
}
|
|
1278
1776
|
}
|
|
1279
1777
|
}
|
|
1280
|
-
/** Each box a sheet's rules ask `:has()` of
|
|
1778
|
+
/** Each box a sheet's rules ask `:has()` of other than the node they style. */
|
|
1281
1779
|
export function hasAbove(sheet) {
|
|
1282
1780
|
let known = HAS_ABOVE.get(sheet);
|
|
1283
1781
|
if (known === undefined) {
|
|
@@ -1285,10 +1783,16 @@ export function hasAbove(sheet) {
|
|
|
1285
1783
|
for (const rule of sheet.rules) {
|
|
1286
1784
|
const last = rule.compounds.length - 1;
|
|
1287
1785
|
for (const [at, compound] of rule.compounds.entries()) {
|
|
1288
|
-
|
|
1786
|
+
const before = rule.combinators.slice(at).some((one) => SIBLING_COMBINATORS.has(one));
|
|
1787
|
+
for (const { box, beside } of boxesAsked(compound, at === last, before)) {
|
|
1289
1788
|
const subjects = noSubjects();
|
|
1290
1789
|
noteSubject(subjects, rule);
|
|
1291
|
-
found.push({
|
|
1790
|
+
found.push({
|
|
1791
|
+
classes: classesOf(box),
|
|
1792
|
+
...(box.type !== undefined ? { type: box.type } : {}),
|
|
1793
|
+
subjects,
|
|
1794
|
+
...(beside ? { beside: true } : {}),
|
|
1795
|
+
});
|
|
1292
1796
|
}
|
|
1293
1797
|
}
|
|
1294
1798
|
}
|
|
@@ -1296,6 +1800,58 @@ export function hasAbove(sheet) {
|
|
|
1296
1800
|
}
|
|
1297
1801
|
return known;
|
|
1298
1802
|
}
|
|
1803
|
+
/** Whether `node` can be the box a `:has()` rule asks: it has the classes and is the element. */
|
|
1804
|
+
export function couldAsk(reader, node) {
|
|
1805
|
+
if (reader.type !== undefined && reader.type !== typeOf(node))
|
|
1806
|
+
return false;
|
|
1807
|
+
return reader.classes.every((name) => node.classes?.has(name));
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* The classes a node needs to match a compound: its own, and those every argument of one of its
|
|
1811
|
+
* `:is()` or `:where()` lists names, `:where(.group)` as Tailwind's `group-has-*` writes it.
|
|
1812
|
+
*/
|
|
1813
|
+
function classesOf(compound) {
|
|
1814
|
+
if (!compound.is && !compound.isComplex)
|
|
1815
|
+
return compound.classes;
|
|
1816
|
+
const all = new Set(compound.classes);
|
|
1817
|
+
const lists = [
|
|
1818
|
+
...(compound.is ?? []),
|
|
1819
|
+
...(compound.isComplex ?? []).map((list) => list.map((selector) => selector.compounds.at(-1))),
|
|
1820
|
+
];
|
|
1821
|
+
for (const list of lists) {
|
|
1822
|
+
const [first, ...rest] = list.map(classesOf);
|
|
1823
|
+
for (const name of first ?? [])
|
|
1824
|
+
if (rest.every((other) => other.includes(name)))
|
|
1825
|
+
all.add(name);
|
|
1826
|
+
}
|
|
1827
|
+
return [...all];
|
|
1828
|
+
}
|
|
1829
|
+
/** Each compound in one, and each nested in it at any depth, `:has()` arguments among them. */
|
|
1830
|
+
function* everyCompound(compound) {
|
|
1831
|
+
yield compound;
|
|
1832
|
+
for (const inner of [...nestedCompounds(compound), ...hasCompounds(compound)]) {
|
|
1833
|
+
yield* everyCompound(inner);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
const HAS_BESIDE = new WeakMap();
|
|
1837
|
+
/**
|
|
1838
|
+
* Whether a sheet asks `:has()` about what comes after a node, `:has(+ .x)` or `:has(~ .x .y)`:
|
|
1839
|
+
* then a change can change what the elements before it match, and those before what it is in.
|
|
1840
|
+
*/
|
|
1841
|
+
export function hasBeside(sheet) {
|
|
1842
|
+
let known = HAS_BESIDE.get(sheet);
|
|
1843
|
+
if (known === undefined) {
|
|
1844
|
+
const after = (test) => test.relative?.some((one) => SIBLING_COMBINATORS.has(one.combinators[0])) === true;
|
|
1845
|
+
known = sheet.rules.some((rule) => rule.compounds.some((outer) => {
|
|
1846
|
+
for (const compound of everyCompound(outer))
|
|
1847
|
+
if (compound.has?.some(after))
|
|
1848
|
+
return true;
|
|
1849
|
+
return false;
|
|
1850
|
+
}));
|
|
1851
|
+
HAS_BESIDE.set(sheet, known);
|
|
1852
|
+
}
|
|
1853
|
+
return known;
|
|
1854
|
+
}
|
|
1299
1855
|
/** Note in `into` what a rule is for. */
|
|
1300
1856
|
function noteSubject(into, rule) {
|
|
1301
1857
|
const subject = rule.compounds.at(-1);
|
|
@@ -1420,7 +1976,7 @@ export function joinSubjects(each) {
|
|
|
1420
1976
|
* nothing but `>` and a space between there and the element, and those of what `:is(.box *)`
|
|
1421
1977
|
* and `:is(.box > *)` put over it;
|
|
1422
1978
|
* - `beneath`, inside `:has()`, wherever in the rule that is: what it asks of is under the box
|
|
1423
|
-
* that answers it;
|
|
1979
|
+
* that answers it, or after it;
|
|
1424
1980
|
* - `parentHas`, of what `:has(> )` asks of the box's children themselves, and nowhere else in
|
|
1425
1981
|
* `:has()`: the element is right under that box.
|
|
1426
1982
|
*
|
|
@@ -1481,16 +2037,21 @@ function placeClasses(compound, at, inside, below) {
|
|
|
1481
2037
|
collectClasses(rest, below.elsewhere, []);
|
|
1482
2038
|
}
|
|
1483
2039
|
/**
|
|
1484
|
-
* The classes `:has()` asks of what is under the box that answers it, into
|
|
1485
|
-
* in the rule it is written, or into `parentHas` for what `:has(> )` asks of
|
|
1486
|
-
* One it can ask of what is above the box goes `elsewhere`: `:is()` in
|
|
1487
|
-
* so the `.night` of `:has(:is(.night .cell))` can be any element over
|
|
2040
|
+
* The classes `:has()` asks of what is under the box that answers it or after it, into
|
|
2041
|
+
* `beneath`, wherever in the rule it is written, or into `parentHas` for what `:has(> )` asks of
|
|
2042
|
+
* the box's children. One it can ask of what is above the box goes `elsewhere`: `:is()` in
|
|
2043
|
+
* `:has()` is not relative, so the `.night` of `:has(:is(.night .cell))` can be any element over
|
|
2044
|
+
* the cell.
|
|
1488
2045
|
*/
|
|
1489
2046
|
function placeHas(tests, below) {
|
|
1490
2047
|
for (const test of tests ?? []) {
|
|
1491
2048
|
const into = test.child ? below.parentHas : below.beneath;
|
|
1492
2049
|
for (const compound of test.any)
|
|
1493
2050
|
placeBeneath(compound, into, below);
|
|
2051
|
+
for (const one of test.relative ?? []) {
|
|
2052
|
+
for (const compound of one.compounds)
|
|
2053
|
+
placeBeneath(compound, below.beneath, below);
|
|
2054
|
+
}
|
|
1494
2055
|
}
|
|
1495
2056
|
}
|
|
1496
2057
|
/** `placeHas` for one compound of a `:has()` argument, whose own classes go `into`. */
|
|
@@ -1552,15 +2113,29 @@ function collectClasses(value, into, reads, named) {
|
|
|
1552
2113
|
* One class of the list where the test is for a whole class or for part of one; any class
|
|
1553
2114
|
* where it is for how the list starts or ends, or for a part with a space in it.
|
|
1554
2115
|
*/
|
|
1555
|
-
function answers(test, name) {
|
|
2116
|
+
function answers(test, name, attribute) {
|
|
1556
2117
|
const value = test.value ?? '';
|
|
1557
2118
|
const folded = test.insensitive ? name.toLowerCase() : name;
|
|
1558
2119
|
if (test.operator === 'includes')
|
|
1559
2120
|
return folded === value;
|
|
1560
2121
|
if (test.operator === 'substring' && !/\s/.test(value))
|
|
1561
2122
|
return folded.includes(value);
|
|
2123
|
+
// `[class^="icon-"]` reads the attribute as a whole: told it, before and after, a change that
|
|
2124
|
+
// leaves its answer as it was answers nothing new.
|
|
2125
|
+
if (attribute)
|
|
2126
|
+
return classHolds(test, attribute.was) !== classHolds(test, attribute.now);
|
|
1562
2127
|
return true;
|
|
1563
2128
|
}
|
|
2129
|
+
/** Whether a test of the `class` attribute holds for it written as `value`. */
|
|
2130
|
+
function classHolds(test, value) {
|
|
2131
|
+
if (value === undefined)
|
|
2132
|
+
return false;
|
|
2133
|
+
if (test.operator === undefined)
|
|
2134
|
+
return true;
|
|
2135
|
+
if (test.value === '' && !EMPTY_MATCHES.has(test.operator))
|
|
2136
|
+
return false;
|
|
2137
|
+
return matchesValue(attributeText(value, test), test.operator, test.value);
|
|
2138
|
+
}
|
|
1564
2139
|
/**
|
|
1565
2140
|
* A class of the nearest box a rule's subject has to be in: the compound before it, where that
|
|
1566
2141
|
* is joined as what holds it and names a class. Nothing where it is a sibling, or has no class.
|
|
@@ -1573,7 +2148,7 @@ function boxAround(rule) {
|
|
|
1573
2148
|
}
|
|
1574
2149
|
/** Whether a rule among these could be for a node: never no where one is. */
|
|
1575
2150
|
function writtenFor(of, node) {
|
|
1576
|
-
if (of.any || of.types.has(node
|
|
2151
|
+
if (of.any || of.types.has(typeOf(node)))
|
|
1577
2152
|
return true;
|
|
1578
2153
|
for (const name of node.classes ?? [])
|
|
1579
2154
|
if (of.classes.has(name))
|
|
@@ -1647,10 +2222,11 @@ function unstyledStyle(inherited, inline) {
|
|
|
1647
2222
|
* node set itself. Copied on first write only, so a node that sets none hands the same object on
|
|
1648
2223
|
* and a whole subtree can share it.
|
|
1649
2224
|
*/
|
|
1650
|
-
function inheritFrom(parentInherited, own) {
|
|
1651
|
-
|
|
2225
|
+
function inheritFrom(parentInherited, own, node) {
|
|
2226
|
+
const drawing = node?.namespace !== undefined;
|
|
2227
|
+
let inherited = drawing ? ownPaint(node, parentInherited, own) : parentInherited;
|
|
1652
2228
|
for (const key of Object.keys(own)) {
|
|
1653
|
-
if (!INHERITED.has(key))
|
|
2229
|
+
if (!(INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))))
|
|
1654
2230
|
continue;
|
|
1655
2231
|
// `pointer-events: inherit`, which only an inline style on an element no rule matches still
|
|
1656
2232
|
// holds here: what the parent hands down stands.
|
|
@@ -1667,6 +2243,34 @@ function inheritFrom(parentInherited, own) {
|
|
|
1667
2243
|
}
|
|
1668
2244
|
return inherited;
|
|
1669
2245
|
}
|
|
2246
|
+
/** What a node no rule matches hands down: its inline style's, or a drawing's own paint. */
|
|
2247
|
+
function handedOn(node, parentInherited, inline, drawing) {
|
|
2248
|
+
return inline || drawing ? inheritFrom(parentInherited, inline ?? {}, node) : parentInherited;
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* What a shape of a drawing hands down of the paint it set itself, as an attribute its
|
|
2252
|
+
* `propList` names, over a paint it inherits: a `<g stroke="none">` under a stroked `<svg>`
|
|
2253
|
+
* strokes nothing inside it, as in a browser. A style of its own stands over either.
|
|
2254
|
+
*/
|
|
2255
|
+
function ownPaint(node, parentInherited, own) {
|
|
2256
|
+
const named = node.props['propList'];
|
|
2257
|
+
if (!Array.isArray(named))
|
|
2258
|
+
return parentInherited;
|
|
2259
|
+
let inherited = parentInherited;
|
|
2260
|
+
for (const key of named) {
|
|
2261
|
+
if (!SHAPE_PAINT_INHERITED.has(key) || key in own)
|
|
2262
|
+
continue;
|
|
2263
|
+
const value = node.props[key];
|
|
2264
|
+
// A paint named and not set is `none`, as react-native-svg reads a list naming it.
|
|
2265
|
+
const paint = value ?? (key === 'strokeWidth' ? undefined : 'none');
|
|
2266
|
+
if (paint === undefined || inherited[key] === paint)
|
|
2267
|
+
continue;
|
|
2268
|
+
if (inherited === parentInherited)
|
|
2269
|
+
inherited = { ...parentInherited };
|
|
2270
|
+
inherited[key] = paint;
|
|
2271
|
+
}
|
|
2272
|
+
return inherited;
|
|
2273
|
+
}
|
|
1670
2274
|
/**
|
|
1671
2275
|
* A text decoration's colour, which travels with its line rather than inheriting on its own. A
|
|
1672
2276
|
* decoration is drawn under the text inside the element that declares it, in that element's
|
|
@@ -1694,14 +2298,18 @@ function decorate(style, inherited, own) {
|
|
|
1694
2298
|
function drawsLine(line) {
|
|
1695
2299
|
return line !== undefined && line !== 'none';
|
|
1696
2300
|
}
|
|
1697
|
-
/**
|
|
1698
|
-
|
|
2301
|
+
/**
|
|
2302
|
+
* Whether an inline style sets a property children inherit, by a node of a library's drawing or by
|
|
2303
|
+
* any other. Allocates nothing, and calls nothing a key: it runs per set.
|
|
2304
|
+
*/
|
|
2305
|
+
export function setsInherited(style, drawing = false) {
|
|
1699
2306
|
if (!style || typeof style !== 'object')
|
|
1700
2307
|
return false;
|
|
1701
2308
|
if (Array.isArray(style))
|
|
1702
|
-
return style.some(setsInherited);
|
|
2309
|
+
return style.some((entry) => setsInherited(entry, drawing));
|
|
1703
2310
|
for (const key in style) {
|
|
1704
|
-
if (INHERITED.has(key) &&
|
|
2311
|
+
if ((INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))) &&
|
|
2312
|
+
style[key] != null)
|
|
1705
2313
|
return true;
|
|
1706
2314
|
}
|
|
1707
2315
|
return false;
|
|
@@ -1711,11 +2319,13 @@ export function setsInherited(style) {
|
|
|
1711
2319
|
* strongest normal declaration on its element, so what it sets is inherited, and read by
|
|
1712
2320
|
* `color: inherit` and `currentColor`, as a rule's would be.
|
|
1713
2321
|
*/
|
|
1714
|
-
export function inlineInherited(style) {
|
|
2322
|
+
export function inlineInherited(style, drawing = false) {
|
|
1715
2323
|
let out = null;
|
|
1716
2324
|
// Flattened first, as native applies it, so a later entry's null clears an earlier value.
|
|
1717
2325
|
for (const [key, value] of Object.entries(flattenInline(style, {}))) {
|
|
1718
|
-
if (INHERITED.has(key)
|
|
2326
|
+
if ((INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))) &&
|
|
2327
|
+
value !== undefined &&
|
|
2328
|
+
value !== null)
|
|
1719
2329
|
(out ??= {})[key] = value;
|
|
1720
2330
|
}
|
|
1721
2331
|
return out;
|
|
@@ -1742,7 +2352,7 @@ function flattenInline(style, into) {
|
|
|
1742
2352
|
*
|
|
1743
2353
|
* Correctness first. Walking up meant matching every ancestor against the *starting node's*
|
|
1744
2354
|
* sheet, but an ancestor almost always belongs to a different component, so a component's rules
|
|
1745
|
-
* reached nodes it did not own while a parent's real values never arrived. Reading `
|
|
2355
|
+
* reached nodes it did not own while a parent's real values never arrived. Reading `templateSheet` off
|
|
1746
2356
|
* each node fixes both directions at once.
|
|
1747
2357
|
*
|
|
1748
2358
|
* Cost second. Walking up re-derived, for every node, everything its parent had already worked
|
|
@@ -1862,9 +2472,9 @@ export class StyleResolver {
|
|
|
1862
2472
|
* them could match. Emptied by anything else that moves the generation on.
|
|
1863
2473
|
*/
|
|
1864
2474
|
additions = [];
|
|
1865
|
-
/**
|
|
1866
|
-
|
|
1867
|
-
return
|
|
2475
|
+
/** What the rules of several sheets are for, as one: see `isFor`. */
|
|
2476
|
+
subjectsOfAll(sheets) {
|
|
2477
|
+
return sheets.length === 1 ? subjectsOf(sheets[0]) : joinSubjects(sheets.map(subjectsOf));
|
|
1868
2478
|
}
|
|
1869
2479
|
/** How far each class named by the sheets met so far reaches: see `classReach`. */
|
|
1870
2480
|
reach = new Map();
|
|
@@ -1877,6 +2487,8 @@ export class StyleResolver {
|
|
|
1877
2487
|
*/
|
|
1878
2488
|
deepSheets = new Set();
|
|
1879
2489
|
deepIndexes = new WeakMap();
|
|
2490
|
+
/** Each component sheet's rules for a box of its host: see `hostBoxIndex`. */
|
|
2491
|
+
hostBoxIndexes = new WeakMap();
|
|
1880
2492
|
/**
|
|
1881
2493
|
* Read the classes a sheet names, once, as soon as any node is given it. One `scoped` to a
|
|
1882
2494
|
* component or a host asks of its elements for good, global or not.
|
|
@@ -1962,10 +2574,10 @@ export class StyleResolver {
|
|
|
1962
2574
|
* elements under it to match again, by what rules are for; or null, where no rule names any
|
|
1963
2575
|
* of them and nothing is styled again.
|
|
1964
2576
|
*/
|
|
1965
|
-
reachOf(classes) {
|
|
2577
|
+
reachOf(classes, attribute) {
|
|
1966
2578
|
let found = null;
|
|
1967
2579
|
for (const name of classes) {
|
|
1968
|
-
for (const how of this.reachesOf(name)) {
|
|
2580
|
+
for (const how of this.reachesOf(name, attribute)) {
|
|
1969
2581
|
if (how === true)
|
|
1970
2582
|
return true;
|
|
1971
2583
|
addSubjects((found ??= noSubjects()), how);
|
|
@@ -1974,15 +2586,19 @@ export class StyleResolver {
|
|
|
1974
2586
|
return found;
|
|
1975
2587
|
}
|
|
1976
2588
|
/** How far a class reaches by its name, and by each test of the class attribute it answers. */
|
|
1977
|
-
*reachesOf(name) {
|
|
2589
|
+
*reachesOf(name, attribute) {
|
|
1978
2590
|
const how = this.reach.get(name);
|
|
1979
2591
|
if (how)
|
|
1980
2592
|
yield how;
|
|
1981
2593
|
for (const [key, test] of this.classTests) {
|
|
1982
|
-
if (answers(test, name))
|
|
2594
|
+
if (answers(test, name, attribute))
|
|
1983
2595
|
yield this.reach.get(key) ?? true;
|
|
1984
2596
|
}
|
|
1985
2597
|
}
|
|
2598
|
+
/** Whether a sheet tests the `class` attribute as a whole: see `ClassChange`. */
|
|
2599
|
+
get testsClassAttribute() {
|
|
2600
|
+
return this.classTests.size > 0;
|
|
2601
|
+
}
|
|
1986
2602
|
/** Whether one of the rules `subjects` stands for could be for a node. */
|
|
1987
2603
|
isFor(subjects, node) {
|
|
1988
2604
|
styleStats.subjectTests++;
|
|
@@ -2129,9 +2745,9 @@ export class StyleResolver {
|
|
|
2129
2745
|
return cache;
|
|
2130
2746
|
}
|
|
2131
2747
|
/**
|
|
2132
|
-
* Set by the engine
|
|
2133
|
-
* in a child list.
|
|
2134
|
-
* pays nothing for it.
|
|
2748
|
+
* Set by the engine while a sheet in play uses `:has()` or styles a pressed element, and once
|
|
2749
|
+
* one asks about a place in a child list. Otherwise no cache keeps the rules it matched, so an
|
|
2750
|
+
* app that never asks pays nothing for it.
|
|
2135
2751
|
*/
|
|
2136
2752
|
tracksHas = false;
|
|
2137
2753
|
/**
|
|
@@ -2228,8 +2844,10 @@ export class StyleResolver {
|
|
|
2228
2844
|
* is whatever it inherits.
|
|
2229
2845
|
*/
|
|
2230
2846
|
hasNoRules(node) {
|
|
2231
|
-
return (node.
|
|
2847
|
+
return (node.templateSheet === null &&
|
|
2232
2848
|
node.hostSheet === null &&
|
|
2849
|
+
// A generated box matches the `:host::before` of the component its element hosts.
|
|
2850
|
+
!(boxesDrawn && node.tag.charCodeAt(0) === COLON && node.parent?.hostSheet) &&
|
|
2233
2851
|
this.deepSheets.size === 0 &&
|
|
2234
2852
|
this.globalSheet === null &&
|
|
2235
2853
|
this.addedSheets.length === 0 &&
|
|
@@ -2250,7 +2868,8 @@ export class StyleResolver {
|
|
|
2250
2868
|
const tokens = parent ? parent.tokens : this.tokensOnRoot;
|
|
2251
2869
|
const parentContext = parent ? parent.context : ROOT_CONTEXT;
|
|
2252
2870
|
// What inline style sets is inherited too, with no rule to read it against.
|
|
2253
|
-
const
|
|
2871
|
+
const drawing = node.namespace !== undefined;
|
|
2872
|
+
const inline = node.inlineInherits ? inlineInherited(node.props['style'], drawing) : null;
|
|
2254
2873
|
const nothing = !inline &&
|
|
2255
2874
|
parentInherited === EMPTY &&
|
|
2256
2875
|
parentContext === ROOT_CONTEXT &&
|
|
@@ -2266,7 +2885,7 @@ export class StyleResolver {
|
|
|
2266
2885
|
scope: scope ?? context,
|
|
2267
2886
|
parentScope,
|
|
2268
2887
|
style: unstyledStyle(parentInherited, inline),
|
|
2269
|
-
inherited:
|
|
2888
|
+
inherited: handedOn(node, parentInherited, inline, drawing),
|
|
2270
2889
|
tokens,
|
|
2271
2890
|
};
|
|
2272
2891
|
node.styleCache = passthrough;
|
|
@@ -2309,8 +2928,13 @@ export class StyleResolver {
|
|
|
2309
2928
|
const indexes = [];
|
|
2310
2929
|
if (node.hostSheet)
|
|
2311
2930
|
indexes.push(this.ownIndex(node.hostSheet, HOSTS));
|
|
2312
|
-
|
|
2313
|
-
|
|
2931
|
+
// A generated box is tried against the rules of the component its element hosts too, where
|
|
2932
|
+
// `:host::before` is written: it is no host itself, so a bare `:host` rule still misses it.
|
|
2933
|
+
const host = boxesDrawn && node.tag.charCodeAt(0) === COLON ? node.parent?.hostSheet : undefined;
|
|
2934
|
+
if (host)
|
|
2935
|
+
indexes.push(this.hostBoxIndex(host));
|
|
2936
|
+
if (node.templateSheet)
|
|
2937
|
+
indexes.push(this.ownIndex(node.templateSheet, CREATED));
|
|
2314
2938
|
if (this.deepSheets.size)
|
|
2315
2939
|
this.reachedFromAbove(node, indexes);
|
|
2316
2940
|
indexes.push(this.sharedIndex());
|
|
@@ -2323,12 +2947,26 @@ export class StyleResolver {
|
|
|
2323
2947
|
reachedFromAbove(node, indexes) {
|
|
2324
2948
|
for (let up = node.parent; up; up = up.parent) {
|
|
2325
2949
|
const sheet = up.hostSheet;
|
|
2326
|
-
if (!sheet || sheet === node.
|
|
2950
|
+
if (!sheet || sheet === node.templateSheet || sheet === node.hostSheet)
|
|
2327
2951
|
continue;
|
|
2328
2952
|
if (this.deepSheets.has(sheet))
|
|
2329
2953
|
indexes.push(this.deepIndex(sheet));
|
|
2330
2954
|
}
|
|
2331
2955
|
}
|
|
2956
|
+
/**
|
|
2957
|
+
* A sheet's rules for a box of its host alone, `:host::before` and `:host(.x)::after`, filed as
|
|
2958
|
+
* a component's own are. Its other rules for a box are for the elements of its template, which
|
|
2959
|
+
* is how emulated encapsulation scopes them on the web: `.icon::before` in it never reaches the
|
|
2960
|
+
* box of a host its parent puts the class `icon` on.
|
|
2961
|
+
*/
|
|
2962
|
+
hostBoxIndex(sheet) {
|
|
2963
|
+
let index = this.hostBoxIndexes.get(sheet);
|
|
2964
|
+
if (!index) {
|
|
2965
|
+
const boxes = this.entries(sheet, COMPONENT_SPECIFICITY_BUMP).filter(({ rule }) => rule.compounds.at(-2)?.host !== undefined);
|
|
2966
|
+
this.hostBoxIndexes.set(sheet, (index = indexRules(boxes, undefined, HOSTS * SHEET_ROOM)));
|
|
2967
|
+
}
|
|
2968
|
+
return index;
|
|
2969
|
+
}
|
|
2332
2970
|
/** A sheet's `::ng-deep` rules alone, filed as a component's own are. */
|
|
2333
2971
|
deepIndex(sheet) {
|
|
2334
2972
|
let index = this.deepIndexes.get(sheet);
|
|
@@ -2413,9 +3051,15 @@ export class StyleResolver {
|
|
|
2413
3051
|
*/
|
|
2414
3052
|
styled(node, matched, parentTokens, parentInherited) {
|
|
2415
3053
|
// Shared between nodes that match alike, unless the node sets something of its own.
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
3054
|
+
const drawing = node.namespace !== undefined;
|
|
3055
|
+
const inline = node.inlineInherits ? inlineInherited(node.props['style'], drawing) : null;
|
|
3056
|
+
// Nor one that takes a value from its parent's own style, which two parents differ in, nor
|
|
3057
|
+
// a node of a drawing, which hands down paint another node with its rules does not.
|
|
3058
|
+
if (node.customProperties ||
|
|
3059
|
+
inline ||
|
|
3060
|
+
node.boundStyle ||
|
|
3061
|
+
drawing ||
|
|
3062
|
+
matched.some(takesParents)) {
|
|
2419
3063
|
return this.style(node, matched, parentTokens, parentInherited, inline);
|
|
2420
3064
|
}
|
|
2421
3065
|
if (this.sharedGeneration !== this.generation) {
|
|
@@ -2469,7 +3113,7 @@ export class StyleResolver {
|
|
|
2469
3113
|
delete own['pointerEvents'];
|
|
2470
3114
|
const style = { ...parentInherited, ...own };
|
|
2471
3115
|
settleLineHeight(style);
|
|
2472
|
-
const inherited = decorate(style, inheritFrom(parentInherited, own), own);
|
|
3116
|
+
const inherited = decorate(style, inheritFrom(parentInherited, own, node), own);
|
|
2473
3117
|
const rtl = (style['direction'] ?? this.conditions.direction) === 'rtl';
|
|
2474
3118
|
const important = importantNames(result, () => TWIN[rtl ? 'rtl' : 'ltr']);
|
|
2475
3119
|
return important ? { style, inherited, tokens, important } : { style, inherited, tokens };
|
|
@@ -3306,31 +3950,46 @@ function referenced(declaration, tokens) {
|
|
|
3306
3950
|
return declaration.adjust ? adjusted(base, declaration.adjust) : base;
|
|
3307
3951
|
}
|
|
3308
3952
|
/**
|
|
3309
|
-
* What
|
|
3310
|
-
*
|
|
3311
|
-
*
|
|
3312
|
-
*
|
|
3953
|
+
* What a display keyword is laid out as: flex, none, contents, or one of the block containers,
|
|
3954
|
+
* which the engine lays out as a flex column and commits as flex (`flow.ts`). An inline box is
|
|
3955
|
+
* flex, as the compiler reads one written out, an inline-flex box sits in a line, and `flow` is
|
|
3956
|
+
* a block.
|
|
3957
|
+
* Anything else, a grid or a table, is no display native has, so the property is unset.
|
|
3313
3958
|
*/
|
|
3314
3959
|
const DISPLAYS = new Map([
|
|
3315
|
-
...['flex', '
|
|
3316
|
-
['
|
|
3317
|
-
['
|
|
3960
|
+
...['flex', 'inline'].map((word) => [word, 'flex']),
|
|
3961
|
+
...['block', 'flow-root', 'inline-block', 'inline-flex', 'none', 'contents'].map((word) => [word, word]),
|
|
3962
|
+
['grid', 'grid'],
|
|
3963
|
+
['inline-grid', 'inline-grid'],
|
|
3964
|
+
['flow', 'block'],
|
|
3318
3965
|
]);
|
|
3319
3966
|
/**
|
|
3320
|
-
* The two-keyword form, an outer display and an inner one in either order
|
|
3321
|
-
* `inline
|
|
3967
|
+
* The two-keyword form, an outer display and an inner one in either order, as Chrome computes
|
|
3968
|
+
* it: `inline flex` is `inline-flex`, `block flow` is `block`, `inline flow-root` is
|
|
3969
|
+
* `inline-block`.
|
|
3322
3970
|
*/
|
|
3323
3971
|
const OUTER = new Set(['block', 'inline']);
|
|
3324
|
-
const INNER = new Set(['flow', 'flow-root', 'flex']);
|
|
3972
|
+
const INNER = new Set(['flow', 'flow-root', 'flex', 'grid']);
|
|
3973
|
+
const PAIRS = {
|
|
3974
|
+
'block flow': 'block',
|
|
3975
|
+
'block flow-root': 'flow-root',
|
|
3976
|
+
'inline flow-root': 'inline-block',
|
|
3977
|
+
'block grid': 'grid',
|
|
3978
|
+
'inline grid': 'inline-grid',
|
|
3979
|
+
'inline flex': 'inline-flex',
|
|
3980
|
+
};
|
|
3325
3981
|
function displayOf(value) {
|
|
3326
3982
|
if (typeof value !== 'string')
|
|
3327
3983
|
return undefined;
|
|
3328
3984
|
const words = value.toLowerCase().split(CSS_SPACE);
|
|
3329
3985
|
if (words.length === 1)
|
|
3330
3986
|
return DISPLAYS.get(words[0]);
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3987
|
+
if (words.length !== 2)
|
|
3988
|
+
return undefined;
|
|
3989
|
+
const [outer, inner] = (OUTER.has(words[0]) ? words : [words[1], words[0]]);
|
|
3990
|
+
if (!OUTER.has(outer) || !INNER.has(inner))
|
|
3991
|
+
return undefined;
|
|
3992
|
+
return PAIRS[`${outer} ${inner}`] ?? 'flex';
|
|
3334
3993
|
}
|
|
3335
3994
|
/**
|
|
3336
3995
|
* A set token's value in the form wanted. A token of `currentColor` is the colour in scope where
|
|
@@ -3867,7 +4526,9 @@ function wholePercentage(marker, tokens) {
|
|
|
3867
4526
|
return undefined;
|
|
3868
4527
|
const leaf = marker.expression;
|
|
3869
4528
|
const value = formOf(tokens[leaf.reference], 'length');
|
|
3870
|
-
|
|
4529
|
+
// And one holding a percentage with a length added, which the engine finishes: `calc(100% + -2px)`.
|
|
4530
|
+
const whole = typeof value === 'string' && (value.endsWith('%') || value.startsWith('calc('));
|
|
4531
|
+
return whole ? value : undefined;
|
|
3871
4532
|
}
|
|
3872
4533
|
function calculated(expression, kind, tokens) {
|
|
3873
4534
|
if (typeof expression === 'number')
|
|
@@ -4050,6 +4711,25 @@ function insetOrColour(token) {
|
|
|
4050
4711
|
function colourMarker(value) {
|
|
4051
4712
|
return value?.__colour;
|
|
4052
4713
|
}
|
|
4714
|
+
/**
|
|
4715
|
+
* A token added to a percentage, `calc(100% - var(--handle))`: a length in points or a share of
|
|
4716
|
+
* the same box adds up to one value. A length of the box's own font does not, here, so the box is
|
|
4717
|
+
* given none rather than one that is not what the rule says.
|
|
4718
|
+
*/
|
|
4719
|
+
function withPercent(value, adjust) {
|
|
4720
|
+
const percent = adjust.percent;
|
|
4721
|
+
if (typeof value === 'string' && value.endsWith('%')) {
|
|
4722
|
+
const share = percent + Number.parseFloat(value) * (adjust.scale ?? 1);
|
|
4723
|
+
const offset = adjust.offset ?? 0;
|
|
4724
|
+
return offset === 0 ? `${share}%` : `calc(${share}% + ${offset}px)`;
|
|
4725
|
+
}
|
|
4726
|
+
// A length of its own font, deferred, is no number here.
|
|
4727
|
+
if (typeof value !== 'number')
|
|
4728
|
+
return undefined;
|
|
4729
|
+
const scaled = value * (adjust.scale ?? 1) + (adjust.offset ?? 0);
|
|
4730
|
+
// With nothing to add, the percentage alone, which needs no second pass.
|
|
4731
|
+
return scaled === 0 ? `${percent}%` : `calc(${percent}% + ${scaled}px)`;
|
|
4732
|
+
}
|
|
4053
4733
|
/**
|
|
4054
4734
|
* A resolved length, moved by the arithmetic that surrounded its `var()`.
|
|
4055
4735
|
*
|
|
@@ -4060,6 +4740,8 @@ function adjusted(value, adjust) {
|
|
|
4060
4740
|
if (adjust.alpha !== undefined)
|
|
4061
4741
|
return faded(value, adjust.alpha);
|
|
4062
4742
|
const em = value?.__defer;
|
|
4743
|
+
if (adjust.percent !== undefined)
|
|
4744
|
+
return withPercent(value, adjust);
|
|
4063
4745
|
if (em && adjust.floor === undefined)
|
|
4064
4746
|
return adjustedEm(em, adjust);
|
|
4065
4747
|
if (typeof value !== 'number')
|