@solidrt/components 0.0.35 → 0.0.37

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/src/slider.tsx CHANGED
@@ -1,5 +1,6 @@
1
- import { createSignal, getBoundingBox, onLayout, setPointerCapture } from "@solidrt/core"
1
+ import { createSignal, getBoundingBox, onLayout, onSettled } from "@solidrt/core"
2
2
  import type { LayoutProps, PointerEvent } from "@solidrt/core"
3
+ import { release, steal } from "./arena"
3
4
  import { theme } from "./theme"
4
5
  import { densityScale } from "./policy"
5
6
  import type { StyleProps } from "./types"
@@ -25,9 +26,9 @@ const THUMB = 20
25
26
  let clamp = (x: number, lo: number, hi: number) => (x < lo ? lo : x > hi ? hi : x)
26
27
 
27
28
  // A horizontal slider. The groove fills up to the thumb; dragging or pressing
28
- // the track sets the value from the pointer x. No pointer capture, so a drag
29
- // that leaves the track stops updating. Controlled via value/onChange, or
30
- // uncontrolled via defaultValue.
29
+ // the track sets the value from the pointer x. Moves arrive on the frozen down
30
+ // path, so a drag keeps updating when the pointer drifts off the track.
31
+ // Controlled via value/onChange, or uncontrolled via defaultValue.
31
32
  export function Slider(props: SliderProps) {
32
33
  let min = () => props.min ?? 0
33
34
  let max = () => props.max ?? 100
@@ -35,7 +36,7 @@ export function Slider(props: SliderProps) {
35
36
  let value = () => (props.value !== undefined ? props.value : internal())
36
37
 
37
38
  let track: { id: number } | undefined
38
- let dragging = false
39
+ let active: number | null = null
39
40
 
40
41
  let height = () => Math.round(HEIGHT * densityScale())
41
42
  let thumb = () => Math.round(THUMB * densityScale())
@@ -68,22 +69,31 @@ export function Slider(props: SliderProps) {
68
69
  commit(clamp(raw, min(), max()))
69
70
  }
70
71
 
72
+ let endDrag = () => {
73
+ if (active != null) {
74
+ release(active, owner)
75
+ active = null
76
+ }
77
+ }
78
+ let owner = { cancel: endDrag }
79
+
80
+ // An unmount mid-drag must not leave a resolved claim behind.
81
+ onSettled(() => endDrag)
82
+
71
83
  let handleDown = (e: PointerEvent) => {
72
- if (props.disabled) return
73
- // Claim the drag: stopPropagation keeps an ancestor scroller from starting a
74
- // scroll, and pointer capture routes moves/up here even when the pointer
75
- // drifts off the track.
76
- e.stopPropagation()
77
- if (track) setPointerCapture(track.id, e.pointerId)
78
- dragging = true
84
+ if (props.disabled || active != null) return
85
+ // A down on the track is unambiguously a slider drag: resolve the arena
86
+ // outright so an ancestor scroller's pan cannot take the pointer over.
87
+ steal(e.pointerId, owner)
88
+ active = e.pointerId
79
89
  setFromClientX(e.clientX)
80
90
  }
81
91
  let handleMove = (e: PointerEvent) => {
82
- if (!dragging) return
92
+ if (active !== e.pointerId) return
83
93
  setFromClientX(e.clientX)
84
94
  }
85
- let handleUp = () => {
86
- dragging = false
95
+ let handleUp = (e: PointerEvent) => {
96
+ if (active === e.pointerId) endDrag()
87
97
  }
88
98
 
89
99
  return (
package/src/switch.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import { createSignal } from "@solidrt/core"
1
+ import { createSignal, Show } from "@solidrt/core"
2
2
  import type { LayoutProps } from "@solidrt/core"
3
- import { Pressable } from "./pressable"
3
+ import { createPress } from "./press"
4
4
  import { theme } from "./theme"
5
5
  import { densityScale } from "./policy"
6
6
  import type { StyleProps } from "./types"
@@ -24,7 +24,7 @@ const PAD = 2
24
24
 
25
25
  // A toggle. Track fills with primary when on, surfaceAlt when off; the thumb
26
26
  // slides across. Controlled via value/onChange, or uncontrolled via
27
- // defaultValue. Built on Pressable, so disabled takes no pointer events.
27
+ // defaultValue. When disabled, it takes no pointer events at all.
28
28
  export function Switch(props: SwitchProps) {
29
29
  let [internal, setInternal] = createSignal(props.defaultValue ?? false)
30
30
  let on = () => (props.value !== undefined ? props.value : internal())
@@ -34,25 +34,45 @@ export function Switch(props: SwitchProps) {
34
34
  if (props.value === undefined) setInternal(next)
35
35
  props.onChange?.(next)
36
36
  }
37
+ let press = createPress({ onPress: toggle })
37
38
 
38
39
  let w = () => Math.round(W * densityScale())
39
40
  let h = () => Math.round(H * densityScale())
40
41
  let thumb = () => h() - PAD * 2
41
42
 
43
+ let style = () => ({
44
+ backgroundColor: on() ? theme.color.primary : theme.color.surfaceAlt,
45
+ borderRadius: h() / 2,
46
+ ...props.style,
47
+ })
48
+
42
49
  return (
43
- <Pressable
44
- onPress={toggle}
45
- disabled={props.disabled}
46
- layout={{ width: w(), height: h(), ...props.layout }}
47
- style={{
48
- backgroundColor: on() ? theme.color.primary : theme.color.surfaceAlt,
49
- borderRadius: h() / 2,
50
- ...props.style,
51
- }}
50
+ <view
51
+ ref={press.ref}
52
+ repaintBoundary
53
+ width={w()}
54
+ height={h()}
55
+ {...props.layout}
56
+ x={style().x}
57
+ y={style().y}
58
+ scale={style().scale}
59
+ rotate={style().rotate}
60
+ opacity={style().opacity}
61
+ {...press.handlers}
62
+ pointerEvents={props.disabled ? "none" : undefined}
52
63
  >
64
+ <d-rect color={style().backgroundColor ?? "transparent"} radius={style().borderRadius} />
53
65
  <view position="absolute" top={PAD} left={PAD} x={on() ? w() - thumb() - PAD * 2 : 0}>
54
66
  <d-oval w={thumb()} h={thumb()} color={theme.color.onPrimary} />
55
67
  </view>
56
- </Pressable>
68
+ <Show when={(style().borderWidth ?? 0) > 0}>
69
+ <d-rect
70
+ drawStyle="stroke"
71
+ color={style().borderColor ?? "transparent"}
72
+ strokeWidth={style().borderWidth}
73
+ radius={style().borderRadius}
74
+ />
75
+ </Show>
76
+ </view>
57
77
  )
58
78
  }
@@ -96,10 +96,10 @@ export function TextInput(props: TextInputProps) {
96
96
  } else if (e.key === "Delete") {
97
97
  buffer.deleteForward()
98
98
  setCaretOn(true)
99
- } else if (e.key === "Left") {
99
+ } else if (e.key === "ArrowLeft") {
100
100
  buffer.move("left")
101
101
  setCaretOn(true)
102
- } else if (e.key === "Right") {
102
+ } else if (e.key === "ArrowRight") {
103
103
  buffer.move("right")
104
104
  setCaretOn(true)
105
105
  } else if (e.key === "Home") {
@@ -108,7 +108,7 @@ export function TextInput(props: TextInputProps) {
108
108
  } else if (e.key === "End") {
109
109
  buffer.move("end")
110
110
  setCaretOn(true)
111
- } else if (e.key === "Return" || e.key === "Enter") {
111
+ } else if (e.key === "Enter") {
112
112
  props.onSubmit?.(value())
113
113
  setFocus(null)
114
114
  } else if (e.key === "Escape") {
package/src/types.ts CHANGED
@@ -36,6 +36,14 @@ export interface StyleProps {
36
36
  opacity?: number
37
37
  }
38
38
 
39
+ // One choice in an options list, shared by the single-choice controls
40
+ // (Select, SegmentedControl). Lives here so those components stay independent
41
+ // of each other: shared shapes go through this module, never a sibling import.
42
+ export interface Option {
43
+ value: unknown
44
+ label: string
45
+ }
46
+
39
47
  // Text shaping affects measurement, so font props belong with layout rather
40
48
  // than style. These end up on the inner <text> node, while the box layout
41
49
  // fields go on the wrapping <view>.