@opencode/ui 0.0.0-dev-20716 → 0.0.0-dev-20717
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/dist/navigation/segmented-control/segmented-control.d.ts +21 -2
- package/package.json +2 -1
- package/src/actions/button/button.css +5 -5
- package/src/actions/icon-button/icon-button.css +1 -5
- package/src/navigation/segmented-control/segmented-control.css +37 -4
- package/src/navigation/segmented-control/segmented-control.tsx +194 -14
- package/src/navigation/tabs/tabs-current.css +1 -5
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type
|
|
1
|
+
import { type Transition } from "@kitlangton/solid-motion";
|
|
2
|
+
import { type ComponentProps, type JSX, type ParentProps } from "solid-js";
|
|
3
3
|
import "./segmented-control.css";
|
|
4
4
|
type OnChange = (value: string | null) => void;
|
|
5
|
+
export declare const SEGMENTED_CONTROL_SPRING: Transition;
|
|
6
|
+
export type OffsetElement = {
|
|
7
|
+
offsetLeft: number;
|
|
8
|
+
offsetWidth: number;
|
|
9
|
+
offsetParent: Element | OffsetElement | null;
|
|
10
|
+
getBoundingClientRect: () => {
|
|
11
|
+
left: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare function measureOffset(root: OffsetElement, button: OffsetElement): {
|
|
15
|
+
x: number;
|
|
16
|
+
width: number;
|
|
17
|
+
};
|
|
5
18
|
export type SegmentedControlProps = Omit<ComponentProps<"div">, "onChange"> & ParentProps<{
|
|
6
19
|
/** Selected value when controlled (including `null` when empty). Omit key for uncontrolled. */
|
|
7
20
|
value?: string | null;
|
|
@@ -11,6 +24,8 @@ export type SegmentedControlProps = Omit<ComponentProps<"div">, "onChange"> & Pa
|
|
|
11
24
|
/** When true, clicking the active segment clears selection (`onChange(null)`). Default false. */
|
|
12
25
|
allowDeselect?: boolean;
|
|
13
26
|
disabled?: boolean;
|
|
27
|
+
/** Motion transition for the active indicator pill. */
|
|
28
|
+
transition?: Transition;
|
|
14
29
|
}>;
|
|
15
30
|
export declare function SegmentedControl(props: SegmentedControlProps): JSX.Element;
|
|
16
31
|
export type SegmentedControlItemProps = Omit<ComponentProps<"button">, "type" | "children"> & ParentProps<{
|
|
@@ -18,4 +33,8 @@ export type SegmentedControlItemProps = Omit<ComponentProps<"button">, "type" |
|
|
|
18
33
|
children: JSX.Element;
|
|
19
34
|
}>;
|
|
20
35
|
export declare function SegmentedControlItem(props: SegmentedControlItemProps): JSX.Element;
|
|
36
|
+
export declare const SegmentedControlV2: typeof SegmentedControl;
|
|
37
|
+
export declare const SegmentedControlItemV2: typeof SegmentedControlItem;
|
|
38
|
+
export type SegmentedControlV2Props = SegmentedControlProps;
|
|
39
|
+
export type SegmentedControlItemV2Props = SegmentedControlItemProps;
|
|
21
40
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode/ui",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20717",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -244,6 +244,7 @@
|
|
|
244
244
|
"vite-plugin-solid": "2.11.10"
|
|
245
245
|
},
|
|
246
246
|
"dependencies": {
|
|
247
|
+
"@kitlangton/solid-motion": "0.2.2",
|
|
247
248
|
"@kobalte/core": "0.13.13",
|
|
248
249
|
"@pierre/diffs": "1.5.1",
|
|
249
250
|
"@solid-primitives/event-listener": "2.4.6",
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
@import "../submit.css";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[data-component="button-v2"] {
|
|
4
4
|
-webkit-font-smoothing: antialiased;
|
|
5
5
|
-moz-osx-font-smoothing: grayscale;
|
|
6
|
-
text-rendering: geometricPrecision;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
[data-component="button-v2"] {
|
|
10
6
|
position: relative;
|
|
11
7
|
display: inline-flex;
|
|
12
8
|
align-items: center;
|
|
@@ -26,6 +22,10 @@
|
|
|
26
22
|
outline: none;
|
|
27
23
|
}
|
|
28
24
|
|
|
25
|
+
[data-component="button-v2"] > * {
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
29
|
[data-component="button-v2"]:focus {
|
|
30
30
|
outline: none;
|
|
31
31
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
@import "../submit.css";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[data-component="icon-button-v2"] {
|
|
4
4
|
-webkit-font-smoothing: antialiased;
|
|
5
5
|
-moz-osx-font-smoothing: grayscale;
|
|
6
|
-
text-rendering: geometricPrecision;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
[data-component="icon-button-v2"] {
|
|
10
6
|
display: inline-flex;
|
|
11
7
|
align-items: center;
|
|
12
8
|
justify-content: center;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
[data-slot="segmented-control-v2"] {
|
|
6
|
+
position: relative;
|
|
7
|
+
isolation: isolate;
|
|
6
8
|
box-sizing: border-box;
|
|
7
9
|
display: flex;
|
|
8
10
|
flex-direction: row;
|
|
@@ -16,9 +18,28 @@
|
|
|
16
18
|
border-radius: 6px;
|
|
17
19
|
box-shadow: 0 0 0 0.5px var(--v2-border-border-base);
|
|
18
20
|
flex: none;
|
|
21
|
+
user-select: none;
|
|
22
|
+
-webkit-user-select: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-slot="segmented-control-v2-indicator"] {
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 0;
|
|
28
|
+
bottom: 0;
|
|
29
|
+
left: 0;
|
|
30
|
+
border-radius: 6px;
|
|
31
|
+
background: var(--v2-background-bg-base);
|
|
32
|
+
box-shadow: 0 0 0 0.5px var(--v2-border-border-strong);
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
user-select: none;
|
|
35
|
+
-webkit-user-select: none;
|
|
36
|
+
z-index: 0;
|
|
37
|
+
will-change: transform, width;
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
[data-slot="segmented-control-v2-item"] {
|
|
41
|
+
position: relative;
|
|
42
|
+
z-index: 1;
|
|
22
43
|
box-sizing: border-box;
|
|
23
44
|
display: flex;
|
|
24
45
|
flex-direction: column;
|
|
@@ -34,20 +55,26 @@
|
|
|
34
55
|
background: transparent;
|
|
35
56
|
box-shadow: none;
|
|
36
57
|
cursor: pointer;
|
|
58
|
+
user-select: none;
|
|
59
|
+
-webkit-user-select: none;
|
|
37
60
|
font-style: normal;
|
|
38
61
|
font-weight: 440;
|
|
39
62
|
font-size: 13px;
|
|
40
|
-
line-height:
|
|
63
|
+
line-height: 100%;
|
|
41
64
|
letter-spacing: -0.04px;
|
|
42
65
|
font-variant-numeric: tabular-nums;
|
|
43
66
|
font-variation-settings: "slnt" 0;
|
|
44
67
|
color: var(--v2-text-text-muted);
|
|
45
68
|
transition:
|
|
69
|
+
color 0.15s ease,
|
|
46
70
|
background-color 0.12s ease,
|
|
47
|
-
color 0.12s ease,
|
|
48
71
|
box-shadow 0.12s ease;
|
|
49
72
|
}
|
|
50
73
|
|
|
74
|
+
[data-slot="segmented-control-v2-item"]:where(:hover:not(:disabled):not([data-pressed])) {
|
|
75
|
+
color: var(--v2-text-text-base);
|
|
76
|
+
}
|
|
77
|
+
|
|
51
78
|
[data-slot="segmented-control-v2-item"]:where(:disabled) {
|
|
52
79
|
cursor: not-allowed;
|
|
53
80
|
opacity: 0.45;
|
|
@@ -60,12 +87,15 @@
|
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
[data-slot="segmented-control-v2-item"]:where([data-pressed]) {
|
|
63
|
-
background: var(--v2-background-bg-base);
|
|
64
90
|
color: var(--v2-text-text-base);
|
|
65
|
-
box-shadow: 0 0 0 0.5px var(--v2-border-border-strong);
|
|
66
91
|
z-index: 1;
|
|
67
92
|
}
|
|
68
93
|
|
|
94
|
+
[data-slot="segmented-control-v2"]:not([data-ready]) [data-slot="segmented-control-v2-item"]:where([data-pressed]) {
|
|
95
|
+
background: var(--v2-background-bg-base);
|
|
96
|
+
box-shadow: 0 0 0 0.5px var(--v2-border-border-strong);
|
|
97
|
+
}
|
|
98
|
+
|
|
69
99
|
[data-slot="segmented-control-v2-item-label"] {
|
|
70
100
|
display: flex;
|
|
71
101
|
flex-direction: row;
|
|
@@ -77,4 +107,7 @@
|
|
|
77
107
|
overflow: hidden;
|
|
78
108
|
text-overflow: ellipsis;
|
|
79
109
|
white-space: nowrap;
|
|
110
|
+
pointer-events: none;
|
|
111
|
+
user-select: none;
|
|
112
|
+
-webkit-user-select: none;
|
|
80
113
|
}
|
|
@@ -1,25 +1,37 @@
|
|
|
1
|
+
import { animate, motion, useMotionValue, type Transition } from "@kitlangton/solid-motion"
|
|
1
2
|
import {
|
|
2
3
|
createContext,
|
|
4
|
+
createEffect,
|
|
3
5
|
createMemo,
|
|
4
6
|
createSignal,
|
|
5
7
|
mergeProps,
|
|
8
|
+
onCleanup,
|
|
9
|
+
onMount,
|
|
10
|
+
Show,
|
|
6
11
|
splitProps,
|
|
7
12
|
useContext,
|
|
8
13
|
type Accessor,
|
|
14
|
+
type ComponentProps,
|
|
9
15
|
type JSX,
|
|
10
16
|
type ParentProps,
|
|
11
17
|
} from "solid-js"
|
|
12
|
-
import type { ComponentProps } from "solid-js"
|
|
13
18
|
import "./segmented-control.css"
|
|
14
19
|
|
|
15
20
|
type OnChange = (value: string | null) => void
|
|
16
21
|
|
|
22
|
+
export const SEGMENTED_CONTROL_SPRING: Transition = {
|
|
23
|
+
type: "spring",
|
|
24
|
+
visualDuration: 0.15,
|
|
25
|
+
bounce: 0,
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
type SegmentedControlContextValue = {
|
|
18
29
|
selected: Accessor<string | null>
|
|
19
30
|
groupDisabled: Accessor<boolean>
|
|
20
31
|
select: (value: string) => void
|
|
21
32
|
clearIfAllowed: (value: string) => void
|
|
22
33
|
focusNext: (from: HTMLButtonElement, direction: 1 | -1) => void
|
|
34
|
+
registerItem: (value: string, el: HTMLButtonElement) => () => void
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
const SegmentedControlContext = createContext<SegmentedControlContextValue>()
|
|
@@ -32,6 +44,33 @@ function useSegmentedControlContext() {
|
|
|
32
44
|
return ctx
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
export type OffsetElement = {
|
|
48
|
+
offsetLeft: number
|
|
49
|
+
offsetWidth: number
|
|
50
|
+
offsetParent: Element | OffsetElement | null
|
|
51
|
+
getBoundingClientRect: () => { left: number }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isOffsetElement(node: Element | OffsetElement | null): node is OffsetElement {
|
|
55
|
+
return node !== null && "offsetLeft" in node && "offsetParent" in node
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function measureOffset(root: OffsetElement, button: OffsetElement) {
|
|
59
|
+
let offset = 0
|
|
60
|
+
let node: OffsetElement | null = button
|
|
61
|
+
|
|
62
|
+
while (node && node !== root) {
|
|
63
|
+
offset += node.offsetLeft
|
|
64
|
+
node = isOffsetElement(node.offsetParent) ? node.offsetParent : null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (node !== root) {
|
|
68
|
+
offset = button.getBoundingClientRect().left - root.getBoundingClientRect().left
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { x: offset, width: button.offsetWidth }
|
|
72
|
+
}
|
|
73
|
+
|
|
35
74
|
export type SegmentedControlProps = Omit<ComponentProps<"div">, "onChange"> &
|
|
36
75
|
ParentProps<{
|
|
37
76
|
/** Selected value when controlled (including `null` when empty). Omit key for uncontrolled. */
|
|
@@ -42,10 +81,12 @@ export type SegmentedControlProps = Omit<ComponentProps<"div">, "onChange"> &
|
|
|
42
81
|
/** When true, clicking the active segment clears selection (`onChange(null)`). Default false. */
|
|
43
82
|
allowDeselect?: boolean
|
|
44
83
|
disabled?: boolean
|
|
84
|
+
/** Motion transition for the active indicator pill. */
|
|
85
|
+
transition?: Transition
|
|
45
86
|
}>
|
|
46
87
|
|
|
47
88
|
export function SegmentedControl(props: SegmentedControlProps) {
|
|
48
|
-
const isControlled = createMemo(() => Object.hasOwn(props
|
|
89
|
+
const isControlled = createMemo(() => Object.hasOwn(props, "value"))
|
|
49
90
|
const merged = mergeProps({ allowDeselect: false, disabled: false }, props)
|
|
50
91
|
|
|
51
92
|
const [local, rest] = splitProps(merged, [
|
|
@@ -57,13 +98,88 @@ export function SegmentedControl(props: SegmentedControlProps) {
|
|
|
57
98
|
"onChange",
|
|
58
99
|
"allowDeselect",
|
|
59
100
|
"disabled",
|
|
101
|
+
"transition",
|
|
60
102
|
"ref",
|
|
61
103
|
])
|
|
62
104
|
|
|
63
105
|
const [internal, setInternal] = createSignal<string | null>(local.defaultValue ?? null)
|
|
106
|
+
const [layoutVersion, setLayoutVersion] = createSignal(0)
|
|
107
|
+
const [ready, setReady] = createSignal(false)
|
|
64
108
|
|
|
65
109
|
const selected = createMemo(() => (isControlled() ? (local.value ?? null) : internal()))
|
|
66
110
|
|
|
111
|
+
const reducedMotion = () => globalThis.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true
|
|
112
|
+
|
|
113
|
+
const x = useMotionValue(0)
|
|
114
|
+
const width = useMotionValue(0)
|
|
115
|
+
|
|
116
|
+
let rootEl: HTMLDivElement | undefined
|
|
117
|
+
let observer: ResizeObserver | undefined
|
|
118
|
+
const items = new Map<string, HTMLButtonElement>()
|
|
119
|
+
let target = { x: 0, width: 0 }
|
|
120
|
+
let lastSelected: string | null = null
|
|
121
|
+
|
|
122
|
+
const syncIndicator = (shouldAnimate: boolean) => {
|
|
123
|
+
const current = selected()
|
|
124
|
+
const button = current !== null ? items.get(current) : undefined
|
|
125
|
+
|
|
126
|
+
if (!rootEl || !button) {
|
|
127
|
+
setReady(false)
|
|
128
|
+
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const next = measureOffset(rootEl, button)
|
|
133
|
+
|
|
134
|
+
if (next.width === 0) return
|
|
135
|
+
|
|
136
|
+
target = next
|
|
137
|
+
|
|
138
|
+
if (!ready() || !shouldAnimate || reducedMotion()) {
|
|
139
|
+
x.jump(next.x)
|
|
140
|
+
width.jump(next.width)
|
|
141
|
+
setReady(true)
|
|
142
|
+
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const transition = local.transition ?? SEGMENTED_CONTROL_SPRING
|
|
147
|
+
animate(x, next.x, transition)
|
|
148
|
+
animate(width, next.width, transition)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
onMount(() => {
|
|
152
|
+
if (!("ResizeObserver" in globalThis)) return
|
|
153
|
+
|
|
154
|
+
observer = new ResizeObserver(() => {
|
|
155
|
+
const current = selected()
|
|
156
|
+
const button = current !== null ? items.get(current) : undefined
|
|
157
|
+
|
|
158
|
+
if (!rootEl || !button) return
|
|
159
|
+
|
|
160
|
+
const next = measureOffset(rootEl, button)
|
|
161
|
+
|
|
162
|
+
if (next.width === 0) return
|
|
163
|
+
|
|
164
|
+
if (ready() && next.x === target.x && next.width === target.width) return
|
|
165
|
+
|
|
166
|
+
syncIndicator(false)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
if (rootEl) observer.observe(rootEl)
|
|
170
|
+
|
|
171
|
+
items.forEach((button) => observer?.observe(button))
|
|
172
|
+
onCleanup(() => observer?.disconnect())
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
createEffect(() => {
|
|
176
|
+
const current = selected()
|
|
177
|
+
layoutVersion()
|
|
178
|
+
const shouldAnimate = ready() && lastSelected !== null && current !== null && current !== lastSelected
|
|
179
|
+
lastSelected = current
|
|
180
|
+
syncIndicator(shouldAnimate)
|
|
181
|
+
})
|
|
182
|
+
|
|
67
183
|
const setSelected = (next: string | null) => {
|
|
68
184
|
if (!isControlled()) setInternal(next)
|
|
69
185
|
local.onChange?.(next)
|
|
@@ -92,19 +208,32 @@ export function SegmentedControl(props: SegmentedControlProps) {
|
|
|
92
208
|
next?.focus()
|
|
93
209
|
}
|
|
94
210
|
|
|
211
|
+
const registerItem = (value: string, el: HTMLButtonElement) => {
|
|
212
|
+
items.set(value, el)
|
|
213
|
+
observer?.observe(el)
|
|
214
|
+
setLayoutVersion((v) => v + 1)
|
|
215
|
+
|
|
216
|
+
return () => {
|
|
217
|
+
if (items.get(value) === el) items.delete(value)
|
|
218
|
+
observer?.unobserve(el)
|
|
219
|
+
setLayoutVersion((v) => v + 1)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
95
223
|
const ctx: SegmentedControlContextValue = {
|
|
96
224
|
selected,
|
|
97
225
|
groupDisabled: () => !!local.disabled,
|
|
98
226
|
select,
|
|
99
227
|
clearIfAllowed,
|
|
100
228
|
focusNext,
|
|
229
|
+
registerItem,
|
|
101
230
|
}
|
|
102
231
|
|
|
103
|
-
const assignRef = (el: HTMLDivElement
|
|
232
|
+
const assignRef = (el: HTMLDivElement) => {
|
|
233
|
+
rootEl = el
|
|
104
234
|
const r = local.ref
|
|
105
235
|
|
|
106
|
-
if (
|
|
107
|
-
else if (r != null && typeof r === "object" && "value" in r) (r as { value: HTMLDivElement | undefined }).value = el
|
|
236
|
+
if (r instanceof Function) r(el)
|
|
108
237
|
}
|
|
109
238
|
|
|
110
239
|
return (
|
|
@@ -115,11 +244,15 @@ export function SegmentedControl(props: SegmentedControlProps) {
|
|
|
115
244
|
role="group"
|
|
116
245
|
data-component="segmented-control-v2"
|
|
117
246
|
data-slot="segmented-control-v2"
|
|
247
|
+
data-ready={ready() ? "" : undefined}
|
|
118
248
|
classList={{
|
|
119
249
|
...local.classList,
|
|
120
250
|
[local.class ?? ""]: !!local.class,
|
|
121
251
|
}}
|
|
122
252
|
>
|
|
253
|
+
<Show when={ready() && selected() !== null}>
|
|
254
|
+
<motion.span data-slot="segmented-control-v2-indicator" aria-hidden="true" style={{ x, width }} />
|
|
255
|
+
</Show>
|
|
123
256
|
{local.children}
|
|
124
257
|
</div>
|
|
125
258
|
</SegmentedControlContext.Provider>
|
|
@@ -134,9 +267,17 @@ export type SegmentedControlItemProps = Omit<ComponentProps<"button">, "type" |
|
|
|
134
267
|
|
|
135
268
|
function invokeButtonHandler<E extends Event>(
|
|
136
269
|
handler: JSX.EventHandlerUnion<HTMLButtonElement, E> | undefined,
|
|
137
|
-
e:
|
|
270
|
+
e: Parameters<JSX.EventHandler<HTMLButtonElement, E>>[0],
|
|
138
271
|
) {
|
|
139
|
-
if (
|
|
272
|
+
if (handler instanceof Function) {
|
|
273
|
+
handler(e)
|
|
274
|
+
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (Array.isArray(handler)) {
|
|
279
|
+
handler[0](handler[1], e)
|
|
280
|
+
}
|
|
140
281
|
}
|
|
141
282
|
|
|
142
283
|
export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
@@ -150,10 +291,18 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
150
291
|
"disabled",
|
|
151
292
|
"onClick",
|
|
152
293
|
"onKeyDown",
|
|
294
|
+
"ref",
|
|
153
295
|
])
|
|
154
296
|
|
|
155
297
|
const ctx = useSegmentedControlContext()
|
|
156
298
|
|
|
299
|
+
let buttonEl: HTMLButtonElement | undefined
|
|
300
|
+
createEffect(() => {
|
|
301
|
+
if (!buttonEl) return
|
|
302
|
+
const cleanup = ctx.registerItem(local.value, buttonEl)
|
|
303
|
+
onCleanup(cleanup)
|
|
304
|
+
})
|
|
305
|
+
|
|
157
306
|
const pressed = createMemo(() => ctx.selected() === local.value)
|
|
158
307
|
const disabled = createMemo(() => ctx.groupDisabled() || !!local.disabled)
|
|
159
308
|
|
|
@@ -162,27 +311,38 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
162
311
|
|
|
163
312
|
if (e.defaultPrevented || disabled()) return
|
|
164
313
|
|
|
165
|
-
if (pressed())
|
|
166
|
-
|
|
314
|
+
if (pressed()) {
|
|
315
|
+
ctx.clearIfAllowed(local.value)
|
|
316
|
+
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
ctx.select(local.value)
|
|
167
321
|
}
|
|
168
322
|
|
|
169
323
|
const onKeyDown: JSX.EventHandlerUnion<HTMLButtonElement, KeyboardEvent> = (e) => {
|
|
170
324
|
invokeButtonHandler(local.onKeyDown, e)
|
|
171
325
|
|
|
172
326
|
if (e.defaultPrevented || disabled()) return
|
|
327
|
+
|
|
173
328
|
const t = e.currentTarget
|
|
174
329
|
const horizontal = t.closest(`[data-slot="segmented-control-v2"]`)?.matches(":dir(rtl)") ? -1 : 1
|
|
175
330
|
|
|
176
331
|
if (e.key === "ArrowRight") {
|
|
177
332
|
e.preventDefault()
|
|
178
333
|
ctx.focusNext(t, horizontal)
|
|
179
|
-
|
|
334
|
+
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (e.key === "ArrowLeft") {
|
|
180
339
|
e.preventDefault()
|
|
181
|
-
ctx.focusNext(t,
|
|
340
|
+
ctx.focusNext(t, horizontal === 1 ? -1 : 1)
|
|
341
|
+
|
|
342
|
+
return
|
|
182
343
|
}
|
|
183
344
|
|
|
184
|
-
|
|
185
|
-
else if (e.key === "Home") {
|
|
345
|
+
if (e.key === "Home") {
|
|
186
346
|
e.preventDefault()
|
|
187
347
|
const root = t.closest(`[data-slot="segmented-control-v2"]`)
|
|
188
348
|
|
|
@@ -191,7 +351,11 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
191
351
|
)
|
|
192
352
|
|
|
193
353
|
first?.focus()
|
|
194
|
-
|
|
354
|
+
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (e.key === "End") {
|
|
195
359
|
e.preventDefault()
|
|
196
360
|
const root = t.closest(`[data-slot="segmented-control-v2"]`)
|
|
197
361
|
|
|
@@ -204,9 +368,17 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
204
368
|
}
|
|
205
369
|
}
|
|
206
370
|
|
|
371
|
+
const assignRef = (el: HTMLButtonElement) => {
|
|
372
|
+
buttonEl = el
|
|
373
|
+
const r = local.ref
|
|
374
|
+
|
|
375
|
+
if (r instanceof Function) r(el)
|
|
376
|
+
}
|
|
377
|
+
|
|
207
378
|
return (
|
|
208
379
|
<button
|
|
209
380
|
{...rest}
|
|
381
|
+
ref={assignRef}
|
|
210
382
|
type="button"
|
|
211
383
|
data-slot="segmented-control-v2-item"
|
|
212
384
|
data-pressed={pressed() ? "" : undefined}
|
|
@@ -223,3 +395,11 @@ export function SegmentedControlItem(props: SegmentedControlItemProps) {
|
|
|
223
395
|
</button>
|
|
224
396
|
)
|
|
225
397
|
}
|
|
398
|
+
|
|
399
|
+
export const SegmentedControlV2 = SegmentedControl
|
|
400
|
+
|
|
401
|
+
export const SegmentedControlItemV2 = SegmentedControlItem
|
|
402
|
+
|
|
403
|
+
export type SegmentedControlV2Props = SegmentedControlProps
|
|
404
|
+
|
|
405
|
+
export type SegmentedControlItemV2Props = SegmentedControlItemProps
|