@tremolo-ui/react 0.5.0 → 0.7.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/compose-refs.cjs +72 -0
- package/dist/compose-refs.cjs.map +1 -0
- package/dist/compose-refs.d.cts +43 -0
- package/dist/compose-refs.d.cts.map +1 -0
- package/dist/compose-refs.d.ts +43 -0
- package/dist/compose-refs.d.ts.map +1 -0
- package/dist/compose-refs.js +70 -0
- package/dist/compose-refs.js.map +1 -0
- package/dist/index.cjs +1669 -541
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1571 -486
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1571 -486
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1640 -538
- package/dist/index.js.map +1 -1
- package/package.json +29 -27
- package/src/components/AnimationCanvas/index.tsx +115 -76
- package/src/components/DropZone/index.tsx +100 -0
- package/src/components/FileInput/Trigger.tsx +39 -0
- package/src/components/FileInput/context.tsx +24 -0
- package/src/components/FileInput/index.tsx +144 -0
- package/src/components/Knob/ActiveLine.tsx +7 -6
- package/src/components/Knob/InactiveLine.tsx +9 -10
- package/src/components/Knob/SVGRoot.tsx +5 -3
- package/src/components/Knob/Thumb.tsx +33 -11
- package/src/components/Knob/context.tsx +17 -1
- package/src/components/Knob/index.tsx +156 -60
- package/src/components/NumberInput/DecrementStepper.tsx +3 -10
- package/src/components/NumberInput/IncrementStepper.tsx +3 -10
- package/src/components/NumberInput/InputField.tsx +185 -108
- package/src/components/NumberInput/Stepper.tsx +120 -84
- package/src/components/NumberInput/StepperButton.tsx +100 -0
- package/src/components/NumberInput/context.tsx +34 -8
- package/src/components/NumberInput/index.tsx +270 -82
- package/src/components/Piano/index.tsx +363 -200
- package/src/components/PointsEditor/Background.tsx +13 -10
- package/src/components/PointsEditor/Container.tsx +83 -29
- package/src/components/PointsEditor/Point.tsx +241 -79
- package/src/components/PointsEditor/SelectionBox.tsx +48 -0
- package/src/components/PointsEditor/context.tsx +78 -10
- package/src/components/PointsEditor/index.tsx +440 -41
- package/src/components/Slider/Marks.tsx +48 -19
- package/src/components/Slider/MarksOption.tsx +64 -45
- package/src/components/Slider/Thumb.tsx +107 -55
- package/src/components/Slider/Track.tsx +66 -62
- package/src/components/Slider/context.tsx +4 -2
- package/src/components/Slider/index.tsx +136 -77
- package/src/components/Slider/type.ts +29 -9
- package/src/components/XYPad/Area.tsx +46 -41
- package/src/components/XYPad/Thumb.tsx +131 -64
- package/src/components/XYPad/context.tsx +4 -1
- package/src/components/XYPad/index.tsx +183 -53
- package/src/components/_util/Placement.tsx +68 -0
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +12 -0
- package/src/components/_util/css-length.ts +13 -0
- package/src/components/_util/decimal-digits.ts +23 -0
- package/src/components/_util/visually-hidden.ts +25 -0
- package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
- package/src/css-variables.ts +23 -0
- package/src/hooks/_internal/useCallbackRef.ts +3 -1
- package/src/hooks/_internal/useCheckSteps.ts +169 -0
- package/src/hooks/useAnimationFrame.ts +17 -7
- package/src/hooks/useDrag.ts +51 -12
- package/src/hooks/useDragValue.ts +71 -11
- package/src/hooks/useDropZone.ts +78 -0
- package/src/hooks/useEventListener.ts +10 -6
- package/src/hooks/useLongPress.ts +42 -8
- package/src/hooks/useMIDIAccess.ts +82 -11
- package/src/hooks/useMIDIInput.ts +45 -13
- package/src/hooks/useMIDIMessage.ts +23 -5
- package/src/hooks/useWheel.ts +4 -1
- package/src/index.ts +45 -25
- package/src/input-event.ts +32 -0
- package/dist/index.css +0 -501
- package/src/Globals.d.ts +0 -3
- package/src/components/Knob/index.css +0 -81
- package/src/components/NumberInput/index.css +0 -121
- package/src/components/NumberInput/stepperButton.tsx +0 -90
- package/src/components/Piano/index.css +0 -90
- package/src/components/PointsEditor/index.css +0 -35
- package/src/components/Slider/index.css +0 -125
- package/src/components/XYPad/index.css +0 -60
- package/src/components/_util/index.ts +0 -28
- package/src/styles/global.css +0 -19
- /package/src/components/Piano/{keyboardShortcuts.ts → keyboard-shortcuts.ts} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import React, {
|
|
3
2
|
ComponentPropsWithoutRef,
|
|
4
3
|
CSSProperties,
|
|
@@ -10,20 +9,25 @@ import React, {
|
|
|
10
9
|
useRef,
|
|
11
10
|
} from 'react'
|
|
12
11
|
|
|
13
|
-
import type { AxisOptions, XY } from '@tremolo-ui/dom'
|
|
14
12
|
import {
|
|
15
13
|
applyDelta,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type
|
|
21
|
-
} from '@tremolo-ui/
|
|
14
|
+
type AxisOptions,
|
|
15
|
+
type InputEventOption,
|
|
16
|
+
type ModifierValue,
|
|
17
|
+
selectModifier,
|
|
18
|
+
type XY,
|
|
19
|
+
} from '@tremolo-ui/dom'
|
|
20
|
+
import { linearScale, type Scale, toFixed } from '@tremolo-ui/functions'
|
|
22
21
|
|
|
22
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
23
|
+
import { useCheckSteps } from '../../hooks/_internal/useCheckSteps'
|
|
23
24
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
24
25
|
import { useWheel } from '../../hooks/useWheel'
|
|
25
|
-
import {
|
|
26
|
-
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
28
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
29
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
30
|
+
} from '../../input-event'
|
|
27
31
|
|
|
28
32
|
import { SliderProvider } from './context'
|
|
29
33
|
import { Marks } from './Marks'
|
|
@@ -32,17 +36,23 @@ import { Thumb, SliderThumbMethods } from './Thumb'
|
|
|
32
36
|
import { Track } from './Track'
|
|
33
37
|
|
|
34
38
|
const defaultExternalStyles: SliderProps['externalStyles'] = {
|
|
35
|
-
userSelectNone: true,
|
|
36
39
|
cursor: 'pointer',
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export interface SliderProps {
|
|
40
|
-
|
|
43
|
+
/** The current value. The slider shows only this, so update it from `onChange`. */
|
|
41
44
|
value: number
|
|
45
|
+
/** The value at the start of the travel. */
|
|
42
46
|
min: number
|
|
47
|
+
/** The value at the end of the travel. */
|
|
43
48
|
max: number
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Granularity of the value. A drag, the wheel and the arrow keys snap it to
|
|
52
|
+
* multiples of `step`.
|
|
53
|
+
*
|
|
54
|
+
* @default 1
|
|
55
|
+
*/
|
|
46
56
|
step?: number
|
|
47
57
|
/**
|
|
48
58
|
* How the value is distributed across the travel.
|
|
@@ -55,43 +65,91 @@ export interface SliderProps {
|
|
|
55
65
|
*/
|
|
56
66
|
scale?: Scale
|
|
57
67
|
/**
|
|
58
|
-
* slider
|
|
59
|
-
* aria-orientation
|
|
68
|
+
* Run the slider vertically, with the value growing upwards. The range input
|
|
69
|
+
* inside the thumb takes its `aria-orientation` from this.
|
|
70
|
+
*
|
|
71
|
+
* @default false
|
|
60
72
|
*/
|
|
61
73
|
vertical?: boolean
|
|
74
|
+
/**
|
|
75
|
+
* Grow the value the other way: leftwards, or downwards when `vertical`.
|
|
76
|
+
* The arrow keys follow the direction on screen.
|
|
77
|
+
*
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
62
80
|
reverse?: boolean
|
|
63
81
|
|
|
64
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* The cursor to show while dragging. It is set on the dragged element, so it
|
|
84
|
+
* stays while the pointer is outside the slider.
|
|
85
|
+
*
|
|
86
|
+
* @default { cursor: 'pointer' }
|
|
87
|
+
*/
|
|
65
88
|
externalStyles?: {
|
|
66
|
-
|
|
67
|
-
cursor?: Cursor
|
|
89
|
+
cursor?: CSSProperties['cursor']
|
|
68
90
|
}
|
|
69
91
|
/**
|
|
70
|
-
* wheel
|
|
71
|
-
*
|
|
92
|
+
* How much one notch of the wheel moves the value. It only acts while the
|
|
93
|
+
* focus is inside, so that scrolling the page past the slider leaves it
|
|
94
|
+
* alone.
|
|
95
|
+
*
|
|
96
|
+
* `['raw', n]` moves the value by `n`, and `['normalized', n]` by `n` of the
|
|
97
|
+
* range between `min` and `max`. The result is snapped to `step`, except for
|
|
98
|
+
* an amount set on a modifier key (`{ default: …, shift: … }`). `null` turns
|
|
99
|
+
* the wheel off.
|
|
100
|
+
*
|
|
101
|
+
* @default ['raw', 1]
|
|
102
|
+
*/
|
|
103
|
+
wheel?: ModifierValue<InputEventOption> | null
|
|
104
|
+
/**
|
|
105
|
+
* How much a drag moves the value, per modifier key.
|
|
106
|
+
*
|
|
107
|
+
* `1` is the pointer position itself, which is what a drag normally is here.
|
|
108
|
+
* **Anything else turns the drag relative**: `0.1` makes the same movement
|
|
109
|
+
* cover a tenth of the travel, so the value stops following the pointer and
|
|
110
|
+
* starts moving a tenth as fast. Shift is bound to `0.1` by default, to
|
|
111
|
+
* match what it does on the arrow keys.
|
|
112
|
+
*
|
|
113
|
+
* Pressing or releasing the key mid-drag does not disturb the value: the
|
|
114
|
+
* travel so far is kept and the new sensitivity applies from there. **The
|
|
115
|
+
* pointer and the value stay apart for the rest of the drag** — snapping
|
|
116
|
+
* them back together on release would move the value nobody asked to move.
|
|
117
|
+
*
|
|
118
|
+
* @default { default: 1, shift: 0.1 }
|
|
72
119
|
*/
|
|
73
|
-
|
|
120
|
+
dragSensitivity?: ModifierValue<number>
|
|
121
|
+
|
|
74
122
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
123
|
+
* How much one arrow key press moves the value.
|
|
124
|
+
*
|
|
125
|
+
* `['raw', n]` moves the value by `n`, and `['normalized', n]` by `n` of the
|
|
126
|
+
* range between `min` and `max`. The result is snapped to `step`, except for
|
|
127
|
+
* an amount set on a modifier key, which is what lets shift move off the
|
|
128
|
+
* grid. `null` turns the arrow keys off.
|
|
129
|
+
*
|
|
130
|
+
* The default moves by 1, and by 0.1 with shift. With a `step` above 1, raise
|
|
131
|
+
* the amount to match: 1 would round straight back to where it started, and
|
|
132
|
+
* a development build warns about it.
|
|
133
|
+
*
|
|
134
|
+
* @default { default: ['raw', 1], shift: ['raw', 0.1] }
|
|
77
135
|
*/
|
|
78
|
-
keyboard?: InputEventOption | null
|
|
136
|
+
keyboard?: ModifierValue<InputEventOption> | null
|
|
79
137
|
|
|
80
138
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* aria-disabled property is also applied.
|
|
139
|
+
* Make the slider unchangeable and remove it from the tab order.
|
|
140
|
+
* The parts carry `data-disabled` while it is set.
|
|
84
141
|
*/
|
|
85
142
|
disabled?: boolean
|
|
86
143
|
/**
|
|
87
144
|
* Make the value unchangeable.
|
|
88
|
-
*
|
|
145
|
+
* The parts carry `data-readonly` while it is set.
|
|
89
146
|
*/
|
|
90
147
|
readonly?: boolean
|
|
91
|
-
|
|
92
|
-
style?: CSSProperties
|
|
148
|
+
/** Called with the new value when a drag, the wheel or an arrow key moves it. */
|
|
93
149
|
onChange?: (value: number) => void
|
|
150
|
+
/** Called when a drag starts, with the value where the track was pressed. */
|
|
94
151
|
onDragStart?: (value: number) => void
|
|
152
|
+
/** Called when the drag ends, with the value it ended on. */
|
|
95
153
|
onDragEnd?: (value: number) => void
|
|
96
154
|
/**
|
|
97
155
|
* The slider renders exactly what you compose here; there is no default
|
|
@@ -100,7 +158,7 @@ export interface SliderProps {
|
|
|
100
158
|
* @example
|
|
101
159
|
* <Slider.Root value={value} min={0} max={100} onChange={setValue}>
|
|
102
160
|
* <Slider.Track>
|
|
103
|
-
* <Slider.Thumb />
|
|
161
|
+
* <Slider.Thumb aria-label="Level" />
|
|
104
162
|
* </Slider.Track>
|
|
105
163
|
* </Slider.Root>
|
|
106
164
|
*/
|
|
@@ -121,7 +179,7 @@ type Props = SliderProps &
|
|
|
121
179
|
*/
|
|
122
180
|
const WHEEL_OPTIONS = { requireFocus: true }
|
|
123
181
|
|
|
124
|
-
export const Root = forwardRef<SliderMethods, Props>(
|
|
182
|
+
export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
125
183
|
(
|
|
126
184
|
{
|
|
127
185
|
value,
|
|
@@ -132,8 +190,9 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
132
190
|
vertical = false,
|
|
133
191
|
reverse = false,
|
|
134
192
|
externalStyles: _externalStyles,
|
|
135
|
-
wheel =
|
|
136
|
-
keyboard =
|
|
193
|
+
wheel = DEFAULT_WHEEL_OPTIONS,
|
|
194
|
+
keyboard = DEFAULT_KEYBOARD_OPTIONS,
|
|
195
|
+
dragSensitivity = DEFAULT_DRAG_SENSITIVITY,
|
|
137
196
|
disabled = false,
|
|
138
197
|
readonly = false,
|
|
139
198
|
onChange,
|
|
@@ -153,9 +212,9 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
153
212
|
// -- state and ref ---
|
|
154
213
|
const trackRef = useRef<HTMLDivElement>(null)
|
|
155
214
|
const thumbRef = useRef<SliderThumbMethods>(null)
|
|
156
|
-
|
|
157
215
|
// --- interpret props ---
|
|
158
216
|
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
217
|
+
const inactive = disabled || readonly
|
|
159
218
|
|
|
160
219
|
const p = toFixed(scale.normalize(value, min, max) * 100)
|
|
161
220
|
const rev = toFixed(100 - p)
|
|
@@ -164,10 +223,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
164
223
|
// vertical -> rev (up)
|
|
165
224
|
// reverse -> rev (left)
|
|
166
225
|
// vertical & reverse -> normal (down)
|
|
167
|
-
const displayReversed =
|
|
168
|
-
() => xor(vertical, reverse),
|
|
169
|
-
[vertical, reverse],
|
|
170
|
-
)
|
|
226
|
+
const displayReversed = vertical !== reverse
|
|
171
227
|
const percent = displayReversed ? rev : p
|
|
172
228
|
|
|
173
229
|
// --- internal functions ---
|
|
@@ -179,18 +235,20 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
179
235
|
[min, max, step, scale, displayReversed],
|
|
180
236
|
)
|
|
181
237
|
|
|
238
|
+
useCheckSteps({ component: 'Slider', range: axis, keyboard, wheel })
|
|
239
|
+
|
|
182
240
|
const handleKeyDown = useCallback(
|
|
183
241
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
184
|
-
if (!keyboard || !onChange || readonly) return
|
|
185
242
|
const key = event.key
|
|
186
|
-
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key))
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
243
|
+
if (!['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key))
|
|
244
|
+
return
|
|
245
|
+
event.preventDefault()
|
|
246
|
+
if (!keyboard || !onChange || inactive) return
|
|
247
|
+
let direction = key === 'ArrowRight' || key === 'ArrowUp' ? 1 : -1
|
|
248
|
+
if (reverse) direction *= -1
|
|
249
|
+
onChange(applyDelta(value, direction, keyboard, axis, event))
|
|
192
250
|
},
|
|
193
|
-
[keyboard, onChange,
|
|
251
|
+
[keyboard, onChange, inactive, reverse, value, axis],
|
|
194
252
|
)
|
|
195
253
|
|
|
196
254
|
// --- hooks ---
|
|
@@ -199,40 +257,38 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
199
257
|
const { refCallback: dragRefCallback } = useDragValue<HTMLDivElement>({
|
|
200
258
|
axis,
|
|
201
259
|
baseElementRef: trackRef,
|
|
202
|
-
cursor:
|
|
260
|
+
cursor: inactive ? undefined : externalStyles.cursor,
|
|
261
|
+
shouldStart: () => !inactive,
|
|
262
|
+
sensitivity: (state) =>
|
|
263
|
+
selectModifier(dragSensitivity, state.event).value,
|
|
203
264
|
updateOnPointerDown: true,
|
|
204
265
|
onChange: (v) => {
|
|
205
|
-
if (
|
|
266
|
+
if (inactive) return
|
|
206
267
|
onChange?.(valueOf(v))
|
|
207
268
|
},
|
|
208
269
|
onDragStart: (v) => {
|
|
209
|
-
if (
|
|
210
|
-
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
211
|
-
|
|
270
|
+
if (inactive) return
|
|
212
271
|
thumbRef.current?.focus()
|
|
213
272
|
onDragStart?.(valueOf(v))
|
|
214
273
|
},
|
|
215
274
|
onDragEnd: (v) => {
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
219
|
-
|
|
275
|
+
if (inactive) return
|
|
220
276
|
onDragEnd?.(valueOf(v))
|
|
221
277
|
},
|
|
222
278
|
})
|
|
223
279
|
|
|
224
280
|
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
225
|
-
if (!wheel || !onChange ||
|
|
281
|
+
if (!wheel || !onChange || inactive) return
|
|
226
282
|
event.preventDefault()
|
|
227
283
|
let direction
|
|
228
|
-
if (!vertical && event.deltaX
|
|
284
|
+
if (!vertical && event.deltaX !== 0) {
|
|
229
285
|
direction = event.deltaX > 0 ? 1 : -1
|
|
230
286
|
} else {
|
|
231
|
-
if (event.deltaY
|
|
287
|
+
if (event.deltaY === 0) return
|
|
232
288
|
direction = event.deltaY > 0 ? -1 : 1
|
|
233
289
|
}
|
|
234
|
-
if (
|
|
235
|
-
onChange(applyDelta(value, direction, wheel, axis))
|
|
290
|
+
if (reverse) direction *= -1
|
|
291
|
+
onChange(applyDelta(value, direction, wheel, axis, event))
|
|
236
292
|
}, WHEEL_OPTIONS)
|
|
237
293
|
|
|
238
294
|
// Composed once, so React attaches the refs a single time instead of
|
|
@@ -253,6 +309,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
253
309
|
reverse,
|
|
254
310
|
disabled,
|
|
255
311
|
readonly,
|
|
312
|
+
onChange,
|
|
256
313
|
percent,
|
|
257
314
|
trackRef,
|
|
258
315
|
thumbRef,
|
|
@@ -267,6 +324,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
267
324
|
reverse,
|
|
268
325
|
disabled,
|
|
269
326
|
readonly,
|
|
327
|
+
onChange,
|
|
270
328
|
percent,
|
|
271
329
|
],
|
|
272
330
|
)
|
|
@@ -274,28 +332,29 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
274
332
|
useImperativeHandle(forwardedRef, () => {
|
|
275
333
|
return {
|
|
276
334
|
focus() {
|
|
277
|
-
thumbRef.current?.focus()
|
|
335
|
+
if (!disabled) thumbRef.current?.focus()
|
|
278
336
|
},
|
|
279
337
|
blur() {
|
|
280
338
|
thumbRef.current?.blur()
|
|
281
339
|
},
|
|
282
340
|
}
|
|
283
|
-
}, [])
|
|
341
|
+
}, [disabled])
|
|
284
342
|
|
|
285
343
|
return (
|
|
286
344
|
<SliderProvider value={context}>
|
|
345
|
+
{/* oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the group is the pointer and keyboard event area, while the nested range input carries its control semantics */}
|
|
287
346
|
<div
|
|
288
|
-
className={
|
|
347
|
+
className={className}
|
|
289
348
|
ref={rootRefCallback}
|
|
349
|
+
role="group"
|
|
350
|
+
// A press lands on the track or the thumb, neither of which can hold
|
|
351
|
+
// focus, and the browser answers that by clearing the focus to the
|
|
352
|
+
// body — undoing the focus the drag just gave the input. Taking the
|
|
353
|
+
// focus here keeps it inside, and `onFocus` passes it to the input.
|
|
290
354
|
tabIndex={-1}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
aria-valuemax={max}
|
|
295
|
-
aria-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
296
|
-
aria-disabled={disabled}
|
|
297
|
-
aria-readonly={readonly}
|
|
298
|
-
data-vertical={vertical}
|
|
355
|
+
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
356
|
+
data-disabled={disabled ? '' : undefined}
|
|
357
|
+
data-readonly={readonly ? '' : undefined}
|
|
299
358
|
style={style}
|
|
300
359
|
onPointerDown={onPointerDown}
|
|
301
360
|
onKeyDown={(event) => {
|
|
@@ -303,7 +362,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
303
362
|
onKeyDown?.(event)
|
|
304
363
|
}}
|
|
305
364
|
onFocus={(event) => {
|
|
306
|
-
thumbRef.current?.focus()
|
|
365
|
+
if (!disabled) thumbRef.current?.focus()
|
|
307
366
|
onFocus?.(event)
|
|
308
367
|
}}
|
|
309
368
|
onBlur={(event) => {
|
|
@@ -330,9 +389,9 @@ export const Slider = {
|
|
|
330
389
|
MarksOption,
|
|
331
390
|
}
|
|
332
391
|
|
|
333
|
-
export { useSliderContext } from './context'
|
|
392
|
+
export { useSliderContext, type SliderContextValue } from './context'
|
|
334
393
|
export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
335
394
|
export { type SliderTrackProps } from './Track'
|
|
336
|
-
export { type
|
|
337
|
-
export { type
|
|
338
|
-
export { type MarksOptions
|
|
395
|
+
export { type SliderMarksProps } from './Marks'
|
|
396
|
+
export { type SliderMarksOptionProps } from './MarksOption'
|
|
397
|
+
export { type MarksOptions } from './type'
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toFixed } from '@tremolo-ui/functions'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { decimalDigits } from '../_util/decimal-digits'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How `Slider.Marks` fills itself in when it is given no children: one option
|
|
7
|
+
* every `per`, or every `step` of the slider. The object form turns off the
|
|
8
|
+
* mark or the label for the whole set; a single option is customized by
|
|
9
|
+
* writing `Slider.MarksOption` out instead.
|
|
10
|
+
*/
|
|
11
|
+
export type MarksOptions =
|
|
12
|
+
| 'step'
|
|
13
|
+
| number
|
|
14
|
+
| {
|
|
15
|
+
per: 'step' | number
|
|
16
|
+
mark?: boolean
|
|
17
|
+
label?: boolean
|
|
18
|
+
}
|
|
5
19
|
|
|
6
20
|
export function generateOptionsList(
|
|
7
21
|
options: MarksOptions,
|
|
@@ -9,18 +23,24 @@ export function generateOptionsList(
|
|
|
9
23
|
max: number,
|
|
10
24
|
step: number,
|
|
11
25
|
) {
|
|
26
|
+
const {
|
|
27
|
+
per,
|
|
28
|
+
mark = true,
|
|
29
|
+
label = true,
|
|
30
|
+
} = typeof options === 'object' ? options : { per: options }
|
|
12
31
|
const optionsList: {
|
|
13
32
|
value: number
|
|
14
|
-
|
|
33
|
+
mark: boolean
|
|
34
|
+
label: boolean
|
|
15
35
|
}[] = []
|
|
16
|
-
const
|
|
17
|
-
const count = Math.floor(max /
|
|
36
|
+
const interval = per === 'step' ? step : per
|
|
37
|
+
const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1
|
|
18
38
|
for (let i = 0; i < count; i++) {
|
|
19
39
|
const value = toFixed(
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
interval * (Math.ceil(min / interval) + i),
|
|
41
|
+
decimalDigits(interval),
|
|
22
42
|
)
|
|
23
|
-
optionsList.push({ value: value,
|
|
43
|
+
optionsList.push({ value: value, mark: mark, label: label })
|
|
24
44
|
}
|
|
25
45
|
return optionsList
|
|
26
46
|
}
|
|
@@ -1,51 +1,56 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ComponentPropsWithoutRef, CSSProperties, ReactNode, Ref } from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
|
|
3
2
|
|
|
4
|
-
import { useComposedRefs } from '
|
|
3
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
4
|
+
import { cssLength } from '../_util/css-length'
|
|
5
|
+
import { Placement } from '../_util/Placement'
|
|
5
6
|
|
|
6
7
|
import { useXYPadContext } from './context'
|
|
7
8
|
|
|
9
|
+
import type { CSSVariables } from '../../css-variables'
|
|
10
|
+
|
|
8
11
|
export interface XYPadAreaProps {
|
|
12
|
+
/** Sets `--width`; the size the theme gives it stands when omitted. */
|
|
9
13
|
width?: number | string
|
|
14
|
+
/** Sets `--height`; the size the theme gives it stands when omitted. */
|
|
10
15
|
height?: number | string
|
|
16
|
+
/** Sets `--color`, for the theme to colour the area with. */
|
|
11
17
|
color?: string
|
|
12
|
-
className?: string
|
|
13
|
-
style?: CSSProperties
|
|
14
|
-
/** `<XYPad.Thumb />` goes here. */
|
|
15
|
-
children?: ReactNode
|
|
16
|
-
ref?: Ref<HTMLDivElement>
|
|
17
|
-
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
width = 120,
|
|
21
|
-
height = 120,
|
|
22
|
-
color,
|
|
23
|
-
children,
|
|
24
|
-
className,
|
|
25
|
-
style,
|
|
26
|
-
ref,
|
|
27
|
-
...props
|
|
28
|
-
}: XYPadAreaProps &
|
|
29
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>) {
|
|
30
|
-
const { areaRef } = useXYPadContext()
|
|
31
|
-
|
|
32
|
-
// The area is what the pointer position is normalized against, so the
|
|
33
|
-
// context ref is composed with any ref the caller passed.
|
|
34
|
-
const composedRef = useComposedRefs<HTMLDivElement>(ref, areaRef)
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div
|
|
38
|
-
ref={composedRef}
|
|
39
|
-
className={clsx('tremolo-xy-pad-area', className)}
|
|
40
|
-
style={{
|
|
41
|
-
...{ '--color': color },
|
|
42
|
-
width: width,
|
|
43
|
-
height: height,
|
|
44
|
-
...style,
|
|
45
|
-
}}
|
|
46
|
-
{...props}
|
|
47
|
-
>
|
|
48
|
-
{children}
|
|
49
|
-
</div>
|
|
50
|
-
)
|
|
19
|
+
style?: CSSProperties & CSSVariables<'width' | 'height' | 'color'>
|
|
51
20
|
}
|
|
21
|
+
|
|
22
|
+
type Props = XYPadAreaProps &
|
|
23
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>
|
|
24
|
+
|
|
25
|
+
export const Area = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
26
|
+
function Area(
|
|
27
|
+
{ width, height, color, children, className, style, ...props },
|
|
28
|
+
forwardedRef,
|
|
29
|
+
) {
|
|
30
|
+
const { areaRef } = useXYPadContext()
|
|
31
|
+
|
|
32
|
+
// The area is what the pointer position is normalized against, so the
|
|
33
|
+
// context ref is composed with any ref the caller passed.
|
|
34
|
+
const composedRef = useComposedRefs<HTMLDivElement>(forwardedRef, areaRef)
|
|
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
|
+
)
|