@tremolo-ui/react 0.7.0 → 0.9.0
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/index.cjs +666 -1171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -315
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +316 -315
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +643 -1123
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/components/AnimationCanvas/index.tsx +4 -18
- package/src/components/DropZone/index.tsx +2 -1
- package/src/components/FileInput/index.tsx +11 -13
- package/src/components/Knob/ActiveLine.tsx +5 -3
- package/src/components/Knob/InactiveLine.tsx +6 -4
- package/src/components/Knob/SVGRoot.tsx +3 -3
- package/src/components/Knob/Thumb.tsx +21 -20
- package/src/components/Knob/context.tsx +3 -74
- package/src/components/Knob/index.tsx +99 -52
- package/src/components/NumberInput/InputField.tsx +20 -33
- package/src/components/NumberInput/Stepper.tsx +76 -100
- package/src/components/NumberInput/StepperButton.tsx +15 -8
- package/src/components/NumberInput/context.tsx +26 -3
- package/src/components/NumberInput/index.tsx +175 -91
- package/src/components/Piano/index.tsx +54 -144
- package/src/components/PointsEditor/Container.tsx +15 -26
- package/src/components/PointsEditor/Point.tsx +94 -110
- package/src/components/PointsEditor/context.tsx +8 -60
- package/src/components/PointsEditor/index.tsx +41 -324
- package/src/components/Slider/Marks.tsx +5 -6
- package/src/components/Slider/MarksOption.tsx +9 -19
- package/src/components/Slider/Thumb.tsx +20 -11
- package/src/components/Slider/Track.tsx +16 -13
- package/src/components/Slider/context.tsx +3 -3
- package/src/components/Slider/index.tsx +110 -72
- package/src/components/XYPad/Area.tsx +27 -49
- package/src/components/XYPad/Thumb.tsx +20 -8
- package/src/components/XYPad/context.tsx +2 -4
- package/src/components/XYPad/index.tsx +102 -83
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
- package/src/hooks/_internal/useChangeGesture.ts +93 -0
- package/src/hooks/_internal/useCheckSteps.ts +9 -146
- package/src/hooks/useAnimationFrame.ts +37 -15
- package/src/hooks/useEventListener.ts +93 -22
- package/src/hooks/useLongPress.ts +35 -51
- package/src/hooks/useMIDIAccess.ts +0 -8
- package/src/hooks/useMIDIInput.ts +0 -3
- package/src/hooks/useWheel.ts +4 -4
- package/src/index.ts +11 -32
- package/src/components/Piano/keyboard-shortcuts.ts +0 -61
- package/src/components/Slider/type.ts +0 -46
- package/src/components/_util/css-length.ts +0 -13
- package/src/components/_util/decimal-digits.ts +0 -23
- package/src/components/_util/visually-hidden.ts +0 -25
- package/src/hooks/useInterval.ts +0 -27
- package/src/input-event.ts +0 -32
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
|
|
2
2
|
|
|
3
|
+
import { cssLength } from '@tremolo-ui/dom/internal'
|
|
4
|
+
|
|
3
5
|
import { useComposedRefs } from '../../compose-refs'
|
|
4
|
-
import { cssLength } from '../_util/css-length'
|
|
5
6
|
import { Placement } from '../_util/Placement'
|
|
6
7
|
|
|
7
8
|
import { useSliderContext } from './context'
|
|
@@ -17,13 +18,15 @@ export interface SliderTrackProps {
|
|
|
17
18
|
/** How thick the track is across that axis. Sets `--thickness`. */
|
|
18
19
|
thickness?: number | string
|
|
19
20
|
|
|
20
|
-
/** Colour of the part from `min` to the value. Sets `--active`. */
|
|
21
|
-
|
|
22
|
-
/** Colour of the rest of the track. Sets `--inactive`. */
|
|
23
|
-
|
|
21
|
+
/** Colour of the part from `min` to the value. Sets `--active-color`. */
|
|
22
|
+
activeColor?: string
|
|
23
|
+
/** Colour of the rest of the track. Sets `--inactive-color`. */
|
|
24
|
+
inactiveColor?: string
|
|
24
25
|
|
|
25
26
|
style?: CSSProperties &
|
|
26
|
-
CSSVariables<
|
|
27
|
+
CSSVariables<
|
|
28
|
+
'length' | 'thickness' | 'active-color' | 'inactive-color' | 'percent'
|
|
29
|
+
>
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
type Props = SliderTrackProps &
|
|
@@ -34,8 +37,8 @@ export const Track = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
|
34
37
|
{
|
|
35
38
|
length,
|
|
36
39
|
thickness,
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
activeColor,
|
|
41
|
+
inactiveColor,
|
|
39
42
|
children,
|
|
40
43
|
className,
|
|
41
44
|
style,
|
|
@@ -43,7 +46,7 @@ export const Track = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
|
43
46
|
},
|
|
44
47
|
forwardedRef,
|
|
45
48
|
) {
|
|
46
|
-
const {
|
|
49
|
+
const { orientation, reverse, disabled, percent, trackRef } =
|
|
47
50
|
useSliderContext()
|
|
48
51
|
|
|
49
52
|
// The track is what the pointer position is normalized against, so the
|
|
@@ -55,14 +58,14 @@ export const Track = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
|
55
58
|
ref={composedRef}
|
|
56
59
|
className={className}
|
|
57
60
|
data-disabled={disabled ? '' : undefined}
|
|
58
|
-
data-orientation={
|
|
61
|
+
data-orientation={orientation}
|
|
59
62
|
// Which end the value grows from. `percent` is already the position on
|
|
60
63
|
// screen, so this only says which side of it is the filled one.
|
|
61
|
-
data-flipped={vertical !== reverse ? '' : undefined}
|
|
64
|
+
data-flipped={(orientation === 'vertical') !== reverse ? '' : undefined}
|
|
62
65
|
style={
|
|
63
66
|
{
|
|
64
|
-
'--active':
|
|
65
|
-
'--inactive':
|
|
67
|
+
'--active-color': activeColor,
|
|
68
|
+
'--inactive-color': inactiveColor,
|
|
66
69
|
'--length': cssLength(length),
|
|
67
70
|
'--thickness': cssLength(thickness),
|
|
68
71
|
// Where the value sits, for the theme to paint the fill with. The
|
|
@@ -11,14 +11,14 @@ export type SliderContextValue = {
|
|
|
11
11
|
step: number
|
|
12
12
|
/** How the value is distributed across the travel. */
|
|
13
13
|
scale: Scale
|
|
14
|
-
|
|
14
|
+
orientation: 'horizontal' | 'vertical'
|
|
15
15
|
reverse: boolean
|
|
16
16
|
disabled: boolean
|
|
17
|
-
|
|
17
|
+
readOnly: boolean
|
|
18
18
|
onChange?: (value: number) => void
|
|
19
19
|
/**
|
|
20
20
|
* Position of the thumb, 0-100, already accounting for the display direction
|
|
21
|
-
* implied by `
|
|
21
|
+
* implied by `orientation` and `reverse`.
|
|
22
22
|
*/
|
|
23
23
|
percent: number
|
|
24
24
|
|
|
@@ -3,6 +3,7 @@ import React, {
|
|
|
3
3
|
CSSProperties,
|
|
4
4
|
forwardRef,
|
|
5
5
|
ReactNode,
|
|
6
|
+
Ref,
|
|
6
7
|
useCallback,
|
|
7
8
|
useImperativeHandle,
|
|
8
9
|
useMemo,
|
|
@@ -10,24 +11,29 @@ import React, {
|
|
|
10
11
|
} from 'react'
|
|
11
12
|
|
|
12
13
|
import {
|
|
13
|
-
applyDelta,
|
|
14
14
|
type AxisOptions,
|
|
15
|
+
type ChangeSource,
|
|
16
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
17
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
18
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
15
19
|
type InputEventOption,
|
|
16
20
|
type ModifierValue,
|
|
17
|
-
|
|
21
|
+
valuePercent,
|
|
18
22
|
type XY,
|
|
19
23
|
} from '@tremolo-ui/dom'
|
|
20
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
applyDelta,
|
|
26
|
+
arrowKeyDirection,
|
|
27
|
+
selectModifier,
|
|
28
|
+
wheelDirection,
|
|
29
|
+
} from '@tremolo-ui/dom/internal'
|
|
30
|
+
import { linearScale, type Scale } from '@tremolo-ui/functions'
|
|
21
31
|
|
|
22
32
|
import { useComposedRefs } from '../../compose-refs'
|
|
33
|
+
import { useChangeGesture } from '../../hooks/_internal/useChangeGesture'
|
|
23
34
|
import { useCheckSteps } from '../../hooks/_internal/useCheckSteps'
|
|
24
35
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
25
36
|
import { useWheel } from '../../hooks/useWheel'
|
|
26
|
-
import {
|
|
27
|
-
DEFAULT_DRAG_SENSITIVITY,
|
|
28
|
-
DEFAULT_KEYBOARD_OPTIONS,
|
|
29
|
-
DEFAULT_WHEEL_OPTIONS,
|
|
30
|
-
} from '../../input-event'
|
|
31
37
|
|
|
32
38
|
import { SliderProvider } from './context'
|
|
33
39
|
import { Marks } from './Marks'
|
|
@@ -35,10 +41,6 @@ import { MarksOption } from './MarksOption'
|
|
|
35
41
|
import { Thumb, SliderThumbMethods } from './Thumb'
|
|
36
42
|
import { Track } from './Track'
|
|
37
43
|
|
|
38
|
-
const defaultExternalStyles: SliderProps['externalStyles'] = {
|
|
39
|
-
cursor: 'pointer',
|
|
40
|
-
}
|
|
41
|
-
|
|
42
44
|
export interface SliderProps {
|
|
43
45
|
/** The current value. The slider shows only this, so update it from `onChange`. */
|
|
44
46
|
value: number
|
|
@@ -65,14 +67,15 @@ export interface SliderProps {
|
|
|
65
67
|
*/
|
|
66
68
|
scale?: Scale
|
|
67
69
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
+
* Which way the slider runs. A vertical slider grows upwards. Every part
|
|
71
|
+
* carries the same value as `data-orientation`, and the range input inside
|
|
72
|
+
* the thumb takes its `aria-orientation` from it.
|
|
70
73
|
*
|
|
71
|
-
* @default
|
|
74
|
+
* @default 'horizontal'
|
|
72
75
|
*/
|
|
73
|
-
|
|
76
|
+
orientation?: 'horizontal' | 'vertical'
|
|
74
77
|
/**
|
|
75
|
-
* Grow the value the other way: leftwards, or downwards when
|
|
78
|
+
* Grow the value the other way: leftwards, or downwards when vertical.
|
|
76
79
|
* The arrow keys follow the direction on screen.
|
|
77
80
|
*
|
|
78
81
|
* @default false
|
|
@@ -83,11 +86,9 @@ export interface SliderProps {
|
|
|
83
86
|
* The cursor to show while dragging. It is set on the dragged element, so it
|
|
84
87
|
* stays while the pointer is outside the slider.
|
|
85
88
|
*
|
|
86
|
-
* @default
|
|
89
|
+
* @default 'pointer'
|
|
87
90
|
*/
|
|
88
|
-
|
|
89
|
-
cursor?: CSSProperties['cursor']
|
|
90
|
-
}
|
|
91
|
+
dragCursor?: CSSProperties['cursor']
|
|
91
92
|
/**
|
|
92
93
|
* How much one notch of the wheel moves the value. It only acts while the
|
|
93
94
|
* focus is inside, so that scrolling the page past the slider leaves it
|
|
@@ -144,13 +145,28 @@ export interface SliderProps {
|
|
|
144
145
|
* Make the value unchangeable.
|
|
145
146
|
* The parts carry `data-readonly` while it is set.
|
|
146
147
|
*/
|
|
147
|
-
|
|
148
|
+
readOnly?: boolean
|
|
148
149
|
/** Called with the new value when a drag, the wheel or an arrow key moves it. */
|
|
149
150
|
onChange?: (value: number) => void
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Called when a change of the value starts — a press on the track, the first wheel notch
|
|
153
|
+
* or arrow key — with the value
|
|
154
|
+
* before it and what it is made with. A host recording automation can treat
|
|
155
|
+
* the control as touched from here until `onChangeEnd`.
|
|
156
|
+
*/
|
|
157
|
+
onChangeStart?: (value: number, source: ChangeSource) => void
|
|
158
|
+
/**
|
|
159
|
+
* Called when the change ends, with the value it ended on: on release, or
|
|
160
|
+
* `changeEndDelay` after the last wheel notch or arrow key.
|
|
161
|
+
*/
|
|
162
|
+
onChangeEnd?: (value: number, source: ChangeSource) => void
|
|
163
|
+
/**
|
|
164
|
+
* How long after the last wheel notch or arrow key the change counts as
|
|
165
|
+
* over, in milliseconds. Neither has an event that says it is done.
|
|
166
|
+
*
|
|
167
|
+
* @default 500
|
|
168
|
+
*/
|
|
169
|
+
changeEndDelay?: number
|
|
154
170
|
/**
|
|
155
171
|
* The slider renders exactly what you compose here; there is no default
|
|
156
172
|
* markup to fall back to.
|
|
@@ -163,6 +179,13 @@ export interface SliderProps {
|
|
|
163
179
|
* </Slider.Root>
|
|
164
180
|
*/
|
|
165
181
|
children: ReactNode
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Receives `focus` and `blur`, which act on the range input inside the thumb —
|
|
185
|
+
* the element that takes the focus — and do nothing while the slider is
|
|
186
|
+
* disabled. `ref` reaches the root element itself.
|
|
187
|
+
*/
|
|
188
|
+
actionsRef?: Ref<SliderMethods>
|
|
166
189
|
}
|
|
167
190
|
|
|
168
191
|
export interface SliderMethods {
|
|
@@ -179,7 +202,7 @@ type Props = SliderProps &
|
|
|
179
202
|
*/
|
|
180
203
|
const WHEEL_OPTIONS = { requireFocus: true }
|
|
181
204
|
|
|
182
|
-
export const Root = /* @__PURE__ */ forwardRef<
|
|
205
|
+
export const Root = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
183
206
|
(
|
|
184
207
|
{
|
|
185
208
|
value,
|
|
@@ -187,17 +210,18 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
187
210
|
max,
|
|
188
211
|
step = 1,
|
|
189
212
|
scale = linearScale,
|
|
190
|
-
|
|
213
|
+
orientation = 'horizontal',
|
|
191
214
|
reverse = false,
|
|
192
|
-
|
|
215
|
+
dragCursor = 'pointer',
|
|
193
216
|
wheel = DEFAULT_WHEEL_OPTIONS,
|
|
194
217
|
keyboard = DEFAULT_KEYBOARD_OPTIONS,
|
|
195
218
|
dragSensitivity = DEFAULT_DRAG_SENSITIVITY,
|
|
196
219
|
disabled = false,
|
|
197
|
-
|
|
220
|
+
readOnly = false,
|
|
198
221
|
onChange,
|
|
199
|
-
|
|
200
|
-
|
|
222
|
+
onChangeStart,
|
|
223
|
+
onChangeEnd,
|
|
224
|
+
changeEndDelay,
|
|
201
225
|
className,
|
|
202
226
|
style,
|
|
203
227
|
children,
|
|
@@ -205,6 +229,7 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
205
229
|
onBlur,
|
|
206
230
|
onPointerDown,
|
|
207
231
|
onKeyDown,
|
|
232
|
+
actionsRef,
|
|
208
233
|
...props
|
|
209
234
|
}: Props,
|
|
210
235
|
forwardedRef,
|
|
@@ -213,18 +238,16 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
213
238
|
const trackRef = useRef<HTMLDivElement>(null)
|
|
214
239
|
const thumbRef = useRef<SliderThumbMethods>(null)
|
|
215
240
|
// --- interpret props ---
|
|
216
|
-
const
|
|
217
|
-
const inactive = disabled || readonly
|
|
241
|
+
const inactive = disabled || readOnly
|
|
218
242
|
|
|
219
|
-
|
|
220
|
-
const rev = toFixed(100 - p)
|
|
221
|
-
// NOTE
|
|
243
|
+
// Measured from the left or the top, as CSS places things:
|
|
222
244
|
// normal -> normal (right)
|
|
223
|
-
// vertical ->
|
|
224
|
-
// reverse ->
|
|
245
|
+
// vertical -> reversed (up)
|
|
246
|
+
// reverse -> reversed (left)
|
|
225
247
|
// vertical & reverse -> normal (down)
|
|
248
|
+
const vertical = orientation === 'vertical'
|
|
226
249
|
const displayReversed = vertical !== reverse
|
|
227
|
-
const percent =
|
|
250
|
+
const percent = valuePercent(value, { min, max, scale }, displayReversed)
|
|
228
251
|
|
|
229
252
|
// --- internal functions ---
|
|
230
253
|
// The pointer is normalized against the track on both axes; only the one
|
|
@@ -237,18 +260,37 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
237
260
|
|
|
238
261
|
useCheckSteps({ component: 'Slider', range: axis, keyboard, wheel })
|
|
239
262
|
|
|
263
|
+
const gesture = useChangeGesture(
|
|
264
|
+
value,
|
|
265
|
+
{ onChangeStart, onChangeEnd, changeEndDelay },
|
|
266
|
+
inactive,
|
|
267
|
+
)
|
|
268
|
+
const change = useCallback(
|
|
269
|
+
(next: number) => {
|
|
270
|
+
gesture.changed(next)
|
|
271
|
+
onChange?.(next)
|
|
272
|
+
},
|
|
273
|
+
[gesture, onChange],
|
|
274
|
+
)
|
|
275
|
+
|
|
240
276
|
const handleKeyDown = useCallback(
|
|
241
277
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
return
|
|
278
|
+
const direction = arrowKeyDirection(event.key)
|
|
279
|
+
if (direction === null) return
|
|
245
280
|
event.preventDefault()
|
|
246
281
|
if (!keyboard || !onChange || inactive) return
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
282
|
+
gesture.pulse('keyboard')
|
|
283
|
+
change(
|
|
284
|
+
applyDelta(
|
|
285
|
+
value,
|
|
286
|
+
reverse ? -direction : direction,
|
|
287
|
+
keyboard,
|
|
288
|
+
axis,
|
|
289
|
+
event,
|
|
290
|
+
),
|
|
291
|
+
)
|
|
250
292
|
},
|
|
251
|
-
[keyboard, onChange, inactive, reverse, value, axis],
|
|
293
|
+
[keyboard, onChange, inactive, reverse, value, axis, gesture, change],
|
|
252
294
|
)
|
|
253
295
|
|
|
254
296
|
// --- hooks ---
|
|
@@ -257,43 +299,40 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
257
299
|
const { refCallback: dragRefCallback } = useDragValue<HTMLDivElement>({
|
|
258
300
|
axis,
|
|
259
301
|
baseElementRef: trackRef,
|
|
260
|
-
cursor: inactive ? undefined :
|
|
302
|
+
cursor: inactive ? undefined : dragCursor,
|
|
261
303
|
shouldStart: () => !inactive,
|
|
262
304
|
sensitivity: (state) =>
|
|
263
305
|
selectModifier(dragSensitivity, state.event).value,
|
|
264
306
|
updateOnPointerDown: true,
|
|
265
307
|
onChange: (v) => {
|
|
266
308
|
if (inactive) return
|
|
267
|
-
|
|
309
|
+
change(valueOf(v))
|
|
268
310
|
},
|
|
269
|
-
onDragStart: (
|
|
311
|
+
onDragStart: () => {
|
|
270
312
|
if (inactive) return
|
|
313
|
+
gesture.hold('pointer')
|
|
271
314
|
thumbRef.current?.focus()
|
|
272
|
-
onDragStart?.(valueOf(v))
|
|
273
|
-
},
|
|
274
|
-
onDragEnd: (v) => {
|
|
275
|
-
if (inactive) return
|
|
276
|
-
onDragEnd?.(valueOf(v))
|
|
277
315
|
},
|
|
316
|
+
onDragEnd: () => gesture.end(),
|
|
278
317
|
})
|
|
279
318
|
|
|
280
319
|
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
281
320
|
if (!wheel || !onChange || inactive) return
|
|
321
|
+
// A notch the slider does not read — a sideways scroll on a vertical
|
|
322
|
+
// slider — is left to the page rather than swallowed.
|
|
323
|
+
const direction = wheelDirection(event, { horizontal: !vertical })
|
|
324
|
+
if (direction === null) return
|
|
282
325
|
event.preventDefault()
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (event.deltaY === 0) return
|
|
288
|
-
direction = event.deltaY > 0 ? -1 : 1
|
|
289
|
-
}
|
|
290
|
-
if (reverse) direction *= -1
|
|
291
|
-
onChange(applyDelta(value, direction, wheel, axis, event))
|
|
326
|
+
gesture.pulse('wheel')
|
|
327
|
+
change(
|
|
328
|
+
applyDelta(value, reverse ? -direction : direction, wheel, axis, event),
|
|
329
|
+
)
|
|
292
330
|
}, WHEEL_OPTIONS)
|
|
293
331
|
|
|
294
332
|
// Composed once, so React attaches the refs a single time instead of
|
|
295
333
|
// detaching and re-attaching on every render.
|
|
296
334
|
const rootRefCallback = useComposedRefs<HTMLDivElement>(
|
|
335
|
+
forwardedRef,
|
|
297
336
|
dragRefCallback,
|
|
298
337
|
wheelRefCallback,
|
|
299
338
|
)
|
|
@@ -305,10 +344,10 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
305
344
|
max,
|
|
306
345
|
step,
|
|
307
346
|
scale,
|
|
308
|
-
|
|
347
|
+
orientation,
|
|
309
348
|
reverse,
|
|
310
349
|
disabled,
|
|
311
|
-
|
|
350
|
+
readOnly,
|
|
312
351
|
onChange,
|
|
313
352
|
percent,
|
|
314
353
|
trackRef,
|
|
@@ -320,16 +359,16 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
320
359
|
max,
|
|
321
360
|
step,
|
|
322
361
|
scale,
|
|
323
|
-
|
|
362
|
+
orientation,
|
|
324
363
|
reverse,
|
|
325
364
|
disabled,
|
|
326
|
-
|
|
365
|
+
readOnly,
|
|
327
366
|
onChange,
|
|
328
367
|
percent,
|
|
329
368
|
],
|
|
330
369
|
)
|
|
331
370
|
|
|
332
|
-
useImperativeHandle(
|
|
371
|
+
useImperativeHandle(actionsRef, () => {
|
|
333
372
|
return {
|
|
334
373
|
focus() {
|
|
335
374
|
if (!disabled) thumbRef.current?.focus()
|
|
@@ -352,9 +391,9 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
352
391
|
// body — undoing the focus the drag just gave the input. Taking the
|
|
353
392
|
// focus here keeps it inside, and `onFocus` passes it to the input.
|
|
354
393
|
tabIndex={-1}
|
|
355
|
-
data-orientation={
|
|
394
|
+
data-orientation={orientation}
|
|
356
395
|
data-disabled={disabled ? '' : undefined}
|
|
357
|
-
data-readonly={
|
|
396
|
+
data-readonly={readOnly ? '' : undefined}
|
|
358
397
|
style={style}
|
|
359
398
|
onPointerDown={onPointerDown}
|
|
360
399
|
onKeyDown={(event) => {
|
|
@@ -394,4 +433,3 @@ export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
|
394
433
|
export { type SliderTrackProps } from './Track'
|
|
395
434
|
export { type SliderMarksProps } from './Marks'
|
|
396
435
|
export { type SliderMarksOptionProps } from './MarksOption'
|
|
397
|
-
export { type MarksOptions } from './type'
|
|
@@ -1,56 +1,34 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useComposedRefs } from '../../compose-refs'
|
|
4
|
-
import { cssLength } from '../_util/css-length'
|
|
5
4
|
import { Placement } from '../_util/Placement'
|
|
6
5
|
|
|
7
6
|
import { useXYPadContext } from './context'
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div
|
|
38
|
-
ref={composedRef}
|
|
39
|
-
className={className}
|
|
40
|
-
style={
|
|
41
|
-
{
|
|
42
|
-
'--color': color,
|
|
43
|
-
'--width': cssLength(width),
|
|
44
|
-
'--height': cssLength(height),
|
|
45
|
-
// The thumb inside is placed against this box.
|
|
46
|
-
position: 'relative',
|
|
47
|
-
...style,
|
|
48
|
-
} as CSSProperties
|
|
49
|
-
}
|
|
50
|
-
{...props}
|
|
51
|
-
>
|
|
52
|
-
<Placement name="XYPad.Area">{children}</Placement>
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
},
|
|
56
|
-
)
|
|
8
|
+
export const Area = /* @__PURE__ */ forwardRef<
|
|
9
|
+
HTMLDivElement,
|
|
10
|
+
ComponentPropsWithoutRef<'div'>
|
|
11
|
+
>(function Area({ children, className, style, ...props }, forwardedRef) {
|
|
12
|
+
const { areaRef } = useXYPadContext()
|
|
13
|
+
|
|
14
|
+
// The area is what the pointer position is normalized against, so the
|
|
15
|
+
// context ref is composed with any ref the caller passed.
|
|
16
|
+
const composedRef = useComposedRefs<HTMLDivElement>(forwardedRef, areaRef)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
ref={composedRef}
|
|
21
|
+
className={className}
|
|
22
|
+
style={
|
|
23
|
+
{
|
|
24
|
+
// The thumb inside is placed against this box.
|
|
25
|
+
position: 'relative',
|
|
26
|
+
...style,
|
|
27
|
+
} as CSSProperties
|
|
28
|
+
}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
<Placement name="XYPad.Area">{children}</Placement>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
})
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AriaAttributes,
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
|
+
CSSProperties,
|
|
4
5
|
forwardRef,
|
|
6
|
+
Ref,
|
|
5
7
|
useImperativeHandle,
|
|
6
8
|
useRef,
|
|
7
|
-
CSSProperties,
|
|
8
9
|
} from 'react'
|
|
9
10
|
|
|
11
|
+
import { type XYInput } from '@tremolo-ui/dom'
|
|
12
|
+
import { toXY } from '@tremolo-ui/dom/internal'
|
|
13
|
+
|
|
10
14
|
import { useCheckPlacement } from '../_util/Placement'
|
|
11
15
|
import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
|
|
12
16
|
|
|
13
|
-
import {
|
|
17
|
+
import { useXYPadContext } from './context'
|
|
14
18
|
|
|
15
19
|
import type { CSSVariables } from '../../css-variables'
|
|
16
20
|
|
|
@@ -39,6 +43,12 @@ export interface XYPadThumbProps {
|
|
|
39
43
|
'aria-valuetext'?: XYInput<AriaAttributes['aria-valuetext']>
|
|
40
44
|
|
|
41
45
|
style?: CSSProperties & CSSVariables<'color' | 'translate'>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Receives `focus` and `blur` for the range inputs inside, which do nothing
|
|
49
|
+
* while the pad is disabled. `ref` reaches the thumb element itself.
|
|
50
|
+
*/
|
|
51
|
+
actionsRef?: Ref<XYPadThumbMethods>
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
export interface XYPadThumbMethods {
|
|
@@ -49,7 +59,7 @@ export interface XYPadThumbMethods {
|
|
|
49
59
|
type Props = XYPadThumbProps &
|
|
50
60
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadThumbProps>
|
|
51
61
|
|
|
52
|
-
export const Thumb = /* @__PURE__ */ forwardRef<
|
|
62
|
+
export const Thumb = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
53
63
|
function Thumb(
|
|
54
64
|
{
|
|
55
65
|
color,
|
|
@@ -60,6 +70,7 @@ export const Thumb = /* @__PURE__ */ forwardRef<XYPadThumbMethods, Props>(
|
|
|
60
70
|
'aria-labelledby': ariaLabelledby,
|
|
61
71
|
'aria-describedby': ariaDescribedby,
|
|
62
72
|
'aria-valuetext': ariaValuetext,
|
|
73
|
+
actionsRef,
|
|
63
74
|
...props
|
|
64
75
|
},
|
|
65
76
|
forwardedRef,
|
|
@@ -72,7 +83,7 @@ export const Thumb = /* @__PURE__ */ forwardRef<XYPadThumbMethods, Props>(
|
|
|
72
83
|
max,
|
|
73
84
|
step,
|
|
74
85
|
disabled,
|
|
75
|
-
|
|
86
|
+
readOnly,
|
|
76
87
|
onChange,
|
|
77
88
|
percent,
|
|
78
89
|
thumbRef,
|
|
@@ -97,15 +108,16 @@ export const Thumb = /* @__PURE__ */ forwardRef<XYPadThumbMethods, Props>(
|
|
|
97
108
|
},
|
|
98
109
|
})
|
|
99
110
|
|
|
100
|
-
useImperativeHandle(
|
|
111
|
+
useImperativeHandle(actionsRef, methods, [disabled])
|
|
101
112
|
// Root focuses the thumb when a drag starts, wherever the user placed it.
|
|
102
113
|
useImperativeHandle(thumbRef, methods, [disabled])
|
|
103
114
|
|
|
104
115
|
return (
|
|
105
116
|
<div
|
|
117
|
+
ref={forwardedRef}
|
|
106
118
|
className={className}
|
|
107
119
|
data-disabled={disabled ? '' : undefined}
|
|
108
|
-
data-readonly={
|
|
120
|
+
data-readonly={readOnly ? '' : undefined}
|
|
109
121
|
{...props}
|
|
110
122
|
style={{
|
|
111
123
|
...{ '--color': color },
|
|
@@ -133,14 +145,14 @@ export const Thumb = /* @__PURE__ */ forwardRef<XYPadThumbMethods, Props>(
|
|
|
133
145
|
max={max[axis]}
|
|
134
146
|
step={step[axis]}
|
|
135
147
|
disabled={disabled}
|
|
136
|
-
aria-readonly={
|
|
148
|
+
aria-readonly={readOnly}
|
|
137
149
|
aria-orientation={axis === 0 ? 'horizontal' : 'vertical'}
|
|
138
150
|
aria-label={labels[axis] ?? (axis === 0 ? 'x' : 'y')}
|
|
139
151
|
aria-labelledby={labelledby[axis]}
|
|
140
152
|
aria-describedby={describedby[axis]}
|
|
141
153
|
aria-valuetext={valueText[axis]}
|
|
142
154
|
onChange={(event) => {
|
|
143
|
-
if (
|
|
155
|
+
if (readOnly) {
|
|
144
156
|
event.currentTarget.value = String(value[axis])
|
|
145
157
|
return
|
|
146
158
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { createContext, RefObject, useContext } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { type XY } from '@tremolo-ui/dom'
|
|
4
4
|
import type { Scale } from '@tremolo-ui/functions'
|
|
5
5
|
|
|
6
6
|
import type { XYPadThumbMethods } from './Thumb'
|
|
7
7
|
|
|
8
|
-
export { toXY, type XY, type XYInput }
|
|
9
|
-
|
|
10
8
|
export type XYPadContextValue = {
|
|
11
9
|
value: XY<number>
|
|
12
10
|
min: XY<number>
|
|
@@ -16,7 +14,7 @@ export type XYPadContextValue = {
|
|
|
16
14
|
scale: XY<Scale>
|
|
17
15
|
reverse: XY<boolean>
|
|
18
16
|
disabled: boolean
|
|
19
|
-
|
|
17
|
+
readOnly: boolean
|
|
20
18
|
onChange?: (value: XY<number>) => void
|
|
21
19
|
|
|
22
20
|
/**
|