@react-x11/components 0.14.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +27 -8
  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 +29 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +172 -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 +1759 -238
  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 +64 -19
  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 +122 -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 +27 -8
  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 +211 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1975 -231
  90. package/src/html/layout/inline.ts +70 -16
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +155 -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
package/src/html/node.ts CHANGED
@@ -56,8 +56,15 @@ import {
56
56
  HOVER_PSEUDO_NONE,
57
57
  HOVER_PSEUDO_OTHER,
58
58
  HOVER_UNTOUCHED,
59
+ NO_FOCUS,
60
+ } from './css/cascade.js';
61
+ import type {
62
+ FocusState,
63
+ HoverTouch,
64
+ KeptStyles,
65
+ MetricFace,
66
+ PointerState,
59
67
  } from './css/cascade.js';
60
- import type { HoverTouch, KeptStyles, MetricFace } from './css/cascade.js';
61
68
  import { mediaMatches, parseStylesheet } from './css/parse.js';
62
69
  import type { MediaCondition, Stylesheet } from './css/parse.js';
63
70
  import type { ShapeStyles } from './css/shapes.js';
@@ -129,7 +136,7 @@ import {
129
136
  stacksLayers,
130
137
  } from './paint.js';
131
138
  import type { PaintContext } from './paint.js';
132
- import { controlRectsOf, measureControl } from './controls.js';
139
+ import { controlRectsOf, measureControl, styledField } from './controls.js';
133
140
  import type { BareField, ControlRect } from './controls.js';
134
141
  import { ResourceStore } from './resources.js';
135
142
  import type { ResourceRequest, ResourceResult } from './resources.js';
@@ -290,6 +297,8 @@ export class HtmlViewNode extends Node {
290
297
  * the text carries surrogate pairs. null until checked. */
291
298
  private _pointsAreUnits: boolean | null = null;
292
299
  private _hovered: Element[] = [];
300
+ /** Which element's widget holds the focus (`setFocus`). */
301
+ private _focus: FocusState = NO_FOCUS;
293
302
  /** Where the pointer last moved to, in logical window pixels, while it
294
303
  * is over the element (`defaultMouseMove`). */
295
304
  private _pointerAt: { x: number; y: number } | null = null;
@@ -402,6 +411,12 @@ export class HtmlViewNode extends Node {
402
411
  controlBorder: look.controlBorder * s,
403
412
  controlRadius: look.controlRadius * s,
404
413
  controlFontSize: (look.controlFontSize ?? look.fontSize) * s,
414
+ ...(look.focusRingWidth !== undefined && {
415
+ focusRingWidth: look.focusRingWidth * s,
416
+ }),
417
+ ...(look.focusRingOffset !== undefined && {
418
+ focusRingOffset: look.focusRingOffset * s,
419
+ }),
405
420
  };
406
421
  this._deviceLookFor = look;
407
422
  this._deviceLookAt = s;
@@ -711,6 +726,7 @@ export class HtmlViewNode extends Node {
711
726
  hovered: new Set(this._hovered),
712
727
  active: EMPTY_SET,
713
728
  });
729
+ this._cascade.setFocus(this._focus);
714
730
  this._mediaBand = this._cascade.mediaBand(width);
715
731
  }
716
732
 
@@ -1031,6 +1047,14 @@ export class HtmlViewNode extends Node {
1031
1047
  scale: this._scale,
1032
1048
  imageSize: (el) => this._resources.imageSize(imageUrlOf(el) ?? ''),
1033
1049
  urlSize: (url) => this._resources.imageSize(url),
1050
+ faceAscent: (style) => {
1051
+ const fonts = this._fonts();
1052
+ return fonts ? faceExtentOf(fonts, style).ascent : undefined;
1053
+ },
1054
+ faceExtent: (style) => {
1055
+ const fonts = this._fonts();
1056
+ return fonts ? faceExtentOf(fonts, style) : undefined;
1057
+ },
1034
1058
  controlSize: (el, kind, style) =>
1035
1059
  measureControl(el, kind, style, this._fonts(), look),
1036
1060
  });
