@solidrt/components 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
@@ -12,48 +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
- ### SafeArea
39
+ ### Window
18
40
 
19
- Wraps its children in a view that applies padding to avoid system UI intrusions (status bars, home indicators, notches, etc.).
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 { SafeArea } from "@solidrt/components"
44
+ import { Window } from "@solidrt/components"
23
45
 
24
46
  function App() {
25
47
  return (
26
- <window>
27
- <SafeArea top bottom>
28
- <text>Content clear of system UI</text>
29
- </SafeArea>
30
- </window>
48
+ <Window title="My App" style={{ backgroundColor: "#111" }}>
49
+ {/* ... */}
50
+ </Window>
31
51
  )
32
52
  }
33
53
  ```
34
54
 
35
- 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.
55
+ **Props**
56
+
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.
36
70
 
37
71
  ```jsx
38
- // top only
39
- <SafeArea bottom={false}>
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
+ ```
40
81
 
41
- // apply top and bottom insets, with a minimum of 16px each
42
- <SafeArea top={16} bottom={16}>
82
+ **Props**
43
83
 
44
- // all four edges
45
- <SafeArea top bottom left right>
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>
46
103
  ```
47
104
 
105
+ `layout` accepts all `LayoutProps` plus the font fields `fontFamily`, `fontSize`, `lineHeight`, `fontStyle`, `fontWeight`, `textAlign`, and `maxLines`.
106
+
48
107
  **Props**
49
108
 
50
- | Prop | Type | Default | Description |
51
- | ---------- | ------------------- | ------- | --------------------------------------------------- |
52
- | `top` | `boolean \| number` | `true` | Apply top inset. A number sets the minimum padding. |
53
- | `bottom` | `boolean \| number` | `true` | Apply bottom inset. A number sets the minimum padding. |
54
- | `left` | `boolean \| number` | `false` | Apply left inset. A number sets the minimum padding. |
55
- | `right` | `boolean \| number` | `false` | Apply right inset. A number sets the minimum padding. |
56
- | `children` | `any` | - | Content to render inside the safe area. |
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 |
57
137
 
58
138
  ### TextInput
59
139
 
@@ -71,7 +151,7 @@ function NameField() {
71
151
  onInput={setName}
72
152
  onSubmit={(v) => console.log("submitted", v)}
73
153
  placeholder="Your name"
74
- width={240}
154
+ layout={{ width: 240 }}
75
155
  />
76
156
  )
77
157
  }
@@ -79,39 +159,61 @@ function NameField() {
79
159
 
80
160
  **Props**
81
161
 
82
- | Prop | Type | Default | Description |
83
- | -------------- | -------------------------- | ------- | ------------------------------------------------------------ |
84
- | `value` | `string` | - | Controlled value. If omitted, the component is uncontrolled. |
85
- | `defaultValue` | `string` | `""` | Initial value for uncontrolled use |
86
- | `onInput` | `(value: string) => void` | - | Fires on every change |
87
- | `onSubmit` | `(value: string) => void` | - | Fires on Enter |
88
- | `onFocus` | `() => void` | - | Fires when the field gains focus |
89
- | `onBlur` | `() => void` | - | Fires when the field loses focus |
90
- | `placeholder` | `string` | - | Shown when value is empty and the field is not focused |
91
- | `maxLength` | `number` | - | Truncates input to this length |
92
- | `disabled` | `boolean` | `false` | Ignores pointer and key events when true |
93
- | `autoFocus` | `boolean` | `false` | Focuses on mount |
94
- | `width` | `number \| "auto" \| "N%"` | - | Field width |
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. |
95
176
 
96
- ### Image
177
+ ### SafeArea
97
178
 
98
- Loads and displays an image from a URL or raw bytes.
179
+ Wraps its children in a view that applies padding to avoid system UI intrusions (status bars, home indicators, notches, etc.).
99
180
 
100
181
  ```jsx
101
- import { Image } from "@solidrt/components"
182
+ import { SafeArea } from "@solidrt/components"
102
183
 
103
- function Avatar() {
104
- return <Image src="https://example.com/avatar.png" width={64} height={64} />
184
+ function App() {
185
+ return (
186
+ <Window>
187
+ <SafeArea top bottom>
188
+ <Text>Content clear of system UI</Text>
189
+ </SafeArea>
190
+ </Window>
191
+ )
105
192
  }
106
193
  ```
107
194
 
108
- **Props**
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}>
109
200
 
110
- Accepts all layout, transform, and pointer event props, plus:
201
+ // apply top and bottom insets, with a minimum of 16px each
202
+ <SafeArea top={16} bottom={16}>
111
203
 
112
- | Prop | Type | Description |
113
- | ----- | ---------------------- | -------------------------------------------- |
114
- | `src` | `string \| Uint8Array` | URL to fetch, or raw image bytes to decode |
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. |
115
217
 
116
218
  ## License
