gpui-bridge-react 0.0.0-stage → 0.3.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 +41 -2
- package/dist/src/components.d.ts +551 -0
- package/dist/src/components.js +94 -0
- package/dist/src/host-config.d.ts +45 -0
- package/dist/src/host-config.js +491 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +16 -0
- package/package.json +50 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gpui-bridge contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# gpui-bridge-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A React renderer for [gpui-bridge](https://www.npmjs.com/package/gpui-bridge): native desktop
|
|
4
|
+
windows, drawn by GPUI and gpui-kit, declared as React components.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { Button, Div, Text, render } from "gpui-bridge-react";
|
|
9
|
+
|
|
10
|
+
function Counter() {
|
|
11
|
+
const [count, setCount] = useState(0);
|
|
12
|
+
return (
|
|
13
|
+
<Div class="flex flex-col items-center gap-4 p-6 size-full">
|
|
14
|
+
<Text class="text-xl font-bold">{String(count)}</Text>
|
|
15
|
+
<Button variant="primary" onClick={() => setCount(count + 1)}>Add</Button>
|
|
16
|
+
</Div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await render(<Counter />, { window: { title: "Counter", width: 320, height: 200 } });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm install gpui-bridge-react gpui-bridge react
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`gpui-bridge` and `react` are **peer dependencies**: this package has no native binary of its
|
|
30
|
+
own. The host (`gpui-bridge-host`) arrives with `gpui-bridge`, which pulls in the platform
|
|
31
|
+
package for the machine installing it (Windows x64, macOS arm64 or Linux x64 today).
|
|
32
|
+
|
|
33
|
+
Host lookup is the one from `gpui-bridge`: `createApp` options, `$GPUI_BRIDGE_HOST`, the asset of
|
|
34
|
+
the platform package, then a Cargo build in a checkout. `render` accepts the same host options.
|
|
35
|
+
|
|
36
|
+
## Links
|
|
37
|
+
|
|
38
|
+
- [gpui-bridge](https://www.npmjs.com/package/gpui-bridge), the underlying client.
|
|
39
|
+
- [React examples](https://github.com/InDIOS/gpui-bridge/tree/main/examples/react).
|
|
40
|
+
- [Source and issues](https://github.com/InDIOS/gpui-bridge).
|
|
41
|
+
|
|
42
|
+
Protocol version 1. MIT licensed.
|
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
2
|
+
import type { A11y, ButtonProps as WireButtonProps, DiffSelection, ImageProps as WireImageProps, Menu, MenuItem, NodeProps, NodeType, Option, QuestionnaireAnswer, RadioGroupProps as WireRadioGroupProps, SettingsChange, TableCell, TableColumn, TableSort, TabsProps as WireTabsProps, UINode, WindowControl, WindowEventPayload } from "gpui-bridge";
|
|
3
|
+
/** A `ref` gets the node (`ref.current.focus()`). */
|
|
4
|
+
type HostComponent<P> = (props: P & {
|
|
5
|
+
ref?: Ref<UINode<NodeType>>;
|
|
6
|
+
}) => ReactNode;
|
|
7
|
+
/** Props every node accepts (PROTOCOL.md section 16). */
|
|
8
|
+
export interface SharedProps {
|
|
9
|
+
/** Accessibility: a node without it is not reported to assistive technology. */
|
|
10
|
+
a11y?: A11y;
|
|
11
|
+
}
|
|
12
|
+
export interface DivProps extends SharedProps {
|
|
13
|
+
class?: string;
|
|
14
|
+
/** Makes the div a title bar part handled by the system: drag area or minimize/maximize/close button. */
|
|
15
|
+
windowControl?: WindowControl;
|
|
16
|
+
/** Opens on right-click; choosing an item calls `onMenu` with its `id`. */
|
|
17
|
+
contextMenu?: MenuItem[];
|
|
18
|
+
onClick?: () => void;
|
|
19
|
+
onDoubleClick?: () => void;
|
|
20
|
+
onMenu?: (item: string) => void;
|
|
21
|
+
/** Shown when the pointer rests on the div. */
|
|
22
|
+
tooltip?: string;
|
|
23
|
+
/** Files dropped on the div. */
|
|
24
|
+
onDrop?: (paths: string[]) => void;
|
|
25
|
+
/** Makes the div focusable; called with keys such as `escape` or `ctrl-enter`. */
|
|
26
|
+
onKeyDown?: (key: string) => void;
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
export interface TextProps extends SharedProps {
|
|
30
|
+
class?: string;
|
|
31
|
+
/** Text content: strings and numbers. */
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
export interface ButtonProps extends SharedProps {
|
|
35
|
+
/** Alternatively, pass the label as text children. */
|
|
36
|
+
label?: string;
|
|
37
|
+
variant?: WireButtonProps["variant"];
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
tooltip?: string;
|
|
40
|
+
class?: string;
|
|
41
|
+
onClick?: () => void;
|
|
42
|
+
children?: ReactNode;
|
|
43
|
+
}
|
|
44
|
+
export interface InputProps extends SharedProps {
|
|
45
|
+
/** Passing `value` makes the input controlled (the host waits for React to confirm edits). */
|
|
46
|
+
value?: string;
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
class?: string;
|
|
50
|
+
onChange?: (value: string) => void;
|
|
51
|
+
onSubmit?: (value: string) => void;
|
|
52
|
+
onKeyDown?: (key: string) => void;
|
|
53
|
+
}
|
|
54
|
+
export interface CheckboxProps extends SharedProps {
|
|
55
|
+
/** Passing `checked` makes the checkbox controlled (the host waits for React to confirm clicks). */
|
|
56
|
+
checked?: boolean;
|
|
57
|
+
label?: string;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
class?: string;
|
|
60
|
+
onChange?: (checked: boolean) => void;
|
|
61
|
+
}
|
|
62
|
+
export interface IconProps extends SharedProps {
|
|
63
|
+
/** A bundled icon, by its Lucide name (`folder`, `file-text`…). */
|
|
64
|
+
name: string;
|
|
65
|
+
class?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface ImageProps extends SharedProps {
|
|
68
|
+
/** A local image file; a relative path is resolved against the host's working directory. */
|
|
69
|
+
src?: string;
|
|
70
|
+
/** The image bytes as standard base64 (no `data:` prefix), for images the client downloads
|
|
71
|
+
* itself. While non-empty it wins over `src`. */
|
|
72
|
+
data?: string;
|
|
73
|
+
fit?: WireImageProps["fit"];
|
|
74
|
+
class?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface SwitchProps extends SharedProps {
|
|
77
|
+
/** Passing `checked` makes the switch controlled (the host waits for React to confirm clicks). */
|
|
78
|
+
checked?: boolean;
|
|
79
|
+
label?: string;
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
class?: string;
|
|
82
|
+
onChange?: (checked: boolean) => void;
|
|
83
|
+
}
|
|
84
|
+
export interface RadioGroupProps extends SharedProps {
|
|
85
|
+
options: Option[];
|
|
86
|
+
/** Passing `value` makes the group controlled (the host waits for React to confirm choices). */
|
|
87
|
+
value?: string;
|
|
88
|
+
direction?: WireRadioGroupProps["direction"];
|
|
89
|
+
disabled?: boolean;
|
|
90
|
+
class?: string;
|
|
91
|
+
onChange?: (value: string) => void;
|
|
92
|
+
}
|
|
93
|
+
export interface SelectProps extends SharedProps {
|
|
94
|
+
options: Option[];
|
|
95
|
+
/** Passing `value` makes the select controlled (the host waits for React to confirm choices). */
|
|
96
|
+
value?: string;
|
|
97
|
+
placeholder?: string;
|
|
98
|
+
searchable?: boolean;
|
|
99
|
+
disabled?: boolean;
|
|
100
|
+
class?: string;
|
|
101
|
+
onChange?: (value: string) => void;
|
|
102
|
+
}
|
|
103
|
+
export interface SliderProps extends SharedProps {
|
|
104
|
+
/** Passing `value` makes the slider controlled (the host waits for React to confirm drags). */
|
|
105
|
+
value?: number;
|
|
106
|
+
min?: number;
|
|
107
|
+
max?: number;
|
|
108
|
+
step?: number;
|
|
109
|
+
disabled?: boolean;
|
|
110
|
+
class?: string;
|
|
111
|
+
onChange?: (value: number) => void;
|
|
112
|
+
}
|
|
113
|
+
export interface TextareaProps extends SharedProps {
|
|
114
|
+
/** The host keeps the text; a `value` that differs from it replaces it. */
|
|
115
|
+
value?: string;
|
|
116
|
+
placeholder?: string;
|
|
117
|
+
disabled?: boolean;
|
|
118
|
+
class?: string;
|
|
119
|
+
onChange?: (value: string) => void;
|
|
120
|
+
}
|
|
121
|
+
export interface MenuBarProps extends SharedProps {
|
|
122
|
+
menus: Menu[];
|
|
123
|
+
class?: string;
|
|
124
|
+
/** Called with the chosen item's `id`. */
|
|
125
|
+
onMenu?: (item: string) => void;
|
|
126
|
+
}
|
|
127
|
+
export interface TrayProps {
|
|
128
|
+
/** An image file (PNG, ICO…). */
|
|
129
|
+
icon?: string;
|
|
130
|
+
tooltip?: string;
|
|
131
|
+
/** Opens on right-click; a native menu, so item icons are not drawn. */
|
|
132
|
+
menu?: MenuItem[];
|
|
133
|
+
onClick?: () => void;
|
|
134
|
+
/** Called with the chosen item's `id`. */
|
|
135
|
+
onMenu?: (item: string) => void;
|
|
136
|
+
}
|
|
137
|
+
export interface DialogProps extends SharedProps {
|
|
138
|
+
/** Shown while true. The host closes it when the user answers: set your state in onOk/onCancel. */
|
|
139
|
+
open: boolean;
|
|
140
|
+
title?: string;
|
|
141
|
+
okText?: string;
|
|
142
|
+
/** Adds a cancel button. */
|
|
143
|
+
cancelText?: string;
|
|
144
|
+
width?: number;
|
|
145
|
+
/** Classes of the body that holds the children. */
|
|
146
|
+
class?: string;
|
|
147
|
+
onOk?: () => void;
|
|
148
|
+
/** Cancel button, Escape, the X or a click outside. */
|
|
149
|
+
onCancel?: () => void;
|
|
150
|
+
children?: ReactNode;
|
|
151
|
+
}
|
|
152
|
+
export interface VirtualListProps extends SharedProps {
|
|
153
|
+
/** Every row's height in pixels; default 32. */
|
|
154
|
+
rowHeight?: number;
|
|
155
|
+
/** Give the list a height (`flex-1`, `h-64`...): it fills its container. */
|
|
156
|
+
class?: string;
|
|
157
|
+
/** One child per row; only the rows in view are drawn. */
|
|
158
|
+
children?: ReactNode;
|
|
159
|
+
}
|
|
160
|
+
export interface TableProps extends SharedProps {
|
|
161
|
+
columns: TableColumn[];
|
|
162
|
+
/** One object per row. Keep it in state: a new array sends every row again. */
|
|
163
|
+
rows: Record<string, TableCell>[];
|
|
164
|
+
/** Passing `selected` makes the selection controlled (the host waits for React to confirm it). */
|
|
165
|
+
selected?: number | null;
|
|
166
|
+
stripe?: boolean;
|
|
167
|
+
bordered?: boolean;
|
|
168
|
+
class?: string;
|
|
169
|
+
/** A row index; `null` when Escape clears the selection. */
|
|
170
|
+
onChange?: (row: number | null) => void;
|
|
171
|
+
onDoubleClick?: (row: number) => void;
|
|
172
|
+
/** Sort `rows` yourself and render them again. */
|
|
173
|
+
onSort?: (sort: TableSort) => void;
|
|
174
|
+
}
|
|
175
|
+
export interface TabsProps extends SharedProps {
|
|
176
|
+
/** Ignored when the `Tabs` has `Tab` children. */
|
|
177
|
+
tabs?: Option[];
|
|
178
|
+
/** Passing `value` makes the tabs controlled (the host waits for React to confirm choices). */
|
|
179
|
+
value?: string;
|
|
180
|
+
variant?: WireTabsProps["variant"];
|
|
181
|
+
class?: string;
|
|
182
|
+
onChange?: (value: string) => void;
|
|
183
|
+
/** One `Tab` per child. */
|
|
184
|
+
children?: ReactNode;
|
|
185
|
+
}
|
|
186
|
+
export interface TabProps extends SharedProps {
|
|
187
|
+
/** What `change` reports for this tab. Required: the host rejects a `Tab` without it. */
|
|
188
|
+
value?: string;
|
|
189
|
+
/** The tab's text, drawn before its children. A chip of nodes names itself with `a11y.label`. */
|
|
190
|
+
label?: string;
|
|
191
|
+
disabled?: boolean;
|
|
192
|
+
/** Nodes inside the chip, e.g. an `Icon`, a `Text` and a `Tag`; a last `Button` is drawn after them. */
|
|
193
|
+
children?: ReactNode;
|
|
194
|
+
/** Styles the chip itself: `flex-1` makes the tabs share the bar's width. */
|
|
195
|
+
class?: string;
|
|
196
|
+
}
|
|
197
|
+
export interface WindowProps {
|
|
198
|
+
title?: string;
|
|
199
|
+
width?: number;
|
|
200
|
+
height?: number;
|
|
201
|
+
/** Initial position; only read when the window opens. */
|
|
202
|
+
x?: number;
|
|
203
|
+
y?: number;
|
|
204
|
+
/** Minimum size the user can resize to; only read when the window opens. */
|
|
205
|
+
minWidth?: number;
|
|
206
|
+
minHeight?: number;
|
|
207
|
+
/** Minimizes (`true`) or restores (`false`) on the next commit. */
|
|
208
|
+
minimized?: boolean;
|
|
209
|
+
/** No system title bar; only read when the window opens. */
|
|
210
|
+
frameless?: boolean;
|
|
211
|
+
/** Classes of the window's root div (default `size-full`). */
|
|
212
|
+
class?: string;
|
|
213
|
+
/** The user closed the window. Unmount the `Window` (or React keeps an element for a window that is gone). */
|
|
214
|
+
onClose?: () => void;
|
|
215
|
+
/** The window gained the system focus. */
|
|
216
|
+
onFocus?: (event: WindowEventPayload) => void;
|
|
217
|
+
/** The window lost the system focus. */
|
|
218
|
+
onBlur?: (event: WindowEventPayload) => void;
|
|
219
|
+
/** The user resized the window: the payload has `x`, `y`, `width`, `height`. */
|
|
220
|
+
onResize?: (bounds: WindowEventPayload) => void;
|
|
221
|
+
/** The user moved the window: the payload has `x`, `y`, `width`, `height`. */
|
|
222
|
+
onMove?: (bounds: WindowEventPayload) => void;
|
|
223
|
+
/** The window is being presented or not (`visible`); minimizing reports it too. */
|
|
224
|
+
onVisibility?: (event: WindowEventPayload) => void;
|
|
225
|
+
children?: ReactNode;
|
|
226
|
+
}
|
|
227
|
+
export declare const Div: HostComponent<DivProps>;
|
|
228
|
+
export declare const Text: HostComponent<TextProps>;
|
|
229
|
+
export declare const Button: HostComponent<ButtonProps>;
|
|
230
|
+
export declare const Input: HostComponent<InputProps>;
|
|
231
|
+
export declare const Checkbox: HostComponent<CheckboxProps>;
|
|
232
|
+
export declare const Icon: HostComponent<IconProps>;
|
|
233
|
+
export declare const Image: HostComponent<ImageProps>;
|
|
234
|
+
export declare const MenuBar: HostComponent<MenuBarProps>;
|
|
235
|
+
export declare const Switch: HostComponent<SwitchProps>;
|
|
236
|
+
export declare const RadioGroup: HostComponent<RadioGroupProps>;
|
|
237
|
+
export declare const Select: HostComponent<SelectProps>;
|
|
238
|
+
export declare const Slider: HostComponent<SliderProps>;
|
|
239
|
+
export declare const Textarea: HostComponent<TextareaProps>;
|
|
240
|
+
/** A modal dialog; its children are its body. */
|
|
241
|
+
export declare const Dialog: HostComponent<DialogProps>;
|
|
242
|
+
/** A list that draws only the rows in view; each child is a row of `rowHeight`. */
|
|
243
|
+
export declare const VirtualList: HostComponent<VirtualListProps>;
|
|
244
|
+
/** A data table with text cells; it draws only the rows in view. */
|
|
245
|
+
export declare const Table: HostComponent<TableProps>;
|
|
246
|
+
/** A tab bar; render the chosen tab's content yourself. */
|
|
247
|
+
export declare const Tabs: HostComponent<TabsProps>;
|
|
248
|
+
/** One tab of a `Tabs`: its children are the nodes of its chip. */
|
|
249
|
+
export declare const Tab: HostComponent<TabProps>;
|
|
250
|
+
/**
|
|
251
|
+
* Shows the system tray icon while mounted (one per app). While it is shown, closing the
|
|
252
|
+
* last window does not end the app; unmounting it then does.
|
|
253
|
+
*/
|
|
254
|
+
export declare const Tray: HostComponent<TrayProps>;
|
|
255
|
+
/** Opens another window while mounted; its children render there. Closes when unmounted. */
|
|
256
|
+
export declare const Window: HostComponent<WindowProps>;
|
|
257
|
+
export type AlertProps = NodeProps["Alert"] & {
|
|
258
|
+
onClose?: () => void;
|
|
259
|
+
children?: ReactNode;
|
|
260
|
+
};
|
|
261
|
+
export declare const Alert: HostComponent<AlertProps>;
|
|
262
|
+
export type BadgeProps = NodeProps["Badge"] & {
|
|
263
|
+
children?: ReactNode;
|
|
264
|
+
};
|
|
265
|
+
export declare const Badge: HostComponent<BadgeProps>;
|
|
266
|
+
export type TagProps = NodeProps["Tag"];
|
|
267
|
+
export declare const Tag: HostComponent<TagProps>;
|
|
268
|
+
export type KbdProps = NodeProps["Kbd"];
|
|
269
|
+
export declare const Kbd: HostComponent<KbdProps>;
|
|
270
|
+
export type ProgressProps = NodeProps["Progress"];
|
|
271
|
+
export declare const Progress: HostComponent<ProgressProps>;
|
|
272
|
+
export type SpinnerProps = NodeProps["Spinner"];
|
|
273
|
+
export declare const Spinner: HostComponent<SpinnerProps>;
|
|
274
|
+
export type SkeletonProps = NodeProps["Skeleton"];
|
|
275
|
+
export declare const Skeleton: HostComponent<SkeletonProps>;
|
|
276
|
+
export type RatingProps = NodeProps["Rating"] & {
|
|
277
|
+
onChange?: (value: number) => void;
|
|
278
|
+
};
|
|
279
|
+
export declare const Rating: HostComponent<RatingProps>;
|
|
280
|
+
export type PaginationProps = NodeProps["Pagination"] & {
|
|
281
|
+
onChange?: (page: number) => void;
|
|
282
|
+
};
|
|
283
|
+
export declare const Pagination: HostComponent<PaginationProps>;
|
|
284
|
+
export type CollapsibleProps = NodeProps["Collapsible"] & {
|
|
285
|
+
children?: ReactNode;
|
|
286
|
+
};
|
|
287
|
+
export declare const Collapsible: HostComponent<CollapsibleProps>;
|
|
288
|
+
export type AccordionProps = NodeProps["Accordion"] & {
|
|
289
|
+
onChange?: (id: string) => void;
|
|
290
|
+
children?: ReactNode;
|
|
291
|
+
};
|
|
292
|
+
export declare const Accordion: HostComponent<AccordionProps>;
|
|
293
|
+
export type AvatarProps = NodeProps["Avatar"];
|
|
294
|
+
export declare const Avatar: HostComponent<AvatarProps>;
|
|
295
|
+
export type StepperProps = NodeProps["Stepper"] & {
|
|
296
|
+
onChange?: (value: string) => void;
|
|
297
|
+
};
|
|
298
|
+
export declare const Stepper: HostComponent<StepperProps>;
|
|
299
|
+
export type NumberInputProps = NodeProps["NumberInput"] & {
|
|
300
|
+
onChange?: (value: string) => void;
|
|
301
|
+
onSubmit?: (value: string) => void;
|
|
302
|
+
};
|
|
303
|
+
export declare const NumberInput: HostComponent<NumberInputProps>;
|
|
304
|
+
export type OtpInputProps = NodeProps["OtpInput"] & {
|
|
305
|
+
onChange?: (value: string) => void;
|
|
306
|
+
};
|
|
307
|
+
export declare const OtpInput: HostComponent<OtpInputProps>;
|
|
308
|
+
export type FormProps = NodeProps["Form"] & {
|
|
309
|
+
children?: ReactNode;
|
|
310
|
+
};
|
|
311
|
+
export declare const Form: HostComponent<FormProps>;
|
|
312
|
+
export type FieldProps = NodeProps["Field"] & {
|
|
313
|
+
children?: ReactNode;
|
|
314
|
+
};
|
|
315
|
+
export declare const Field: HostComponent<FieldProps>;
|
|
316
|
+
export type GroupBoxProps = NodeProps["GroupBox"] & {
|
|
317
|
+
children?: ReactNode;
|
|
318
|
+
};
|
|
319
|
+
export declare const GroupBox: HostComponent<GroupBoxProps>;
|
|
320
|
+
export type DescriptionListProps = NodeProps["DescriptionList"];
|
|
321
|
+
export declare const DescriptionList: HostComponent<DescriptionListProps>;
|
|
322
|
+
export type StatusBarProps = NodeProps["StatusBar"] & {
|
|
323
|
+
children?: ReactNode;
|
|
324
|
+
};
|
|
325
|
+
export declare const StatusBar: HostComponent<StatusBarProps>;
|
|
326
|
+
export type ToolbarProps = NodeProps["Toolbar"] & {
|
|
327
|
+
children?: ReactNode;
|
|
328
|
+
};
|
|
329
|
+
export declare const Toolbar: HostComponent<ToolbarProps>;
|
|
330
|
+
export type ResizableProps = NodeProps["Resizable"] & {
|
|
331
|
+
children?: ReactNode;
|
|
332
|
+
};
|
|
333
|
+
export declare const Resizable: HostComponent<ResizableProps>;
|
|
334
|
+
export type CalendarProps = NodeProps["Calendar"] & {
|
|
335
|
+
onChange?: (day: string) => void;
|
|
336
|
+
};
|
|
337
|
+
export declare const Calendar: HostComponent<CalendarProps>;
|
|
338
|
+
export type TreeProps = NodeProps["Tree"] & {
|
|
339
|
+
onChange?: (id: string) => void;
|
|
340
|
+
onExpand?: (id: string) => void;
|
|
341
|
+
onCollapse?: (id: string) => void;
|
|
342
|
+
};
|
|
343
|
+
export declare const Tree: HostComponent<TreeProps>;
|
|
344
|
+
/** The selected lines, or `null` when the selection is cleared. */
|
|
345
|
+
export type DiffProps = NodeProps["Diff"] & {
|
|
346
|
+
onChange?: (selection: DiffSelection | null) => void;
|
|
347
|
+
};
|
|
348
|
+
export declare const Diff: HostComponent<DiffProps>;
|
|
349
|
+
/** Each child is one slide. */
|
|
350
|
+
export type CarouselProps = NodeProps["Carousel"] & {
|
|
351
|
+
onChange?: (index: number) => void;
|
|
352
|
+
children?: ReactNode;
|
|
353
|
+
};
|
|
354
|
+
export declare const Carousel: HostComponent<CarouselProps>;
|
|
355
|
+
/** The sections are data; `onChange` gets the clicked entry's id. */
|
|
356
|
+
export type SidebarProps = NodeProps["Sidebar"] & {
|
|
357
|
+
onChange?: (id: string) => void;
|
|
358
|
+
};
|
|
359
|
+
export declare const Sidebar: HostComponent<SidebarProps>;
|
|
360
|
+
/** A line, bar or pie chart of the points in `data`; it has no events of its own. */
|
|
361
|
+
export type ChartProps = NodeProps["Chart"];
|
|
362
|
+
export declare const Chart: HostComponent<ChartProps>;
|
|
363
|
+
/** Paints the drawing commands in `commands`; `onPointerDown` gets the local point. */
|
|
364
|
+
export type CanvasProps = NodeProps["Canvas"] & {
|
|
365
|
+
onPointerDown?: (point: {
|
|
366
|
+
x: number;
|
|
367
|
+
y: number;
|
|
368
|
+
}) => void;
|
|
369
|
+
};
|
|
370
|
+
export declare const Canvas: HostComponent<CanvasProps>;
|
|
371
|
+
/** A command palette over the sections in `items`; `onConfirm` gets the chosen entry's `id`. */
|
|
372
|
+
export type CommandProps = NodeProps["Command"] & {
|
|
373
|
+
onConfirm?: (id: string) => void;
|
|
374
|
+
};
|
|
375
|
+
export declare const Command: HostComponent<CommandProps>;
|
|
376
|
+
/** Settings pages of typed fields; `onChange` gets `{key, value}` of the field the user set. */
|
|
377
|
+
export type SettingsProps = NodeProps["Settings"] & {
|
|
378
|
+
onChange?: (change: SettingsChange) => void;
|
|
379
|
+
};
|
|
380
|
+
export declare const Settings: HostComponent<SettingsProps>;
|
|
381
|
+
/** A button that stays pressed; `onChange` gets the new `checked`. */
|
|
382
|
+
export type ToggleProps = NodeProps["Toggle"] & {
|
|
383
|
+
onChange?: (checked: boolean) => void;
|
|
384
|
+
};
|
|
385
|
+
export declare const Toggle: HostComponent<ToggleProps>;
|
|
386
|
+
/** An action with a menu: `onClick` is the left half, `onMenu` gets the chosen item's id. */
|
|
387
|
+
export type DropdownButtonProps = NodeProps["DropdownButton"] & {
|
|
388
|
+
onClick?: () => void;
|
|
389
|
+
onMenu?: (item: string) => void;
|
|
390
|
+
};
|
|
391
|
+
export declare const DropdownButton: HostComponent<DropdownButtonProps>;
|
|
392
|
+
export type LabelProps = NodeProps["Label"];
|
|
393
|
+
export declare const Label: HostComponent<LabelProps>;
|
|
394
|
+
/** A centered empty state; its children are the action under the header. */
|
|
395
|
+
export type EmptyProps = NodeProps["Empty"] & {
|
|
396
|
+
children?: ReactNode;
|
|
397
|
+
};
|
|
398
|
+
export declare const Empty: HostComponent<EmptyProps>;
|
|
399
|
+
export type ShimmerProps = NodeProps["Shimmer"];
|
|
400
|
+
export declare const Shimmer: HostComponent<ShimmerProps>;
|
|
401
|
+
/** A framed text field; `onChange` and `onSubmit` work as in an `Input`. */
|
|
402
|
+
export type InputGroupProps = NodeProps["InputGroup"] & {
|
|
403
|
+
onChange?: (value: string) => void;
|
|
404
|
+
onSubmit?: (value: string) => void;
|
|
405
|
+
};
|
|
406
|
+
export declare const InputGroup: HostComponent<InputGroupProps>;
|
|
407
|
+
/** Copies `value` on click; `onCopied` gets the text that was copied. */
|
|
408
|
+
export type ClipboardProps = NodeProps["Clipboard"] & {
|
|
409
|
+
onCopied?: (value: string) => void;
|
|
410
|
+
};
|
|
411
|
+
export declare const Clipboard: HostComponent<ClipboardProps>;
|
|
412
|
+
/** A confirmation; `onOk` and `onCancel` close it, as in a `Dialog`. */
|
|
413
|
+
export type AlertDialogProps = NodeProps["AlertDialog"] & {
|
|
414
|
+
onOk?: () => void;
|
|
415
|
+
onCancel?: () => void;
|
|
416
|
+
children?: ReactNode;
|
|
417
|
+
};
|
|
418
|
+
export declare const AlertDialog: HostComponent<AlertDialogProps>;
|
|
419
|
+
/** A panel that slides in from `side`; `onClose` is its X, Escape or a click on the overlay. */
|
|
420
|
+
export type SheetProps = NodeProps["Sheet"] & {
|
|
421
|
+
onClose?: () => void;
|
|
422
|
+
children?: ReactNode;
|
|
423
|
+
};
|
|
424
|
+
export declare const Sheet: HostComponent<SheetProps>;
|
|
425
|
+
/**
|
|
426
|
+
* A panel anchored to its first child (the trigger); the rest are the panel. `onChange` gets the
|
|
427
|
+
* new `open`: the host keeps it, as it keeps a `Checkbox`'s `checked`.
|
|
428
|
+
*/
|
|
429
|
+
export type PopoverProps = NodeProps["Popover"] & {
|
|
430
|
+
onChange?: (checked: boolean) => void;
|
|
431
|
+
children?: ReactNode;
|
|
432
|
+
};
|
|
433
|
+
export declare const Popover: HostComponent<PopoverProps>;
|
|
434
|
+
/** A card on hover, opened by the mouse alone; `onChange` gets the new `open`. */
|
|
435
|
+
export type HoverCardProps = NodeProps["HoverCard"] & {
|
|
436
|
+
onChange?: (checked: boolean) => void;
|
|
437
|
+
children?: ReactNode;
|
|
438
|
+
};
|
|
439
|
+
export declare const HoverCard: HostComponent<HoverCardProps>;
|
|
440
|
+
/** A day picker; `onChange` gets the chosen day, `null` when cleared. */
|
|
441
|
+
export type DatePickerProps = NodeProps["DatePicker"] & {
|
|
442
|
+
onChange?: (value: string | null) => void;
|
|
443
|
+
};
|
|
444
|
+
export declare const DatePicker: HostComponent<DatePickerProps>;
|
|
445
|
+
/** A time typed by segments; `onChange` gets the time (`HH:MM[:SS]`). */
|
|
446
|
+
export type TimeFieldProps = NodeProps["TimeField"] & {
|
|
447
|
+
onChange?: (value: string) => void;
|
|
448
|
+
};
|
|
449
|
+
export declare const TimeField: HostComponent<TimeFieldProps>;
|
|
450
|
+
/** A color swatch and hex box; `onChange` gets the color, `null` when cleared. */
|
|
451
|
+
export type ColorPickerProps = NodeProps["ColorPicker"] & {
|
|
452
|
+
onChange?: (value: string | null) => void;
|
|
453
|
+
};
|
|
454
|
+
export declare const ColorPicker: HostComponent<ColorPickerProps>;
|
|
455
|
+
/** A list that picks several options; `onChange` gets the whole selection. */
|
|
456
|
+
export type ComboboxProps = NodeProps["Combobox"] & {
|
|
457
|
+
onChange?: (value: string[]) => void;
|
|
458
|
+
};
|
|
459
|
+
export declare const Combobox: HostComponent<ComboboxProps>;
|
|
460
|
+
/** A chat bubble; its children are the content. */
|
|
461
|
+
export type BubbleProps = NodeProps["Bubble"] & {
|
|
462
|
+
children?: ReactNode;
|
|
463
|
+
};
|
|
464
|
+
export declare const Bubble: HostComponent<BubbleProps>;
|
|
465
|
+
/** A message row: a `MessageHeader` and a `MessageFooter` child, the rest is the content. */
|
|
466
|
+
export type MessageProps = NodeProps["Message"] & {
|
|
467
|
+
children?: ReactNode;
|
|
468
|
+
};
|
|
469
|
+
export declare const Message: HostComponent<MessageProps>;
|
|
470
|
+
export type MessageHeaderProps = NodeProps["MessageHeader"] & {
|
|
471
|
+
children?: ReactNode;
|
|
472
|
+
};
|
|
473
|
+
export declare const MessageHeader: HostComponent<MessageHeaderProps>;
|
|
474
|
+
export type MessageFooterProps = NodeProps["MessageFooter"] & {
|
|
475
|
+
children?: ReactNode;
|
|
476
|
+
};
|
|
477
|
+
export declare const MessageFooter: HostComponent<MessageFooterProps>;
|
|
478
|
+
/** A compact status row; children go after the text. */
|
|
479
|
+
export type MarkerProps = NodeProps["Marker"] & {
|
|
480
|
+
children?: ReactNode;
|
|
481
|
+
};
|
|
482
|
+
export declare const Marker: HostComponent<MarkerProps>;
|
|
483
|
+
/** A file or picture card; each handler shows its control (`onRetry` only while `failed`). */
|
|
484
|
+
export type AttachmentProps = NodeProps["Attachment"] & {
|
|
485
|
+
onClick?: () => void;
|
|
486
|
+
onRemove?: () => void;
|
|
487
|
+
onRetry?: () => void;
|
|
488
|
+
children?: ReactNode;
|
|
489
|
+
};
|
|
490
|
+
export declare const Attachment: HostComponent<AttachmentProps>;
|
|
491
|
+
/** A virtual list of messages anchored at the end; each child is a row. */
|
|
492
|
+
export type MessageScrollerProps = NodeProps["MessageScroller"] & {
|
|
493
|
+
children?: ReactNode;
|
|
494
|
+
};
|
|
495
|
+
export declare const MessageScroller: HostComponent<MessageScrollerProps>;
|
|
496
|
+
/** Questions one at a time; `onChange` gets one answer, `onSubmit` all of them. */
|
|
497
|
+
export type QuestionnaireProps = NodeProps["Questionnaire"] & {
|
|
498
|
+
onChange?: (answer: QuestionnaireAnswer) => void;
|
|
499
|
+
onSubmit?: (answers: QuestionnaireAnswer[]) => void;
|
|
500
|
+
};
|
|
501
|
+
export declare const Questionnaire: HostComponent<QuestionnaireProps>;
|
|
502
|
+
/**
|
|
503
|
+
* A selectable list over its `ListItem` children. Passing `value` makes the selection controlled.
|
|
504
|
+
* `onQuery` gets the search text (render the matching rows); `onLoadMore` asks for more rows.
|
|
505
|
+
*/
|
|
506
|
+
export type ListProps = NodeProps["List"] & {
|
|
507
|
+
value?: string | null;
|
|
508
|
+
onChange?: (value: string | null) => void;
|
|
509
|
+
onConfirm?: (value: string) => void;
|
|
510
|
+
onQuery?: (query: string) => void;
|
|
511
|
+
onLoadMore?: () => void;
|
|
512
|
+
children?: ReactNode;
|
|
513
|
+
};
|
|
514
|
+
export declare const List: HostComponent<ListProps>;
|
|
515
|
+
/** A `List` row: `value` is what the list reports; its children are the content. */
|
|
516
|
+
export type ListItemProps = NodeProps["ListItem"] & {
|
|
517
|
+
children?: ReactNode;
|
|
518
|
+
};
|
|
519
|
+
export declare const ListItem: HostComponent<ListItemProps>;
|
|
520
|
+
/** Panels the user rearranges: its `DockPanel` children. The host owns the layout. */
|
|
521
|
+
export type DockProps = NodeProps["Dock"] & {
|
|
522
|
+
children?: ReactNode;
|
|
523
|
+
};
|
|
524
|
+
export declare const Dock: HostComponent<DockProps>;
|
|
525
|
+
/** A `Dock` panel; `onClose` means the user closed it: stop rendering it. */
|
|
526
|
+
export type DockPanelProps = NodeProps["DockPanel"] & {
|
|
527
|
+
onClose?: () => void;
|
|
528
|
+
children?: ReactNode;
|
|
529
|
+
};
|
|
530
|
+
export declare const DockPanel: HostComponent<DockPanelProps>;
|
|
531
|
+
/** An `Accordion` section: `id` is what `onChange` reports; its children are the content. */
|
|
532
|
+
export type AccordionItemProps = NodeProps["AccordionItem"] & {
|
|
533
|
+
children?: ReactNode;
|
|
534
|
+
};
|
|
535
|
+
export declare const AccordionItem: HostComponent<AccordionItemProps>;
|
|
536
|
+
/** A `Dialog`'s or `AlertDialog`'s footer: its children (buttons) replace OK/Cancel. */
|
|
537
|
+
export type DialogFooterProps = NodeProps["DialogFooter"] & {
|
|
538
|
+
children?: ReactNode;
|
|
539
|
+
};
|
|
540
|
+
export declare const DialogFooter: HostComponent<DialogFooterProps>;
|
|
541
|
+
/** A `StatusBar`'s left side: its children replace the `left` text. */
|
|
542
|
+
export type StatusBarLeftProps = NodeProps["StatusBarLeft"] & {
|
|
543
|
+
children?: ReactNode;
|
|
544
|
+
};
|
|
545
|
+
export declare const StatusBarLeft: HostComponent<StatusBarLeftProps>;
|
|
546
|
+
/** A `StatusBar`'s right side: its children replace the `right` text. */
|
|
547
|
+
export type StatusBarRightProps = NodeProps["StatusBarRight"] & {
|
|
548
|
+
children?: ReactNode;
|
|
549
|
+
};
|
|
550
|
+
export declare const StatusBarRight: HostComponent<StatusBarRightProps>;
|
|
551
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Host components. Each is the node type string at runtime (React treats string
|
|
2
|
+
// element types as host components), typed as a component for JSX.
|
|
3
|
+
export const Div = "div";
|
|
4
|
+
export const Text = "text";
|
|
5
|
+
export const Button = "Button";
|
|
6
|
+
export const Input = "Input";
|
|
7
|
+
export const Checkbox = "Checkbox";
|
|
8
|
+
export const Icon = "Icon";
|
|
9
|
+
export const Image = "Image";
|
|
10
|
+
export const MenuBar = "MenuBar";
|
|
11
|
+
export const Switch = "Switch";
|
|
12
|
+
export const RadioGroup = "RadioGroup";
|
|
13
|
+
export const Select = "Select";
|
|
14
|
+
export const Slider = "Slider";
|
|
15
|
+
export const Textarea = "Textarea";
|
|
16
|
+
/** A modal dialog; its children are its body. */
|
|
17
|
+
export const Dialog = "Dialog";
|
|
18
|
+
/** A list that draws only the rows in view; each child is a row of `rowHeight`. */
|
|
19
|
+
export const VirtualList = "VirtualList";
|
|
20
|
+
/** A data table with text cells; it draws only the rows in view. */
|
|
21
|
+
export const Table = "Table";
|
|
22
|
+
/** A tab bar; render the chosen tab's content yourself. */
|
|
23
|
+
export const Tabs = "Tabs";
|
|
24
|
+
/** One tab of a `Tabs`: its children are the nodes of its chip. */
|
|
25
|
+
export const Tab = "Tab";
|
|
26
|
+
/**
|
|
27
|
+
* Shows the system tray icon while mounted (one per app). While it is shown, closing the
|
|
28
|
+
* last window does not end the app; unmounting it then does.
|
|
29
|
+
*/
|
|
30
|
+
export const Tray = "Tray";
|
|
31
|
+
/** Opens another window while mounted; its children render there. Closes when unmounted. */
|
|
32
|
+
export const Window = "Window";
|
|
33
|
+
export const Alert = "Alert";
|
|
34
|
+
export const Badge = "Badge";
|
|
35
|
+
export const Tag = "Tag";
|
|
36
|
+
export const Kbd = "Kbd";
|
|
37
|
+
export const Progress = "Progress";
|
|
38
|
+
export const Spinner = "Spinner";
|
|
39
|
+
export const Skeleton = "Skeleton";
|
|
40
|
+
export const Rating = "Rating";
|
|
41
|
+
export const Pagination = "Pagination";
|
|
42
|
+
export const Collapsible = "Collapsible";
|
|
43
|
+
export const Accordion = "Accordion";
|
|
44
|
+
export const Avatar = "Avatar";
|
|
45
|
+
export const Stepper = "Stepper";
|
|
46
|
+
export const NumberInput = "NumberInput";
|
|
47
|
+
export const OtpInput = "OtpInput";
|
|
48
|
+
export const Form = "Form";
|
|
49
|
+
export const Field = "Field";
|
|
50
|
+
export const GroupBox = "GroupBox";
|
|
51
|
+
export const DescriptionList = "DescriptionList";
|
|
52
|
+
export const StatusBar = "StatusBar";
|
|
53
|
+
export const Toolbar = "Toolbar";
|
|
54
|
+
export const Resizable = "Resizable";
|
|
55
|
+
export const Calendar = "Calendar";
|
|
56
|
+
export const Tree = "Tree";
|
|
57
|
+
export const Diff = "Diff";
|
|
58
|
+
export const Carousel = "Carousel";
|
|
59
|
+
export const Sidebar = "Sidebar";
|
|
60
|
+
export const Chart = "Chart";
|
|
61
|
+
export const Canvas = "Canvas";
|
|
62
|
+
export const Command = "Command";
|
|
63
|
+
export const Settings = "Settings";
|
|
64
|
+
export const Toggle = "Toggle";
|
|
65
|
+
export const DropdownButton = "DropdownButton";
|
|
66
|
+
export const Label = "Label";
|
|
67
|
+
export const Empty = "Empty";
|
|
68
|
+
export const Shimmer = "Shimmer";
|
|
69
|
+
export const InputGroup = "InputGroup";
|
|
70
|
+
export const Clipboard = "Clipboard";
|
|
71
|
+
export const AlertDialog = "AlertDialog";
|
|
72
|
+
export const Sheet = "Sheet";
|
|
73
|
+
export const Popover = "Popover";
|
|
74
|
+
export const HoverCard = "HoverCard";
|
|
75
|
+
export const DatePicker = "DatePicker";
|
|
76
|
+
export const TimeField = "TimeField";
|
|
77
|
+
export const ColorPicker = "ColorPicker";
|
|
78
|
+
export const Combobox = "Combobox";
|
|
79
|
+
export const Bubble = "Bubble";
|
|
80
|
+
export const Message = "Message";
|
|
81
|
+
export const MessageHeader = "MessageHeader";
|
|
82
|
+
export const MessageFooter = "MessageFooter";
|
|
83
|
+
export const Marker = "Marker";
|
|
84
|
+
export const Attachment = "Attachment";
|
|
85
|
+
export const MessageScroller = "MessageScroller";
|
|
86
|
+
export const Questionnaire = "Questionnaire";
|
|
87
|
+
export const List = "List";
|
|
88
|
+
export const ListItem = "ListItem";
|
|
89
|
+
export const Dock = "Dock";
|
|
90
|
+
export const DockPanel = "DockPanel";
|
|
91
|
+
export const AccordionItem = "AccordionItem";
|
|
92
|
+
export const DialogFooter = "DialogFooter";
|
|
93
|
+
export const StatusBarLeft = "StatusBarLeft";
|
|
94
|
+
export const StatusBarRight = "StatusBarRight";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import createReconciler from "react-reconciler";
|
|
2
|
+
import type { App, AppWindow, NodeType, UINode, WindowEventName, WindowEventPayload } from "gpui-bridge";
|
|
3
|
+
export interface Container {
|
|
4
|
+
app: App;
|
|
5
|
+
/** The window root; React's top-level children are appended here. */
|
|
6
|
+
root: UINode<"div">;
|
|
7
|
+
}
|
|
8
|
+
type Handler = (...args: any[]) => void;
|
|
9
|
+
/** Host node types plus `Window` and `Tray`, which add no node to their parent. */
|
|
10
|
+
type ElementType = NodeType | "Window" | "Tray";
|
|
11
|
+
export interface Instance {
|
|
12
|
+
/** For a `Window`, the root div of its window. */
|
|
13
|
+
node: UINode<NodeType>;
|
|
14
|
+
type: ElementType;
|
|
15
|
+
/** Latest React handlers by protocol event name; host events call through these. */
|
|
16
|
+
handlers: Map<string, Handler>;
|
|
17
|
+
window?: WindowState;
|
|
18
|
+
tray?: TrayState;
|
|
19
|
+
}
|
|
20
|
+
interface TrayState {
|
|
21
|
+
app: App;
|
|
22
|
+
onClick?: () => void;
|
|
23
|
+
onMenu?: (item: string) => void;
|
|
24
|
+
listener: (event: {
|
|
25
|
+
event: "click";
|
|
26
|
+
} | {
|
|
27
|
+
event: "menu";
|
|
28
|
+
item: string;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** React detaches a deleted instance more than once. */
|
|
31
|
+
removed?: boolean;
|
|
32
|
+
}
|
|
33
|
+
interface WindowState {
|
|
34
|
+
app: App;
|
|
35
|
+
handle: AppWindow;
|
|
36
|
+
onClose?: () => void;
|
|
37
|
+
/** Latest React handler per window event name. */
|
|
38
|
+
handlers: Map<WindowEventName, Handler>;
|
|
39
|
+
/** What each subscribed event calls, so it can be unsubscribed. */
|
|
40
|
+
listeners: Map<WindowEventName, (payload: WindowEventPayload) => void>;
|
|
41
|
+
/** Closed by the user: the host already forgot it. */
|
|
42
|
+
closed: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare const reconciler: createReconciler.Reconciler<Container, Instance, Instance, never, never, UINode<NodeType>, never>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
// react-reconciler host config mapping React's mutations onto the gpui-bridge client API.
|
|
2
|
+
import { createContext } from "react";
|
|
3
|
+
import createReconciler from "react-reconciler";
|
|
4
|
+
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants.js";
|
|
5
|
+
const TRAY_OPTIONS = ["icon", "tooltip", "menu"];
|
|
6
|
+
function makeTray(container, props) {
|
|
7
|
+
const { app } = container;
|
|
8
|
+
const state = { app, listener: (e) => (e.event === "menu" ? state.onMenu?.(e.item) : state.onClick?.()) };
|
|
9
|
+
updateTrayHandlers(state, props);
|
|
10
|
+
app.on("tray", state.listener);
|
|
11
|
+
app.tray(trayOptions(TRAY_OPTIONS, props));
|
|
12
|
+
// No node of its own: the container root stands in and is never changed through it.
|
|
13
|
+
return { node: container.root, type: "Tray", handlers: new Map(), tray: state };
|
|
14
|
+
}
|
|
15
|
+
function trayOptions(keys, props) {
|
|
16
|
+
// shortcut: a prop that becomes undefined keeps its last value (the protocol cannot clear tray fields).
|
|
17
|
+
return Object.fromEntries(keys.filter((key) => props[key] !== undefined).map((key) => [key, props[key]]));
|
|
18
|
+
}
|
|
19
|
+
function updateTrayHandlers(state, props) {
|
|
20
|
+
state.onClick = props.onClick;
|
|
21
|
+
state.onMenu = props.onMenu;
|
|
22
|
+
}
|
|
23
|
+
function updateTray(instance, prevProps, nextProps) {
|
|
24
|
+
const state = instance.tray;
|
|
25
|
+
updateTrayHandlers(state, nextProps);
|
|
26
|
+
// By value: a `menu` written inline is a new array on every render.
|
|
27
|
+
const changed = TRAY_OPTIONS.filter((key) => JSON.stringify(prevProps[key]) !== JSON.stringify(nextProps[key]));
|
|
28
|
+
const options = trayOptions(changed, nextProps);
|
|
29
|
+
if (Object.keys(options).length)
|
|
30
|
+
state.app.tray(options);
|
|
31
|
+
}
|
|
32
|
+
/** `Window` and `Tray` are not nodes of their parent. */
|
|
33
|
+
const detached = (instance) => Boolean(instance.window || instance.tray);
|
|
34
|
+
const WINDOW_OPTIONS = ["title", "width", "height", "x", "y", "minWidth", "minHeight", "minimized", "frameless"];
|
|
35
|
+
/** React handler prop per window event (PROTOCOL.md section 3). */
|
|
36
|
+
const WINDOW_EVENT_PROPS = {
|
|
37
|
+
onFocus: "focus",
|
|
38
|
+
onBlur: "blur",
|
|
39
|
+
onResize: "resize",
|
|
40
|
+
onMove: "move",
|
|
41
|
+
onVisibility: "visibility",
|
|
42
|
+
};
|
|
43
|
+
function windowOptions(props) {
|
|
44
|
+
return Object.fromEntries(WINDOW_OPTIONS.filter((key) => props[key] !== undefined).map((key) => [key, props[key]]));
|
|
45
|
+
}
|
|
46
|
+
/** Subscribes or unsubscribes according to the handlers present in `props`. */
|
|
47
|
+
function syncWindowHandlers(state, props) {
|
|
48
|
+
for (const [prop, event] of Object.entries(WINDOW_EVENT_PROPS)) {
|
|
49
|
+
const handler = props[prop];
|
|
50
|
+
if (typeof handler === "function") {
|
|
51
|
+
state.handlers.set(event, handler);
|
|
52
|
+
if (!state.listeners.has(event)) {
|
|
53
|
+
const listener = (payload) => state.handlers.get(event)?.(payload);
|
|
54
|
+
state.listeners.set(event, listener);
|
|
55
|
+
state.handle.on(event, listener);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
state.handlers.delete(event);
|
|
60
|
+
const listener = state.listeners.get(event);
|
|
61
|
+
if (listener) {
|
|
62
|
+
state.handle.off(event, listener);
|
|
63
|
+
state.listeners.delete(event);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function makeWindow(app, props) {
|
|
69
|
+
const root = app.div({ class: props.class ?? "size-full" });
|
|
70
|
+
const state = {
|
|
71
|
+
app,
|
|
72
|
+
handle: app.createWindow({ ...windowOptions(props), root }),
|
|
73
|
+
handlers: new Map(),
|
|
74
|
+
listeners: new Map(),
|
|
75
|
+
closed: false,
|
|
76
|
+
};
|
|
77
|
+
state.onClose = props.onClose;
|
|
78
|
+
state.handle.on("closed", () => {
|
|
79
|
+
state.closed = true;
|
|
80
|
+
state.onClose?.();
|
|
81
|
+
});
|
|
82
|
+
syncWindowHandlers(state, props);
|
|
83
|
+
return { node: root, type: "Window", handlers: new Map(), window: state };
|
|
84
|
+
}
|
|
85
|
+
function updateWindow(instance, prevProps, nextProps) {
|
|
86
|
+
const state = instance.window;
|
|
87
|
+
state.onClose = nextProps.onClose;
|
|
88
|
+
const changes = Object.fromEntries(WINDOW_OPTIONS.filter((key) => !Object.is(prevProps[key], nextProps[key])).map((key) => [key, nextProps[key]]));
|
|
89
|
+
if (Object.keys(changes).length)
|
|
90
|
+
state.handle.set(changes);
|
|
91
|
+
syncWindowHandlers(state, nextProps);
|
|
92
|
+
if (prevProps.class !== nextProps.class)
|
|
93
|
+
instance.node.set({ class: nextProps.class ?? "size-full" });
|
|
94
|
+
}
|
|
95
|
+
/** A `Window` child is not a node of its parent: it has its own window. */
|
|
96
|
+
function append(parent, child) {
|
|
97
|
+
if (!detached(child))
|
|
98
|
+
parent.append(child.node);
|
|
99
|
+
}
|
|
100
|
+
function insert(parent, child, before) {
|
|
101
|
+
if (detached(child))
|
|
102
|
+
return;
|
|
103
|
+
// shortcut: before a `Window` sibling (which has no node here) the child goes last; track siblings if order matters.
|
|
104
|
+
if (detached(before))
|
|
105
|
+
parent.append(child.node);
|
|
106
|
+
else
|
|
107
|
+
parent.insertBefore(child.node, before.node);
|
|
108
|
+
}
|
|
109
|
+
const EVENT_PROPS = {
|
|
110
|
+
onClick: "click",
|
|
111
|
+
onDoubleClick: "doubleClick",
|
|
112
|
+
onChange: "change",
|
|
113
|
+
onSubmit: "submit",
|
|
114
|
+
onMenu: "menu",
|
|
115
|
+
onKeyDown: "keyDown",
|
|
116
|
+
onDrop: "drop",
|
|
117
|
+
onOk: "ok",
|
|
118
|
+
onCancel: "cancel",
|
|
119
|
+
onClose: "close",
|
|
120
|
+
onSort: "sort",
|
|
121
|
+
onExpand: "expand",
|
|
122
|
+
onCollapse: "collapse",
|
|
123
|
+
onPointerDown: "pointerDown",
|
|
124
|
+
onConfirm: "confirm",
|
|
125
|
+
onCopied: "copied",
|
|
126
|
+
onRemove: "remove",
|
|
127
|
+
onRetry: "retry",
|
|
128
|
+
onQuery: "query",
|
|
129
|
+
onLoadMore: "loadMore",
|
|
130
|
+
};
|
|
131
|
+
function isTextChildren(children) {
|
|
132
|
+
const items = Array.isArray(children) ? children : [children];
|
|
133
|
+
return items.every((item) => typeof item === "string" || typeof item === "number");
|
|
134
|
+
}
|
|
135
|
+
function textOf(children) {
|
|
136
|
+
return (Array.isArray(children) ? children : [children]).join("");
|
|
137
|
+
}
|
|
138
|
+
/** Splits React props into wire props and event handlers. */
|
|
139
|
+
function split(type, props) {
|
|
140
|
+
const wire = {};
|
|
141
|
+
const handlers = new Map();
|
|
142
|
+
for (const [key, value] of Object.entries(props)) {
|
|
143
|
+
if (key === "children")
|
|
144
|
+
continue;
|
|
145
|
+
if (key in EVENT_PROPS) {
|
|
146
|
+
if (typeof value === "function")
|
|
147
|
+
handlers.set(EVENT_PROPS[key], value);
|
|
148
|
+
}
|
|
149
|
+
else if (value !== undefined) {
|
|
150
|
+
wire[key] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (props.children !== undefined && isTextChildren(props.children)) {
|
|
154
|
+
if (type === "text")
|
|
155
|
+
wire.content = textOf(props.children);
|
|
156
|
+
if (type === "Button" && wire.label === undefined)
|
|
157
|
+
wire.label = textOf(props.children);
|
|
158
|
+
}
|
|
159
|
+
// Like React DOM: providing the value makes the component controlled.
|
|
160
|
+
if (type === "Input" || type === "RadioGroup" || type === "Select" || type === "Slider" || type === "Tabs")
|
|
161
|
+
wire.controlled = props.value !== undefined;
|
|
162
|
+
if (type === "Rating" || type === "Stepper" || type === "Calendar")
|
|
163
|
+
wire.controlled = props.value !== undefined;
|
|
164
|
+
if (type === "Pagination")
|
|
165
|
+
wire.controlled = props.page !== undefined;
|
|
166
|
+
if (type === "NumberInput" || type === "OtpInput" || type === "InputGroup")
|
|
167
|
+
wire.controlled = props.value !== undefined;
|
|
168
|
+
if (type === "Table")
|
|
169
|
+
wire.controlled = props.selected !== undefined;
|
|
170
|
+
if (type === "Tree" || type === "Carousel" || type === "Sidebar" || type === "List")
|
|
171
|
+
wire.controlled = props.value !== undefined;
|
|
172
|
+
if (type === "Checkbox" || type === "Switch" || type === "Toggle")
|
|
173
|
+
wire.controlled = props.checked !== undefined;
|
|
174
|
+
if (type === "DatePicker" || type === "TimeField" || type === "ColorPicker" || type === "Combobox")
|
|
175
|
+
wire.controlled = props.value !== undefined;
|
|
176
|
+
return { wire, handlers };
|
|
177
|
+
}
|
|
178
|
+
/** Host event payloads become plain handler arguments. */
|
|
179
|
+
function subscribe(instance, event) {
|
|
180
|
+
const node = instance.node;
|
|
181
|
+
node.on(event, (payload) => {
|
|
182
|
+
const handler = instance.handlers.get(event);
|
|
183
|
+
if (!handler)
|
|
184
|
+
return;
|
|
185
|
+
if (event === "click" || event === "ok" || event === "cancel" || event === "close" || event === "loadMore")
|
|
186
|
+
handler();
|
|
187
|
+
// A Table's double click carries the row.
|
|
188
|
+
else if (event === "doubleClick")
|
|
189
|
+
instance.type === "Table" ? handler(payload.value) : handler();
|
|
190
|
+
else if (event === "sort")
|
|
191
|
+
handler(payload);
|
|
192
|
+
else if (event === "menu")
|
|
193
|
+
handler(payload.item);
|
|
194
|
+
else if (event === "keyDown")
|
|
195
|
+
handler(payload.key);
|
|
196
|
+
else if (event === "drop")
|
|
197
|
+
handler(payload.paths);
|
|
198
|
+
else if (instance.type === "Checkbox" || instance.type === "Switch" || instance.type === "Toggle")
|
|
199
|
+
handler(payload.checked);
|
|
200
|
+
else if (instance.type === "Popover" || instance.type === "HoverCard")
|
|
201
|
+
handler(payload.checked);
|
|
202
|
+
else
|
|
203
|
+
handler(payload.value);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function createNode(app, type, wire) {
|
|
207
|
+
switch (type) {
|
|
208
|
+
case "div":
|
|
209
|
+
return app.div(wire);
|
|
210
|
+
case "text":
|
|
211
|
+
return app.text(wire);
|
|
212
|
+
case "Button":
|
|
213
|
+
return app.button(wire);
|
|
214
|
+
case "Input":
|
|
215
|
+
return app.input(wire);
|
|
216
|
+
case "Checkbox":
|
|
217
|
+
return app.checkbox(wire);
|
|
218
|
+
case "Icon":
|
|
219
|
+
return app.icon(wire);
|
|
220
|
+
case "Image":
|
|
221
|
+
return app.image(wire);
|
|
222
|
+
case "MenuBar":
|
|
223
|
+
return app.menuBar(wire);
|
|
224
|
+
case "Switch":
|
|
225
|
+
return app.switch(wire);
|
|
226
|
+
case "RadioGroup":
|
|
227
|
+
return app.radioGroup(wire);
|
|
228
|
+
case "Select":
|
|
229
|
+
return app.select(wire);
|
|
230
|
+
case "Slider":
|
|
231
|
+
return app.slider(wire);
|
|
232
|
+
case "Textarea":
|
|
233
|
+
return app.textarea(wire);
|
|
234
|
+
case "Dialog":
|
|
235
|
+
return app.dialog(wire);
|
|
236
|
+
case "VirtualList":
|
|
237
|
+
return app.virtualList(wire);
|
|
238
|
+
case "Table":
|
|
239
|
+
return app.table(wire);
|
|
240
|
+
case "Tabs":
|
|
241
|
+
return app.tabs(wire);
|
|
242
|
+
case "Tab":
|
|
243
|
+
return app.tab(wire);
|
|
244
|
+
case "Alert":
|
|
245
|
+
return app.alert(wire);
|
|
246
|
+
case "Badge":
|
|
247
|
+
return app.badge(wire);
|
|
248
|
+
case "Tag":
|
|
249
|
+
return app.tag(wire);
|
|
250
|
+
case "Kbd":
|
|
251
|
+
return app.kbd(wire);
|
|
252
|
+
case "Progress":
|
|
253
|
+
return app.progress(wire);
|
|
254
|
+
case "Spinner":
|
|
255
|
+
return app.spinner(wire);
|
|
256
|
+
case "Skeleton":
|
|
257
|
+
return app.skeleton(wire);
|
|
258
|
+
case "Rating":
|
|
259
|
+
return app.rating(wire);
|
|
260
|
+
case "Pagination":
|
|
261
|
+
return app.pagination(wire);
|
|
262
|
+
case "Collapsible":
|
|
263
|
+
return app.collapsible(wire);
|
|
264
|
+
case "Accordion":
|
|
265
|
+
return app.accordion(wire);
|
|
266
|
+
case "Avatar":
|
|
267
|
+
return app.avatar(wire);
|
|
268
|
+
case "Stepper":
|
|
269
|
+
return app.stepper(wire);
|
|
270
|
+
case "NumberInput":
|
|
271
|
+
return app.numberInput(wire);
|
|
272
|
+
case "OtpInput":
|
|
273
|
+
return app.otpInput(wire);
|
|
274
|
+
case "Form":
|
|
275
|
+
return app.form(wire);
|
|
276
|
+
case "Field":
|
|
277
|
+
return app.field(wire);
|
|
278
|
+
case "GroupBox":
|
|
279
|
+
return app.groupBox(wire);
|
|
280
|
+
case "DescriptionList":
|
|
281
|
+
return app.descriptionList(wire);
|
|
282
|
+
case "StatusBar":
|
|
283
|
+
return app.statusBar(wire);
|
|
284
|
+
case "Toolbar":
|
|
285
|
+
return app.toolbar(wire);
|
|
286
|
+
case "Resizable":
|
|
287
|
+
return app.resizable(wire);
|
|
288
|
+
case "Calendar":
|
|
289
|
+
return app.calendar(wire);
|
|
290
|
+
case "Tree":
|
|
291
|
+
return app.tree(wire);
|
|
292
|
+
case "Diff":
|
|
293
|
+
return app.diff(wire);
|
|
294
|
+
case "Carousel":
|
|
295
|
+
return app.carousel(wire);
|
|
296
|
+
case "Sidebar":
|
|
297
|
+
return app.sidebar(wire);
|
|
298
|
+
case "Chart":
|
|
299
|
+
return app.chart(wire);
|
|
300
|
+
case "Canvas":
|
|
301
|
+
return app.canvas(wire);
|
|
302
|
+
case "Command":
|
|
303
|
+
return app.command(wire);
|
|
304
|
+
case "Settings":
|
|
305
|
+
return app.settings(wire);
|
|
306
|
+
case "Toggle":
|
|
307
|
+
return app.toggle(wire);
|
|
308
|
+
case "DropdownButton":
|
|
309
|
+
return app.dropdownButton(wire);
|
|
310
|
+
case "Label":
|
|
311
|
+
return app.label(wire);
|
|
312
|
+
case "Empty":
|
|
313
|
+
return app.empty(wire);
|
|
314
|
+
case "Shimmer":
|
|
315
|
+
return app.shimmer(wire);
|
|
316
|
+
case "InputGroup":
|
|
317
|
+
return app.inputGroup(wire);
|
|
318
|
+
case "Clipboard":
|
|
319
|
+
return app.clipboard(wire);
|
|
320
|
+
case "AlertDialog":
|
|
321
|
+
return app.alertDialog(wire);
|
|
322
|
+
case "Sheet":
|
|
323
|
+
return app.sheet(wire);
|
|
324
|
+
case "Popover":
|
|
325
|
+
return app.popover(wire);
|
|
326
|
+
case "HoverCard":
|
|
327
|
+
return app.hoverCard(wire);
|
|
328
|
+
case "DatePicker":
|
|
329
|
+
return app.datePicker(wire);
|
|
330
|
+
case "TimeField":
|
|
331
|
+
return app.timeField(wire);
|
|
332
|
+
case "ColorPicker":
|
|
333
|
+
return app.colorPicker(wire);
|
|
334
|
+
case "Combobox":
|
|
335
|
+
return app.combobox(wire);
|
|
336
|
+
case "Bubble":
|
|
337
|
+
return app.bubble(wire);
|
|
338
|
+
case "Message":
|
|
339
|
+
return app.message(wire);
|
|
340
|
+
case "MessageHeader":
|
|
341
|
+
return app.messageHeader(wire);
|
|
342
|
+
case "MessageFooter":
|
|
343
|
+
return app.messageFooter(wire);
|
|
344
|
+
case "Marker":
|
|
345
|
+
return app.marker(wire);
|
|
346
|
+
case "Attachment":
|
|
347
|
+
return app.attachment(wire);
|
|
348
|
+
case "MessageScroller":
|
|
349
|
+
return app.messageScroller(wire);
|
|
350
|
+
case "Questionnaire":
|
|
351
|
+
return app.questionnaire(wire);
|
|
352
|
+
case "List":
|
|
353
|
+
return app.list(wire);
|
|
354
|
+
case "ListItem":
|
|
355
|
+
return app.listItem(wire);
|
|
356
|
+
case "Dock":
|
|
357
|
+
return app.dock(wire);
|
|
358
|
+
case "DockPanel":
|
|
359
|
+
return app.dockPanel(wire);
|
|
360
|
+
case "AccordionItem":
|
|
361
|
+
return app.accordionItem(wire);
|
|
362
|
+
case "DialogFooter":
|
|
363
|
+
return app.dialogFooter(wire);
|
|
364
|
+
case "StatusBarLeft":
|
|
365
|
+
return app.statusBarLeft(wire);
|
|
366
|
+
case "StatusBarRight":
|
|
367
|
+
return app.statusBarRight(wire);
|
|
368
|
+
default:
|
|
369
|
+
throw new Error(`gpui-bridge-react: unknown element type '${type}'`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function makeInstance(container, type, props) {
|
|
373
|
+
if (type === "Window")
|
|
374
|
+
return makeWindow(container.app, props);
|
|
375
|
+
if (type === "Tray")
|
|
376
|
+
return makeTray(container, props);
|
|
377
|
+
const { wire, handlers } = split(type, props);
|
|
378
|
+
const instance = { node: createNode(container.app, type, wire), type, handlers };
|
|
379
|
+
for (const event of handlers.keys())
|
|
380
|
+
subscribe(instance, event);
|
|
381
|
+
return instance;
|
|
382
|
+
}
|
|
383
|
+
let currentUpdatePriority = NoEventPriority;
|
|
384
|
+
export const reconciler = createReconciler({
|
|
385
|
+
rendererPackageName: "gpui-bridge-react",
|
|
386
|
+
rendererVersion: "0.1.0",
|
|
387
|
+
extraDevToolsConfig: null,
|
|
388
|
+
supportsMutation: true,
|
|
389
|
+
supportsPersistence: false,
|
|
390
|
+
supportsHydration: false,
|
|
391
|
+
isPrimaryRenderer: true,
|
|
392
|
+
createInstance: (type, props, container) => makeInstance(container, type, props),
|
|
393
|
+
createTextInstance: (text, container) => makeInstance(container, "text", { children: text }),
|
|
394
|
+
shouldSetTextContent: (type, props) => (type === "text" || type === "Button") && props.children !== undefined && isTextChildren(props.children),
|
|
395
|
+
finalizeInitialChildren: () => false,
|
|
396
|
+
appendInitialChild: (parent, child) => append(parent.node, child),
|
|
397
|
+
appendChild: (parent, child) => append(parent.node, child),
|
|
398
|
+
appendChildToContainer: (container, child) => append(container.root, child),
|
|
399
|
+
insertBefore: (parent, child, before) => insert(parent.node, child, before),
|
|
400
|
+
insertInContainerBefore: (container, child, before) => insert(container.root, child, before),
|
|
401
|
+
// A removed `Window` (or one inside a removed subtree) is closed in `detachDeletedInstance`.
|
|
402
|
+
removeChild: (_parent, child) => void (detached(child) || child.node.remove()),
|
|
403
|
+
removeChildFromContainer: (_container, child) => void (detached(child) || child.node.remove()),
|
|
404
|
+
// The root starts empty and is only populated by React.
|
|
405
|
+
clearContainer: () => { },
|
|
406
|
+
commitUpdate(instance, type, prevProps, nextProps) {
|
|
407
|
+
if (instance.window)
|
|
408
|
+
return updateWindow(instance, prevProps, nextProps);
|
|
409
|
+
if (instance.tray)
|
|
410
|
+
return updateTray(instance, prevProps, nextProps);
|
|
411
|
+
const prev = split(type, prevProps);
|
|
412
|
+
const next = split(type, nextProps);
|
|
413
|
+
const changes = {};
|
|
414
|
+
for (const key of new Set([...Object.keys(prev.wire), ...Object.keys(next.wire)])) {
|
|
415
|
+
if (!(key in next.wire))
|
|
416
|
+
changes[key] = null;
|
|
417
|
+
else if (!Object.is(prev.wire[key], next.wire[key]))
|
|
418
|
+
changes[key] = next.wire[key];
|
|
419
|
+
}
|
|
420
|
+
if (Object.keys(changes).length)
|
|
421
|
+
instance.node.set(changes);
|
|
422
|
+
for (const event of prev.handlers.keys()) {
|
|
423
|
+
if (!next.handlers.has(event))
|
|
424
|
+
instance.node.off(event);
|
|
425
|
+
}
|
|
426
|
+
for (const event of next.handlers.keys()) {
|
|
427
|
+
if (!prev.handlers.has(event))
|
|
428
|
+
subscribe(instance, event);
|
|
429
|
+
}
|
|
430
|
+
instance.handlers = next.handlers;
|
|
431
|
+
},
|
|
432
|
+
commitTextUpdate: (instance, _old, text) => void instance.node.set({ content: text }),
|
|
433
|
+
resetTextContent: (instance) => void instance.node.set({ content: null, label: null }),
|
|
434
|
+
// One host render per React commit.
|
|
435
|
+
resetAfterCommit: (container) => container.app.commit(),
|
|
436
|
+
prepareForCommit: () => null,
|
|
437
|
+
getRootHostContext: () => ({}),
|
|
438
|
+
getChildHostContext: (parent) => parent,
|
|
439
|
+
getPublicInstance: (instance) => instance.node,
|
|
440
|
+
preparePortalMount: () => { },
|
|
441
|
+
scheduleTimeout: setTimeout,
|
|
442
|
+
cancelTimeout: clearTimeout,
|
|
443
|
+
noTimeout: -1,
|
|
444
|
+
supportsMicrotasks: true,
|
|
445
|
+
scheduleMicrotask: queueMicrotask,
|
|
446
|
+
setCurrentUpdatePriority: (priority) => void (currentUpdatePriority = priority),
|
|
447
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
448
|
+
resolveUpdatePriority: () => currentUpdatePriority || DefaultEventPriority,
|
|
449
|
+
// Features this renderer does not use (transitions, forms, suspense commits, scopes).
|
|
450
|
+
NotPendingTransition: null,
|
|
451
|
+
HostTransitionContext: createContext(null),
|
|
452
|
+
resetFormInstance: () => { },
|
|
453
|
+
requestPostPaintCallback: () => { },
|
|
454
|
+
shouldAttemptEagerTransition: () => false,
|
|
455
|
+
trackSchedulerEvent: () => { },
|
|
456
|
+
resolveEventType: () => null,
|
|
457
|
+
resolveEventTimeStamp: () => -1.1,
|
|
458
|
+
maySuspendCommit: () => false,
|
|
459
|
+
maySuspendCommitOnUpdate: () => false,
|
|
460
|
+
maySuspendCommitInSyncRender: () => false,
|
|
461
|
+
preloadInstance: () => true,
|
|
462
|
+
startSuspendingCommit: () => null,
|
|
463
|
+
suspendInstance: () => { },
|
|
464
|
+
suspendOnActiveViewTransition: () => { },
|
|
465
|
+
waitForCommitToBeReady: () => null,
|
|
466
|
+
getSuspendedCommitReason: () => null,
|
|
467
|
+
getInstanceFromNode: () => null,
|
|
468
|
+
beforeActiveInstanceBlur: () => { },
|
|
469
|
+
afterActiveInstanceBlur: () => { },
|
|
470
|
+
prepareScopeUpdate: () => { },
|
|
471
|
+
getInstanceFromScope: () => null,
|
|
472
|
+
// Called for every deleted host instance, after the commit: a deleted `Window` or `Tray` goes away with a commit of its own.
|
|
473
|
+
detachDeletedInstance(instance) {
|
|
474
|
+
if (instance.tray) {
|
|
475
|
+
if (instance.tray.removed)
|
|
476
|
+
return;
|
|
477
|
+
instance.tray.removed = true;
|
|
478
|
+
instance.tray.app.off("tray", instance.tray.listener);
|
|
479
|
+
instance.tray.app.removeTray().commit();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const state = instance.window;
|
|
483
|
+
if (!state || state.closed)
|
|
484
|
+
return;
|
|
485
|
+
state.handle.close();
|
|
486
|
+
instance.node.remove();
|
|
487
|
+
state.closed = true;
|
|
488
|
+
state.app.commit();
|
|
489
|
+
},
|
|
490
|
+
bindToConsole: (method, args) => console[method].bind(console, ...args),
|
|
491
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type App, type AppOptions, type WindowOptions } from "gpui-bridge";
|
|
3
|
+
export * from "./components.js";
|
|
4
|
+
export interface RenderOptions extends AppOptions {
|
|
5
|
+
window?: Omit<WindowOptions, "root">;
|
|
6
|
+
/** Classes of the window root that holds the React tree. */
|
|
7
|
+
rootClass?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface Root {
|
|
10
|
+
app: App;
|
|
11
|
+
unmount(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Starts the host and renders `element` into its window. */
|
|
14
|
+
export declare function render(element: ReactNode, options?: RenderOptions): Promise<Root>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// React renderer for gpui-bridge: render a React tree into a native GPUI window.
|
|
2
|
+
import { ConcurrentRoot } from "react-reconciler/constants.js";
|
|
3
|
+
import { createApp } from "gpui-bridge";
|
|
4
|
+
import { reconciler } from "./host-config.js";
|
|
5
|
+
export * from "./components.js";
|
|
6
|
+
/** Starts the host and renders `element` into its window. */
|
|
7
|
+
export async function render(element, options = {}) {
|
|
8
|
+
const app = await createApp(options);
|
|
9
|
+
const root = app.div({ class: options.rootClass ?? "size-full" });
|
|
10
|
+
// Sent with React's first commit.
|
|
11
|
+
app.window({ ...options.window, root });
|
|
12
|
+
const report = (error) => console.error(error);
|
|
13
|
+
const fiberRoot = reconciler.createContainer({ app, root }, ConcurrentRoot, null, false, null, "", report, report, report, () => { }, null);
|
|
14
|
+
reconciler.updateContainer(element, fiberRoot, null, null);
|
|
15
|
+
return { app, unmount: () => reconciler.updateContainer(null, fiberRoot, null, null) };
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gpui-bridge-react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "React renderer for gpui-bridge native UIs",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "gpui-bridge contributors",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"gpui",
|
|
9
|
+
"gpui-component",
|
|
10
|
+
"native",
|
|
11
|
+
"desktop",
|
|
12
|
+
"ui",
|
|
13
|
+
"react",
|
|
14
|
+
"renderer"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/InDIOS/gpui-bridge#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/InDIOS/gpui-bridge.git",
|
|
20
|
+
"directory": "packages/client-react"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/InDIOS/gpui-bridge/issues"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "dist/src/index.js",
|
|
27
|
+
"types": "dist/src/index.d.ts",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.6"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"gpui-bridge": "^0.3.0",
|
|
33
|
+
"react": "^19.3.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"react-reconciler": "0.34.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"gpui-bridge": "0.3.0",
|
|
40
|
+
"@types/node": "^26.6.5",
|
|
41
|
+
"@types/react": "19.3.0",
|
|
42
|
+
"@types/react-reconciler": "^0.34.0",
|
|
43
|
+
"react": "19.3.0",
|
|
44
|
+
"typescript": "7.0.2"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/src"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc",
|
|
51
|
+
"test": "tsc && node --test dist/test/*.test.js"
|
|
52
|
+
}
|
|
6
53
|
}
|