@solidrt/core 0.0.23 → 0.0.25
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/jsx-runtime.d.ts +0 -2
- package/package.json +3 -2
- package/src/color.ts +5 -0
- package/src/environment.ts +12 -4
- package/src/gpu.ts +7 -0
- package/src/index.ts +6 -1
- package/src/sound.ts +119 -0
- package/src/types.d.ts +25 -19
- package/src/window.ts +6 -4
package/jsx-runtime.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
ViewProps,
|
|
8
8
|
TextProps,
|
|
9
9
|
TextureProps,
|
|
10
|
-
AudioProps,
|
|
11
10
|
LayoutProps,
|
|
12
11
|
Element as CoreElement,
|
|
13
12
|
ElementChildrenAttribute as CoreElementChildrenAttribute
|
|
@@ -40,7 +39,6 @@ export namespace JSX {
|
|
|
40
39
|
path: PathProps & LayoutProps & ElementRef
|
|
41
40
|
svg: SvgProps & LayoutProps & ElementRef
|
|
42
41
|
texture: TextureProps & LayoutProps & ElementRef
|
|
43
|
-
audio: AudioProps & ElementRef
|
|
44
42
|
"d-view": ViewProps & ElementRef
|
|
45
43
|
"d-rect": RectProps & ElementRef
|
|
46
44
|
"d-oval": OvalProps & ElementRef
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"./gpu": "./src/gpu.ts",
|
|
12
12
|
"./image": "./src/image.ts",
|
|
13
13
|
"./microphone": "./src/microphone.ts",
|
|
14
|
+
"./sound": "./src/sound.ts",
|
|
14
15
|
"./speech-recognition": "./src/speech-recognition.ts",
|
|
15
16
|
"./text-input": "./src/text-input.ts",
|
|
16
17
|
"./jsx-runtime": "./jsx-runtime.d.ts",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"colord": "^2.9.3"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@solidrt/flux-types": "0.0.
|
|
30
|
+
"@solidrt/flux-types": "0.0.25"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@solidjs/signals": "2.0.0-beta.15",
|
package/src/color.ts
CHANGED
|
@@ -43,6 +43,11 @@ type Stop = { offset: number; color: number }
|
|
|
43
43
|
// element's box), so one gradient can be reused on elements of any size. Branded
|
|
44
44
|
// so the renderer can tell it from a solid color string. The object crosses to
|
|
45
45
|
// the runtime as-is and is decoded by key (see properties/paint.rs).
|
|
46
|
+
//
|
|
47
|
+
// These 0..1 coords are deliberately their own normalized space, NOT the pixel/
|
|
48
|
+
// `pct()` length vocabulary used by layout and transformOrigin: a gradient's
|
|
49
|
+
// position is naturally a fraction (like a stop offset), so 0..1 reads cleaner
|
|
50
|
+
// than pct(0)..pct(100). Do not "unify" them onto pct().
|
|
46
51
|
export type Gradient =
|
|
47
52
|
| { readonly __gradient: "linear"; x0: number; y0: number; x1: number; y1: number; stops: Stop[] }
|
|
48
53
|
| { readonly __gradient: "radial"; cx: number; cy: number; r: number; circle: boolean; stops: Stop[] }
|
package/src/environment.ts
CHANGED
|
@@ -10,6 +10,14 @@ import { windowSize, safeArea, displayScale, windowFocused, keyboardHeight } fro
|
|
|
10
10
|
// been seen this session. Seen-flags only ever go from false to true, so a
|
|
11
11
|
// capability derived from them can appear mid-session (e.g. the first mouse
|
|
12
12
|
// move) but never flickers away.
|
|
13
|
+
//
|
|
14
|
+
// `ownedWrite: true` on the signals below: each is lazily created the first
|
|
15
|
+
// time its ensure* function runs, which can happen inside a tracked scope
|
|
16
|
+
// (e.g. a memo's first read of env.inputDevices). Sticky events replay their
|
|
17
|
+
// cached value synchronously on subscribe (srt:events' on()), so that same
|
|
18
|
+
// call can immediately write the signal it just created. That's a legitimate
|
|
19
|
+
// internal-state write, not a stray write escaping a computation, so it opts
|
|
20
|
+
// out of the write-in-owned-scope guard.
|
|
13
21
|
|
|
14
22
|
/** Connected input device classes, as reported by the runtime. */
|
|
15
23
|
export interface InputDevices {
|
|
@@ -26,7 +34,7 @@ let devicesAccessor: (() => InputDevices | undefined) | undefined
|
|
|
26
34
|
|
|
27
35
|
function ensureDevicesState() {
|
|
28
36
|
if (devicesAccessor) return
|
|
29
|
-
let [devices, setDevices] = createSignal<InputDevices | undefined>(undefined)
|
|
37
|
+
let [devices, setDevices] = createSignal<InputDevices | undefined>(undefined, { ownedWrite: true })
|
|
30
38
|
// Sticky: the current state replays on subscribe, so the first read already
|
|
31
39
|
// sees it on runtimes that report devices.
|
|
32
40
|
on("inputDevices", (d: InputDevices) => {
|
|
@@ -39,7 +47,7 @@ let systemThemeAccessor: (() => SystemTheme) | undefined
|
|
|
39
47
|
|
|
40
48
|
function ensureSystemThemeState() {
|
|
41
49
|
if (systemThemeAccessor) return
|
|
42
|
-
let [theme, setTheme] = createSignal<SystemTheme>("unknown")
|
|
50
|
+
let [theme, setTheme] = createSignal<SystemTheme>("unknown", { ownedWrite: true })
|
|
43
51
|
on("systemTheme", (e: { theme?: SystemTheme }) => setTheme(e.theme ?? "unknown"))
|
|
44
52
|
systemThemeAccessor = theme
|
|
45
53
|
}
|
|
@@ -48,7 +56,7 @@ let orientationAccessor: (() => Orientation) | undefined
|
|
|
48
56
|
|
|
49
57
|
function ensureOrientationState() {
|
|
50
58
|
if (orientationAccessor) return
|
|
51
|
-
let [orientation, setOrientation] = createSignal<Orientation>("unknown")
|
|
59
|
+
let [orientation, setOrientation] = createSignal<Orientation>("unknown", { ownedWrite: true })
|
|
52
60
|
on("displayOrientation", (e: { orientation?: Orientation }) => {
|
|
53
61
|
setOrientation(e.orientation ?? "unknown")
|
|
54
62
|
})
|
|
@@ -59,7 +67,7 @@ let textScaleAccessor: (() => number) | undefined
|
|
|
59
67
|
|
|
60
68
|
function ensureTextScaleState() {
|
|
61
69
|
if (textScaleAccessor) return
|
|
62
|
-
let [scale, setScale] = createSignal(1)
|
|
70
|
+
let [scale, setScale] = createSignal(1, { ownedWrite: true })
|
|
63
71
|
// Sticky, like systemTheme. Guard nonsense values: a runtime bug reporting
|
|
64
72
|
// 0 or a negative would otherwise collapse all text.
|
|
65
73
|
on("textScale", (e: { scale?: number }) => {
|
package/src/gpu.ts
CHANGED
|
@@ -19,6 +19,13 @@ import * as gpu from "flux:gpu"
|
|
|
19
19
|
// `<texture params={...}>` when a `<texture>` element is already in the tree.
|
|
20
20
|
export { destroyTexture, setShaderParams, uploadTexture } from "flux:gpu"
|
|
21
21
|
|
|
22
|
+
// captureSnapshot renders a node to a texture and readTexture reads any
|
|
23
|
+
// texture's bytes back. Re-exported raw (no reactive auto-cleanup wrapper):
|
|
24
|
+
// captureSnapshot resolves asynchronously, by which point the reactive owner is
|
|
25
|
+
// no longer current, so the caller owns the returned id and frees it with
|
|
26
|
+
// destroyTexture (as with any texture created after an await).
|
|
27
|
+
export { captureSnapshot, readTexture } from "flux:gpu"
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* Uploads raw RGBA8 pixels to an immutable GPU texture and returns its id (use
|
|
24
31
|
* it as `<texture src={id} />`). `data` must be exactly `width * height * 4`
|
package/src/index.ts
CHANGED
|
@@ -32,11 +32,16 @@ export type {
|
|
|
32
32
|
PathProps,
|
|
33
33
|
TextProps,
|
|
34
34
|
TextureProps,
|
|
35
|
-
AudioProps,
|
|
36
35
|
Color,
|
|
36
|
+
Pct,
|
|
37
37
|
} from "./types"
|
|
38
38
|
export type { MeasureTextOptions } from "flux:rendertree"
|
|
39
39
|
|
|
40
|
+
// A percentage value for dimensional props (e.g. transformOrigin): `pct(50)` is
|
|
41
|
+
// half the element box. Keeps percentages a first-class branded value rather
|
|
42
|
+
// than a string that has to be parsed - a bare number stays pixels.
|
|
43
|
+
export let pct = (v: number): import("./types").Pct => ({ __unit: "pct", v })
|
|
44
|
+
|
|
40
45
|
// --- Authoring-surface re-exports -------------------------------------------
|
|
41
46
|
// A SolidRT app is built from three substrate packages: @solidjs/signals
|
|
42
47
|
// (reactivity), solid-js (control-flow components), and @solidjs/universal (the
|
package/src/sound.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Sound playback, reactive (SolidJS) layer. `createSound` decodes an encoded
|
|
2
|
+
// clip (Ogg/Vorbis or WAV) once and owns its lifecycle: the decoded clip is
|
|
3
|
+
// released, and any playing voices stopped, when the reactive owner is disposed.
|
|
4
|
+
// Each play() is cheap (no re-decode). `createSoundStream` is the same but reads
|
|
5
|
+
// a large track from a file path on demand instead of decoding it into memory.
|
|
6
|
+
//
|
|
7
|
+
// The imperative primitive lives in the `flux:audio` module; import
|
|
8
|
+
// { play, load, stream } from "flux:audio" for non-reactive use.
|
|
9
|
+
|
|
10
|
+
import { createSignal, onCleanup } from "@solidjs/signals"
|
|
11
|
+
import { load, stream } from "flux:audio"
|
|
12
|
+
import { file } from "flux:fs"
|
|
13
|
+
|
|
14
|
+
type FluxFile = ReturnType<typeof file>
|
|
15
|
+
|
|
16
|
+
type LoadedSound = ReturnType<typeof load>
|
|
17
|
+
|
|
18
|
+
export type SoundOptions = {
|
|
19
|
+
/** Repeat the clip until stopped. Defaults to false. */
|
|
20
|
+
loop?: boolean
|
|
21
|
+
/** Volume scale, 1.0 leaves the clip unchanged. Defaults to 1.0. */
|
|
22
|
+
gain?: number
|
|
23
|
+
/**
|
|
24
|
+
* Let play() stack overlapping voices instead of restarting. Defaults to
|
|
25
|
+
* true: rapid triggers overlap. Set false for a single-voice sound where each
|
|
26
|
+
* play() cuts off the previous one.
|
|
27
|
+
*/
|
|
28
|
+
overlap?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Options for a streamed sound. Streams are always single-voice. */
|
|
32
|
+
export type SoundStreamOptions = {
|
|
33
|
+
/** Repeat the track until stopped. Defaults to false. */
|
|
34
|
+
loop?: boolean
|
|
35
|
+
/** Volume scale, 1.0 leaves the track unchanged. Defaults to 1.0. */
|
|
36
|
+
gain?: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A decoded sound with reactive lifecycle. */
|
|
40
|
+
export type Sound = {
|
|
41
|
+
/** Start the clip. Overlaps or restarts per the `overlap` option. */
|
|
42
|
+
play(): void
|
|
43
|
+
/** Stop every voice started from this sound. */
|
|
44
|
+
stop(): void
|
|
45
|
+
/** True after play() until stop() (does not track natural completion). */
|
|
46
|
+
playing(): boolean
|
|
47
|
+
/** Set if loading failed. */
|
|
48
|
+
error(): Error | undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Shared reactive wrapper: owns the loaded handle, tracks live voices, and
|
|
52
|
+
// disposes both on cleanup. `loader` runs once (may throw -> error signal).
|
|
53
|
+
function reactiveSound(
|
|
54
|
+
loader: () => LoadedSound,
|
|
55
|
+
overlap: boolean,
|
|
56
|
+
playOptions: { loop?: boolean; gain?: number },
|
|
57
|
+
): Sound {
|
|
58
|
+
let [error, setError] = createSignal<Error | undefined>(undefined, { ownedWrite: true })
|
|
59
|
+
let [playing, setPlaying] = createSignal(false, { ownedWrite: true })
|
|
60
|
+
|
|
61
|
+
let handle: LoadedSound | undefined
|
|
62
|
+
let voices: { stop(): void }[] = []
|
|
63
|
+
try {
|
|
64
|
+
handle = loader()
|
|
65
|
+
} catch (e) {
|
|
66
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let stopAll = () => {
|
|
70
|
+
for (let v of voices) v.stop()
|
|
71
|
+
voices = []
|
|
72
|
+
setPlaying(false)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
onCleanup(() => {
|
|
76
|
+
stopAll()
|
|
77
|
+
if (handle) {
|
|
78
|
+
handle.unload()
|
|
79
|
+
handle = undefined
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
play() {
|
|
85
|
+
if (!handle) return
|
|
86
|
+
if (!overlap) stopAll()
|
|
87
|
+
voices.push(handle.play(playOptions))
|
|
88
|
+
setPlaying(true)
|
|
89
|
+
},
|
|
90
|
+
stop: stopAll,
|
|
91
|
+
playing,
|
|
92
|
+
error,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Decodes a sound once and owns its lifecycle: releases the clip and stops its
|
|
98
|
+
* voices when the reactive owner is disposed. play() replays without decoding.
|
|
99
|
+
* For imperative use, call load()/play() from "flux:audio".
|
|
100
|
+
*/
|
|
101
|
+
export function createSound(source: Uint8Array, options: SoundOptions = {}): Sound {
|
|
102
|
+
return reactiveSound(() => load(source), options.overlap ?? true, {
|
|
103
|
+
loop: options.loop,
|
|
104
|
+
gain: options.gain,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Streams a large track, decoding on demand instead of loading it into memory.
|
|
110
|
+
* Single-voice: each play() restarts it. Pass a path (resolved like flux:fs,
|
|
111
|
+
* relative to the process cwd) or a `file()` from flux:fs; a path is wrapped in
|
|
112
|
+
* `file()` for you, so a dev-server-proxied file streams over the proxy. Owns
|
|
113
|
+
* the stream's lifecycle: stopped and released when the reactive owner is
|
|
114
|
+
* disposed. For imperative use, call stream()/play() from "flux:audio".
|
|
115
|
+
*/
|
|
116
|
+
export function createSoundStream(source: string | FluxFile, options: SoundStreamOptions = {}): Sound {
|
|
117
|
+
let src = typeof source === "string" ? file(source) : source
|
|
118
|
+
return reactiveSound(() => stream(src), false, { loop: options.loop, gain: options.gain })
|
|
119
|
+
}
|
package/src/types.d.ts
CHANGED
|
@@ -44,9 +44,9 @@ type Children = Element
|
|
|
44
44
|
// alone (e.g. shorthand-vs-longhand precedence, a subsetted value range).
|
|
45
45
|
|
|
46
46
|
interface FlexboxProps {
|
|
47
|
-
gap?:
|
|
48
|
-
rowGap?:
|
|
49
|
-
columnGap?:
|
|
47
|
+
gap?: LengthPercentage
|
|
48
|
+
rowGap?: LengthPercentage
|
|
49
|
+
columnGap?: LengthPercentage
|
|
50
50
|
/** Shorthand; overridden by flexGrow/flexShrink/flexBasis when they're also set. */
|
|
51
51
|
flex?: number | "none" | "auto" | (string & {})
|
|
52
52
|
flexGrow?: number
|
|
@@ -74,8 +74,14 @@ interface GridProps {
|
|
|
74
74
|
gridTemplateRows?: string
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
77
|
+
/**
|
|
78
|
+
* A layout length: a bare number is pixels, `pct(n)` is a percentage of the
|
|
79
|
+
* containing block, plus "auto" and the `"50%"` string form (kept for paste).
|
|
80
|
+
*/
|
|
81
|
+
type Dimension = number | Pct | "auto" | `${number}%`
|
|
82
|
+
|
|
83
|
+
/** Like {@link Dimension} without "auto" (e.g. gap, which has no auto value). */
|
|
84
|
+
type LengthPercentage = number | Pct | `${number}%`
|
|
79
85
|
|
|
80
86
|
export interface LayoutProps extends FlexboxProps, GridProps {
|
|
81
87
|
display?: "block" | "flex" | "grid" | "none"
|
|
@@ -131,6 +137,16 @@ export interface PaintProps {
|
|
|
131
137
|
strokeWidth?: number
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
/** A percentage value, from `pct(50)`. Resolves against the element box. */
|
|
141
|
+
export type Pct = { readonly __unit: "pct"; v: number }
|
|
142
|
+
|
|
143
|
+
// One axis of the transform origin (the point rotate/scale/3D pivot around),
|
|
144
|
+
// split per axis to match the engine's x/y prop convention. A bare number is
|
|
145
|
+
// pixels; `pct(50)` is a fraction of the box, so a percentage origin tracks the
|
|
146
|
+
// layout size with no reactive wiring. Unset defaults to the axis center.
|
|
147
|
+
type OriginX = number | Pct | "left" | "center" | "right"
|
|
148
|
+
type OriginY = number | Pct | "top" | "center" | "bottom"
|
|
149
|
+
|
|
134
150
|
export interface TransformProps {
|
|
135
151
|
rotate?: number
|
|
136
152
|
scale?: number
|
|
@@ -149,8 +165,8 @@ export interface TransformProps {
|
|
|
149
165
|
perspective?: number
|
|
150
166
|
x?: number
|
|
151
167
|
y?: number
|
|
152
|
-
|
|
153
|
-
|
|
168
|
+
originX?: OriginX
|
|
169
|
+
originY?: OriginY
|
|
154
170
|
// Group opacity in 0..1: children are composited together, then faded as a
|
|
155
171
|
// whole (CSS `opacity`). Does not affect hit testing.
|
|
156
172
|
opacity?: number
|
|
@@ -212,7 +228,7 @@ interface Position {
|
|
|
212
228
|
|
|
213
229
|
// Primitives
|
|
214
230
|
|
|
215
|
-
export interface WindowProps extends LayoutProps {
|
|
231
|
+
export interface WindowProps extends LayoutProps, PointerProps {
|
|
216
232
|
children?: Children
|
|
217
233
|
title?: string
|
|
218
234
|
fullscreen?: boolean
|
|
@@ -242,16 +258,6 @@ export interface ViewProps extends LayoutProps, TransformProps, PointerProps {
|
|
|
242
258
|
repaintBoundary?: boolean | "snapshot"
|
|
243
259
|
}
|
|
244
260
|
|
|
245
|
-
/**
|
|
246
|
-
* Not implemented: there is no native `audio` element kind yet, so using
|
|
247
|
-
* `<audio>` panics ("unknown node kind: audio"). Typed ahead of the backing
|
|
248
|
-
* work; treat this interface as a plan, not a working API.
|
|
249
|
-
*/
|
|
250
|
-
export interface AudioProps {
|
|
251
|
-
src?: Uint8Array
|
|
252
|
-
play?: number
|
|
253
|
-
}
|
|
254
|
-
|
|
255
261
|
// draw primitives
|
|
256
262
|
|
|
257
263
|
export interface RectProps extends Position, PaintProps, PointerProps {
|
|
@@ -309,7 +315,7 @@ export interface TextProps extends Position, PaintProps, PointerProps {
|
|
|
309
315
|
maxLines?: number
|
|
310
316
|
}
|
|
311
317
|
|
|
312
|
-
export interface TextureProps extends Position {
|
|
318
|
+
export interface TextureProps extends Position, PointerProps {
|
|
313
319
|
src?: number
|
|
314
320
|
w?: number
|
|
315
321
|
h?: number
|
package/src/window.ts
CHANGED
|
@@ -93,7 +93,9 @@ export function onResize(fn: (data: ResizeEvent) => void) {
|
|
|
93
93
|
// Singleton accessors over the same events as onResize / onWindowFocus. There
|
|
94
94
|
// is one window, so these are bare accessors rather than a createX instance.
|
|
95
95
|
// Lazily subscribed on first read (resize is sticky, so the first read sees the
|
|
96
|
-
// current value); app-lifetime, so no onCleanup.
|
|
96
|
+
// current value); app-lifetime, so no onCleanup. `ownedWrite: true` on the
|
|
97
|
+
// signals below is the sticky-replay-into-a-tracked-scope case explained on
|
|
98
|
+
// the ensure* functions in environment.ts.
|
|
97
99
|
|
|
98
100
|
let sizeAccessor: (() => { width: number; height: number }) | undefined
|
|
99
101
|
let safeAreaAccessor: (() => SafeArea) | undefined
|
|
@@ -101,9 +103,9 @@ let displayScaleAccessor: (() => number) | undefined
|
|
|
101
103
|
|
|
102
104
|
function ensureResizeState() {
|
|
103
105
|
if (sizeAccessor) return
|
|
104
|
-
let [size, setSize] = createSignal({ width: 0, height: 0 })
|
|
105
|
-
let [safe, setSafe] = createSignal<SafeArea>({ top: 0, left: 0, right: 0, bottom: 0 })
|
|
106
|
-
let [scale, setScale] = createSignal(1)
|
|
106
|
+
let [size, setSize] = createSignal({ width: 0, height: 0 }, { ownedWrite: true })
|
|
107
|
+
let [safe, setSafe] = createSignal<SafeArea>({ top: 0, left: 0, right: 0, bottom: 0 }, { ownedWrite: true })
|
|
108
|
+
let [scale, setScale] = createSignal(1, { ownedWrite: true })
|
|
107
109
|
on("resize", (e: ResizeEvent) => {
|
|
108
110
|
setSize({ width: e.width, height: e.height })
|
|
109
111
|
setSafe(e.safeArea)
|