@solidrt/core 0.0.4 → 0.0.6

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,6 +15,16 @@ export namespace JSX {
16
15
  type Element = SolidJSX.Element
17
16
  type ElementChildrenAttribute = SolidJSX.ElementChildrenAttribute
18
17
 
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
22
+ type Ref<T> = T | RefCallback<T>
23
+
24
+ interface IntrinsicAttributes {
25
+ ref?: Ref<{ id: number }> | undefined
26
+ }
27
+
19
28
  interface IntrinsicElements {
20
29
  window: WindowProps
21
30
  view: ViewProps
@@ -25,6 +34,7 @@ export namespace JSX {
25
34
  path: PathProps & LayoutProps
26
35
  texture: TextureProps & LayoutProps
27
36
  audio: AudioProps
37
+ "d-view": ViewProps
28
38
  "d-rect": RectProps
29
39
  "d-oval": OvalProps
30
40
  "d-path": PathProps
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
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
+ "./gpu": "./src/gpu.ts",
10
11
  "./jsx-runtime": "./jsx-runtime.d.ts",
11
12
  "./jsx-runtime-dev": "./jsx-runtime.d.ts"
12
13
  },
@@ -19,10 +20,10 @@
19
20
  "colord": "^2.9.3"
20
21
  },
21
22
  "devDependencies": {
22
- "@solidrt/flux-types": "0.0.4"
23
+ "@solidrt/flux-types": "0.0.6"
23
24
  },
24
25
  "peerDependencies": {
25
- "@solidjs/signals": "2.0.0-beta.13",
26
- "@solidjs/universal": "2.0.0-beta.13"
26
+ "@solidjs/signals": "2.0.0-beta.14",
27
+ "@solidjs/universal": "2.0.0-beta.14"
27
28
  }
28
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/gpu.ts ADDED
@@ -0,0 +1,13 @@
1
+ export type DecodedImage = {
2
+ data: Uint8Array
3
+ width: number
4
+ height: number
5
+ }
6
+
7
+ export function decodeImage(bytes: Uint8Array): DecodedImage {
8
+ return gpu.decodeImage(bytes)
9
+ }
10
+
11
+ export function createTexture(data: Uint8Array, width: number, height: number): number {
12
+ return gpu.createTexture(data, width, height)
13
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,27 @@
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"
5
- export type { MeasureTextOptions } from "./types"
2
+ export { setFocus, getFocusedNodeId, measureText, getBoundingBox } from "./core"
3
+ export type { BoundingBox } from "./core"
4
+ export { onFrame, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
5
+ export { createTexture, decodeImage } from "./gpu"
6
+ export type { DecodedImage } from "./gpu"
7
+ export type {
8
+ LayoutProps,
9
+ TransformProps,
10
+ PointerProps,
11
+ PointerEvent,
12
+ WheelEvent,
13
+ KeyEvent,
14
+ TextEvent,
15
+ PaintProps,
16
+ WindowProps,
17
+ ViewProps,
18
+ RectProps,
19
+ OvalProps,
20
+ LineProps,
21
+ PathProps,
22
+ TextProps,
23
+ TextureProps,
24
+ AudioProps,
25
+ MeasureTextOptions,
26
+ Color,
27
+ } from "./types"
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,12 @@ 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
+ }
16
+
17
+ let gpu: {
18
+ createTexture(data: Uint8Array, width: number, height: number): number
19
+ decodeImage(bytes: Uint8Array): { data: Uint8Array, width: number, height: number }
15
20
  }
16
21
  }
17
22
 
@@ -25,8 +30,6 @@ export interface MeasureTextOptions {
25
30
 
26
31
  type Children = SolidJSX.Element
27
32
 
28
- type OA<T> = T | Accessor<T>
29
-
30
33
  interface FlexboxProps {
31
34
  gap?: number
32
35
  rowGap?: number
@@ -34,7 +37,7 @@ interface FlexboxProps {
34
37
  flex?: number | "none" | "auto" | (string & {})
35
38
  flexGrow?: number
36
39
  flexShrink?: number
37
- flexBasis?: number
40
+ flexBasis?: Dimension
38
41
 
39
42
  flexDirection?: "row" | "column" | "row-reverse" | "column-reverse"
40
43
  flexWrap?: "nowrap" | "wrap" | "wrap-reverse"
@@ -91,8 +94,8 @@ export interface LayoutProps extends FlexboxProps, GridProps {
91
94
  overflowY?: "visible" | "clip" | "hidden" | "scroll"
92
95
  }
93
96
 
94
- import type { LCH } from "./color"
95
- export type Color = string | LCH
97
+ // Colors are CSS color strings, parsed to a packed u32 by parseColorToU32.
98
+ export type Color = string
96
99
 
97
100
  export interface PaintProps {
98
101
  color?: Color
@@ -105,28 +108,48 @@ export interface PaintProps {
105
108
  }
106
109
 
107
110
  export interface TransformProps {
108
- rotate?: OA<number>
109
- scale?: OA<number>
110
- x?: OA<number>
111
- y?: OA<number>
112
- cx?: OA<number>
113
- cy?: OA<number>
114
- scrollX?: OA<number>
115
- 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
116
139
  }
117
140
 
118
141
  export interface PointerProps {
119
- onPointerDown?: Function
120
- onPointerUp?: Function
121
- onPointerMove?: Function
122
- onPointerEnter?: Function
123
- onPointerLeave?: Function
124
- onWheel?: Function
125
- onFocus?: Function
126
- onBlur?: Function
127
- onKeyDown?: Function
128
- onKeyUp?: Function
129
- 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
130
153
  pointerEvents?: "auto" | "none" | "all"
131
154
  }
132
155
 
@@ -170,6 +193,15 @@ export interface OvalProps extends Position, PaintProps, PointerProps {
170
193
  h?: number
171
194
  }
172
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
+
173
205
  export interface PathProps extends Position, PaintProps, PointerProps {
174
206
  d?: string
175
207
  fillRule?: "nonZero" | "evenOdd"
package/src/window.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { onCleanup, onSettled } from "@solidjs/signals"
2
- import { getEventHandler } from "./events"
3
- import { getFocusedNodeId, setFocus } from "./focus"
1
+ import { onCleanup, onSettled, flush } from "@solidjs/signals"
2
+ import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
4
3
 
5
4
  // ------ Animation frames ----------------
6
5
 
@@ -8,10 +7,11 @@ let nextFrameId = 1
8
7
  let animationFrames = new Map<number, Function>()
9
8
 
10
9
  /**
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.
10
+ * Calls `fn` before every frame is painted. The first call receives tick=0 (game time).
11
+ * Returns a cleanup function to stop updates. When called within a reactive scope
12
+ * (e.g. a component or createEffect), cleanup is also automatic.
13
13
  */
14
- export function onRender(fn: (tick: number, frame: number) => void) {
14
+ export function onFrame(fn: (tick: number, frame: number) => void) {
15
15
  let frameId: number = null!
16
16
 
17
17
  let extendedFn = (tick: number, frame: number) => {
@@ -86,57 +86,72 @@ export function attachWindow(_nodeId: number) {
86
86
  let unsubKeyUp: () => void = null!
87
87
  let unsubTextInput: () => void = null!
88
88
  let unsubKeyboardVisibility: () => void = null!
89
+ let unsubFirstResize: (() => void) | null = null
90
+
91
+ function runFrame(t: number, frame: number) {
92
+ if (animationFrames.size > 0) {
93
+ let frames = animationFrames
94
+ animationFrames = new Map()
95
+ for (let fn of frames.values()) fn(t, frame)
96
+ }
97
+ flush()
98
+ draw()
99
+ }
89
100
 
90
101
  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()
101
- })
102
-
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
- }
102
+ unsubscribe = Flux.on("render", ({ time, frame }: { time: number; frame: number }) => {
103
+ runFrame((time * 1000) | 0, frame)
113
104
  })
114
105
 
115
- unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
106
+ unsubDown = Flux.on(
107
+ "pointerDown",
108
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
109
+ for (let nodeId of targets) {
110
+ getEventHandler(nodeId, "onPointerDown")?.(e)
111
+ }
112
+ // Outside-tap blur. Read focus AFTER per-node handlers so a tap that
113
+ // moves focus to a new node is not immediately blurred again.
114
+ let focused = getFocusedNodeId()
115
+ if (focused != null && !targets.includes(focused)) {
116
+ setFocus(null)
117
+ }
118
+ },
119
+ )
120
+
121
+ unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
116
122
  for (let nodeId of targets) {
117
123
  getEventHandler(nodeId, "onPointerUp")?.(e)
118
124
  }
119
125
  })
120
126
 
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 }) => {
127
+ unsubMove = Flux.on(
128
+ "pointerMove",
129
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
130
+ for (let nodeId of targets) {
131
+ getEventHandler(nodeId, "onPointerMove")?.(e)
132
+ }
133
+ },
134
+ )
135
+
136
+ unsubEnter = Flux.on(
137
+ "pointerEnter",
138
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
139
+ for (let nodeId of targets) {
140
+ getEventHandler(nodeId, "onPointerEnter")?.(e)
141
+ }
142
+ },
143
+ )
144
+
145
+ unsubLeave = Flux.on(
146
+ "pointerLeave",
147
+ ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
148
+ for (let nodeId of targets) {
149
+ getEventHandler(nodeId, "onPointerLeave")?.(e)
150
+ }
151
+ },
152
+ )
153
+
154
+ unsubWheel = Flux.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
140
155
  for (let nodeId of targets) {
141
156
  getEventHandler(nodeId, "onWheel")?.(e)
142
157
  }
