@tremolo-ui/react 0.3.0 → 0.4.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 +152 -232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -52
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +48 -52
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -231
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/components/Knob/index.tsx +13 -30
- package/src/components/NumberInput/InternalInput.tsx +10 -15
- package/src/components/PointsEditor/Point.tsx +25 -40
- package/src/components/Slider/index.tsx +22 -37
- package/src/components/XYPad/index.tsx +24 -41
- package/src/hooks/useDrag.ts +41 -76
- package/src/hooks/useDragWithElement.ts +71 -43
- package/src/hooks/useWheel.ts +31 -0
- package/src/index.ts +1 -5
- package/src/components/DragObserver/index.tsx +0 -58
- package/src/components/WheelObserver/index.tsx +0 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tremolo-ui/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React component library for audio app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"@storybook/addon-a11y": "10.5.10",
|
|
27
27
|
"@storybook/addon-links": "10.5.10",
|
|
28
28
|
"@storybook/react-vite": "10.5.10",
|
|
29
|
-
"@testing-library/react": "16.3.
|
|
29
|
+
"@testing-library/react": "16.3.3",
|
|
30
30
|
"@types/react": "19.2.18",
|
|
31
|
-
"@types/react-dom": "19.2.
|
|
31
|
+
"@types/react-dom": "19.2.5",
|
|
32
32
|
"jest-environment-jsdom": "29.7.0",
|
|
33
|
-
"jotai": "2.20.
|
|
33
|
+
"jotai": "2.20.3",
|
|
34
34
|
"react": "19.2.8",
|
|
35
35
|
"react-dom": "19.2.8",
|
|
36
36
|
"storybook": "10.5.10",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"react-dom": "^18 || ^19"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@tremolo-ui/dom": "^0.
|
|
46
|
-
"@tremolo-ui/functions": "^0.
|
|
45
|
+
"@tremolo-ui/dom": "^0.4.0",
|
|
46
|
+
"@tremolo-ui/functions": "^0.4.0",
|
|
47
47
|
"clsx": "^2.1.1",
|
|
48
48
|
"zustand": "^5.0.3"
|
|
49
49
|
},
|
|
@@ -17,14 +17,8 @@ import {
|
|
|
17
17
|
} from '@tremolo-ui/functions'
|
|
18
18
|
|
|
19
19
|
import { useDrag } from '../../hooks/useDrag'
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
addUserSelectNone,
|
|
23
|
-
Cursor,
|
|
24
|
-
removeUserSelectNone,
|
|
25
|
-
resetCursorStyle,
|
|
26
|
-
setCursorStyle,
|
|
27
|
-
} from '../_util'
|
|
20
|
+
import { useWheel } from '../../hooks/useWheel'
|
|
21
|
+
import { addUserSelectNone, Cursor, removeUserSelectNone } from '../_util'
|
|
28
22
|
|
|
29
23
|
import { ActiveLine } from './ActiveLine'
|
|
30
24
|
import { KnobProvider } from './context'
|
|
@@ -174,35 +168,27 @@ const Root = forwardRef<KnobMethods, Props>(
|
|
|
174
168
|
)
|
|
175
169
|
|
|
176
170
|
// --- hooks ---
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
>({
|
|
171
|
+
const dragRefCallback = useDrag<HTMLElement | SVGElement>({
|
|
172
|
+
cursor: readonly ? undefined : externalStyles.cursor,
|
|
180
173
|
onDrag: onDrag,
|
|
181
174
|
onDragStart: () => {
|
|
182
175
|
setDragging(true)
|
|
183
176
|
valueRef.current = normalizeValue(value, min, max, skew)
|
|
184
177
|
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
185
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
186
178
|
},
|
|
187
179
|
onDragEnd: () => {
|
|
188
180
|
setDragging(false)
|
|
189
181
|
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
190
|
-
if (externalStyles.cursor) resetCursorStyle()
|
|
191
182
|
},
|
|
192
183
|
})
|
|
193
184
|
|
|
194
|
-
const wheelRefCallback =
|
|
195
|
-
|
|
196
|
-
(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
onChange(updateValueByEvent(wheel[0], x))
|
|
202
|
-
},
|
|
203
|
-
{ passive: false },
|
|
204
|
-
[wheel, onChange, readonly, updateValueByEvent],
|
|
205
|
-
)
|
|
185
|
+
const wheelRefCallback = useWheel<HTMLElement>((event) => {
|
|
186
|
+
if (!wheel || readonly) return
|
|
187
|
+
event.preventDefault()
|
|
188
|
+
if (!onChange || event.deltaY == 0) return
|
|
189
|
+
const x = Math.sign(event.deltaY) * -wheel[1]
|
|
190
|
+
onChange(updateValueByEvent(wheel[0], x))
|
|
191
|
+
})
|
|
206
192
|
|
|
207
193
|
useImperativeHandle(forwardedRef, () => {
|
|
208
194
|
return {
|
|
@@ -229,7 +215,7 @@ const Root = forwardRef<KnobMethods, Props>(
|
|
|
229
215
|
ref={(elm) => {
|
|
230
216
|
elmRef.current = elm
|
|
231
217
|
wheelRefCallback(elm)
|
|
232
|
-
|
|
218
|
+
dragRefCallback(elm)
|
|
233
219
|
}}
|
|
234
220
|
className={clsx('tremolo-knob', className)}
|
|
235
221
|
tabIndex={0}
|
|
@@ -245,10 +231,7 @@ const Root = forwardRef<KnobMethods, Props>(
|
|
|
245
231
|
height: size,
|
|
246
232
|
...style,
|
|
247
233
|
}}
|
|
248
|
-
onPointerDown={
|
|
249
|
-
pointerDownHandler(event)
|
|
250
|
-
onPointerDown?.(event)
|
|
251
|
-
}}
|
|
234
|
+
onPointerDown={onPointerDown}
|
|
252
235
|
onDoubleClick={(event) => {
|
|
253
236
|
if (enableDoubleClickDefault && onChange) {
|
|
254
237
|
onChange(defaultValue)
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stepValue,
|
|
18
18
|
} from '@tremolo-ui/functions'
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { useWheel } from '../../hooks/useWheel'
|
|
21
21
|
|
|
22
22
|
import { safeClamp, useNumberInputContext } from './context'
|
|
23
23
|
import { parseValue } from './type'
|
|
@@ -125,20 +125,15 @@ export const InternalInput = forwardRef<
|
|
|
125
125
|
[keyboard, readonly, onChange, updateValueByEvent],
|
|
126
126
|
)
|
|
127
127
|
|
|
128
|
-
const wheelRefCallback =
|
|
129
|
-
|
|
130
|
-
(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
onChange?.(parsed.rawValue, parsed.formatValue)
|
|
138
|
-
},
|
|
139
|
-
{ passive: false },
|
|
140
|
-
[wheel, readonly, onChange, updateValueByEvent],
|
|
141
|
-
)
|
|
128
|
+
const wheelRefCallback = useWheel<HTMLInputElement>((event) => {
|
|
129
|
+
if (!wheel || readonly) return
|
|
130
|
+
event.preventDefault()
|
|
131
|
+
if (!onChange || event.deltaY == 0) return
|
|
132
|
+
const x = Math.sign(event.deltaY) * -wheel[1]
|
|
133
|
+
const parsed = parseValue(String(updateValueByEvent(wheel[0], x)))
|
|
134
|
+
// console.log('wheel control')
|
|
135
|
+
onChange?.(parsed.rawValue, parsed.formatValue)
|
|
136
|
+
})
|
|
142
137
|
|
|
143
138
|
useImperativeHandle(ref, () => {
|
|
144
139
|
return {
|
|
@@ -4,12 +4,7 @@ import { ComponentPropsWithoutRef } from 'react'
|
|
|
4
4
|
import { clamp } from '@tremolo-ui/functions'
|
|
5
5
|
|
|
6
6
|
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
7
|
-
import {
|
|
8
|
-
addUserSelectNone,
|
|
9
|
-
removeUserSelectNone,
|
|
10
|
-
resetCursorStyle,
|
|
11
|
-
setCursorStyle,
|
|
12
|
-
} from '../_util'
|
|
7
|
+
import { addUserSelectNone, removeUserSelectNone } from '../_util'
|
|
13
8
|
|
|
14
9
|
import { usePointsEditorContext } from './context'
|
|
15
10
|
|
|
@@ -76,32 +71,28 @@ export function Point<T extends PointBaseType>({
|
|
|
76
71
|
|
|
77
72
|
// Piano: 値が更新されることで、イベントハンドラがリセットされる
|
|
78
73
|
|
|
79
|
-
const {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
onDragEnd?.(clampPoint({ x, y }, min, max))
|
|
103
|
-
},
|
|
104
|
-
})
|
|
74
|
+
const { refCallback: dragRefCallback, dragging } =
|
|
75
|
+
useDragWithElement<HTMLDivElement>({
|
|
76
|
+
baseElementRef: containerElementRef,
|
|
77
|
+
cursor: readonly ? undefined : externalStyles.cursor,
|
|
78
|
+
onDrag: (x, y) => {
|
|
79
|
+
if (readonly) return
|
|
80
|
+
|
|
81
|
+
onChange?.(clampPoint({ x, y }, min, max))
|
|
82
|
+
},
|
|
83
|
+
onDragStart: (x, y) => {
|
|
84
|
+
if (readonly) return
|
|
85
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
86
|
+
|
|
87
|
+
onDragStart?.(clampPoint({ x, y }, min, max))
|
|
88
|
+
},
|
|
89
|
+
onDragEnd: (x, y) => {
|
|
90
|
+
if (readonly) return
|
|
91
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
92
|
+
|
|
93
|
+
onDragEnd?.(clampPoint({ x, y }, min, max))
|
|
94
|
+
},
|
|
95
|
+
})
|
|
105
96
|
|
|
106
97
|
const colors: Record<string, string | undefined> = {
|
|
107
98
|
'--color': color,
|
|
@@ -109,10 +100,7 @@ export function Point<T extends PointBaseType>({
|
|
|
109
100
|
|
|
110
101
|
return (
|
|
111
102
|
<div
|
|
112
|
-
ref={
|
|
113
|
-
// wheelRefCallback(div)
|
|
114
|
-
touchMoveRefCallback(div)
|
|
115
|
-
}}
|
|
103
|
+
ref={dragRefCallback}
|
|
116
104
|
className={clsx('tremolo-points-editor-point', className)}
|
|
117
105
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
118
106
|
tabIndex={0}
|
|
@@ -127,10 +115,7 @@ export function Point<T extends PointBaseType>({
|
|
|
127
115
|
top: `${value.y * 100}%`,
|
|
128
116
|
...style,
|
|
129
117
|
}}
|
|
130
|
-
onPointerDown={
|
|
131
|
-
pointerDownHandler(event)
|
|
132
|
-
onPointerDown?.(event)
|
|
133
|
-
}}
|
|
118
|
+
onPointerDown={onPointerDown}
|
|
134
119
|
{...props}
|
|
135
120
|
/>
|
|
136
121
|
)
|
|
@@ -22,14 +22,8 @@ import {
|
|
|
22
22
|
} from '@tremolo-ui/functions'
|
|
23
23
|
|
|
24
24
|
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
addUserSelectNone,
|
|
28
|
-
Cursor,
|
|
29
|
-
removeUserSelectNone,
|
|
30
|
-
resetCursorStyle,
|
|
31
|
-
setCursorStyle,
|
|
32
|
-
} from '../_util'
|
|
25
|
+
import { useWheel } from '../../hooks/useWheel'
|
|
26
|
+
import { addUserSelectNone, Cursor, removeUserSelectNone } from '../_util'
|
|
33
27
|
|
|
34
28
|
import { SliderProvider } from './context'
|
|
35
29
|
import { Scale } from './Scale'
|
|
@@ -215,14 +209,15 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
215
209
|
)
|
|
216
210
|
|
|
217
211
|
// --- hooks ---
|
|
218
|
-
const {
|
|
219
|
-
|
|
212
|
+
const { refCallback: dragRefCallback } = useDragWithElement<HTMLDivElement>(
|
|
213
|
+
{
|
|
220
214
|
baseElementRef: trackElementRef,
|
|
215
|
+
cursor: readonly ? undefined : externalStyles.cursor,
|
|
216
|
+
updateOnPointerDown: true,
|
|
221
217
|
onDrag: onDrag,
|
|
222
218
|
onDragStart: (nx, ny) => {
|
|
223
219
|
if (readonly) return
|
|
224
220
|
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
225
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
226
221
|
|
|
227
222
|
thumbRef.current?.focus()
|
|
228
223
|
onDragStart?.(
|
|
@@ -237,7 +232,6 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
237
232
|
if (readonly) return
|
|
238
233
|
|
|
239
234
|
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
240
|
-
if (externalStyles.cursor) resetCursorStyle()
|
|
241
235
|
|
|
242
236
|
onDragEnd?.(
|
|
243
237
|
clamp(
|
|
@@ -247,29 +241,23 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
247
241
|
),
|
|
248
242
|
)
|
|
249
243
|
},
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
const wheelRefCallback = useRefCallbackEvent(
|
|
253
|
-
'wheel',
|
|
254
|
-
(event) => {
|
|
255
|
-
if (!wheel || !onChange || readonly) return
|
|
256
|
-
event.preventDefault()
|
|
257
|
-
let x
|
|
258
|
-
if (!vertical && event.deltaX != 0) {
|
|
259
|
-
x = event.deltaX > 0 ? wheel[1] : -wheel[1]
|
|
260
|
-
} else {
|
|
261
|
-
if (event.deltaY == 0) return
|
|
262
|
-
x = event.deltaY > 0 ? -wheel[1] : wheel[1]
|
|
263
|
-
}
|
|
264
|
-
if (vertical && reverse) x *= -1
|
|
265
|
-
onChange(updateValueByEvent(wheel[0], x))
|
|
266
244
|
},
|
|
267
|
-
{
|
|
268
|
-
passive: false,
|
|
269
|
-
},
|
|
270
|
-
[wheel, vertical, readonly, onChange, updateValueByEvent],
|
|
271
245
|
)
|
|
272
246
|
|
|
247
|
+
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
248
|
+
if (!wheel || !onChange || readonly) return
|
|
249
|
+
event.preventDefault()
|
|
250
|
+
let x
|
|
251
|
+
if (!vertical && event.deltaX != 0) {
|
|
252
|
+
x = event.deltaX > 0 ? wheel[1] : -wheel[1]
|
|
253
|
+
} else {
|
|
254
|
+
if (event.deltaY == 0) return
|
|
255
|
+
x = event.deltaY > 0 ? -wheel[1] : wheel[1]
|
|
256
|
+
}
|
|
257
|
+
if (vertical && reverse) x *= -1
|
|
258
|
+
onChange(updateValueByEvent(wheel[0], x))
|
|
259
|
+
})
|
|
260
|
+
|
|
273
261
|
useImperativeHandle(forwardedRef, () => {
|
|
274
262
|
return {
|
|
275
263
|
focus() {
|
|
@@ -296,7 +284,7 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
296
284
|
className={clsx('tremolo-slider', className)}
|
|
297
285
|
ref={(div) => {
|
|
298
286
|
wheelRefCallback(div)
|
|
299
|
-
|
|
287
|
+
dragRefCallback(div)
|
|
300
288
|
}}
|
|
301
289
|
tabIndex={-1}
|
|
302
290
|
role="slider"
|
|
@@ -310,10 +298,7 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
310
298
|
margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`, // half thumb size
|
|
311
299
|
...style,
|
|
312
300
|
}}
|
|
313
|
-
onPointerDown={
|
|
314
|
-
pointerDownHandler(event)
|
|
315
|
-
onPointerDown?.(event)
|
|
316
|
-
}}
|
|
301
|
+
onPointerDown={onPointerDown}
|
|
317
302
|
onKeyDown={(event) => {
|
|
318
303
|
handleKeyDown(event)
|
|
319
304
|
onKeyDown?.(event)
|
|
@@ -21,14 +21,8 @@ import {
|
|
|
21
21
|
} from '@tremolo-ui/functions'
|
|
22
22
|
|
|
23
23
|
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
addUserSelectNone,
|
|
27
|
-
Cursor,
|
|
28
|
-
removeUserSelectNone,
|
|
29
|
-
resetCursorStyle,
|
|
30
|
-
setCursorStyle,
|
|
31
|
-
} from '../_util'
|
|
24
|
+
import { useWheel } from '../../hooks/useWheel'
|
|
25
|
+
import { addUserSelectNone, Cursor, removeUserSelectNone } from '../_util'
|
|
32
26
|
import { composeRefs } from '../_util/composeRefs'
|
|
33
27
|
|
|
34
28
|
import { Area, XYPadAreaProps } from './Area'
|
|
@@ -231,15 +225,16 @@ export const Root = forwardRef<XYPadMethods, Props>(
|
|
|
231
225
|
)
|
|
232
226
|
|
|
233
227
|
// --- hooks ---
|
|
234
|
-
const {
|
|
235
|
-
|
|
228
|
+
const { refCallback: dragRefCallback } = useDragWithElement<HTMLDivElement>(
|
|
229
|
+
{
|
|
236
230
|
baseElementRef: areaElementRef,
|
|
231
|
+
cursor: readonly ? undefined : externalStyles.cursor,
|
|
232
|
+
updateOnPointerDown: true,
|
|
237
233
|
onDrag: onDrag,
|
|
238
234
|
onDragStart: (nx, ny) => {
|
|
239
235
|
if (readonly) return
|
|
240
236
|
|
|
241
237
|
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
242
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
243
238
|
|
|
244
239
|
thumbRef.current?.focus()
|
|
245
240
|
|
|
@@ -254,15 +249,12 @@ export const Root = forwardRef<XYPadMethods, Props>(
|
|
|
254
249
|
y.max,
|
|
255
250
|
)
|
|
256
251
|
|
|
257
|
-
// TODO: ドラッグせず、pointer downだけの場合でも、onChange を発火させるべき
|
|
258
|
-
// onChange()
|
|
259
252
|
onDragStart?.(valueX, valueY)
|
|
260
253
|
},
|
|
261
254
|
onDragEnd: (nx, ny) => {
|
|
262
255
|
if (readonly) return
|
|
263
256
|
|
|
264
257
|
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
265
|
-
if (externalStyles.cursor) resetCursorStyle()
|
|
266
258
|
|
|
267
259
|
const valueX = clamp(
|
|
268
260
|
stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
|
|
@@ -276,31 +268,25 @@ export const Root = forwardRef<XYPadMethods, Props>(
|
|
|
276
268
|
)
|
|
277
269
|
onDragEnd?.(valueX, valueY)
|
|
278
270
|
},
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
const wheelRefCallback = useRefCallbackEvent(
|
|
282
|
-
'wheel',
|
|
283
|
-
(event) => {
|
|
284
|
-
if (!onChange || readonly) return
|
|
285
|
-
const target = event.shiftKey ? x : y
|
|
286
|
-
if (!target.wheel) return
|
|
287
|
-
event.preventDefault()
|
|
288
|
-
let delta = target.wheel[1]
|
|
289
|
-
if (event.deltaY < 0) delta *= -1
|
|
290
|
-
if (target.reverse) delta *= -1
|
|
291
|
-
const newValue = updateValueByEvent(target.wheel[0], target, delta)
|
|
292
|
-
if (event.shiftKey) {
|
|
293
|
-
onChange(newValue, y.value)
|
|
294
|
-
} else {
|
|
295
|
-
onChange(x.value, newValue)
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
passive: false,
|
|
300
271
|
},
|
|
301
|
-
[x, y, onChange, readonly, updateValueByEvent],
|
|
302
272
|
)
|
|
303
273
|
|
|
274
|
+
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
275
|
+
if (!onChange || readonly) return
|
|
276
|
+
const target = event.shiftKey ? x : y
|
|
277
|
+
if (!target.wheel) return
|
|
278
|
+
event.preventDefault()
|
|
279
|
+
let delta = target.wheel[1]
|
|
280
|
+
if (event.deltaY < 0) delta *= -1
|
|
281
|
+
if (target.reverse) delta *= -1
|
|
282
|
+
const newValue = updateValueByEvent(target.wheel[0], target, delta)
|
|
283
|
+
if (event.shiftKey) {
|
|
284
|
+
onChange(newValue, y.value)
|
|
285
|
+
} else {
|
|
286
|
+
onChange(x.value, newValue)
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
304
290
|
useImperativeHandle(forwardedRef, () => {
|
|
305
291
|
return {
|
|
306
292
|
focus() {
|
|
@@ -317,7 +303,7 @@ export const Root = forwardRef<XYPadMethods, Props>(
|
|
|
317
303
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
318
304
|
<div
|
|
319
305
|
className={clsx('tremolo-xy-pad', className)}
|
|
320
|
-
ref={composeRefs(rootRef, wheelRefCallback,
|
|
306
|
+
ref={composeRefs(rootRef, wheelRefCallback, dragRefCallback)}
|
|
321
307
|
tabIndex={-1}
|
|
322
308
|
aria-disabled={disabled}
|
|
323
309
|
aria-readonly={readonly}
|
|
@@ -327,10 +313,7 @@ export const Root = forwardRef<XYPadMethods, Props>(
|
|
|
327
313
|
WebkitTapHighlightColor: 'transparent',
|
|
328
314
|
...style,
|
|
329
315
|
}}
|
|
330
|
-
onPointerDown={
|
|
331
|
-
pointerDownHandler(event)
|
|
332
|
-
onPointerDown?.(event)
|
|
333
|
-
}}
|
|
316
|
+
onPointerDown={onPointerDown}
|
|
334
317
|
onKeyDown={(event) => {
|
|
335
318
|
handleKeyDown(event)
|
|
336
319
|
onKeyDown?.(event)
|
package/src/hooks/useDrag.ts
CHANGED
|
@@ -1,93 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { createDrag } from '@tremolo-ui/dom'
|
|
4
|
+
|
|
5
|
+
import { useCallbackRef } from './useCallbackRef'
|
|
5
6
|
|
|
6
7
|
interface UseDragProps {
|
|
8
|
+
/**
|
|
9
|
+
* Threshold at which the onDrag event fires.
|
|
10
|
+
* Prevents onDrag events from firing, for example, when double-clicking.
|
|
11
|
+
*/
|
|
7
12
|
threshold?: number
|
|
8
13
|
|
|
14
|
+
/** CSS cursor to show while dragging. Applied to the element itself. */
|
|
15
|
+
cursor?: string
|
|
16
|
+
|
|
9
17
|
onDrag?: (x: number, y: number, deltaX: number, deltaY: number) => void
|
|
10
18
|
onDragStart?: () => void
|
|
11
19
|
onDragEnd?: () => void
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
|
-
*
|
|
23
|
+
* Track a pointer drag on an element.
|
|
24
|
+
*
|
|
25
|
+
* @returns a ref callback to attach to the element being dragged
|
|
16
26
|
*/
|
|
17
27
|
export function useDrag<T extends Element>({
|
|
18
|
-
threshold
|
|
28
|
+
threshold = 1,
|
|
29
|
+
cursor,
|
|
19
30
|
onDrag,
|
|
20
31
|
onDragStart,
|
|
21
32
|
onDragEnd,
|
|
22
|
-
}: UseDragProps):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
if (dragOffsetX.current) {
|
|
49
|
-
deltaX = screenX - dragOffsetX.current
|
|
50
|
-
dragOffsetX.current = screenX
|
|
51
|
-
}
|
|
52
|
-
if (dragOffsetY.current) {
|
|
53
|
-
deltaY = screenY - dragOffsetY.current
|
|
54
|
-
dragOffsetY.current = screenY
|
|
55
|
-
}
|
|
56
|
-
if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return
|
|
57
|
-
onDrag?.(
|
|
58
|
-
screenX - dragStartX.current,
|
|
59
|
-
screenY - dragStartY.current,
|
|
60
|
-
deltaX,
|
|
61
|
-
deltaY,
|
|
62
|
-
)
|
|
63
|
-
},
|
|
64
|
-
[threshold, onDrag],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const pointerDownHandler = useCallback(
|
|
68
|
-
(event: React.PointerEvent<T>) => {
|
|
69
|
-
dragOffsetX.current = event.screenX
|
|
70
|
-
dragOffsetY.current = event.screenY
|
|
71
|
-
handleDrag(event, true)
|
|
72
|
-
onDragStart?.()
|
|
73
|
-
},
|
|
74
|
-
[handleDrag, onDragStart],
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
const refHandler = useRefCallbackEvent(
|
|
78
|
-
'touchmove',
|
|
79
|
-
handleDrag,
|
|
80
|
-
{ passive: false },
|
|
81
|
-
[onDrag],
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
useEventListener(globalThis.window, 'mousemove', handleDrag)
|
|
85
|
-
|
|
86
|
-
useEventListener(globalThis.window, 'pointerup', () => {
|
|
87
|
-
dragOffsetX.current = undefined
|
|
88
|
-
dragOffsetY.current = undefined
|
|
89
|
-
onDragEnd?.()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
return [refHandler, pointerDownHandler]
|
|
33
|
+
}: UseDragProps): (node: T | null) => void {
|
|
34
|
+
const dragHandler = useCallbackRef(onDrag)
|
|
35
|
+
const dragStartHandler = useCallbackRef(onDragStart)
|
|
36
|
+
const dragEndHandler = useCallbackRef(onDragEnd)
|
|
37
|
+
|
|
38
|
+
// The node is held in state rather than bound in the ref callback itself, so
|
|
39
|
+
// that re-attaching the ref with the same node does not restart the drag.
|
|
40
|
+
// React re-attaches on every render when the caller passes an inline ref.
|
|
41
|
+
const [node, setNode] = useState<T | null>(null)
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!node) return
|
|
45
|
+
|
|
46
|
+
const instance = createDrag(node, {
|
|
47
|
+
threshold,
|
|
48
|
+
cursor,
|
|
49
|
+
onDragStart: () => dragStartHandler(),
|
|
50
|
+
onDrag: ({ x, y, deltaX, deltaY }) => dragHandler(x, y, deltaX, deltaY),
|
|
51
|
+
onDragEnd: () => dragEndHandler(),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return () => instance.destroy()
|
|
55
|
+
}, [node, threshold, cursor, dragHandler, dragStartHandler, dragEndHandler])
|
|
56
|
+
|
|
57
|
+
return setNode
|
|
93
58
|
}
|