@solidrt/components 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 +164 -27
- package/package.json +3 -3
- package/src/image.tsx +69 -0
- package/src/index.ts +6 -1
- package/src/safe-area.tsx +13 -5
- package/src/text-input.tsx +39 -14
- package/src/text.tsx +70 -0
- package/src/types.ts +33 -0
- package/src/view.tsx +54 -0
- package/src/window.tsx +31 -0
package/README.md
CHANGED
|
@@ -12,33 +12,128 @@ bun add @solidrt/components
|
|
|
12
12
|
|
|
13
13
|
Appearance (colors, spacing, border, font size) is controlled via the shared theme. Call `setTheme` from `@solidrt/components` to override defaults.
|
|
14
14
|
|
|
15
|
+
## Layout and style
|
|
16
|
+
|
|
17
|
+
Most components group their props into two objects:
|
|
18
|
+
|
|
19
|
+
- `layout` - properties that feed the layout engine (flexbox/grid, sizing, padding, margin, position). Changing them triggers a relayout. This is the core `LayoutProps` set.
|
|
20
|
+
- `style` - paint-only properties that never affect layout: `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, and the transform `x`, `y`, `rotate`, `scale`.
|
|
21
|
+
|
|
22
|
+
Event handlers (`onPointerDown`, `onKeyDown`, etc.) are passed directly as top-level props.
|
|
23
|
+
|
|
24
|
+
`StyleProps`:
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Description |
|
|
27
|
+
| ----------------- | ------------------------------------------ | ------------------------------------ |
|
|
28
|
+
| `color` | `string` | Text color (used by `Text`). |
|
|
29
|
+
| `backgroundColor` | `string` | Fill color. |
|
|
30
|
+
| `borderColor` | `string` | Stroke color (when `borderWidth` set). |
|
|
31
|
+
| `borderWidth` | `number` | Border stroke width. |
|
|
32
|
+
| `borderRadius` | `number \| [number, number, number, number]` | Corner radius. |
|
|
33
|
+
| `x` / `y` | `number` | Translation offset. |
|
|
34
|
+
| `rotate` | `number` | Rotation. |
|
|
35
|
+
| `scale` | `number` | Scale factor. |
|
|
36
|
+
|
|
15
37
|
## Components
|
|
16
38
|
|
|
17
|
-
###
|
|
39
|
+
### Window
|
|
18
40
|
|
|
19
|
-
|
|
41
|
+
The root surface of an app. Accepts `layout` and the `backgroundColor` from `style`; a window cannot be transformed or bordered.
|
|
20
42
|
|
|
21
43
|
```jsx
|
|
22
|
-
import {
|
|
44
|
+
import { Window } from "@solidrt/components"
|
|
23
45
|
|
|
24
46
|
function App() {
|
|
25
47
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</SafeArea>
|
|
30
|
-
</window>
|
|
48
|
+
<Window title="My App" style={{ backgroundColor: "#111" }}>
|
|
49
|
+
{/* ... */}
|
|
50
|
+
</Window>
|
|
31
51
|
)
|
|
32
52
|
}
|
|
33
53
|
```
|
|
34
54
|
|
|
35
55
|
**Props**
|
|
36
56
|
|
|
37
|
-
| Prop
|
|
38
|
-
|
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
57
|
+
| Prop | Type | Default | Description |
|
|
58
|
+
| ------------ | ------------- | ------- | ------------------------------------ |
|
|
59
|
+
| `title` | `string` | - | Window title. |
|
|
60
|
+
| `fullscreen` | `boolean` | - | Open fullscreen. |
|
|
61
|
+
| `vsync` | `boolean` | - | Enable vsync. |
|
|
62
|
+
| `fps` | `boolean` | - | Show an FPS counter. |
|
|
63
|
+
| `layout` | `LayoutProps` | - | Layout properties. |
|
|
64
|
+
| `style` | `StyleProps` | - | Only `backgroundColor` is applied. |
|
|
65
|
+
| `children` | `any` | - | Content. |
|
|
66
|
+
|
|
67
|
+
### View
|
|
68
|
+
|
|
69
|
+
A general-purpose box. Spreads `layout` onto the underlying view, applies the transform from `style`, and draws a background and/or border when those style props are set.
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
import { View } from "@solidrt/components"
|
|
73
|
+
|
|
74
|
+
<View
|
|
75
|
+
layout={{ padding: 16, flexDirection: "column", gap: 8 }}
|
|
76
|
+
style={{ backgroundColor: "#222", borderRadius: 8 }}
|
|
77
|
+
>
|
|
78
|
+
{/* ... */}
|
|
79
|
+
</View>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Props**
|
|
83
|
+
|
|
84
|
+
Accepts all pointer event props, plus:
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Description |
|
|
87
|
+
| ---------- | ----------------------------- | --------------------- |
|
|
88
|
+
| `layout` | `LayoutProps` | Layout properties. |
|
|
89
|
+
| `style` | `StyleProps` | Paint properties. |
|
|
90
|
+
| `ref` | `(node: { id: number }) => void` | Node reference. |
|
|
91
|
+
| `children` | `any` | Content. |
|
|
92
|
+
|
|
93
|
+
### Text
|
|
94
|
+
|
|
95
|
+
Renders text inside a layout box. Font properties live in `layout` (they affect measurement); `color` lives in `style`.
|
|
96
|
+
|
|
97
|
+
```jsx
|
|
98
|
+
import { Text } from "@solidrt/components"
|
|
99
|
+
|
|
100
|
+
<Text layout={{ fontSize: 18, maxLines: 2 }} style={{ color: "#fff" }}>
|
|
101
|
+
Hello
|
|
102
|
+
</Text>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`layout` accepts all `LayoutProps` plus the font fields `fontFamily`, `fontSize`, `lineHeight`, `fontStyle`, `fontWeight`, `textAlign`, and `maxLines`.
|
|
106
|
+
|
|
107
|
+
**Props**
|
|
108
|
+
|
|
109
|
+
Accepts all pointer event props, plus:
|
|
110
|
+
|
|
111
|
+
| Prop | Type | Description |
|
|
112
|
+
| ---------- | ----------------------------- | --------------------------------- |
|
|
113
|
+
| `layout` | `TextLayoutProps` | Layout properties plus font fields. |
|
|
114
|
+
| `style` | `StyleProps` | `color` and transform. |
|
|
115
|
+
| `ref` | `(node: { id: number }) => void` | Node reference. |
|
|
116
|
+
| `children` | `any` | Text content. |
|
|
117
|
+
|
|
118
|
+
### Image
|
|
119
|
+
|
|
120
|
+
Loads and displays an image from a URL or raw bytes.
|
|
121
|
+
|
|
122
|
+
```jsx
|
|
123
|
+
import { Image } from "@solidrt/components"
|
|
124
|
+
|
|
125
|
+
function Avatar() {
|
|
126
|
+
return <Image src="https://example.com/avatar.png" layout={{ width: 64, height: 64 }} />
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Props**
|
|
131
|
+
|
|
132
|
+
Accepts `layout`, `style`, and all pointer event props, plus:
|
|
133
|
+
|
|
134
|
+
| Prop | Type | Description |
|
|
135
|
+
| ----- | ---------------------- | ------------------------------------------ |
|
|
136
|
+
| `src` | `string \| Uint8Array` | URL to fetch, or raw image bytes to decode |
|
|
42
137
|
|
|
43
138
|
### TextInput
|
|
44
139
|
|
|
@@ -56,7 +151,7 @@ function NameField() {
|
|
|
56
151
|
onInput={setName}
|
|
57
152
|
onSubmit={(v) => console.log("submitted", v)}
|
|
58
153
|
placeholder="Your name"
|
|
59
|
-
|
|
154
|
+
layout={{ width: 240 }}
|
|
60
155
|
/>
|
|
61
156
|
)
|
|
62
157
|
}
|
|
@@ -64,19 +159,61 @@ function NameField() {
|
|
|
64
159
|
|
|
65
160
|
**Props**
|
|
66
161
|
|
|
67
|
-
| Prop | Type
|
|
68
|
-
| -------------- |
|
|
69
|
-
| `value` | `string`
|
|
70
|
-
| `defaultValue` | `string`
|
|
71
|
-
| `onInput` | `(value: string) => void`
|
|
72
|
-
| `onSubmit` | `(value: string) => void`
|
|
73
|
-
| `onFocus` | `() => void`
|
|
74
|
-
| `onBlur` | `() => void`
|
|
75
|
-
| `placeholder` | `string`
|
|
76
|
-
| `maxLength` | `number`
|
|
77
|
-
| `disabled` | `boolean`
|
|
78
|
-
| `autoFocus` | `boolean`
|
|
79
|
-
| `
|
|
162
|
+
| Prop | Type | Default | Description |
|
|
163
|
+
| -------------- | ------------------------- | ------- | ------------------------------------------------------------ |
|
|
164
|
+
| `value` | `string` | - | Controlled value. If omitted, the component is uncontrolled. |
|
|
165
|
+
| `defaultValue` | `string` | `""` | Initial value for uncontrolled use. |
|
|
166
|
+
| `onInput` | `(value: string) => void` | - | Fires on every change. |
|
|
167
|
+
| `onSubmit` | `(value: string) => void` | - | Fires on Enter. |
|
|
168
|
+
| `onFocus` | `() => void` | - | Fires when the field gains focus. |
|
|
169
|
+
| `onBlur` | `() => void` | - | Fires when the field loses focus. |
|
|
170
|
+
| `placeholder` | `string` | - | Shown when value is empty and the field is not focused. |
|
|
171
|
+
| `maxLength` | `number` | - | Truncates input to this length. |
|
|
172
|
+
| `disabled` | `boolean` | `false` | Ignores pointer and key events when true. |
|
|
173
|
+
| `autoFocus` | `boolean` | `false` | Focuses on mount. |
|
|
174
|
+
| `layout` | `LayoutProps` | - | Layout properties (e.g. `width`). |
|
|
175
|
+
| `style` | `StyleProps` | - | Overrides theme colors, border, and radius. |
|
|
176
|
+
|
|
177
|
+
### SafeArea
|
|
178
|
+
|
|
179
|
+
Wraps its children in a view that applies padding to avoid system UI intrusions (status bars, home indicators, notches, etc.).
|
|
180
|
+
|
|
181
|
+
```jsx
|
|
182
|
+
import { SafeArea } from "@solidrt/components"
|
|
183
|
+
|
|
184
|
+
function App() {
|
|
185
|
+
return (
|
|
186
|
+
<Window>
|
|
187
|
+
<SafeArea top bottom>
|
|
188
|
+
<Text>Content clear of system UI</Text>
|
|
189
|
+
</SafeArea>
|
|
190
|
+
</Window>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Top and bottom insets are applied by default. Pass `false` to opt out of an edge, or a number to apply the inset with a minimum padding.
|
|
196
|
+
|
|
197
|
+
```jsx
|
|
198
|
+
// top only
|
|
199
|
+
<SafeArea bottom={false}>
|
|
200
|
+
|
|
201
|
+
// apply top and bottom insets, with a minimum of 16px each
|
|
202
|
+
<SafeArea top={16} bottom={16}>
|
|
203
|
+
|
|
204
|
+
// all four edges
|
|
205
|
+
<SafeArea top bottom left right>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Props**
|
|
209
|
+
|
|
210
|
+
| Prop | Type | Default | Description |
|
|
211
|
+
| ---------- | ------------------- | ------- | ------------------------------------------------------ |
|
|
212
|
+
| `top` | `boolean \| number` | `true` | Apply top inset. A number sets the minimum padding. |
|
|
213
|
+
| `bottom` | `boolean \| number` | `true` | Apply bottom inset. A number sets the minimum padding. |
|
|
214
|
+
| `left` | `boolean \| number` | `false` | Apply left inset. A number sets the minimum padding. |
|
|
215
|
+
| `right` | `boolean \| number` | `false` | Apply right inset. A number sets the minimum padding. |
|
|
216
|
+
| `children` | `any` | - | Content to render inside the safe area. |
|
|
80
217
|
|
|
81
218
|
## License
|
|
82
219
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"LICENSE"
|
|
15
15
|
],
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@solidjs/signals": "2.0.0-beta.
|
|
18
|
-
"@solidrt/core": "0.0.
|
|
17
|
+
"@solidjs/signals": "2.0.0-beta.14",
|
|
18
|
+
"@solidrt/core": "0.0.6"
|
|
19
19
|
}
|
|
20
20
|
}
|
package/src/image.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createSignal, onCleanup } from "@solidjs/signals"
|
|
2
|
+
import { decodeImage, createTexture } from "@solidrt/core/gpu"
|
|
3
|
+
import type { LayoutProps, PointerProps } from "@solidrt/core"
|
|
4
|
+
import type { StyleProps } from "./types"
|
|
5
|
+
|
|
6
|
+
export interface ImageProps extends PointerProps {
|
|
7
|
+
src: string | Uint8Array
|
|
8
|
+
layout?: LayoutProps
|
|
9
|
+
style?: StyleProps
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//TODO onLoad, onError
|
|
13
|
+
//TODO release texture in onCleanup
|
|
14
|
+
export function Image(props: ImageProps) {
|
|
15
|
+
let [res] = createSignal(async () => {
|
|
16
|
+
let bytes: Uint8Array
|
|
17
|
+
if (typeof props.src === "string") {
|
|
18
|
+
let response = await fetch(props.src)
|
|
19
|
+
bytes = await response.bytes()
|
|
20
|
+
} else {
|
|
21
|
+
bytes = props.src
|
|
22
|
+
}
|
|
23
|
+
let { data, width, height } = decodeImage(bytes)
|
|
24
|
+
let id = createTexture(data, width, height)
|
|
25
|
+
return { id, width, height }
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
let src = () => res()?.id
|
|
29
|
+
let hasBorder = () => (props.style?.borderWidth ?? 0) > 0
|
|
30
|
+
|
|
31
|
+
onCleanup(() => {
|
|
32
|
+
//TODO release texture
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<view
|
|
37
|
+
{...props.layout}
|
|
38
|
+
x={props.style?.x}
|
|
39
|
+
y={props.style?.y}
|
|
40
|
+
scale={props.style?.scale}
|
|
41
|
+
rotate={props.style?.rotate}
|
|
42
|
+
onPointerEnter={props.onPointerEnter}
|
|
43
|
+
onPointerLeave={props.onPointerLeave}
|
|
44
|
+
onPointerDown={props.onPointerDown}
|
|
45
|
+
onPointerUp={props.onPointerUp}
|
|
46
|
+
onPointerMove={props.onPointerMove}
|
|
47
|
+
onWheel={props.onWheel}
|
|
48
|
+
onFocus={props.onFocus}
|
|
49
|
+
onBlur={props.onBlur}
|
|
50
|
+
onKeyDown={props.onKeyDown}
|
|
51
|
+
onKeyUp={props.onKeyUp}
|
|
52
|
+
onTextInput={props.onTextInput}
|
|
53
|
+
pointerEvents={props.pointerEvents}
|
|
54
|
+
>
|
|
55
|
+
{props.style?.backgroundColor != null ? (
|
|
56
|
+
<d-rect color={props.style.backgroundColor} radius={props.style?.borderRadius} />
|
|
57
|
+
) : null}
|
|
58
|
+
<texture src={src()} />
|
|
59
|
+
{hasBorder() ? (
|
|
60
|
+
<d-rect
|
|
61
|
+
drawStyle="stroke"
|
|
62
|
+
color={props.style?.borderColor ?? "transparent"}
|
|
63
|
+
strokeWidth={props.style?.borderWidth}
|
|
64
|
+
radius={props.style?.borderRadius}
|
|
65
|
+
/>
|
|
66
|
+
) : null}
|
|
67
|
+
</view>
|
|
68
|
+
)
|
|
69
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export { Window, type WindowProps } from "./window"
|
|
2
|
+
export { View, type ViewProps } from "./view"
|
|
3
|
+
export { Text, type TextProps } from "./text"
|
|
4
|
+
export { Image, type ImageProps } from "./image"
|
|
1
5
|
export { SafeArea } from "./safe-area"
|
|
2
6
|
export { TextInput, type TextInputProps } from "./text-input"
|
|
3
|
-
export { theme, setTheme, type Theme } from "./theme"
|
|
7
|
+
export { theme, setTheme, type Theme } from "./theme"
|
|
8
|
+
export type { StyleProps, TextLayoutProps } from "./types"
|
package/src/safe-area.tsx
CHANGED
|
@@ -2,8 +2,11 @@ import { createSignal } from "@solidjs/signals"
|
|
|
2
2
|
import { onResize } from "@solidrt/core"
|
|
3
3
|
|
|
4
4
|
export function SafeArea(props: {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
top?: boolean | number
|
|
6
|
+
bottom?: boolean | number
|
|
7
|
+
left?: boolean | number
|
|
8
|
+
right?: boolean | number
|
|
9
|
+
relative?: boolean
|
|
7
10
|
children?: any
|
|
8
11
|
}) {
|
|
9
12
|
let [insets, setInsets] = createSignal({ top: 0, bottom: 0, left: 0, right: 0 })
|
|
@@ -15,13 +18,18 @@ export function SafeArea(props: {
|
|
|
15
18
|
right: width - safeArea.right,
|
|
16
19
|
})
|
|
17
20
|
})
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
let pad = (edge: "top" | "bottom" | "left" | "right") => {
|
|
22
|
+
let defaultOn = edge === "top" || edge === "bottom"
|
|
23
|
+
let p = props[edge] ?? defaultOn
|
|
24
|
+
if (p === false) return 0
|
|
25
|
+
if (p === true) return insets()[edge]
|
|
26
|
+
return Math.max(insets()[edge], p as number)
|
|
27
|
+
}
|
|
21
28
|
return (
|
|
22
29
|
<view
|
|
23
30
|
flex={1}
|
|
24
31
|
flexDirection="column"
|
|
32
|
+
position={props.relative !== false ? "relative" : undefined}
|
|
25
33
|
marginTop={pad("top")}
|
|
26
34
|
marginBottom={pad("bottom")}
|
|
27
35
|
marginLeft={pad("left")}
|
package/src/text-input.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { createSignal, onCleanup } from "@solidjs/signals"
|
|
2
|
-
import { measureText, setFocus } from "@solidrt/core"
|
|
1
|
+
import { createSignal, flush, onCleanup } from "@solidjs/signals"
|
|
2
|
+
import { getBoundingBox, measureText, onLayout, setFocus } from "@solidrt/core"
|
|
3
|
+
import type { LayoutProps } from "@solidrt/core"
|
|
4
|
+
import type { StyleProps } from "./types"
|
|
3
5
|
import { theme } from "./theme"
|
|
4
6
|
|
|
5
|
-
type Dimension = number | "auto" | `${number}%`
|
|
6
|
-
|
|
7
7
|
export interface TextInputProps {
|
|
8
8
|
value?: string
|
|
9
9
|
defaultValue?: string
|
|
@@ -17,7 +17,8 @@ export interface TextInputProps {
|
|
|
17
17
|
disabled?: boolean
|
|
18
18
|
autoFocus?: boolean
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
layout?: LayoutProps
|
|
21
|
+
style?: StyleProps
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// V1: single-line, caret-at-end only, no selection, no mid-string editing.
|
|
@@ -27,8 +28,10 @@ export function TextInput(props: TextInputProps) {
|
|
|
27
28
|
let [internalValue, setInternalValue] = createSignal(props.defaultValue ?? "")
|
|
28
29
|
let [focused, setFocused] = createSignal(false)
|
|
29
30
|
let [caretOn, setCaretOn] = createSignal(true)
|
|
31
|
+
let [viewportWidth, setViewportWidth] = createSignal(0)
|
|
30
32
|
|
|
31
33
|
let node: { id: number } | undefined
|
|
34
|
+
let viewport: { id: number } | undefined
|
|
32
35
|
let blinkId: any = null
|
|
33
36
|
|
|
34
37
|
let value = () => props.value ?? internalValue()
|
|
@@ -87,13 +90,30 @@ export function TextInput(props: TextInputProps) {
|
|
|
87
90
|
if (blinkId != null) clearInterval(blinkId)
|
|
88
91
|
})
|
|
89
92
|
|
|
93
|
+
// Style overrides fall back to theme defaults.
|
|
94
|
+
let textColor = () => props.style?.color ?? theme.color.text
|
|
95
|
+
let surfaceColor = () => props.style?.backgroundColor ?? theme.color.surface
|
|
96
|
+
let borderColor = () => props.style?.borderColor ?? theme.color.border
|
|
97
|
+
let borderWidth = () => props.style?.borderWidth ?? theme.borderWidth.sm
|
|
98
|
+
let borderRadius = () => props.style?.borderRadius ?? theme.radius.sm
|
|
99
|
+
|
|
90
100
|
let showPlaceholder = () => !focused() && value().length === 0 && (props.placeholder ?? "").length > 0
|
|
91
101
|
let displayText = () => (showPlaceholder() ? (props.placeholder ?? "") : value())
|
|
92
|
-
let displayColor = () => (showPlaceholder() ? theme.color.textMuted :
|
|
102
|
+
let displayColor = () => (showPlaceholder() ? theme.color.textMuted : textColor())
|
|
103
|
+
|
|
104
|
+
// Viewport width is the inner scroll container's own laid-out width, read
|
|
105
|
+
// after layout. This works for numeric, "auto" and "%" widths alike (no need
|
|
106
|
+
// to subtract padding: the padding lives on the outer view). getBoundingBox
|
|
107
|
+
// is a snapshot, so we push it into a signal from onLayout. The flush drains
|
|
108
|
+
// the resulting scrollX update synchronously, before paint, so the scroll
|
|
109
|
+
// tracks the width in the same frame instead of trailing it by one.
|
|
110
|
+
onLayout(() => {
|
|
111
|
+
if (!viewport) return
|
|
112
|
+
let w = getBoundingBox(viewport)?.width ?? 0
|
|
113
|
+
if (w !== viewportWidth()) setViewportWidth(w)
|
|
114
|
+
flush()
|
|
115
|
+
})
|
|
93
116
|
|
|
94
|
-
// V1: viewport width derived from numeric props.width minus padding.
|
|
95
|
-
// For "auto" / "%" widths, fall back to 0 (no scroll, caret may overflow).
|
|
96
|
-
let viewportWidth = () => (typeof props.width === "number" ? props.width - 2 * theme.spacing.md : 0)
|
|
97
117
|
let caretWidth = () => (focused() && !showPlaceholder() ? 1 : 0)
|
|
98
118
|
let scrollX = () => {
|
|
99
119
|
if (showPlaceholder()) return 0
|
|
@@ -111,25 +131,30 @@ export function TextInput(props: TextInputProps) {
|
|
|
111
131
|
}}
|
|
112
132
|
flexDirection="row"
|
|
113
133
|
alignItems="center"
|
|
114
|
-
width={props.width}
|
|
115
134
|
paddingLeft={theme.spacing.md}
|
|
116
135
|
paddingRight={theme.spacing.md}
|
|
117
136
|
paddingTop={theme.spacing.sm}
|
|
118
137
|
paddingBottom={theme.spacing.sm}
|
|
138
|
+
{...props.layout}
|
|
139
|
+
x={props.style?.x}
|
|
140
|
+
y={props.style?.y}
|
|
141
|
+
scale={props.style?.scale}
|
|
142
|
+
rotate={props.style?.rotate}
|
|
119
143
|
onPointerDown={handlePointerDown}
|
|
120
144
|
onFocus={handleFocus}
|
|
121
145
|
onBlur={handleBlur}
|
|
122
146
|
onKeyDown={handleKeyDown}
|
|
123
147
|
onTextInput={handleTextInput}
|
|
124
148
|
>
|
|
125
|
-
<d-rect color={
|
|
149
|
+
<d-rect color={surfaceColor()} radius={borderRadius()} />
|
|
126
150
|
<d-rect
|
|
127
151
|
drawStyle="stroke"
|
|
128
|
-
color={
|
|
129
|
-
strokeWidth={
|
|
130
|
-
radius={
|
|
152
|
+
color={borderColor()}
|
|
153
|
+
strokeWidth={borderWidth()}
|
|
154
|
+
radius={borderRadius()}
|
|
131
155
|
/>
|
|
132
156
|
<view
|
|
157
|
+
ref={(n: { id: number }) => (viewport = n)}
|
|
133
158
|
flex={1}
|
|
134
159
|
flexDirection="row"
|
|
135
160
|
alignItems="center"
|
package/src/text.tsx
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createMemo } from "@solidjs/signals"
|
|
2
|
+
import type { PointerProps } from "@solidrt/core"
|
|
3
|
+
import type { StyleProps, TextLayoutProps } from "./types"
|
|
4
|
+
|
|
5
|
+
export interface TextProps extends PointerProps {
|
|
6
|
+
children?: any
|
|
7
|
+
ref?: (node: { id: number }) => void
|
|
8
|
+
layout?: TextLayoutProps
|
|
9
|
+
style?: StyleProps
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Font keys live in layout (they affect measurement) but belong on the inner
|
|
13
|
+
// <text> node, not the box <view>. Strip them out before spreading layout.
|
|
14
|
+
const FONT_KEYS = [
|
|
15
|
+
"fontFamily",
|
|
16
|
+
"fontSize",
|
|
17
|
+
"lineHeight",
|
|
18
|
+
"fontStyle",
|
|
19
|
+
"fontWeight",
|
|
20
|
+
"textAlign",
|
|
21
|
+
"maxLines",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export function Text(props: TextProps) {
|
|
25
|
+
let box = createMemo(() => {
|
|
26
|
+
let l = props.layout
|
|
27
|
+
if (!l) return {}
|
|
28
|
+
let out: Record<string, unknown> = {}
|
|
29
|
+
for (let key in l) {
|
|
30
|
+
if (!FONT_KEYS.includes(key)) out[key] = (l as Record<string, unknown>)[key]
|
|
31
|
+
}
|
|
32
|
+
return out
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<view
|
|
37
|
+
ref={props.ref}
|
|
38
|
+
{...box()}
|
|
39
|
+
x={props.style?.x}
|
|
40
|
+
y={props.style?.y}
|
|
41
|
+
scale={props.style?.scale}
|
|
42
|
+
rotate={props.style?.rotate}
|
|
43
|
+
onPointerEnter={props.onPointerEnter}
|
|
44
|
+
onPointerLeave={props.onPointerLeave}
|
|
45
|
+
onPointerDown={props.onPointerDown}
|
|
46
|
+
onPointerUp={props.onPointerUp}
|
|
47
|
+
onPointerMove={props.onPointerMove}
|
|
48
|
+
onWheel={props.onWheel}
|
|
49
|
+
onFocus={props.onFocus}
|
|
50
|
+
onBlur={props.onBlur}
|
|
51
|
+
onKeyDown={props.onKeyDown}
|
|
52
|
+
onKeyUp={props.onKeyUp}
|
|
53
|
+
onTextInput={props.onTextInput}
|
|
54
|
+
pointerEvents={props.pointerEvents}
|
|
55
|
+
>
|
|
56
|
+
<text
|
|
57
|
+
color={props.style?.color}
|
|
58
|
+
fontFamily={props.layout?.fontFamily}
|
|
59
|
+
fontSize={props.layout?.fontSize}
|
|
60
|
+
lineHeight={props.layout?.lineHeight}
|
|
61
|
+
fontStyle={props.layout?.fontStyle}
|
|
62
|
+
fontWeight={props.layout?.fontWeight}
|
|
63
|
+
textAlign={props.layout?.textAlign}
|
|
64
|
+
maxLines={props.layout?.maxLines}
|
|
65
|
+
>
|
|
66
|
+
{props.children}
|
|
67
|
+
</text>
|
|
68
|
+
</view>
|
|
69
|
+
)
|
|
70
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Color, LayoutProps } from "@solidrt/core"
|
|
2
|
+
|
|
3
|
+
// The split between layout and style follows one rule: layout properties feed
|
|
4
|
+
// into Taffy and changing them triggers a relayout; style properties are
|
|
5
|
+
// paint-only and can change without affecting layout.
|
|
6
|
+
|
|
7
|
+
// Paint-only props. None of these change the box Taffy computes. Borders are
|
|
8
|
+
// drawn as a stroke overlay (not part of the box model), and the transform is
|
|
9
|
+
// applied at paint time, so both live here.
|
|
10
|
+
export interface StyleProps {
|
|
11
|
+
color?: Color
|
|
12
|
+
backgroundColor?: Color
|
|
13
|
+
borderColor?: Color
|
|
14
|
+
borderWidth?: number
|
|
15
|
+
borderRadius?: number | [number, number, number, number]
|
|
16
|
+
x?: number
|
|
17
|
+
y?: number
|
|
18
|
+
rotate?: number
|
|
19
|
+
scale?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Text shaping affects measurement, so font props belong with layout rather
|
|
23
|
+
// than style. These end up on the inner <text> node, while the box layout
|
|
24
|
+
// fields go on the wrapping <view>.
|
|
25
|
+
export interface TextLayoutProps extends LayoutProps {
|
|
26
|
+
fontFamily?: "sans" | "mono" | (string & {})
|
|
27
|
+
fontSize?: number
|
|
28
|
+
lineHeight?: number
|
|
29
|
+
fontStyle?: "normal" | "italic"
|
|
30
|
+
fontWeight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
|
31
|
+
textAlign?: "left" | "right" | "center" | "justify"
|
|
32
|
+
maxLines?: number
|
|
33
|
+
}
|
package/src/view.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { LayoutProps, PointerProps } from "@solidrt/core"
|
|
2
|
+
import type { StyleProps } from "./types"
|
|
3
|
+
|
|
4
|
+
export interface ViewProps extends PointerProps {
|
|
5
|
+
children?: any
|
|
6
|
+
ref?: (node: { id: number }) => void
|
|
7
|
+
layout?: LayoutProps
|
|
8
|
+
style?: StyleProps
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function View(props: ViewProps) {
|
|
12
|
+
let hasBackground = () =>
|
|
13
|
+
props.style?.backgroundColor != null || props.style?.borderRadius != null
|
|
14
|
+
let hasBorder = () => (props.style?.borderWidth ?? 0) > 0
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<view
|
|
18
|
+
ref={props.ref}
|
|
19
|
+
{...props.layout}
|
|
20
|
+
x={props.style?.x}
|
|
21
|
+
y={props.style?.y}
|
|
22
|
+
scale={props.style?.scale}
|
|
23
|
+
rotate={props.style?.rotate}
|
|
24
|
+
onPointerEnter={props.onPointerEnter}
|
|
25
|
+
onPointerLeave={props.onPointerLeave}
|
|
26
|
+
onPointerDown={props.onPointerDown}
|
|
27
|
+
onPointerUp={props.onPointerUp}
|
|
28
|
+
onPointerMove={props.onPointerMove}
|
|
29
|
+
onWheel={props.onWheel}
|
|
30
|
+
onFocus={props.onFocus}
|
|
31
|
+
onBlur={props.onBlur}
|
|
32
|
+
onKeyDown={props.onKeyDown}
|
|
33
|
+
onKeyUp={props.onKeyUp}
|
|
34
|
+
onTextInput={props.onTextInput}
|
|
35
|
+
pointerEvents={props.pointerEvents}
|
|
36
|
+
>
|
|
37
|
+
{hasBackground() ? (
|
|
38
|
+
<d-rect
|
|
39
|
+
color={props.style?.backgroundColor ?? "transparent"}
|
|
40
|
+
radius={props.style?.borderRadius}
|
|
41
|
+
/>
|
|
42
|
+
) : null}
|
|
43
|
+
{props.children}
|
|
44
|
+
{hasBorder() ? (
|
|
45
|
+
<d-rect
|
|
46
|
+
drawStyle="stroke"
|
|
47
|
+
color={props.style?.borderColor ?? "transparent"}
|
|
48
|
+
strokeWidth={props.style?.borderWidth}
|
|
49
|
+
radius={props.style?.borderRadius}
|
|
50
|
+
/>
|
|
51
|
+
) : null}
|
|
52
|
+
</view>
|
|
53
|
+
)
|
|
54
|
+
}
|
package/src/window.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LayoutProps } from "@solidrt/core"
|
|
2
|
+
import type { StyleProps } from "./types"
|
|
3
|
+
|
|
4
|
+
export interface WindowProps {
|
|
5
|
+
children?: any
|
|
6
|
+
title?: string
|
|
7
|
+
fullscreen?: boolean
|
|
8
|
+
vsync?: boolean
|
|
9
|
+
fps?: boolean
|
|
10
|
+
layout?: LayoutProps
|
|
11
|
+
style?: StyleProps
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A window is the root surface: it can't be transformed or bordered, so only
|
|
15
|
+
// the paint-only backgroundColor from style applies here.
|
|
16
|
+
export function Window(props: WindowProps) {
|
|
17
|
+
return (
|
|
18
|
+
<window
|
|
19
|
+
{...props.layout}
|
|
20
|
+
title={props.title}
|
|
21
|
+
fullscreen={props.fullscreen}
|
|
22
|
+
vsync={props.vsync}
|
|
23
|
+
fps={props.fps}
|
|
24
|
+
>
|
|
25
|
+
{props.style?.backgroundColor != null ? (
|
|
26
|
+
<d-rect color={props.style.backgroundColor} />
|
|
27
|
+
) : null}
|
|
28
|
+
{props.children}
|
|
29
|
+
</window>
|
|
30
|
+
)
|
|
31
|
+
}
|