@tremolo-ui/react 0.0.10 → 0.0.12
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/cjs/index.js +435 -203
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AnimationCanvas/index.d.ts +2 -2
- package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
- package/dist/cjs/types/components/AnimationKnob/index.d.ts +10 -3
- package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Knob/index.d.ts +13 -3
- package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +2 -0
- package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +2 -0
- package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/InternalInput.d.ts +18 -0
- package/dist/cjs/types/components/NumberInput/InternalInput.d.ts.map +1 -0
- package/dist/cjs/types/components/NumberInput/Stepper.d.ts +2 -0
- package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/context.d.ts +3 -2
- package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/index.d.ts +12 -3
- package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/index.d.ts +7 -4
- package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/key.d.ts +4 -0
- package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -2
- package/dist/cjs/types/components/Slider/Scale.d.ts +1 -3
- package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/Thumb.d.ts +1 -0
- package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/Track.d.ts +2 -4
- package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/context.d.ts +2 -10
- package/dist/cjs/types/components/Slider/context.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/index.d.ts +5 -1
- package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
- package/dist/cjs/types/components/WheelObserver/index.d.ts +3 -3
- package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
- package/dist/cjs/types/components/XYPad/Area.d.ts +3 -3
- package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
- package/dist/cjs/types/components/XYPad/Thumb.d.ts +1 -0
- package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
- package/dist/cjs/types/components/XYPad/index.d.ts +6 -1
- package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
- package/dist/cjs/types/hooks/useAnimationFrame.d.ts.map +1 -1
- package/dist/cjs/types/hooks/useInterval.d.ts +2 -0
- package/dist/cjs/types/hooks/useInterval.d.ts.map +1 -0
- package/dist/cjs/types/hooks/useLongPress.d.ts +2 -0
- package/dist/cjs/types/hooks/useLongPress.d.ts.map +1 -0
- package/dist/cjs/types/index.d.ts +5 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/esm/index.js +431 -204
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AnimationCanvas/index.d.ts +2 -2
- package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
- package/dist/esm/types/components/AnimationKnob/index.d.ts +10 -3
- package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
- package/dist/esm/types/components/Knob/index.d.ts +13 -3
- package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +2 -0
- package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +2 -0
- package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/InternalInput.d.ts +18 -0
- package/dist/esm/types/components/NumberInput/InternalInput.d.ts.map +1 -0
- package/dist/esm/types/components/NumberInput/Stepper.d.ts +2 -0
- package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/context.d.ts +3 -2
- package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/index.d.ts +12 -3
- package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/index.d.ts +7 -4
- package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/key.d.ts +4 -0
- package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -2
- package/dist/esm/types/components/Slider/Scale.d.ts +1 -3
- package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/Thumb.d.ts +1 -0
- package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/Track.d.ts +2 -4
- package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/context.d.ts +2 -10
- package/dist/esm/types/components/Slider/context.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/index.d.ts +5 -1
- package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
- package/dist/esm/types/components/WheelObserver/index.d.ts +3 -3
- package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
- package/dist/esm/types/components/XYPad/Area.d.ts +3 -3
- package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
- package/dist/esm/types/components/XYPad/Thumb.d.ts +1 -0
- package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
- package/dist/esm/types/components/XYPad/index.d.ts +6 -1
- package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
- package/dist/esm/types/hooks/useAnimationFrame.d.ts.map +1 -1
- package/dist/esm/types/hooks/useInterval.d.ts +2 -0
- package/dist/esm/types/hooks/useInterval.d.ts.map +1 -0
- package/dist/esm/types/hooks/useLongPress.d.ts +2 -0
- package/dist/esm/types/hooks/useLongPress.d.ts.map +1 -0
- package/dist/esm/types/index.d.ts +5 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/styles/index.css +115 -33
- package/package.json +41 -40
- package/src/components/AnimationCanvas/index.tsx +15 -7
- package/src/components/AnimationKnob/index.tsx +216 -187
- package/src/components/Knob/index.tsx +230 -171
- package/src/components/NumberInput/DecrementStepper.tsx +9 -2
- package/src/components/NumberInput/IncrementStepper.tsx +9 -2
- package/src/components/NumberInput/InternalInput.tsx +219 -0
- package/src/components/NumberInput/Stepper.tsx +2 -0
- package/src/components/NumberInput/context.tsx +21 -9
- package/src/components/NumberInput/index.css +76 -22
- package/src/components/NumberInput/index.tsx +94 -162
- package/src/components/Piano/BlackKey.tsx +14 -0
- package/src/components/Piano/WhiteKey.tsx +14 -0
- package/src/components/Piano/index.css +22 -6
- package/src/components/Piano/index.tsx +20 -22
- package/src/components/Piano/key.ts +5 -0
- package/src/components/Piano/keyboardShortcuts.ts +2 -2
- package/src/components/Slider/Scale.tsx +1 -4
- package/src/components/Slider/Thumb.tsx +6 -0
- package/src/components/Slider/Track.tsx +5 -4
- package/src/components/Slider/context.tsx +5 -20
- package/src/components/Slider/index.css +5 -2
- package/src/components/Slider/index.tsx +52 -26
- package/src/components/WheelObserver/index.tsx +4 -2
- package/src/components/XYPad/Area.tsx +7 -6
- package/src/components/XYPad/Thumb.tsx +6 -0
- package/src/components/XYPad/index.css +12 -2
- package/src/components/XYPad/index.tsx +57 -41
- package/src/hooks/useInterval.ts +27 -0
- package/src/hooks/useLongPress.ts +31 -0
- package/src/index.ts +6 -0
- package/src/styles/index.css +0 -1
|
@@ -9,16 +9,7 @@ type State = {
|
|
|
9
9
|
vertical: boolean
|
|
10
10
|
reverse: boolean
|
|
11
11
|
disabled: boolean
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type Action = {
|
|
15
|
-
updateMin: (min: State['min']) => void
|
|
16
|
-
updateMax: (max: State['max']) => void
|
|
17
|
-
updateStep: (step: State['step']) => void
|
|
18
|
-
updateSkew: (skew: State['skew']) => void
|
|
19
|
-
updateVertical: (vertical: State['vertical']) => void
|
|
20
|
-
updateReverse: (reverse: State['reverse']) => void
|
|
21
|
-
updateDisabled: (disabled: State['disabled']) => void
|
|
12
|
+
readonly: boolean
|
|
22
13
|
}
|
|
23
14
|
|
|
24
15
|
type SliderStore = ReturnType<typeof createSliderStore>
|
|
@@ -32,18 +23,12 @@ const createSliderStore = (initProps?: Partial<State>) => {
|
|
|
32
23
|
vertical: false,
|
|
33
24
|
reverse: false,
|
|
34
25
|
disabled: false,
|
|
26
|
+
readonly: false,
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
return createStore<State
|
|
29
|
+
return createStore<State>()(() => ({
|
|
38
30
|
...DEFAULT_PROPS,
|
|
39
31
|
...initProps,
|
|
40
|
-
updateMin: (min) => set(() => ({ min: min })),
|
|
41
|
-
updateMax: (max) => set(() => ({ max: max })),
|
|
42
|
-
updateStep: (step) => set(() => ({ step: step })),
|
|
43
|
-
updateSkew: (skew) => set(() => ({ skew: skew })),
|
|
44
|
-
updateVertical: (vertical) => set(() => ({ vertical: vertical })),
|
|
45
|
-
updateReverse: (reverse) => set(() => ({ reverse: reverse })),
|
|
46
|
-
updateDisabled: (disabled) => set(() => ({ disabled: disabled })),
|
|
47
32
|
}))
|
|
48
33
|
}
|
|
49
34
|
|
|
@@ -52,7 +37,7 @@ const SliderContext = createContext<SliderStore | null>(null)
|
|
|
52
37
|
type SliderProviderProps = React.PropsWithChildren<Partial<State>>
|
|
53
38
|
|
|
54
39
|
export function SliderProvider({ children, ...props }: SliderProviderProps) {
|
|
55
|
-
const storeRef = useRef<SliderStore>()
|
|
40
|
+
const storeRef = useRef<SliderStore>(null)
|
|
56
41
|
if (!storeRef.current) {
|
|
57
42
|
storeRef.current = createSliderStore(props)
|
|
58
43
|
}
|
|
@@ -73,7 +58,7 @@ export function SliderProvider({ children, ...props }: SliderProviderProps) {
|
|
|
73
58
|
}
|
|
74
59
|
|
|
75
60
|
/** @category Slider */
|
|
76
|
-
export function useSliderContext<T>(selector: (state: State
|
|
61
|
+
export function useSliderContext<T>(selector: (state: State) => T): T {
|
|
77
62
|
const store = useContext(SliderContext)
|
|
78
63
|
if (!store) throw new Error('Missing SliderContext.Provider in the tree')
|
|
79
64
|
return useStore(store, selector)
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.tremolo-slider-thumb {
|
|
19
|
-
|
|
19
|
+
--color: var(--tremolo-theme-color);
|
|
20
|
+
|
|
21
|
+
background: var(--color);
|
|
20
22
|
border-radius: 50%;
|
|
21
23
|
outline: none;
|
|
22
24
|
|
|
@@ -25,7 +27,8 @@
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
&[aria-readonly='false']:focus {
|
|
28
|
-
box-shadow: 0px 0px 0px 3px
|
|
30
|
+
box-shadow: 0px 0px 0px 3px
|
|
31
|
+
color-mix(in srgb, var(--color) 20%, transparent);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -63,12 +63,16 @@ export interface SliderProps {
|
|
|
63
63
|
className?: string
|
|
64
64
|
style?: CSSProperties
|
|
65
65
|
onChange?: (value: number) => void
|
|
66
|
+
onDragStart?: (value: number) => void
|
|
67
|
+
onDragEnd?: (value: number) => void
|
|
66
68
|
/** \<SliderThumb /> | \<SliderTrack /> */
|
|
67
69
|
children?: ReactElement | ReactElement[]
|
|
68
70
|
// ReactElement<typeof SliderThumb, typeof SliderTrack>[]
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* @category Slider
|
|
75
|
+
*/
|
|
72
76
|
export interface SliderMethods {
|
|
73
77
|
focus: () => void
|
|
74
78
|
blur: () => void
|
|
@@ -91,15 +95,21 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
91
95
|
skew = 1,
|
|
92
96
|
vertical = false,
|
|
93
97
|
reverse = false,
|
|
94
|
-
className,
|
|
95
|
-
style,
|
|
96
98
|
bodyNoSelect = true,
|
|
97
99
|
wheel = ['raw', 1],
|
|
98
100
|
keyboard = ['raw', 1],
|
|
99
101
|
disabled = false,
|
|
100
102
|
readonly = false,
|
|
101
103
|
onChange,
|
|
104
|
+
onDragStart,
|
|
105
|
+
onDragEnd,
|
|
106
|
+
className,
|
|
107
|
+
style,
|
|
102
108
|
children,
|
|
109
|
+
onFocus,
|
|
110
|
+
onBlur,
|
|
111
|
+
onPointerDown,
|
|
112
|
+
onKeyDown,
|
|
103
113
|
...props
|
|
104
114
|
}: Props,
|
|
105
115
|
ref,
|
|
@@ -171,9 +181,25 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
171
181
|
? normalizeValue(mouseY, y1, y2)
|
|
172
182
|
: normalizeValue(mouseX, x1, x2)
|
|
173
183
|
const v = rawValue(displayReversed ? 1 - n : n, min, max, skew)
|
|
174
|
-
|
|
184
|
+
const v2 = clamp(stepValue(v, step), min, max)
|
|
185
|
+
onChange(v2)
|
|
186
|
+
return v2
|
|
175
187
|
}
|
|
176
188
|
|
|
189
|
+
const updateValueByEvent = useCallback(
|
|
190
|
+
(eventType: InputEventOption[0], x: number) => {
|
|
191
|
+
let newValue
|
|
192
|
+
if (eventType == 'normalized') {
|
|
193
|
+
const n = normalizeValue(value, min, max, skew)
|
|
194
|
+
newValue = rawValue(n + x, min, max, skew)
|
|
195
|
+
} else {
|
|
196
|
+
newValue = value + x
|
|
197
|
+
}
|
|
198
|
+
return clamp(stepValue(newValue, step), min, max)
|
|
199
|
+
},
|
|
200
|
+
[max, min, skew, step, value],
|
|
201
|
+
)
|
|
202
|
+
|
|
177
203
|
const handleKeyDown = useCallback(
|
|
178
204
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
179
205
|
if (!keyboard || !onChange || readonly) return
|
|
@@ -183,17 +209,10 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
183
209
|
let x =
|
|
184
210
|
key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1]
|
|
185
211
|
if (reverse) x *= -1
|
|
186
|
-
|
|
187
|
-
if (keyboard[0] == 'normalized') {
|
|
188
|
-
const n = normalizeValue(value, min, max, skew)
|
|
189
|
-
v = rawValue(n + x, min, max, skew)
|
|
190
|
-
} else {
|
|
191
|
-
v = value + x
|
|
192
|
-
}
|
|
193
|
-
onChange(clamp(stepValue(v, step), min, max))
|
|
212
|
+
onChange(updateValueByEvent(keyboard[0], x))
|
|
194
213
|
}
|
|
195
214
|
},
|
|
196
|
-
[
|
|
215
|
+
[keyboard, onChange, readonly, reverse, updateValueByEvent],
|
|
197
216
|
)
|
|
198
217
|
|
|
199
218
|
// --- hooks ---
|
|
@@ -217,19 +236,12 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
217
236
|
x = event.deltaY > 0 ? -wheel[1] : wheel[1]
|
|
218
237
|
}
|
|
219
238
|
if (vertical && reverse) x *= -1
|
|
220
|
-
|
|
221
|
-
if (wheel[0] == 'normalized') {
|
|
222
|
-
const n = normalizeValue(value, min, max, skew)
|
|
223
|
-
v = rawValue(n + x, min, max, skew)
|
|
224
|
-
} else {
|
|
225
|
-
v = value + x
|
|
226
|
-
}
|
|
227
|
-
onChange(clamp(stepValue(v, step), min, max))
|
|
239
|
+
onChange(updateValueByEvent(wheel[0], x))
|
|
228
240
|
},
|
|
229
241
|
{
|
|
230
242
|
passive: false,
|
|
231
243
|
},
|
|
232
|
-
[wheel,
|
|
244
|
+
[wheel, vertical, readonly, onChange, updateValueByEvent],
|
|
233
245
|
)
|
|
234
246
|
|
|
235
247
|
useEventListener(globalThis.window, 'mousemove', (event) => {
|
|
@@ -237,7 +249,9 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
237
249
|
})
|
|
238
250
|
|
|
239
251
|
useEventListener(globalThis.window, 'mouseup', () => {
|
|
252
|
+
if (!thumbDragged.current || readonly) return
|
|
240
253
|
thumbDragged.current = false
|
|
254
|
+
onDragEnd?.(value)
|
|
241
255
|
if (bodyNoSelect) removeNoSelect()
|
|
242
256
|
})
|
|
243
257
|
|
|
@@ -261,6 +275,7 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
261
275
|
vertical={vertical}
|
|
262
276
|
reverse={reverse}
|
|
263
277
|
disabled={disabled}
|
|
278
|
+
readonly={readonly}
|
|
264
279
|
>
|
|
265
280
|
<div
|
|
266
281
|
className={clsx('tremolo-slider', className)}
|
|
@@ -282,11 +297,22 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
282
297
|
}}
|
|
283
298
|
onPointerDown={(event) => {
|
|
284
299
|
thumbDragged.current = true
|
|
285
|
-
handleValue(event)
|
|
300
|
+
const v = handleValue(event)
|
|
301
|
+
if (!readonly) onDragStart?.(v ?? value)
|
|
302
|
+
onPointerDown?.(event)
|
|
303
|
+
}}
|
|
304
|
+
onKeyDown={(event) => {
|
|
305
|
+
handleKeyDown(event)
|
|
306
|
+
onKeyDown?.(event)
|
|
307
|
+
}}
|
|
308
|
+
onFocus={(event) => {
|
|
309
|
+
thumbRef.current?.focus()
|
|
310
|
+
onFocus?.(event)
|
|
311
|
+
}}
|
|
312
|
+
onBlur={(event) => {
|
|
313
|
+
thumbRef.current?.blur()
|
|
314
|
+
onBlur?.(event)
|
|
286
315
|
}}
|
|
287
|
-
onKeyDown={handleKeyDown}
|
|
288
|
-
onFocus={thumbRef.current?.focus}
|
|
289
|
-
onBlur={thumbRef.current?.blur}
|
|
290
316
|
{...props}
|
|
291
317
|
>
|
|
292
318
|
<div
|
|
@@ -14,7 +14,8 @@ import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
|
14
14
|
* </WheelObserver>
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/** @category WheelObserver */
|
|
18
|
+
export interface WheelObserverProps<T extends ElementType> {
|
|
18
19
|
/**
|
|
19
20
|
* React.ElementType
|
|
20
21
|
* @default div
|
|
@@ -27,7 +28,8 @@ interface Props<T extends ElementType> {
|
|
|
27
28
|
|
|
28
29
|
/** @category WheelObserver */
|
|
29
30
|
export function WheelObserver<T extends ElementType = 'div'>(
|
|
30
|
-
props:
|
|
31
|
+
props: WheelObserverProps<T> &
|
|
32
|
+
Omit<ComponentPropsWithoutRef<T>, keyof WheelObserverProps<T>>,
|
|
31
33
|
) {
|
|
32
34
|
const { children, onWheel, as: Component = 'div', ...attributes } = props
|
|
33
35
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
-
import { CSSProperties, ReactElement } from 'react'
|
|
2
|
+
import { ComponentPropsWithoutRef, CSSProperties, ReactElement } from 'react'
|
|
3
3
|
|
|
4
4
|
/** @category XYPad */
|
|
5
5
|
export interface AreaProps {
|
|
6
6
|
width?: number | string
|
|
7
7
|
height?: number | string
|
|
8
|
-
|
|
8
|
+
color?: string
|
|
9
9
|
className?: string
|
|
10
10
|
style?: CSSProperties
|
|
11
11
|
children?: ReactElement
|
|
@@ -18,22 +18,23 @@ export interface AreaProps {
|
|
|
18
18
|
export function XYPadArea({
|
|
19
19
|
width = 120,
|
|
20
20
|
height = 120,
|
|
21
|
-
|
|
21
|
+
color,
|
|
22
22
|
children,
|
|
23
23
|
className,
|
|
24
24
|
style,
|
|
25
25
|
__thumb,
|
|
26
|
-
|
|
26
|
+
...props
|
|
27
|
+
}: AreaProps & Omit<ComponentPropsWithoutRef<'div'>, keyof AreaProps>) {
|
|
27
28
|
return (
|
|
28
29
|
<div
|
|
29
30
|
className={clsx('tremolo-xy-pad-area', className)}
|
|
30
31
|
style={{
|
|
31
|
-
|
|
32
|
+
...{ '--color': color },
|
|
32
33
|
width: width,
|
|
33
34
|
height: height,
|
|
34
|
-
background: bg,
|
|
35
35
|
...style,
|
|
36
36
|
}}
|
|
37
|
+
{...props}
|
|
37
38
|
>
|
|
38
39
|
{children}
|
|
39
40
|
{__thumb}
|
|
@@ -14,6 +14,8 @@ export interface ThumbProps {
|
|
|
14
14
|
width?: number | string
|
|
15
15
|
height?: number | string
|
|
16
16
|
|
|
17
|
+
color?: string
|
|
18
|
+
|
|
17
19
|
wrapperClassName?: string
|
|
18
20
|
wrapperStyle?: CSSProperties
|
|
19
21
|
|
|
@@ -41,6 +43,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
41
43
|
size,
|
|
42
44
|
width = 22,
|
|
43
45
|
height = 22,
|
|
46
|
+
color,
|
|
44
47
|
children,
|
|
45
48
|
wrapperClassName,
|
|
46
49
|
wrapperStyle,
|
|
@@ -48,6 +51,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
48
51
|
style,
|
|
49
52
|
__disabled,
|
|
50
53
|
__css,
|
|
54
|
+
...props
|
|
51
55
|
}: Props,
|
|
52
56
|
ref,
|
|
53
57
|
) => {
|
|
@@ -71,6 +75,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
71
75
|
...__css,
|
|
72
76
|
...wrapperStyle,
|
|
73
77
|
}}
|
|
78
|
+
{...props}
|
|
74
79
|
>
|
|
75
80
|
{children ? (
|
|
76
81
|
children
|
|
@@ -83,6 +88,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
83
88
|
tabIndex={0}
|
|
84
89
|
aria-disabled={__disabled}
|
|
85
90
|
style={{
|
|
91
|
+
...{ '--color': color },
|
|
86
92
|
width: size ?? width,
|
|
87
93
|
height: size ?? height,
|
|
88
94
|
...style,
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
cursor: pointer;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
.tremolo-xy-pad-area {
|
|
7
|
+
--color: #eee;
|
|
8
|
+
|
|
9
|
+
position: relative;
|
|
10
|
+
background-color: var(--color);
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
.tremolo-xy-pad-thumb-wrapper {
|
|
7
14
|
position: absolute;
|
|
8
15
|
translate: -50% -50%;
|
|
@@ -10,15 +17,18 @@
|
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
.tremolo-xy-pad-thumb {
|
|
20
|
+
--color: var(--tremolo-theme-color);
|
|
21
|
+
|
|
13
22
|
border-radius: 50%;
|
|
14
23
|
outline: none;
|
|
15
|
-
background-color:
|
|
24
|
+
background-color: var(--color);
|
|
16
25
|
|
|
17
26
|
&[aria-disabled='true'] {
|
|
18
27
|
background-color: #5d6478;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
&:focus {
|
|
22
|
-
box-shadow: 0px 0px 0px 3px
|
|
31
|
+
box-shadow: 0px 0px 0px 3px
|
|
32
|
+
color-mix(in srgb, var(--color) 20%, transparent);
|
|
23
33
|
}
|
|
24
34
|
}
|
|
@@ -26,7 +26,10 @@ import { addNoSelect, removeNoSelect } from '../_util'
|
|
|
26
26
|
import { XYPadArea, AreaProps } from './Area'
|
|
27
27
|
import { XYPadThumb, XYPadThumbMethods, ThumbProps } from './Thumb'
|
|
28
28
|
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Two-dimensional slider component.
|
|
31
|
+
* @category XYPad
|
|
32
|
+
*/
|
|
30
33
|
export interface ValueOptions {
|
|
31
34
|
// required
|
|
32
35
|
value: number
|
|
@@ -56,6 +59,8 @@ export interface XYPadProps {
|
|
|
56
59
|
disabled?: boolean
|
|
57
60
|
readonly?: boolean
|
|
58
61
|
onChange?: (valueX: number, valueY: number) => void
|
|
62
|
+
onDragStart?: (valueX: number, valueY: number) => void
|
|
63
|
+
onDragEnd?: (valueX: number, valueY: number) => void
|
|
59
64
|
/** \<XYPadThumb /> | \<XYPadArea /> */
|
|
60
65
|
children?: ReactElement | ReactElement[]
|
|
61
66
|
}
|
|
@@ -92,6 +97,12 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
92
97
|
disabled = false,
|
|
93
98
|
readonly = false,
|
|
94
99
|
onChange,
|
|
100
|
+
onDragStart,
|
|
101
|
+
onDragEnd,
|
|
102
|
+
onPointerDown,
|
|
103
|
+
onKeyDown,
|
|
104
|
+
onFocus,
|
|
105
|
+
onBlur,
|
|
95
106
|
children,
|
|
96
107
|
...props
|
|
97
108
|
}: Props,
|
|
@@ -162,8 +173,32 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
162
173
|
const newX = clamp(stepValue(vx, x.step), x.min, x.max)
|
|
163
174
|
const newY = clamp(stepValue(vy, y.step), y.min, y.max)
|
|
164
175
|
onChange(newX, newY)
|
|
176
|
+
return [newX, newY]
|
|
165
177
|
}
|
|
166
178
|
|
|
179
|
+
const updateValueByEvent = useCallback(
|
|
180
|
+
(
|
|
181
|
+
eventType: InputEventOption[0],
|
|
182
|
+
target: Required<ValueOptions>,
|
|
183
|
+
x: number,
|
|
184
|
+
) => {
|
|
185
|
+
let v
|
|
186
|
+
if (eventType == 'normalized') {
|
|
187
|
+
const n = normalizeValue(
|
|
188
|
+
target.value,
|
|
189
|
+
target.min,
|
|
190
|
+
target.max,
|
|
191
|
+
target.skew,
|
|
192
|
+
)
|
|
193
|
+
v = rawValue(n + x, target.min, target.max, target.skew)
|
|
194
|
+
} else {
|
|
195
|
+
v = target.value + x
|
|
196
|
+
}
|
|
197
|
+
return clamp(stepValue(v, target.step), target.min, target.max)
|
|
198
|
+
},
|
|
199
|
+
[],
|
|
200
|
+
)
|
|
201
|
+
|
|
167
202
|
const handleKeyDown = useCallback(
|
|
168
203
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
169
204
|
if (!onChange || readonly) return
|
|
@@ -176,23 +211,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
176
211
|
let delta = target.keyboard[1]
|
|
177
212
|
if (key == 'ArrowLeft' || key == 'ArrowUp') delta *= -1
|
|
178
213
|
if (target.reverse) delta *= -1
|
|
179
|
-
|
|
180
|
-
if (target.keyboard[0] == 'normalized') {
|
|
181
|
-
const n = normalizeValue(
|
|
182
|
-
target.value,
|
|
183
|
-
target.min,
|
|
184
|
-
target.max,
|
|
185
|
-
target.skew,
|
|
186
|
-
)
|
|
187
|
-
v = rawValue(n + delta, target.min, target.max, target.skew)
|
|
188
|
-
} else {
|
|
189
|
-
v = target.value + delta
|
|
190
|
-
}
|
|
191
|
-
const newValue = clamp(
|
|
192
|
-
stepValue(v, target.step),
|
|
193
|
-
target.min,
|
|
194
|
-
target.max,
|
|
195
|
-
)
|
|
214
|
+
const newValue = updateValueByEvent(target.keyboard[0], target, delta)
|
|
196
215
|
if (isHorizontal) {
|
|
197
216
|
onChange(newValue, y.value)
|
|
198
217
|
} else {
|
|
@@ -200,7 +219,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
200
219
|
}
|
|
201
220
|
}
|
|
202
221
|
},
|
|
203
|
-
[x, y,
|
|
222
|
+
[onChange, readonly, x, y, updateValueByEvent],
|
|
204
223
|
)
|
|
205
224
|
|
|
206
225
|
// --- hooks ---
|
|
@@ -221,23 +240,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
221
240
|
let delta = target.wheel[1]
|
|
222
241
|
if (event.deltaY < 0) delta *= -1
|
|
223
242
|
if (target.reverse) delta *= -1
|
|
224
|
-
|
|
225
|
-
if (target.wheel[0] == 'normalized') {
|
|
226
|
-
const n = normalizeValue(
|
|
227
|
-
target.value,
|
|
228
|
-
target.min,
|
|
229
|
-
target.max,
|
|
230
|
-
target.skew,
|
|
231
|
-
)
|
|
232
|
-
v = rawValue(n + delta, target.min, target.max, target.skew)
|
|
233
|
-
} else {
|
|
234
|
-
v = target.value + delta
|
|
235
|
-
}
|
|
236
|
-
const newValue = clamp(
|
|
237
|
-
stepValue(v, target.step),
|
|
238
|
-
target.min,
|
|
239
|
-
target.max,
|
|
240
|
-
)
|
|
243
|
+
const newValue = updateValueByEvent(target.wheel[0], target, delta)
|
|
241
244
|
if (event.shiftKey) {
|
|
242
245
|
onChange(newValue, y.value)
|
|
243
246
|
} else {
|
|
@@ -247,7 +250,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
247
250
|
{
|
|
248
251
|
passive: false,
|
|
249
252
|
},
|
|
250
|
-
[x, y],
|
|
253
|
+
[x, y, onChange, readonly, updateValueByEvent],
|
|
251
254
|
)
|
|
252
255
|
|
|
253
256
|
useEventListener(globalThis.window, 'mousemove', (event) => {
|
|
@@ -255,7 +258,9 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
255
258
|
})
|
|
256
259
|
|
|
257
260
|
useEventListener(globalThis.window, 'mouseup', () => {
|
|
261
|
+
if (!thumbDragged.current || readonly) return
|
|
258
262
|
thumbDragged.current = false
|
|
263
|
+
onDragEnd?.(x.value, y.value)
|
|
259
264
|
if (bodyNoSelect) removeNoSelect()
|
|
260
265
|
})
|
|
261
266
|
|
|
@@ -289,11 +294,22 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
289
294
|
}}
|
|
290
295
|
onPointerDown={(event) => {
|
|
291
296
|
thumbDragged.current = true
|
|
292
|
-
handleValue(event)
|
|
297
|
+
const v = handleValue(event)
|
|
298
|
+
if (!readonly) onDragStart?.(v?.[0] ?? x.value, v?.[1] ?? y.value)
|
|
299
|
+
onPointerDown?.(event)
|
|
300
|
+
}}
|
|
301
|
+
onKeyDown={(event) => {
|
|
302
|
+
handleKeyDown(event)
|
|
303
|
+
onKeyDown?.(event)
|
|
304
|
+
}}
|
|
305
|
+
onFocus={(event) => {
|
|
306
|
+
thumbRef.current?.focus()
|
|
307
|
+
onFocus?.(event)
|
|
308
|
+
}}
|
|
309
|
+
onBlur={(event) => {
|
|
310
|
+
thumbRef.current?.blur()
|
|
311
|
+
onBlur?.(event)
|
|
293
312
|
}}
|
|
294
|
-
onKeyDown={handleKeyDown}
|
|
295
|
-
onFocus={thumbRef.current?.focus}
|
|
296
|
-
onBlur={thumbRef.current?.blur}
|
|
297
313
|
{...props}
|
|
298
314
|
>
|
|
299
315
|
<div className="tremolo-xy-pad-area-wrapper" ref={areaElementRef}>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useInterval(callback: () => void, delay: number | null) {
|
|
4
|
+
const savedCallback = useRef(callback)
|
|
5
|
+
|
|
6
|
+
// Remember the latest callback if it changes.
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
savedCallback.current = callback
|
|
9
|
+
}, [callback])
|
|
10
|
+
|
|
11
|
+
// Set up the interval.
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Don't schedule if no delay is specified.
|
|
14
|
+
// Note: 0 is a valid value for delay.
|
|
15
|
+
if (delay === null) {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const id = setInterval(() => {
|
|
20
|
+
savedCallback.current()
|
|
21
|
+
}, delay)
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
clearInterval(id)
|
|
25
|
+
}
|
|
26
|
+
}, [delay])
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useEventListener } from './useEventListener'
|
|
4
|
+
import { useInterval } from './useInterval'
|
|
5
|
+
|
|
6
|
+
export function useLongPress(
|
|
7
|
+
callback: () => void,
|
|
8
|
+
initialDelay = 500,
|
|
9
|
+
interval = 40,
|
|
10
|
+
) {
|
|
11
|
+
const [pressed, setPressed] = useState(false)
|
|
12
|
+
const [delay, setDelay] = useState(initialDelay)
|
|
13
|
+
|
|
14
|
+
useInterval(
|
|
15
|
+
() => {
|
|
16
|
+
callback()
|
|
17
|
+
setDelay(interval)
|
|
18
|
+
},
|
|
19
|
+
pressed ? delay : null,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
useEventListener(globalThis.window, 'pointerup', () => {
|
|
23
|
+
setPressed(false)
|
|
24
|
+
setDelay(initialDelay)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return useCallback(() => {
|
|
28
|
+
callback()
|
|
29
|
+
setPressed(true)
|
|
30
|
+
}, [callback])
|
|
31
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,3 +14,9 @@ export * from './components/Piano'
|
|
|
14
14
|
export * from './components/Slider'
|
|
15
15
|
export * from './components/WheelObserver'
|
|
16
16
|
export * from './components/XYPad'
|
|
17
|
+
|
|
18
|
+
export * from './hooks/useAnimationFrame'
|
|
19
|
+
export * from './hooks/useDrag'
|
|
20
|
+
export * from './hooks/useEventListener'
|
|
21
|
+
export * from './hooks/useInterval'
|
|
22
|
+
export * from './hooks/useLongPress'
|