@solidrt/cli 0.0.42 → 0.0.44
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/AGENTS.md +7 -1
- package/package.json +6 -7
- package/scaffold/AGENTS.md +42 -7
- package/scaffold/package.json +5 -4
- package/scaffold/templates/components/icon.tsx +48 -0
- package/scaffold/templates/components/index.tsx +15 -4
- package/scaffold/templates/components/template.json +1 -1
- package/scaffold/templates/default/icon.tsx +48 -0
- package/scaffold/templates/default/index.tsx +14 -50
- package/scaffold/templates/default/template.json +1 -1
- package/src/bundler.ts +5 -1
- package/src/commands/init.ts +7 -5
- package/scaffold/templates/gallery/icon.png +0 -0
- package/scaffold/templates/gallery/index.tsx +0 -485
- package/scaffold/templates/gallery/template.json +0 -4
- package/scaffold/templates/minimal/index.tsx +0 -14
- package/scaffold/templates/minimal/template.json +0 -4
package/AGENTS.md
CHANGED
|
@@ -32,10 +32,16 @@ Two reliable checks that need no GUI:
|
|
|
32
32
|
|
|
33
33
|
1. `bunx srt bundle src/index.tsx` - exit 0 means the app compiles. Fast.
|
|
34
34
|
2. `bunx srt render src/index.tsx --size 480x640 --duration 1 --fps 2` -
|
|
35
|
-
renders offscreen via EGL
|
|
35
|
+
renders offscreen via EGL and writes `frame-NNNNNN.png`. This actually
|
|
36
36
|
proves the app renders. Combine with `--fps`/`--duration` (defaults
|
|
37
37
|
1280x720, 60fps, 1s).
|
|
38
38
|
|
|
39
|
+
Also headless: the bundled flux runtime runs a plain `.js` file directly -
|
|
40
|
+
`node_modules/@solidrt/<platform>/flux script.js` (e.g.
|
|
41
|
+
`@solidrt/linux-x64-gnu`). No display, no dev server, full `flux:*` module
|
|
42
|
+
access. The right tool for micro-benchmarks and for probing flux module
|
|
43
|
+
behavior in isolation.
|
|
44
|
+
|
|
39
45
|
`render` gotchas:
|
|
40
46
|
- Frames are written to the RUNTIME's working dir (`~/.local/share/SolidRT/go/`),
|
|
41
47
|
NOT the directory you ran the command from. Look there for the PNGs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -28,17 +28,16 @@
|
|
|
28
28
|
"zod": "^4.4.3"
|
|
29
29
|
},
|
|
30
30
|
"optionalDependencies": {
|
|
31
|
-
"@solidrt/darwin-arm64": "0.0.
|
|
32
|
-
"@solidrt/linux-arm64-gnu": "0.0.
|
|
33
|
-
"@solidrt/linux-x64-gnu": "0.0.
|
|
34
|
-
"@solidrt/win32-x64-msvc": "0.0.
|
|
31
|
+
"@solidrt/darwin-arm64": "0.0.44",
|
|
32
|
+
"@solidrt/linux-arm64-gnu": "0.0.44",
|
|
33
|
+
"@solidrt/linux-x64-gnu": "0.0.44",
|
|
34
|
+
"@solidrt/win32-x64-msvc": "0.0.44"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@solidrt/core": "0.0.42",
|
|
38
37
|
"typescript": "^7"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
|
-
"@solidrt/flux-types": "0.0.
|
|
40
|
+
"@solidrt/flux-types": "0.0.44",
|
|
42
41
|
"@types/babel__core": "^7.20.5",
|
|
43
42
|
"@types/bun": "latest"
|
|
44
43
|
}
|
package/scaffold/AGENTS.md
CHANGED
|
@@ -54,9 +54,12 @@ Authoritative references ship inside the installed packages - read them:
|
|
|
54
54
|
top-level props, not inside `layout`/`style`.
|
|
55
55
|
3. `render(() => <App/>)` once, top level. The root MUST be a `<Window>`
|
|
56
56
|
(from @solidrt/components) or the core `<window>` - it throws otherwise.
|
|
57
|
-
4. `Window`/`View` do not paint on their own; they only paint
|
|
58
|
-
`style.backgroundColor`/`borderColor` etc - there is no
|
|
59
|
-
background element to place by hand.
|
|
57
|
+
4. Components' `Window`/`View` do not paint on their own; they only paint
|
|
58
|
+
when you set `style.backgroundColor`/`borderColor` etc - there is no
|
|
59
|
+
separate background element to place by hand. That is the components level
|
|
60
|
+
only: the core `<view>`/`<window>` have no background prop at all - in a
|
|
61
|
+
core-only app the background is a draw-primitive child
|
|
62
|
+
(`<d-rect color={...} />`) behind the content.
|
|
60
63
|
5. There is no onClick/onPress on host elements. Use `Pressable`/`Button`
|
|
61
64
|
from components (`onPress`), or `onPointerDown` on a `View` for anything
|
|
62
65
|
custom.
|
|
@@ -125,7 +128,15 @@ Authoritative references ship inside the installed packages - read them:
|
|
|
125
128
|
effect's apply phase, or `onSettled`; opt in narrowly with
|
|
126
129
|
`createSignal(v, { ownedWrite: true })` for a signal that genuinely is
|
|
127
130
|
internal state.
|
|
128
|
-
19.
|
|
131
|
+
19. Transform origin on a `d-view`: unset `originX`/`originY` pivots
|
|
132
|
+
scale/rotate at the view's local (0,0), the point its children's
|
|
133
|
+
coordinates are drawn against (a laid-out view pivots at its own box
|
|
134
|
+
center; a d-view has no box). To pivot a detached group around its
|
|
135
|
+
content's center, set the origin explicitly in pixels
|
|
136
|
+
(`originX={100} originY={50}` for content drawn in a 200x100 local
|
|
137
|
+
space). Avoid pct()/keyword origins on a d-view - they resolve against
|
|
138
|
+
the box inherited from the nearest laid-out ancestor.
|
|
139
|
+
20. Cover/contain images: give `Image` a `fit` prop ("fill" | "cover" |
|
|
129
140
|
"contain" | "none" | "scale-down", CSS object-fit semantics, centered)
|
|
130
141
|
plus a box via `layout` in any form - numbers, pct(), flex. Without
|
|
131
142
|
`fit`, only NUMERIC layout sizes reach the image; `width: pct(100)`
|
|
@@ -153,7 +164,7 @@ work stops being free" below is where it does not. Rules, in order of leverage:
|
|
|
153
164
|
to compileShader/linkProgram. Params drive any uniform type: a number
|
|
154
165
|
fills a `float`/`int` scalar, a flat number array fills `vec2`/`vec3`/
|
|
155
166
|
`vec4` (2/3/4 numbers) or `mat4` (16, column-major), dispatched by the
|
|
156
|
-
shader's own declaration - a ported shader's `vec2 uCenter` or
|
|
167
|
+
shader's own declaration - a ported shader's `vec2 uCenter` or
|
|
157
168
|
`vec3 iResolution` needs no splitting into scalars. To combine several
|
|
158
169
|
GPU passes, stack `<texture>` elements and set `blendMode` (e.g. a base
|
|
159
170
|
pass plus an additive `blendMode="plus"` pass) rather than writing a
|
|
@@ -171,7 +182,10 @@ work stops being free" below is where it does not. Rules, in order of leverage:
|
|
|
171
182
|
on screen and to shaders sampling the texture.
|
|
172
183
|
2. Reduce setProperty calls wherever possible: one path string rebuilt per
|
|
173
184
|
frame beats N elements with N animated positions; a shader beats the path
|
|
174
|
-
string. get_stats' setPropsPerFrame is the counter to watch.
|
|
185
|
+
string. get_stats' setPropsPerFrame is the counter to watch. Compiled JSX
|
|
186
|
+
attribute expressions diff before writing, so a per-frame expression that
|
|
187
|
+
returns an unchanged value costs no property write - setPropsPerFrame
|
|
188
|
+
counts values that actually changed, not expressions re-run.
|
|
175
189
|
3. Never leave onFrame registered while nothing animates: a pending onFrame
|
|
176
190
|
is a standing frame request, so the runtime renders and presents every
|
|
177
191
|
vsync even when the callback body does nothing - an invisible 60fps GPU
|
|
@@ -187,7 +201,11 @@ work stops being free" below is where it does not. Rules, in order of leverage:
|
|
|
187
201
|
system classifies these as Transform and keeps the node's own cache).
|
|
188
202
|
What DOES invalidate the cache is any paint or content change inside the
|
|
189
203
|
subtree - colors, path data, text, a Show toggling - so drive animation
|
|
190
|
-
with transforms and keep the cached content itself static.
|
|
204
|
+
with transforms and keep the cached content itself static. Off a boundary,
|
|
205
|
+
`opacity` on a view is NOT cheap: it wraps the subtree in a compositing
|
|
206
|
+
layer (save_layer) for as long as it is below 1. To fade a single
|
|
207
|
+
primitive, put the alpha in its `color` (`rgba(...)`) - paint alpha is
|
|
208
|
+
free; reserve view `opacity` for fading a genuine group as a whole.
|
|
191
209
|
5. "snapshot" boundaries pay first-frame texture allocation + raster:
|
|
192
210
|
creating many at once (dealing a board of 64 sprites) is a visible
|
|
193
211
|
one-frame hiccup - pool or pre-warm if that moment matters.
|
|
@@ -308,6 +326,9 @@ its tools over guessing at runtime state:
|
|
|
308
326
|
- get_buffer: a vertex-buffer range decoded to numbers (f32/u16/u8, 64 KiB
|
|
309
327
|
per call) - verify geometry after a writeBuffer instead of inferring it
|
|
310
328
|
from pixels
|
|
329
|
+
- list_debug / call_debug: the app's own debug commands (registered with
|
|
330
|
+
`registerDebug` from `srt:dev`) - list them, then invoke by name with a
|
|
331
|
+
JSON argument. Per client, like get_snapshot
|
|
311
332
|
- reload: rebuild from source and push to every client - THE dev loop is
|
|
312
333
|
edit -> reload -> get_logs -> get_snapshot. reload surfaces build errors
|
|
313
334
|
but not type errors; run `bunx srt check` for those.
|
|
@@ -350,6 +371,13 @@ flag: `"args": [..., "mcp", "--port", "N"]`.
|
|
|
350
371
|
- console.log + get_logs is your primary probe into runtime state. For state
|
|
351
372
|
you will want repeatedly (a pose, a mode, a counter), bind a debug key that
|
|
352
373
|
logs it and read it back via get_logs.
|
|
374
|
+
- Better than debug keys when driving the app over MCP: register debug
|
|
375
|
+
COMMANDS - `registerDebug(name, fn)` from `srt:dev`, invoked via the
|
|
376
|
+
list_debug/call_debug tools. `seek`/`pause`/`play` commands turn verifying
|
|
377
|
+
an animation into "jump to t, snapshot, look"; a `zoom` command that
|
|
378
|
+
shrinks a viewBox to a region gives magnified captures without touching
|
|
379
|
+
source. Registrations reset on hot reload, so register at module init;
|
|
380
|
+
sync return values only.
|
|
353
381
|
- Key events start at the focused node and bubble to the window root; with
|
|
354
382
|
nothing focused they go to the window root alone. So a debug key bound via
|
|
355
383
|
`<window onKeyDown>` always fires (unless a focused component consumes the
|
|
@@ -367,6 +395,13 @@ flag: `"args": [..., "mcp", "--port", "N"]`.
|
|
|
367
395
|
- When a human reports a visual bug: capture a snapshot and SAY WHAT YOU SEE
|
|
368
396
|
in it before investigating, so you agree on the symptom. If you cannot see
|
|
369
397
|
the problem in the capture, say that instead of guessing.
|
|
398
|
+
- Snapshots are downscaled by the time you see them, so a full-window capture
|
|
399
|
+
cannot show you a defect a few pixels across. Whenever you hand-author
|
|
400
|
+
geometry - a `d-path` from raw path math, a `radius` where two shapes meet,
|
|
401
|
+
a stroke join - inspect it MAGNIFIED once, when you write it: a throwaway
|
|
402
|
+
entry file drawing the construction at 4-8x (pushed with `load`), or a
|
|
403
|
+
`zoom` debug command on the real app. Verifying that a shape is in the
|
|
404
|
+
right place is not the same check as verifying it is drawn right.
|
|
370
405
|
- GPU/geometry bugs: inspect the actual GPU data FIRST - get_gpu_resources
|
|
371
406
|
for draw counts/uniforms/sizes, get_texture for atlas or data-texture
|
|
372
407
|
contents ("is this tile blank?" is a ten-second question), get_buffer for
|
package/scaffold/package.json
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "srt run src/index.tsx",
|
|
7
|
+
"pack": "srt pack -o out src/index.tsx",
|
|
7
8
|
"server": "srt server",
|
|
8
9
|
"client": "srt client",
|
|
9
10
|
"android": "srt client --android"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
|
-
"@solidrt/core": "0.0.
|
|
13
|
-
"@solidrt/components": "0.0.
|
|
13
|
+
"@solidrt/core": "0.0.44",
|
|
14
|
+
"@solidrt/components": "0.0.44"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
|
-
"@solidrt/cli": "0.0.
|
|
17
|
-
"@solidrt/flux-types": "0.0.
|
|
17
|
+
"@solidrt/cli": "0.0.44",
|
|
18
|
+
"@solidrt/flux-types": "0.0.44",
|
|
18
19
|
"typescript": "^7"
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The animated SolidRT logo: each segment fades in, then out, on its own delay.
|
|
2
|
+
import { For, onFrame, createSignal, createLinearGradient } from "@solidrt/core"
|
|
3
|
+
|
|
4
|
+
const FADE = 360
|
|
5
|
+
|
|
6
|
+
let SEGMENTS = [
|
|
7
|
+
{ base: 0, light: "#3f5494", dark: "#162b6c", d: "M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" },
|
|
8
|
+
{ base: 90, light: "#547ebf", dark: "#2b5696", d: "M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" },
|
|
9
|
+
{ base: 180, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" },
|
|
10
|
+
{ base: 270, light: "#547ebf", dark: "#2b5696", d: "M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" },
|
|
11
|
+
{ base: 360, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" },
|
|
12
|
+
{ base: 450, light: "#3f5494", dark: "#162b6c", d: "M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" },
|
|
13
|
+
{ base: 540, light: "#7ea9ea", dark: "#5681c1", d: "M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
let LAST = SEGMENTS[SEGMENTS.length - 1]!.base
|
|
17
|
+
let IN_DONE = LAST + FADE
|
|
18
|
+
let CYCLE = IN_DONE + LAST + FADE
|
|
19
|
+
|
|
20
|
+
let clamp = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x)
|
|
21
|
+
let ease = (t: number) => 1 - (1 - t) * (1 - t)
|
|
22
|
+
let byte = (x: number) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0")
|
|
23
|
+
|
|
24
|
+
let fill = (seg: (typeof SEGMENTS)[number], a: number) =>
|
|
25
|
+
createLinearGradient(0, 0, 1, 1, [
|
|
26
|
+
{ offset: 0, color: seg.light + byte(a * 255) },
|
|
27
|
+
{ offset: 1, color: seg.dark + byte(a * 255) },
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
export function Icon() {
|
|
31
|
+
let [clock, setClock] = createSignal(0)
|
|
32
|
+
let start = -1
|
|
33
|
+
onFrame((t) => (start < 0 && (start = t), setClock((t - start) % CYCLE)))
|
|
34
|
+
return (
|
|
35
|
+
<view width={300} height={300} viewBox={[100, 100]}>
|
|
36
|
+
<For each={SEGMENTS}>
|
|
37
|
+
{(seg) => {
|
|
38
|
+
let a = () => {
|
|
39
|
+
let t = clock(), end = IN_DONE + seg.base + FADE
|
|
40
|
+
if (t < seg.base || t >= end) return 0
|
|
41
|
+
return ease(Math.min(clamp((t - seg.base) / FADE), clamp((end - t) / FADE)))
|
|
42
|
+
}
|
|
43
|
+
return <d-path d={seg.d} color={fill(seg, a())} />
|
|
44
|
+
}}
|
|
45
|
+
</For>
|
|
46
|
+
</view>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
// Components template: built with the @solidrt/components framework.
|
|
2
|
-
import { render } from "@solidrt/core"
|
|
3
|
-
import { Window, SafeArea, Text } from "@solidrt/components"
|
|
2
|
+
import { render, createLinearGradient } from "@solidrt/core"
|
|
3
|
+
import { Window, SafeArea, View, Text } from "@solidrt/components"
|
|
4
|
+
import { Icon } from "./icon"
|
|
4
5
|
|
|
5
6
|
function App() {
|
|
7
|
+
let backgroundColor = createLinearGradient(0, 0, 1, 1, [
|
|
8
|
+
{ offset: 0, color: "#080b16" },
|
|
9
|
+
{ offset: 1, color: "#1d2a52" },
|
|
10
|
+
])
|
|
11
|
+
|
|
6
12
|
return (
|
|
7
|
-
<Window>
|
|
13
|
+
<Window title="The Solid Runtime" style={{ backgroundColor }}>
|
|
8
14
|
<SafeArea>
|
|
9
|
-
<
|
|
15
|
+
<View layout={{ flex: 1, gap: 20, alignItems: "center", justifyContent: "center" }}>
|
|
16
|
+
<Icon />
|
|
17
|
+
<Text layout={{ fontSize: 40 }} style={{ color: "#ccc" }}>
|
|
18
|
+
The Solid Runtime
|
|
19
|
+
</Text>
|
|
20
|
+
</View>
|
|
10
21
|
</SafeArea>
|
|
11
22
|
</Window>
|
|
12
23
|
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The animated SolidRT logo: each segment fades in, then out, on its own delay.
|
|
2
|
+
import { For, onFrame, createSignal, createLinearGradient } from "@solidrt/core"
|
|
3
|
+
|
|
4
|
+
const FADE = 360
|
|
5
|
+
|
|
6
|
+
let SEGMENTS = [
|
|
7
|
+
{ base: 0, light: "#3f5494", dark: "#162b6c", d: "M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" },
|
|
8
|
+
{ base: 90, light: "#547ebf", dark: "#2b5696", d: "M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" },
|
|
9
|
+
{ base: 180, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" },
|
|
10
|
+
{ base: 270, light: "#547ebf", dark: "#2b5696", d: "M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" },
|
|
11
|
+
{ base: 360, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" },
|
|
12
|
+
{ base: 450, light: "#3f5494", dark: "#162b6c", d: "M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" },
|
|
13
|
+
{ base: 540, light: "#7ea9ea", dark: "#5681c1", d: "M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
let LAST = SEGMENTS[SEGMENTS.length - 1]!.base
|
|
17
|
+
let IN_DONE = LAST + FADE
|
|
18
|
+
let CYCLE = IN_DONE + LAST + FADE
|
|
19
|
+
|
|
20
|
+
let clamp = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x)
|
|
21
|
+
let ease = (t: number) => 1 - (1 - t) * (1 - t)
|
|
22
|
+
let byte = (x: number) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0")
|
|
23
|
+
|
|
24
|
+
let fill = (seg: (typeof SEGMENTS)[number], a: number) =>
|
|
25
|
+
createLinearGradient(0, 0, 1, 1, [
|
|
26
|
+
{ offset: 0, color: seg.light + byte(a * 255) },
|
|
27
|
+
{ offset: 1, color: seg.dark + byte(a * 255) },
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
export function Icon() {
|
|
31
|
+
let [clock, setClock] = createSignal(0)
|
|
32
|
+
let start = -1
|
|
33
|
+
onFrame((t) => (start < 0 && (start = t), setClock((t - start) % CYCLE)))
|
|
34
|
+
return (
|
|
35
|
+
<view width={300} height={300} viewBox={[100, 100]}>
|
|
36
|
+
<For each={SEGMENTS}>
|
|
37
|
+
{(seg) => {
|
|
38
|
+
let a = () => {
|
|
39
|
+
let t = clock(), end = IN_DONE + seg.base + FADE
|
|
40
|
+
if (t < seg.base || t >= end) return 0
|
|
41
|
+
return ease(Math.min(clamp((t - seg.base) / FADE), clamp((end - t) / FADE)))
|
|
42
|
+
}
|
|
43
|
+
return <d-path d={seg.d} color={fill(seg, a())} />
|
|
44
|
+
}}
|
|
45
|
+
</For>
|
|
46
|
+
</view>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -1,51 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const FADE = 360
|
|
5
|
-
|
|
6
|
-
let SEGMENTS = [
|
|
7
|
-
{ base: 0, light: "#3f5494", dark: "#162b6c", d: "M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" },
|
|
8
|
-
{ base: 90, light: "#547ebf", dark: "#2b5696", d: "M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" },
|
|
9
|
-
{ base: 180, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" },
|
|
10
|
-
{ base: 270, light: "#547ebf", dark: "#2b5696", d: "M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" },
|
|
11
|
-
{ base: 360, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" },
|
|
12
|
-
{ base: 450, light: "#3f5494", dark: "#162b6c", d: "M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" },
|
|
13
|
-
{ base: 540, light: "#7ea9ea", dark: "#5681c1", d: "M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" },
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
let LAST = SEGMENTS[SEGMENTS.length - 1]!.base
|
|
17
|
-
let IN_DONE = LAST + FADE
|
|
18
|
-
let CYCLE = IN_DONE + LAST + FADE
|
|
19
|
-
|
|
20
|
-
let clamp = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x)
|
|
21
|
-
let ease = (t: number) => 1 - (1 - t) * (1 - t)
|
|
22
|
-
let byte = (x: number) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0")
|
|
23
|
-
|
|
24
|
-
let fill = (seg: (typeof SEGMENTS)[number], a: number) =>
|
|
25
|
-
createLinearGradient(0, 0, 1, 1, [
|
|
26
|
-
{ offset: 0, color: seg.light + byte(a * 255) },
|
|
27
|
-
{ offset: 1, color: seg.dark + byte(a * 255) },
|
|
28
|
-
])
|
|
29
|
-
|
|
30
|
-
function Icon() {
|
|
31
|
-
let [clock, setClock] = createSignal(0)
|
|
32
|
-
let start = -1
|
|
33
|
-
onFrame((t) => (start < 0 && (start = t), setClock((t - start) % CYCLE)))
|
|
34
|
-
return (
|
|
35
|
-
<view width={100} height={100} scale={3}>
|
|
36
|
-
<For each={SEGMENTS}>
|
|
37
|
-
{(seg) => {
|
|
38
|
-
let a = () => {
|
|
39
|
-
let t = clock(), end = IN_DONE + seg.base + FADE
|
|
40
|
-
if (t < seg.base || t >= end) return 0
|
|
41
|
-
return ease(Math.min(clamp((t - seg.base) / FADE), clamp((end - t) / FADE)))
|
|
42
|
-
}
|
|
43
|
-
return <d-path d={seg.d} color={fill(seg, a())} />
|
|
44
|
-
}}
|
|
45
|
-
</For>
|
|
46
|
-
</view>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
1
|
+
import { render, createLinearGradient, safeArea } from "@solidrt/core"
|
|
2
|
+
import { Icon } from "./icon"
|
|
49
3
|
|
|
50
4
|
function App() {
|
|
51
5
|
let backgroundColor = createLinearGradient(0, 0, 1, 1, [
|
|
@@ -54,9 +8,19 @@ function App() {
|
|
|
54
8
|
])
|
|
55
9
|
|
|
56
10
|
return (
|
|
57
|
-
<window
|
|
11
|
+
<window title="The Solid Runtime">
|
|
58
12
|
<d-rect color={backgroundColor} />
|
|
59
|
-
<
|
|
13
|
+
<view
|
|
14
|
+
flex={1}
|
|
15
|
+
gap={20}
|
|
16
|
+
alignItems="center"
|
|
17
|
+
justifyContent="center"
|
|
18
|
+
paddingTop={safeArea().top}
|
|
19
|
+
paddingBottom={safeArea().bottom}
|
|
20
|
+
>
|
|
21
|
+
<Icon />
|
|
22
|
+
<text fontSize={40} color="#ccc">The Solid Runtime</text>
|
|
23
|
+
</view>
|
|
60
24
|
</window>
|
|
61
25
|
)
|
|
62
26
|
}
|
package/src/bundler.ts
CHANGED
|
@@ -72,7 +72,11 @@ async function codeFromOutputs(outputs: BuildArtifact[]): Promise<string> {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Bun build plugin that runs JSX/TSX through babel-preset-solid (universal
|
|
75
|
-
// generate, targeting @solidrt/core) plus the TS preset.
|
|
75
|
+
// generate, targeting @solidrt/core) plus the TS preset. `moduleName` only ends
|
|
76
|
+
// up as an import specifier in the emitted code, resolved from the app's tree at
|
|
77
|
+
// bundle time; the CLI itself never loads core, so core is deliberately neither
|
|
78
|
+
// a dependency nor a peer of this package (a second copy under the CLI could be
|
|
79
|
+
// hoisted over the app's and bundle two runtime instances). Plain .js/.ts app
|
|
76
80
|
// modules take the same path (solid is a no-op without JSX) so inlineImport
|
|
77
81
|
// can rewrite their `with { type: "binary" }` imports too; dependency code
|
|
78
82
|
// (node_modules) skips the babel detour and keeps Bun's native loaders.
|
package/src/commands/init.ts
CHANGED
|
@@ -89,10 +89,12 @@ async function resolveTemplate(): Promise<TemplateInfo> {
|
|
|
89
89
|
if (process.stdin.isTTY) {
|
|
90
90
|
let picked = await select(
|
|
91
91
|
"Select a template",
|
|
92
|
-
templates.map((t) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
templates.map((t) => {
|
|
93
|
+
// Core is the runtime every app has; anything else is a package the
|
|
94
|
+
// app opts into, so the picker marks it as such.
|
|
95
|
+
let name = t.level === "core" ? t.name : `${t.name} (extension)`
|
|
96
|
+
return { label: t.description ? `${name} - ${t.description}` : name, value: t.name }
|
|
97
|
+
}),
|
|
96
98
|
)
|
|
97
99
|
return templates.find((t) => t.name === picked)!
|
|
98
100
|
}
|
|
@@ -171,4 +173,4 @@ export async function runInitCommand() {
|
|
|
171
173
|
let prefix = dir === "." ? "" : `cd ${dir} && `
|
|
172
174
|
console.log(`\n>> Done. Next:\n ${prefix}bun run dev\n`)
|
|
173
175
|
process.exit()
|
|
174
|
-
}
|
|
176
|
+
}
|
|
Binary file
|
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
render,
|
|
3
|
-
createSignal,
|
|
4
|
-
createEffect,
|
|
5
|
-
untrack,
|
|
6
|
-
env,
|
|
7
|
-
capabilities,
|
|
8
|
-
} from "@solidrt/core"
|
|
9
|
-
import {
|
|
10
|
-
policy,
|
|
11
|
-
setPolicy,
|
|
12
|
-
type DensityPolicy,
|
|
13
|
-
type MotionPolicy,
|
|
14
|
-
Window,
|
|
15
|
-
View,
|
|
16
|
-
Text,
|
|
17
|
-
Image,
|
|
18
|
-
Button,
|
|
19
|
-
TextInput,
|
|
20
|
-
Switch,
|
|
21
|
-
Checkbox,
|
|
22
|
-
RadioGroup,
|
|
23
|
-
Radio,
|
|
24
|
-
Slider,
|
|
25
|
-
QrCode,
|
|
26
|
-
Card,
|
|
27
|
-
Divider,
|
|
28
|
-
Badge,
|
|
29
|
-
Spinner,
|
|
30
|
-
ProgressBar,
|
|
31
|
-
Tooltip,
|
|
32
|
-
Select,
|
|
33
|
-
ContextMenu,
|
|
34
|
-
Icon,
|
|
35
|
-
ScrollView,
|
|
36
|
-
theme,
|
|
37
|
-
space,
|
|
38
|
-
typeWeight,
|
|
39
|
-
setTheme,
|
|
40
|
-
darkTheme,
|
|
41
|
-
lightTheme,
|
|
42
|
-
SafeArea,
|
|
43
|
-
} from "@solidrt/components"
|
|
44
|
-
|
|
45
|
-
import icon from "./icon.png" with { type: "binary" }
|
|
46
|
-
|
|
47
|
-
// A growing gallery of @solidrt/components. The whole thing recolors live when
|
|
48
|
-
// the theme switches: every component reads theme.* reactively, so pressing
|
|
49
|
-
// "Toggle theme" reflows the palette with no remount. The "Environment and
|
|
50
|
-
// policies" card shows the environment -> capabilities -> policies cascade and
|
|
51
|
-
// lets you force density/motion overrides to watch every control adapt.
|
|
52
|
-
|
|
53
|
-
// Lucide icons are SVG documents (strings). In a real app you import them as
|
|
54
|
-
// assets (`import House from "lucide-static/icons/house.svg"`) or from a string
|
|
55
|
-
// export; we inline a few here so the example stays self-contained. Each strokes
|
|
56
|
-
// with currentColor, so Icon recolors them from the theme.
|
|
57
|
-
const LUCIDE = (body: string) =>
|
|
58
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"` +
|
|
59
|
-
` stroke="currentColor" stroke-width="2" stroke-linecap="round"` +
|
|
60
|
-
` stroke-linejoin="round">${body}</svg>`
|
|
61
|
-
const HOUSE = LUCIDE(
|
|
62
|
-
`<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/>` +
|
|
63
|
-
`<path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>`,
|
|
64
|
-
)
|
|
65
|
-
const HEART = LUCIDE(
|
|
66
|
-
`<path d="M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"/>`,
|
|
67
|
-
)
|
|
68
|
-
const STAR = LUCIDE(
|
|
69
|
-
`<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"/>`,
|
|
70
|
-
)
|
|
71
|
-
const SETTINGS = LUCIDE(
|
|
72
|
-
`<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/>` +
|
|
73
|
-
`<circle cx="12" cy="12" r="3"/>`,
|
|
74
|
-
)
|
|
75
|
-
const BELL = LUCIDE(
|
|
76
|
-
`<path d="M10.268 21a2 2 0 0 0 3.464 0"/>` +
|
|
77
|
-
`<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>`,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
function Row(props: { label: string; children?: any }) {
|
|
81
|
-
return (
|
|
82
|
-
<View
|
|
83
|
-
layout={{
|
|
84
|
-
flexDirection: "row",
|
|
85
|
-
alignItems: "center",
|
|
86
|
-
justifyContent: "space-between",
|
|
87
|
-
gap: space("lg"),
|
|
88
|
-
}}
|
|
89
|
-
>
|
|
90
|
-
<Text muted>{props.label}</Text>
|
|
91
|
-
{props.children}
|
|
92
|
-
</View>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function Value(props: { children?: any }) {
|
|
97
|
-
return <Text>{props.children}</Text>
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Joins the names whose flag is true, e.g. list(["mouse", env.mouseSeen], ...).
|
|
101
|
-
function list(...entries: [string, boolean][]) {
|
|
102
|
-
let names = entries.filter(([, on]) => on).map(([name]) => name)
|
|
103
|
-
return names.length > 0 ? names.join(", ") : "none yet"
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function App() {
|
|
107
|
-
let [dark, setDark] = createSignal(true)
|
|
108
|
-
let [name, setName] = createSignal("")
|
|
109
|
-
let [notify, setNotify] = createSignal(true)
|
|
110
|
-
let [agree, setAgree] = createSignal(false)
|
|
111
|
-
let [plan, setPlan] = createSignal<unknown>("pro")
|
|
112
|
-
let [volume, setVolume] = createSignal(40)
|
|
113
|
-
let [fruit, setFruit] = createSignal<unknown>(undefined)
|
|
114
|
-
|
|
115
|
-
// The QR code follows the name field debounced, not per keystroke: each data
|
|
116
|
-
// change regenerates the whole module grid, which is too heavy to run at
|
|
117
|
-
// typing frequency.
|
|
118
|
-
// untrack: seed with the current name once; the debounced effect below owns
|
|
119
|
-
// keeping it in sync, and a bare name() read here would warn (strict mode
|
|
120
|
-
// flags top-level reactive reads in component bodies as one-shot).
|
|
121
|
-
let [qrData, setQrData] = createSignal(untrack(name))
|
|
122
|
-
createEffect(
|
|
123
|
-
() => name(),
|
|
124
|
-
(v) => {
|
|
125
|
-
let timer = setTimeout(() => setQrData(v), 250)
|
|
126
|
-
return () => clearTimeout(timer)
|
|
127
|
-
},
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
// Follow the OS dark/light preference until the user toggles manually; the
|
|
131
|
-
// manual toggle then owns the choice for the rest of the session.
|
|
132
|
-
let userToggledTheme = false
|
|
133
|
-
let toggleTheme = () => {
|
|
134
|
-
userToggledTheme = true
|
|
135
|
-
let next = !dark()
|
|
136
|
-
setDark(next)
|
|
137
|
-
setTheme(next ? darkTheme : lightTheme)
|
|
138
|
-
}
|
|
139
|
-
createEffect(
|
|
140
|
-
() => env.systemTheme,
|
|
141
|
-
(t) => {
|
|
142
|
-
if (userToggledTheme || t === "unknown") return
|
|
143
|
-
let isDark = t === "dark"
|
|
144
|
-
setDark(isDark)
|
|
145
|
-
setTheme(isDark ? darkTheme : lightTheme)
|
|
146
|
-
},
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
// Policy override choices; "auto" hands the policy back to the resolver.
|
|
150
|
-
let [densityChoice, setDensityChoice] = createSignal<unknown>("auto")
|
|
151
|
-
let [motionChoice, setMotionChoice] = createSignal<unknown>("auto")
|
|
152
|
-
let chooseDensity = (v: unknown) => {
|
|
153
|
-
setDensityChoice(v)
|
|
154
|
-
setPolicy({ density: v === "auto" ? undefined : (v as DensityPolicy) })
|
|
155
|
-
}
|
|
156
|
-
let chooseMotion = (v: unknown) => {
|
|
157
|
-
setMotionChoice(v)
|
|
158
|
-
setPolicy({ motion: v === "auto" ? undefined : (v as MotionPolicy) })
|
|
159
|
-
}
|
|
160
|
-
let [textScaleChoice, setTextScaleChoice] = createSignal<unknown>("auto")
|
|
161
|
-
let chooseTextScale = (v: unknown) => {
|
|
162
|
-
setTextScaleChoice(v)
|
|
163
|
-
setPolicy({ textScale: v === "auto" ? undefined : (v as number) })
|
|
164
|
-
}
|
|
165
|
-
let [weightDeltaChoice, setWeightDeltaChoice] = createSignal<unknown>("auto")
|
|
166
|
-
let chooseWeightDelta = (v: unknown) => {
|
|
167
|
-
setWeightDeltaChoice(v)
|
|
168
|
-
setPolicy({ textWeightDelta: v === "auto" ? undefined : (v as number) })
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return (
|
|
172
|
-
<Window
|
|
173
|
-
title="Components gallery"
|
|
174
|
-
layout={{ flexDirection: "column" }}
|
|
175
|
-
style={{ backgroundColor: theme.color.background }}
|
|
176
|
-
>
|
|
177
|
-
<SafeArea>
|
|
178
|
-
<ScrollView horizontal layout={{ flex: 1 }}>
|
|
179
|
-
<View
|
|
180
|
-
layout={{
|
|
181
|
-
height: "100%",
|
|
182
|
-
flexDirection: "column",
|
|
183
|
-
flexWrap: "wrap",
|
|
184
|
-
alignContent: "flex-start",
|
|
185
|
-
justifyContent: "flex-start",
|
|
186
|
-
gap: space("xl"),
|
|
187
|
-
padding: space("xl"),
|
|
188
|
-
}}
|
|
189
|
-
>
|
|
190
|
-
<Card title={dark() ? "Dark theme" : "Light theme"} layout={{ width: 360 }}>
|
|
191
|
-
<Text muted>Toggle to recolor every control below.</Text>
|
|
192
|
-
<Button onPress={toggleTheme}>Toggle theme</Button>
|
|
193
|
-
</Card>
|
|
194
|
-
|
|
195
|
-
<Card title="Environment" layout={{ width: 360 }}>
|
|
196
|
-
<Row label="Window">
|
|
197
|
-
<Value>
|
|
198
|
-
{`${env.windowSize.width} x ${env.windowSize.height} @ ${env.displayScale}x`}
|
|
199
|
-
</Value>
|
|
200
|
-
</Row>
|
|
201
|
-
<Row label="System">
|
|
202
|
-
<Value>{`${env.systemTheme} theme, ${env.orientation}`}</Value>
|
|
203
|
-
</Row>
|
|
204
|
-
<Row label="Devices">
|
|
205
|
-
<Value>
|
|
206
|
-
{env.inputDevices
|
|
207
|
-
? list(
|
|
208
|
-
["mouse", env.inputDevices.mouse],
|
|
209
|
-
["touch", env.inputDevices.touch],
|
|
210
|
-
["keyboard", env.inputDevices.keyboard],
|
|
211
|
-
)
|
|
212
|
-
: "not reported"}
|
|
213
|
-
</Value>
|
|
214
|
-
</Row>
|
|
215
|
-
<Row label="Inputs seen">
|
|
216
|
-
<Value>
|
|
217
|
-
{list(
|
|
218
|
-
["mouse", env.mouseSeen],
|
|
219
|
-
["touch", env.touchSeen],
|
|
220
|
-
["keyboard", env.keyboardSeen],
|
|
221
|
-
)}
|
|
222
|
-
</Value>
|
|
223
|
-
</Row>
|
|
224
|
-
<Row label="Capabilities">
|
|
225
|
-
<Value>
|
|
226
|
-
{list(
|
|
227
|
-
["hover", capabilities.hover],
|
|
228
|
-
["touch", capabilities.touch],
|
|
229
|
-
["keyboard nav", capabilities.keyboardNav],
|
|
230
|
-
)}
|
|
231
|
-
</Value>
|
|
232
|
-
</Row>
|
|
233
|
-
<Row label="Size class">
|
|
234
|
-
<Value>{capabilities.windowSizeClass}</Value>
|
|
235
|
-
</Row>
|
|
236
|
-
</Card>
|
|
237
|
-
|
|
238
|
-
<Card title="Policies" layout={{ width: 360 }}>
|
|
239
|
-
<Row label="Interaction policy">
|
|
240
|
-
<Value>{policy.interaction}</Value>
|
|
241
|
-
</Row>
|
|
242
|
-
<Row label="Focus ring">
|
|
243
|
-
<Value>{policy.focusRing ? "visible" : "hidden"}</Value>
|
|
244
|
-
</Row>
|
|
245
|
-
<Row label="App policies">
|
|
246
|
-
<Value>{`${policy.navigation}, ${policy.layout}`}</Value>
|
|
247
|
-
</Row>
|
|
248
|
-
<Divider />
|
|
249
|
-
<Row label="Density policy">
|
|
250
|
-
<Value>{policy.density}</Value>
|
|
251
|
-
</Row>
|
|
252
|
-
<RadioGroup
|
|
253
|
-
value={densityChoice()}
|
|
254
|
-
onChange={chooseDensity}
|
|
255
|
-
layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
|
|
256
|
-
>
|
|
257
|
-
<Radio value="auto">Auto</Radio>
|
|
258
|
-
<Radio value="comfortable">Comfortable</Radio>
|
|
259
|
-
<Radio value="compact">Compact</Radio>
|
|
260
|
-
<Radio value="dense">Dense</Radio>
|
|
261
|
-
</RadioGroup>
|
|
262
|
-
<Divider />
|
|
263
|
-
<Row label="Motion policy">
|
|
264
|
-
<Value>{policy.motion}</Value>
|
|
265
|
-
</Row>
|
|
266
|
-
<RadioGroup
|
|
267
|
-
value={motionChoice()}
|
|
268
|
-
onChange={chooseMotion}
|
|
269
|
-
layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
|
|
270
|
-
>
|
|
271
|
-
<Radio value="auto">Auto</Radio>
|
|
272
|
-
<Radio value="normal">Normal</Radio>
|
|
273
|
-
<Radio value="reduced">Reduced</Radio>
|
|
274
|
-
<Radio value="none">None</Radio>
|
|
275
|
-
</RadioGroup>
|
|
276
|
-
<Divider />
|
|
277
|
-
<Row label="Text scale">
|
|
278
|
-
<Value>{`${policy.textScale}x`}</Value>
|
|
279
|
-
</Row>
|
|
280
|
-
<RadioGroup
|
|
281
|
-
value={textScaleChoice()}
|
|
282
|
-
onChange={chooseTextScale}
|
|
283
|
-
layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
|
|
284
|
-
>
|
|
285
|
-
<Radio value="auto">Auto</Radio>
|
|
286
|
-
<Radio value={0.9}>0.9</Radio>
|
|
287
|
-
<Radio value={1.0}>1.0</Radio>
|
|
288
|
-
<Radio value={1.1}>1.1</Radio>
|
|
289
|
-
</RadioGroup>
|
|
290
|
-
<Divider />
|
|
291
|
-
<Row label="Text weight delta">
|
|
292
|
-
<Value>
|
|
293
|
-
{`base +${policy.textWeightDelta}, body ${typeWeight(
|
|
294
|
-
theme.text.body.weight,
|
|
295
|
-
theme.text.body.size * policy.textScale,
|
|
296
|
-
)}, title ${typeWeight(theme.text.title.weight, theme.text.title.size * policy.textScale)}`}
|
|
297
|
-
</Value>
|
|
298
|
-
</Row>
|
|
299
|
-
<RadioGroup
|
|
300
|
-
value={weightDeltaChoice()}
|
|
301
|
-
onChange={chooseWeightDelta}
|
|
302
|
-
layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
|
|
303
|
-
>
|
|
304
|
-
<Radio value="auto">Auto</Radio>
|
|
305
|
-
<Radio value={0}>0</Radio>
|
|
306
|
-
<Radio value={100}>+100</Radio>
|
|
307
|
-
<Radio value={200}>+200</Radio>
|
|
308
|
-
</RadioGroup>
|
|
309
|
-
</Card>
|
|
310
|
-
|
|
311
|
-
<Card title="Buttons" layout={{ width: 360 }}>
|
|
312
|
-
<View
|
|
313
|
-
layout={{
|
|
314
|
-
flexDirection: "row",
|
|
315
|
-
flexWrap: "wrap",
|
|
316
|
-
gap: space("md"),
|
|
317
|
-
alignItems: "center",
|
|
318
|
-
}}
|
|
319
|
-
>
|
|
320
|
-
<Button>Primary</Button>
|
|
321
|
-
<Button variant="secondary">Secondary</Button>
|
|
322
|
-
<Button variant="ghost">Ghost</Button>
|
|
323
|
-
<Button variant="danger">Danger</Button>
|
|
324
|
-
<Button disabled>Disabled</Button>
|
|
325
|
-
</View>
|
|
326
|
-
</Card>
|
|
327
|
-
|
|
328
|
-
<Card title="Switch and checkbox" layout={{ width: 360 }}>
|
|
329
|
-
<Row label="Notifications">
|
|
330
|
-
<Switch value={notify()} onChange={setNotify} />
|
|
331
|
-
</Row>
|
|
332
|
-
<Row label="I agree to the terms">
|
|
333
|
-
<Checkbox checked={agree()} onChange={setAgree} />
|
|
334
|
-
</Row>
|
|
335
|
-
</Card>
|
|
336
|
-
|
|
337
|
-
<Card title="Radio group" layout={{ width: 360 }}>
|
|
338
|
-
<RadioGroup value={plan()} onChange={setPlan}>
|
|
339
|
-
<Radio value="free">Free</Radio>
|
|
340
|
-
<Radio value="pro">Pro</Radio>
|
|
341
|
-
<Radio value="team">Team</Radio>
|
|
342
|
-
</RadioGroup>
|
|
343
|
-
</Card>
|
|
344
|
-
|
|
345
|
-
<Card title="Context menu" layout={{ width: 360 }}>
|
|
346
|
-
<Text muted>
|
|
347
|
-
Right-click (mouse) or long-press (touch) the box below. Touch policy presents a
|
|
348
|
-
bottom sheet, desktop and hybrid a menu at the pointer.
|
|
349
|
-
</Text>
|
|
350
|
-
<ContextMenu
|
|
351
|
-
items={[
|
|
352
|
-
{ label: "Copy", onSelect: () => console.log("copy") },
|
|
353
|
-
{ label: "Paste", onSelect: () => console.log("paste") },
|
|
354
|
-
{ label: "Rename", disabled: true },
|
|
355
|
-
{ label: "Delete", onSelect: () => console.log("delete") },
|
|
356
|
-
]}
|
|
357
|
-
>
|
|
358
|
-
<View
|
|
359
|
-
layout={{ padding: space("lg"), alignItems: "center" }}
|
|
360
|
-
style={{
|
|
361
|
-
backgroundColor: theme.color.surfaceAlt,
|
|
362
|
-
borderRadius: theme.radius.sm,
|
|
363
|
-
}}
|
|
364
|
-
>
|
|
365
|
-
<Text>Secondary actions live here</Text>
|
|
366
|
-
</View>
|
|
367
|
-
</ContextMenu>
|
|
368
|
-
</Card>
|
|
369
|
-
|
|
370
|
-
<Card title="Select" layout={{ width: 360 }}>
|
|
371
|
-
<Text muted>
|
|
372
|
-
Forks on the interaction policy: desktop and hybrid anchor a dropdown, touch opens a
|
|
373
|
-
bottom sheet.
|
|
374
|
-
</Text>
|
|
375
|
-
<Select
|
|
376
|
-
value={fruit()}
|
|
377
|
-
onChange={setFruit}
|
|
378
|
-
placeholder="Pick a fruit"
|
|
379
|
-
options={[
|
|
380
|
-
{ value: "apple", label: "Apple" },
|
|
381
|
-
{ value: "banana", label: "Banana" },
|
|
382
|
-
{ value: "cherry", label: "Cherry" },
|
|
383
|
-
{ value: "dragonfruit", label: "Dragonfruit" },
|
|
384
|
-
]}
|
|
385
|
-
layout={{ width: 200 }}
|
|
386
|
-
/>
|
|
387
|
-
</Card>
|
|
388
|
-
|
|
389
|
-
<Card title="Slider" layout={{ width: 360 }}>
|
|
390
|
-
<Row label={`Volume: ${Math.round(volume())}`}>
|
|
391
|
-
<Slider
|
|
392
|
-
value={volume()}
|
|
393
|
-
onChange={setVolume}
|
|
394
|
-
min={0}
|
|
395
|
-
max={100}
|
|
396
|
-
step={1}
|
|
397
|
-
layout={{ width: 180 }}
|
|
398
|
-
/>
|
|
399
|
-
</Row>
|
|
400
|
-
</Card>
|
|
401
|
-
|
|
402
|
-
<Card title="Progress and spinner" layout={{ width: 360 }}>
|
|
403
|
-
<Row label="Loading">
|
|
404
|
-
<Spinner />
|
|
405
|
-
</Row>
|
|
406
|
-
<Text muted>Determinate, tracking the volume slider:</Text>
|
|
407
|
-
<ProgressBar value={volume() / 100} />
|
|
408
|
-
<Text muted>Indeterminate:</Text>
|
|
409
|
-
<ProgressBar />
|
|
410
|
-
</Card>
|
|
411
|
-
|
|
412
|
-
<Card title="Badges and divider" layout={{ width: 360 }}>
|
|
413
|
-
<Row label="Status">
|
|
414
|
-
<View layout={{ flexDirection: "row", gap: space("md"), alignItems: "center" }}>
|
|
415
|
-
<Badge>New</Badge>
|
|
416
|
-
<Badge variant="neutral">3</Badge>
|
|
417
|
-
<Badge variant="danger">Error</Badge>
|
|
418
|
-
</View>
|
|
419
|
-
</Row>
|
|
420
|
-
<Divider />
|
|
421
|
-
<Text muted>A divider separates content within a card.</Text>
|
|
422
|
-
</Card>
|
|
423
|
-
|
|
424
|
-
<Card title="Tooltip" layout={{ width: 360 }}>
|
|
425
|
-
<Text muted>
|
|
426
|
-
Rest the mouse on a button. Shows under desktop and hybrid interaction policies,
|
|
427
|
-
never under touch.
|
|
428
|
-
</Text>
|
|
429
|
-
<View layout={{ flexDirection: "row", gap: space("md") }}>
|
|
430
|
-
<Tooltip content="Saves your changes">
|
|
431
|
-
<Button>Hover me</Button>
|
|
432
|
-
</Tooltip>
|
|
433
|
-
<Tooltip content="Appears below the anchor" placement="bottom">
|
|
434
|
-
<Button>Below</Button>
|
|
435
|
-
</Tooltip>
|
|
436
|
-
</View>
|
|
437
|
-
</Card>
|
|
438
|
-
|
|
439
|
-
<Card title="Icons" layout={{ width: 360 }}>
|
|
440
|
-
<Text muted>
|
|
441
|
-
Lucide SVGs drawn through the core svg primitive. currentColor follows the theme;
|
|
442
|
-
the last two are recolored explicitly.
|
|
443
|
-
</Text>
|
|
444
|
-
<View layout={{ flexDirection: "row", gap: space("lg"), alignItems: "center" }}>
|
|
445
|
-
<Icon src={HOUSE} />
|
|
446
|
-
<Icon src={SETTINGS} />
|
|
447
|
-
<Icon src={BELL} />
|
|
448
|
-
<Icon src={HEART} color={theme.color.danger} />
|
|
449
|
-
<Icon src={STAR} color={theme.color.primary} />
|
|
450
|
-
</View>
|
|
451
|
-
</Card>
|
|
452
|
-
|
|
453
|
-
<Card title="Text input" layout={{ width: 360 }}>
|
|
454
|
-
<TextInput
|
|
455
|
-
value={name()}
|
|
456
|
-
onInput={setName}
|
|
457
|
-
placeholder="Your name"
|
|
458
|
-
layout={{ width: 320 }}
|
|
459
|
-
/>
|
|
460
|
-
</Card>
|
|
461
|
-
|
|
462
|
-
<Card title="QR code" layout={{ width: 360 }}>
|
|
463
|
-
<Text muted>Encodes your name, or a link if the field is empty.</Text>
|
|
464
|
-
<View layout={{ alignItems: "center" }}>
|
|
465
|
-
<QrCode data={qrData() || "https://solidjs.com"} />
|
|
466
|
-
</View>
|
|
467
|
-
</Card>
|
|
468
|
-
|
|
469
|
-
<Card title="Image" layout={{ width: 360 }}>
|
|
470
|
-
<Text muted>
|
|
471
|
-
Fetches, decodes, and uploads an image, then shows it as a GPU texture. Rounded via
|
|
472
|
-
a clipping border radius.
|
|
473
|
-
</Text>
|
|
474
|
-
<View layout={{ alignItems: "center" }}>
|
|
475
|
-
<Image src={icon} style={{ borderRadius: theme.radius.md }} />
|
|
476
|
-
</View>
|
|
477
|
-
</Card>
|
|
478
|
-
</View>
|
|
479
|
-
</ScrollView>
|
|
480
|
-
</SafeArea>
|
|
481
|
-
</Window>
|
|
482
|
-
)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
render(() => <App />)
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Core-only template: built from @solidrt/core primitives.
|
|
2
|
-
import { render, safeArea } from "@solidrt/core"
|
|
3
|
-
|
|
4
|
-
function App() {
|
|
5
|
-
return (
|
|
6
|
-
<window>
|
|
7
|
-
<view flex={1} paddingTop={safeArea().top} paddingBottom={safeArea().bottom}>
|
|
8
|
-
<text>Hello, World!</text>
|
|
9
|
-
</view>
|
|
10
|
-
</window>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
render(() => <App />)
|