@solidrt/core 0.0.8 → 0.0.10

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/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
7
7
  "main": "src/index.ts",
8
8
  "exports": {
9
9
  ".": "./src/index.ts",
10
+ "./camera": "./src/camera.ts",
10
11
  "./gpu": "./src/gpu.ts",
12
+ "./microphone": "./src/microphone.ts",
13
+ "./speech": "./src/speech.ts",
11
14
  "./jsx-runtime": "./jsx-runtime.d.ts",
12
15
  "./jsx-runtime-dev": "./jsx-runtime.d.ts"
13
16
  },
@@ -20,7 +23,7 @@
20
23
  "colord": "^2.9.3"
21
24
  },
22
25
  "devDependencies": {
23
- "@solidrt/flux-types": "0.0.8"
26
+ "@solidrt/flux-types": "0.0.10"
24
27
  },
25
28
  "peerDependencies": {
26
29
  "@solidjs/signals": "2.0.0-beta.14",
@@ -0,0 +1,55 @@
1
+ import { createSignal, onCleanup } from "@solidjs/signals"
2
+ import { openCamera, type BarcodeResult, type Camera } from "./camera"
3
+
4
+ // Convenience viewfinder over openCamera: opens on mount, renders the stream
5
+ // texture, closes on cleanup. Use openCamera directly for anything it does not
6
+ // cover; this is just composition, no extra capability.
7
+
8
+ export interface CameraViewProps {
9
+ /** Explicit device id from listCameras(); takes precedence over facing. */
10
+ camera?: number
11
+ facing?: "front" | "back"
12
+ /**
13
+ * Size hint for the stream and explicit size of the view. Omit height to
14
+ * follow the stream's aspect ratio, which can flip when a phone rotates.
15
+ */
16
+ width?: number
17
+ height?: number
18
+ scan?: "qr"[]
19
+ onReady?: (cam: Camera) => void
20
+ onError?: (error: Error) => void
21
+ onBarcode?: (result: BarcodeResult) => void
22
+ }
23
+
24
+ export function CameraView(props: CameraViewProps) {
25
+ let [texture, setTexture] = createSignal<number | undefined>(undefined)
26
+ let cam: Camera | undefined
27
+ let disposed = false
28
+
29
+ openCamera({
30
+ camera: props.camera,
31
+ facing: props.facing,
32
+ width: props.width,
33
+ height: props.height,
34
+ scan: props.scan,
35
+ })
36
+ .then((opened) => {
37
+ if (disposed) {
38
+ opened.close()
39
+ return
40
+ }
41
+ cam = opened
42
+ if (props.onBarcode) opened.onBarcode(props.onBarcode)
43
+ setTexture(opened.texture)
44
+ props.onReady?.(opened)
45
+ })
46
+ .catch((e) => props.onError?.(e instanceof Error ? e : new Error(String(e))))
47
+
48
+ onCleanup(() => {
49
+ disposed = true
50
+ cam?.close()
51
+ cam = undefined
52
+ })
53
+
54
+ return <texture src={texture()} width={props.width} height={props.height} />
55
+ }
package/src/camera.ts ADDED
@@ -0,0 +1,78 @@
1
+ // Camera capture. A camera streams into a texture id, so a viewfinder is just
2
+ // <texture src={cam.texture} />. Opening the camera IS the permission request
3
+ // (SDL semantics): the promise resolves once the stream is configured and
4
+ // rejects if the user denies access.
5
+
6
+ import { on } from "srt:events"
7
+
8
+ export type CameraFacing = "front" | "back" | "unknown"
9
+
10
+ export type CameraInfo = {
11
+ id: number
12
+ name: string
13
+ facing: CameraFacing
14
+ }
15
+
16
+ export type BarcodeFormat = "qr"
17
+
18
+ export type BarcodeResult = {
19
+ data: string
20
+ format: BarcodeFormat
21
+ }
22
+
23
+ export type CameraOptions = {
24
+ /** Explicit device id from listCameras(); takes precedence over facing. */
25
+ camera?: number
26
+ /** Pick the first camera with this facing (falls back to the first camera). */
27
+ facing?: "front" | "back"
28
+ /** Size hint; the device picks the closest supported mode. */
29
+ width?: number
30
+ height?: number
31
+ /** Decode these barcode formats from the stream (delivered via onBarcode). */
32
+ scan?: BarcodeFormat[]
33
+ }
34
+
35
+ export type Camera = {
36
+ /** Texture id updated every frame while open; render with <texture src={...}>. */
37
+ texture: number
38
+ /** Actual stream size (may differ from the requested hint). */
39
+ width: number
40
+ height: number
41
+ /** Receive decoded barcodes (requires the scan option; replaces any previous callback). */
42
+ onBarcode(callback: (result: BarcodeResult) => void): void
43
+ /** Release the device. The texture keeps showing the last frame. */
44
+ close(): void
45
+ }
46
+
47
+ export function listCameras(): CameraInfo[] {
48
+ return camera.listCameras()
49
+ }
50
+
51
+ // Camera hotplug. Re-enumerate with listCameras() to see the new device set.
52
+ // Events only flow once the camera subsystem is up, i.e. after the first
53
+ // listCameras() or openCamera() call. Returns an unsubscribe function.
54
+ // Coverage caveat (SDL 3.4.8): only Android delivers both add and remove. On
55
+ // Linux you get added=true but not added=false (removal is broken upstream);
56
+ // on macOS/Windows there is no camera hotplug at all, so nothing fires.
57
+ export function onDeviceChange(callback: (event: { added: boolean }) => void): () => void {
58
+ return on("cameraDeviceChange", callback)
59
+ }
60
+
61
+ // One-shot scan of an RGBA8 pixel buffer for QR codes; composes with
62
+ // decodeImage: scanBarcodes(img.data, img.width, img.height).
63
+ export function scanBarcodes(data: Uint8Array, width: number, height: number): BarcodeResult[] {
64
+ return camera.scanImage(data, width, height)
65
+ }
66
+
67
+ export async function openCamera(options: CameraOptions = {}): Promise<Camera> {
68
+ let opened = await camera.open(options)
69
+ return {
70
+ texture: opened.texture,
71
+ width: opened.width,
72
+ height: opened.height,
73
+ onBarcode: (callback: (result: BarcodeResult) => void) => camera.setBarcodeCallback(opened.handle, callback),
74
+ close: () => camera.close(opened.handle),
75
+ }
76
+ }
77
+
78
+ export { CameraView, type CameraViewProps } from "./camera-view"
package/src/gpu.ts CHANGED
@@ -10,4 +10,14 @@ export function decodeImage(bytes: Uint8Array): DecodedImage {
10
10
 
11
11
  export function createTexture(data: Uint8Array, width: number, height: number): number {
12
12
  return gpu.createTexture(data, width, height)
13
+ }
14
+
15
+ // The texture keeps reading from `data` (which may hold multiple frames):
16
+ // mutate it in place, then call uploadTexture to push the pixels to the GPU.
17
+ export function createMutableTexture(data: Uint8Array, width: number, height: number): number {
18
+ return gpu.createMutableTexture(data, width, height)
19
+ }
20
+
21
+ export function uploadTexture(textureId: number, offset: number = 0): void {
22
+ gpu.uploadTexture(textureId, offset)
13
23
  }
@@ -0,0 +1,40 @@
1
+ // Microphone capture. A session delivers raw mono float32 samples at the
2
+ // requested sample rate (the device format is converted by SDL); read()
3
+ // drains whatever was captured since the last call. Captured audio buffers
4
+ // until read, so poll read() regularly (e.g. once per frame) while open.
5
+
6
+ export type MicrophoneInfo = {
7
+ id: number
8
+ name: string
9
+ }
10
+
11
+ export type MicrophoneOptions = {
12
+ /** Explicit device id from listMicrophones(); default is the system default recording device. */
13
+ microphone?: number
14
+ /** Sample rate of the delivered samples (the device rate is converted). Default 16000. */
15
+ sampleRate?: number
16
+ }
17
+
18
+ export type Microphone = {
19
+ /** Sample rate of read() samples. */
20
+ sampleRate: number
21
+ /** Drain the mono float32 samples captured since the last read. */
22
+ read(): Float32Array
23
+ /** Release the device. */
24
+ close(): void
25
+ }
26
+
27
+ export function listMicrophones(): MicrophoneInfo[] {
28
+ return microphone.listMicrophones()
29
+ }
30
+
31
+ // Async to leave room for an OS permission prompt on platforms that need one
32
+ // (the desktop backends open synchronously).
33
+ export async function openMicrophone(options: MicrophoneOptions = {}): Promise<Microphone> {
34
+ let opened = microphone.open(options)
35
+ return {
36
+ sampleRate: opened.sampleRate,
37
+ read: () => microphone.read(opened.handle),
38
+ close: () => microphone.close(opened.handle),
39
+ }
40
+ }
package/src/speech.ts ADDED
@@ -0,0 +1,67 @@
1
+ // Speech recognition. A session captures the microphone, segments utterances
2
+ // by silence (Silero VAD) and transcribes each one with Whisper, delivering
3
+ // final transcripts through onResult. With wakeWord the session starts
4
+ // asleep behind an efficient wake word detector (livekit-wakeword) and only
5
+ // transcribes after the wake word. startRecognition resolves once the models
6
+ // are loaded and listening has begun; it rejects when loading fails.
7
+ // Models are passed as bytes so any source composes: flux:fs file(), fetch
8
+ // (incl. the dev-server file proxy), or a download cache layered on top.
9
+ // Requires a runtime built with speech support.
10
+
11
+ export type SpeechOptions = {
12
+ /** A ggml Whisper model (file contents, e.g. ggml-tiny.en.bin). */
13
+ model: Uint8Array
14
+ /** A ggml Silero VAD model (file contents). */
15
+ vadModel: Uint8Array
16
+ /** Whisper language code; "auto" detects (multilingual models only). Default "en". */
17
+ language?: string
18
+ /** Explicit microphone device id from listMicrophones(). */
19
+ microphone?: number
20
+ /** Stop automatically after the first final result (with wakeWord: re-arm instead, one result per wake). */
21
+ singleUtterance?: boolean
22
+ /** Also deliver snapshot transcripts (final: false) while an utterance is still being spoken. */
23
+ interimResults?: boolean
24
+ /**
25
+ * Wake word: start asleep, fire onWake when it is heard, then transcribe
26
+ * the speech that follows. How the wake word is specified depends on the
27
+ * engine. The current engine detects with a trained classifier and takes
28
+ * the model's bytes (livekit-wakeword ONNX, e.g. the pretrained "hey
29
+ * livekit"; custom phrases are trained offline with its toolkit). Phrase
30
+ * strings are reserved for engines that match text; passing them to this
31
+ * engine rejects with an error.
32
+ */
33
+ wakeWord?: Uint8Array | string | string[]
34
+ /** Detector confidence (0..1) that counts as a wake. Default 0.5. */
35
+ wakeThreshold?: number
36
+ }
37
+
38
+ export type SpeechResult = {
39
+ /** Transcript of the utterance (a snapshot of it when final is false). */
40
+ text: string
41
+ /** True for the completed utterance, false for interim snapshots. */
42
+ final: boolean
43
+ }
44
+
45
+ export type SpeechSession = {
46
+ /** Receive transcripts (replaces any previous callback). */
47
+ onResult(callback: (result: SpeechResult) => void): void
48
+ /** The user started speaking (replaces any previous callback). */
49
+ onSpeechStart(callback: () => void): void
50
+ /** The utterance ended; its final result follows once transcribed. */
51
+ onSpeechEnd(callback: () => void): void
52
+ /** The wake word was heard (wakeWordModel sessions only). */
53
+ onWake(callback: () => void): void
54
+ /** Release the microphone and discard any utterance in progress. */
55
+ stop(): void
56
+ }
57
+
58
+ export async function startRecognition(options: SpeechOptions): Promise<SpeechSession> {
59
+ let started = await speech.start(options)
60
+ return {
61
+ onResult: (callback: (result: SpeechResult) => void) => speech.setResultCallback(started.handle, callback),
62
+ onSpeechStart: (callback: () => void) => speech.setSpeechStartCallback(started.handle, callback),
63
+ onSpeechEnd: (callback: () => void) => speech.setSpeechEndCallback(started.handle, callback),
64
+ onWake: (callback: () => void) => speech.setWakeCallback(started.handle, callback),
65
+ stop: () => speech.stop(started.handle),
66
+ }
67
+ }
package/src/types.d.ts CHANGED
@@ -2,16 +2,28 @@
2
2
 
3
3
  import type { JSX as SolidJSX } from "@solidjs/signals"
4
4
 
5
+ // UI event bus (lattice), provided by the runtime as a builtin module.
6
+ // on/once return an unsubscribe function.
7
+ declare module "srt:events" {
8
+ export function on(event: string, callback: (data: any) => void): () => void
9
+ export function once(event: string, callback: (data: any) => void): () => void
10
+ }
11
+
12
+ // Dev-server control surface (lattice). Present only in dev/go builds; in other
13
+ // builds `available` is false and the functions are no-ops.
14
+ declare module "srt:dev" {
15
+ export const available: boolean
16
+ export const canDiscover: boolean
17
+ export const recents: string[]
18
+ export function connect(address: string): void
19
+ export function discover(): void
20
+ export function stop(): void
21
+ }
22
+
5
23
  declare global {
6
24
  function requestAnimationFrame(callback: (time: number) => void): number
7
25
  function cancelAnimationFrame(id: number): void
8
26
 
9
- // UI event bus (lattice). on/once return an unsubscribe function.
10
- let srt: {
11
- on(event: string, callback: (data: any) => void): () => void
12
- once(event: string, callback: (data: any) => void): () => void
13
- }
14
-
15
27
  let ffi: {
16
28
  createRoot(id: number): void
17
29
  createNode(id: number, kind: string): void
@@ -19,14 +31,45 @@ declare global {
19
31
  deleteNode(parentId: number, nodeId: number): void
20
32
  setProperty(nodeId: number, name: string, value: unknown): void
21
33
  setTextInputActive(active: boolean): void
34
+ requestFrame(): void
22
35
  measureText(text: string, options?: MeasureTextOptions): { width: number, height: number }
23
36
  getBoundingBox(id: number): { x: number, y: number, width: number, height: number } | null
24
37
  }
25
38
 
26
39
  let gpu: {
27
40
  createTexture(data: Uint8Array, width: number, height: number): number
41
+ createMutableTexture(data: Uint8Array, width: number, height: number): number
42
+ uploadTexture(textureId: number, offset?: number): void
28
43
  decodeImage(bytes: Uint8Array): { data: Uint8Array, width: number, height: number }
29
44
  }
45
+
46
+ let camera: {
47
+ listCameras(): { id: number, name: string, facing: "front" | "back" | "unknown" }[]
48
+ open(options: { camera?: number, facing?: "front" | "back", width?: number, height?: number, scan?: string[] }):
49
+ Promise<{ handle: number, texture: number, width: number, height: number }>
50
+ setBarcodeCallback(handle: number, callback: (result: { data: string, format: "qr" }) => void): void
51
+ scanImage(data: Uint8Array, width: number, height: number): { data: string, format: "qr" }[]
52
+ close(handle: number): void
53
+ }
54
+
55
+ let microphone: {
56
+ listMicrophones(): { id: number, name: string }[]
57
+ open(options: { microphone?: number, sampleRate?: number }): { handle: number, sampleRate: number }
58
+ read(handle: number): Float32Array
59
+ close(handle: number): void
60
+ }
61
+
62
+ let speech: {
63
+ start(options: {
64
+ model: Uint8Array, vadModel: Uint8Array, language?: string, microphone?: number,
65
+ singleUtterance?: boolean, interimResults?: boolean, wakeWord?: Uint8Array | string | string[], wakeThreshold?: number,
66
+ }): Promise<{ handle: number }>
67
+ setResultCallback(handle: number, callback: (result: { text: string, final: boolean }) => void): void
68
+ setSpeechStartCallback(handle: number, callback: () => void): void
69
+ setSpeechEndCallback(handle: number, callback: () => void): void
70
+ setWakeCallback(handle: number, callback: () => void): void
71
+ stop(handle: number): void
72
+ }
30
73
  }
31
74
 
32
75
  export interface MeasureTextOptions {
@@ -119,6 +162,19 @@ export interface PaintProps {
119
162
  export interface TransformProps {
120
163
  rotate?: number
121
164
  scale?: number
165
+ // Per-axis scale; overrides `scale` on that axis (e.g. scaleX for a flip).
166
+ scaleX?: number
167
+ scaleY?: number
168
+ // 3D rotation about the horizontal axis, in radians (a top/bottom tilt). Like
169
+ // rotateY, reads as 3D only with `perspective` set.
170
+ rotateX?: number
171
+ // 3D rotation about the vertical axis, in radians, for a card-flip. Reads as a
172
+ // real flip only with `perspective` set; on its own it is an orthographic
173
+ // squash (like scaleX).
174
+ rotateY?: number
175
+ // Perspective viewing distance in pixels (CSS `perspective`). Enables the 3D
176
+ // depth for rotateY; larger values give a shallower effect.
177
+ perspective?: number
122
178
  x?: number
123
179
  y?: number
124
180
  cx?: number
@@ -180,6 +236,25 @@ export interface WindowProps extends LayoutProps {
180
236
  export interface ViewProps extends LayoutProps, TransformProps, PointerProps {
181
237
  children?: Children
182
238
  trace?: boolean
239
+ /**
240
+ * Corner radii for the clip applied when overflow is non-visible (hidden,
241
+ * clip, scroll on both axes). A single number rounds all four corners; an
242
+ * array is [top-left, top-right, bottom-right, bottom-left]. Without overflow
243
+ * clipping this has no effect.
244
+ */
245
+ clipRadius?: number | [number, number, number, number]
246
+ /**
247
+ * Marks a repaint boundary: the subtree is recorded into its own retained
248
+ * display list and reused until something inside it changes. Place around
249
+ * heavy static content that sits next to frequently changing content.
250
+ *
251
+ * "snapshot" additionally retains the rasterized pixels as a GPU texture,
252
+ * skipping rasterization entirely. Costs texture memory and re-rasterizes
253
+ * on layout-size or display-scale changes. Content painted outside the
254
+ * element's layout box is cropped, and ancestor scale animations smear the
255
+ * bitmap; best for screen-aligned, static, raster-expensive content.
256
+ */
257
+ repaintBoundary?: boolean | "snapshot"
183
258
  }
184
259
 
185
260
  export interface AudioProps {
package/src/window.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { onCleanup, onSettled, flush } from "@solidjs/signals"
2
+ import { on, once } from "srt:events"
2
3
  import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
3
4
 
4
5
  // ------ Animation frames ----------------
@@ -26,10 +27,13 @@ export function onFrame(fn: (tick: number, frame: number, rate: number) => void)
26
27
  fn(tick, frame, rate)
27
28
  frameId = nextFrameId++
28
29
  animationFrames.set(frameId, extendedFn)
30
+ // A pending onFrame callback is a standing request for the next frame.
31
+ ffi.requestFrame()
29
32
  }
30
33
 
31
34
  frameId = nextFrameId++
32
35
  animationFrames.set(frameId, extendedFn)
36
+ ffi.requestFrame()
33
37
 
34
38
  let cleanup = () => animationFrames.delete(frameId)
35
39
  onCleanup(cleanup)
@@ -53,7 +57,7 @@ interface ResizeEvent {
53
57
  }
54
58
 
55
59
  export function onResize(fn: (data: ResizeEvent) => void) {
56
- let unsubscribe = srt.on("resize", fn)
60
+ let unsubscribe = on("resize", fn)
57
61
  onCleanup(unsubscribe)
58
62
  return unsubscribe
59
63
  }
@@ -63,19 +67,19 @@ export function onResize(fn: (data: ResizeEvent) => void) {
63
67
  // by a re-layout pass before painting (one extra pass; cascades beyond that
64
68
  // paint stale).
65
69
  export function onLayout(fn: () => void) {
66
- let unsubscribe = srt.on("postLayout", fn)
70
+ let unsubscribe = on("postLayout", fn)
67
71
  onCleanup(unsubscribe)
68
72
  return unsubscribe
69
73
  }
70
74
 
71
75
  export function onWindowFocus(fn: () => void) {
72
- let unsubscribe = srt.on("windowFocus", fn)
76
+ let unsubscribe = on("windowFocus", fn)
73
77
  onCleanup(unsubscribe)
74
78
  return unsubscribe
75
79
  }
76
80
 
77
81
  export function onWindowBlur(fn: () => void) {
78
- let unsubscribe = srt.on("windowBlur", fn)
82
+ let unsubscribe = on("windowBlur", fn)
79
83
  onCleanup(unsubscribe)
80
84
  return unsubscribe
81
85
  }
@@ -109,15 +113,15 @@ export function attachWindow(_nodeId: number) {
109
113
 
110
114
  onSettled(() => {
111
115
  // Sticky event: a late subscriber still receives the current rate.
112
- unsubRefreshRate = srt.on("displayRefreshRate", ({ hz }: { hz: number }) => {
116
+ unsubRefreshRate = on("displayRefreshRate", ({ hz }: { hz: number }) => {
113
117
  if (hz > 0) refreshRate = hz
114
118
  })
115
119
 
116
- unsubscribe = srt.on("render", ({ time, frame }: { time: number; frame: number }) => {
120
+ unsubscribe = on("render", ({ time, frame }: { time: number; frame: number }) => {
117
121
  runFrame(time * 1000, frame)
118
122
  })
119
123
 
120
- unsubDown = srt.on(
124
+ unsubDown = on(
121
125
  "pointerDown",
122
126
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
123
127
  for (let nodeId of targets) {
@@ -132,13 +136,13 @@ export function attachWindow(_nodeId: number) {
132
136
  },
133
137
  )
134
138
 
135
- unsubUp = srt.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
139
+ unsubUp = on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
136
140
  for (let nodeId of targets) {
137
141
  getEventHandler(nodeId, "onPointerUp")?.(e)
138
142
  }
139
143
  })
140
144
 
141
- unsubMove = srt.on(
145
+ unsubMove = on(
142
146
  "pointerMove",
143
147
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
144
148
  for (let nodeId of targets) {
@@ -147,7 +151,7 @@ export function attachWindow(_nodeId: number) {
147
151
  },
148
152
  )
149
153
 
150
- unsubEnter = srt.on(
154
+ unsubEnter = on(
151
155
  "pointerEnter",
152
156
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
153
157
  for (let nodeId of targets) {
@@ -156,7 +160,7 @@ export function attachWindow(_nodeId: number) {
156
160
  },
157
161
  )
158
162
 
159
- unsubLeave = srt.on(
163
+ unsubLeave = on(
160
164
  "pointerLeave",
161
165
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
162
166
  for (let nodeId of targets) {
@@ -165,27 +169,27 @@ export function attachWindow(_nodeId: number) {
165
169
  },
166
170
  )
167
171
 
168
- unsubWheel = srt.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
172
+ unsubWheel = on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
169
173
  for (let nodeId of targets) {
170
174
  getEventHandler(nodeId, "onWheel")?.(e)
171
175
  }
172
176
  })
173
177
 
174
- unsubKeyDown = srt.on("keydown", (e: any) => {
178
+ unsubKeyDown = on("keydown", (e: any) => {
175
179
  let id = getFocusedNodeId()
176
180
  if (id != null) {
177
181
  getEventHandler(id, "onKeyDown")?.(e)
178
182
  }
179
183
  })
180
184
 
181
- unsubKeyUp = srt.on("keyup", (e: any) => {
185
+ unsubKeyUp = on("keyup", (e: any) => {
182
186
  let id = getFocusedNodeId()
183
187
  if (id != null) {
184
188
  getEventHandler(id, "onKeyUp")?.(e)
185
189
  }
186
190
  })
187
191
 
188
- unsubTextInput = srt.on("textInput", (e: any) => {
192
+ unsubTextInput = on("textInput", (e: any) => {
189
193
  let id = getFocusedNodeId()
190
194
  if (id != null) {
191
195
  getEventHandler(id, "onTextInput")?.(e)
@@ -194,7 +198,7 @@ export function attachWindow(_nodeId: number) {
194
198
 
195
199
  // When the user dismisses the on-screen keyboard (swipe down, "Done",
196
200
  // back button), blur the focused node so the app's UI state catches up.
197
- unsubKeyboardVisibility = srt.on("keyboardVisibility", ({ shown }: { shown: boolean }) => {
201
+ unsubKeyboardVisibility = on("keyboardVisibility", ({ shown }: { shown: boolean }) => {
198
202
  if (!shown) setFocus(null)
199
203
  })
200
204
 
@@ -205,7 +209,7 @@ export function attachWindow(_nodeId: number) {
205
209
  // synchronously here, while we are still inside this onSettled callback
206
210
  // where flush() is illegal (not reentrant). Defer runFrame to a microtask
207
211
  // so the first frame always runs after this callback returns.
208
- unsubFirstResize = srt.once("resize", () => {
212
+ unsubFirstResize = once("resize", () => {
209
213
  queueMicrotask(() => runFrame(0, 0))
210
214
  })
211
215
  })