@react-x11/components 0.14.1 → 0.16.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/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +171 -26
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +616 -216
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +18 -0
- package/dist/html/css/shapes.d.ts.map +1 -1
- package/dist/html/css/shapes.js +21 -1
- package/dist/html/css/shapes.js.map +1 -1
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +74 -12
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +27 -8
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +29 -9
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +141 -52
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +172 -23
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +11 -9
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +1759 -238
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +64 -19
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +886 -324
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +35 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +122 -26
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +558 -178
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +23 -7
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +264 -59
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +697 -221
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +35 -1
- package/src/html/css/style.ts +122 -17
- package/src/html/css/ua.ts +27 -8
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +149 -58
- package/src/html/layout/boxes.ts +211 -27
- package/src/html/layout/css-grid.ts +12 -9
- package/src/html/layout/flex.ts +1975 -231
- package/src/html/layout/inline.ts +70 -16
- package/src/html/layout/table.ts +961 -332
- package/src/html/node.ts +155 -27
- package/src/html/paint.ts +692 -234
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +299 -56
- package/src/html/widgets.ts +61 -4
package/dist/html/css/cascade.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// compiles a selector to a closure once and the closure is what runs per
|
|
7
7
|
// element, and it has the combinator, `:nth-child(an+b)` and attribute-
|
|
8
8
|
// operator cases right — so it is imported, through an adapter that answers
|
|
9
|
-
// `:hover` from this renderer's own pointer state
|
|
9
|
+
// `:hover` from this renderer's own pointer state, and pseudo-classes that
|
|
10
|
+
// answer `:focus` and its kin from which element's widget holds the focus.
|
|
10
11
|
//
|
|
11
12
|
// What is *not* delegated is the choice of which selectors to try. Testing
|
|
12
13
|
// every rule against every element is O(rules × elements) and is what makes
|
|
@@ -19,14 +20,15 @@ import { compile } from 'css-select';
|
|
|
19
20
|
import * as DomUtils from 'domutils';
|
|
20
21
|
import { Element as DomElement, isTag } from 'domhandler';
|
|
21
22
|
import { attr, tagOf } from '../dom.js';
|
|
23
|
+
import { systemColorTable, usedColorScheme } from './color.js';
|
|
22
24
|
import { asciiLower, escapeEnd, mediaMatches, readIdent, startsIdent, } from './parse.js';
|
|
23
|
-
import { applyDeclaration, blockify, settleButton, settleContentVisibility, settleOverflow, settleClamp, copyStyle, decorate, inherit, initialOne, initialStyle, isInherited, } from './style.js';
|
|
25
|
+
import { applyDeclaration, blockify, settleAlign, settleButton, settleContentVisibility, settleOverflow, settleOutline, settleClamp, copyStyle, decorate, inherit, initialOne, initialStyle, isInherited, } from './style.js';
|
|
24
26
|
import { parseDeclarations } from './parse.js';
|
|
25
27
|
import { CounterStyles, counterStyleRule } from './counter-styles.js';
|
|
26
28
|
import { PALETTE_CHROME } from './ua.js';
|
|
27
29
|
import { viewportUnit } from './values.js';
|
|
28
30
|
import { customProperties, substituteIn } from './vars.js';
|
|
29
|
-
import { ROOT_BOX_PROPS, SHAPE_TAGS, isShapeProp, shapeValue, } from './shapes.js';
|
|
31
|
+
import { ROOT_BOX_PROPS, SHAPE_TAGS, isShapeProp, isUse, shapeValue, useHref, } from './shapes.js';
|
|
30
32
|
/** Where a declaration came from. Higher wins before specificity is asked. */
|
|
31
33
|
var Origin;
|
|
32
34
|
(function (Origin) {
|
|
@@ -79,10 +81,12 @@ class RuleIndex {
|
|
|
79
81
|
/** Whether any rule in here is pointer-sensitive, so the renderer knows
|
|
80
82
|
* whether a pointer move can change the cascade at all. */
|
|
81
83
|
hoverSensitive = false;
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
*
|
|
84
|
+
/** …and focus-sensitive, for a widget taking the focus or giving it up. */
|
|
85
|
+
focusSensitive = false;
|
|
86
|
+
/** The rules that test `:hover` or the focus, in buckets of their own:
|
|
87
|
+
* the ones whose answers a pointer move or a focus change can change,
|
|
88
|
+
* which is all that is asked to know whether an element's did
|
|
89
|
+
* (`Cascade.pointerChanged`). Null until there is one. */
|
|
86
90
|
pointer = null;
|
|
87
91
|
/** The buckets holding a rule an element cannot share its style under
|
|
88
92
|
* (`UNSHAREABLE`): the ids, classes and tags whose elements compute
|
|
@@ -133,10 +137,15 @@ class RuleIndex {
|
|
|
133
137
|
id: this._nextId++,
|
|
134
138
|
needs: null,
|
|
135
139
|
root: null,
|
|
140
|
+
inCopy: null,
|
|
141
|
+
inCopyCompiled: false,
|
|
136
142
|
};
|
|
137
143
|
if (rule.selector.includes(':hover') || rule.selector.includes(':active')) {
|
|
138
144
|
this.hoverSensitive = true;
|
|
139
145
|
}
|
|
146
|
+
const focus = FOCUS.test(rule.selector);
|
|
147
|
+
if (focus)
|
|
148
|
+
this.focusSensitive = true;
|
|
140
149
|
if (UNSHAREABLE.test(rule.selector)) {
|
|
141
150
|
if (key.kind === 'id')
|
|
142
151
|
this.ownStyleIds.add(key.name);
|
|
@@ -148,7 +157,7 @@ class RuleIndex {
|
|
|
148
157
|
this.ownStyleEverywhere = true;
|
|
149
158
|
}
|
|
150
159
|
this._bucket(key).push(indexed);
|
|
151
|
-
if (HOVER.test(rule.selector)) {
|
|
160
|
+
if (focus || HOVER.test(rule.selector)) {
|
|
152
161
|
this.pointer ??= new RuleIndex();
|
|
153
162
|
this.pointer.size += 1;
|
|
154
163
|
this.pointer._bucket(key).push(indexed);
|
|
@@ -221,8 +230,15 @@ function splitPseudoElement(rule) {
|
|
|
221
230
|
* handed none), so a selector that tests only it never changes as the
|
|
222
231
|
* pointer moves — Wikipedia's buttons' `:focus:not(:active)` among them.
|
|
223
232
|
* A press that sets it would have to be counted here too.
|
|
233
|
+
*
|
|
234
|
+
* The focus is the other state a change of reaches only some elements, and
|
|
235
|
+
* `kind` says which is asked for: `FOCUS_STATE` finds the compounds that
|
|
236
|
+
* test `:focus`, `:focus-visible` or `:focus-within` the same way. Either
|
|
237
|
+
* kind leaves the other's pseudo-classes out of a compound as well as its
|
|
238
|
+
* own, so what is left matches wherever the compound could, in any state —
|
|
239
|
+
* `a:focus:hover` is a compound of `a`'s, for both.
|
|
224
240
|
*/
|
|
225
|
-
export function pointerCompounds(selector) {
|
|
241
|
+
export function pointerCompounds(selector, kind = HOVER_STATE) {
|
|
226
242
|
const out = {
|
|
227
243
|
compounds: [],
|
|
228
244
|
siblings: false,
|
|
@@ -230,9 +246,21 @@ export function pointerCompounds(selector) {
|
|
|
230
246
|
has: [],
|
|
231
247
|
followed: [],
|
|
232
248
|
};
|
|
233
|
-
scanComplex(selector, out);
|
|
249
|
+
scanComplex(selector, out, kind);
|
|
234
250
|
return out;
|
|
235
251
|
}
|
|
252
|
+
/** The pointer's: `:hover`, and `:active`, which nothing sets. */
|
|
253
|
+
export const HOVER_STATE = {
|
|
254
|
+
mentions: /:(?:hover|active)/i,
|
|
255
|
+
any: /:hover(?![\w-])/i,
|
|
256
|
+
own: /^:hover$/i,
|
|
257
|
+
};
|
|
258
|
+
/** The focus's: `:focus`, `:focus-visible` and `:focus-within`. */
|
|
259
|
+
export const FOCUS_STATE = {
|
|
260
|
+
mentions: /:focus/i,
|
|
261
|
+
any: /:focus(?:-visible|-within)?(?![\w-])/i,
|
|
262
|
+
own: /^:focus/i,
|
|
263
|
+
};
|
|
236
264
|
/** The functions that take a selector list and match the element they are
|
|
237
265
|
* written on (Selectors 4, 4.2 to 4.4), under the names css-select and
|
|
238
266
|
* older sheets know them by. */
|
|
@@ -326,11 +354,11 @@ function closeOf(text, open) {
|
|
|
326
354
|
return text.length;
|
|
327
355
|
}
|
|
328
356
|
/** `pointerCompounds` for one complex selector, into `out`. */
|
|
329
|
-
function scanComplex(selector, out) {
|
|
357
|
+
function scanComplex(selector, out, kind) {
|
|
330
358
|
for (const { text, next } of compoundsOf(selector)) {
|
|
331
359
|
const compoundsFrom = out.compounds.length;
|
|
332
360
|
const hasFrom = out.has.length;
|
|
333
|
-
const found = scanCompound(text, out);
|
|
361
|
+
const found = scanCompound(text, out, kind);
|
|
334
362
|
const sibling = next === '+' || next === '~';
|
|
335
363
|
if (found.args.length) {
|
|
336
364
|
out.has.push({
|
|
@@ -360,9 +388,10 @@ function scanComplex(selector, out) {
|
|
|
360
388
|
* it (`bare`), whether it tests the element's own hover (`pointer`), the
|
|
361
389
|
* arguments of each `:has()` of the element's that tests it below (`args`),
|
|
362
390
|
* and whether a selector nested in it named another element's (`below`),
|
|
363
|
-
* whose compounds went into `out`.
|
|
391
|
+
* whose compounds went into `out`. Of the focus, likewise, for
|
|
392
|
+
* `FOCUS_STATE`.
|
|
364
393
|
*/
|
|
365
|
-
function scanCompound(text, out) {
|
|
394
|
+
function scanCompound(text, out, kind) {
|
|
366
395
|
let bare = '';
|
|
367
396
|
let pointer = false;
|
|
368
397
|
let below = false;
|
|
@@ -390,9 +419,9 @@ function scanCompound(text, out) {
|
|
|
390
419
|
else if (c === ']')
|
|
391
420
|
bracket = Math.max(0, bracket - 1);
|
|
392
421
|
else if (c === ':' && bracket === 0 && text[i + 1] !== ':') {
|
|
393
|
-
const m =
|
|
422
|
+
const m = STATE_PSEUDO_AT.exec(text.slice(i));
|
|
394
423
|
if (m) {
|
|
395
|
-
if (m[0]
|
|
424
|
+
if (kind.own.test(m[0]))
|
|
396
425
|
pointer = true;
|
|
397
426
|
i += m[0].length - 1;
|
|
398
427
|
continue;
|
|
@@ -404,7 +433,7 @@ function scanCompound(text, out) {
|
|
|
404
433
|
const arg = text.slice(open + 1, close);
|
|
405
434
|
const whole = text.slice(i, close + 1);
|
|
406
435
|
i = close;
|
|
407
|
-
if (!
|
|
436
|
+
if (!kind.any.test(arg)) {
|
|
408
437
|
bare += whole;
|
|
409
438
|
continue;
|
|
410
439
|
}
|
|
@@ -426,16 +455,16 @@ function scanCompound(text, out) {
|
|
|
426
455
|
const own = [];
|
|
427
456
|
let any = false;
|
|
428
457
|
for (const entry of selectorsOf(arg)) {
|
|
429
|
-
if (!
|
|
458
|
+
if (!kind.any.test(entry))
|
|
430
459
|
continue;
|
|
431
460
|
const parts = compoundsOf(entry);
|
|
432
461
|
if (parts.length !== 1) {
|
|
433
|
-
scanComplex(entry, out);
|
|
462
|
+
scanComplex(entry, out, kind);
|
|
434
463
|
below = true;
|
|
435
464
|
continue;
|
|
436
465
|
}
|
|
437
466
|
// of this element itself
|
|
438
|
-
const one = scanCompound(parts[0].text, out);
|
|
467
|
+
const one = scanCompound(parts[0].text, out, kind);
|
|
439
468
|
if (one.below)
|
|
440
469
|
below = true;
|
|
441
470
|
if (!one.pointer && !one.args.length)
|
|
@@ -467,26 +496,33 @@ function scanCompound(text, out) {
|
|
|
467
496
|
* start of a string. */
|
|
468
497
|
const FUNCTION_AT = /^:([\w-]+)\(/;
|
|
469
498
|
/** `:hover` anywhere in a selector. */
|
|
470
|
-
const HOVER =
|
|
471
|
-
/** `:
|
|
472
|
-
const
|
|
473
|
-
/**
|
|
474
|
-
|
|
499
|
+
const HOVER = HOVER_STATE.any;
|
|
500
|
+
/** `:focus`, `:focus-visible` or `:focus-within` anywhere in a selector. */
|
|
501
|
+
const FOCUS = FOCUS_STATE.any;
|
|
502
|
+
/** A pseudo-class of a state this renderer keeps — `:hover`, `:active`,
|
|
503
|
+
* `:focus` and its two kin — at the start of a string, and nothing
|
|
504
|
+
* longer. */
|
|
505
|
+
const STATE_PSEUDO_AT = /^:(?:hover|active|focus(?:-visible|-within)?)(?![\w-])/i;
|
|
506
|
+
/** Any of them anywhere in a selector. */
|
|
507
|
+
const STATE_PSEUDO = /:(?:hover|active|focus(?:-visible|-within)?)(?![\w-])/i;
|
|
475
508
|
/**
|
|
476
509
|
* A selector compiled to a matcher over this adapter. css-select keeps the
|
|
477
510
|
* answers of a `:has()` above the subject, or of the ancestors a
|
|
478
511
|
* descendant combinator tried under one, for as long as the matcher lives
|
|
479
512
|
* (`cacheResults`) — which is the cascade's life, and a pointer move is no
|
|
480
|
-
* new cascade. So a selector that tests the pointer
|
|
481
|
-
* `#box:has(a:hover) .m` answered what it did before the
|
|
482
|
-
* good.
|
|
513
|
+
* new cascade. So a selector that tests the pointer or the focus keeps
|
|
514
|
+
* none: with them, `#box:has(a:hover) .m` answered what it did before the
|
|
515
|
+
* first move, for good.
|
|
483
516
|
*/
|
|
484
|
-
function compileSelector(selector, adapter, pseudos = PSEUDOS
|
|
517
|
+
function compileSelector(selector, adapter, pseudos = PSEUDOS,
|
|
518
|
+
/** Whether what it answers may be kept: not where the adapter's tree
|
|
519
|
+
* moves under it, as a `<use>`'s copy's does (`Cascade.shapeStyles`). */
|
|
520
|
+
cache = true) {
|
|
485
521
|
return compile(noEmptyWords(selector), {
|
|
486
522
|
adapter,
|
|
487
523
|
xmlMode: false,
|
|
488
524
|
pseudos,
|
|
489
|
-
cacheResults: !
|
|
525
|
+
cacheResults: cache && !STATE_PSEUDO.test(selector),
|
|
490
526
|
});
|
|
491
527
|
}
|
|
492
528
|
function mapBucket(map, key) {
|
|
@@ -608,6 +644,30 @@ function ancestorKeys(selector) {
|
|
|
608
644
|
}
|
|
609
645
|
return keys;
|
|
610
646
|
}
|
|
647
|
+
/** A drawing none of whose own elements a rule reaches. */
|
|
648
|
+
const NO_SHAPES = new Map();
|
|
649
|
+
/**
|
|
650
|
+
* What `Cascade.shapeStyles` keeps for the drawings of one build of the
|
|
651
|
+
* boxes (`BoxTree.shapeCopies`): the document's elements by their ids, and
|
|
652
|
+
* what the rules matched in the copy of each element a `<use>` names —
|
|
653
|
+
* the same whichever `<use>` makes it, so a page of hundreds of icons from
|
|
654
|
+
* one sprite matches each symbol once — and what that comes to for a
|
|
655
|
+
* drawing of each style. A build is made again whenever the document, its
|
|
656
|
+
* style sheets or a viewport a value reads change, and a pointer move
|
|
657
|
+
* changes nothing a copy is matched by: nothing in one is under the
|
|
658
|
+
* pointer, and a drawing whose style it changes has another style.
|
|
659
|
+
*/
|
|
660
|
+
export class ShapeCopies {
|
|
661
|
+
byId;
|
|
662
|
+
/** @internal */
|
|
663
|
+
matched = new Map();
|
|
664
|
+
/** @internal What each copy comes to for a drawing of a style, and the
|
|
665
|
+
* key that names it. */
|
|
666
|
+
resolved = new WeakMap();
|
|
667
|
+
constructor(byId) {
|
|
668
|
+
this.byId = byId;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
611
671
|
function balancedEnd(text, from, open, close) {
|
|
612
672
|
let depth = 0;
|
|
613
673
|
for (let i = from; i < text.length; i += 1) {
|
|
@@ -622,6 +682,112 @@ function balancedEnd(text, from, open, close) {
|
|
|
622
682
|
return text.length;
|
|
623
683
|
}
|
|
624
684
|
const NO_POINTER = { hovered: new Set(), active: new Set() };
|
|
685
|
+
export const NO_FOCUS = {
|
|
686
|
+
element: null,
|
|
687
|
+
visible: false,
|
|
688
|
+
within: new Set(),
|
|
689
|
+
};
|
|
690
|
+
/**
|
|
691
|
+
* What the selectors that test one state — the pointer, or the focus — say
|
|
692
|
+
* of the elements a change of it can restyle: the compounds that test it,
|
|
693
|
+
* each without it and with whether a sibling combinator follows it, and
|
|
694
|
+
* the anchors of the `:has()`s that test it (`pointerCompounds`).
|
|
695
|
+
*/
|
|
696
|
+
class StateRules {
|
|
697
|
+
_kind;
|
|
698
|
+
/** The compounds, each without the state, and their matchers, compiled
|
|
699
|
+
* when a change first asks (`touches`). */
|
|
700
|
+
_compounds = new Map();
|
|
701
|
+
_matchers = null;
|
|
702
|
+
/** Whether a change can be restyled where it happened
|
|
703
|
+
* (`HtmlViewNode._hoverInPlace`): false where a selector tests the
|
|
704
|
+
* state inside a functional pseudo-class, which reaches elements no
|
|
705
|
+
* compound names. */
|
|
706
|
+
local = true;
|
|
707
|
+
/** The compounds whose `:has()` tests the state, each without it,
|
|
708
|
+
* whether its argument starts at a sibling, and whether a sibling
|
|
709
|
+
* combinator follows it (`anchors`). */
|
|
710
|
+
_anchors = new Map();
|
|
711
|
+
_anchorMatchers = null;
|
|
712
|
+
constructor(_kind) {
|
|
713
|
+
this._kind = _kind;
|
|
714
|
+
}
|
|
715
|
+
note(selector) {
|
|
716
|
+
if (!this._kind.mentions.test(selector))
|
|
717
|
+
return;
|
|
718
|
+
const found = pointerCompounds(selector, this._kind);
|
|
719
|
+
if (found.nested)
|
|
720
|
+
this.local = false;
|
|
721
|
+
const followed = new Set(found.followed);
|
|
722
|
+
for (const c of found.compounds) {
|
|
723
|
+
this._compounds.set(c, followed.has(c) || (this._compounds.get(c) ?? false));
|
|
724
|
+
}
|
|
725
|
+
for (const { anchor, siblings } of found.has) {
|
|
726
|
+
const was = this._anchors.get(anchor);
|
|
727
|
+
this._anchors.set(anchor, {
|
|
728
|
+
siblings: siblings || (was?.siblings ?? false),
|
|
729
|
+
followed: followed.has(anchor) || (was?.followed ?? false),
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/** `Cascade.hoverTouches`, for this state. */
|
|
734
|
+
touches(el, compile) {
|
|
735
|
+
// the followed ones first, so the first that matches is the answer
|
|
736
|
+
this._matchers ??= [...this._compounds]
|
|
737
|
+
.sort(([, a], [, b]) => Number(b) - Number(a))
|
|
738
|
+
.flatMap(([compound, followed]) => {
|
|
739
|
+
const match = compile(compound);
|
|
740
|
+
return match ? [{ match, followed }] : [];
|
|
741
|
+
});
|
|
742
|
+
for (const { match, followed } of this._matchers) {
|
|
743
|
+
if (match(el))
|
|
744
|
+
return followed ? HOVER_FOLLOWED : HOVER_TOUCHED;
|
|
745
|
+
}
|
|
746
|
+
return HOVER_UNTOUCHED;
|
|
747
|
+
}
|
|
748
|
+
/** `Cascade.hoverAnchors`, for this state. */
|
|
749
|
+
anchors(el, into, compile) {
|
|
750
|
+
if (!this._anchors.size)
|
|
751
|
+
return;
|
|
752
|
+
this._anchorMatchers ??= [...this._anchors]
|
|
753
|
+
.sort(([, a], [, b]) => Number(b.followed) - Number(a.followed))
|
|
754
|
+
.flatMap(([anchor, { siblings, followed }]) => {
|
|
755
|
+
const match = compile(anchor);
|
|
756
|
+
return match ? [{ match, siblings, followed }] : [];
|
|
757
|
+
});
|
|
758
|
+
const note = (at, followed) => {
|
|
759
|
+
if (followed)
|
|
760
|
+
into.set(at, HOVER_FOLLOWED);
|
|
761
|
+
else if (!into.has(at))
|
|
762
|
+
into.set(at, HOVER_TOUCHED);
|
|
763
|
+
};
|
|
764
|
+
const siblings = this._anchorMatchers.some((m) => m.siblings);
|
|
765
|
+
for (let at = el; at;) {
|
|
766
|
+
if (at !== el) {
|
|
767
|
+
for (const { match, followed } of this._anchorMatchers) {
|
|
768
|
+
if (match(at)) {
|
|
769
|
+
note(at, followed);
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (siblings) {
|
|
775
|
+
for (let s = at.prev; s; s = s.prev) {
|
|
776
|
+
if (!isTag(s))
|
|
777
|
+
continue;
|
|
778
|
+
for (const m of this._anchorMatchers) {
|
|
779
|
+
if (m.siblings && m.match(s)) {
|
|
780
|
+
note(s, m.followed);
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const parent = at.parent;
|
|
787
|
+
at = parent && isTag(parent) ? parent : null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
625
791
|
function faceOf(style) {
|
|
626
792
|
return {
|
|
627
793
|
family: style.fontFamily,
|
|
@@ -657,22 +823,23 @@ export class Cascade {
|
|
|
657
823
|
_shapes = new RuleIndex();
|
|
658
824
|
_shapeNeeds = null;
|
|
659
825
|
_adapter;
|
|
660
|
-
/**
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
*
|
|
673
|
-
|
|
674
|
-
|
|
826
|
+
/**
|
|
827
|
+
* The tree a `<use>`'s copy is in, for `_copyAdapter`: the element it is
|
|
828
|
+
* a copy of, which is its top (`ShapeStyles.used`). A copy is its own
|
|
829
|
+
* tree, a shadow tree of the `<use>`'s (SVG 2, 5.5.1), so a selector is
|
|
830
|
+
* matched against its original with nothing above that element and
|
|
831
|
+
* nothing beside it — the top of a copy is its tree's first child and
|
|
832
|
+
* its last, whichever of its parent's it is. And nothing in it is under
|
|
833
|
+
* the pointer, as nothing in a drawing is: it has no box to be.
|
|
834
|
+
*/
|
|
835
|
+
_copyTop = null;
|
|
836
|
+
_copyAdapter;
|
|
837
|
+
/** What the selectors that test the pointer, and the ones that test the
|
|
838
|
+
* focus, say of the elements a change of it reaches. */
|
|
839
|
+
_hoverRules = new StateRules(HOVER_STATE);
|
|
840
|
+
_focusRules = new StateRules(FOCUS_STATE);
|
|
675
841
|
_pointer = NO_POINTER;
|
|
842
|
+
_focus = NO_FOCUS;
|
|
676
843
|
initial;
|
|
677
844
|
look;
|
|
678
845
|
/** Viewport width the media queries were evaluated at, in device pixels
|
|
@@ -719,15 +886,37 @@ export class Cascade {
|
|
|
719
886
|
/** Whether any declaration has a length in `lh` or `rlh`: only then is
|
|
720
887
|
* the line height settled ahead of the declarations that read it. */
|
|
721
888
|
_lh = false;
|
|
722
|
-
/** The pseudo-classes css-select is handed
|
|
889
|
+
/** The pseudo-classes css-select is handed: `:root` this cascade's, and
|
|
890
|
+
* the focus's three answered from `_focus`. */
|
|
723
891
|
_pseudos;
|
|
892
|
+
/** The colour schemes the page says it supports with a `<meta
|
|
893
|
+
* name="color-scheme">` (HTML 4.2.5.4), which its root element takes
|
|
894
|
+
* as it takes a presentational attribute: under any rule that sets its
|
|
895
|
+
* `color-scheme`. Set by the box builder from the document. */
|
|
896
|
+
pageColorScheme = null;
|
|
897
|
+
/** Each system colour as the `light-dark()` it is: the palette's where
|
|
898
|
+
* the element's scheme is the palette's, and Chrome's in an SVG image,
|
|
899
|
+
* which has none. */
|
|
900
|
+
_systemColors;
|
|
724
901
|
constructor(sheets, look, viewportWidth, viewportHeight, scale = 1, xHeight = null, zeroWidth = null, normalLine = null, families = null,
|
|
725
902
|
/** The document's element where it is not an `<html>`: an SVG image's
|
|
726
903
|
* `<svg>`, which is `:root` there (Selectors 4, 14.1). */
|
|
727
|
-
documentElement = null
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
904
|
+
documentElement = null,
|
|
905
|
+
/** The element the document's URL names by its fragment, which is
|
|
906
|
+
* `:target` (Selectors 4, 9.1): an SVG image's, `image.svg#icon`. */
|
|
907
|
+
target = null) {
|
|
908
|
+
// css-select has none of the focus's three, and a pseudo-class is
|
|
909
|
+
// handed only the element, so they are this cascade's own closures
|
|
910
|
+
this._pseudos = {
|
|
911
|
+
...PSEUDOS,
|
|
912
|
+
...(documentElement && {
|
|
913
|
+
root: (el) => el === documentElement,
|
|
914
|
+
}),
|
|
915
|
+
...(target && { target: (el) => el === target }),
|
|
916
|
+
focus: (el) => this._focus.element === el,
|
|
917
|
+
'focus-visible': (el) => this._focus.visible && this._focus.element === el,
|
|
918
|
+
'focus-within': (el) => this._focus.within.has(el),
|
|
919
|
+
};
|
|
731
920
|
this._xHeightOf = xHeight;
|
|
732
921
|
this._zeroWidthOf = zeroWidth;
|
|
733
922
|
this._normalLineOf = normalLine;
|
|
@@ -736,6 +925,7 @@ export class Cascade {
|
|
|
736
925
|
? (list) => families.map(list)
|
|
737
926
|
: undefined;
|
|
738
927
|
this.look = look;
|
|
928
|
+
this._systemColors = systemColorTable(documentElement ? null : look);
|
|
739
929
|
this.initial = initialStyle(look, scale);
|
|
740
930
|
this.viewportWidth = viewportWidth;
|
|
741
931
|
this.viewportHeight = viewportHeight;
|
|
@@ -767,7 +957,9 @@ export class Cascade {
|
|
|
767
957
|
this._shapes.add(rule, key);
|
|
768
958
|
}
|
|
769
959
|
}
|
|
770
|
-
|
|
960
|
+
const selector = (pseudo?.rule ?? rule).selector;
|
|
961
|
+
this._hoverRules.note(selector);
|
|
962
|
+
this._focusRules.note(selector);
|
|
771
963
|
}
|
|
772
964
|
for (const bp of sheet.breakpoints)
|
|
773
965
|
breakpoints.add(bp);
|
|
@@ -797,6 +989,14 @@ export class Cascade {
|
|
|
797
989
|
isActive: (el) => this._pointer.active.has(el),
|
|
798
990
|
isVisited: () => false,
|
|
799
991
|
};
|
|
992
|
+
this._copyAdapter = {
|
|
993
|
+
...this._adapter,
|
|
994
|
+
getParent: (el) => (el === this._copyTop ? null : DomUtils.getParent(el)),
|
|
995
|
+
getSiblings: (el) => el === this._copyTop ? [el] : DomUtils.getSiblings(el),
|
|
996
|
+
prevElementSibling: (el) => el === this._copyTop ? null : DomUtils.prevElementSibling(el),
|
|
997
|
+
isHovered: () => false,
|
|
998
|
+
isActive: () => false,
|
|
999
|
+
};
|
|
800
1000
|
}
|
|
801
1001
|
/** Whether declarations read the viewport's width or height (`vw`,
|
|
802
1002
|
* `vh`, `vmin`, `vmax`), noted on the cascade. */
|
|
@@ -827,6 +1027,30 @@ export class Cascade {
|
|
|
827
1027
|
setPointer(pointer) {
|
|
828
1028
|
this._pointer = pointer;
|
|
829
1029
|
}
|
|
1030
|
+
/** Whether a widget taking the focus or giving it up can change what this
|
|
1031
|
+
* cascade produces. */
|
|
1032
|
+
get focusSensitive() {
|
|
1033
|
+
return (this._index.focusSensitive ||
|
|
1034
|
+
this._pseudo.before.focusSensitive ||
|
|
1035
|
+
this._pseudo.after.focusSensitive ||
|
|
1036
|
+
this._pseudo['first-letter'].focusSensitive ||
|
|
1037
|
+
this._pseudo['first-line'].focusSensitive ||
|
|
1038
|
+
this._pseudo.marker.focusSensitive);
|
|
1039
|
+
}
|
|
1040
|
+
setFocus(focus) {
|
|
1041
|
+
this._focus = focus;
|
|
1042
|
+
}
|
|
1043
|
+
/** Whether a pointer move can be restyled where it happened
|
|
1044
|
+
* (`HtmlViewNode._hoverInPlace`): false where a selector tests the
|
|
1045
|
+
* pointer inside a functional pseudo-class, which reaches elements no
|
|
1046
|
+
* compound names. */
|
|
1047
|
+
get hoverLocal() {
|
|
1048
|
+
return this._hoverRules.local;
|
|
1049
|
+
}
|
|
1050
|
+
/** …and a focus change. */
|
|
1051
|
+
get focusLocal() {
|
|
1052
|
+
return this._focusRules.local;
|
|
1053
|
+
}
|
|
830
1054
|
/**
|
|
831
1055
|
* Which pseudo-elements' rules test the pointer: none's
|
|
832
1056
|
* (`HOVER_PSEUDO_NONE`); a `::before`'s or an `::after`'s only
|
|
@@ -836,13 +1060,19 @@ export class Cascade {
|
|
|
836
1060
|
* move cannot.
|
|
837
1061
|
*/
|
|
838
1062
|
get hoverPseudo() {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1063
|
+
return this._pseudoRules('hoverSensitive');
|
|
1064
|
+
}
|
|
1065
|
+
/** …and which test the focus. */
|
|
1066
|
+
get focusPseudo() {
|
|
1067
|
+
return this._pseudoRules('focusSensitive');
|
|
1068
|
+
}
|
|
1069
|
+
_pseudoRules(state) {
|
|
1070
|
+
if (this._pseudo['first-letter'][state] ||
|
|
1071
|
+
this._pseudo['first-line'][state] ||
|
|
1072
|
+
this._pseudo.marker[state]) {
|
|
842
1073
|
return HOVER_PSEUDO_OTHER;
|
|
843
1074
|
}
|
|
844
|
-
return this._pseudo.before
|
|
845
|
-
this._pseudo.after.hoverSensitive
|
|
1075
|
+
return this._pseudo.before[state] || this._pseudo.after[state]
|
|
846
1076
|
? HOVER_PSEUDO_GENERATED
|
|
847
1077
|
: HOVER_PSEUDO_NONE;
|
|
848
1078
|
}
|
|
@@ -857,18 +1087,12 @@ export class Cascade {
|
|
|
857
1087
|
* table restyle the rows after it.
|
|
858
1088
|
*/
|
|
859
1089
|
hoverTouches(el) {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
});
|
|
867
|
-
for (const { match, followed } of this._hoverMatchers) {
|
|
868
|
-
if (match(el))
|
|
869
|
-
return followed ? HOVER_FOLLOWED : HOVER_TOUCHED;
|
|
870
|
-
}
|
|
871
|
-
return HOVER_UNTOUCHED;
|
|
1090
|
+
return this._hoverRules.touches(el, (selector) => this._compile(selector));
|
|
1091
|
+
}
|
|
1092
|
+
/** `hoverTouches` for the focus: whether `el` taking the focus or giving
|
|
1093
|
+
* it up, or its `:focus-within` changing, can change a style. */
|
|
1094
|
+
focusTouches(el) {
|
|
1095
|
+
return this._focusRules.touches(el, (selector) => this._compile(selector));
|
|
872
1096
|
}
|
|
873
1097
|
/**
|
|
874
1098
|
* The elements whose `:has()` may have changed its answer as `el` was
|
|
@@ -879,63 +1103,11 @@ export class Cascade {
|
|
|
879
1103
|
* siblings can change with it (`hoverTouches`).
|
|
880
1104
|
*/
|
|
881
1105
|
hoverAnchors(el, into) {
|
|
882
|
-
|
|
883
|
-
return;
|
|
884
|
-
this._anchorMatchers ??= [...this._hoverAnchors]
|
|
885
|
-
.sort(([, a], [, b]) => Number(b.followed) - Number(a.followed))
|
|
886
|
-
.flatMap(([anchor, { siblings, followed }]) => {
|
|
887
|
-
const match = this._compile(anchor);
|
|
888
|
-
return match ? [{ match, siblings, followed }] : [];
|
|
889
|
-
});
|
|
890
|
-
const note = (at, followed) => {
|
|
891
|
-
if (followed)
|
|
892
|
-
into.set(at, HOVER_FOLLOWED);
|
|
893
|
-
else if (!into.has(at))
|
|
894
|
-
into.set(at, HOVER_TOUCHED);
|
|
895
|
-
};
|
|
896
|
-
const siblings = this._anchorMatchers.some((m) => m.siblings);
|
|
897
|
-
for (let at = el; at;) {
|
|
898
|
-
if (at !== el) {
|
|
899
|
-
for (const { match, followed } of this._anchorMatchers) {
|
|
900
|
-
if (match(at)) {
|
|
901
|
-
note(at, followed);
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
if (siblings) {
|
|
907
|
-
for (let s = at.prev; s; s = s.prev) {
|
|
908
|
-
if (!isTag(s))
|
|
909
|
-
continue;
|
|
910
|
-
for (const m of this._anchorMatchers) {
|
|
911
|
-
if (m.siblings && m.match(s)) {
|
|
912
|
-
note(s, m.followed);
|
|
913
|
-
break;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
const parent = at.parent;
|
|
919
|
-
at = parent && isTag(parent) ? parent : null;
|
|
920
|
-
}
|
|
1106
|
+
this._hoverRules.anchors(el, into, (selector) => this._compile(selector));
|
|
921
1107
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const found = pointerCompounds(selector);
|
|
926
|
-
if (found.nested)
|
|
927
|
-
this.hoverLocal = false;
|
|
928
|
-
const followed = new Set(found.followed);
|
|
929
|
-
for (const c of found.compounds) {
|
|
930
|
-
this._hoverCompounds.set(c, followed.has(c) || (this._hoverCompounds.get(c) ?? false));
|
|
931
|
-
}
|
|
932
|
-
for (const { anchor, siblings } of found.has) {
|
|
933
|
-
const was = this._hoverAnchors.get(anchor);
|
|
934
|
-
this._hoverAnchors.set(anchor, {
|
|
935
|
-
siblings: siblings || (was?.siblings ?? false),
|
|
936
|
-
followed: followed.has(anchor) || (was?.followed ?? false),
|
|
937
|
-
});
|
|
938
|
-
}
|
|
1108
|
+
/** `hoverAnchors` for the focus: `form:has(input:focus)`'s form. */
|
|
1109
|
+
focusAnchors(el, into) {
|
|
1110
|
+
this._focusRules.anchors(el, into, (selector) => this._compile(selector));
|
|
939
1111
|
}
|
|
940
1112
|
/** A selector compiled as a rule's is, or null where css-select refuses
|
|
941
1113
|
* it, as a rule it refuses drops out of the cascade. */
|
|
@@ -1106,6 +1278,11 @@ export class Cascade {
|
|
|
1106
1278
|
if (this._pointer.active.has(el))
|
|
1107
1279
|
key += '\u0001:active';
|
|
1108
1280
|
}
|
|
1281
|
+
// `:focus-within` is not shared under (`UNSHAREABLE`), and the focus
|
|
1282
|
+
// is on one element
|
|
1283
|
+
if (this._focus.element === el && this._index.focusSensitive) {
|
|
1284
|
+
key += this._focus.visible ? '\u0001:focus-visible' : '\u0001:focus';
|
|
1285
|
+
}
|
|
1109
1286
|
const attribs = el.attribs;
|
|
1110
1287
|
for (const name in attribs) {
|
|
1111
1288
|
const value = attribs[name];
|
|
@@ -1389,6 +1566,8 @@ export class Cascade {
|
|
|
1389
1566
|
families: this._mapFamilies,
|
|
1390
1567
|
lh: () => this._lineHeightOf(parentStyle),
|
|
1391
1568
|
rlh: () => this._lineHeightOf(rootStyle),
|
|
1569
|
+
focusRing: this.look.focusRing,
|
|
1570
|
+
systemColors: this._systemColors,
|
|
1392
1571
|
};
|
|
1393
1572
|
// The family, the weight and the slant go with the size: together they
|
|
1394
1573
|
// pick the face an `ex`, a `ch` or an `lh` in any declaration is
|
|
@@ -1503,7 +1682,9 @@ export class Cascade {
|
|
|
1503
1682
|
style.alignBlocks = null;
|
|
1504
1683
|
}
|
|
1505
1684
|
settleClamp(style, parentStyle);
|
|
1685
|
+
settleAlign(style, parentStyle);
|
|
1506
1686
|
settleOverflow(style);
|
|
1687
|
+
settleOutline(style, this.look, this.scale);
|
|
1507
1688
|
settleContentVisibility(style);
|
|
1508
1689
|
blockify(style, inFlexContainer);
|
|
1509
1690
|
// a button is laid out as one, whatever `display` it was given
|
|
@@ -1604,12 +1785,20 @@ export class Cascade {
|
|
|
1604
1785
|
* itself. The root's box has its paint, its opacity and whether it is
|
|
1605
1786
|
* shown from its computed style, so those are left out for it.
|
|
1606
1787
|
*
|
|
1788
|
+
* What a `<use>` draws is a copy, styled in a tree of its own
|
|
1789
|
+
* (`ShapeStyles.used`): the element it names is looked for as `copyTree`
|
|
1790
|
+
* looks — the drawing's own first, then the document's — and what is in
|
|
1791
|
+
* it asked of where it stands in that tree, once a build for all the
|
|
1792
|
+
* `<use>`s that name it (`ShapeCopies`). An icon sprite's `<symbol>`,
|
|
1793
|
+
* outside the drawing in a hidden `<svg>` of them, is styled that way,
|
|
1794
|
+
* and so is one of the drawing's own.
|
|
1795
|
+
*
|
|
1607
1796
|
* Asked as a drawing is first painted (`BoxTree.shapeStyler`), of the
|
|
1608
1797
|
* rules in `_shapes` alone: a page of hundreds of icons pays for the ones
|
|
1609
1798
|
* it shows, a look in the buckets for each of their elements where no
|
|
1610
1799
|
* rule reaches them, and a document with no such rule nothing.
|
|
1611
1800
|
*/
|
|
1612
|
-
shapeStyles(root, style) {
|
|
1801
|
+
shapeStyles(root, style, copies = null) {
|
|
1613
1802
|
const index = this._shapes;
|
|
1614
1803
|
if (!index.size)
|
|
1615
1804
|
return null;
|
|
@@ -1620,9 +1809,9 @@ export class Cascade {
|
|
|
1620
1809
|
// id it asks of an ancestor, which is looked for once for the drawing,
|
|
1621
1810
|
// around it and in it, where matching the rule would look for it from
|
|
1622
1811
|
// each of its elements. On a page with none of them around a drawing,
|
|
1623
|
-
// nothing is matched at all.
|
|
1812
|
+
// nothing is matched at all. A copy's tree has nothing around it.
|
|
1624
1813
|
const needs = (this._shapeNeeds ??= this._needsOfShapes());
|
|
1625
|
-
|
|
1814
|
+
let found = new Set();
|
|
1626
1815
|
const asks = needs.classes.size > 0 || needs.ids.size > 0;
|
|
1627
1816
|
const note = (el) => {
|
|
1628
1817
|
if (needs.ids.size) {
|
|
@@ -1660,42 +1849,46 @@ export class Cascade {
|
|
|
1660
1849
|
// nothing else to say of it.
|
|
1661
1850
|
const liveForRoot = (indexed) => (indexed.root ??= declaresRootShape(indexed.rule.declarations)) &&
|
|
1662
1851
|
live(indexed);
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
const child = children[i];
|
|
1679
|
-
if (child.type === 'tag')
|
|
1680
|
-
stack.push(child);
|
|
1852
|
+
/** The elements under `top` and it, in document order, the top first:
|
|
1853
|
+
* its ancestors are noted before an element is asked of. Each with
|
|
1854
|
+
* the rules it matched, in cascade order, where it matched any. */
|
|
1855
|
+
const walk = (top, inCopy, each) => {
|
|
1856
|
+
const stack = [top];
|
|
1857
|
+
while (stack.length) {
|
|
1858
|
+
const el = stack.pop();
|
|
1859
|
+
// what is in a `<foreignObject>` is not the drawing's to draw
|
|
1860
|
+
if (el.name !== 'foreignobject' && el.name !== 'foreignObject') {
|
|
1861
|
+
const children = el.children;
|
|
1862
|
+
for (let i = children.length - 1; i >= 0; i -= 1) {
|
|
1863
|
+
const child = children[i];
|
|
1864
|
+
if (child.type === 'tag')
|
|
1865
|
+
stack.push(child);
|
|
1866
|
+
}
|
|
1681
1867
|
}
|
|
1868
|
+
if (asks)
|
|
1869
|
+
note(el);
|
|
1870
|
+
const tag = tagOf(el);
|
|
1871
|
+
const asked = found.size
|
|
1872
|
+
? index.universal.length > 0 || index.byTag.has(tag)
|
|
1873
|
+
: needs.universal || needs.tags.has(tag);
|
|
1874
|
+
if (!asked && !index.names(el)) {
|
|
1875
|
+
each(el, null);
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
const candidates = [];
|
|
1879
|
+
this._matchInto(index, el, candidates, undefined, el === root && !inCopy ? liveForRoot : live, inCopy);
|
|
1880
|
+
if (candidates.length) {
|
|
1881
|
+
const inline = attr(el, 'style');
|
|
1882
|
+
if (inline)
|
|
1883
|
+
pushInlineShapes(candidates, inline);
|
|
1884
|
+
candidates.sort(byCascade);
|
|
1885
|
+
}
|
|
1886
|
+
each(el, candidates.length ? candidates : null);
|
|
1682
1887
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
? index.universal.length > 0 || index.byTag.has(tag)
|
|
1688
|
-
: needs.universal || needs.tags.has(tag);
|
|
1689
|
-
if (!asked && !index.names(el))
|
|
1690
|
-
continue;
|
|
1691
|
-
const candidates = [];
|
|
1692
|
-
this._matchInto(index, el, candidates, undefined, el === root ? liveForRoot : live);
|
|
1693
|
-
if (!candidates.length)
|
|
1694
|
-
continue;
|
|
1695
|
-
const inline = attr(el, 'style');
|
|
1696
|
-
if (inline)
|
|
1697
|
-
pushInlineShapes(candidates, inline);
|
|
1698
|
-
candidates.sort(byCascade);
|
|
1888
|
+
};
|
|
1889
|
+
let ctx = null;
|
|
1890
|
+
/** What an element's rules come to, against the drawing's style. */
|
|
1891
|
+
const resolve = (candidates, isRoot) => {
|
|
1699
1892
|
ctx ??= {
|
|
1700
1893
|
color: style.color,
|
|
1701
1894
|
scheme: style.colorScheme,
|
|
@@ -1706,6 +1899,7 @@ export class Cascade {
|
|
|
1706
1899
|
vh: this.viewportHeight,
|
|
1707
1900
|
scale: this.scale,
|
|
1708
1901
|
initial: this.initial,
|
|
1902
|
+
systemColors: this._systemColors,
|
|
1709
1903
|
},
|
|
1710
1904
|
};
|
|
1711
1905
|
let own = null;
|
|
@@ -1713,7 +1907,7 @@ export class Cascade {
|
|
|
1713
1907
|
for (const d of pick(c)) {
|
|
1714
1908
|
if (!isShapeProp(d.prop))
|
|
1715
1909
|
continue;
|
|
1716
|
-
if (
|
|
1910
|
+
if (isRoot && ROOT_BOX_PROPS.has(d.prop))
|
|
1717
1911
|
continue;
|
|
1718
1912
|
// a `var()` with nothing to stand for it is invalid at
|
|
1719
1913
|
// computed-value time, and the property as though `unset`
|
|
@@ -1722,15 +1916,119 @@ export class Cascade {
|
|
|
1722
1916
|
(own ??= {})[d.prop] = value;
|
|
1723
1917
|
}
|
|
1724
1918
|
}
|
|
1919
|
+
return own;
|
|
1920
|
+
};
|
|
1921
|
+
// an element's place in the walks is what the key names it by
|
|
1922
|
+
let key = '';
|
|
1923
|
+
let place = -1;
|
|
1924
|
+
const named = (own, at) => {
|
|
1925
|
+
let part = `${at}{`;
|
|
1926
|
+
for (const prop in own)
|
|
1927
|
+
part += `${prop}:${own[prop]};`;
|
|
1928
|
+
return `${part}}`;
|
|
1929
|
+
};
|
|
1930
|
+
// (made as they are first needed: most drawings need none of them)
|
|
1931
|
+
let of = null;
|
|
1932
|
+
// the `<use>`s met, and the drawing's own elements by their ids, which
|
|
1933
|
+
// one looks in first
|
|
1934
|
+
let uses = null;
|
|
1935
|
+
let ids = null;
|
|
1936
|
+
walk(root, false, (el, candidates) => {
|
|
1937
|
+
place += 1;
|
|
1938
|
+
if (isUse(el))
|
|
1939
|
+
(uses ??= []).push(el);
|
|
1940
|
+
if (el !== root) {
|
|
1941
|
+
const id = el.attribs.id;
|
|
1942
|
+
if (id && !ids?.has(id))
|
|
1943
|
+
(ids ??= new Map()).set(id, el);
|
|
1944
|
+
}
|
|
1945
|
+
const own = candidates && resolve(candidates, el === root);
|
|
1725
1946
|
if (!own)
|
|
1726
|
-
|
|
1947
|
+
return;
|
|
1727
1948
|
(of ??= new Map()).set(el, own);
|
|
1728
|
-
key +=
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
key
|
|
1949
|
+
key += named(own, place);
|
|
1950
|
+
});
|
|
1951
|
+
if (!uses)
|
|
1952
|
+
return of ? { of, used: null, key } : null;
|
|
1953
|
+
// Each element a `<use>` names, in the tree of its copy: once however
|
|
1954
|
+
// many name it, and one a copy's `<use>` names in turn, however deep —
|
|
1955
|
+
// `copyTree` stops at a depth, and a `<use>` that names what it is in
|
|
1956
|
+
// finds it being walked. What is matched there is the same for every
|
|
1957
|
+
// drawing, and kept for the build (`ShapeCopies`); what it comes to is
|
|
1958
|
+
// the drawing's, since every property the copy inherits is the
|
|
1959
|
+
// `<use>`'s, and its custom properties and its colour are the
|
|
1960
|
+
// drawing's, as they are for the rest of it — kept too, for each
|
|
1961
|
+
// style a drawing has, which the icons of a page mostly share.
|
|
1962
|
+
const byId = copies?.byId ?? ((id) => elementById(root, id));
|
|
1963
|
+
const matched = copies?.matched ?? new Map();
|
|
1964
|
+
let styled = copies?.resolved.get(style);
|
|
1965
|
+
if (!styled) {
|
|
1966
|
+
styled = new Map();
|
|
1967
|
+
copies?.resolved.set(style, styled);
|
|
1968
|
+
}
|
|
1969
|
+
const match = (target) => {
|
|
1970
|
+
let copy = matched.get(target);
|
|
1971
|
+
if (copy)
|
|
1972
|
+
return copy;
|
|
1973
|
+
const rules = [];
|
|
1974
|
+
const inner = [];
|
|
1975
|
+
let size = 0;
|
|
1976
|
+
found = new Set();
|
|
1977
|
+
this._copyTop = target;
|
|
1978
|
+
try {
|
|
1979
|
+
walk(target, true, (el, candidates) => {
|
|
1980
|
+
if (isUse(el))
|
|
1981
|
+
inner.push(el);
|
|
1982
|
+
if (candidates)
|
|
1983
|
+
rules.push({ el, at: size, candidates });
|
|
1984
|
+
size += 1;
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
finally {
|
|
1988
|
+
this._copyTop = null;
|
|
1989
|
+
}
|
|
1990
|
+
copy = { rules, size, uses: inner };
|
|
1991
|
+
matched.set(target, copy);
|
|
1992
|
+
return copy;
|
|
1993
|
+
};
|
|
1994
|
+
let used = null;
|
|
1995
|
+
let made = null;
|
|
1996
|
+
for (let i = 0; i < uses.length; i += 1) {
|
|
1997
|
+
const use = uses[i];
|
|
1998
|
+
const id = useHref(use);
|
|
1999
|
+
if (id === null)
|
|
2000
|
+
continue;
|
|
2001
|
+
const target = ids?.get(id) ?? byId(id);
|
|
2002
|
+
if (!target)
|
|
2003
|
+
continue;
|
|
2004
|
+
const copy = match(target);
|
|
2005
|
+
let done = styled.get(target);
|
|
2006
|
+
if (!done) {
|
|
2007
|
+
let into = null;
|
|
2008
|
+
let part = '';
|
|
2009
|
+
for (const { el, at, candidates } of copy.rules) {
|
|
2010
|
+
const own = resolve(candidates, false);
|
|
2011
|
+
if (!own)
|
|
2012
|
+
continue;
|
|
2013
|
+
(into ??= new Map()).set(el, own);
|
|
2014
|
+
part += named(own, at);
|
|
2015
|
+
}
|
|
2016
|
+
done = { into, key: part };
|
|
2017
|
+
styled.set(target, done);
|
|
2018
|
+
}
|
|
2019
|
+
if (!made?.has(target)) {
|
|
2020
|
+
(made ??= new Set()).add(target);
|
|
2021
|
+
for (const next of copy.uses)
|
|
2022
|
+
uses.push(next);
|
|
2023
|
+
// its place in the key is after what came before it
|
|
2024
|
+
if (done.key)
|
|
2025
|
+
key += `@${place + 1}:${done.key}`;
|
|
2026
|
+
place += copy.size;
|
|
2027
|
+
}
|
|
2028
|
+
if (done.into)
|
|
2029
|
+
(used ??= new Map()).set(use, done.into);
|
|
1732
2030
|
}
|
|
1733
|
-
return of ? { of, key } : null;
|
|
2031
|
+
return of || used ? { of: of ?? NO_SHAPES, used, key } : null;
|
|
1734
2032
|
}
|
|
1735
2033
|
/** `ShapeNeeds`, and each such rule's own (`IndexedRule.needs`). */
|
|
1736
2034
|
_needsOfShapes() {
|
|
@@ -1768,18 +2066,22 @@ export class Cascade {
|
|
|
1768
2066
|
* nothing else, since no other rule's answer is the pointer's. An element
|
|
1769
2067
|
* they answer the same for, under a parent whose style is what it was, has
|
|
1770
2068
|
* the style it had — which is most of what a move reaches: the rows of a
|
|
1771
|
-
* hovered table body, what is in a hovered card.
|
|
2069
|
+
* hovered table body, what is in a hovered card. The focus's move from
|
|
2070
|
+
* `wasFocus` likewise, the rules that test it asked under both.
|
|
1772
2071
|
*/
|
|
1773
|
-
pointerChanged(el, was) {
|
|
2072
|
+
pointerChanged(el, was, wasFocus = this._focus) {
|
|
1774
2073
|
const now = this._pointer;
|
|
2074
|
+
const nowFocus = this._focus;
|
|
1775
2075
|
const after = this._pointerAnswers(el);
|
|
1776
2076
|
this._pointer = was;
|
|
2077
|
+
this._focus = wasFocus;
|
|
1777
2078
|
const before = this._pointerAnswers(el);
|
|
1778
2079
|
this._pointer = now;
|
|
2080
|
+
this._focus = nowFocus;
|
|
1779
2081
|
return before !== after;
|
|
1780
2082
|
}
|
|
1781
|
-
/** The ids of the rules testing the pointer
|
|
1782
|
-
* stand, the element's and each pseudo-element's. */
|
|
2083
|
+
/** The ids of the rules testing the pointer or the focus that match
|
|
2084
|
+
* `el` as things stand, the element's and each pseudo-element's. */
|
|
1783
2085
|
_pointerAnswers(el) {
|
|
1784
2086
|
let answers = '';
|
|
1785
2087
|
const ask = (index, name) => {
|
|
@@ -1800,7 +2102,10 @@ export class Cascade {
|
|
|
1800
2102
|
* and their ids onto `matched` in the order they were tried. */
|
|
1801
2103
|
_matchInto(index, el, out, matched,
|
|
1802
2104
|
/** The rules to try, where some are known not to match without it. */
|
|
1803
|
-
live
|
|
2105
|
+
live,
|
|
2106
|
+
/** Whether `el` is matched where a `<use>` draws a copy of it
|
|
2107
|
+
* (`_copyTop`). */
|
|
2108
|
+
inCopy = false) {
|
|
1804
2109
|
// A media query's width is CSS pixels; the viewport is kept in device.
|
|
1805
2110
|
const width = this.viewportWidth / this.scale;
|
|
1806
2111
|
const height = this.viewportHeight / this.scale;
|
|
@@ -1814,19 +2119,37 @@ export class Cascade {
|
|
|
1814
2119
|
if (!mediaMatches(rule.media, width, this.look.colorScheme, height, this.scale)) {
|
|
1815
2120
|
continue;
|
|
1816
2121
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
indexed.
|
|
2122
|
+
let match;
|
|
2123
|
+
if (inCopy) {
|
|
2124
|
+
if (!indexed.inCopyCompiled) {
|
|
2125
|
+
indexed.inCopyCompiled = true;
|
|
2126
|
+
try {
|
|
2127
|
+
// what it finds above an element depends on which copy it is
|
|
2128
|
+
// in, so none of it is kept
|
|
2129
|
+
indexed.inCopy = compileSelector(rule.selector, this._copyAdapter, this._pseudos, false);
|
|
2130
|
+
}
|
|
2131
|
+
catch {
|
|
2132
|
+
indexed.inCopy = null;
|
|
2133
|
+
}
|
|
1821
2134
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
indexed.
|
|
2135
|
+
match = indexed.inCopy;
|
|
2136
|
+
}
|
|
2137
|
+
else {
|
|
2138
|
+
if (!indexed.compiled) {
|
|
2139
|
+
indexed.compiled = true;
|
|
2140
|
+
try {
|
|
2141
|
+
indexed.match = compileSelector(rule.selector, this._adapter, this._pseudos);
|
|
2142
|
+
}
|
|
2143
|
+
catch {
|
|
2144
|
+
// A selector this matcher does not know (`::-moz-…`, a CSS4
|
|
2145
|
+
// form it has not learnt) drops out of the cascade rather than
|
|
2146
|
+
// out of the render.
|
|
2147
|
+
indexed.match = null;
|
|
2148
|
+
}
|
|
1827
2149
|
}
|
|
2150
|
+
match = indexed.match;
|
|
1828
2151
|
}
|
|
1829
|
-
if (!
|
|
2152
|
+
if (!match || !match(el))
|
|
1830
2153
|
continue;
|
|
1831
2154
|
matched?.push(indexed.id);
|
|
1832
2155
|
if (out === null)
|
|
@@ -1855,6 +2178,16 @@ export class Cascade {
|
|
|
1855
2178
|
const out = [];
|
|
1856
2179
|
this._matchInto(this._index, el, out, matched);
|
|
1857
2180
|
const hints = presentationHints(el);
|
|
2181
|
+
// the page's colour schemes are its root's where no rule sets them
|
|
2182
|
+
if (this.pageColorScheme &&
|
|
2183
|
+
el.name === 'html' &&
|
|
2184
|
+
!(el.parent && isTag(el.parent))) {
|
|
2185
|
+
hints.push({
|
|
2186
|
+
prop: 'color-scheme',
|
|
2187
|
+
value: this.pageColorScheme,
|
|
2188
|
+
important: false,
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
1858
2191
|
if (hints.length) {
|
|
1859
2192
|
out.push({
|
|
1860
2193
|
origin: Origin.Presentation,
|
|
@@ -1920,9 +2253,22 @@ function dropPaletteChrome(out) {
|
|
|
1920
2253
|
if (!chrome)
|
|
1921
2254
|
return;
|
|
1922
2255
|
let styled = false;
|
|
2256
|
+
// `appearance` by the value that wins: `appearance: none; appearance:
|
|
2257
|
+
// auto` keeps the control's look, as a reset a page takes back does
|
|
2258
|
+
let appearance = '';
|
|
1923
2259
|
for (let j = i; j < out.length && !styled; j += 1) {
|
|
1924
|
-
|
|
2260
|
+
for (const d of pick(out[j])) {
|
|
2261
|
+
if (d.prop === 'appearance' || d.prop === '-webkit-appearance') {
|
|
2262
|
+
const v = d.value.trim().toLowerCase();
|
|
2263
|
+
if (/^[a-z-]+$/.test(v))
|
|
2264
|
+
appearance = v;
|
|
2265
|
+
}
|
|
2266
|
+
else if (stylesChrome(d))
|
|
2267
|
+
styled = true;
|
|
2268
|
+
}
|
|
1925
2269
|
}
|
|
2270
|
+
if (appearance === 'none')
|
|
2271
|
+
styled = true;
|
|
1926
2272
|
if (!styled)
|
|
1927
2273
|
return;
|
|
1928
2274
|
let kept = 0;
|
|
@@ -1935,8 +2281,9 @@ function dropPaletteChrome(out) {
|
|
|
1935
2281
|
out.length = kept;
|
|
1936
2282
|
}
|
|
1937
2283
|
/** Whether a declaration styles what a control's native look draws: Blink's
|
|
1938
|
-
* `is_background` and `is_border` properties and their shorthands
|
|
1939
|
-
* `appearance` of `none
|
|
2284
|
+
* `is_background` and `is_border` properties and their shorthands. An
|
|
2285
|
+
* `appearance` of `none` does too, where it is the one that wins
|
|
2286
|
+
* (`dropPaletteChrome`). */
|
|
1940
2287
|
function stylesChrome(d) {
|
|
1941
2288
|
const prop = d.prop;
|
|
1942
2289
|
if (prop.startsWith('background')) {
|
|
@@ -1945,8 +2292,7 @@ function stylesChrome(d) {
|
|
|
1945
2292
|
if (prop.startsWith('border')) {
|
|
1946
2293
|
return prop !== 'border-collapse' && prop !== 'border-spacing';
|
|
1947
2294
|
}
|
|
1948
|
-
return
|
|
1949
|
-
d.value.trim().toLowerCase() === 'none');
|
|
2295
|
+
return false;
|
|
1950
2296
|
}
|
|
1951
2297
|
/** An implied element's style as the root box's: its look, not its role —
|
|
1952
2298
|
* the root is still a block, in flow, and the initial containing block. */
|
|
@@ -1960,17 +2306,21 @@ function asRoot(style) {
|
|
|
1960
2306
|
* fragment, whose style the root box takes (`rootStyle`), and never a
|
|
1961
2307
|
* fragment's top-level elements, which css-select would take for it.
|
|
1962
2308
|
*
|
|
1963
|
-
* `:focus` and its two kin
|
|
1964
|
-
*
|
|
1965
|
-
* the
|
|
1966
|
-
*
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
2309
|
+
* `:focus` and its two kin are here as matching nothing, and each cascade
|
|
2310
|
+
* answers them from where the focus is (`Cascade.setFocus`): no element
|
|
2311
|
+
* of the document takes it itself — a control's widget does, beside it.
|
|
2312
|
+
* css-select has none of the three and throws on them, which dropped
|
|
2313
|
+
* every rule that named one, `:not(:focus)` among them: Wikipedia's skip
|
|
2314
|
+
* link hides with `.mw-jump-link:not(:focus)`, and without it stood in
|
|
2315
|
+
* the flow at the top of every page. */
|
|
1969
2316
|
const PSEUDOS = {
|
|
1970
2317
|
root: (el) => el.name === 'html' && !(el.parent && isTag(el.parent)),
|
|
1971
|
-
focus: () => false,
|
|
1972
|
-
'focus-visible': () => false,
|
|
1973
|
-
'focus-within': () => false,
|
|
2318
|
+
focus: (_el) => false,
|
|
2319
|
+
'focus-visible': (_el) => false,
|
|
2320
|
+
'focus-within': (_el) => false,
|
|
2321
|
+
// css-select has none, and threw on one, so `:not(:target)` matched
|
|
2322
|
+
// nothing: a document is drawn at no fragment, an SVG image at its own
|
|
2323
|
+
target: (_el) => false,
|
|
1974
2324
|
// css-select's, but its ranges and tags lower-cased as ASCII has it, and
|
|
1975
2325
|
// no other script (CSS 2.1 4.1.3): Unicode's took `:lang(\u212Al)`, a
|
|
1976
2326
|
// Kelvin sign for the K, for `:lang(kl)`
|
|
@@ -1980,29 +2330,32 @@ const PSEUDOS = {
|
|
|
1980
2330
|
.map((range) => range.trim().replace(/^['"]|['"]$/g, ''))
|
|
1981
2331
|
.filter((range) => range.length > 0)
|
|
1982
2332
|
.map((range) => asciiLower(range).split('-'));
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
const value = node.attribs['xml:lang'] ?? node.attribs.lang;
|
|
1986
|
-
if (value != null) {
|
|
1987
|
-
if (!value)
|
|
1988
|
-
return ranges.some((range) => range[0] === '');
|
|
1989
|
-
const tag = asciiLower(value).split('-');
|
|
1990
|
-
return ranges.some((range) => langRangeMatches(tag, range));
|
|
1991
|
-
}
|
|
1992
|
-
root = node;
|
|
1993
|
-
const parent = node.parent;
|
|
1994
|
-
node = parent && isTag(parent) ? parent : null;
|
|
1995
|
-
}
|
|
1996
|
-
// with no element saying, the document's, as its `<meta>` sets it —
|
|
1997
|
-
// searched from the document, where a fragment's `<meta>` is a
|
|
1998
|
-
// sibling of what it covers
|
|
1999
|
-
const pragma = pragmaLanguage(root.parent ?? root);
|
|
2000
|
-
if (!pragma)
|
|
2333
|
+
const value = languageOf(el);
|
|
2334
|
+
if (!value)
|
|
2001
2335
|
return ranges.some((range) => range[0] === '');
|
|
2002
|
-
const tag = asciiLower(
|
|
2336
|
+
const tag = asciiLower(value).split('-');
|
|
2003
2337
|
return ranges.some((range) => langRangeMatches(tag, range));
|
|
2004
2338
|
},
|
|
2005
2339
|
};
|
|
2340
|
+
/**
|
|
2341
|
+
* An element's language: the `lang` or `xml:lang` of the nearest element
|
|
2342
|
+
* from it up that says one, or with none saying, the document's, as its
|
|
2343
|
+
* `<meta http-equiv="content-language">` sets it — searched from the
|
|
2344
|
+
* document, where a fragment's `<meta>` is a sibling of what it covers.
|
|
2345
|
+
* '' where none does, or the nearest says ''.
|
|
2346
|
+
*/
|
|
2347
|
+
export function languageOf(el) {
|
|
2348
|
+
let root = el;
|
|
2349
|
+
for (let node = el; node;) {
|
|
2350
|
+
const value = node.attribs['xml:lang'] ?? node.attribs.lang;
|
|
2351
|
+
if (value != null)
|
|
2352
|
+
return value;
|
|
2353
|
+
root = node;
|
|
2354
|
+
const parent = node.parent;
|
|
2355
|
+
node = parent && isTag(parent) ? parent : null;
|
|
2356
|
+
}
|
|
2357
|
+
return pragmaLanguage(root.parent ?? root);
|
|
2358
|
+
}
|
|
2006
2359
|
/** A length in a unit of the viewport: a number, then the unit, and no
|
|
2007
2360
|
* more of a name after it. */
|
|
2008
2361
|
const VIEWPORT_UNIT = /\d[sld]?v(?:w|h|i|b|min|max)(?![\w-])/i;
|
|
@@ -2053,6 +2406,34 @@ function pragmaLanguage(root) {
|
|
|
2053
2406
|
return lang;
|
|
2054
2407
|
}
|
|
2055
2408
|
const PRAGMA_LANGUAGE = new WeakMap();
|
|
2409
|
+
/**
|
|
2410
|
+
* The page's supported colour schemes: the content of the first `<meta
|
|
2411
|
+
* name="color-scheme">` that is a `color-scheme` value (HTML 4.2.5.4), or
|
|
2412
|
+
* null. Looked for where a `<meta>` is, at the top of a fragment and in
|
|
2413
|
+
* the `<html>` and its `<head>`, and not through the body.
|
|
2414
|
+
*/
|
|
2415
|
+
export function metaColorScheme(root) {
|
|
2416
|
+
const stack = [root];
|
|
2417
|
+
while (stack.length) {
|
|
2418
|
+
const node = stack.pop();
|
|
2419
|
+
for (const child of node.children) {
|
|
2420
|
+
if (!isTag(child))
|
|
2421
|
+
continue;
|
|
2422
|
+
const el = child;
|
|
2423
|
+
if (el.name === 'meta') {
|
|
2424
|
+
const name = el.attribs.name?.trim().toLowerCase();
|
|
2425
|
+
const content = el.attribs.content?.trim();
|
|
2426
|
+
if (name === 'color-scheme' && content && usedColorScheme(content)) {
|
|
2427
|
+
return content;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
else if (el.name === 'html' || el.name === 'head') {
|
|
2431
|
+
stack.push(el);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return null;
|
|
2436
|
+
}
|
|
2056
2437
|
/** RFC 4647's extended filtering, of a tag's subtags by a range's, as
|
|
2057
2438
|
* css-select does it. */
|
|
2058
2439
|
function langRangeMatches(tag, range) {
|
|
@@ -2096,6 +2477,25 @@ function setsContent(c) {
|
|
|
2096
2477
|
}
|
|
2097
2478
|
/** Whether a rule's declarations set a custom property, by the array. */
|
|
2098
2479
|
const CUSTOM_IN = new WeakMap();
|
|
2480
|
+
/** The document's first element with an id, looked for from one of its
|
|
2481
|
+
* elements: `shapeStyles`' answer where no index of them is handed in. */
|
|
2482
|
+
function elementById(from, id) {
|
|
2483
|
+
let top = from;
|
|
2484
|
+
while (top.parent)
|
|
2485
|
+
top = top.parent;
|
|
2486
|
+
const stack = [...top.children].reverse();
|
|
2487
|
+
for (let node = stack.pop(); node; node = stack.pop()) {
|
|
2488
|
+
if (node.type !== 'tag')
|
|
2489
|
+
continue;
|
|
2490
|
+
const el = node;
|
|
2491
|
+
if (el.attribs.id === id)
|
|
2492
|
+
return el;
|
|
2493
|
+
for (let i = el.children.length - 1; i >= 0; i -= 1) {
|
|
2494
|
+
stack.push(el.children[i]);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2099
2499
|
/** Whether declarations set a property a shape in a drawing has. */
|
|
2100
2500
|
function declaresShape(declarations) {
|
|
2101
2501
|
for (const d of declarations)
|