react-x11 2.11.0 → 2.12.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/README.md +278 -129
  2. package/package.json +10 -3
  3. package/src/Reconciler.js +15 -17
  4. package/src/a11y.js +2 -2
  5. package/src/anchor.js +7 -5
  6. package/src/bootstrap.js +14 -0
  7. package/src/clientmessage.js +1 -1
  8. package/src/cocoa/app.js +292 -49
  9. package/src/cocoa/bezels.js +175 -30
  10. package/src/cocoa/dnd.js +27 -13
  11. package/src/cocoa/fonts.js +3 -3
  12. package/src/cocoa/glarea.js +20 -3
  13. package/src/cocoa/main.d.ts +8 -0
  14. package/src/cocoa/main.js +43 -0
  15. package/src/cocoa/panehost.js +15 -5
  16. package/src/cocoa/presenter.js +13 -9
  17. package/src/cocoa/promotion.js +4 -7
  18. package/src/cocoa/relaunch.js +207 -0
  19. package/src/cocoa/threaded.js +246 -0
  20. package/src/cocoa/window.js +256 -42
  21. package/src/components/Select.js +2 -2
  22. package/src/components/anchor.js +3 -3
  23. package/src/components/native.js +12 -7
  24. package/src/components/theme.js +2 -2
  25. package/src/debug.js +1 -1
  26. package/src/decorations.js +1 -1
  27. package/src/editmenu.js +2 -2
  28. package/src/errors.js +46 -0
  29. package/src/events.js +6 -6
  30. package/src/foreignnodes.js +3 -2
  31. package/src/frames.js +2 -2
  32. package/src/glnodes.js +1 -1
  33. package/src/grid.js +1653 -0
  34. package/src/host.d.ts +230 -0
  35. package/src/host.js +11 -3
  36. package/src/imagesource.js +1 -1
  37. package/src/index.d.ts +21 -4
  38. package/src/index.js +9 -1
  39. package/src/layouts.js +721 -0
  40. package/src/node.d.ts +4 -2
  41. package/src/node.js +19 -21
  42. package/src/nodes/animation.js +644 -0
  43. package/src/nodes/box.js +21 -0
  44. package/src/nodes/boxpaint.js +473 -0
  45. package/src/nodes/canvas.js +269 -0
  46. package/src/nodes/cascade.js +600 -0
  47. package/src/nodes/damage.js +183 -0
  48. package/src/nodes/edithistory.js +124 -0
  49. package/src/nodes/editmenupopup.js +260 -0
  50. package/src/nodes/hittest.js +185 -0
  51. package/src/nodes/image.js +266 -0
  52. package/src/nodes/install.js +75 -0
  53. package/src/nodes/invalidate.js +465 -0
  54. package/src/nodes/kinds.js +31 -0
  55. package/src/nodes/layout.js +439 -0
  56. package/src/nodes/layouthost.js +949 -0
  57. package/src/nodes/node.js +868 -0
  58. package/src/nodes/paint.js +466 -0
  59. package/src/nodes/position.js +366 -0
  60. package/src/nodes/preedit.js +127 -0
  61. package/src/nodes/queries.js +330 -0
  62. package/src/nodes/rects.js +102 -0
  63. package/src/nodes/scrollable.js +891 -0
  64. package/src/nodes/scrollbars.js +138 -0
  65. package/src/nodes/scrollblit.js +1034 -0
  66. package/src/nodes/selectable.js +142 -0
  67. package/src/nodes/styling.js +225 -0
  68. package/src/nodes/text.js +649 -0
  69. package/src/nodes/textarea.js +391 -0
  70. package/src/nodes/textinput.js +1146 -0
  71. package/src/nodes/util.js +17 -0
  72. package/src/nodes/window/anchoring.js +161 -0
  73. package/src/nodes/window/capabilities.js +190 -0
  74. package/src/nodes/window/debugpaint.js +83 -0
  75. package/src/nodes/window/droptarget.js +145 -0
  76. package/src/nodes/window/floors.js +577 -0
  77. package/src/nodes/window/flush.js +334 -0
  78. package/src/nodes/window/hints.js +482 -0
  79. package/src/nodes/window/listeners.js +222 -0
  80. package/src/nodes/window/popup.js +71 -0
  81. package/src/nodes/window/size.js +591 -0
  82. package/src/nodes/window/window.js +945 -0
  83. package/src/palette.js +1 -1
  84. package/src/registry.js +7 -3
  85. package/src/styles.js +137 -15
  86. package/src/svgnodes.js +2 -1
  87. package/src/testing/harness.js +2 -2
  88. package/src/textselection.js +5 -3
  89. package/src/trace-registry.js +1 -1
  90. package/src/types/components.d.ts +38 -6
  91. package/src/types/elements.d.ts +11 -1
  92. package/src/types/nodes.d.ts +17 -2
  93. package/src/types/style.d.ts +94 -3
  94. package/src/windowstate.js +1 -1
  95. package/src/yoga.js +1 -1
  96. package/src/nodes.js +0 -13120
package/src/palette.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Separate from `components/theme.js` because the palette is not a React
4
4
  // concern. There are two routes into the tree — `useTheme()` through React
