@quickgui/solid 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/compiler.ts +60 -0
- package/index.ts +1214 -0
- package/jsx-runtime.ts +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @quickgui/solid
|
|
2
|
+
|
|
3
|
+
Solid 2 renderer for QuickGUI. It exports unstyled `View`, `Text`, `Button`, `Input`, `TextArea`,
|
|
4
|
+
retained core `Markdown`, variable-height `VirtualList`, compound in-window `Popover` and
|
|
5
|
+
native-window `SystemPopover` parts, and `createRenderer`.
|
|
6
|
+
Import native application/window APIs from `@quickgui/native` and reactive primitives from
|
|
7
|
+
`solid-js` itself.
|
|
8
|
+
|
|
9
|
+
Run applications through the QuickGUI CLI:
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
bun run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { app, Window } from "@quickgui/native";
|
|
17
|
+
import { Button, createRenderer } from "@quickgui/solid";
|
|
18
|
+
import { createSignal } from "solid-js";
|
|
19
|
+
|
|
20
|
+
function Counter() {
|
|
21
|
+
const [count, setCount] = createSignal(0);
|
|
22
|
+
return <Button onClick={() => setCount(count() + 1)}>Count: {count()}</Button>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await app.whenReady();
|
|
26
|
+
new Window({
|
|
27
|
+
title: "QuickGUI",
|
|
28
|
+
renderer: createRenderer(() => <Counter />),
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The CLI owns the native application loop; application source does not call `app.run()`.
|
|
33
|
+
|
|
34
|
+
See the [Solid renderer guide](../../docs/solid.md) and the
|
|
35
|
+
[runnable example](../../examples/solid/app.tsx). The
|
|
36
|
+
[AI chat example](../../examples/ai-chat-solid/app.tsx) demonstrates controlled input and live
|
|
37
|
+
streaming Markdown over a tail-following virtual transcript with the Vercel AI SDK and DeepSeek.
|
|
38
|
+
The [alert-dialog example](../../examples/alert-dialog-solid/app.tsx) demonstrates information,
|
|
39
|
+
warning, and critical native alerts with optional window ownership and semantic button roles.
|
|
40
|
+
The [file-dialog example](../../examples/file-dialog-solid/app.tsx) demonstrates native open-file,
|
|
41
|
+
open-folder, and save-destination panels with explicit cancellation results.
|
|
42
|
+
The [popover example](../../examples/popover-solid/app.tsx) compares the shared
|
|
43
|
+
`Root`/`Trigger`/`Content` JSX API of `SystemPopover` and the retained in-window `Popover`.
|
|
44
|
+
The [system API example](../../examples/system-api-solid/app.tsx) demonstrates core-owned shell,
|
|
45
|
+
app environment, clipboard, display, notification, menu, tray, shortcut, single-instance,
|
|
46
|
+
deep-link, secure-storage, autostart, permission, preference, desktop, power, window, and updater
|
|
47
|
+
services from `@quickgui/native` alongside the Solid renderer.
|
package/compiler.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { transform } from "@solidjs/compiler";
|
|
2
|
+
import type { BunPlugin } from "bun";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
export interface QuickGuiSolidPluginOptions {
|
|
7
|
+
development?: boolean;
|
|
8
|
+
projectRoot?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function quickguiSolidPlugin(options: QuickGuiSolidPluginOptions = {}): BunPlugin {
|
|
12
|
+
const development = options.development ?? process.env.NODE_ENV !== "production";
|
|
13
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
14
|
+
const solidPackage = resolveSolidPackage(projectRoot);
|
|
15
|
+
const solidRuntime = join(dirname(solidPackage), "dist", development ? "dev.js" : "solid.js");
|
|
16
|
+
const solidRuntimeUrl = pathToFileURL(solidRuntime).href;
|
|
17
|
+
return {
|
|
18
|
+
name: "quickgui-solid",
|
|
19
|
+
setup(build) {
|
|
20
|
+
// Bun's default `node` condition selects Solid's server build. QuickGUI is a persistent UI
|
|
21
|
+
// runtime, so signal setters must always resolve to the client/reactive implementation even
|
|
22
|
+
// when a development app imports its source dynamically from outside the compiled host.
|
|
23
|
+
if (build.config) {
|
|
24
|
+
build.onResolve({ filter: /^solid-js$/ }, () => ({ path: solidRuntime }));
|
|
25
|
+
} else {
|
|
26
|
+
// Runtime plugins cannot intercept a bare package without `.` or `:` via `onResolve`.
|
|
27
|
+
// Registering the exact module specifier also covers @solidjs/universal's JS import.
|
|
28
|
+
build.module("solid-js", async () => ({
|
|
29
|
+
exports: await import(solidRuntimeUrl),
|
|
30
|
+
loader: "object",
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
build.onLoad({ filter: /\.[jt]sx$/ }, async ({ path }) => {
|
|
34
|
+
const source = await Bun.file(path).text();
|
|
35
|
+
const result = transform(source, {
|
|
36
|
+
filename: path,
|
|
37
|
+
moduleName: "@quickgui/solid",
|
|
38
|
+
generate: "universal",
|
|
39
|
+
hydratable: false,
|
|
40
|
+
sourceMap: true,
|
|
41
|
+
dev: development,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
contents: result.code,
|
|
45
|
+
// The Solid transform lowers JSX but intentionally preserves TypeScript syntax such as
|
|
46
|
+
// interfaces and type-only imports. Let Bun perform the final TypeScript erasure.
|
|
47
|
+
loader: "ts",
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveSolidPackage(projectRoot: string): string {
|
|
55
|
+
try {
|
|
56
|
+
return Bun.resolveSync("solid-js/package.json", projectRoot);
|
|
57
|
+
} catch {
|
|
58
|
+
return Bun.resolveSync("solid-js/package.json", dirname(fileURLToPath(import.meta.url)));
|
|
59
|
+
}
|
|
60
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
import { createRenderer as createUniversalRenderer } from "@solidjs/universal";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
createSignal,
|
|
5
|
+
flush as flushSolid,
|
|
6
|
+
getOwner,
|
|
7
|
+
omit,
|
|
8
|
+
onCleanup,
|
|
9
|
+
runWithOwner,
|
|
10
|
+
Show,
|
|
11
|
+
type Element as SolidElement,
|
|
12
|
+
useContext,
|
|
13
|
+
} from "solid-js";
|
|
14
|
+
import {
|
|
15
|
+
type NativeElementName,
|
|
16
|
+
type NativeEventListener,
|
|
17
|
+
type PopoverPlacement,
|
|
18
|
+
NativeNode,
|
|
19
|
+
PropertyCode,
|
|
20
|
+
QuickGuiEvent,
|
|
21
|
+
Window,
|
|
22
|
+
cleanupNativeNodes,
|
|
23
|
+
createNativeElement,
|
|
24
|
+
createNativeSentinel,
|
|
25
|
+
createNativeText,
|
|
26
|
+
getNativeFirstChild,
|
|
27
|
+
getNativeNextSibling,
|
|
28
|
+
getNativeParent,
|
|
29
|
+
insertNativeNode,
|
|
30
|
+
isNativeText,
|
|
31
|
+
parseColor,
|
|
32
|
+
removeNativeNode,
|
|
33
|
+
replaceNativeText,
|
|
34
|
+
setNativeEventListener,
|
|
35
|
+
setNativeProperty,
|
|
36
|
+
type WindowRenderer,
|
|
37
|
+
} from "@quickgui/native";
|
|
38
|
+
|
|
39
|
+
type PropertyInput = unknown;
|
|
40
|
+
type PropertyEntry = {
|
|
41
|
+
code: PropertyCode;
|
|
42
|
+
color?: boolean;
|
|
43
|
+
normalize?: (value: PropertyInput) => boolean | number | string | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const properties: Record<string, PropertyEntry> = {
|
|
47
|
+
display: { code: PropertyCode.Display },
|
|
48
|
+
flexDirection: { code: PropertyCode.FlexDirection },
|
|
49
|
+
flexWrap: { code: PropertyCode.FlexWrap },
|
|
50
|
+
flexGrow: { code: PropertyCode.FlexGrow },
|
|
51
|
+
flexShrink: { code: PropertyCode.FlexShrink },
|
|
52
|
+
flexBasis: { code: PropertyCode.FlexBasis },
|
|
53
|
+
alignItems: { code: PropertyCode.AlignItems },
|
|
54
|
+
alignSelf: { code: PropertyCode.AlignSelf },
|
|
55
|
+
justifyContent: { code: PropertyCode.JustifyContent },
|
|
56
|
+
alignContent: { code: PropertyCode.AlignContent },
|
|
57
|
+
gap: { code: PropertyCode.Gap },
|
|
58
|
+
columnGap: { code: PropertyCode.ColumnGap },
|
|
59
|
+
rowGap: { code: PropertyCode.RowGap },
|
|
60
|
+
width: { code: PropertyCode.Width },
|
|
61
|
+
height: { code: PropertyCode.Height },
|
|
62
|
+
minWidth: { code: PropertyCode.MinWidth },
|
|
63
|
+
minHeight: { code: PropertyCode.MinHeight },
|
|
64
|
+
maxWidth: { code: PropertyCode.MaxWidth },
|
|
65
|
+
maxHeight: { code: PropertyCode.MaxHeight },
|
|
66
|
+
padding: { code: PropertyCode.Padding },
|
|
67
|
+
paddingTop: { code: PropertyCode.PaddingTop },
|
|
68
|
+
paddingRight: { code: PropertyCode.PaddingRight },
|
|
69
|
+
paddingBottom: { code: PropertyCode.PaddingBottom },
|
|
70
|
+
paddingLeft: { code: PropertyCode.PaddingLeft },
|
|
71
|
+
margin: { code: PropertyCode.Margin },
|
|
72
|
+
marginTop: { code: PropertyCode.MarginTop },
|
|
73
|
+
marginRight: { code: PropertyCode.MarginRight },
|
|
74
|
+
marginBottom: { code: PropertyCode.MarginBottom },
|
|
75
|
+
marginLeft: { code: PropertyCode.MarginLeft },
|
|
76
|
+
background: { code: PropertyCode.BackgroundColor, color: true },
|
|
77
|
+
backgroundColor: { code: PropertyCode.BackgroundColor, color: true },
|
|
78
|
+
color: { code: PropertyCode.Color, color: true },
|
|
79
|
+
hoverBackgroundColor: {
|
|
80
|
+
code: PropertyCode.HoverBackgroundColor,
|
|
81
|
+
color: true,
|
|
82
|
+
},
|
|
83
|
+
hoverColor: { code: PropertyCode.HoverColor, color: true },
|
|
84
|
+
activeBackgroundColor: {
|
|
85
|
+
code: PropertyCode.ActiveBackgroundColor,
|
|
86
|
+
color: true,
|
|
87
|
+
},
|
|
88
|
+
activeColor: { code: PropertyCode.ActiveColor, color: true },
|
|
89
|
+
transition: {
|
|
90
|
+
code: PropertyCode.Transition,
|
|
91
|
+
normalize: normalizeTransitionShorthand,
|
|
92
|
+
},
|
|
93
|
+
opacity: { code: PropertyCode.Opacity },
|
|
94
|
+
borderWidth: { code: PropertyCode.BorderWidth },
|
|
95
|
+
borderColor: { code: PropertyCode.BorderColor, color: true },
|
|
96
|
+
borderRadius: { code: PropertyCode.BorderRadius },
|
|
97
|
+
fontSize: { code: PropertyCode.FontSize },
|
|
98
|
+
fontFamily: { code: PropertyCode.FontFamily },
|
|
99
|
+
fontWeight: { code: PropertyCode.FontWeight },
|
|
100
|
+
lineHeight: { code: PropertyCode.LineHeight },
|
|
101
|
+
textAlign: { code: PropertyCode.TextAlign },
|
|
102
|
+
whiteSpace: { code: PropertyCode.WhiteSpace },
|
|
103
|
+
textOverflow: { code: PropertyCode.TextOverflow },
|
|
104
|
+
lineClamp: { code: PropertyCode.LineClamp },
|
|
105
|
+
WebkitLineClamp: { code: PropertyCode.LineClamp },
|
|
106
|
+
overflow: { code: PropertyCode.Overflow },
|
|
107
|
+
overflowX: { code: PropertyCode.OverflowX },
|
|
108
|
+
overflowY: { code: PropertyCode.OverflowY },
|
|
109
|
+
cursor: { code: PropertyCode.Cursor },
|
|
110
|
+
appRegion: { code: PropertyCode.AppRegion },
|
|
111
|
+
disabled: { code: PropertyCode.Disabled },
|
|
112
|
+
ariaLabel: { code: PropertyCode.AccessibilityLabel },
|
|
113
|
+
role: { code: PropertyCode.Role },
|
|
114
|
+
tabIndex: { code: PropertyCode.TabIndex },
|
|
115
|
+
focusOnPointer: { code: PropertyCode.FocusOnPointer },
|
|
116
|
+
hitSlop: { code: PropertyCode.HitSlop },
|
|
117
|
+
hitSlopTop: { code: PropertyCode.HitSlopTop },
|
|
118
|
+
hitSlopRight: { code: PropertyCode.HitSlopRight },
|
|
119
|
+
hitSlopBottom: { code: PropertyCode.HitSlopBottom },
|
|
120
|
+
hitSlopLeft: { code: PropertyCode.HitSlopLeft },
|
|
121
|
+
position: { code: PropertyCode.Position },
|
|
122
|
+
top: { code: PropertyCode.Top },
|
|
123
|
+
right: { code: PropertyCode.Right },
|
|
124
|
+
bottom: { code: PropertyCode.Bottom },
|
|
125
|
+
left: { code: PropertyCode.Left },
|
|
126
|
+
userSelect: { code: PropertyCode.UserSelect },
|
|
127
|
+
visibility: { code: PropertyCode.Visibility },
|
|
128
|
+
aspectRatio: { code: PropertyCode.AspectRatio },
|
|
129
|
+
value: { code: PropertyCode.Value },
|
|
130
|
+
content: { code: PropertyCode.Value },
|
|
131
|
+
source: { code: PropertyCode.Value },
|
|
132
|
+
placeholder: { code: PropertyCode.Placeholder },
|
|
133
|
+
multiline: { code: PropertyCode.Multiline },
|
|
134
|
+
streaming: { code: PropertyCode.Streaming },
|
|
135
|
+
markdownCodeBackground: {
|
|
136
|
+
code: PropertyCode.MarkdownCodeBackground,
|
|
137
|
+
color: true,
|
|
138
|
+
},
|
|
139
|
+
markdownBorderColor: { code: PropertyCode.MarkdownBorderColor, color: true },
|
|
140
|
+
markdownMutedColor: { code: PropertyCode.MarkdownMutedColor, color: true },
|
|
141
|
+
markdownLinkColor: { code: PropertyCode.MarkdownLinkColor, color: true },
|
|
142
|
+
markdownCodeTextColor: {
|
|
143
|
+
code: PropertyCode.MarkdownCodeTextColor,
|
|
144
|
+
color: true,
|
|
145
|
+
},
|
|
146
|
+
markdownBlockGap: { code: PropertyCode.MarkdownBlockGap },
|
|
147
|
+
markdownCodeFontSize: { code: PropertyCode.MarkdownCodeFontSize },
|
|
148
|
+
scrollToEndRevision: { code: PropertyCode.ScrollToEndRevision },
|
|
149
|
+
estimatedItemHeight: { code: PropertyCode.EstimatedItemHeight },
|
|
150
|
+
overscan: { code: PropertyCode.Overscan },
|
|
151
|
+
listAlignment: { code: PropertyCode.ListAlignment },
|
|
152
|
+
followMode: { code: PropertyCode.FollowMode },
|
|
153
|
+
anchorPlacement: { code: PropertyCode.AnchorPlacement },
|
|
154
|
+
anchorGap: { code: PropertyCode.AnchorGap },
|
|
155
|
+
viewportMargin: { code: PropertyCode.ViewportMargin },
|
|
156
|
+
dismissOnEscape: { code: PropertyCode.DismissOnEscape },
|
|
157
|
+
dismissOnPointerOutside: { code: PropertyCode.DismissOnPointerOutside },
|
|
158
|
+
overlay: { code: PropertyCode.Overlay },
|
|
159
|
+
focusTrap: { code: PropertyCode.FocusTrap },
|
|
160
|
+
restorePreviousFocus: { code: PropertyCode.RestorePreviousFocus },
|
|
161
|
+
autoFocus: { code: PropertyCode.AutoFocus },
|
|
162
|
+
ariaModal: { code: PropertyCode.AccessibilityModal },
|
|
163
|
+
program: { code: PropertyCode.TerminalProgram },
|
|
164
|
+
command: { code: PropertyCode.TerminalProgram },
|
|
165
|
+
workingDirectory: { code: PropertyCode.TerminalWorkingDirectory },
|
|
166
|
+
cwd: { code: PropertyCode.TerminalWorkingDirectory },
|
|
167
|
+
scrollback: { code: PropertyCode.TerminalScrollback },
|
|
168
|
+
terminalCursorColor: {
|
|
169
|
+
code: PropertyCode.TerminalCursorColor,
|
|
170
|
+
color: true,
|
|
171
|
+
},
|
|
172
|
+
terminalPaddingColor: { code: PropertyCode.TerminalPaddingColor },
|
|
173
|
+
fontThicken: { code: PropertyCode.TerminalFontThicken },
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const colorProperties = new Set([
|
|
177
|
+
PropertyCode.BackgroundColor,
|
|
178
|
+
PropertyCode.Color,
|
|
179
|
+
PropertyCode.HoverBackgroundColor,
|
|
180
|
+
PropertyCode.HoverColor,
|
|
181
|
+
PropertyCode.ActiveBackgroundColor,
|
|
182
|
+
PropertyCode.ActiveColor,
|
|
183
|
+
PropertyCode.BorderColor,
|
|
184
|
+
PropertyCode.MarkdownCodeBackground,
|
|
185
|
+
PropertyCode.MarkdownBorderColor,
|
|
186
|
+
PropertyCode.MarkdownMutedColor,
|
|
187
|
+
PropertyCode.MarkdownLinkColor,
|
|
188
|
+
PropertyCode.MarkdownCodeTextColor,
|
|
189
|
+
PropertyCode.TerminalCursorColor,
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
function setProperty(
|
|
193
|
+
node: NativeNode,
|
|
194
|
+
name: string,
|
|
195
|
+
value: PropertyInput,
|
|
196
|
+
previous?: PropertyInput,
|
|
197
|
+
) {
|
|
198
|
+
if (name === "children" || name === "ref" || name === "key") return;
|
|
199
|
+
if (name === "style") {
|
|
200
|
+
setStyle(node, value, previous);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (name === "anchor") {
|
|
204
|
+
if (value === null || value === undefined || value === false) {
|
|
205
|
+
setNativeProperty(node, PropertyCode.AnchorTarget, null);
|
|
206
|
+
} else if (value instanceof NativeNode) {
|
|
207
|
+
setNativeProperty(node, PropertyCode.AnchorTarget, String(value.id));
|
|
208
|
+
} else {
|
|
209
|
+
throw new TypeError("QuickGUI popover anchor must be a NativeNode");
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const event = eventName(name);
|
|
214
|
+
if (event) {
|
|
215
|
+
setNativeEventListener(
|
|
216
|
+
node,
|
|
217
|
+
event,
|
|
218
|
+
typeof value === "function"
|
|
219
|
+
? (nativeEvent) => {
|
|
220
|
+
try {
|
|
221
|
+
(value as NativeEventListener)(nativeEvent);
|
|
222
|
+
} finally {
|
|
223
|
+
// Solid 2 batches external writes until the host marks the event boundary.
|
|
224
|
+
flushSolid();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
: undefined,
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (name === "class" || name === "className") return;
|
|
232
|
+
if (name === "aria-label") name = "ariaLabel";
|
|
233
|
+
if (name === "aria-modal") name = "ariaModal";
|
|
234
|
+
if (name === "arguments" || name === "args") {
|
|
235
|
+
setNativeProperty(
|
|
236
|
+
node,
|
|
237
|
+
PropertyCode.TerminalArguments,
|
|
238
|
+
value === null || value === undefined
|
|
239
|
+
? null
|
|
240
|
+
: encodeTerminalArguments(value),
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (name === "environment" || name === "env") {
|
|
245
|
+
setNativeProperty(
|
|
246
|
+
node,
|
|
247
|
+
PropertyCode.TerminalEnvironment,
|
|
248
|
+
value === null || value === undefined
|
|
249
|
+
? null
|
|
250
|
+
: encodeTerminalEnvironment(value),
|
|
251
|
+
);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (name === "terminalPalette") {
|
|
255
|
+
setNativeProperty(
|
|
256
|
+
node,
|
|
257
|
+
PropertyCode.TerminalPalette,
|
|
258
|
+
value === null || value === undefined
|
|
259
|
+
? null
|
|
260
|
+
: encodeTerminalPalette(value),
|
|
261
|
+
);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (name === "type") {
|
|
265
|
+
setNativeProperty(node, PropertyCode.Password, value === "password");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (name === "flex") {
|
|
269
|
+
setFlex(node, value);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const entry = properties[name];
|
|
273
|
+
if (!entry) return;
|
|
274
|
+
const normalized = entry.normalize
|
|
275
|
+
? entry.normalize(value)
|
|
276
|
+
: normalizeValue(value, entry.code);
|
|
277
|
+
setNativeProperty(node, entry.code, normalized, { color: !!entry.color });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function setStyle(
|
|
281
|
+
node: NativeNode,
|
|
282
|
+
value: PropertyInput,
|
|
283
|
+
previous: PropertyInput,
|
|
284
|
+
): void {
|
|
285
|
+
const next = isRecord(value) ? value : {};
|
|
286
|
+
const old = isRecord(previous) ? previous : {};
|
|
287
|
+
for (const name of Object.keys(old)) {
|
|
288
|
+
if (!(name in next)) setProperty(node, name, null, old[name]);
|
|
289
|
+
}
|
|
290
|
+
for (const [name, nextValue] of Object.entries(next)) {
|
|
291
|
+
if (!Object.is(nextValue, old[name]))
|
|
292
|
+
setProperty(node, name, nextValue, old[name]);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function setFlex(node: NativeNode, value: PropertyInput): void {
|
|
297
|
+
if (value === null || value === undefined || value === false) {
|
|
298
|
+
for (const code of [
|
|
299
|
+
PropertyCode.FlexGrow,
|
|
300
|
+
PropertyCode.FlexShrink,
|
|
301
|
+
PropertyCode.FlexBasis,
|
|
302
|
+
]) {
|
|
303
|
+
setNativeProperty(node, code, null);
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (typeof value === "number") {
|
|
308
|
+
setNativeProperty(node, PropertyCode.FlexGrow, value);
|
|
309
|
+
setNativeProperty(node, PropertyCode.FlexShrink, 1);
|
|
310
|
+
setNativeProperty(node, PropertyCode.FlexBasis, 0);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const parts = String(value).trim().split(/\s+/);
|
|
314
|
+
if (parts.length === 1 && parts[0] === "none") {
|
|
315
|
+
setNativeProperty(node, PropertyCode.FlexGrow, 0);
|
|
316
|
+
setNativeProperty(node, PropertyCode.FlexShrink, 0);
|
|
317
|
+
setNativeProperty(node, PropertyCode.FlexBasis, "auto");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (parts.length >= 1)
|
|
321
|
+
setNativeProperty(node, PropertyCode.FlexGrow, Number(parts[0]));
|
|
322
|
+
if (parts.length >= 2)
|
|
323
|
+
setNativeProperty(node, PropertyCode.FlexShrink, Number(parts[1]));
|
|
324
|
+
if (parts.length >= 3) {
|
|
325
|
+
setNativeProperty(node, PropertyCode.FlexBasis, normalizeLength(parts[2]));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function normalizeValue(
|
|
330
|
+
value: PropertyInput,
|
|
331
|
+
code: PropertyCode,
|
|
332
|
+
): boolean | number | string | null {
|
|
333
|
+
if (value === null || value === undefined) return null;
|
|
334
|
+
if (value === false) {
|
|
335
|
+
return code === PropertyCode.Disabled ||
|
|
336
|
+
code === PropertyCode.DismissOnEscape ||
|
|
337
|
+
code === PropertyCode.DismissOnPointerOutside ||
|
|
338
|
+
code === PropertyCode.FocusOnPointer
|
|
339
|
+
? false
|
|
340
|
+
: null;
|
|
341
|
+
}
|
|
342
|
+
if (colorProperties.has(code)) return parseColor(value as number | string);
|
|
343
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
344
|
+
if (isLengthProperty(code)) return normalizeLength(String(value));
|
|
345
|
+
return String(value);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function normalizeLength(value: string | undefined): number | string | null {
|
|
349
|
+
if (value === undefined) return null;
|
|
350
|
+
const trimmed = value.trim();
|
|
351
|
+
if (trimmed.endsWith("px")) {
|
|
352
|
+
const number = Number(trimmed.slice(0, -2));
|
|
353
|
+
return Number.isFinite(number) ? number : null;
|
|
354
|
+
}
|
|
355
|
+
if (trimmed === "0") return 0;
|
|
356
|
+
const number = Number(trimmed);
|
|
357
|
+
return Number.isFinite(number) ? number : trimmed;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeTransitionShorthand(value: PropertyInput): number | null {
|
|
361
|
+
if (value === null || value === undefined || value === false) return null;
|
|
362
|
+
if (typeof value !== "string") {
|
|
363
|
+
throw new TypeError("QuickGUI transition must use the CSS transition shorthand");
|
|
364
|
+
}
|
|
365
|
+
const shorthand = value.trim();
|
|
366
|
+
if (shorthand === "" || shorthand === "none") return null;
|
|
367
|
+
|
|
368
|
+
const declarations = splitCssList(shorthand);
|
|
369
|
+
const supported = new Set(["background-color", "border-color", "color"]);
|
|
370
|
+
const declared = new Set<string>();
|
|
371
|
+
let sharedDuration: number | undefined;
|
|
372
|
+
for (const declaration of declarations) {
|
|
373
|
+
const property = declaration
|
|
374
|
+
.split(/\s+/)
|
|
375
|
+
.find((token) => supported.has(token));
|
|
376
|
+
if (!property) {
|
|
377
|
+
throw new TypeError(
|
|
378
|
+
"QuickGUI transition currently supports background-color, border-color, and color",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
declared.add(property);
|
|
382
|
+
const times = Array.from(
|
|
383
|
+
declaration.matchAll(/(?:^|\s)(\d*\.?\d+)(ms|s)(?=\s|$)/g),
|
|
384
|
+
(match) => Number(match[1]) * (match[2] === "s" ? 1_000 : 1),
|
|
385
|
+
);
|
|
386
|
+
const duration = times[0] ?? 0;
|
|
387
|
+
const delay = times[1] ?? 0;
|
|
388
|
+
if (delay !== 0) {
|
|
389
|
+
throw new TypeError("QuickGUI transition does not support a non-zero delay");
|
|
390
|
+
}
|
|
391
|
+
if (sharedDuration !== undefined && sharedDuration !== duration) {
|
|
392
|
+
throw new TypeError(
|
|
393
|
+
"QuickGUI color transition properties must share one duration",
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
sharedDuration = duration;
|
|
397
|
+
}
|
|
398
|
+
if (
|
|
399
|
+
declared.size !== supported.size ||
|
|
400
|
+
Array.from(supported).some((property) => !declared.has(property))
|
|
401
|
+
) {
|
|
402
|
+
throw new TypeError(
|
|
403
|
+
"QuickGUI color transition must declare background-color, border-color, and color",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return sharedDuration ?? 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function splitCssList(value: string): string[] {
|
|
410
|
+
const values: string[] = [];
|
|
411
|
+
let start = 0;
|
|
412
|
+
let depth = 0;
|
|
413
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
414
|
+
const character = value[index];
|
|
415
|
+
if (character === "(") depth += 1;
|
|
416
|
+
else if (character === ")") depth = Math.max(0, depth - 1);
|
|
417
|
+
else if (character === "," && depth === 0) {
|
|
418
|
+
values.push(value.slice(start, index).trim());
|
|
419
|
+
start = index + 1;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
values.push(value.slice(start).trim());
|
|
423
|
+
return values.filter(Boolean);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function isLengthProperty(code: PropertyCode): boolean {
|
|
427
|
+
return (
|
|
428
|
+
(code >= PropertyCode.Gap && code <= PropertyCode.MarginLeft) ||
|
|
429
|
+
code === PropertyCode.BorderWidth ||
|
|
430
|
+
code === PropertyCode.BorderRadius ||
|
|
431
|
+
code === PropertyCode.FontSize ||
|
|
432
|
+
code === PropertyCode.LineHeight ||
|
|
433
|
+
(code >= PropertyCode.Top && code <= PropertyCode.Left) ||
|
|
434
|
+
code === PropertyCode.AnchorGap ||
|
|
435
|
+
code === PropertyCode.ViewportMargin ||
|
|
436
|
+
(code >= PropertyCode.HitSlop && code <= PropertyCode.HitSlopLeft)
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function eventName(
|
|
441
|
+
name: string,
|
|
442
|
+
):
|
|
443
|
+
| "click"
|
|
444
|
+
| "mouseenter"
|
|
445
|
+
| "mouseleave"
|
|
446
|
+
| "input"
|
|
447
|
+
| "submit"
|
|
448
|
+
| "dismiss"
|
|
449
|
+
| "terminal"
|
|
450
|
+
| "pointer"
|
|
451
|
+
| undefined {
|
|
452
|
+
switch (name.toLowerCase()) {
|
|
453
|
+
case "onclick":
|
|
454
|
+
case "on:click":
|
|
455
|
+
return "click";
|
|
456
|
+
case "onmouseenter":
|
|
457
|
+
case "onpointerenter":
|
|
458
|
+
return "mouseenter";
|
|
459
|
+
case "onmouseleave":
|
|
460
|
+
case "onpointerleave":
|
|
461
|
+
return "mouseleave";
|
|
462
|
+
case "oninput":
|
|
463
|
+
case "onchange":
|
|
464
|
+
return "input";
|
|
465
|
+
case "onsubmit":
|
|
466
|
+
return "submit";
|
|
467
|
+
case "ondismiss":
|
|
468
|
+
case "on:dismiss":
|
|
469
|
+
return "dismiss";
|
|
470
|
+
case "onstatus":
|
|
471
|
+
case "onterminal":
|
|
472
|
+
case "on:terminal":
|
|
473
|
+
return "terminal";
|
|
474
|
+
case "onpointer":
|
|
475
|
+
case "on:pointer":
|
|
476
|
+
return "pointer";
|
|
477
|
+
default:
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function encodeTerminalArguments(value: unknown): string {
|
|
483
|
+
if (
|
|
484
|
+
!Array.isArray(value) ||
|
|
485
|
+
value.some((argument) => typeof argument !== "string")
|
|
486
|
+
) {
|
|
487
|
+
throw new TypeError(
|
|
488
|
+
"QuickGUI terminal arguments must be an array of strings",
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
return JSON.stringify(value);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function encodeTerminalEnvironment(value: unknown): string {
|
|
495
|
+
if (
|
|
496
|
+
!isRecord(value) ||
|
|
497
|
+
Object.entries(value).some(
|
|
498
|
+
([key, item]) => key.length === 0 || typeof item !== "string",
|
|
499
|
+
)
|
|
500
|
+
) {
|
|
501
|
+
throw new TypeError(
|
|
502
|
+
"QuickGUI terminal environment must contain string keys and values",
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return JSON.stringify(value);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function encodeTerminalPalette(value: unknown): string {
|
|
509
|
+
if (
|
|
510
|
+
!Array.isArray(value) ||
|
|
511
|
+
value.length !== 16 ||
|
|
512
|
+
value.some(
|
|
513
|
+
(color) => typeof color !== "string" && typeof color !== "number",
|
|
514
|
+
)
|
|
515
|
+
) {
|
|
516
|
+
throw new TypeError(
|
|
517
|
+
"QuickGUI terminalPalette must contain exactly 16 colors",
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
return JSON.stringify(value.map((color) => parseColor(color)));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
524
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const universal = createUniversalRenderer<NativeNode>({
|
|
528
|
+
createElement(tag, staticProps) {
|
|
529
|
+
const name = tag as NativeElementName;
|
|
530
|
+
if (
|
|
531
|
+
![
|
|
532
|
+
"view",
|
|
533
|
+
"div",
|
|
534
|
+
"text",
|
|
535
|
+
"button",
|
|
536
|
+
"input",
|
|
537
|
+
"textarea",
|
|
538
|
+
"markdown",
|
|
539
|
+
"virtual-list",
|
|
540
|
+
"terminal",
|
|
541
|
+
"svg",
|
|
542
|
+
].includes(name)
|
|
543
|
+
) {
|
|
544
|
+
throw new TypeError(`unknown QuickGUI element <${tag}>`);
|
|
545
|
+
}
|
|
546
|
+
const node = createNativeElement(name);
|
|
547
|
+
if (staticProps) {
|
|
548
|
+
for (const [name, value] of Object.entries(staticProps))
|
|
549
|
+
setProperty(node, name, value);
|
|
550
|
+
}
|
|
551
|
+
return node;
|
|
552
|
+
},
|
|
553
|
+
createTextNode: createNativeText,
|
|
554
|
+
createSentinel: createNativeSentinel,
|
|
555
|
+
replaceText: replaceNativeText,
|
|
556
|
+
isTextNode: isNativeText,
|
|
557
|
+
setProperty,
|
|
558
|
+
insertNode: insertNativeNode,
|
|
559
|
+
removeNode: removeNativeNode,
|
|
560
|
+
cleanupNodes: cleanupNativeNodes,
|
|
561
|
+
getParentNode: getNativeParent,
|
|
562
|
+
getFirstChild: getNativeFirstChild,
|
|
563
|
+
getNextSibling: getNativeNextSibling,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const nativeRender = universal.render;
|
|
567
|
+
|
|
568
|
+
/** Unstyled block/flex/grid container. */
|
|
569
|
+
export function View(props: JSX.NativeProps): NativeNode {
|
|
570
|
+
const node = universal.createElement("view");
|
|
571
|
+
universal.spread(node, props);
|
|
572
|
+
return node;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** Unstyled text-semantic container whose string children remain individually reactive. */
|
|
576
|
+
export function Text(props: JSX.NativeProps): NativeNode {
|
|
577
|
+
const node = universal.createElement("text");
|
|
578
|
+
universal.spread(node, props);
|
|
579
|
+
return node;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** Unstyled, focusable native button with web-style arrow-cursor behavior by default. */
|
|
583
|
+
export function Button(props: JSX.NativeProps): NativeNode {
|
|
584
|
+
const node = universal.createElement("button");
|
|
585
|
+
universal.spread(node, props);
|
|
586
|
+
return node;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** Controlled, unstyled single-line native text input. */
|
|
590
|
+
export function Input(props: JSX.InputProps): NativeNode {
|
|
591
|
+
const node = universal.createElement("input");
|
|
592
|
+
universal.spread(node, props);
|
|
593
|
+
return node;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Controlled, unstyled multiline native text area. */
|
|
597
|
+
export function TextArea(props: JSX.InputProps): NativeNode {
|
|
598
|
+
const node = universal.createElement("textarea");
|
|
599
|
+
universal.spread(node, props);
|
|
600
|
+
return node;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Retained, incremental native Markdown document. */
|
|
604
|
+
export function Markdown(props: JSX.MarkdownProps): NativeNode {
|
|
605
|
+
const node = universal.createElement("markdown");
|
|
606
|
+
universal.spread(node, props);
|
|
607
|
+
return node;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** Unstyled variable-height list; only visible child blocks are mounted by QuickGUI core. */
|
|
611
|
+
export function VirtualList(props: JSX.VirtualListProps): NativeNode {
|
|
612
|
+
const node = universal.createElement("virtual-list");
|
|
613
|
+
universal.spread(node, props);
|
|
614
|
+
return node;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Real PTY terminal rendered by QuickGUI core through libghostty-vt. */
|
|
618
|
+
export function Terminal(props: JSX.TerminalProps): NativeNode {
|
|
619
|
+
const node = universal.createElement("terminal");
|
|
620
|
+
universal.spread(node, props);
|
|
621
|
+
return node;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Parsed-once retained SVG mask tinted by the inherited `color` style. */
|
|
625
|
+
export function Svg(props: JSX.SvgProps): NativeNode {
|
|
626
|
+
const node = universal.createElement("svg");
|
|
627
|
+
universal.spread(node, props);
|
|
628
|
+
return node;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export type TerminalStatusKind = "starting" | "running" | "exited" | "failed";
|
|
632
|
+
|
|
633
|
+
export interface TerminalStatusEvent {
|
|
634
|
+
status: TerminalStatusKind;
|
|
635
|
+
title: string;
|
|
636
|
+
workingDirectory: string | null;
|
|
637
|
+
processId?: number;
|
|
638
|
+
exitCode?: number | null;
|
|
639
|
+
signal?: string | null;
|
|
640
|
+
message?: string;
|
|
641
|
+
agent?: string;
|
|
642
|
+
agentStatus?: "idle" | "working" | "blocked";
|
|
643
|
+
agentProcessId?: number;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export type TerminalPalette = readonly [
|
|
647
|
+
number | string,
|
|
648
|
+
number | string,
|
|
649
|
+
number | string,
|
|
650
|
+
number | string,
|
|
651
|
+
number | string,
|
|
652
|
+
number | string,
|
|
653
|
+
number | string,
|
|
654
|
+
number | string,
|
|
655
|
+
number | string,
|
|
656
|
+
number | string,
|
|
657
|
+
number | string,
|
|
658
|
+
number | string,
|
|
659
|
+
number | string,
|
|
660
|
+
number | string,
|
|
661
|
+
number | string,
|
|
662
|
+
number | string,
|
|
663
|
+
];
|
|
664
|
+
|
|
665
|
+
/** Decode the structured payload delivered to a terminal's `onStatus` listener. */
|
|
666
|
+
export function terminalStatusFromEvent(
|
|
667
|
+
event: QuickGuiEvent,
|
|
668
|
+
): TerminalStatusEvent {
|
|
669
|
+
if (!event.value)
|
|
670
|
+
throw new TypeError("QuickGUI terminal status event has no payload");
|
|
671
|
+
return JSON.parse(event.value) as TerminalStatusEvent;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export type PointerPhase = "down" | "move" | "up" | "cancel";
|
|
675
|
+
|
|
676
|
+
export interface CapturedPointerEvent {
|
|
677
|
+
phase: PointerPhase;
|
|
678
|
+
position: { x: number; y: number };
|
|
679
|
+
origin: { x: number; y: number };
|
|
680
|
+
localPosition: { x: number; y: number };
|
|
681
|
+
localOrigin: { x: number; y: number };
|
|
682
|
+
delta: { x: number; y: number };
|
|
683
|
+
button: "left" | "right" | "middle" | "back" | "forward" | "other";
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/** Decode a Rust-core captured pointer payload. */
|
|
687
|
+
export function capturedPointerFromEvent(
|
|
688
|
+
event: QuickGuiEvent,
|
|
689
|
+
): CapturedPointerEvent {
|
|
690
|
+
if (!event.value)
|
|
691
|
+
throw new TypeError("QuickGUI pointer event has no payload");
|
|
692
|
+
return JSON.parse(event.value) as CapturedPointerEvent;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
export type PopoverOpenChangeReason = "trigger-press" | "dismiss";
|
|
696
|
+
|
|
697
|
+
export interface PopoverOpenChangeDetails {
|
|
698
|
+
reason: PopoverOpenChangeReason;
|
|
699
|
+
event: QuickGuiEvent;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
type PopoverSurface = "popover" | "system-popover";
|
|
703
|
+
|
|
704
|
+
interface PopoverContextValue {
|
|
705
|
+
surface: PopoverSurface;
|
|
706
|
+
open: () => boolean;
|
|
707
|
+
anchor: () => NativeNode | undefined;
|
|
708
|
+
dismissOnEscape: () => boolean;
|
|
709
|
+
dismissOnPointerOutside: () => boolean;
|
|
710
|
+
registerTrigger: (node: NativeNode) => void;
|
|
711
|
+
unregisterTrigger: (node: NativeNode) => void;
|
|
712
|
+
toggleFromTrigger: (node: NativeNode, event: QuickGuiEvent) => void;
|
|
713
|
+
dismiss: (event: QuickGuiEvent) => void;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const PopoverContext = createContext<PopoverContextValue>();
|
|
717
|
+
|
|
718
|
+
function createPopoverRoot(
|
|
719
|
+
surface: PopoverSurface,
|
|
720
|
+
props: JSX.PopoverRootProps,
|
|
721
|
+
): NativeNode {
|
|
722
|
+
const [uncontrolledOpen, setUncontrolledOpen] = createSignal(
|
|
723
|
+
props.defaultOpen ?? false,
|
|
724
|
+
);
|
|
725
|
+
const [anchor, setAnchor] = createSignal<NativeNode>();
|
|
726
|
+
const triggers = new Set<NativeNode>();
|
|
727
|
+
const open = () => props.open ?? uncontrolledOpen();
|
|
728
|
+
|
|
729
|
+
const changeOpen = (
|
|
730
|
+
nextOpen: boolean,
|
|
731
|
+
reason: PopoverOpenChangeReason,
|
|
732
|
+
event: QuickGuiEvent,
|
|
733
|
+
) => {
|
|
734
|
+
if (props.open === undefined) setUncontrolledOpen(nextOpen);
|
|
735
|
+
props.onOpenChange?.(nextOpen, { reason, event });
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const context: PopoverContextValue = {
|
|
739
|
+
surface,
|
|
740
|
+
open,
|
|
741
|
+
anchor,
|
|
742
|
+
dismissOnEscape: () => props.dismissOnEscape ?? true,
|
|
743
|
+
dismissOnPointerOutside: () => props.dismissOnPointerOutside ?? true,
|
|
744
|
+
registerTrigger(node) {
|
|
745
|
+
triggers.add(node);
|
|
746
|
+
if (!anchor()) setAnchor(node);
|
|
747
|
+
},
|
|
748
|
+
unregisterTrigger(node) {
|
|
749
|
+
triggers.delete(node);
|
|
750
|
+
if (anchor() === node) setAnchor(triggers.values().next().value);
|
|
751
|
+
},
|
|
752
|
+
toggleFromTrigger(node, event) {
|
|
753
|
+
setAnchor(node);
|
|
754
|
+
changeOpen(!open(), "trigger-press", event);
|
|
755
|
+
},
|
|
756
|
+
dismiss(event) {
|
|
757
|
+
changeOpen(false, "dismiss", event);
|
|
758
|
+
},
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
return PopoverContext({
|
|
762
|
+
value: context,
|
|
763
|
+
get children() {
|
|
764
|
+
return props.children as SolidElement;
|
|
765
|
+
},
|
|
766
|
+
}) as unknown as NativeNode;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Logical root for an in-window popover. It does not create a native element. */
|
|
770
|
+
export function PopoverRoot(props: JSX.PopoverRootProps): NativeNode {
|
|
771
|
+
return createPopoverRoot("popover", props);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/** Logical root for a native-window popover. It does not create a native window by itself. */
|
|
775
|
+
export function SystemPopoverRoot(props: JSX.PopoverRootProps): NativeNode {
|
|
776
|
+
return createPopoverRoot("system-popover", props);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/** Trigger button shared by in-window and system popover roots. */
|
|
780
|
+
export function PopoverTrigger(props: JSX.PopoverTriggerProps): NativeNode {
|
|
781
|
+
const context = useContext(PopoverContext);
|
|
782
|
+
let trigger: NativeNode | undefined;
|
|
783
|
+
const forwarded = universal.mergeProps(props, {
|
|
784
|
+
ref: [
|
|
785
|
+
(node: NativeNode) => {
|
|
786
|
+
trigger = node;
|
|
787
|
+
context.registerTrigger(node);
|
|
788
|
+
},
|
|
789
|
+
props.ref,
|
|
790
|
+
].filter(
|
|
791
|
+
(value): value is (node: NativeNode) => void =>
|
|
792
|
+
typeof value === "function",
|
|
793
|
+
),
|
|
794
|
+
onClick(event: QuickGuiEvent) {
|
|
795
|
+
props.onClick?.(event);
|
|
796
|
+
if (!event.defaultPrevented && trigger)
|
|
797
|
+
context.toggleFromTrigger(trigger, event);
|
|
798
|
+
},
|
|
799
|
+
}) as JSX.PopoverTriggerProps;
|
|
800
|
+
const node = universal.createElement("button");
|
|
801
|
+
universal.spread(node, forwarded);
|
|
802
|
+
onCleanup(() => {
|
|
803
|
+
if (trigger) context.unregisterTrigger(trigger);
|
|
804
|
+
});
|
|
805
|
+
return node;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function requirePopoverSurface(
|
|
809
|
+
expected: PopoverSurface,
|
|
810
|
+
component: string,
|
|
811
|
+
): PopoverContextValue {
|
|
812
|
+
const context = useContext(PopoverContext);
|
|
813
|
+
if (context.surface !== expected) {
|
|
814
|
+
const root = expected === "popover" ? "Popover.Root" : "SystemPopover.Root";
|
|
815
|
+
throw new TypeError(`${component} must be used inside <${root}>`);
|
|
816
|
+
}
|
|
817
|
+
return context;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function createInWindowPopoverContent(
|
|
821
|
+
props: JSX.PopoverContentProps,
|
|
822
|
+
context: PopoverContextValue,
|
|
823
|
+
anchor: NativeNode,
|
|
824
|
+
): NativeNode {
|
|
825
|
+
const surface = omit(
|
|
826
|
+
props,
|
|
827
|
+
"placement",
|
|
828
|
+
"gap",
|
|
829
|
+
"viewportMargin",
|
|
830
|
+
) as JSX.NativeProps;
|
|
831
|
+
const node = universal.createElement("view");
|
|
832
|
+
const forwarded = universal.mergeProps(surface, {
|
|
833
|
+
anchor,
|
|
834
|
+
get anchorPlacement() {
|
|
835
|
+
return props.placement ?? "bottom-start";
|
|
836
|
+
},
|
|
837
|
+
get anchorGap() {
|
|
838
|
+
return props.gap ?? 6;
|
|
839
|
+
},
|
|
840
|
+
get viewportMargin() {
|
|
841
|
+
return props.viewportMargin ?? 8;
|
|
842
|
+
},
|
|
843
|
+
get dismissOnEscape() {
|
|
844
|
+
return context.dismissOnEscape();
|
|
845
|
+
},
|
|
846
|
+
get dismissOnPointerOutside() {
|
|
847
|
+
return context.dismissOnPointerOutside();
|
|
848
|
+
},
|
|
849
|
+
onDismiss(event: QuickGuiEvent) {
|
|
850
|
+
context.dismiss(event);
|
|
851
|
+
},
|
|
852
|
+
}) as object;
|
|
853
|
+
universal.spread(node, forwarded);
|
|
854
|
+
return node;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Popover content rendered in the current window's retained overlay plane. */
|
|
858
|
+
export function PopoverContent(props: JSX.PopoverContentProps): NativeNode {
|
|
859
|
+
const context = requirePopoverSurface("popover", "Popover.Content");
|
|
860
|
+
return Show({
|
|
861
|
+
keyed: true,
|
|
862
|
+
get when() {
|
|
863
|
+
return context.open() ? context.anchor() : undefined;
|
|
864
|
+
},
|
|
865
|
+
children: (anchor) => createInWindowPopoverContent(props, context, anchor),
|
|
866
|
+
}) as unknown as NativeNode;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function createSystemPopoverContent(
|
|
870
|
+
props: JSX.PopoverContentProps,
|
|
871
|
+
context: PopoverContextValue,
|
|
872
|
+
anchor: NativeNode,
|
|
873
|
+
): NativeNode {
|
|
874
|
+
const owner = getOwner();
|
|
875
|
+
const placeholder = createNativeSentinel();
|
|
876
|
+
const surface = omit(
|
|
877
|
+
props,
|
|
878
|
+
"placement",
|
|
879
|
+
"gap",
|
|
880
|
+
"viewportMargin",
|
|
881
|
+
) as JSX.NativeProps;
|
|
882
|
+
let systemWindow: Window | undefined;
|
|
883
|
+
let disposing = false;
|
|
884
|
+
|
|
885
|
+
// Initial JSX is rendered before its owner Window has a native handle. The microtask also makes
|
|
886
|
+
// later mounts use the same lifecycle path instead of special-casing initial render.
|
|
887
|
+
queueMicrotask(() => {
|
|
888
|
+
if (disposing) return;
|
|
889
|
+
systemWindow = new Window({
|
|
890
|
+
title: "QuickGUI System Popover",
|
|
891
|
+
anchor,
|
|
892
|
+
width: props.width,
|
|
893
|
+
height: props.height,
|
|
894
|
+
placement: props.placement ?? "bottom-start",
|
|
895
|
+
gap: props.gap ?? 6,
|
|
896
|
+
viewportMargin: props.viewportMargin ?? 8,
|
|
897
|
+
dismissOnEscape: context.dismissOnEscape(),
|
|
898
|
+
dismissOnPointerOutside: context.dismissOnPointerOutside(),
|
|
899
|
+
renderer: (window) =>
|
|
900
|
+
runWithOwner(owner, () =>
|
|
901
|
+
createRenderer(() => {
|
|
902
|
+
const node = universal.createElement("view");
|
|
903
|
+
universal.spread(node, surface);
|
|
904
|
+
return node;
|
|
905
|
+
})(window),
|
|
906
|
+
),
|
|
907
|
+
});
|
|
908
|
+
systemWindow.onClose(() => {
|
|
909
|
+
systemWindow = undefined;
|
|
910
|
+
if (disposing) return;
|
|
911
|
+
// Leave the native close/disposal stack before controlled state unmounts this portal.
|
|
912
|
+
queueMicrotask(() => {
|
|
913
|
+
if (disposing) return;
|
|
914
|
+
try {
|
|
915
|
+
context.dismiss(new QuickGuiEvent("dismiss", placeholder));
|
|
916
|
+
} finally {
|
|
917
|
+
flushSolid();
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
onCleanup(() => {
|
|
924
|
+
disposing = true;
|
|
925
|
+
systemWindow?.close();
|
|
926
|
+
systemWindow = undefined;
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
return placeholder;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/** Popover content rendered through a separate Solid renderer in a native child window. */
|
|
933
|
+
export function SystemPopoverContent(
|
|
934
|
+
props: JSX.PopoverContentProps,
|
|
935
|
+
): NativeNode {
|
|
936
|
+
const context = requirePopoverSurface(
|
|
937
|
+
"system-popover",
|
|
938
|
+
"SystemPopover.Content",
|
|
939
|
+
);
|
|
940
|
+
return Show({
|
|
941
|
+
keyed: true,
|
|
942
|
+
get when() {
|
|
943
|
+
return context.open() ? context.anchor() : undefined;
|
|
944
|
+
},
|
|
945
|
+
children: (anchor) => createSystemPopoverContent(props, context, anchor),
|
|
946
|
+
}) as unknown as NativeNode;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/** Base-UI-shaped compound parts for an in-window retained popover. */
|
|
950
|
+
export const Popover = Object.assign(PopoverRoot, {
|
|
951
|
+
Root: PopoverRoot,
|
|
952
|
+
Trigger: PopoverTrigger,
|
|
953
|
+
Content: PopoverContent,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
/** Compound popover parts whose content uses a native child window. */
|
|
957
|
+
export const SystemPopover = Object.assign(SystemPopoverRoot, {
|
|
958
|
+
Root: SystemPopoverRoot,
|
|
959
|
+
Trigger: PopoverTrigger,
|
|
960
|
+
Content: SystemPopoverContent,
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
export function createRenderer(code: () => JSX.Element): WindowRenderer {
|
|
964
|
+
return (window) => {
|
|
965
|
+
const nativeDispose = nativeRender(() => code() as NativeNode, window.root);
|
|
966
|
+
let disposed = false;
|
|
967
|
+
window.flush();
|
|
968
|
+
return () => {
|
|
969
|
+
if (disposed) return;
|
|
970
|
+
disposed = true;
|
|
971
|
+
nativeDispose();
|
|
972
|
+
window.flush();
|
|
973
|
+
};
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
export const effect = universal.effect;
|
|
978
|
+
export const memo = universal.memo;
|
|
979
|
+
export const createComponent = universal.createComponent;
|
|
980
|
+
export const createElement = universal.createElement;
|
|
981
|
+
export const createTextNode = universal.createTextNode;
|
|
982
|
+
export const insertNode = universal.insertNode;
|
|
983
|
+
export const insert = universal.insert;
|
|
984
|
+
export const spread = universal.spread;
|
|
985
|
+
export const setProp = universal.setProp;
|
|
986
|
+
export const mergeProps = universal.mergeProps;
|
|
987
|
+
export const applyRef = universal.applyRef;
|
|
988
|
+
export const ref = universal.ref;
|
|
989
|
+
|
|
990
|
+
export namespace JSX {
|
|
991
|
+
export type Element = SolidElement;
|
|
992
|
+
export type Child = SolidElement;
|
|
993
|
+
export type EventHandler = NativeEventListener;
|
|
994
|
+
|
|
995
|
+
export interface ElementChildrenAttribute {
|
|
996
|
+
children: {};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export interface IntrinsicAttributes {
|
|
1000
|
+
key?: string | number;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export interface Style {
|
|
1004
|
+
display?: "none" | "block" | "flex" | "grid";
|
|
1005
|
+
flex?: number | string;
|
|
1006
|
+
flexDirection?: "row" | "row-reverse" | "column" | "column-reverse";
|
|
1007
|
+
flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
|
|
1008
|
+
flexGrow?: number;
|
|
1009
|
+
flexShrink?: number;
|
|
1010
|
+
flexBasis?: number | string;
|
|
1011
|
+
alignItems?:
|
|
1012
|
+
| "start"
|
|
1013
|
+
| "flex-start"
|
|
1014
|
+
| "center"
|
|
1015
|
+
| "end"
|
|
1016
|
+
| "flex-end"
|
|
1017
|
+
| "baseline"
|
|
1018
|
+
| "stretch";
|
|
1019
|
+
alignSelf?: Style["alignItems"];
|
|
1020
|
+
justifyContent?:
|
|
1021
|
+
| "start"
|
|
1022
|
+
| "flex-start"
|
|
1023
|
+
| "center"
|
|
1024
|
+
| "end"
|
|
1025
|
+
| "flex-end"
|
|
1026
|
+
| "space-between"
|
|
1027
|
+
| "space-around"
|
|
1028
|
+
| "space-evenly";
|
|
1029
|
+
alignContent?: Style["justifyContent"] | "normal" | "stretch";
|
|
1030
|
+
gap?: number | string;
|
|
1031
|
+
columnGap?: number | string;
|
|
1032
|
+
rowGap?: number | string;
|
|
1033
|
+
width?: number | string;
|
|
1034
|
+
height?: number | string;
|
|
1035
|
+
minWidth?: number | string;
|
|
1036
|
+
minHeight?: number | string;
|
|
1037
|
+
maxWidth?: number | string;
|
|
1038
|
+
maxHeight?: number | string;
|
|
1039
|
+
padding?: number | string;
|
|
1040
|
+
paddingTop?: number | string;
|
|
1041
|
+
paddingRight?: number | string;
|
|
1042
|
+
paddingBottom?: number | string;
|
|
1043
|
+
paddingLeft?: number | string;
|
|
1044
|
+
margin?: number | string;
|
|
1045
|
+
marginTop?: number | string;
|
|
1046
|
+
marginRight?: number | string;
|
|
1047
|
+
marginBottom?: number | string;
|
|
1048
|
+
marginLeft?: number | string;
|
|
1049
|
+
background?: number | string;
|
|
1050
|
+
backgroundColor?: number | string;
|
|
1051
|
+
color?: number | string;
|
|
1052
|
+
hoverBackgroundColor?: number | string;
|
|
1053
|
+
hoverColor?: number | string;
|
|
1054
|
+
activeBackgroundColor?: number | string;
|
|
1055
|
+
activeColor?: number | string;
|
|
1056
|
+
transition?: string;
|
|
1057
|
+
opacity?: number;
|
|
1058
|
+
borderWidth?: number | string;
|
|
1059
|
+
borderColor?: number | string;
|
|
1060
|
+
borderRadius?: number | string;
|
|
1061
|
+
fontSize?: number | string;
|
|
1062
|
+
fontFamily?: string;
|
|
1063
|
+
fontWeight?: number | string;
|
|
1064
|
+
lineHeight?: number | string;
|
|
1065
|
+
textAlign?: "left" | "center" | "right" | "justify" | "start" | "end";
|
|
1066
|
+
whiteSpace?: "normal" | "nowrap";
|
|
1067
|
+
textOverflow?: "clip" | "ellipsis";
|
|
1068
|
+
lineClamp?: number;
|
|
1069
|
+
overflow?: "visible" | "hidden" | "auto" | "scroll";
|
|
1070
|
+
overflowX?: Style["overflow"];
|
|
1071
|
+
overflowY?: Style["overflow"];
|
|
1072
|
+
cursor?: string;
|
|
1073
|
+
appRegion?: "drag" | "no-drag";
|
|
1074
|
+
position?: "relative" | "absolute";
|
|
1075
|
+
top?: number | string;
|
|
1076
|
+
right?: number | string;
|
|
1077
|
+
bottom?: number | string;
|
|
1078
|
+
left?: number | string;
|
|
1079
|
+
userSelect?: "auto" | "text" | "none";
|
|
1080
|
+
visibility?: "visible" | "hidden";
|
|
1081
|
+
aspectRatio?: number;
|
|
1082
|
+
markdownCodeBackground?: number | string;
|
|
1083
|
+
markdownBorderColor?: number | string;
|
|
1084
|
+
markdownMutedColor?: number | string;
|
|
1085
|
+
markdownLinkColor?: number | string;
|
|
1086
|
+
markdownCodeTextColor?: number | string;
|
|
1087
|
+
markdownBlockGap?: number;
|
|
1088
|
+
markdownCodeFontSize?: number;
|
|
1089
|
+
scrollToEndRevision?: number;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export interface NativeProps extends Style {
|
|
1093
|
+
children?: unknown;
|
|
1094
|
+
style?: Style;
|
|
1095
|
+
class?: string;
|
|
1096
|
+
className?: string;
|
|
1097
|
+
disabled?: boolean;
|
|
1098
|
+
role?: string;
|
|
1099
|
+
tabIndex?: number;
|
|
1100
|
+
/** Keep keyboard focus where it is when this element is activated with a pointer. */
|
|
1101
|
+
focusOnPointer?: boolean;
|
|
1102
|
+
/** Paint this subtree in the viewport overlay plane above embedded native views. */
|
|
1103
|
+
overlay?: boolean;
|
|
1104
|
+
/** Contain keyboard focus within this subtree while it is the topmost trap. */
|
|
1105
|
+
focusTrap?: boolean;
|
|
1106
|
+
/** Restore the previously focused mounted control when this surface unmounts. */
|
|
1107
|
+
restorePreviousFocus?: boolean;
|
|
1108
|
+
/** Prefer this control when its containing focus trap takes focus. */
|
|
1109
|
+
autoFocus?: boolean;
|
|
1110
|
+
/** Expose modal semantics to assistive technology. */
|
|
1111
|
+
"aria-modal"?: boolean;
|
|
1112
|
+
ariaModal?: boolean;
|
|
1113
|
+
dismissOnEscape?: boolean;
|
|
1114
|
+
dismissOnPointerOutside?: boolean;
|
|
1115
|
+
hitSlop?: number | string;
|
|
1116
|
+
hitSlopTop?: number | string;
|
|
1117
|
+
hitSlopRight?: number | string;
|
|
1118
|
+
hitSlopBottom?: number | string;
|
|
1119
|
+
hitSlopLeft?: number | string;
|
|
1120
|
+
"aria-label"?: string;
|
|
1121
|
+
ariaLabel?: string;
|
|
1122
|
+
ref?: ((node: NativeNode) => void) | NativeNode;
|
|
1123
|
+
onClick?: EventHandler;
|
|
1124
|
+
onMouseEnter?: EventHandler;
|
|
1125
|
+
onMouseLeave?: EventHandler;
|
|
1126
|
+
onPointerEnter?: EventHandler;
|
|
1127
|
+
onPointerLeave?: EventHandler;
|
|
1128
|
+
/** Captured pointer stream from press through release/cancel, including outside the element. */
|
|
1129
|
+
onPointer?: EventHandler;
|
|
1130
|
+
onInput?: EventHandler;
|
|
1131
|
+
onChange?: EventHandler;
|
|
1132
|
+
onSubmit?: EventHandler;
|
|
1133
|
+
onDismiss?: EventHandler;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
export interface InputProps extends NativeProps {
|
|
1137
|
+
type?: "text" | "password";
|
|
1138
|
+
value?: string;
|
|
1139
|
+
placeholder?: string;
|
|
1140
|
+
multiline?: boolean;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
export interface MarkdownProps extends NativeProps {
|
|
1144
|
+
content?: string;
|
|
1145
|
+
source?: string;
|
|
1146
|
+
streaming?: boolean;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export interface VirtualListProps extends NativeProps {
|
|
1150
|
+
estimatedItemHeight?: number;
|
|
1151
|
+
overscan?: number;
|
|
1152
|
+
listAlignment?: "top" | "bottom";
|
|
1153
|
+
followMode?: "normal" | "tail";
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
export interface TerminalProps extends NativeProps {
|
|
1157
|
+
/** Executable to launch. Omit to use the user's default shell. */
|
|
1158
|
+
program?: string;
|
|
1159
|
+
command?: string;
|
|
1160
|
+
arguments?: readonly string[];
|
|
1161
|
+
args?: readonly string[];
|
|
1162
|
+
workingDirectory?: string;
|
|
1163
|
+
cwd?: string;
|
|
1164
|
+
environment?: Readonly<Record<string, string>>;
|
|
1165
|
+
env?: Readonly<Record<string, string>>;
|
|
1166
|
+
scrollback?: number;
|
|
1167
|
+
/** Standard black-through-white colors followed by their eight bright variants. */
|
|
1168
|
+
terminalPalette?: TerminalPalette;
|
|
1169
|
+
terminalCursorColor?: number | string;
|
|
1170
|
+
/** Paint grid padding with the default background or extend edge-cell backgrounds into it. */
|
|
1171
|
+
terminalPaddingColor?: "background" | "extend";
|
|
1172
|
+
/** Optically thicken terminal glyph stems without selecting another font weight. */
|
|
1173
|
+
fontThicken?: boolean;
|
|
1174
|
+
onStatus?: EventHandler;
|
|
1175
|
+
onTerminal?: EventHandler;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export interface SvgProps extends NativeProps {
|
|
1179
|
+
/** Complete inline SVG document. External resources are ignored by the Rust core. */
|
|
1180
|
+
source: string;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
export interface PopoverRootProps {
|
|
1184
|
+
children?: unknown;
|
|
1185
|
+
open?: boolean;
|
|
1186
|
+
defaultOpen?: boolean;
|
|
1187
|
+
onOpenChange?: (open: boolean, details: PopoverOpenChangeDetails) => void;
|
|
1188
|
+
dismissOnEscape?: boolean;
|
|
1189
|
+
dismissOnPointerOutside?: boolean;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
export interface PopoverTriggerProps extends NativeProps {}
|
|
1193
|
+
|
|
1194
|
+
export interface PopoverContentProps extends NativeProps {
|
|
1195
|
+
width: number;
|
|
1196
|
+
height: number;
|
|
1197
|
+
placement?: PopoverPlacement;
|
|
1198
|
+
gap?: number;
|
|
1199
|
+
viewportMargin?: number;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export interface IntrinsicElements {
|
|
1203
|
+
view: NativeProps;
|
|
1204
|
+
div: NativeProps;
|
|
1205
|
+
text: NativeProps;
|
|
1206
|
+
button: NativeProps;
|
|
1207
|
+
input: InputProps;
|
|
1208
|
+
textarea: InputProps;
|
|
1209
|
+
markdown: MarkdownProps;
|
|
1210
|
+
"virtual-list": VirtualListProps;
|
|
1211
|
+
terminal: TerminalProps;
|
|
1212
|
+
svg: SvgProps;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
package/jsx-runtime.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { JSX } from "./index.ts";
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quickgui/solid",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Solid 2 renderer for QuickGUI",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/egoist/quickgui.git",
|
|
8
|
+
"directory": "packages/solid"
|
|
9
|
+
},
|
|
10
|
+
"bugs": "https://github.com/egoist/quickgui/issues",
|
|
11
|
+
"homepage": "https://github.com/egoist/quickgui#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./index.ts",
|
|
15
|
+
"./compiler": "./compiler.ts",
|
|
16
|
+
"./jsx-runtime": "./jsx-runtime.ts",
|
|
17
|
+
"./jsx-dev-runtime": "./jsx-runtime.ts"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"compiler.ts",
|
|
22
|
+
"jsx-runtime.ts",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "bun test --conditions=browser"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@quickgui/native": "0.0.1",
|
|
30
|
+
"@solidjs/compiler": "2.0.0-rc.3",
|
|
31
|
+
"@solidjs/universal": "2.0.0-rc.3",
|
|
32
|
+
"solid-js": "2.0.0-rc.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"solid-js": "2.0.0-rc.3"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"bun": ">=1.3.0"
|
|
39
|
+
},
|
|
40
|
+
"os": [
|
|
41
|
+
"darwin"
|
|
42
|
+
],
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT OR Apache-2.0"
|
|
47
|
+
}
|