@@ -1539,37 +1563,120 @@ export class HtmlViewNode extends Node {
1539
1563
  * with the styles of every element the move did not reach kept.
1540
1564
  */
1541
1565
  private _restyleHover(was: readonly Element[], now: readonly Element[]) {
1566
+ const cascade = this._cascade;
1542
1567
  const reach = this._hoverReach(was, now);
1543
- if (reach !== null) {
1568
+ if (reach !== null && cascade) {
1544
1569
  if (!reach.size) return;
1545
- if (this._hoverInPlace(reach, was)) return;
1570
+ const before = { hovered: new Set(was), active: EMPTY_SET };
1571
+ if (this._hoverInPlace(reach, before, this._focus, cascade.hoverPseudo)) {
1572
+ return;
1573
+ }
1546
1574
  }
1547
1575
  this._invalidate(Stale.Boxes, reach);
1548
1576
  }
1549
1577
 
1578
+ /** The element whose widget holds the focus, or null (`setFocus`). */
1579
+ get focusedElement(): Element | null {
1580
+ return this._focus.element;
1581
+ }
1582
+
1583
+ /**
1584
+ * The focus moved: `element`'s widget took it, or, given null, no widget
1585
+ * of this document holds it now — it went to something else in the
1586
+ * window, or nowhere. `visible` is whether it shows, `:focus-visible`,
1587
+ * which it always does in a text field. Restyles what `:focus` and its
1588
+ * kin reach as a hover does (`_restyleHover`), and returns whether the
1589
+ * cascade's answer could have changed.
1590
+ *
1591
+ * No element of a document takes the focus: a control's widget, mounted
1592
+ * beside it, does, and says so here. Only a text field's does, so far —
1593
+ * the widgets core draws as components keep their own focus to
1594
+ * themselves.
1595
+ */
1596
+ setFocus(element: Element | null, visible = true): boolean {
1597
+ const was = this._focus;
1598
+ if (was.element === element && (!element || was.visible === visible)) {
1599
+ return false;
1600
+ }
1601
+ const within = new Set<Element>();
1602
+ for (
1603
+ let at: Element | null = element;
1604
+ at;
1605
+ at = isElement(at.parent) ? at.parent : null
1606
+ ) {
1607
+ within.add(at);
1608
+ }
1609
+ const now: FocusState = element ? { element, visible, within } : NO_FOCUS;
1610
+ this._focus = now;
1611
+ const cascade = this._cascade;
1612
+ cascade?.setFocus(now);
1613
+ if (!cascade || !cascade.focusSensitive) return false;
1614
+ const reach = cascade.focusLocal
1615
+ ? this._stateReach(
1616
+ [...was.within],
1617
+ [...now.within],
1618
+ // one element, whose ring came or went
1619
+ was.element === element ? element : null,
1620
+ (el) => cascade.focusTouches(el),
1621
+ (el, into) => cascade.focusAnchors(el, into),
1622
+ )
1623
+ : null;
1624
+ if (reach !== null) {
1625
+ if (!reach.size) return true;
1626
+ const pointer = { hovered: new Set(this._hovered), active: EMPTY_SET };
1627
+ if (this._hoverInPlace(reach, pointer, was, cascade.focusPseudo)) {
1628
+ return true;
1629
+ }
1630
+ }
1631
+ this._invalidate(Stale.Boxes, reach);
1632
+ return true;
1633
+ }
1634
+
1550
1635
  /**
1551
1636
  * The elements whose styles a move of the hovered chain can change, or
1552
1637
  * null where that cannot be said (`Cascade.hoverLocal`).
1638
+ */
1639
+ private _hoverReach(
1640
+ was: readonly Element[],
1641
+ now: readonly Element[],
1642
+ ): Set<Element> | null {
1643
+ const cascade = this._cascade;
1644
+ if (!cascade || !cascade.hoverLocal) return null;
1645
+ return this._stateReach(
1646
+ was,
1647
+ now,
1648
+ null,
1649
+ (el) => cascade.hoverTouches(el),
1650
+ (el, into) => cascade.hoverAnchors(el, into),
1651
+ );
1652
+ }
1653
+
1654
+ /**
1655
+ * The elements whose styles a move of the hovered chain can change, or
1656
+ * of the focus's chain.
1553
1657
  *
1554
1658
  * Only an element whose hover state flipped and that a compound testing
1555
1659
  * the pointer could match can change (`Cascade.hoverTouches`), with its
1556
1660
  * subtree, and its later siblings' where a sibling combinator is in play;
1557
1661
  * and the elements a `:has()` testing the pointer may flip, which are
1558
1662
  * around the ones that changed. A move between two paragraphs under
1559
- * `a:hover` reaches nothing.
1663
+ * `a:hover` reaches nothing. The focus's the same way, its chain the
1664
+ * focused element and every element around it, and `also` one whose
1665
+ * state flipped though it is in both — the ring coming or going.
1560
1666
  */
1561
- private _hoverReach(
1667
+ private _stateReach(
1562
1668
  was: readonly Element[],
1563
1669
  now: readonly Element[],
1564
- ): Set<Element> | null {
1565
- const cascade = this._cascade;
1566
- if (!cascade || !cascade.hoverLocal) return null;
1670
+ also: Element | null,
1671
+ touches: (el: Element) => HoverTouch,
1672
+ anchors: (el: Element, into: Map<Element, HoverTouch>) => void,
1673
+ ): Set<Element> {
1567
1674
  const before = new Set(was);
1568
1675
  const after = new Set(now);
1569
1676
  const roots = new Map<Element, HoverTouch>();
1570
- const flipped = (el: Element, other: Set<Element>): void => {
1571
- if (other.has(el)) return;
1572
- const touch = cascade.hoverTouches(el);
1677
+ const flipped = (el: Element, other: Set<Element> | null): void => {
1678
+ if (other?.has(el)) return;
1679
+ const touch = touches(el);
1573
1680
  if (touch > (roots.get(el) ?? HOVER_UNTOUCHED)) roots.set(el, touch);
1574
1681
  };
1575
1682
  for (const el of was) flipped(el, after);
@@ -1578,10 +1685,14 @@ export class HtmlViewNode extends Node {
1578
1685
  // side is where a `:has()` is looked for from
1579
1686
  const deepest = (chain: readonly Element[], other: Set<Element>) => {
1580
1687
  const el = chain[0];
1581
- if (el && !other.has(el)) cascade.hoverAnchors(el, roots);
1688
+ if (el && !other.has(el)) anchors(el, roots);
1582
1689
  };
1583
1690
  deepest(was, after);
1584
1691
  deepest(now, before);
1692
+ if (also) {
1693
+ flipped(also, null);
1694
+ anchors(also, roots);
1695
+ }
1585
1696
 
1586
1697
  // the roots' subtrees, which inherit from them and a descendant
1587
1698
  // combinator reaches, and their later siblings' too where a sibling
@@ -1626,8 +1737,10 @@ export class HtmlViewNode extends Node {
1626
1737
  * - The boxes take their new styles: an element's own, the text in it,
1627
1738
  * the anonymous boxes the fix-up made around what is in it, and its
1628
1739
  * `::before` and `::after`, styled again by their own rules. A box
1629
- * whose style is derived some other way — a marker, a first letter, a
1630
- * control — is not one to restyle here.
1740
+ * whose style is derived some other way — a marker, a first letter —
1741
+ * is not one to restyle here. A control's is, and its widget is told
1742
+ * (`_reportControls`), unless the page took to drawing the field or
1743
+ * stopped.
1631
1744
  * - Each layout of their text is made again from the runs it was made
1632
1745
  * from (`TextLayoutCache.inputsOf`) with the new ink (`runFor`), where
1633
1746
  * its geometry comes out the same.
@@ -1637,10 +1750,16 @@ export class HtmlViewNode extends Node {
1637
1750
  * costs what it changed, in a document of any length. True where it was
1638
1751
  * restyled here, or had nothing to restyle; false where the boxes have to
1639
1752
  * be built again.
1753
+ *
1754
+ * A focus change is answered the same way (`setFocus`): `before` and
1755
+ * `beforeFocus` are the states the change was from, and `pseudoRules`
1756
+ * which pseudo-elements' rules test the state that changed.
1640
1757
  */
1641
1758
  private _hoverInPlace(
1642
1759
  reach: ReadonlySet<Element>,
1643
- was: readonly Element[],
1760
+ before: PointerState,
1761
+ beforeFocus: FocusState,
1762
+ pseudoRules: 0 | 1 | 2,
1644
1763
  ): boolean {
1645
1764
  const cascade = this._cascade;
1646
1765
  const tree = this._tree;
@@ -1649,17 +1768,15 @@ export class HtmlViewNode extends Node {
1649
1768
  if (this._stale !== Stale.Nothing || this._laidOutWidth < 0) return false;
1650
1769
  if (reach.size > HOVER_RESTYLE_LIMIT) return false;
1651
1770
  // a first line's, a first letter's and a marker's styles are layout's
1652
- const pseudoRules = cascade.hoverPseudo;
1653
1771
  if (pseudoRules === HOVER_PSEUDO_OTHER) return false;
1654
1772
 
1655
- // whether the rules that test the pointer answer differently for an
1656
- // element than they did before the move
1657
- const before = { hovered: new Set(was), active: EMPTY_SET };
1773
+ // whether the rules that test the pointer or the focus answer
1774
+ // differently for an element than they did before the change
1658
1775
  const flips = new Map<Element, boolean>();
1659
1776
  const flipped = (el: Element): boolean => {
1660
1777
  let flip = flips.get(el);
1661
1778
  if (flip === undefined) {
1662
- flip = cascade.pointerChanged(el, before);
1779
+ flip = cascade.pointerChanged(el, before, beforeFocus);
1663
1780
  flips.set(el, flip);
1664
1781
  }
1665
1782
  return flip;
@@ -1677,6 +1794,8 @@ export class HtmlViewNode extends Node {
1677
1794
  let reaches = false;
1678
1795
  let reorders = false;
1679
1796
  const moving = new Set<Element>();
1797
+ /** Whether a control's box was restyled: its widget is told. */
1798
+ let widgets = false;
1680
1799
  const styleOf = (el: Element): ComputedStyle | null => {
1681
1800
  const kept = tree.styles.get(el);
1682
1801
  if (!kept) return null;
@@ -1729,7 +1848,7 @@ export class HtmlViewNode extends Node {
1729
1848
  const box = this._firstBoxesOf(tree).get(el);
1730
1849
  const was = box && SHAPE_STYLES.get(box);
1731
1850
  if (!box || was === undefined) continue;
1732
- const shapes = cascade.shapeStyles(el, style);
1851
+ const shapes = cascade.shapeStyles(el, style, tree.shapeCopies);
1733
1852
  if (shapes?.key !== was?.key) redrawn.push([box, shapes]);
1734
1853
  }
1735
1854
  }
@@ -1820,11 +1939,20 @@ export class HtmlViewNode extends Node {
1820
1939
  if (!style) continue;
1821
1940
  const kept = tree.styles.get(el)!.style;
1822
1941
  // a style derived from the element's, or drawn from it somewhere
1823
- // else — a widget mounted beside the document takes its look from
1824
- // it. An image, a drawing or a rule is drawn here, from its box,
1825
- // like any.
1942
+ // else. A widget mounted beside the document takes its look from
1943
+ // it, and is told again once it is restyled — unless the page took
1944
+ // to drawing the field, or stopped, which is another size of box.
1945
+ // An image, a drawing or a rule is drawn here, from its box, like
1946
+ // any.
1826
1947
  if (box.style !== kept) return false;
1827
- if (box.marker || WIDGETS.has(box.replaced)) return false;
1948
+ if (box.marker) return false;
1949
+ if (WIDGETS.has(box.replaced)) {
1950
+ const kind = box.replaced!;
1951
+ if (styledField(kind, kept) !== styledField(kind, style)) {
1952
+ return false;
1953
+ }
1954
+ widgets = true;
1955
+ }
1828
1956
  if (moving.has(el) && !movable(box, kept, style)) return false;
1829
1957
  ink = !quiet.has(el);
1830
1958
  } else if (above) {
@@ -1944,7 +2072,7 @@ export class HtmlViewNode extends Node {
1944
2072
  relayout = true;
1945
2073
  }
1946
2074
  }
1947
- if (moved.length) this._reportControls();
2075
+ if (moved.length || widgets) this._reportControls();
1948
2076
  if (relayout) {
1949
2077
  this._invalidate(Stale.Layout);
1950
2078
  return true;