@react-x11/components 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +209 -23
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +873 -175
- 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 +62 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +214 -0
- package/dist/html/css/shapes.js.map +1 -0
- 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 +75 -27
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +15 -0
- 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 +38 -7
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +160 -50
- 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 +159 -20
- 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 +69 -19
- 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 +1648 -225
- 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 +23 -8
- 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 +907 -323
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +36 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +174 -37
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +4 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +769 -204
- 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 +50 -17
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +501 -83
- 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 +987 -176
- 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 +273 -0
- package/src/html/css/style.ts +123 -30
- package/src/html/css/ua.ts +15 -0
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +169 -56
- package/src/html/layout/boxes.ts +198 -24
- package/src/html/layout/css-grid.ts +89 -22
- package/src/html/layout/flex.ts +1863 -219
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +989 -333
- package/src/html/node.ts +204 -37
- package/src/html/paint.ts +999 -252
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +600 -78
- 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';
|
|
22
|
-
import {
|
|
23
|
+
import { systemColorTable, usedColorScheme } from './color.js';
|
|
23
24
|
import { asciiLower, escapeEnd, mediaMatches, readIdent, startsIdent, } from './parse.js';
|
|
24
|
-
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';
|
|
25
26
|
import { parseDeclarations } from './parse.js';
|
|
26
27
|
import { CounterStyles, counterStyleRule } from './counter-styles.js';
|
|
27
28
|
import { PALETTE_CHROME } from './ua.js';
|
|
28
29
|
import { viewportUnit } from './values.js';
|
|
29
30
|
import { customProperties, substituteIn } from './vars.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
|
|
@@ -104,6 +108,10 @@ class RuleIndex {
|
|
|
104
108
|
reaches(el) {
|
|
105
109
|
if (this.universal.length || this.byTag.has(tagOf(el)))
|
|
106
110
|
return true;
|
|
111
|
+
return this.names(el);
|
|
112
|
+
}
|
|
113
|
+
/** Whether a bucket here is for `el`'s id, or for a class of its. */
|
|
114
|
+
names(el) {
|
|
107
115
|
if (this.byId.size) {
|
|
108
116
|
const id = attr(el, 'id');
|
|
109
117
|
if (id && this.byId.has(id))
|
|
@@ -120,18 +128,24 @@ class RuleIndex {
|
|
|
120
128
|
}
|
|
121
129
|
return false;
|
|
122
130
|
}
|
|
123
|
-
add(rule) {
|
|
131
|
+
add(rule, key = rightmostKey(rule.selector)) {
|
|
124
132
|
this.size += 1;
|
|
125
133
|
const indexed = {
|
|
126
134
|
rule,
|
|
127
135
|
match: null,
|
|
128
136
|
compiled: false,
|
|
129
137
|
id: this._nextId++,
|
|
138
|
+
needs: null,
|
|
139
|
+
root: null,
|
|
140
|
+
inCopy: null,
|
|
141
|
+
inCopyCompiled: false,
|
|
130
142
|
};
|
|
131
143
|
if (rule.selector.includes(':hover') || rule.selector.includes(':active')) {
|
|
132
144
|
this.hoverSensitive = true;
|
|
133
145
|
}
|
|
134
|
-
const
|
|
146
|
+
const focus = FOCUS.test(rule.selector);
|
|
147
|
+
if (focus)
|
|
148
|
+
this.focusSensitive = true;
|
|
135
149
|
if (UNSHAREABLE.test(rule.selector)) {
|
|
136
150
|
if (key.kind === 'id')
|
|
137
151
|
this.ownStyleIds.add(key.name);
|
|
@@ -143,7 +157,7 @@ class RuleIndex {
|
|
|
143
157
|
this.ownStyleEverywhere = true;
|
|
144
158
|
}
|
|
145
159
|
this._bucket(key).push(indexed);
|
|
146
|
-
if (HOVER.test(rule.selector)) {
|
|
160
|
+
if (focus || HOVER.test(rule.selector)) {
|
|
147
161
|
this.pointer ??= new RuleIndex();
|
|
148
162
|
this.pointer.size += 1;
|
|
149
163
|
this.pointer._bucket(key).push(indexed);
|
|
@@ -216,8 +230,15 @@ function splitPseudoElement(rule) {
|
|
|
216
230
|
* handed none), so a selector that tests only it never changes as the
|
|
217
231
|
* pointer moves — Wikipedia's buttons' `:focus:not(:active)` among them.
|
|
218
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.
|
|
219
240
|
*/
|
|
220
|
-
export function pointerCompounds(selector) {
|
|
241
|
+
export function pointerCompounds(selector, kind = HOVER_STATE) {
|
|
221
242
|
const out = {
|
|
222
243
|
compounds: [],
|
|
223
244
|
siblings: false,
|
|
@@ -225,9 +246,21 @@ export function pointerCompounds(selector) {
|
|
|
225
246
|
has: [],
|
|
226
247
|
followed: [],
|
|
227
248
|
};
|
|
228
|
-
scanComplex(selector, out);
|
|
249
|
+
scanComplex(selector, out, kind);
|
|
229
250
|
return out;
|
|
230
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
|
+
};
|
|
231
264
|
/** The functions that take a selector list and match the element they are
|
|
232
265
|
* written on (Selectors 4, 4.2 to 4.4), under the names css-select and
|
|
233
266
|
* older sheets know them by. */
|
|
@@ -321,11 +354,11 @@ function closeOf(text, open) {
|
|
|
321
354
|
return text.length;
|
|
322
355
|
}
|
|
323
356
|
/** `pointerCompounds` for one complex selector, into `out`. */
|
|
324
|
-
function scanComplex(selector, out) {
|
|
357
|
+
function scanComplex(selector, out, kind) {
|
|
325
358
|
for (const { text, next } of compoundsOf(selector)) {
|
|
326
359
|
const compoundsFrom = out.compounds.length;
|
|
327
360
|
const hasFrom = out.has.length;
|
|
328
|
-
const found = scanCompound(text, out);
|
|
361
|
+
const found = scanCompound(text, out, kind);
|
|
329
362
|
const sibling = next === '+' || next === '~';
|
|
330
363
|
if (found.args.length) {
|
|
331
364
|
out.has.push({
|
|
@@ -355,9 +388,10 @@ function scanComplex(selector, out) {
|
|
|
355
388
|
* it (`bare`), whether it tests the element's own hover (`pointer`), the
|
|
356
389
|
* arguments of each `:has()` of the element's that tests it below (`args`),
|
|
357
390
|
* and whether a selector nested in it named another element's (`below`),
|
|
358
|
-
* whose compounds went into `out`.
|
|
391
|
+
* whose compounds went into `out`. Of the focus, likewise, for
|
|
392
|
+
* `FOCUS_STATE`.
|
|
359
393
|
*/
|
|
360
|
-
function scanCompound(text, out) {
|
|
394
|
+
function scanCompound(text, out, kind) {
|
|
361
395
|
let bare = '';
|
|
362
396
|
let pointer = false;
|
|
363
397
|
let below = false;
|
|
@@ -385,9 +419,9 @@ function scanCompound(text, out) {
|
|
|
385
419
|
else if (c === ']')
|
|
386
420
|
bracket = Math.max(0, bracket - 1);
|
|
387
421
|
else if (c === ':' && bracket === 0 && text[i + 1] !== ':') {
|
|
388
|
-
const m =
|
|
422
|
+
const m = STATE_PSEUDO_AT.exec(text.slice(i));
|
|
389
423
|
if (m) {
|
|
390
|
-
if (m[0]
|
|
424
|
+
if (kind.own.test(m[0]))
|
|
391
425
|
pointer = true;
|
|
392
426
|
i += m[0].length - 1;
|
|
393
427
|
continue;
|
|
@@ -399,7 +433,7 @@ function scanCompound(text, out) {
|
|
|
399
433
|
const arg = text.slice(open + 1, close);
|
|
400
434
|
const whole = text.slice(i, close + 1);
|
|
401
435
|
i = close;
|
|
402
|
-
if (!
|
|
436
|
+
if (!kind.any.test(arg)) {
|
|
403
437
|
bare += whole;
|
|
404
438
|
continue;
|
|
405
439
|
}
|
|
@@ -421,16 +455,16 @@ function scanCompound(text, out) {
|
|
|
421
455
|
const own = [];
|
|
422
456
|
let any = false;
|
|
423
457
|
for (const entry of selectorsOf(arg)) {
|
|
424
|
-
if (!
|
|
458
|
+
if (!kind.any.test(entry))
|
|
425
459
|
continue;
|
|
426
460
|
const parts = compoundsOf(entry);
|
|
427
461
|
if (parts.length !== 1) {
|
|
428
|
-
scanComplex(entry, out);
|
|
462
|
+
scanComplex(entry, out, kind);
|
|
429
463
|
below = true;
|
|
430
464
|
continue;
|
|
431
465
|
}
|
|
432
466
|
// of this element itself
|
|
433
|
-
const one = scanCompound(parts[0].text, out);
|
|
467
|
+
const one = scanCompound(parts[0].text, out, kind);
|
|
434
468
|
if (one.below)
|
|
435
469
|
below = true;
|
|
436
470
|
if (!one.pointer && !one.args.length)
|
|
@@ -462,26 +496,33 @@ function scanCompound(text, out) {
|
|
|
462
496
|
* start of a string. */
|
|
463
497
|
const FUNCTION_AT = /^:([\w-]+)\(/;
|
|
464
498
|
/** `:hover` anywhere in a selector. */
|
|
465
|
-
const HOVER =
|
|
466
|
-
/** `:
|
|
467
|
-
const
|
|
468
|
-
/**
|
|
469
|
-
|
|
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;
|
|
470
508
|
/**
|
|
471
509
|
* A selector compiled to a matcher over this adapter. css-select keeps the
|
|
472
510
|
* answers of a `:has()` above the subject, or of the ancestors a
|
|
473
511
|
* descendant combinator tried under one, for as long as the matcher lives
|
|
474
512
|
* (`cacheResults`) — which is the cascade's life, and a pointer move is no
|
|
475
|
-
* new cascade. So a selector that tests the pointer
|
|
476
|
-
* `#box:has(a:hover) .m` answered what it did before the
|
|
477
|
-
* 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.
|
|
478
516
|
*/
|
|
479
|
-
function compileSelector(selector, adapter
|
|
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) {
|
|
480
521
|
return compile(noEmptyWords(selector), {
|
|
481
522
|
adapter,
|
|
482
523
|
xmlMode: false,
|
|
483
|
-
pseudos
|
|
484
|
-
cacheResults: !
|
|
524
|
+
pseudos,
|
|
525
|
+
cacheResults: cache && !STATE_PSEUDO.test(selector),
|
|
485
526
|
});
|
|
486
527
|
}
|
|
487
528
|
function mapBucket(map, key) {
|
|
@@ -534,7 +575,10 @@ function rightmostKey(selector) {
|
|
|
534
575
|
start = i + 1;
|
|
535
576
|
}
|
|
536
577
|
}
|
|
537
|
-
|
|
578
|
+
return compoundKey(selector.slice(start));
|
|
579
|
+
}
|
|
580
|
+
/** A compound selector's most selective key (`rightmostKey`). */
|
|
581
|
+
function compoundKey(compound) {
|
|
538
582
|
let id = null;
|
|
539
583
|
let cls = null;
|
|
540
584
|
let tag = null;
|
|
@@ -577,6 +621,53 @@ function rightmostKey(selector) {
|
|
|
577
621
|
return { kind: 'tag', name: tag };
|
|
578
622
|
return { kind: 'any', name: '' };
|
|
579
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* The ids and classes a selector asks of its subject's ancestors: for each
|
|
626
|
+
* compound a descendant or a child combinator follows, its id, or else its
|
|
627
|
+
* first class, as `#name` or `.name`. Such a compound is an ancestor of
|
|
628
|
+
* the subject — one a sibling combinator follows is a sibling of one — so
|
|
629
|
+
* an element with no ancestor of some such name is not the rule's, which
|
|
630
|
+
* is known without matching it. A compound with neither asks nothing.
|
|
631
|
+
*/
|
|
632
|
+
function ancestorKeys(selector) {
|
|
633
|
+
const keys = [];
|
|
634
|
+
const compounds = compoundsOf(selector);
|
|
635
|
+
for (let i = 0; i < compounds.length - 1; i += 1) {
|
|
636
|
+
const { text, next } = compounds[i];
|
|
637
|
+
if (next !== ' ' && next !== '>')
|
|
638
|
+
continue;
|
|
639
|
+
const key = compoundKey(text);
|
|
640
|
+
if (key.kind === 'id')
|
|
641
|
+
keys.push(`#${key.name}`);
|
|
642
|
+
else if (key.kind === 'class')
|
|
643
|
+
keys.push(`.${key.name}`);
|
|
644
|
+
}
|
|
645
|
+
return keys;
|
|
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
|
+
}
|
|
580
671
|
function balancedEnd(text, from, open, close) {
|
|
581
672
|
let depth = 0;
|
|
582
673
|
for (let i = from; i < text.length; i += 1) {
|
|
@@ -591,6 +682,112 @@ function balancedEnd(text, from, open, close) {
|
|
|
591
682
|
return text.length;
|
|
592
683
|
}
|
|
593
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
|
+
}
|
|
594
791
|
function faceOf(style) {
|
|
595
792
|
return {
|
|
596
793
|
family: style.fontFamily,
|
|
@@ -618,23 +815,31 @@ export class Cascade {
|
|
|
618
815
|
marker: new RuleIndex(),
|
|
619
816
|
selection: new RuleIndex(),
|
|
620
817
|
};
|
|
818
|
+
/** The author's rules that could style a shape in a drawing: the ones
|
|
819
|
+
* that declare a property a shape has (`shapes.ts`) and whose subject
|
|
820
|
+
* is no element of HTML's alone. They are in `_index` too, for the
|
|
821
|
+
* elements that have boxes; these are asked of the ones in an inline
|
|
822
|
+
* `<svg>`, which have none (`shapeStyles`). */
|
|
823
|
+
_shapes = new RuleIndex();
|
|
824
|
+
_shapeNeeds = null;
|
|
621
825
|
_adapter;
|
|
622
|
-
/**
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
*
|
|
635
|
-
|
|
636
|
-
|
|
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);
|
|
637
841
|
_pointer = NO_POINTER;
|
|
842
|
+
_focus = NO_FOCUS;
|
|
638
843
|
initial;
|
|
639
844
|
look;
|
|
640
845
|
/** Viewport width the media queries were evaluated at, in device pixels
|
|
@@ -681,7 +886,37 @@ export class Cascade {
|
|
|
681
886
|
/** Whether any declaration has a length in `lh` or `rlh`: only then is
|
|
682
887
|
* the line height settled ahead of the declarations that read it. */
|
|
683
888
|
_lh = false;
|
|
684
|
-
|
|
889
|
+
/** The pseudo-classes css-select is handed: `:root` this cascade's, and
|
|
890
|
+
* the focus's three answered from `_focus`. */
|
|
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;
|
|
901
|
+
constructor(sheets, look, viewportWidth, viewportHeight, scale = 1, xHeight = null, zeroWidth = null, normalLine = null, families = null,
|
|
902
|
+
/** The document's element where it is not an `<html>`: an SVG image's
|
|
903
|
+
* `<svg>`, which is `:root` there (Selectors 4, 14.1). */
|
|
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
|
+
};
|
|
685
920
|
this._xHeightOf = xHeight;
|
|
686
921
|
this._zeroWidthOf = zeroWidth;
|
|
687
922
|
this._normalLineOf = normalLine;
|
|
@@ -690,6 +925,7 @@ export class Cascade {
|
|
|
690
925
|
? (list) => families.map(list)
|
|
691
926
|
: undefined;
|
|
692
927
|
this.look = look;
|
|
928
|
+
this._systemColors = systemColorTable(documentElement ? null : look);
|
|
693
929
|
this.initial = initialStyle(look, scale);
|
|
694
930
|
this.viewportWidth = viewportWidth;
|
|
695
931
|
this.viewportHeight = viewportHeight;
|
|
@@ -712,9 +948,18 @@ export class Cascade {
|
|
|
712
948
|
const pseudo = splitPseudoElement(rule);
|
|
713
949
|
if (pseudo)
|
|
714
950
|
this._pseudo[pseudo.which].add(pseudo.rule);
|
|
715
|
-
else
|
|
716
|
-
|
|
717
|
-
|
|
951
|
+
else {
|
|
952
|
+
const key = rightmostKey(rule.selector);
|
|
953
|
+
this._index.add(rule, key);
|
|
954
|
+
if (rule.order >= 0 &&
|
|
955
|
+
(key.kind !== 'tag' || SHAPE_TAGS.has(key.name)) &&
|
|
956
|
+
declaresShape(rule.declarations)) {
|
|
957
|
+
this._shapes.add(rule, key);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const selector = (pseudo?.rule ?? rule).selector;
|
|
961
|
+
this._hoverRules.note(selector);
|
|
962
|
+
this._focusRules.note(selector);
|
|
718
963
|
}
|
|
719
964
|
for (const bp of sheet.breakpoints)
|
|
720
965
|
breakpoints.add(bp);
|
|
@@ -744,6 +989,14 @@ export class Cascade {
|
|
|
744
989
|
isActive: (el) => this._pointer.active.has(el),
|
|
745
990
|
isVisited: () => false,
|
|
746
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
|
+
};
|
|
747
1000
|
}
|
|
748
1001
|
/** Whether declarations read the viewport's width or height (`vw`,
|
|
749
1002
|
* `vh`, `vmin`, `vmax`), noted on the cascade. */
|
|
@@ -774,6 +1027,30 @@ export class Cascade {
|
|
|
774
1027
|
setPointer(pointer) {
|
|
775
1028
|
this._pointer = pointer;
|
|
776
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
|
+
}
|
|
777
1054
|
/**
|
|
778
1055
|
* Which pseudo-elements' rules test the pointer: none's
|
|
779
1056
|
* (`HOVER_PSEUDO_NONE`); a `::before`'s or an `::after`'s only
|
|
@@ -783,13 +1060,19 @@ export class Cascade {
|
|
|
783
1060
|
* move cannot.
|
|
784
1061
|
*/
|
|
785
1062
|
get hoverPseudo() {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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]) {
|
|
789
1073
|
return HOVER_PSEUDO_OTHER;
|
|
790
1074
|
}
|
|
791
|
-
return this._pseudo.before
|
|
792
|
-
this._pseudo.after.hoverSensitive
|
|
1075
|
+
return this._pseudo.before[state] || this._pseudo.after[state]
|
|
793
1076
|
? HOVER_PSEUDO_GENERATED
|
|
794
1077
|
: HOVER_PSEUDO_NONE;
|
|
795
1078
|
}
|
|
@@ -804,18 +1087,12 @@ export class Cascade {
|
|
|
804
1087
|
* table restyle the rows after it.
|
|
805
1088
|
*/
|
|
806
1089
|
hoverTouches(el) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
});
|
|
814
|
-
for (const { match, followed } of this._hoverMatchers) {
|
|
815
|
-
if (match(el))
|
|
816
|
-
return followed ? HOVER_FOLLOWED : HOVER_TOUCHED;
|
|
817
|
-
}
|
|
818
|
-
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));
|
|
819
1096
|
}
|
|
820
1097
|
/**
|
|
821
1098
|
* The elements whose `:has()` may have changed its answer as `el` was
|
|
@@ -826,69 +1103,17 @@ export class Cascade {
|
|
|
826
1103
|
* siblings can change with it (`hoverTouches`).
|
|
827
1104
|
*/
|
|
828
1105
|
hoverAnchors(el, into) {
|
|
829
|
-
|
|
830
|
-
return;
|
|
831
|
-
this._anchorMatchers ??= [...this._hoverAnchors]
|
|
832
|
-
.sort(([, a], [, b]) => Number(b.followed) - Number(a.followed))
|
|
833
|
-
.flatMap(([anchor, { siblings, followed }]) => {
|
|
834
|
-
const match = this._compile(anchor);
|
|
835
|
-
return match ? [{ match, siblings, followed }] : [];
|
|
836
|
-
});
|
|
837
|
-
const note = (at, followed) => {
|
|
838
|
-
if (followed)
|
|
839
|
-
into.set(at, HOVER_FOLLOWED);
|
|
840
|
-
else if (!into.has(at))
|
|
841
|
-
into.set(at, HOVER_TOUCHED);
|
|
842
|
-
};
|
|
843
|
-
const siblings = this._anchorMatchers.some((m) => m.siblings);
|
|
844
|
-
for (let at = el; at;) {
|
|
845
|
-
if (at !== el) {
|
|
846
|
-
for (const { match, followed } of this._anchorMatchers) {
|
|
847
|
-
if (match(at)) {
|
|
848
|
-
note(at, followed);
|
|
849
|
-
break;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
if (siblings) {
|
|
854
|
-
for (let s = at.prev; s; s = s.prev) {
|
|
855
|
-
if (!isTag(s))
|
|
856
|
-
continue;
|
|
857
|
-
for (const m of this._anchorMatchers) {
|
|
858
|
-
if (m.siblings && m.match(s)) {
|
|
859
|
-
note(s, m.followed);
|
|
860
|
-
break;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
const parent = at.parent;
|
|
866
|
-
at = parent && isTag(parent) ? parent : null;
|
|
867
|
-
}
|
|
1106
|
+
this._hoverRules.anchors(el, into, (selector) => this._compile(selector));
|
|
868
1107
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const found = pointerCompounds(selector);
|
|
873
|
-
if (found.nested)
|
|
874
|
-
this.hoverLocal = false;
|
|
875
|
-
const followed = new Set(found.followed);
|
|
876
|
-
for (const c of found.compounds) {
|
|
877
|
-
this._hoverCompounds.set(c, followed.has(c) || (this._hoverCompounds.get(c) ?? false));
|
|
878
|
-
}
|
|
879
|
-
for (const { anchor, siblings } of found.has) {
|
|
880
|
-
const was = this._hoverAnchors.get(anchor);
|
|
881
|
-
this._hoverAnchors.set(anchor, {
|
|
882
|
-
siblings: siblings || (was?.siblings ?? false),
|
|
883
|
-
followed: followed.has(anchor) || (was?.followed ?? false),
|
|
884
|
-
});
|
|
885
|
-
}
|
|
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));
|
|
886
1111
|
}
|
|
887
1112
|
/** A selector compiled as a rule's is, or null where css-select refuses
|
|
888
1113
|
* it, as a rule it refuses drops out of the cascade. */
|
|
889
1114
|
_compile(selector) {
|
|
890
1115
|
try {
|
|
891
|
-
return compileSelector(selector, this._adapter);
|
|
1116
|
+
return compileSelector(selector, this._adapter, this._pseudos);
|
|
892
1117
|
}
|
|
893
1118
|
catch {
|
|
894
1119
|
return null;
|
|
@@ -1053,6 +1278,11 @@ export class Cascade {
|
|
|
1053
1278
|
if (this._pointer.active.has(el))
|
|
1054
1279
|
key += '\u0001:active';
|
|
1055
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
|
+
}
|
|
1056
1286
|
const attribs = el.attribs;
|
|
1057
1287
|
for (const name in attribs) {
|
|
1058
1288
|
const value = attribs[name];
|
|
@@ -1336,6 +1566,8 @@ export class Cascade {
|
|
|
1336
1566
|
families: this._mapFamilies,
|
|
1337
1567
|
lh: () => this._lineHeightOf(parentStyle),
|
|
1338
1568
|
rlh: () => this._lineHeightOf(rootStyle),
|
|
1569
|
+
focusRing: this.look.focusRing,
|
|
1570
|
+
systemColors: this._systemColors,
|
|
1339
1571
|
};
|
|
1340
1572
|
// The family, the weight and the slant go with the size: together they
|
|
1341
1573
|
// pick the face an `ex`, a `ch` or an `lh` in any declaration is
|
|
@@ -1450,7 +1682,9 @@ export class Cascade {
|
|
|
1450
1682
|
style.alignBlocks = null;
|
|
1451
1683
|
}
|
|
1452
1684
|
settleClamp(style, parentStyle);
|
|
1685
|
+
settleAlign(style, parentStyle);
|
|
1453
1686
|
settleOverflow(style);
|
|
1687
|
+
settleOutline(style, this.look, this.scale);
|
|
1454
1688
|
settleContentVisibility(style);
|
|
1455
1689
|
blockify(style, inFlexContainer);
|
|
1456
1690
|
// a button is laid out as one, whatever `display` it was given
|
|
@@ -1527,6 +1761,304 @@ export class Cascade {
|
|
|
1527
1761
|
// the root is still the initial containing block.
|
|
1528
1762
|
return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
|
|
1529
1763
|
}
|
|
1764
|
+
/** Whether any rule could style a shape in a drawing: a document with
|
|
1765
|
+
* none asks nothing of its drawings (`shapeStyles`). */
|
|
1766
|
+
get stylesShapes() {
|
|
1767
|
+
return this._shapes.size > 0;
|
|
1768
|
+
}
|
|
1769
|
+
/** Whether a pointer move can change what a rule gives a shape in a
|
|
1770
|
+
* drawing. */
|
|
1771
|
+
get shapesFollowPointer() {
|
|
1772
|
+
return this._shapes.hoverSensitive;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* What the rules give the elements of an inline drawing, `root` and what
|
|
1776
|
+
* is in it, or null where none reaches one: the narrow cascade of
|
|
1777
|
+
* `shapes.ts`. `style` is the root's computed style — its `color`, its
|
|
1778
|
+
* custom properties and its font size are what a value in the drawing is
|
|
1779
|
+
* read against, as though every element in it had inherited them, which
|
|
1780
|
+
* all but one a rule gives its own has.
|
|
1781
|
+
*
|
|
1782
|
+
* An element's rules and its `style` attribute are put in cascade order,
|
|
1783
|
+
* and each property is the last valid declaration's. The presentation
|
|
1784
|
+
* attributes are below them all (SVG 2, 6.2), and `SvgView` reads those
|
|
1785
|
+
* itself. The root's box has its paint, its opacity and whether it is
|
|
1786
|
+
* shown from its computed style, so those are left out for it.
|
|
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
|
+
*
|
|
1796
|
+
* Asked as a drawing is first painted (`BoxTree.shapeStyler`), of the
|
|
1797
|
+
* rules in `_shapes` alone: a page of hundreds of icons pays for the ones
|
|
1798
|
+
* it shows, a look in the buckets for each of their elements where no
|
|
1799
|
+
* rule reaches them, and a document with no such rule nothing.
|
|
1800
|
+
*/
|
|
1801
|
+
shapeStyles(root, style, copies = null) {
|
|
1802
|
+
const index = this._shapes;
|
|
1803
|
+
if (!index.size)
|
|
1804
|
+
return null;
|
|
1805
|
+
// The rules kept by a class or an id are asked of the elements with
|
|
1806
|
+
// that name. The rest — `.dark .logo path`, `.menu [aria-hidden]` — are
|
|
1807
|
+
// asked of every `<path>`, or of every element, and nearly all of them
|
|
1808
|
+
// are for some other part of the page: each says so by the class or the
|
|
1809
|
+
// id it asks of an ancestor, which is looked for once for the drawing,
|
|
1810
|
+
// around it and in it, where matching the rule would look for it from
|
|
1811
|
+
// each of its elements. On a page with none of them around a drawing,
|
|
1812
|
+
// nothing is matched at all. A copy's tree has nothing around it.
|
|
1813
|
+
const needs = (this._shapeNeeds ??= this._needsOfShapes());
|
|
1814
|
+
let found = new Set();
|
|
1815
|
+
const asks = needs.classes.size > 0 || needs.ids.size > 0;
|
|
1816
|
+
const note = (el) => {
|
|
1817
|
+
if (needs.ids.size) {
|
|
1818
|
+
const id = attr(el, 'id');
|
|
1819
|
+
if (id && needs.ids.has(id))
|
|
1820
|
+
found.add(`#${id}`);
|
|
1821
|
+
}
|
|
1822
|
+
if (needs.classes.size) {
|
|
1823
|
+
const className = attr(el, 'class');
|
|
1824
|
+
if (className) {
|
|
1825
|
+
for (const name of className.split(/\s+/)) {
|
|
1826
|
+
if (needs.classes.has(name))
|
|
1827
|
+
found.add(`.${name}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
if (asks) {
|
|
1833
|
+
let at = root.parent;
|
|
1834
|
+
while (at && at.type === 'tag') {
|
|
1835
|
+
note(at);
|
|
1836
|
+
at = at.parent;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
const live = (indexed) => {
|
|
1840
|
+
const keys = indexed.needs;
|
|
1841
|
+
if (keys)
|
|
1842
|
+
for (const key of keys)
|
|
1843
|
+
if (!found.has(key))
|
|
1844
|
+
return false;
|
|
1845
|
+
return true;
|
|
1846
|
+
};
|
|
1847
|
+
// The root's box has its paint from its computed style, so most of the
|
|
1848
|
+
// rules that name a drawing by its class — an icon set's — have
|
|
1849
|
+
// nothing else to say of it.
|
|
1850
|
+
const liveForRoot = (indexed) => (indexed.root ??= declaresRootShape(indexed.rule.declarations)) &&
|
|
1851
|
+
live(indexed);
|
|
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
|
+
}
|
|
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);
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
let ctx = null;
|
|
1890
|
+
/** What an element's rules come to, against the drawing's style. */
|
|
1891
|
+
const resolve = (candidates, isRoot) => {
|
|
1892
|
+
ctx ??= {
|
|
1893
|
+
color: style.color,
|
|
1894
|
+
scheme: style.colorScheme,
|
|
1895
|
+
units: {
|
|
1896
|
+
em: style.fontSize,
|
|
1897
|
+
rem: (this._root ?? this.initial).fontSize,
|
|
1898
|
+
vw: this.viewportWidth,
|
|
1899
|
+
vh: this.viewportHeight,
|
|
1900
|
+
scale: this.scale,
|
|
1901
|
+
initial: this.initial,
|
|
1902
|
+
systemColors: this._systemColors,
|
|
1903
|
+
},
|
|
1904
|
+
};
|
|
1905
|
+
let own = null;
|
|
1906
|
+
for (const c of candidates) {
|
|
1907
|
+
for (const d of pick(c)) {
|
|
1908
|
+
if (!isShapeProp(d.prop))
|
|
1909
|
+
continue;
|
|
1910
|
+
if (isRoot && ROOT_BOX_PROPS.has(d.prop))
|
|
1911
|
+
continue;
|
|
1912
|
+
// a `var()` with nothing to stand for it is invalid at
|
|
1913
|
+
// computed-value time, and the property as though `unset`
|
|
1914
|
+
const value = shapeValue(d.prop, d.vars ? (substituteIn(d.value, style.custom) ?? 'unset') : d.value, ctx);
|
|
1915
|
+
if (value !== null)
|
|
1916
|
+
(own ??= {})[d.prop] = value;
|
|
1917
|
+
}
|
|
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);
|
|
1946
|
+
if (!own)
|
|
1947
|
+
return;
|
|
1948
|
+
(of ??= new Map()).set(el, own);
|
|
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);
|
|
2030
|
+
}
|
|
2031
|
+
return of || used ? { of: of ?? NO_SHAPES, used, key } : null;
|
|
2032
|
+
}
|
|
2033
|
+
/** `ShapeNeeds`, and each such rule's own (`IndexedRule.needs`). */
|
|
2034
|
+
_needsOfShapes() {
|
|
2035
|
+
const index = this._shapes;
|
|
2036
|
+
const needs = {
|
|
2037
|
+
classes: new Set(),
|
|
2038
|
+
ids: new Set(),
|
|
2039
|
+
universal: false,
|
|
2040
|
+
tags: new Set(),
|
|
2041
|
+
};
|
|
2042
|
+
const read = (bucket) => {
|
|
2043
|
+
let free = false;
|
|
2044
|
+
for (const indexed of bucket) {
|
|
2045
|
+
const keys = ancestorKeys(indexed.rule.selector);
|
|
2046
|
+
indexed.needs = keys;
|
|
2047
|
+
if (!keys.length)
|
|
2048
|
+
free = true;
|
|
2049
|
+
for (const key of keys) {
|
|
2050
|
+
(key[0] === '#' ? needs.ids : needs.classes).add(key.slice(1));
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return free;
|
|
2054
|
+
};
|
|
2055
|
+
needs.universal = read(index.universal);
|
|
2056
|
+
for (const [tag, bucket] of index.byTag) {
|
|
2057
|
+
if (read(bucket))
|
|
2058
|
+
needs.tags.add(tag);
|
|
2059
|
+
}
|
|
2060
|
+
return needs;
|
|
2061
|
+
}
|
|
1530
2062
|
/**
|
|
1531
2063
|
* Whether the pointer's move from `was` to where it is changed which
|
|
1532
2064
|
* rules match `el`, or its `::before`, `::after` or another of its
|
|
@@ -1534,18 +2066,22 @@ export class Cascade {
|
|
|
1534
2066
|
* nothing else, since no other rule's answer is the pointer's. An element
|
|
1535
2067
|
* they answer the same for, under a parent whose style is what it was, has
|
|
1536
2068
|
* the style it had — which is most of what a move reaches: the rows of a
|
|
1537
|
-
* 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.
|
|
1538
2071
|
*/
|
|
1539
|
-
pointerChanged(el, was) {
|
|
2072
|
+
pointerChanged(el, was, wasFocus = this._focus) {
|
|
1540
2073
|
const now = this._pointer;
|
|
2074
|
+
const nowFocus = this._focus;
|
|
1541
2075
|
const after = this._pointerAnswers(el);
|
|
1542
2076
|
this._pointer = was;
|
|
2077
|
+
this._focus = wasFocus;
|
|
1543
2078
|
const before = this._pointerAnswers(el);
|
|
1544
2079
|
this._pointer = now;
|
|
2080
|
+
this._focus = nowFocus;
|
|
1545
2081
|
return before !== after;
|
|
1546
2082
|
}
|
|
1547
|
-
/** The ids of the rules testing the pointer
|
|
1548
|
-
* 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. */
|
|
1549
2085
|
_pointerAnswers(el) {
|
|
1550
2086
|
let answers = '';
|
|
1551
2087
|
const ask = (index, name) => {
|
|
@@ -1564,7 +2100,12 @@ export class Cascade {
|
|
|
1564
2100
|
}
|
|
1565
2101
|
/** The rules of `index` that match `el`, pushed onto `out` as candidates,
|
|
1566
2102
|
* and their ids onto `matched` in the order they were tried. */
|
|
1567
|
-
_matchInto(index, el, out, matched
|
|
2103
|
+
_matchInto(index, el, out, matched,
|
|
2104
|
+
/** The rules to try, where some are known not to match without it. */
|
|
2105
|
+
live,
|
|
2106
|
+
/** Whether `el` is matched where a `<use>` draws a copy of it
|
|
2107
|
+
* (`_copyTop`). */
|
|
2108
|
+
inCopy = false) {
|
|
1568
2109
|
// A media query's width is CSS pixels; the viewport is kept in device.
|
|
1569
2110
|
const width = this.viewportWidth / this.scale;
|
|
1570
2111
|
const height = this.viewportHeight / this.scale;
|
|
@@ -1572,23 +2113,43 @@ export class Cascade {
|
|
|
1572
2113
|
if (!bucket)
|
|
1573
2114
|
return;
|
|
1574
2115
|
for (const indexed of bucket) {
|
|
2116
|
+
if (live !== undefined && !live(indexed))
|
|
2117
|
+
continue;
|
|
1575
2118
|
const rule = indexed.rule;
|
|
1576
2119
|
if (!mediaMatches(rule.media, width, this.look.colorScheme, height, this.scale)) {
|
|
1577
2120
|
continue;
|
|
1578
2121
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
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
|
+
}
|
|
1583
2134
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
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
|
+
}
|
|
1589
2149
|
}
|
|
2150
|
+
match = indexed.match;
|
|
1590
2151
|
}
|
|
1591
|
-
if (!
|
|
2152
|
+
if (!match || !match(el))
|
|
1592
2153
|
continue;
|
|
1593
2154
|
matched?.push(indexed.id);
|
|
1594
2155
|
if (out === null)
|
|
@@ -1617,6 +2178,16 @@ export class Cascade {
|
|
|
1617
2178
|
const out = [];
|
|
1618
2179
|
this._matchInto(this._index, el, out, matched);
|
|
1619
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
|
+
}
|
|
1620
2191
|
if (hints.length) {
|
|
1621
2192
|
out.push({
|
|
1622
2193
|
origin: Origin.Presentation,
|
|
@@ -1682,9 +2253,22 @@ function dropPaletteChrome(out) {
|
|
|
1682
2253
|
if (!chrome)
|
|
1683
2254
|
return;
|
|
1684
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 = '';
|
|
1685
2259
|
for (let j = i; j < out.length && !styled; j += 1) {
|
|
1686
|
-
|
|
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
|
+
}
|
|
1687
2269
|
}
|
|
2270
|
+
if (appearance === 'none')
|
|
2271
|
+
styled = true;
|
|
1688
2272
|
if (!styled)
|
|
1689
2273
|
return;
|
|
1690
2274
|
let kept = 0;
|
|
@@ -1697,8 +2281,9 @@ function dropPaletteChrome(out) {
|
|
|
1697
2281
|
out.length = kept;
|
|
1698
2282
|
}
|
|
1699
2283
|
/** Whether a declaration styles what a control's native look draws: Blink's
|
|
1700
|
-
* `is_background` and `is_border` properties and their shorthands
|
|
1701
|
-
* `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`). */
|
|
1702
2287
|
function stylesChrome(d) {
|
|
1703
2288
|
const prop = d.prop;
|
|
1704
2289
|
if (prop.startsWith('background')) {
|
|
@@ -1707,8 +2292,7 @@ function stylesChrome(d) {
|
|
|
1707
2292
|
if (prop.startsWith('border')) {
|
|
1708
2293
|
return prop !== 'border-collapse' && prop !== 'border-spacing';
|
|
1709
2294
|
}
|
|
1710
|
-
return
|
|
1711
|
-
d.value.trim().toLowerCase() === 'none');
|
|
2295
|
+
return false;
|
|
1712
2296
|
}
|
|
1713
2297
|
/** An implied element's style as the root box's: its look, not its role —
|
|
1714
2298
|
* the root is still a block, in flow, and the initial containing block. */
|
|
@@ -1722,17 +2306,21 @@ function asRoot(style) {
|
|
|
1722
2306
|
* fragment, whose style the root box takes (`rootStyle`), and never a
|
|
1723
2307
|
* fragment's top-level elements, which css-select would take for it.
|
|
1724
2308
|
*
|
|
1725
|
-
* `:focus` and its two kin
|
|
1726
|
-
*
|
|
1727
|
-
* the
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
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. */
|
|
1731
2316
|
const PSEUDOS = {
|
|
1732
2317
|
root: (el) => el.name === 'html' && !(el.parent && isTag(el.parent)),
|
|
1733
|
-
focus: () => false,
|
|
1734
|
-
'focus-visible': () => false,
|
|
1735
|
-
'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,
|
|
1736
2324
|
// css-select's, but its ranges and tags lower-cased as ASCII has it, and
|
|
1737
2325
|
// no other script (CSS 2.1 4.1.3): Unicode's took `:lang(\u212Al)`, a
|
|
1738
2326
|
// Kelvin sign for the K, for `:lang(kl)`
|
|
@@ -1742,29 +2330,32 @@ const PSEUDOS = {
|
|
|
1742
2330
|
.map((range) => range.trim().replace(/^['"]|['"]$/g, ''))
|
|
1743
2331
|
.filter((range) => range.length > 0)
|
|
1744
2332
|
.map((range) => asciiLower(range).split('-'));
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
const value = node.attribs['xml:lang'] ?? node.attribs.lang;
|
|
1748
|
-
if (value != null) {
|
|
1749
|
-
if (!value)
|
|
1750
|
-
return ranges.some((range) => range[0] === '');
|
|
1751
|
-
const tag = asciiLower(value).split('-');
|
|
1752
|
-
return ranges.some((range) => langRangeMatches(tag, range));
|
|
1753
|
-
}
|
|
1754
|
-
root = node;
|
|
1755
|
-
const parent = node.parent;
|
|
1756
|
-
node = parent && isTag(parent) ? parent : null;
|
|
1757
|
-
}
|
|
1758
|
-
// with no element saying, the document's, as its `<meta>` sets it —
|
|
1759
|
-
// searched from the document, where a fragment's `<meta>` is a
|
|
1760
|
-
// sibling of what it covers
|
|
1761
|
-
const pragma = pragmaLanguage(root.parent ?? root);
|
|
1762
|
-
if (!pragma)
|
|
2333
|
+
const value = languageOf(el);
|
|
2334
|
+
if (!value)
|
|
1763
2335
|
return ranges.some((range) => range[0] === '');
|
|
1764
|
-
const tag = asciiLower(
|
|
2336
|
+
const tag = asciiLower(value).split('-');
|
|
1765
2337
|
return ranges.some((range) => langRangeMatches(tag, range));
|
|
1766
2338
|
},
|
|
1767
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
|
+
}
|
|
1768
2359
|
/** A length in a unit of the viewport: a number, then the unit, and no
|
|
1769
2360
|
* more of a name after it. */
|
|
1770
2361
|
const VIEWPORT_UNIT = /\d[sld]?v(?:w|h|i|b|min|max)(?![\w-])/i;
|
|
@@ -1815,6 +2406,34 @@ function pragmaLanguage(root) {
|
|
|
1815
2406
|
return lang;
|
|
1816
2407
|
}
|
|
1817
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
|
+
}
|
|
1818
2437
|
/** RFC 4647's extended filtering, of a tag's subtags by a range's, as
|
|
1819
2438
|
* css-select does it. */
|
|
1820
2439
|
function langRangeMatches(tag, range) {
|
|
@@ -1858,6 +2477,68 @@ function setsContent(c) {
|
|
|
1858
2477
|
}
|
|
1859
2478
|
/** Whether a rule's declarations set a custom property, by the array. */
|
|
1860
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
|
+
}
|
|
2499
|
+
/** Whether declarations set a property a shape in a drawing has. */
|
|
2500
|
+
function declaresShape(declarations) {
|
|
2501
|
+
for (const d of declarations)
|
|
2502
|
+
if (isShapeProp(d.prop))
|
|
2503
|
+
return true;
|
|
2504
|
+
return false;
|
|
2505
|
+
}
|
|
2506
|
+
/** Whether declarations set one a drawing's root does not have from its
|
|
2507
|
+
* box's style (`ROOT_BOX_PROPS`). */
|
|
2508
|
+
function declaresRootShape(declarations) {
|
|
2509
|
+
for (const d of declarations) {
|
|
2510
|
+
if (isShapeProp(d.prop) && !ROOT_BOX_PROPS.has(d.prop))
|
|
2511
|
+
return true;
|
|
2512
|
+
}
|
|
2513
|
+
return false;
|
|
2514
|
+
}
|
|
2515
|
+
/** A `style` attribute's declarations of a shape's properties, as
|
|
2516
|
+
* candidates: over every rule, and its `!important` ones over theirs. */
|
|
2517
|
+
function pushInlineShapes(out, inline) {
|
|
2518
|
+
const declarations = parseDeclarations(inline).filter((d) => isShapeProp(d.prop));
|
|
2519
|
+
const normal = declarations.filter((d) => !d.important);
|
|
2520
|
+
const important = declarations.filter((d) => d.important);
|
|
2521
|
+
if (normal.length) {
|
|
2522
|
+
out.push({
|
|
2523
|
+
origin: Origin.Inline,
|
|
2524
|
+
layer: null,
|
|
2525
|
+
specificity: 0,
|
|
2526
|
+
order: 0,
|
|
2527
|
+
declarations: normal,
|
|
2528
|
+
only: -1,
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
if (important.length) {
|
|
2532
|
+
out.push({
|
|
2533
|
+
origin: Origin.InlineImportant,
|
|
2534
|
+
layer: null,
|
|
2535
|
+
specificity: 0,
|
|
2536
|
+
order: 0,
|
|
2537
|
+
declarations: important,
|
|
2538
|
+
only: -1,
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
1861
2542
|
function pick(c) {
|
|
1862
2543
|
return c.only < 0 ? c.declarations : [c.declarations[c.only]];
|
|
1863
2544
|
}
|
|
@@ -2252,6 +2933,23 @@ function lengthAttr(value) {
|
|
|
2252
2933
|
const n = parseFloat(v);
|
|
2253
2934
|
return Number.isFinite(n) ? `${n}px` : v;
|
|
2254
2935
|
}
|
|
2936
|
+
/** A number and a unit, as an SVG's `width` and `height` are written. */
|
|
2937
|
+
const SVG_LENGTH = /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)\s*([a-z]*)\s*$/i;
|
|
2938
|
+
/**
|
|
2939
|
+
* The `width`/`height` of an inline `<svg>` as CSS: they are presentation
|
|
2940
|
+
* attributes for the properties of the same names (SVG 2, 5.1.1), so
|
|
2941
|
+
* `height="50%"` is a percentage of the containing block like a style's.
|
|
2942
|
+
* A bare number is pixels; null for what CSS would not parse.
|
|
2943
|
+
*/
|
|
2944
|
+
function svgSizeHint(value) {
|
|
2945
|
+
const v = value.trim();
|
|
2946
|
+
if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?%$/i.test(v))
|
|
2947
|
+
return v;
|
|
2948
|
+
const m = SVG_LENGTH.exec(v);
|
|
2949
|
+
if (!m)
|
|
2950
|
+
return null;
|
|
2951
|
+
return m[2] ? v : `${parseFloat(m[1])}px`;
|
|
2952
|
+
}
|
|
2255
2953
|
function closestTable(el) {
|
|
2256
2954
|
let node = el.parent;
|
|
2257
2955
|
while (node) {
|