117
219
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/components",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
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.13",
18
- "@solidrt/core": "0.0.5"
17
+ "@solidjs/signals": "2.0.0-beta.14",
18
+ "@solidrt/core": "0.0.7"
19
19
  }
20
20
  }
package/src/image.tsx CHANGED
@@ -1,14 +1,16 @@
1
1
  import { createSignal, onCleanup } from "@solidjs/signals"
2
2
  import { decodeImage, createTexture } from "@solidrt/core/gpu"
3
- import type { LayoutProps, TransformProps, PointerProps } from "@solidrt/core"
3
+ import type { LayoutProps, PointerProps } from "@solidrt/core"
4
+ import type { StyleProps } from "./types"
4
5
 
5
- export interface ImageProps extends LayoutProps, TransformProps, PointerProps {
6
+ export interface ImageProps extends PointerProps {
6
7
  src: string | Uint8Array
8
+ layout?: LayoutProps
9
+ style?: StyleProps
7
10
  }
8
11
 
9
12
  //TODO onLoad, onError
10
13
  //TODO release texture in onCleanup
11
- //TODO forward all LayoutProps (currently only the common subset)
12
14
  export function Image(props: ImageProps) {
13
15
  let [res] = createSignal(async () => {
14
16
  let bytes: Uint8Array
@@ -24,6 +26,7 @@ export function Image(props: ImageProps) {
24
26
  })
25
27
 
26
28
  let src = () => res()?.id
29
+ let hasBorder = () => (props.style?.borderWidth ?? 0) > 0
27
30
 
28
31
  onCleanup(() => {
29
32
  //TODO release texture
@@ -31,36 +34,36 @@ export function Image(props: ImageProps) {
31
34
 
32
35
  return (
33
36
  <view
34
- width={props.width}
35
- height={props.height}
36
- flex={props.flex}
37
- flexGrow={props.flexGrow}
38
- flexShrink={props.flexShrink}
39
- alignSelf={props.alignSelf}
40
- margin={props.margin}
41
- marginTop={props.marginTop}
42
- marginBottom={props.marginBottom}
43
- marginLeft={props.marginLeft}
44
- marginRight={props.marginRight}
45
- padding={props.padding}
46
- position={props.position}
47
- top={props.top}
48
- right={props.right}
49
- bottom={props.bottom}
50
- left={props.left}
51
- x={props.x}
52
- y={props.y}
53
- scale={props.scale}
54
- rotate={props.rotate}
37
+ {...props.layout}
38
+ x={props.style?.x}
39
+ y={props.style?.y}
40
+ scale={props.style?.scale}
41
+ rotate={props.style?.rotate}
55
42
  onPointerEnter={props.onPointerEnter}
56
43
  onPointerLeave={props.onPointerLeave}
57
44
  onPointerDown={props.onPointerDown}
58
45
  onPointerUp={props.onPointerUp}
59
46
  onPointerMove={props.onPointerMove}
60
47
  onWheel={props.onWheel}
48
+ onFocus={props.onFocus}
49
+ onBlur={props.onBlur}
50
+ onKeyDown={props.onKeyDown}
51
+ onKeyUp={props.onKeyUp}
52
+ onTextInput={props.onTextInput}
61
53
  pointerEvents={props.pointerEvents}
62
54
  >
55
+ {props.style?.backgroundColor != null ? (
56
+ <d-rect color={props.style.backgroundColor} radius={props.style?.borderRadius} />
57
+ ) : null}
63
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}
64
67
  </view>
65
68
  )
66
69
  }
package/src/index.ts CHANGED
@@ -1,4 +1,8 @@
1
+ export { Window, type WindowProps } from "./window"
2
+ export { View, type ViewProps } from "./view"
3
+ export { Text, type TextProps } from "./text"
1
4
  export { Image, type ImageProps } from "./image"
2
5
  export { SafeArea } from "./safe-area"
3
6
  export { TextInput, type TextInputProps } from "./text-input"
4
- 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
@@ -6,6 +6,7 @@ export function SafeArea(props: {
6
6
  bottom?: boolean | number
7
7
  left?: boolean | number
8
8
  right?: boolean | number
9
+ relative?: boolean
9
10
  children?: any
10
11
  }) {
11
12
  let [insets, setInsets] = createSignal({ top: 0, bottom: 0, left: 0, right: 0 })
@@ -28,6 +29,7 @@ export function SafeArea(props: {
28
29
  <view
29
30
  flex={1}
30
31
  flexDirection="column"
32
+ position={props.relative !== false ? "relative" : undefined}
31
33
  marginTop={pad("top")}
32
34
  marginBottom={pad("bottom")}
33
35
  marginLeft={pad("left")}
@@ -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
- width?: Dimension
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 : theme.color.text)
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={theme.color.surface} radius={theme.radius.sm} />
149
+ <d-rect color={surfaceColor()} radius={borderRadius()} />
126
150
  <d-rect
127
151
  drawStyle="stroke"
128
- color={theme.color.border}
129
- strokeWidth={theme.borderWidth.sm}
130
- radius={theme.radius.sm}
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
+ }