5
5
  // context, and a `$token` resolved against the nearest `theme` **prop** by
6
- // walking the node tree — and the second one lives in `nodes.js`, below the
6
+ // walking the node tree — and the second one lives in `nodes/cascade.js`, below the
7
7
  // widget layer. Both have to agree on what "no theme was given" means, so the
8
8
  // answer belongs underneath both of them.
9
9
  //
package/src/registry.js CHANGED
@@ -28,8 +28,9 @@ import {
28
28
  DRAWN_KINDS,
29
29
  CUSTOM_SEMANTIC_NAMES,
30
30
  CUSTOM_SELF_DAMAGED,
31
- Node,
32
- } from './nodes.js';
31
+ } from './nodes/kinds.js';
32
+ import { Node } from './nodes/node.js';
33
+ import { markLayoutsHotReloadSession } from './layouts.js';
33
34
 
34
35
  /** kind -> definition. Insertion-ordered, which is the order errors list. */
35
36
  const registry = new Map();
@@ -54,6 +55,9 @@ let hotReloadSession = false;
54
55
  /** @internal — called by react-x11/refresh; not part of the public API. */
55
56
  export function markHotReloadSession(active = true) {
56
57
  hotReloadSession = active;
58
+ // the layout and placement registries follow the same rule, for the same
59
+ // module-scope reason
60
+ markLayoutsHotReloadSession(active);
57
61
  }
58
62
 
