@solidrt/components 0.0.37 → 0.0.39
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 +10 -0
- package/package.json +3 -3
- package/src/button.tsx +5 -1
- package/src/image.tsx +30 -11
- package/src/press.ts +21 -1
- package/src/pressable.tsx +9 -2
- package/src/split-view.tsx +10 -7
package/README.md
CHANGED
|
@@ -194,10 +194,20 @@ Accepts `layout`, `style`, and all pointer event props, plus:
|
|
|
194
194
|
| Prop | Type | Description |
|
|
195
195
|
| ---------- | ------------------------ | ------------------------------------------------------------------------------------ |
|
|
196
196
|
| `src` | `string \| Uint8Array` | URL to fetch, or raw image bytes to decode |
|
|
197
|
+
| `fit` | `"fill" \| "cover" \| "contain" \| "none" \| "scale-down"` | How the image maps into the box (CSS object-fit, centered) |
|
|
197
198
|
| `fallback` | `string \| Uint8Array` | Source shown when `src` fails; if it also fails, the `backgroundColor` placeholder stays |
|
|
198
199
|
| `onLoad` | `() => void` | Called each time a source finishes loading |
|
|
199
200
|
| `onError` | `(err: unknown) => void` | Called when `src` fails to load or decode |
|
|
200
201
|
|
|
202
|
+
With `fit` the image fills whatever box `layout` gives the component - numbers,
|
|
203
|
+
`pct()`, or flex - and the fit decides how the pixels map into it (`"cover"` is
|
|
204
|
+
the ported-web-hero-image answer). Without `fit`, only *numeric* layout sizes
|
|
205
|
+
reach the image; anything else draws at intrinsic size.
|
|
206
|
+
|
|
207
|
+
```jsx
|
|
208
|
+
<Image src={hero} fit="cover" layout={{ width: pct(100), height: 240 }} />
|
|
209
|
+
```
|
|
210
|
+
|
|
201
211
|
A failing `src` is contained by the component (the fallback or placeholder
|
|
202
212
|
shows); it does not propagate to an outer `<Errored>` boundary.
|
|
203
213
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"qrcode-generator": "^2.0.4"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@solidjs/signals": "2.0.0-beta.
|
|
21
|
-
"@solidrt/core": "0.0.
|
|
20
|
+
"@solidjs/signals": "2.0.0-beta.26",
|
|
21
|
+
"@solidrt/core": "0.0.39"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/src/button.tsx
CHANGED
|
@@ -24,6 +24,7 @@ export interface ButtonProps {
|
|
|
24
24
|
size?: ButtonSize
|
|
25
25
|
onPress?: () => void
|
|
26
26
|
disabled?: boolean
|
|
27
|
+
ref?: (node: { id: number }) => void
|
|
27
28
|
layout?: LayoutProps
|
|
28
29
|
style?: StyleProps
|
|
29
30
|
}
|
|
@@ -93,7 +94,10 @@ export function Button(props: ButtonProps) {
|
|
|
93
94
|
|
|
94
95
|
return (
|
|
95
96
|
<view
|
|
96
|
-
ref={
|
|
97
|
+
ref={(n: { id: number }) => {
|
|
98
|
+
press.ref(n)
|
|
99
|
+
props.ref?.(n)
|
|
100
|
+
}}
|
|
97
101
|
repaintBoundary
|
|
98
102
|
flexDirection="row"
|
|
99
103
|
alignItems="center"
|
package/src/image.tsx
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { createImage, createEffect, Loading, Errored } from "@solidrt/core"
|
|
2
|
-
import type { ImageSource, LayoutProps, PointerProps } from "@solidrt/core"
|
|
1
|
+
import { createImage, createEffect, Loading, Errored, pct } from "@solidrt/core"
|
|
2
|
+
import type { ImageSource, LayoutProps, Pct, PointerProps, TextureProps } 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
|
+
/**
|
|
8
|
+
* How the image maps into the Image's box (CSS object-fit): "fill"
|
|
9
|
+
* stretches, "cover"/"none" crop, "contain"/"scale-down" letterbox,
|
|
10
|
+
* everything centered. Requires the Image to have a box: give `layout` a
|
|
11
|
+
* size in any form (numbers, `pct()`, flex). Without `fit` the image keeps
|
|
12
|
+
* its legacy sizing: numeric layout sizes are honored, anything else draws
|
|
13
|
+
* at intrinsic size.
|
|
14
|
+
*/
|
|
15
|
+
fit?: TextureProps["fit"]
|
|
7
16
|
/** Image source to show when `src` fails to load. If this also fails, only
|
|
8
17
|
* the `backgroundColor` placeholder remains. */
|
|
9
18
|
fallback?: ImageSource
|
|
@@ -22,11 +31,16 @@ export interface ImageProps extends PointerProps {
|
|
|
22
31
|
// keeps a broken fallback from escaping: the placeholder stays instead.
|
|
23
32
|
// Both fallbacks here take the error argument: an arity >= 1 fallback tells
|
|
24
33
|
// <Errored> the error is handled, so dev builds do not console.error it.
|
|
25
|
-
function FallbackTexture(props: {
|
|
34
|
+
function FallbackTexture(props: {
|
|
35
|
+
src: ImageSource
|
|
36
|
+
fit?: TextureProps["fit"]
|
|
37
|
+
width?: number | Pct
|
|
38
|
+
height?: number | Pct
|
|
39
|
+
}) {
|
|
26
40
|
let tex = createImage(() => props.src)
|
|
27
41
|
return (
|
|
28
42
|
<Errored fallback={(_err: unknown) => null}>
|
|
29
|
-
<texture src={tex()} width={props.width} height={props.height} />
|
|
43
|
+
<texture src={tex()} fit={props.fit} width={props.width} height={props.height} />
|
|
30
44
|
</Errored>
|
|
31
45
|
)
|
|
32
46
|
}
|
|
@@ -47,11 +61,14 @@ export function Image(props: ImageProps) {
|
|
|
47
61
|
error: (err: unknown) => props.onError?.(err),
|
|
48
62
|
})
|
|
49
63
|
|
|
50
|
-
// A texture sizes from its own width/height (not the box around it)
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
// A texture sizes from its own width/height (not the box around it). With
|
|
65
|
+
// `fit` the texture simply fills the Image's box and the fit maps the pixels
|
|
66
|
+
// into it. Without it, legacy sizing: numeric layout dimensions are
|
|
67
|
+
// forwarded, and omitting height lets the texture follow the image's
|
|
68
|
+
// intrinsic aspect ratio.
|
|
69
|
+
let texW = () => (props.fit != null ? pct(100) : typeof props.layout?.width === "number" ? props.layout.width : undefined)
|
|
70
|
+
let texH = () =>
|
|
71
|
+
props.fit != null ? pct(100) : typeof props.layout?.height === "number" ? props.layout.height : undefined
|
|
55
72
|
|
|
56
73
|
return (
|
|
57
74
|
<view
|
|
@@ -82,10 +99,12 @@ export function Image(props: ImageProps) {
|
|
|
82
99
|
<Loading fallback={null}>
|
|
83
100
|
<Errored
|
|
84
101
|
fallback={(_err: unknown) =>
|
|
85
|
-
props.fallback != null ?
|
|
102
|
+
props.fallback != null ? (
|
|
103
|
+
<FallbackTexture src={props.fallback} fit={props.fit} width={texW()} height={texH()} />
|
|
104
|
+
) : null
|
|
86
105
|
}
|
|
87
106
|
>
|
|
88
|
-
<texture src={src()} width={texW()} height={texH()} />
|
|
107
|
+
<texture src={src()} fit={props.fit} width={texW()} height={texH()} />
|
|
89
108
|
</Errored>
|
|
90
109
|
</Loading>
|
|
91
110
|
{hasBorder() ? (
|
package/src/press.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { createSignal, onSettled, getBoundingBoxViewport } from "@solidrt/core"
|
|
|
2
2
|
import type { PointerEvent } from "@solidrt/core"
|
|
3
3
|
import { claim, release } from "./arena"
|
|
4
4
|
|
|
5
|
+
// A live view of a recognizer's state, not a snapshot: both fields are getters,
|
|
6
|
+
// so a consumer that reads one inside a JSX prop or child expression tracks that
|
|
7
|
+
// signal there and nothing else re-runs. Read them in those positions, not
|
|
8
|
+
// eagerly into a local, or the read lands in whatever scope destructured it.
|
|
5
9
|
export type PressState = { pressed: boolean; hovered: boolean }
|
|
6
10
|
|
|
7
11
|
export interface PressOptions {
|
|
@@ -43,7 +47,23 @@ export function createPress(options: PressOptions) {
|
|
|
43
47
|
let active: number | null = null
|
|
44
48
|
let inside = false
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
// One stable object of getters, handed out as-is. Returning a fresh snapshot
|
|
51
|
+
// instead would read both signals at call time, making them dependencies of
|
|
52
|
+
// the caller's scope - and for render-prop children that scope is the one
|
|
53
|
+
// that builds the subtree, so a hover or press would rebuild it. A rebuild
|
|
54
|
+
// mid-gesture replaces a nested recognizer with a fresh one that never saw
|
|
55
|
+
// the down, so its up fires nothing: invisible with a mouse (hover settles
|
|
56
|
+
// long before the click) and fatal on touch, where the finger's arrival flips
|
|
57
|
+
// the ancestor's hover during the very gesture it is meant to recognize.
|
|
58
|
+
let live: PressState = {
|
|
59
|
+
get pressed() {
|
|
60
|
+
return pressed()
|
|
61
|
+
},
|
|
62
|
+
get hovered() {
|
|
63
|
+
return hovered()
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
let state = (): PressState => live
|
|
47
67
|
let ref = (n: { id: number }) => {
|
|
48
68
|
node = n
|
|
49
69
|
}
|
package/src/pressable.tsx
CHANGED
|
@@ -7,7 +7,9 @@ export type { PressState } from "./press"
|
|
|
7
7
|
|
|
8
8
|
export interface PressableProps extends PointerProps {
|
|
9
9
|
// children and style may be functions of the press state, so a caller can
|
|
10
|
-
// restyle on press/hover without wiring their own signals.
|
|
10
|
+
// restyle on press/hover without wiring their own signals. The state is live
|
|
11
|
+
// (getters, not a snapshot): read it inside a prop or child expression, never
|
|
12
|
+
// eagerly into a local, or the value is captured once where it was read.
|
|
11
13
|
children?: any | ((state: PressState) => any)
|
|
12
14
|
ref?: (node: { id: number }) => void
|
|
13
15
|
layout?: LayoutProps
|
|
@@ -29,8 +31,13 @@ export function Pressable(props: PressableProps) {
|
|
|
29
31
|
// ((state) => ...) passes through it intact because flatten only unwraps
|
|
30
32
|
// zero-arg functions.
|
|
31
33
|
let resolved = children(() => props.children)
|
|
34
|
+
// The render prop runs once: the state it receives is a live object of getters
|
|
35
|
+
// (see press.ts), so a press or hover updates only the props that read it
|
|
36
|
+
// rather than rebuilding this subtree - which is what keeps a nested
|
|
37
|
+
// recognizer's in-flight gesture alive.
|
|
32
38
|
let kids = () => {
|
|
33
|
-
|
|
39
|
+
// children()'s return type erases the render-prop variant, hence the any.
|
|
40
|
+
let c = resolved() as any
|
|
34
41
|
return typeof c === "function" ? c(press.state()) : c
|
|
35
42
|
}
|
|
36
43
|
|
package/src/split-view.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Show } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
3
|
-
import { theme } from "./theme"
|
|
4
3
|
import { policy } from "./policy"
|
|
5
4
|
|
|
6
5
|
export interface SplitViewProps {
|
|
@@ -20,11 +19,18 @@ const LIST_WIDTH = 320
|
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* A list-detail container driven by the layout policy: two-pane shows the list
|
|
23
|
-
* beside the detail
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* beside the detail, single-pane shows one pane at a time per `showDetail`.
|
|
23
|
+
* Keep pane state (selection, scroll) in the app, not in the panes: crossing
|
|
24
|
+
* a breakpoint re-arranges and can remount them.
|
|
26
25
|
* SplitView draws no chrome; a back affordance in the single-pane detail is
|
|
27
26
|
* the app's to render (fork on policy.layout, as the shell example does).
|
|
27
|
+
*
|
|
28
|
+
* Panes get no padding, max-width or alignment either - that is content, and
|
|
29
|
+
* SplitView cannot know the intended reading width. Give the single-pane
|
|
30
|
+
* detail the same treatment as the list (centered max-width column), otherwise
|
|
31
|
+
* crossing the breakpoint leaves the detail hugging the window's left edge
|
|
32
|
+
* while every other screen stays centered. Two-pane wants the opposite: the
|
|
33
|
+
* detail sits against the pane edge, since the pane already bounds its width.
|
|
28
34
|
*/
|
|
29
35
|
export function SplitView(props: SplitViewProps) {
|
|
30
36
|
return (
|
|
@@ -42,9 +48,6 @@ export function SplitView(props: SplitViewProps) {
|
|
|
42
48
|
<view width={props.listWidth ?? LIST_WIDTH} flexDirection="column">
|
|
43
49
|
{props.list}
|
|
44
50
|
</view>
|
|
45
|
-
<view width={1}>
|
|
46
|
-
<d-rect color={theme.color.border} />
|
|
47
|
-
</view>
|
|
48
51
|
<view flex={1} flexDirection="column">
|
|
49
52
|
{props.detail}
|
|
50
53
|
</view>
|