@solidrt/components 0.0.27 → 0.0.29
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 +23 -6
- package/package.json +2 -2
- package/src/badge.tsx +7 -4
- package/src/button.tsx +8 -4
- package/src/image.tsx +40 -4
- package/src/modal.tsx +3 -1
- package/src/pressable.tsx +12 -2
- package/src/progress-bar.tsx +1 -2
- package/src/qrcode.tsx +1 -1
- package/src/radio.tsx +7 -4
- package/src/slider.tsx +1 -2
- package/src/spinner.tsx +1 -2
- package/src/switch.tsx +1 -1
- package/src/text-input.tsx +3 -4
- package/src/text.tsx +1 -1
- package/src/tooltip.tsx +7 -4
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ import { Text } from "@solidrt/components"
|
|
|
151
151
|
</Text>
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
`layout` accepts all `LayoutProps` plus the font fields `fontFamily`, `fontSize`, `lineHeight`, `fontStyle`, `fontWeight`, `textAlign`, and `maxLines`.
|
|
154
|
+
`layout` accepts all `LayoutProps` plus the font fields `fontFamily`, `fontSize`, `lineHeight`, `fontStyle`, `fontWeight`, `textAlign`, and `maxLines`. Note that `lineHeight` is a multiplier of `fontSize` (the theme uses 1.3-1.6), not a pixel value.
|
|
155
155
|
|
|
156
156
|
**Props**
|
|
157
157
|
|
|
@@ -166,13 +166,24 @@ Accepts all pointer event props, plus:
|
|
|
166
166
|
|
|
167
167
|
### Image
|
|
168
168
|
|
|
169
|
-
Loads and displays an image from a URL or raw bytes.
|
|
169
|
+
Loads and displays an image from a URL or raw bytes. URL loads are shared
|
|
170
|
+
runtime-wide: mounts of the same URL reuse one fetch and one texture, and the
|
|
171
|
+
bytes are cached on disk (fetched with `cache: "force-cache"` - no freshness
|
|
172
|
+
check, so use versioned URLs for content that changes). The runtime keeps
|
|
173
|
+
concurrent asset fetches polite with a per-host limit; a failed load rejects
|
|
174
|
+
the mounts sharing it and a later remount retries.
|
|
170
175
|
|
|
171
176
|
```jsx
|
|
172
177
|
import { Image } from "@solidrt/components"
|
|
173
178
|
|
|
174
179
|
function Avatar() {
|
|
175
|
-
return
|
|
180
|
+
return (
|
|
181
|
+
<Image
|
|
182
|
+
src="https://example.com/avatar.png"
|
|
183
|
+
fallback={PLACEHOLDER_PNG}
|
|
184
|
+
layout={{ width: 64, height: 64 }}
|
|
185
|
+
/>
|
|
186
|
+
)
|
|
176
187
|
}
|
|
177
188
|
```
|
|
178
189
|
|
|
@@ -180,9 +191,15 @@ function Avatar() {
|
|
|
180
191
|
|
|
181
192
|
Accepts `layout`, `style`, and all pointer event props, plus:
|
|
182
193
|
|
|
183
|
-
| Prop
|
|
184
|
-
|
|
|
185
|
-
| `src`
|
|
194
|
+
| Prop | Type | Description |
|
|
195
|
+
| ---------- | ------------------------ | ------------------------------------------------------------------------------------ |
|
|
196
|
+
| `src` | `string \| Uint8Array` | URL to fetch, or raw image bytes to decode |
|
|
197
|
+
| `fallback` | `string \| Uint8Array` | Source shown when `src` fails; if it also fails, the `backgroundColor` placeholder stays |
|
|
198
|
+
| `onLoad` | `() => void` | Called each time a source finishes loading |
|
|
199
|
+
| `onError` | `(err: unknown) => void` | Called when `src` fails to load or decode |
|
|
200
|
+
|
|
201
|
+
A failing `src` is contained by the component (the fallback or placeholder
|
|
202
|
+
shows); it does not propagate to an outer `<Errored>` boundary.
|
|
186
203
|
|
|
187
204
|
### TextInput
|
|
188
205
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@solidjs/signals": "2.0.0-beta.17",
|
|
21
|
-
"@solidrt/core": "0.0.
|
|
21
|
+
"@solidrt/core": "0.0.29"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/src/badge.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Show } from "@solidrt/core"
|
|
1
|
+
import { Show, children } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
3
3
|
import { theme } from "./theme"
|
|
4
4
|
import { typeStyle, lightOnDark } from "./typography"
|
|
@@ -38,7 +38,10 @@ export function Badge(props: BadgeProps) {
|
|
|
38
38
|
let bg = () => props.style?.backgroundColor ?? colors().bg
|
|
39
39
|
let fg = () => props.style?.color ?? colors().fg
|
|
40
40
|
let radius = () => props.style?.borderRadius ?? RADIUS
|
|
41
|
-
|
|
41
|
+
// Resolved once via children(): the typeof probe and the mount sites must
|
|
42
|
+
// share one build - reading the raw getter again would orphan native nodes.
|
|
43
|
+
let resolved = children(() => props.children)
|
|
44
|
+
let isText = () => typeof resolved() === "string" || typeof resolved() === "number"
|
|
42
45
|
let labelOnDark = () => lightOnDark(fg(), bg())
|
|
43
46
|
|
|
44
47
|
return (
|
|
@@ -58,9 +61,9 @@ export function Badge(props: BadgeProps) {
|
|
|
58
61
|
opacity={props.style?.opacity}
|
|
59
62
|
>
|
|
60
63
|
<d-rect color={bg()} radius={radius()} />
|
|
61
|
-
<Show when={isText()} fallback={
|
|
64
|
+
<Show when={isText()} fallback={resolved()}>
|
|
62
65
|
<text color={fg()} {...typeStyle("label", labelOnDark())}>
|
|
63
|
-
{
|
|
66
|
+
{resolved()}
|
|
64
67
|
</text>
|
|
65
68
|
</Show>
|
|
66
69
|
</view>
|
package/src/button.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Show } from "@solidrt/core"
|
|
1
|
+
import { Show, children } from "@solidrt/core"
|
|
2
2
|
import { Pressable, type PressState } from "./pressable"
|
|
3
3
|
import { theme } from "./theme"
|
|
4
4
|
import { policy } from "./policy"
|
|
@@ -55,7 +55,11 @@ export function Button(props: ButtonProps) {
|
|
|
55
55
|
: colors().fill)
|
|
56
56
|
let radius = () => props.style?.borderRadius ?? theme.radius.sm
|
|
57
57
|
let label = () => (props.disabled ? theme.color.textMuted : colors().label)
|
|
58
|
-
|
|
58
|
+
// Resolved once via children(): reading the raw children getter builds a new
|
|
59
|
+
// subtree per read, so the typeof probe and the two mount sites below must
|
|
60
|
+
// share this single memoized build (an unmounted build leaks native nodes).
|
|
61
|
+
let resolved = children(() => props.children)
|
|
62
|
+
let isText = () => typeof resolved() === "string" || typeof resolved() === "number"
|
|
59
63
|
// The label's polarity against the idle fill: onPrimary on a saturated fill
|
|
60
64
|
// is light-on-dark even in a light theme, so it needs the low-DPI weight
|
|
61
65
|
// compensation there too.
|
|
@@ -92,9 +96,9 @@ export function Button(props: ButtonProps) {
|
|
|
92
96
|
scale: (props.style?.scale ?? 1) * (s.pressed && policy.motion !== "none" ? 0.97 : 1),
|
|
93
97
|
})}
|
|
94
98
|
>
|
|
95
|
-
<Show when={isText()} fallback={
|
|
99
|
+
<Show when={isText()} fallback={resolved()}>
|
|
96
100
|
<text color={label()} {...typeStyle("body", labelOnDark())}>
|
|
97
|
-
{
|
|
101
|
+
{resolved()}
|
|
98
102
|
</text>
|
|
99
103
|
</Show>
|
|
100
104
|
</Pressable>
|
package/src/image.tsx
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
import { createImage, Loading } from "@solidrt/core"
|
|
2
|
-
import type { LayoutProps, PointerProps } from "@solidrt/core"
|
|
1
|
+
import { createImage, createEffect, Loading, Errored } from "@solidrt/core"
|
|
2
|
+
import type { ImageSource, LayoutProps, PointerProps } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps } from "./types"
|
|
4
4
|
|
|
5
5
|
export interface ImageProps extends PointerProps {
|
|
6
6
|
src: string | Uint8Array
|
|
7
|
+
/** Image source to show when `src` fails to load. If this also fails, only
|
|
8
|
+
* the `backgroundColor` placeholder remains. */
|
|
9
|
+
fallback?: ImageSource
|
|
10
|
+
/** Called each time a source finishes loading (including reloads on a
|
|
11
|
+
* reactive `src`). */
|
|
12
|
+
onLoad?: () => void
|
|
13
|
+
/** Called when `src` fails to load or decode. The fallback (if any) is shown
|
|
14
|
+
* regardless; without one the error stays contained here instead of
|
|
15
|
+
* propagating to an outer boundary. */
|
|
16
|
+
onError?: (err: unknown) => void
|
|
7
17
|
layout?: LayoutProps
|
|
8
18
|
style?: StyleProps
|
|
9
19
|
}
|
|
10
20
|
|
|
11
|
-
//
|
|
21
|
+
// Renders the fallback source when the main one failed. Its own <Errored>
|
|
22
|
+
// keeps a broken fallback from escaping: the placeholder stays instead.
|
|
23
|
+
// Both fallbacks here take the error argument: an arity >= 1 fallback tells
|
|
24
|
+
// <Errored> the error is handled, so dev builds do not console.error it.
|
|
25
|
+
function FallbackTexture(props: { src: ImageSource; width?: number; height?: number }) {
|
|
26
|
+
let tex = createImage(() => props.src)
|
|
27
|
+
return (
|
|
28
|
+
<Errored fallback={(_err: unknown) => null}>
|
|
29
|
+
<texture src={tex()} width={props.width} height={props.height} />
|
|
30
|
+
</Errored>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
export function Image(props: ImageProps) {
|
|
13
35
|
// createImage fetches/decodes/uploads, swaps the texture when src changes, and
|
|
14
36
|
// frees it on cleanup, returning the texture id. Pass an accessor so a reactive
|
|
@@ -17,6 +39,14 @@ export function Image(props: ImageProps) {
|
|
|
17
39
|
let src = createImage(() => props.src)
|
|
18
40
|
let hasBorder = () => (props.style?.borderWidth ?? 0) > 0
|
|
19
41
|
|
|
42
|
+
// Load/error callbacks ride a separate effect read of the same async value;
|
|
43
|
+
// the error handler also keeps the failure out of the console when the
|
|
44
|
+
// render side already contains it with a fallback.
|
|
45
|
+
createEffect(() => src(), {
|
|
46
|
+
effect: () => props.onLoad?.(),
|
|
47
|
+
error: (err: unknown) => props.onError?.(err),
|
|
48
|
+
})
|
|
49
|
+
|
|
20
50
|
// A texture sizes from its own width/height (not the box around it), so the
|
|
21
51
|
// numeric layout dimensions are forwarded to it. Omitting height lets the
|
|
22
52
|
// texture follow the image's intrinsic aspect ratio.
|
|
@@ -50,7 +80,13 @@ export function Image(props: ImageProps) {
|
|
|
50
80
|
<d-rect color={props.style?.backgroundColor} radius={props.style?.borderRadius} />
|
|
51
81
|
) : null}
|
|
52
82
|
<Loading fallback={null}>
|
|
53
|
-
<
|
|
83
|
+
<Errored
|
|
84
|
+
fallback={(_err: unknown) =>
|
|
85
|
+
props.fallback != null ? <FallbackTexture src={props.fallback} width={texW()} height={texH()} /> : null
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
<texture src={src()} width={texW()} height={texH()} />
|
|
89
|
+
</Errored>
|
|
54
90
|
</Loading>
|
|
55
91
|
{hasBorder() ? (
|
|
56
92
|
<d-rect
|
package/src/modal.tsx
CHANGED
|
@@ -20,7 +20,9 @@ export interface ModalProps {
|
|
|
20
20
|
* escapes the layout and stacking of its surrounding tree. It fills the window
|
|
21
21
|
* with a dimming backdrop and centers `children` on top. Control visibility by
|
|
22
22
|
* mounting/unmounting it, e.g. `<Show when={open()}><Modal .../></Show>`: the
|
|
23
|
-
* portal's onCleanup removes it when the surrounding scope disposes.
|
|
23
|
+
* portal's onCleanup removes it when the surrounding scope disposes. The
|
|
24
|
+
* gating signal must start false: portals cannot mount during the app's
|
|
25
|
+
* initial render (see createPortal), so a modal visible at startup throws.
|
|
24
26
|
*
|
|
25
27
|
* Pressing the backdrop calls `onClose`; pressing the content does not. This
|
|
26
28
|
* works because pointer events dispatch to the whole hit path with no
|
package/src/pressable.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal } from "@
|
|
1
|
+
import { createSignal, children } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps, PointerEvent, PointerProps } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps } from "./types"
|
|
4
4
|
|
|
@@ -26,7 +26,17 @@ export function Pressable(props: PressableProps) {
|
|
|
26
26
|
|
|
27
27
|
let state = (): PressState => ({ pressed: pressed(), hovered: hovered() })
|
|
28
28
|
let style = () => (typeof props.style === "function" ? props.style(state()) : props.style)
|
|
29
|
-
|
|
29
|
+
// Element-valued props build a fresh native subtree on every read, and a
|
|
30
|
+
// subtree that is never inserted is never destroyed - probing the raw getter
|
|
31
|
+
// with typeof would orphan one full copy per evaluation. children() memoizes
|
|
32
|
+
// the resolve so probe and mount share one build; a render-prop child
|
|
33
|
+
// ((state) => ...) passes through it intact because flatten only unwraps
|
|
34
|
+
// zero-arg functions.
|
|
35
|
+
let resolved = children(() => props.children)
|
|
36
|
+
let kids = () => {
|
|
37
|
+
let c = resolved()
|
|
38
|
+
return typeof c === "function" ? c(state()) : c
|
|
39
|
+
}
|
|
30
40
|
|
|
31
41
|
let handleDown = (e: PointerEvent) => {
|
|
32
42
|
if (e.button != null && e.button !== 0) return
|
package/src/progress-bar.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { createSignal } from "@
|
|
2
|
-
import { onFrame, onLayout, getBoundingBox, Show } from "@solidrt/core"
|
|
1
|
+
import { createSignal, onFrame, onLayout, getBoundingBox, Show } from "@solidrt/core"
|
|
3
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
4
3
|
import { theme } from "./theme"
|
|
5
4
|
import { policy } from "./policy"
|
package/src/qrcode.tsx
CHANGED
package/src/radio.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal, createContext, useContext, Show } from "@solidrt/core"
|
|
1
|
+
import { createSignal, createContext, useContext, Show, children } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
3
3
|
import { Pressable } from "./pressable"
|
|
4
4
|
import { theme } from "./theme"
|
|
@@ -72,7 +72,10 @@ export function Radio(props: RadioProps) {
|
|
|
72
72
|
let selected = () => ctx.value() === props.value
|
|
73
73
|
let disabled = () => props.disabled || ctx.disabled()
|
|
74
74
|
let ringColor = () => (selected() ? theme.color.primary : theme.color.border)
|
|
75
|
-
|
|
75
|
+
// Resolved once via children(): the typeof probe and the mount sites must
|
|
76
|
+
// share one build - reading the raw getter again would orphan native nodes.
|
|
77
|
+
let resolved = children(() => props.children)
|
|
78
|
+
let isText = () => typeof resolved() === "string" || typeof resolved() === "number"
|
|
76
79
|
|
|
77
80
|
let ring = () => Math.round(RING * densityScale())
|
|
78
81
|
// Inner dot inset as a fraction of the ring, so it scales with the density.
|
|
@@ -91,9 +94,9 @@ export function Radio(props: RadioProps) {
|
|
|
91
94
|
<d-oval x={inset()} y={inset()} w={ring() - inset() * 2} h={ring() - inset() * 2} color={theme.color.primary} />
|
|
92
95
|
</Show>
|
|
93
96
|
</view>
|
|
94
|
-
<Show when={isText()} fallback={
|
|
97
|
+
<Show when={isText()} fallback={resolved()}>
|
|
95
98
|
<text color={theme.color.text} {...typeStyle("body")}>
|
|
96
|
-
{
|
|
99
|
+
{resolved()}
|
|
97
100
|
</text>
|
|
98
101
|
</Show>
|
|
99
102
|
</Pressable>
|
package/src/slider.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { createSignal } from "@
|
|
2
|
-
import { getBoundingBox, onLayout, setPointerCapture } from "@solidrt/core"
|
|
1
|
+
import { createSignal, getBoundingBox, onLayout, setPointerCapture } from "@solidrt/core"
|
|
3
2
|
import type { LayoutProps, PointerEvent } from "@solidrt/core"
|
|
4
3
|
import { theme } from "./theme"
|
|
5
4
|
import { densityScale } from "./policy"
|
package/src/spinner.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { createSignal } from "@
|
|
2
|
-
import { onFrame, Show } from "@solidrt/core"
|
|
1
|
+
import { createSignal, onFrame, Show } from "@solidrt/core"
|
|
3
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
4
3
|
import { theme } from "./theme"
|
|
5
4
|
import { policy } from "./policy"
|
package/src/switch.tsx
CHANGED
package/src/text-input.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { createEffect, createSignal, onCleanup } from "@
|
|
2
|
-
import { measureText, setFocus } from "@solidrt/core"
|
|
1
|
+
import { createEffect, createSignal, onCleanup, measureText, setFocus } from "@solidrt/core"
|
|
3
2
|
import { createCaretScroll, createTextBuffer } from "@solidrt/core/text-input"
|
|
4
|
-
import type { LayoutProps } from "@solidrt/core"
|
|
3
|
+
import type { Color, Gradient, LayoutProps } from "@solidrt/core"
|
|
5
4
|
import type { StyleProps } from "./types"
|
|
6
5
|
import { theme } from "./theme"
|
|
7
6
|
import { policy } from "./policy"
|
|
@@ -166,7 +165,7 @@ export function TextInput(props: TextInputProps) {
|
|
|
166
165
|
}),
|
|
167
166
|
)
|
|
168
167
|
|
|
169
|
-
let textStyle = (color:
|
|
168
|
+
let textStyle = (color: Color | Gradient) => ({
|
|
170
169
|
w: TEXT_SHAPE_WIDTH,
|
|
171
170
|
fontSize: fontSize(),
|
|
172
171
|
lineHeight: theme.text.body.lineHeight,
|
package/src/text.tsx
CHANGED
package/src/tooltip.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal, onCleanup, createPortal, onLayout, getBoundingBox, Show, env } from "@solidrt/core"
|
|
1
|
+
import { createSignal, onCleanup, createPortal, onLayout, getBoundingBox, Show, env, children } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps, PointerEvent } from "@solidrt/core"
|
|
3
3
|
import { theme } from "./theme"
|
|
4
4
|
import { policy } from "./policy"
|
|
@@ -66,7 +66,10 @@ export function Tooltip(props: TooltipProps) {
|
|
|
66
66
|
let cur = pos()
|
|
67
67
|
if (!cur || cur.x !== x || cur.y !== y) setPos({ x, y })
|
|
68
68
|
})
|
|
69
|
-
|
|
69
|
+
// Resolved once via children(): the typeof probe and the mount sites must
|
|
70
|
+
// share one build - reading the raw getter again would orphan native nodes.
|
|
71
|
+
let content = children(() => props.content)
|
|
72
|
+
let isText = () => typeof content() === "string" || typeof content() === "number"
|
|
70
73
|
return createPortal(
|
|
71
74
|
<view
|
|
72
75
|
ref={(n: { id: number }) => (bubble = n)}
|
|
@@ -83,9 +86,9 @@ export function Tooltip(props: TooltipProps) {
|
|
|
83
86
|
pointerEvents="none"
|
|
84
87
|
>
|
|
85
88
|
<d-rect color={theme.color.surfaceAlt} radius={theme.radius.sm} />
|
|
86
|
-
<Show when={isText()} fallback={
|
|
89
|
+
<Show when={isText()} fallback={content()}>
|
|
87
90
|
<text color={theme.color.text} {...typeStyle("body")}>
|
|
88
|
-
{
|
|
91
|
+
{content()}
|
|
89
92
|
</text>
|
|
90
93
|
</Show>
|
|
91
94
|
<d-rect
|