59
63
  function assertNode(node, type) {
@@ -225,7 +229,7 @@ export function createRegisteredNode(type, props, app, hostContext) {
225
229
  const definition = registry.get(type);
226
230
  if (!definition) return undefined;
227
231
  const node = assertNode(definition.create(props, app, hostContext), type);
228
- // read by Node.insertBefore — carried on the instance so nodes.js needs
232
+ // read by Node.insertBefore — carried on the instance so src/nodes/ needs
229
233
  // no import from here
230
234
  if (!definition.childrenAllowed) node._childrenAllowed = false;
231
235
  return node;
package/src/styles.js CHANGED
@@ -6,6 +6,11 @@
6
6
  import { cssColorStraight } from 'ntk';
7
7
 
8
8
  import { parseBoxShadow, parseLinearGradient } from './decorations.js';
9
+ import {
10
+ validateGridValue,
11
+ GRID_CONTAINER_PROPS,
12
+ GRID_ITEM_PROPS,
13
+ } from './grid.js';
9
14
  import { Yoga } from './yoga.js';
10
15
 
11
16
  const FLEX_DIRECTION = {
@@ -47,8 +52,34 @@ const POSITION = {
47
52
  absolute: Yoga.POSITION_TYPE_ABSOLUTE,
48
53
  };
49
54
 
55
+ /**
56
+ * Yoga's position type for a `position` value. `static`, `relative` and
57
+ * `absolute` are yoga's own. Everything else — `sticky`, and every position
58
+ * registered with `registerPosition` (src/layouts.js) — is laid out in flow
59
+ * exactly as `relative` is and moved afterwards: placing is not something
60
+ * layout does (nodes/position.js, `WindowNode._placeNodes`). What differs is the
61
+ * insets, which are the placement's to read — thresholds, for sticky — so
62
+ * yoga must never see them as offsets (`applyLayoutStyle`).
63
+ */
64
+ const positionTypeOf = (v) =>
65
+ typeof v === 'string' && Object.hasOwn(POSITION, v)
66
+ ? POSITION[v]
67
+ : Yoga.POSITION_TYPE_RELATIVE;
68
+
69
+ /** Whether a `position` is placed after layout rather than by it. */
70
+ const isPlacedPosition = (v) =>
71
+ v != null && !(typeof v === 'string' && Object.hasOwn(POSITION, v));
72
+
73
+ /** The properties that are an offset under `relative` and `absolute`, and the
74
+ * placement's to read under any other — see `applyLayoutStyle`. */
75
+ const INSETS = new Set(['top', 'right', 'bottom', 'left', 'start', 'end']);
76
+
77
+ /** `'grid'` is a flex item to yoga: the box takes its place in its parent's
78
+ * line like any other, and its own children are the grid's to arrange — a
79
+ * layout host (src/grid.js, nodes/layouthost.js). */
50
80
  const DISPLAY = {
51
81
  flex: Yoga.DISPLAY_FLEX,
82
+ grid: Yoga.DISPLAY_FLEX,
52
83
  none: Yoga.DISPLAY_NONE,
53
84
  };
54
85
 
@@ -129,10 +160,7 @@ const LAYOUT_APPLIERS = {
129
160
  flexGrow: (n, v) => n.setFlexGrow(v ?? 0),
130
161
  flexShrink: (n, v) => n.setFlexShrink(v ?? 1),
131
162
  flexBasis: (n, v) => n.setFlexBasis(v),
132
- position: (n, v) =>
133
- n.setPositionType(
134
- pick(POSITION, v, 'position') ?? Yoga.POSITION_TYPE_RELATIVE,
135
- ),
163
+ position: (n, v) => n.setPositionType(positionTypeOf(v)),
136
164
  // Which way the boxes under this one run. Everything else in this file is
137
165
  // physical; this is the one property that decides what "start" means, and
138
166
  // yoga inherits it down its own tree — so a `<box>` that sets it mirrors
@@ -455,6 +483,18 @@ const STYLE_PROPS = new Set([
455
483
  // the ones that say it. Neither layout nor paint: it changes nothing about
456
484
  // this node, only what the blocks under it resolve against.
457
485
  'container',
486
+ // A registered layout algorithm that arranges this node's children — CSS's
487
+ // `display: layout(name)`, with the algorithm's options written beside the
488
+ // name — and what a child tells the one arranging it, CSS's child input
489
+ // properties (docs/styling.md, "Custom layouts"). Layout in every sense
490
+ // but yoga's: the node side reads them (nodes/layouthost.js).
491
+ 'layout',
492
+ 'layoutItem',
493
+ // CSS grid's own properties, on a `display: 'grid'` box and on each of its
494
+ // children (docs/styling.md, "Grid"). Layout in every sense but yoga's, like
495
+ // the two above: the grid reads them off the style (src/grid.js).
496
+ ...GRID_CONTAINER_PROPS,
497
+ ...GRID_ITEM_PROPS,
458
498
  ]);
459
499
 
460
500
  export const isStyleProp = (name) => STYLE_PROPS.has(name);
@@ -497,7 +537,7 @@ const SIZE_QUERY = /^@(width|height)\s*(>=|<=|>|<)\s*(\d+(?:\.\d+)?)$/;
497
537
  * *when* it is answered — a container's size is what a layout pass
498
538
  * produces, not what one starts from, so the blocks are resolved after the
499
539
  * pass and the tree laid out once more if an answer moved
500
- * (nodes.js, `_resolveContainerQueries`).
540
+ * (nodes/queries.js, `_resolveContainerQueries`).
501
541
  */
502
542
  const CONTAINER_QUERY =
503
543
  /^@container(?:\s+([A-Za-z_][\w-]*))?\s+(width|height)\s*(>=|<=|>|<)\s*(\d+(?:\.\d+)?)$/;
@@ -618,7 +658,7 @@ export function containerQueryNames(style) {
618
658
  * Which of a style's container blocks match, as one string with a character
619
659
  * per block in declaration order. What a node remembers between layout
620
660
  * passes so a re-resolution runs only when an answer actually moved, and
621
- * what the oscillation check compares (nodes.js `_resolveContainerQueries`).
661
+ * what the oscillation check compares (nodes/queries.js `_resolveContainerQueries`).
622
662
  */
623
663
  export function containerAnswers(style, containers) {
624
664
  let out = '';
@@ -690,21 +730,27 @@ function queryMatches(q, size, supports, containers) {
690
730
  }
691
731
  }
692
732
 
733
+ const GRID_VALUES = new Set([...GRID_CONTAINER_PROPS, ...GRID_ITEM_PROPS]);
734
+
693
735
  /**
694
- * The two style values that are a small language rather than a number, and
695
- * therefore the two that can be *wrong* rather than merely absent. Parsed in
736
+ * The style values that are a small language rather than a number — a
737
+ * gradient, a shadow, and grid's track lists, areas and lines and
738
+ * therefore the ones that can be *wrong* rather than merely absent. Parsed in
696
739
  * development wherever they are written — including inside a state block,
697
740
  * which is the half of the surface a `continue` used to skip — so the error
698
741
  * naming the property and the expected spelling arrives at the call site
699
- * instead of as a blank panel three commits later.
742
+ * instead of as a blank panel three commits later. A grid value goes through
743
+ * the parser the layout itself runs (src/grid.js), so the two agree.
700
744
  *
701
745
  * Tokens are still unresolved here (`$accent` is a colour as far as the
702
746
  * grammar is concerned), so this checks the shape and never the colours.
703
747
  */
704
748
  function validateValue(key, value, where) {
705
- if (key !== 'backgroundImage' && key !== 'boxShadow') return;
749
+ const grid = GRID_VALUES.has(key);
750
+ if (!grid && key !== 'backgroundImage' && key !== 'boxShadow') return;
706
751
  try {
707
- if (key === 'backgroundImage') parseLinearGradient(value);
752
+ if (grid) validateGridValue(key, value);
753
+ else if (key === 'backgroundImage') parseLinearGradient(value);
708
754
  else parseBoxShadow(value);
709
755
  } catch (err) {
710
756
  // the parser names the property and the grammar; only the call site is
@@ -714,6 +760,22 @@ function validateValue(key, value, where) {
714
760
  }
715
761
  }
716
762
 
763
+ const ALGORITHM_NAME = /^[A-Za-z_][\w-]*$/;
764
+
765
+ /** A plain object of options — what `layoutItem` is, and what a `layout` or
766
+ * a registered `position` written with its options is. */
767
+ const isOptionBag = (v) =>
768
+ v == null || (typeof v === 'object' && !Array.isArray(v));
769
+
770
+ /** A `layout` / `position` value: a name, or one written with its options —
771
+ * `'masonry'`, `{ name: 'masonry', columns: 3 }`. Whether the name is
772
+ * registered is the node's to find out when it resolves the style: an
773
+ * algorithm may be registered after the style that names it is written. */
774
+ const isAlgorithm = (v) =>
775
+ v == null ||
776
+ (typeof v === 'string' && ALGORITHM_NAME.test(v)) ||
777
+ (isOptionBag(v) && typeof v.name === 'string' && ALGORITHM_NAME.test(v.name));
778
+
717
779
  function validateStyle(style, where) {
718
780
  for (const key of Object.keys(style)) {
719
781
  if (isQuery(key)) {
@@ -765,6 +827,28 @@ function validateStyle(style, where) {
765
827
  "digits, '_' and '-', not starting with a digit)",
766
828
  );
767
829
  }
830
+ if (key === 'layout' && !isAlgorithm(style[key])) {
831
+ throw new Error(
832
+ `react-x11: invalid layout ${JSON.stringify(style[key])} in ${where} ` +
833
+ "(expected a registered name like 'masonry', or { name: 'masonry' } " +
834
+ "with the layout's options beside it)",
835
+ );
836
+ }
837
+ if (key === 'position' && !isAlgorithm(style[key])) {
838
+ throw new Error(
839
+ `react-x11: invalid position ${JSON.stringify(style[key])} in ` +
840
+ `${where} (expected 'static', 'relative', 'absolute', 'sticky', or ` +
841
+ 'a position registered with registerPosition — its name, or ' +
842
+ "{ name: '…' } with its options beside it)",
843
+ );
844
+ }
845
+ if (key === 'layoutItem' && !isOptionBag(style[key])) {
846
+ throw new Error(
847
+ `react-x11: invalid layoutItem ${JSON.stringify(style[key])} in ` +
848
+ `${where} (expected an object of the options the layout arranging ` +
849
+ 'this node reads, like { span: 2 })',
850
+ );
851
+ }
768
852
  if (key === 'flex' && !isFlexShorthand(style[key])) {
769
853
  throw new Error(
770
854
  `react-x11: invalid flex ${JSON.stringify(style[key])} in ${where} ` +
@@ -882,6 +966,10 @@ const NOT_ANIMATABLE = new Set([
882
966
  // nothing is drawn from it, so there is no frame in which a halfway value
883
967
  // would be visible — and it may be an object, which does not lerp
884
968
  'hitSlop',
969
+ // a grid's tracks and a child's lines: a count of 3.5 columns, or line
970
+ // 2.5, is no grid at all, and a track list is a string
971
+ ...GRID_CONTAINER_PROPS,
972
+ ...GRID_ITEM_PROPS,
885
973
  ]);
886
974
 
887
975
  export const isAnimatableProp = (name) =>
@@ -1364,7 +1452,7 @@ export function stripTokens(style) {
1364
1452
  * anyone hears about it: mistakes are pushed onto `problems` and the caller
1365
1453
  * decides what one costs. Resolving itself never throws — it runs from a
1366
1454
  * commit and from an X event alike, and only the caller knows whether React
1367
- * is on the stack to route a throw to a boundary (src/nodes.js).
1455
+ * is on the stack to route a throw to a boundary (src/nodes/cascade.js).
1368
1456
  *
1369
1457
  * A cache hit replays the problems it recorded, so the second node to wear a
1370
1458
  * misspelled shared style is reported like the first.
@@ -1658,13 +1746,21 @@ export const createLayoutNode = () => Yoga.Node.create(layoutConfig());
1658
1746
  export function measuringExactly(measure) {
1659
1747
  const cfg = layoutConfig();
1660
1748
  cfg.setPointScaleFactor(0);
1749
+ exactDepth++;
1661
1750
  try {
1662
1751
  return measure();
1663
1752
  } finally {
1753
+ exactDepth--;
1664
1754
  cfg.setPointScaleFactor(1);
1665
1755
  }
1666
1756
  }
1667
1757
 
1758
+ let exactDepth = 0;
1759
+
1760
+ /** Whether yoga is measuring off the pixel grid right now: a size taken in
1761
+ * here is not the size the same layout comes to on it. */
1762
+ export const isMeasuringExactly = () => exactDepth > 0;
1763
+
1668
1764
  /**
1669
1765
  * The yoga defaults that are not CSS's, written once per node.
1670
1766
  *
@@ -1676,7 +1772,7 @@ export function measuringExactly(measure) {
1676
1772
  * The pair to it is the automatic minimum size — a flex item that may shrink
1677
1773
  * still cannot shrink below its content unless it says so. Yoga has no such
1678
1774
  * rule, so the renderer measures the floors itself; see `Node`'s content
1679
- * floors in nodes.js.
1775
+ * floors in nodes/window/floors.js.
1680
1776
  */
1681
1777
  export function applyLayoutDefaults(yogaNode) {
1682
1778
  yogaNode.setFlexShrink(1);
@@ -1688,15 +1784,41 @@ export function applyLayoutDefaults(yogaNode) {
1688
1784
  */
1689
1785
  export function applyLayoutStyle(yogaNode, props, oldProps = {}) {
1690
1786
  let changed = false;
1787
+ // A placed node's insets — sticky's thresholds, or whatever a registered
1788
+ // position reads them as — are the placement's, never offsets: yoga gets
1789
+ // `undefined` for them, and a node that becomes placed (or stops) has all
1790
+ // six re-sent whether or not they changed. The flip still counts as a
1791
+ // change: the pass it asks for is what places the node.
1792
+ const placed = isPlacedPosition(props.position);
1793
+ const flipped = placed !== isPlacedPosition(oldProps.position);
1691
1794
  for (const key of Object.keys(LAYOUT_APPLIERS)) {
1692
- if (props[key] !== oldProps[key]) {
1693
- LAYOUT_APPLIERS[key](yogaNode, props[key]);
1795
+ const moved =
1796
+ key === 'position'
1797
+ ? !samePosition(props.position, oldProps.position)
1798
+ : props[key] !== oldProps[key];
1799
+ if (moved || (flipped && INSETS.has(key))) {
1800
+ LAYOUT_APPLIERS[key](
1801
+ yogaNode,
1802
+ placed && INSETS.has(key) ? undefined : props[key],
1803
+ );
1694
1804
  changed = true;
1695
1805
  }
1696
1806
  }
1697
1807
  return changed;
1698
1808
  }
1699
1809
 
1810
+ /** Two `position` values asking for the same thing. A registered one written
1811
+ * with its options is an object, and an inline object is a new one every
1812
+ * render — which must not cost a layout pass per commit. */
1813
+ function samePosition(a, b) {
1814
+ if (a === b) return true;
1815
+ if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;
1816
+ const keys = Object.keys(a);
1817
+ if (keys.length !== Object.keys(b).length) return false;
1818
+ for (const key of keys) if (a[key] !== b[key]) return false;
1819
+ return true;
1820
+ }
1821
+
1700
1822
  /** @returns true if any paint-only prop changed */
1701
1823
  export function paintPropsChanged(props, oldProps = {}) {
1702
1824
  for (const key of PAINT_PROPS) {
package/src/svgnodes.js CHANGED
@@ -12,7 +12,8 @@
12
12
  // grain for it.
13
13
  import { SvgView } from 'ntk';
14
14
 
15
- import { Node, intrinsicSize } from './nodes.js';
15
+ import { Node } from './nodes/node.js';
16
+ import { intrinsicSize } from './nodes/layout.js';
16
17
  import { isLayoutProp, isPaintProp } from './styles.js';
17
18
 
18
19
  // SVG attributes that are camelCase in SVG itself; every other camelCase
@@ -9,7 +9,7 @@
9
9
 
10
10
  import React from 'react';
11
11
  import { createRoot } from '../Reconciler.js';
12
- import { setAnimationClock } from '../nodes.js';
12
+ import { setAnimationClock } from '../nodes/animation.js';
13
13
  import { setAppearanceForTests } from '../appearance.js';
14
14
 
15
15
  // A test process must not register with the desktop's live AT-SPI registry —
@@ -383,7 +383,7 @@ let clockInstalled = false;
383
383
  /**
384
384
  * Drive transitions from a number you control instead of from wall-clock
385
385
  * time. `setAnimationClock` exists for exactly this; it was reachable only
386
- * through a deep import into `src/nodes.js`.
386
+ * through a deep import into `src/nodes/animation.js`.
387
387
  *
388
388
  * ```js
389
389
  * const clock = withFrameClock();
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // **A surface is a `selectable` element, and its participants are whatever
11
11
  // under it can answer for its own text.** An element joins by implementing
12
- // the four accessors in nodes.js (`textContent`, `textIndexAt`,
12
+ // the four accessors in nodes/selectable.js (`textContent`, `textIndexAt`,
13
13
  // `textCaretRect`, `textRangeRects`) — `<text>` does, and so does a terminal
14
14
  // or a log view written outside this package. There is no registration call
15
15
  // and no list of blessed kinds: a document is a tree, and the tree is walked.
@@ -226,7 +226,7 @@ export class TextSelection {
226
226
  * pointer is: a press landed on the wrong character and a drag stopped
227
227
  * short at half its distance. The X event underneath already carries the
228
228
  * device numbers; an event synthesized without one is multiplied up, the
229
- * way every other pointer consumer in nodes.js does it.
229
+ * way every other pointer consumer in src/nodes/ does it.
230
230
  */
231
231
  devicePoint(ev) {
232
232
  const scale = this.node.scale > 0 ? this.node.scale : 1;
@@ -407,7 +407,9 @@ export class TextSelection {
407
407
  }
408
408
 
409
409
  /** Take PRIMARY, which in X is what "there is a selection here" means to
410
- * every other application on the display. */
410
+ * every other application on the display. macOS has no PRIMARY, and the
411
+ * cocoa backend's clipboard answers the write with nothing
412
+ * (src/cocoa/app.js) — there, a selection is never a copy. */
411
413
  own() {
412
414
  if (this.isCollapsed) return;
413
415
  takeVisibleSelection(this.node);
@@ -1,6 +1,6 @@
1
1
  // The seam between the renderer and the optional debug module.
2
2
  //
3
- // Reconciler.js and nodes.js import this file, so it has to stay safe to
3
+ // Reconciler.js and nodes/window/flush.js import this file, so it has to stay safe to
4
4
  // bundle for the browser playground: no node builtins, no side effects,
5
5
  // nothing but a registry of live connections and a pair of hook slots. The
6
6
  // heavy half (protocol parsing, sinks, file writing) lives in debug.js and
@@ -125,6 +125,20 @@ export interface Theme {
125
125
  direction: 'ltr' | 'rtl';
126
126
  paddingX: number;
127
127
  paddingY: number;
128
+ /**
129
+ * Not a colour: whether the widgets wear the **platform's own control
130
+ * bezels**. `'auto'` (the default) takes them wherever the backend has
131
+ * them — today the Cocoa backend, never X11 — `'native'` asks for them
132
+ * and says so in development where there are none, and `'drawn'` keeps
133
+ * this library's own rendering everywhere.
134
+ *
135
+ * It lives here rather than in `style` because whether an app looks
136
+ * native is an app-identity decision, not a per-node one;
137
+ * {@link NativeControlProp.native} is the per-control override, and
138
+ * `useSupports('nativeControls')` is how application code asks what this
139
+ * backend can do. See docs/components.md and docs/macos.md.
140
+ */
141
+ controls?: 'auto' | 'native' | 'drawn';
128
142
  }
129
143
 
130
144
  export interface ThemeProviderProps {
@@ -194,6 +208,20 @@ export function useDirection(): 'ltr' | 'rtl';
194
208
  /** Props a widget passes through to the `<box>` it renders. */
195
209
  type WidgetProps = Omit<BoxProps, 'children' | 'style' | 'ref'>;
196
210
 
211
+ /**
212
+ * The per-instance escape hatch from the platform's own control bezel, for
213
+ * the one custom-branded control in an otherwise native app.
214
+ *
215
+ * The policy proper is {@link Theme.controls}; this overrides it for this
216
+ * control. `false` keeps the drawn rendering — which is what a control
217
+ * naming its own colours in `style` should say, because a bezel the system
218
+ * draws ignores them. `true` asks for the bezel regardless of the theme's
219
+ * policy, and is a development warning on a backend that has none.
220
+ */
221
+ interface NativeControlProp {
222
+ native?: boolean;
223
+ }
224
+
197
225
  /**
198
226
  * What a value widget's `onChange` receives — one signature across the
199
227
  * library, so `onChange={formik.handleChange}` wires to a `Checkbox` exactly
@@ -279,7 +307,7 @@ export const iconNames: readonly IconName[];
279
307
  /** The default icon box for a given `fontSize`. */
280
308
  export function iconSize(fontSize: number): number;
281
309
 
282
- export interface ButtonProps extends WidgetProps {
310
+ export interface ButtonProps extends WidgetProps, NativeControlProp {
283
311
  children?: ReactNode;
284
312
  label?: string;
285
313
  onPress?: (ev: MouseEvent<DrawnNode>) => void;
@@ -328,7 +356,8 @@ export interface PasswordInputProps extends WidgetProps, NamedWidget {
328
356
  }
329
357
  export const PasswordInput: ComponentType<PasswordInputProps>;
330
358
 
331
- export interface CheckboxProps extends WidgetProps, NamedWidget {
359
+ export interface CheckboxProps
360
+ extends WidgetProps, NamedWidget, NativeControlProp {
332
361
  children?: ReactNode;
333
362
  label?: string;
334
363
  checked?: boolean;
@@ -346,7 +375,7 @@ export interface RadioGroupProps<T = unknown> extends WidgetProps, NamedWidget {
346
375
  }
347
376
  export function RadioGroup<T = unknown>(props: RadioGroupProps<T>): ReactNode;
348
377
 
349
- export interface RadioProps<T = unknown> {
378
+ export interface RadioProps<T = unknown> extends NativeControlProp {
350
379
  value: T;
351
380
  children?: ReactNode;
352
381
  label?: string;
@@ -354,7 +383,8 @@ export interface RadioProps<T = unknown> {
354
383
  }
355
384
  export function Radio<T = unknown>(props: RadioProps<T>): ReactNode;
356
385
 
357
- export interface SwitchProps extends WidgetProps, NamedWidget {
386
+ export interface SwitchProps
387
+ extends WidgetProps, NamedWidget, NativeControlProp {
358
388
  checked?: boolean;
359
389
  onChange?: (ev: WidgetChangeEvent<boolean>) => void;
360
390
  disabled?: boolean;
@@ -378,7 +408,8 @@ export interface ProgressBarProps extends WidgetProps {
378
408
  }
379
409
  export const ProgressBar: ComponentType<ProgressBarProps>;
380
410
 
381
- export interface SliderProps extends WidgetProps, NamedWidget {
411
+ export interface SliderProps
412
+ extends WidgetProps, NamedWidget, NativeControlProp {
382
413
  value?: number;
383
414
  min?: number;
384
415
  max?: number;
@@ -462,7 +493,8 @@ export const Dialog: ComponentType<DialogProps>;
462
493
  /** An option, or a plain value used as both value and label. */
463
494
  export type SelectOption<T = unknown> = { value: T; label: string } | T;
464
495
 
465
- export interface SelectProps<T = unknown> extends WidgetProps, NamedWidget {
496
+ export interface SelectProps<T = unknown>
497
+ extends WidgetProps, NamedWidget, NativeControlProp {
466
498
  value?: T;
467
499
  options?: readonly SelectOption<T>[];
468
500
  onChange?: (ev: WidgetChangeEvent<T>) => void;
@@ -222,7 +222,8 @@ export interface SelectionProps<T = DrawnNode> {
222
222
  /**
223
223
  * `true` makes this element a selection surface: a drag across the text
224
224
  * inside it selects, double and triple clicks take a word and a block,
225
- * Ctrl+A and Ctrl+C work, and a release takes PRIMARY. It also makes the
225
+ * Ctrl+A and Ctrl+C work, and on X11 a release takes PRIMARY (macOS has
226
+ * none: there, only a copy reaches the pasteboard). It also makes the
226
227
  * element a focus target, so the keys have somewhere to arrive —
227
228
  * `tabIndex={-1}` keeps it out of the Tab cycle.
228
229
  *
@@ -649,6 +650,15 @@ export interface BoxProps
649
650
 
650
651
  /** What `overflow: 'scroll'` adds, on a `<box>` or a `<window>`. */
651
652
  export interface ScrollProps {
653
+ /**
654
+ * The offsets moved, by any route. The wheel, the keys, a bar and
655
+ * `scrollTo`/`scrollBy` report inside the call. Layout reports once its
656
+ * pass is over, after the frame that shows the move: a held `scrollTo`
657
+ * landing (one made before the pane's first layout, or past the extent
658
+ * the last pass measured), a `scrollIntoView` resolving, or the offset
659
+ * pulled back when the content shrinks or the viewport grows under it.
660
+ * Each report carries the offsets in force when it is delivered.
661
+ */
652
662
  onScroll?: (ev: ScrollEvent) => void;
653
663
  /** Fired from layout, so it arrives for a list nobody has scrolled yet. */
654
664
  onViewport?: (ev: ViewportEvent) => void;
@@ -123,8 +123,22 @@ export interface ScrollableNode extends DrawnNode {
123
123
  readonly scrollY: number;
124
124
  readonly contentWidth: number;
125
125
  readonly contentHeight: number;
126
- /** A number scrolls the vertical axis; an object moves either or both. */
126
+ /**
127
+ * A number scrolls the vertical axis; an object moves either or both,
128
+ * clamped to the content. It answers at once from the extent the last
129
+ * layout pass measured. While a pass is owed — before the pane's first
130
+ * layout, or in the commit that added its new rows — whatever it asks past
131
+ * that extent is held too, and the pass answers it again against the
132
+ * content it measures, before the frame paints: `scrollTo(Infinity)` from
133
+ * a layout effect follows new rows to the end in the frame that shows
134
+ * them. `onScroll` reports the call's answer at once, and the pass's once
135
+ * that pass is over.
136
+ */
127
137
  scrollTo(to: number | ScrollTarget): void;
138
+ /**
139
+ * The same, by a delta. Made while a `scrollTo` is held for the pass, it
140
+ * moves on from where the pass puts the pane.
141
+ */
128
142
  scrollBy(by: number | ScrollTarget): void;
129
143
  /**
130
144
  * Is there room to move on the axis a delta names? What the wheel's
@@ -135,7 +149,8 @@ export interface ScrollableNode extends DrawnNode {
135
149
  /**
136
150
  * Scroll the minimum amount on both axes that makes a descendant fully
137
151
  * visible. Safe to call right after that node mounts — the request is
138
- * resolved on the next layout pass, when it has geometry.
152
+ * resolved on the next layout pass, when it has geometry, and `onScroll`
153
+ * reports the move once that pass is over.
139
154
  */
140
155
  scrollIntoView(node: DrawnNode): void;
141
156
  }
@@ -37,8 +37,39 @@ export type Align =
37
37
  | 'space-around';
38
38
 
39
39
  export type FlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse';
40
- export type PositionType = 'static' | 'relative' | 'absolute';
41
- export type Display = 'flex' | 'none';
40
+ export type PositionType = 'static' | 'relative' | 'absolute' | 'sticky';
41
+ /** `'grid'` lays the box's children out as a CSS grid — see docs/styling.md,
42
+ * "Grid"; `'none'` hides the box and everything in it. */
43
+ export type Display = 'flex' | 'grid' | 'none';
44
+
45
+ /**
46
+ * A grid line, as CSS's `grid-column` / `grid-row` write it: a number
47
+ * counting from 1 at the start or from -1 at the end, `'span 2'`, or a
48
+ * start and an end — `'2 / 4'`, `'1 / -1'`, `'2 / span 3'`.
49
+ */
50
+ export type GridLine =
51
+ | number
52
+ | 'auto'
53
+ | `span ${number}`
54
+ | `${number} / ${number}`
55
+ | `${number}/${number}`
56
+ | `${number} / span ${number}`
57
+ | `span ${number} / ${number}`
58
+ | `${number} / auto`
59
+ | `auto / ${number}`
60
+ | `auto / span ${number}`;
61
+
62
+ export type GridAutoFlow =
63
+ | 'row'
64
+ | 'column'
65
+ | 'dense'
66
+ | 'row dense'
67
+ | 'column dense'
68
+ | 'dense row'
69
+ | 'dense column';
70
+
71
+ export type JustifyItems = 'flex-start' | 'center' | 'flex-end' | 'stretch';
72
+ export type JustifySelf = 'auto' | JustifyItems;
42
73
  export type Overflow = 'visible' | 'hidden' | 'scroll';
43
74
  export type BorderStyle = 'solid' | 'dashed';
44
75
  export type PointerEvents = 'auto' | 'none';
@@ -108,7 +139,67 @@ export interface LayoutStyle {
108
139
  * say `minWidth: 0` (or an `overflow` that clips) for that. */
109
140
  flexShrink?: number;
110
141
  flexBasis?: Dimension;
111
- position?: PositionType;
142
+ /**
143
+ * `'relative'` (the default) and `'absolute'` move the box by its insets.
144
+ * `'sticky'` lays it out in flow like `'relative'`, then holds it inside
145
+ * the nearest scroll pane's edges by its insets — `top: 0` keeps a header
146
+ * at the top of the pane until its parent scrolls away and takes it
147
+ * along. A position registered with `registerPosition` (react-x11/host)
148
+ * is written the same way, by name or with its options, and also lays the
149
+ * box out in flow and then moves it. Either paints over its siblings of
150
+ * the same `zIndex`. See docs/styling.md, "Sticky positioning" and
151
+ * "Custom positions".
152
+ */
153
+ position?: import('../host.js').PositionValue;
154
+ /**
155
+ * A layout algorithm arranging this box's children in place of flexbox,
156
+ * by name or written with its options — `'equal-row'`,
157
+ * `{ name: 'masonry', columns: 3 }`. It runs inside the layout pass; the
158
+ * box's own `gap`, `justifyContent` and `alignItems` are what it spaces
159
+ * and aligns by. See docs/styling.md, "Custom layouts".
160
+ */
161
+ layout?: import('../host.js').LayoutValue;
162
+ /** What this box tells the layout arranging it — `masonry`'s `span`. */
163
+ layoutItem?: import('../host.js').CustomLayoutItem;
164
+ /**
165
+ * A `display: 'grid'` box's columns: a CSS track list — `'200px 1fr'`,
166
+ * `'auto 1fr'`, `'repeat(auto-fill, minmax(200px, 1fr))'` — with lengths
167
+ * in logical pixels written `px`, `%` of the grid and `fr` shares; or a
168
+ * number, that many equal columns (`repeat(n, minmax(0, 1fr))`). A list
169
+ * that would not lay out is an error naming it. See docs/styling.md,
170
+ * "Grid".
171
+ */
172
+ gridTemplateColumns?: number | string;
173
+ /** …its rows, the same way. */
174
+ gridTemplateRows?: number | string;
175
+ /** Named areas: one quoted string per row, as CSS writes them —
176
+ * `'"head head" "side main"'` — or an array of the rows. */
177
+ gridTemplateAreas?: string | readonly string[];
178
+ /** The size of a column the grid adds for an item placed past its
179
+ * template: a track list taken in turn, or a number — a length, here. */
180
+ gridAutoColumns?: number | string;
181
+ /** …and of a row, the same way. */
182
+ gridAutoRows?: number | string;
183
+ /** How a child with no place of its own is placed: along the rows (the
184
+ * default) or the columns — and `dense` to back-fill a hole. */
185
+ gridAutoFlow?: GridAutoFlow;
186
+ /** Where each child sits across its area when it is narrower than the
187
+ * area; `alignItems` is the other axis. */
188
+ justifyItems?: JustifyItems;
189
+ /** A grid child's columns: `2`, `'span 2'`, `'1 / -1'`. */
190
+ gridColumn?: GridLine;
191
+ /** …and its rows. */
192
+ gridRow?: GridLine;
193
+ /** The name of an area in the grid's `gridTemplateAreas`, or CSS's four
194
+ * lines; `gridColumn` and `gridRow` beside it win for their axis. */
195
+ gridArea?: string | number;
196
+ /** Where this child sits across its area; `'auto'` is the grid's
197
+ * `justifyItems`. */
198
+ justifySelf?: JustifySelf;
199
+ /** An offset from the edge — or, under sticky or a registered position,
200
+ * the scheme's to read: how close to the scroll pane's edge the box may
201
+ * come, for sticky, where a percentage is of the pane's size. The same
202
+ * holds for the other five. */
112
203
  top?: Length;
113
204
  right?: Length;
114
205
  bottom?: Length;
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // A `<window>` asks for `fullscreen` or `maximized` and the window manager
5
5
  // decides — and it decides on its own account too, when the user hits a
6
- // hotkey or the titlebar button. `nodes.js` sends the requests; this is the
6
+ // hotkey or the titlebar button. `nodes/window/hints.js` sends the requests; this is the
7
7
  // half that reads back what actually happened, plus the two things an app
8
8
  // needs that are not states at all: whether it has the keyboard, and whether
9
9
  // anyone can see it.