@react-x11/components 0.14.1 → 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.
Files changed (96) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +171 -26
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +616 -216
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +18 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -1
  22. package/dist/html/css/shapes.js +21 -1
  23. package/dist/html/css/shapes.js.map +1 -1
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +74 -12
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +15 -0
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +29 -9
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +141 -52
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +16 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +144 -23
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +11 -9
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1631 -221
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +23 -8
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +886 -324
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +35 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +118 -26
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts.map +1 -1
  64. package/dist/html/paint.js +558 -178
  65. package/dist/html/paint.js.map +1 -1
  66. package/dist/html/resources.js +14 -1
  67. package/dist/html/resources.js.map +1 -1
  68. package/dist/html/svg.d.ts +23 -7
  69. package/dist/html/svg.d.ts.map +1 -1
  70. package/dist/html/svg.js +264 -59
  71. package/dist/html/svg.js.map +1 -1
  72. package/dist/html/widgets.d.ts.map +1 -1
  73. package/dist/html/widgets.js +57 -4
  74. package/dist/html/widgets.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/code-editor/node.ts +24 -5
  77. package/src/html/css/cascade.ts +697 -221
  78. package/src/html/css/color.ts +345 -18
  79. package/src/html/css/content.ts +173 -15
  80. package/src/html/css/parse.ts +60 -8
  81. package/src/html/css/shapes.ts +35 -1
  82. package/src/html/css/style.ts +122 -17
  83. package/src/html/css/ua.ts +15 -0
  84. package/src/html/css/values.ts +23 -9
  85. package/src/html/index.ts +7 -0
  86. package/src/html/layout/block.ts +149 -58
  87. package/src/html/layout/boxes.ts +171 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1843 -212
  90. package/src/html/layout/inline.ts +27 -7
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +151 -27
  93. package/src/html/paint.ts +692 -234
  94. package/src/html/resources.ts +13 -1
  95. package/src/html/svg.ts +299 -56
  96. package/src/html/widgets.ts +61 -4
@@ -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
- /** The rules that test `:hover`, in buckets of their own: the ones whose
83
- * answers a pointer move can change, which is all that is asked to know
84
- * whether an element's did (`Cascade.pointerChanged`). Null until there
85
- * is one. */
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 = POINTER_PSEUDO_AT.exec(text.slice(i));
422
+ const m = STATE_PSEUDO_AT.exec(text.slice(i));
394
423
  if (m) {
395
- if (m[0].length === 6)
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 (!HOVER.test(arg)) {
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 (!HOVER.test(entry))
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 = /:hover(?![\w-])/i;
471
- /** `:hover` or `:active` at the start of a string, and nothing longer. */
472
- const POINTER_PSEUDO_AT = /^:(?:hover|active)(?![\w-])/i;
473
- /** `:hover` or `:active` anywhere in a selector. */
474
- const POINTER_PSEUDO = /:(?:hover|active)(?![\w-])/i;
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 keeps none: with them,
481
- * `#box:has(a:hover) .m` answered what it did before the first move, for
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: !POINTER_PSEUDO.test(selector),
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
- /** The compounds of the selectors that test the pointer, each without
661
- * its `:hover` or `:active` (`pointerCompounds`), and their matchers,
662
- * compiled when a pointer move first asks (`hoverTouches`). */
663
- _hoverCompounds = new Map();
664
- _hoverMatchers = null;
665
- /** Whether a pointer move can be restyled where it happened
666
- * (`HtmlViewNode._hoverInPlace`): false where a selector tests the
667
- * pointer inside a functional pseudo-class, which reaches elements no
668
- * compound names. */
669
- hoverLocal = true;
670
- /** The compounds whose `:has()` tests the pointer, each without it,
671
- * whether its argument starts at a sibling, and whether a sibling
672
- * combinator follows it (`hoverAnchors`). */
673
- _hoverAnchors = new Map();
674
- _anchorMatchers = null;
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, `:root` this cascade's. */
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
- this._pseudos = documentElement
729
- ? { ...PSEUDOS, root: (el) => el === documentElement }
730
- : PSEUDOS;
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
- this._noteHover((pseudo?.rule ?? rule).selector);
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
- if (this._pseudo['first-letter'].hoverSensitive ||
840
- this._pseudo['first-line'].hoverSensitive ||
841
- this._pseudo.marker.hoverSensitive) {
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.hoverSensitive ||
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
- // the followed ones first, so the first that matches is the answer
861
- this._hoverMatchers ??= [...this._hoverCompounds]
862
- .sort(([, a], [, b]) => Number(b) - Number(a))
863
- .flatMap(([compound, followed]) => {
864
- const match = this._compile(compound);
865
- return match ? [{ match, followed }] : [];
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
- if (!this._hoverAnchors.size)
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
- _noteHover(selector) {
923
- if (!/:(?:hover|active)/i.test(selector))
924
- return;
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
- const found = new Set();
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
- let of = null;
1664
- let key = '';
1665
- let ctx = null;
1666
- // in document order, the root first: an element's place in it is what
1667
- // the key names it by, and its ancestors in the drawing are noted
1668
- // before it is asked of
1669
- const stack = [root];
1670
- let place = -1;
1671
- while (stack.length) {
1672
- const el = stack.pop();
1673
- place += 1;
1674
- // what is in a `<foreignObject>` is not the drawing's to draw
1675
- if (el.name !== 'foreignobject' && el.name !== 'foreignObject') {
1676
- const children = el.children;
1677
- for (let i = children.length - 1; i >= 0; i -= 1) {
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
- if (asks)
1684
- note(el);
1685
- const tag = tagOf(el);
1686
- const asked = found.size
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 (el === root && ROOT_BOX_PROPS.has(d.prop))
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
- continue;
1947
+ return;
1727
1948
  (of ??= new Map()).set(el, own);
1728
- key += `${place}{`;
1729
- for (const prop in own)
1730
- key += `${prop}:${own[prop]};`;
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 that match `el` as things
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
- if (!indexed.compiled) {
1818
- indexed.compiled = true;
1819
- try {
1820
- indexed.match = compileSelector(rule.selector, this._adapter, this._pseudos);
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
- catch {
1823
- // A selector this matcher does not know (`::-moz-…`, a CSS4 form
1824
- // it has not learnt) drops out of the cascade rather than out of
1825
- // the render.
1826
- indexed.match = null;
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 (!indexed.match || !indexed.match(el))
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
- styled = pick(out[j]).some(stylesChrome);
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, and an
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 ((prop === 'appearance' || prop === '-webkit-appearance') &&
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 match nothing: no element of the document takes
1964
- * the focus — a control's widget does, beside it. css-select has none of
1965
- * the three and throws on them, which dropped every rule that named one,
1966
- * `:not(:focus)` among them: Wikipedia's skip link hides with
1967
- * `.mw-jump-link:not(:focus)`, and without it stood in the flow at the top
1968
- * of every page. */
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
- let root = el;
1984
- for (let node = el; node;) {
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(pragma).split('-');
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)