@@ -169,8 +184,16 @@ export function attachWindow(_nodeId: number) {
169
184
  if (!shown) setFocus(null)
170
185
  })
171
186
 
172
- // trigger first draw
173
- draw()
187
+ // Bootstrap the first frame on the first resize event: by then any
188
+ // onResize subscribers (which run earlier in the dispatch list) have
189
+ // set their initial signal values, so runFrame's flush sees a fully
190
+ // initialized graph. Resize is a sticky event in Flux, so it can replay
191
+ // synchronously here, while we are still inside this onSettled callback
192
+ // where flush() is illegal (not reentrant). Defer runFrame to a microtask
193
+ // so the first frame always runs after this callback returns.
194
+ unsubFirstResize = Flux.once("resize", () => {
195
+ queueMicrotask(() => runFrame(0, 0))
196
+ })
174
197
  })
175
198
 
176
199
  onCleanup(() => {
@@ -185,5 +208,6 @@ export function attachWindow(_nodeId: number) {
185
208
  if (unsubKeyUp) unsubKeyUp()
186
209
  if (unsubTextInput) unsubTextInput()
187
210
  if (unsubKeyboardVisibility) unsubKeyboardVisibility()
211
+ if (unsubFirstResize) unsubFirstResize()
188
212
  })
189
213
  }
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
- }