@solidrt/core 0.0.1 → 0.0.3

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,7 +1,8 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "license": "MIT",
5
+ "author": "Antoine van Wel",
5
6
  "type": "module",
6
7
  "main": "src/index.ts",
7
8
  "exports": {
@@ -17,6 +18,9 @@
17
18
  "dependencies": {
18
19
  "colord": "^2.9.3"
19
20
  },
21
+ "devDependencies": {
22
+ "@solidrt/flux-types": "0.0.3"
23
+ },
20
24
  "peerDependencies": {
21
25
  "@solidjs/signals": "2.0.0-beta.13",
22
26
  "@solidjs/universal": "2.0.0-beta.13"
package/src/focus.ts CHANGED
@@ -3,6 +3,7 @@ import { getEventHandler } from "./events"
3
3
  // Currently-focused node id. Reset to null automatically across engine
4
4
  // reloads because the JS environment is rebuilt from scratch.
5
5
  let focusedNodeId: number | null = null
6
+ let textInputActive = false
6
7
 
7
8
  export function setFocus(nodeId: number | null): void {
8
9
  if (nodeId === focusedNodeId) return
@@ -14,6 +15,11 @@ export function setFocus(nodeId: number | null): void {
14
15
  if (nodeId != null) {
15
16
  getEventHandler(nodeId, "onFocus")?.()
16
17
  }
18
+ let wantActive = nodeId != null && getEventHandler(nodeId, "onTextInput") != null
19
+ if (wantActive !== textInputActive) {
20
+ textInputActive = wantActive
21
+ ffi.setTextInputActive(wantActive)
22
+ }
17
23
  }
18
24
 
19
25
  export function getFocusedNodeId(): number | null {
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from "./renderer"
2
2
  export { setFocus, getFocusedNodeId } from "./focus"
3
- export { onRender, onResize, onWindowFocus, onWindowBlur } from "./window"
3
+ export { onRender, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
4
+ export { measureText } from "./text"
5
+ export type { MeasureTextOptions } from "./types"
package/src/renderer.ts CHANGED
@@ -3,6 +3,7 @@ import { createRenderer } from "@solidjs/universal"
3
3
  import { attachWindow } from "./window"
4
4
  import { parseColorToU32, isColorProp } from "./color"
5
5
  import { setEventHandler, cleanupNodeHandlers } from "./events"
6
+ import { getFocusedNodeId, setFocus } from "./focus"
6
7
 
7
8
  export { getEventHandler } from "./events"
8
9
 
@@ -124,9 +125,11 @@ export let {
124
125
 
125
126
  ffi.deleteNode(parent.id, node.id)
126
127
 
127
- // Recursively clean up node and all descendants
128
+ // Recursively clean up node and all descendants. Clear focus before
129
+ // dropping handlers so onBlur still fires for a focused descendant.
128
130
  let cleanup = (n: ProxyNode) => {
129
131
  for (let child of n.children) cleanup(child)
132
+ if (n.id === getFocusedNodeId()) setFocus(null)
130
133
  nodes.delete(n.id)
131
134
  cleanupNodeHandlers(n.id)
132
135
  }
package/src/text.ts ADDED
@@ -0,0 +1,5 @@
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
+ }
package/src/types.d.ts CHANGED
@@ -1,9 +1,8 @@
1
+ /// <reference types="@solidrt/flux-types" />
2
+
1
3
  import type { Accessor, JSX as SolidJSX } from "@solidjs/signals"
2
4
 
3
5
  declare global {
4
- let Flux: {
5
- on(event: string, callback: (data: any) => void): () => void
6
- }
7
6
 
8
7
  let ffi: {
9
8
  createRoot(id: number): void
@@ -11,9 +10,19 @@ declare global {
11
10
  insertNode(parentId: number, nodeId: number, anchorId?: number): void
12
11
  deleteNode(parentId: number, nodeId: number): void
13
12
  setProperty(nodeId: number, name: string, value: unknown): void
13
+ setTextInputActive(active: boolean): void
14
+ measureText(text: string, options?: MeasureTextOptions): { width: number, height: number }
14
15
  }
15
16
  }
16
17
 
18
+ export interface MeasureTextOptions {
19
+ fontFamily?: "sans" | "mono" | (string & {})
20
+ fontSize?: number
21
+ fontStyle?: "normal" | "italic"
22
+ fontWeight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
23
+ maxLines?: number
24
+ }
25
+
17
26
  type Children = SolidJSX.Element
18
27
 
19
28
  type OA<T> = T | Accessor<T>
@@ -22,6 +31,7 @@ interface FlexboxProps {
22
31
  gap?: number
23
32
  rowGap?: number
24
33
  columnGap?: number
34
+ flex?: number | "none" | "auto" | (string & {})
25
35
  flexGrow?: number
26
36
  flexShrink?: number
27
37
  flexBasis?: number
@@ -77,6 +87,8 @@ export interface LayoutProps extends FlexboxProps, GridProps {
77
87
  marginLeft?: Dimension
78
88
 
79
89
  overflow?: "visible" | "clip" | "hidden" | "scroll"
90
+ overflowX?: "visible" | "clip" | "hidden" | "scroll"
91
+ overflowY?: "visible" | "clip" | "hidden" | "scroll"
80
92
  }
81
93
 
82
94
  import type { LCH } from "./color"
@@ -97,6 +109,10 @@ export interface TransformProps {
97
109
  scale?: OA<number>
98
110
  x?: OA<number>
99
111
  y?: OA<number>
112
+ cx?: OA<number>
113
+ cy?: OA<number>
114
+ scrollX?: OA<number>
115
+ scrollY?: OA<number>
100
116
  }
101
117
 
102
118
  export interface PointerProps {
@@ -124,6 +140,7 @@ interface Position {
124
140
  export interface WindowProps extends LayoutProps {
125
141
  children?: Children
126
142
  title?: string
143
+ fullscreen?: boolean
127
144
  vsync?: boolean
128
145
  fps?: boolean
129
146
  }
@@ -159,9 +176,12 @@ export interface PathProps extends Position, PaintProps, PointerProps {
159
176
  }
160
177
 
161
178
  export interface TextProps extends PaintProps {
162
- children: Children
179
+ children?: Children
180
+ fontFamily?: "sans" | "mono" | (string & {})
163
181
  fontSize?: number
182
+ lineHeight?: number
164
183
  fontStyle?: "normal" | "italic"
184
+ fontWeight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
165
185
  textAlign?: "left" | "right" | "center" | "justify"
166
186
  maxLines?: number
167
187
  }
package/src/window.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { onCleanup, onSettled } from "@solidjs/signals"
2
2
  import { getEventHandler } from "./events"
3
- import { getFocusedNodeId } from "./focus"
3
+ import { getFocusedNodeId, setFocus } from "./focus"
4
4
 
5
5
  // ------ Animation frames ----------------
6
6
 
@@ -50,6 +50,16 @@ export function onResize(fn: (data: ResizeEvent) => void) {
50
50
  return unsubscribe
51
51
  }
52
52
 
53
+ // Fires after layout has been computed for the current frame but before paint.
54
+ // Setting properties that affect layout from this callback will be picked up
55
+ // by a re-layout pass before painting (one extra pass; cascades beyond that
56
+ // paint stale).
57
+ export function onLayout(fn: () => void) {
58
+ let unsubscribe = Flux.on("postLayout", fn)
59
+ onCleanup(unsubscribe)
60
+ return unsubscribe
61
+ }
62
+
53
63
  export function onWindowFocus(fn: () => void) {
54
64
  let unsubscribe = Flux.on("windowFocus", fn)
55
65
  onCleanup(unsubscribe)
@@ -74,6 +84,8 @@ export function attachWindow(_nodeId: number) {
74
84
  let unsubWheel: () => void = null!
75
85
  let unsubKeyDown: () => void = null!
76
86
  let unsubKeyUp: () => void = null!
87
+ let unsubTextInput: () => void = null!
88
+ let unsubKeyboardVisibility: () => void = null!
77
89
 
78
90
  onSettled(() => {
79
91
  unsubscribe = Flux.on("render", ({ time, frame }: { time: number, frame: number }) => {
@@ -92,6 +104,12 @@ export function attachWindow(_nodeId: number) {
92
104
  for (let nodeId of targets) {
93
105
  getEventHandler(nodeId, "onPointerDown")?.(e)
94
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
+ }
95
113
  })
96
114
 
97
115
  unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
@@ -138,6 +156,19 @@ export function attachWindow(_nodeId: number) {
138
156
  }
139
157
  })
140
158
 
159
+ unsubTextInput = Flux.on("textInput", (e: any) => {
160
+ let id = getFocusedNodeId()
161
+ if (id != null) {
162
+ getEventHandler(id, "onTextInput")?.(e)
163
+ }
164
+ })
165
+
166
+ // When the user dismisses the on-screen keyboard (swipe down, "Done",
167
+ // back button), blur the focused node so the app's UI state catches up.
168
+ unsubKeyboardVisibility = Flux.on("keyboardVisibility", ({ shown }: { shown: boolean }) => {
169
+ if (!shown) setFocus(null)
170
+ })
171
+
141
172
  // trigger first draw
142
173
  draw()
143
174
  })
@@ -152,5 +183,7 @@ export function attachWindow(_nodeId: number) {
152
183
  if (unsubWheel) unsubWheel()
153
184
  if (unsubKeyDown) unsubKeyDown()
154
185
  if (unsubKeyUp) unsubKeyUp()
186
+ if (unsubTextInput) unsubTextInput()
187
+ if (unsubKeyboardVisibility) unsubKeyboardVisibility()
155
188
  })
156
189
  }