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.
- package/README.md +278 -129
- package/package.json +10 -3
- package/src/Reconciler.js +15 -17
- package/src/a11y.js +2 -2
- package/src/anchor.js +7 -5
- package/src/bootstrap.js +14 -0
- package/src/clientmessage.js +1 -1
- package/src/cocoa/app.js +292 -49
- package/src/cocoa/bezels.js +175 -30
- package/src/cocoa/dnd.js +27 -13
- package/src/cocoa/fonts.js +3 -3
- package/src/cocoa/glarea.js +20 -3
- package/src/cocoa/main.d.ts +8 -0
- package/src/cocoa/main.js +43 -0
- package/src/cocoa/panehost.js +15 -5
- package/src/cocoa/presenter.js +13 -9
- package/src/cocoa/promotion.js +4 -7
- package/src/cocoa/relaunch.js +207 -0
- package/src/cocoa/threaded.js +246 -0
- package/src/cocoa/window.js +256 -42
- package/src/components/Select.js +2 -2
- package/src/components/anchor.js +3 -3
- package/src/components/native.js +12 -7
- package/src/components/theme.js +2 -2
- package/src/debug.js +1 -1
- package/src/decorations.js +1 -1
- package/src/editmenu.js +2 -2
- package/src/errors.js +46 -0
- package/src/events.js +6 -6
- package/src/foreignnodes.js +3 -2
- package/src/frames.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1653 -0
- package/src/host.d.ts +230 -0
- package/src/host.js +11 -3
- package/src/imagesource.js +1 -1
- package/src/index.d.ts +21 -4
- package/src/index.js +9 -1
- package/src/layouts.js +721 -0
- package/src/node.d.ts +4 -2
- package/src/node.js +19 -21
- package/src/nodes/animation.js +644 -0
- package/src/nodes/box.js +21 -0
- package/src/nodes/boxpaint.js +473 -0
- package/src/nodes/canvas.js +269 -0
- package/src/nodes/cascade.js +600 -0
- package/src/nodes/damage.js +183 -0
- package/src/nodes/edithistory.js +124 -0
- package/src/nodes/editmenupopup.js +260 -0
- package/src/nodes/hittest.js +185 -0
- package/src/nodes/image.js +266 -0
- package/src/nodes/install.js +75 -0
- package/src/nodes/invalidate.js +465 -0
- package/src/nodes/kinds.js +31 -0
- package/src/nodes/layout.js +439 -0
- package/src/nodes/layouthost.js +949 -0
- package/src/nodes/node.js +868 -0
- package/src/nodes/paint.js +466 -0
- package/src/nodes/position.js +366 -0
- package/src/nodes/preedit.js +127 -0
- package/src/nodes/queries.js +330 -0
- package/src/nodes/rects.js +102 -0
- package/src/nodes/scrollable.js +891 -0
- package/src/nodes/scrollbars.js +138 -0
- package/src/nodes/scrollblit.js +1034 -0
- package/src/nodes/selectable.js +142 -0
- package/src/nodes/styling.js +225 -0
- package/src/nodes/text.js +649 -0
- package/src/nodes/textarea.js +391 -0
- package/src/nodes/textinput.js +1146 -0
- package/src/nodes/util.js +17 -0
- package/src/nodes/window/anchoring.js +161 -0
- package/src/nodes/window/capabilities.js +190 -0
- package/src/nodes/window/debugpaint.js +83 -0
- package/src/nodes/window/droptarget.js +145 -0
- package/src/nodes/window/floors.js +577 -0
- package/src/nodes/window/flush.js +334 -0
- package/src/nodes/window/hints.js +482 -0
- package/src/nodes/window/listeners.js +222 -0
- package/src/nodes/window/popup.js +71 -0
- package/src/nodes/window/size.js +591 -0
- package/src/nodes/window/window.js +945 -0
- package/src/palette.js +1 -1
- package/src/registry.js +7 -3
- package/src/styles.js +137 -15
- package/src/svgnodes.js +2 -1
- package/src/testing/harness.js +2 -2
- package/src/textselection.js +5 -3
- package/src/trace-registry.js +1 -1
- package/src/types/components.d.ts +38 -6
- package/src/types/elements.d.ts +11 -1
- package/src/types/nodes.d.ts +17 -2
- package/src/types/style.d.ts +94 -3
- package/src/windowstate.js +1 -1
- package/src/yoga.js +1 -1
- 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
|
-
|
|
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
|
|
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
|
|
695
|
-
*
|
|
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
|
-
|
|
749
|
+
const grid = GRID_VALUES.has(key);
|
|
750
|
+
if (!grid && key !== 'backgroundImage' && key !== 'boxShadow') return;
|
|
706
751
|
try {
|
|
707
|
-
if (
|
|
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
|
-
|
|
1693
|
-
|
|
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
|
|
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
|
package/src/testing/harness.js
CHANGED
|
@@ -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();
|
package/src/textselection.js
CHANGED
|
@@ -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
|
|
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);
|
package/src/trace-registry.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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>
|
|
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;
|
package/src/types/elements.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/src/types/nodes.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
}
|
package/src/types/style.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/src/windowstate.js
CHANGED
|
@@ -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.
|