@solidrt/core 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ Run the app:
36
36
  bunx srt run src/index.tsx
37
37
  ```
38
38
 
39
- Optionally, create a `tsconfig.json` to enable JSX support and type recognition for SolidRT elements:
39
+ Optionally, create a `tsconfig.json` to enable type recognition for SolidRT elements:
40
40
 
41
41
  ```json
42
42
  {
@@ -51,7 +51,7 @@ Optionally, create a `tsconfig.json` to enable JSX support and type recognition
51
51
 
52
52
  ## API
53
53
 
54
- See [docs/api.md](https://github.com/wellawaretech/solidrt/blob/main/docs/api.md) for the full API reference.
54
+ See [docs/core.md](https://github.com/wellawaretech/solidrt/blob/main/docs/core.md) for the full API reference.
55
55
 
56
56
  ## License
57
57
 
package/jsx-runtime.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  WindowProps,
3
- CircleProps,
4
3
  RectProps,
5
4
  OvalProps,
6
5
  PathProps,
@@ -16,7 +15,10 @@ export namespace JSX {
16
15
  type Element = SolidJSX.Element
17
16
  type ElementChildrenAttribute = SolidJSX.ElementChildrenAttribute
18
17
 
19
- type RefCallback<T> = (el: T) => void
18
+ // Return type is unconstrained: a ref callback's return value is ignored, so
19
+ // an arrow like `ref={n => (this.node = n)}` (which returns the assignment)
20
+ // is accepted as readily as a void-returning block body.
21
+ type RefCallback<T> = (el: T) => unknown
20
22
  type Ref<T> = T | RefCallback<T>
21
23
 
22
24
  interface IntrinsicAttributes {
@@ -32,6 +34,7 @@ export namespace JSX {
32
34
  path: PathProps & LayoutProps
33
35
  texture: TextureProps & LayoutProps
34
36
  audio: AudioProps
37
+ "d-view": ViewProps
35
38
  "d-rect": RectProps
36
39
  "d-oval": OvalProps
37
40
  "d-path": PathProps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
@@ -20,10 +20,10 @@
20
20
  "colord": "^2.9.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@solidrt/flux-types": "0.0.5"
23
+ "@solidrt/flux-types": "0.0.7"
24
24
  },
25
25
  "peerDependencies": {
26
- "@solidjs/signals": "2.0.0-beta.13",
27
- "@solidjs/universal": "2.0.0-beta.13"
26
+ "@solidjs/signals": "2.0.0-beta.14",
27
+ "@solidjs/universal": "2.0.0-beta.14"
28
28
  }
29
29
  }
package/src/core.ts ADDED
@@ -0,0 +1,78 @@
1
+ import { colord, extend } from "colord"
2
+ import namesPlugin from "colord/plugins/names"
3
+ import type { MeasureTextOptions } from "./types"
4
+ extend([namesPlugin])
5
+
6
+ export function parseColorToU32(color: string): number {
7
+ let { r, g, b, a } = colord(color).toRgb()
8
+ return (((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | ((a * 255) & 0xFF)) >>> 0
9
+ }
10
+
11
+ let handlers = new Map<number, Map<string, Function>>()
12
+
13
+ export function setEventHandler(nodeId: number, name: string, fn: Function | null | undefined): void {
14
+ if (fn == null) {
15
+ handlers.get(nodeId)?.delete(name)
16
+ return
17
+ }
18
+ let nodeHandlers = handlers.get(nodeId)
19
+ if (!nodeHandlers) {
20
+ nodeHandlers = new Map()
21
+ handlers.set(nodeId, nodeHandlers)
22
+ }
23
+ nodeHandlers.set(name, fn)
24
+ }
25
+
26
+ export function getEventHandler(nodeId: number, name: string): Function | undefined {
27
+ return handlers.get(nodeId)?.get(name)
28
+ }
29
+
30
+ export function cleanupNodeHandlers(nodeId: number): void {
31
+ handlers.delete(nodeId)
32
+ }
33
+
34
+ // Currently-focused node id. Reset to null automatically across engine
35
+ // reloads because the JS environment is rebuilt from scratch.
36
+ let focusedNodeId: number | null = null
37
+ let textInputActive = false
38
+
39
+ export function setFocus(nodeId: number | null): void {
40
+ if (nodeId === focusedNodeId) return
41
+ let oldId = focusedNodeId
42
+ focusedNodeId = nodeId
43
+ if (oldId != null) {
44
+ getEventHandler(oldId, "onBlur")?.()
45
+ }
46
+ if (nodeId != null) {
47
+ getEventHandler(nodeId, "onFocus")?.()
48
+ }
49
+ let wantActive = nodeId != null && getEventHandler(nodeId, "onTextInput") != null
50
+ if (wantActive !== textInputActive) {
51
+ textInputActive = wantActive
52
+ ffi.setTextInputActive(wantActive)
53
+ }
54
+ }
55
+
56
+ export function getFocusedNodeId(): number | null {
57
+ return focusedNodeId
58
+ }
59
+
60
+ export interface BoundingBox {
61
+ x: number
62
+ y: number
63
+ width: number
64
+ height: number
65
+ }
66
+
67
+ // Returns the node's window-relative bounding box from the most recently
68
+ // computed layout, or null if the node has no layout or has not been laid out
69
+ // yet. This is a snapshot read, not reactive: call it inside onLayout (or an
70
+ // event handler) to get values for the current frame. Phase 1 composes only
71
+ // translations; x/y are wrong when a rotate/scale sits anywhere above the node.
72
+ export function getBoundingBox(node: { id: number }): BoundingBox | null {
73
+ return ffi.getBoundingBox(node.id)
74
+ }
75
+
76
+ export function measureText(text: string, options?: MeasureTextOptions): { width: number, height: number } {
77
+ return ffi.measureText(text, options)
78
+ }
package/src/index.ts CHANGED
@@ -1,18 +1,23 @@
1
1
  export * from "./renderer"
2
- export { setFocus, getFocusedNodeId } from "./focus"
3
- export { onRender, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
4
- export { measureText } from "./text"
2
+ export { setFocus, getFocusedNodeId, measureText, getBoundingBox } from "./core"
3
+ export type { BoundingBox } from "./core"
4
+ export { onFrame, createPacedClock, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
5
5
  export { createTexture, decodeImage } from "./gpu"
6
6
  export type { DecodedImage } from "./gpu"
7
7
  export type {
8
8
  LayoutProps,
9
9
  TransformProps,
10
10
  PointerProps,
11
+ PointerEvent,
12
+ WheelEvent,
13
+ KeyEvent,
14
+ TextEvent,
11
15
  PaintProps,
12
16
  WindowProps,
13
17
  ViewProps,
14
18
  RectProps,
15
19
  OvalProps,
20
+ LineProps,
16
21
  PathProps,
17
22
  TextProps,
18
23
  TextureProps,
package/src/renderer.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import { createRoot, createEffect } from "@solidjs/signals"
2
2
  import { createRenderer } from "@solidjs/universal"
3
3
  import { attachWindow } from "./window"
4
- import { parseColorToU32, isColorProp } from "./color"
5
- import { setEventHandler, cleanupNodeHandlers } from "./events"
6
- import { getFocusedNodeId, setFocus } from "./focus"
4
+ import { parseColorToU32, setEventHandler, cleanupNodeHandlers, getFocusedNodeId, setFocus } from "./core"
7
5
 
8
- export { getEventHandler } from "./events"
6
+ export { getEventHandler } from "./core"
9
7
 
10
8
  export let nodes = new Map()
11
9
 
@@ -56,9 +54,9 @@ export let {
56
54
  },
57
55
 
58
56
  createTextNode: (value: string): ProxyNode => {
59
- let proxy = createProxyNode("span")
57
+ let proxy = createProxyNode("d-span")
60
58
  // console.debug("[srt] createTextNode", proxy.id, value)
61
- ffi.createNode(proxy.id, "span")
59
+ ffi.createNode(proxy.id, "d-span")
62
60
  ffi.setProperty(proxy.id, "text", "" + value)
63
61
  return proxy
64
62
  },
@@ -68,7 +66,7 @@ export let {
68
66
  ffi.setProperty(node.id, "text", "" + value)
69
67
  },
70
68
 
71
- isTextNode: (node: ProxyNode): boolean => node?.elementType === "span",
69
+ isTextNode: (node: ProxyNode): boolean => node?.elementType === "d-span",
72
70
  setProperty: <T>(node: ProxyNode, name: string, value: T): void => {
73
71
  if (!node) return
74
72
 
package/src/types.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /// <reference types="@solidrt/flux-types" />
2
2
 
3
- import type { Accessor, JSX as SolidJSX } from "@solidjs/signals"
3
+ import type { JSX as SolidJSX } from "@solidjs/signals"
4
4
 
5
5
  declare global {
6
-
7
6
  let ffi: {
8
7
  createRoot(id: number): void
9
8
  createNode(id: number, kind: string): void
@@ -12,6 +11,7 @@ declare global {
12
11
  setProperty(nodeId: number, name: string, value: unknown): void
13
12
  setTextInputActive(active: boolean): void
14
13
  measureText(text: string, options?: MeasureTextOptions): { width: number, height: number }
14
+ getBoundingBox(id: number): { x: number, y: number, width: number, height: number } | null
15
15
  }
16
16
 
17
17
  let gpu: {
@@ -30,8 +30,6 @@ export interface MeasureTextOptions {
30
30
 
31
31
  type Children = SolidJSX.Element
32
32
 
33
- type OA<T> = T | Accessor<T>
34
-
35
33
  interface FlexboxProps {
36
34
  gap?: number
37
35
  rowGap?: number
@@ -39,7 +37,7 @@ interface FlexboxProps {
39
37
  flex?: number | "none" | "auto" | (string & {})
40
38
  flexGrow?: number
41
39
  flexShrink?: number
42
- flexBasis?: number
40
+ flexBasis?: Dimension
43
41
 
44
42
  flexDirection?: "row" | "column" | "row-reverse" | "column-reverse"
45
43
  flexWrap?: "nowrap" | "wrap" | "wrap-reverse"
@@ -96,8 +94,8 @@ export interface LayoutProps extends FlexboxProps, GridProps {
96
94
  overflowY?: "visible" | "clip" | "hidden" | "scroll"
97
95
  }
98
96
 
99
- import type { LCH } from "./color"
100
- export type Color = string | LCH
97
+ // Colors are CSS color strings, parsed to a packed u32 by parseColorToU32.
98
+ export type Color = string
101
99
 
102
100
  export interface PaintProps {
103
101
  color?: Color
@@ -110,28 +108,48 @@ export interface PaintProps {
110
108
  }
111
109
 
112
110
  export interface TransformProps {
113
- rotate?: OA<number>
114
- scale?: OA<number>
115
- x?: OA<number>
116
- y?: OA<number>
117
- cx?: OA<number>
118
- cy?: OA<number>
119
- scrollX?: OA<number>
120
- scrollY?: OA<number>
111
+ rotate?: number
112
+ scale?: number
113
+ x?: number
114
+ y?: number
115
+ cx?: number
116
+ cy?: number
117
+ scrollX?: number
118
+ scrollY?: number
119
+ }
120
+
121
+ export interface PointerEvent {
122
+ x: number
123
+ y: number
124
+ }
125
+
126
+ export interface WheelEvent {
127
+ x: number
128
+ y: number
129
+ deltaX: number
130
+ deltaY: number
131
+ }
132
+
133
+ export interface KeyEvent {
134
+ key: string
135
+ }
136
+
137
+ export interface TextEvent {
138
+ text: string
121
139
  }
122
140
 
123
141
  export interface PointerProps {
124
- onPointerDown?: Function
125
- onPointerUp?: Function
126
- onPointerMove?: Function
127
- onPointerEnter?: Function
128
- onPointerLeave?: Function
129
- onWheel?: Function
130
- onFocus?: Function
131
- onBlur?: Function
132
- onKeyDown?: Function
133
- onKeyUp?: Function
134
- onTextInput?: Function
142
+ onPointerDown?: (event: PointerEvent) => void
143
+ onPointerUp?: (event: PointerEvent) => void
144
+ onPointerMove?: (event: PointerEvent) => void
145
+ onPointerEnter?: (event: PointerEvent) => void
146
+ onPointerLeave?: (event: PointerEvent) => void
147
+ onWheel?: (event: WheelEvent) => void
148
+ onFocus?: () => void
149
+ onBlur?: () => void
150
+ onKeyDown?: (event: KeyEvent) => void
151
+ onKeyUp?: (event: KeyEvent) => void
152
+ onTextInput?: (event: TextEvent) => void
135
153
  pointerEvents?: "auto" | "none" | "all"
136
154
  }
137
155
 
@@ -175,12 +193,21 @@ export interface OvalProps extends Position, PaintProps, PointerProps {
175
193
  h?: number
176
194
  }
177
195
 
196
+ export interface LineProps extends PaintProps, PointerProps {
197
+ x1?: number
198
+ y1?: number
199
+ x2?: number
200
+ y2?: number
201
+ onLength?: number
202
+ offLength?: number
203
+ }
204
+
178
205
  export interface PathProps extends Position, PaintProps, PointerProps {
179
206
  d?: string
180
207
  fillRule?: "nonZero" | "evenOdd"
181
208
  }
182
209
 
183
- export interface TextProps extends PaintProps {
210
+ export interface TextProps extends PaintProps, PointerProps {
184
211
  children?: Children
185
212
  fontFamily?: "sans" | "mono" | (string & {})
186
213
  fontSize?: number
package/src/window.ts CHANGED
@@ -1,21 +1,27 @@
1
- import { onCleanup, onSettled } from "@solidjs/signals"
2
- import { getEventHandler } from "./events"
3
- import { getFocusedNodeId, setFocus } from "./focus"
1
+ import { onCleanup, onSettled, flush, createSignal } from "@solidjs/signals"
2
+ import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
4
3
 
5
4
  // ------ Animation frames ----------------
6
5
 
7
6
  let nextFrameId = 1
8
7
  let animationFrames = new Map<number, Function>()
9
8
 
9
+ // Latest display refresh rate (Hz) reported by the runtime, passed to onFrame
10
+ // callbacks. Defaults to 60 until the first displayRefreshRate event arrives.
11
+ let refreshRate = 60
12
+
10
13
  /**
11
- * Calls `fn` on every rendered frame. Returns a cleanup function to stop rendering.
12
- * When called within a reactive scope (e.g. a component or createEffect), cleanup is also automatic.
14
+ * Calls `fn` before every frame is painted, with the raw runtime signals: `tick`
15
+ * is the unsmoothed wall-clock time in ms sampled at present, `frame` is the
16
+ * present count, and `rate` is the current refresh rate in Hz. No pacing is
17
+ * applied; see createPacedClock for an opt-in smooth clock.
18
+ * Returns a cleanup function; also auto-cleans within a reactive scope.
13
19
  */
14
- export function onRender(fn: (tick: number, frame: number) => void) {
20
+ export function onFrame(fn: (tick: number, frame: number, rate: number) => void) {
15
21
  let frameId: number = null!
16
22
 
17
- let extendedFn = (tick: number, frame: number) => {
18
- fn(tick, frame)
23
+ let extendedFn = (tick: number, frame: number, rate: number) => {
24
+ fn(tick, frame, rate)
19
25
  frameId = nextFrameId++
20
26
  animationFrames.set(frameId, extendedFn)
21
27
  }
@@ -28,6 +34,26 @@ export function onRender(fn: (tick: number, frame: number) => void) {
28
34
  return cleanup
29
35
  }
30
36
 
37
+ /**
38
+ * Opt-in smooth clock built on the raw onFrame signals. Paces by present count
39
+ * (one refresh period per frame) and slowly corrects toward the raw wall-clock
40
+ * tick, so it stays smooth while keeping up and tracks real time when the
41
+ * framerate drops. `gain` (0..1) trades convergence speed for jitter. Returns an
42
+ * accessor for the paced time in milliseconds.
43
+ */
44
+ export function createPacedClock(opts?: { gain?: number }) {
45
+ let gain = opts?.gain ?? 0.05
46
+ let [time, setTime] = createSignal(0)
47
+ let clock = 0
48
+ onFrame((tick, _frame, rate) => {
49
+ let period = 1000 / rate
50
+ clock += period
51
+ clock += (tick - clock) * gain
52
+ setTime(clock)
53
+ })
54
+ return time
55
+ }
56
+
31
57
  // ------ Resize ----------------
32
58
 
33
59
  interface SafeArea {
@@ -86,57 +112,78 @@ export function attachWindow(_nodeId: number) {
86
112
  let unsubKeyUp: () => void = null!
87
113
  let unsubTextInput: () => void = null!
88
114
  let unsubKeyboardVisibility: () => void = null!
115
+ let unsubRefreshRate: () => void = null!
116
+ let unsubFirstResize: (() => void) | null = null
117
+
118
+ function runFrame(t: number, frame: number) {
119
+ if (animationFrames.size > 0) {
120
+ let frames = animationFrames
121
+ animationFrames = new Map()
122
+ for (let fn of frames.values()) fn(t, frame, refreshRate)
123
+ }
124
+ flush()
125
+ draw()
126
+ }
89
127
 
90
128
  onSettled(() => {
91
- unsubscribe = Flux.on("render", ({ time, frame }: { time: number, frame: number }) => {
92
- if (animationFrames.size > 0) {
93
- let frames = animationFrames
94
- animationFrames = new Map()
95
-
96
- let t = (time * 1000) | 0
97
- for (let fn of frames.values()) fn(t, frame)
98
- }
99
-
100
- draw()
129
+ // Sticky event: a late subscriber still receives the current rate.
130
+ unsubRefreshRate = Flux.on("displayRefreshRate", ({ hz }: { hz: number }) => {
131
+ if (hz > 0) refreshRate = hz
101
132
  })
102
133
 
103
- unsubDown = Flux.on("pointerDown", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
104
- for (let nodeId of targets) {
105
- getEventHandler(nodeId, "onPointerDown")?.(e)
106
- }
107
- // Outside-tap blur. Read focus AFTER per-node handlers so a tap that
108
- // moves focus to a new node is not immediately blurred again.
109
- let focused = getFocusedNodeId()
110
- if (focused != null && !targets.includes(focused)) {
111
- setFocus(null)
112
- }
134
+ unsubscribe = Flux.on("render", ({ time, frame }: { time: number; frame: number }) => {
135
+ runFrame(time * 1000, frame)
113
136
  })
114
137
 
115
- unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
138
+ unsubDown = Flux.on(
139
+ "pointerDown",
140
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
141
+ for (let nodeId of targets) {
142
+ getEventHandler(nodeId, "onPointerDown")?.(e)
143
+ }
144
+ // Outside-tap blur. Read focus AFTER per-node handlers so a tap that
145
+ // moves focus to a new node is not immediately blurred again.
146
+ let focused = getFocusedNodeId()
147
+ if (focused != null && !targets.includes(focused)) {
148
+ setFocus(null)
149
+ }
150
+ },
151
+ )
152
+
153
+ unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
116
154
  for (let nodeId of targets) {
117
155
  getEventHandler(nodeId, "onPointerUp")?.(e)
118
156
  }
119
157
  })
120
158
 
121
- unsubMove = Flux.on("pointerMove", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
122
- for (let nodeId of targets) {
123
- getEventHandler(nodeId, "onPointerMove")?.(e)
124
- }
125
- })
126
-
127
- unsubEnter = Flux.on("pointerEnter", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
128
- for (let nodeId of targets) {
129
- getEventHandler(nodeId, "onPointerEnter")?.(e)
130
- }
131
- })
132
-
133
- unsubLeave = Flux.on("pointerLeave", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
134
- for (let nodeId of targets) {
135
- getEventHandler(nodeId, "onPointerLeave")?.(e)
136
- }
137
- })
138
-
139
- unsubWheel = Flux.on("wheel", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
159
+ unsubMove = Flux.on(
160
+ "pointerMove",
161
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
162
+ for (let nodeId of targets) {
163
+ getEventHandler(nodeId, "onPointerMove")?.(e)
164
+ }
165
+ },
166
+ )
167
+
168
+ unsubEnter = Flux.on(
169
+ "pointerEnter",
170
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
171
+ for (let nodeId of targets) {
172
+ getEventHandler(nodeId, "onPointerEnter")?.(e)
173
+ }
174
+ },
175
+ )
176
+
177
+ unsubLeave = Flux.on(
178
+ "pointerLeave",
179
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
180
+ for (let nodeId of targets) {
181
+ getEventHandler(nodeId, "onPointerLeave")?.(e)
182
+ }
183
+ },
184
+ )
185
+
186
+ unsubWheel = Flux.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
140
187
  for (let nodeId of targets) {
141
188
  getEventHandler(nodeId, "onWheel")?.(e)
142
189
  }
@@ -169,8 +216,16 @@ export function attachWindow(_nodeId: number) {
169
216
  if (!shown) setFocus(null)
170
217
  })
171
218
 
172
- // trigger first draw
173
- draw()
219
+ // Bootstrap the first frame on the first resize event: by then any
220
+ // onResize subscribers (which run earlier in the dispatch list) have
221
+ // set their initial signal values, so runFrame's flush sees a fully
222
+ // initialized graph. Resize is a sticky event in Flux, so it can replay
223
+ // synchronously here, while we are still inside this onSettled callback
224
+ // where flush() is illegal (not reentrant). Defer runFrame to a microtask
225
+ // so the first frame always runs after this callback returns.
226
+ unsubFirstResize = Flux.once("resize", () => {
227
+ queueMicrotask(() => runFrame(0, 0))
228
+ })
174
229
  })
175
230
 
176
231
  onCleanup(() => {
@@ -185,5 +240,7 @@ export function attachWindow(_nodeId: number) {
185
240
  if (unsubKeyUp) unsubKeyUp()
186
241
  if (unsubTextInput) unsubTextInput()
187
242
  if (unsubKeyboardVisibility) unsubKeyboardVisibility()
243
+ if (unsubRefreshRate) unsubRefreshRate()
244
+ if (unsubFirstResize) unsubFirstResize()
188
245
  })
189
246
  }
package/src/color.ts DELETED
@@ -1,9 +0,0 @@
1
- import { colord, extend } from "colord"
2
- import namesPlugin from "colord/plugins/names"
3
- extend([namesPlugin])
4
-
5
- // Parses any CSS color string and returns a packed u32: 0xRRGGBBAA
6
- export function parseColorToU32(color: string): number {
7
- let { r, g, b, a } = colord(color).toRgb()
8
- return (((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | ((a * 255) & 0xFF)) >>> 0
9
- }
package/src/events.ts DELETED
@@ -1,22 +0,0 @@
1
- let handlers = new Map<number, Map<string, Function>>()
2
-
3
- export function setEventHandler(nodeId: number, name: string, fn: Function | null | undefined): void {
4
- if (fn == null) {
5
- handlers.get(nodeId)?.delete(name)
6
- return
7
- }
8
- let nodeHandlers = handlers.get(nodeId)
9
- if (!nodeHandlers) {
10
- nodeHandlers = new Map()
11
- handlers.set(nodeId, nodeHandlers)
12
- }
13
- nodeHandlers.set(name, fn)
14
- }
15
-
16
- export function getEventHandler(nodeId: number, name: string): Function | undefined {
17
- return handlers.get(nodeId)?.get(name)
18
- }
19
-
20
- export function cleanupNodeHandlers(nodeId: number): void {
21
- handlers.delete(nodeId)
22
- }
package/src/focus.ts DELETED
@@ -1,27 +0,0 @@
1
- import { getEventHandler } from "./events"
2
-
3
- // Currently-focused node id. Reset to null automatically across engine
4
- // reloads because the JS environment is rebuilt from scratch.
5
- let focusedNodeId: number | null = null
6
- let textInputActive = false
7
-
8
- export function setFocus(nodeId: number | null): void {
9
- if (nodeId === focusedNodeId) return
10
- let oldId = focusedNodeId
11
- focusedNodeId = nodeId
12
- if (oldId != null) {
13
- getEventHandler(oldId, "onBlur")?.()
14
- }
15
- if (nodeId != null) {
16
- getEventHandler(nodeId, "onFocus")?.()
17
- }
18
- let wantActive = nodeId != null && getEventHandler(nodeId, "onTextInput") != null
19
- if (wantActive !== textInputActive) {
20
- textInputActive = wantActive
21
- ffi.setTextInputActive(wantActive)
22
- }
23
- }
24
-
25
- export function getFocusedNodeId(): number | null {
26
- return focusedNodeId
27
- }
package/src/text.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { MeasureTextOptions } from "./types"
2
-
3
- export function measureText(text: string, options?: MeasureTextOptions): { width: number, height: number } {
4
- return ffi.measureText(text, options)
5
- }