react-x11 0.0.1 → 1.2.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/LICENSE +21 -0
- package/README.md +226 -0
- package/package.json +60 -7
- package/src/ClickToComponent.js +179 -0
- package/src/DevToolsIntegration.js +106 -0
- package/src/Reconciler.js +507 -0
- package/src/components/Button.js +63 -0
- package/src/components/Canvas3D.js +28 -0
- package/src/components/Checkbox.js +69 -0
- package/src/components/Dialog.js +138 -0
- package/src/components/Menu.js +644 -0
- package/src/components/ProgressBar.js +48 -0
- package/src/components/Radio.js +95 -0
- package/src/components/Select.js +272 -0
- package/src/components/Slider.js +177 -0
- package/src/components/Switch.js +49 -0
- package/src/components/Tooltip.js +146 -0
- package/src/components/anchor.js +211 -0
- package/src/components/index.js +17 -0
- package/src/components/keys.js +21 -0
- package/src/components/theme.js +66 -0
- package/src/components/typeahead.js +56 -0
- package/src/events.js +584 -0
- package/src/geometry3d.js +223 -0
- package/src/glnodes.js +275 -0
- package/src/index.js +30 -0
- package/src/mat4.js +235 -0
- package/src/nodes.js +1985 -0
- package/src/pointer3d.js +158 -0
- package/src/priority.js +39 -0
- package/src/raycast3d.js +146 -0
- package/src/richnodes.js +436 -0
- package/src/scene3d.js +683 -0
- package/src/styles.js +189 -0
- package/.npmignore +0 -17
- package/combobox.jsx +0 -69
- package/react-x11.js +0 -119
- package/test-canvas.js +0 -98
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Widget components built purely on the host primitives — no reconciler
|
|
2
|
+
// support needed. Plain createElement (no JSX) so the library stays
|
|
3
|
+
// build-step-free for consumers.
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
6
|
+
import { labelContent, useTheme } from './theme.js';
|
|
7
|
+
import { centerRect } from './anchor.js';
|
|
8
|
+
import { XK_ESCAPE } from './keys.js';
|
|
9
|
+
|
|
10
|
+
const h = React.createElement;
|
|
11
|
+
|
|
12
|
+
const DEFAULT_WIDTH = 360;
|
|
13
|
+
|
|
14
|
+
const DEFAULT_HEIGHT = 170;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* <Dialog open title onClose actions>…</Dialog> — a modal dialog in a
|
|
18
|
+
* `<popup trapFocus grab>`, centred over the owner window.
|
|
19
|
+
*
|
|
20
|
+
* The focus behaviour is the renderer's, not this component's: `trapFocus`
|
|
21
|
+
* keeps Tab inside the dialog, stops presses elsewhere from moving focus,
|
|
22
|
+
* and hands focus back to whatever had it — usually the button that opened
|
|
23
|
+
* the dialog — when it closes. Put `autoFocus` on a control inside to choose
|
|
24
|
+
* the first stop; otherwise the dialog surface itself takes focus, so
|
|
25
|
+
* Escape and Tab work immediately (`docs/events.md`, "Focus scopes").
|
|
26
|
+
*
|
|
27
|
+
* Escape closes: keys go to the focused node inside the popup and bubble out
|
|
28
|
+
* through the popup's place in the JSX tree. `grab` makes a press anywhere
|
|
29
|
+
* else in the session close it too. Pointer modality is *not* enforced —
|
|
30
|
+
* widgets in the owner window stay clickable behind the dialog, so use it
|
|
31
|
+
* for confirmations rather than as a security boundary.
|
|
32
|
+
*
|
|
33
|
+
* A `<popup>` is a real X window and needs its size up front, hence explicit
|
|
34
|
+
* `width`/`height` rather than sizing to content.
|
|
35
|
+
*/
|
|
36
|
+
export function Dialog({
|
|
37
|
+
open,
|
|
38
|
+
title,
|
|
39
|
+
children,
|
|
40
|
+
onClose,
|
|
41
|
+
actions,
|
|
42
|
+
width = DEFAULT_WIDTH,
|
|
43
|
+
height = DEFAULT_HEIGHT,
|
|
44
|
+
...boxProps
|
|
45
|
+
}) {
|
|
46
|
+
const theme = useTheme();
|
|
47
|
+
const anchor = useRef(null);
|
|
48
|
+
const surface = useRef(null);
|
|
49
|
+
const [rect, setRect] = useState(null);
|
|
50
|
+
|
|
51
|
+
// the popup needs screen coordinates, which are only knowable once the
|
|
52
|
+
// anchor is laid out inside a realized window — hence a layout effect
|
|
53
|
+
// rather than placement at render time
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
if (!open) {
|
|
56
|
+
setRect(null);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const next = centerRect(anchor.current, { width, height });
|
|
60
|
+
if (next) setRect(next);
|
|
61
|
+
}, [open, width, height]);
|
|
62
|
+
|
|
63
|
+
// nothing inside claimed focus (no autoFocus): the dialog takes it itself,
|
|
64
|
+
// the way a browser focuses a <dialog> element, so keys have somewhere to
|
|
65
|
+
// land and the trap has an inside
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const node = surface.current;
|
|
68
|
+
if (!rect || !node || node.focusWithin) return;
|
|
69
|
+
node.focus();
|
|
70
|
+
}, [rect]);
|
|
71
|
+
|
|
72
|
+
const onKeyDown = (ev) => {
|
|
73
|
+
if (ev.keysym === XK_ESCAPE) {
|
|
74
|
+
ev.preventDefault();
|
|
75
|
+
onClose?.();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return h(
|
|
80
|
+
'box',
|
|
81
|
+
// out of flow: an anchor for the window geometry, never part of layout
|
|
82
|
+
{ ref: anchor, position: 'absolute', width: 0, height: 0 },
|
|
83
|
+
rect &&
|
|
84
|
+
h(
|
|
85
|
+
'popup',
|
|
86
|
+
{
|
|
87
|
+
x: rect.x,
|
|
88
|
+
y: rect.y,
|
|
89
|
+
width: rect.width,
|
|
90
|
+
height: rect.height,
|
|
91
|
+
trapFocus: true,
|
|
92
|
+
grab: true,
|
|
93
|
+
windowType: 'dialog',
|
|
94
|
+
backgroundColor: theme.background,
|
|
95
|
+
onDismiss: () => onClose?.(),
|
|
96
|
+
onKeyDown,
|
|
97
|
+
},
|
|
98
|
+
h(
|
|
99
|
+
'box',
|
|
100
|
+
{
|
|
101
|
+
ref: surface,
|
|
102
|
+
// focusable but not tabbable: a fallback focus holder, never a
|
|
103
|
+
// stop in the dialog's own tab order
|
|
104
|
+
tabIndex: -1,
|
|
105
|
+
flexGrow: 1,
|
|
106
|
+
padding: 16,
|
|
107
|
+
gap: 12,
|
|
108
|
+
borderWidth: 1,
|
|
109
|
+
borderColor: theme.border,
|
|
110
|
+
backgroundColor: theme.background,
|
|
111
|
+
...boxProps,
|
|
112
|
+
},
|
|
113
|
+
title &&
|
|
114
|
+
h(
|
|
115
|
+
'text',
|
|
116
|
+
{ fontSize: 15, fontWeight: 'bold', color: theme.text },
|
|
117
|
+
title,
|
|
118
|
+
),
|
|
119
|
+
h(
|
|
120
|
+
'box',
|
|
121
|
+
{ flexGrow: 1, gap: 8 },
|
|
122
|
+
labelContent(children, { color: theme.text }),
|
|
123
|
+
),
|
|
124
|
+
actions &&
|
|
125
|
+
h(
|
|
126
|
+
'box',
|
|
127
|
+
{
|
|
128
|
+
flexDirection: 'row',
|
|
129
|
+
justifyContent: 'flex-end',
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
gap: 8,
|
|
132
|
+
},
|
|
133
|
+
actions,
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
}
|