@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tremolo-ui/react",
3
- "version": "0.3.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.2",
29
+ "@testing-library/react": "16.3.3",
30
30
  "@types/react": "19.2.18",
31
- "@types/react-dom": "19.2.4",
31
+ "@types/react-dom": "19.2.5",
32
32
  "jest-environment-jsdom": "29.7.0",
33
- "jotai": "2.20.2",
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.3.0",
46
- "@tremolo-ui/functions": "^0.3.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 { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
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 [touchMoveRefCallback, pointerDownHandler] = useDrag<
178
- HTMLElement | SVGElement
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 = useRefCallbackEvent(
195
- 'wheel',
196
- (event) => {
197
- if (!wheel || readonly) return
198
- event.preventDefault()
199
- if (!onChange || event.deltaY == 0) return
200
- const x = Math.sign(event.deltaY) * -wheel[1]
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
- touchMoveRefCallback(elm)
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={(event) => {
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 { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
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 = useRefCallbackEvent(
129
- 'wheel',
130
- (event) => {
131
- if (!wheel || readonly) return
132
- event.preventDefault()
133
- if (!onChange || event.deltaY == 0) return
134
- const x = Math.sign(event.deltaY) * -wheel[1]
135
- const parsed = parseValue(String(updateValueByEvent(wheel[0], x)))
136
- // console.log('wheel control')
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
- refHandler: touchMoveRefCallback,
81
- pointerDownHandler,
82
- dragging,
83
- } = useDragWithElement<HTMLDivElement>({
84
- baseElementRef: containerElementRef,
85
- onDrag: (x, y) => {
86
- if (readonly) return
87
-
88
- onChange?.(clampPoint({ x, y }, min, max))
89
- },
90
- onDragStart: (x, y) => {
91
- if (readonly) return
92
- if (externalStyles.userSelectNone) addUserSelectNone()
93
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
94
-
95
- onDragStart?.(clampPoint({ x, y }, min, max))
96
- },
97
- onDragEnd: (x, y) => {
98
- if (readonly) return
99
- if (externalStyles.userSelectNone) removeUserSelectNone()
100
- if (externalStyles.cursor) resetCursorStyle()
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={(div) => {
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={(event) => {
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 { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
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 { refHandler: touchMoveRefCallback, pointerDownHandler } =
219
- useDragWithElement<HTMLDivElement>({
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
- touchMoveRefCallback(div)
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={(event) => {
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 { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
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 { refHandler: touchMoveRefCallback, pointerDownHandler } =
235
- useDragWithElement<HTMLDivElement>({
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, touchMoveRefCallback)}
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={(event) => {
331
- pointerDownHandler(event)
332
- onPointerDown?.(event)
333
- }}
316
+ onPointerDown={onPointerDown}
334
317
  onKeyDown={(event) => {
335
318
  handleKeyDown(event)
336
319
  onKeyDown?.(event)
@@ -1,93 +1,58 @@
1
- import { useRef, useCallback } from 'react'
1
+ import { useEffect, useState } from 'react'
2
2
 
3
- import { useEventListener } from './useEventListener'
4
- import { useRefCallbackEvent } from './useRefCallbackEvent'
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
- * @returns [refCallback, pointerDownHandler]
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: _threshold = 1,
28
+ threshold = 1,
29
+ cursor,
19
30
  onDrag,
20
31
  onDragStart,
21
32
  onDragEnd,
22
- }: UseDragProps): [
23
- (div: EventTarget | null) => void,
24
- (event: React.PointerEvent<T>) => void,
25
- ] {
26
- const dragOffsetX = useRef<number | undefined>(undefined)
27
- const dragOffsetY = useRef<number | undefined>(undefined)
28
- const dragStartX = useRef(0)
29
- const dragStartY = useRef(0)
30
-
31
- const threshold = Math.max(_threshold, 1)
32
-
33
- const handleDrag = useCallback(
34
- (
35
- event: MouseEvent | React.MouseEvent<T, MouseEvent> | TouchEvent,
36
- first = false,
37
- ) => {
38
- const isTouch = event instanceof TouchEvent
39
- if (isTouch && event.cancelable) event.preventDefault()
40
- const screenX = isTouch ? event.touches[0].screenX : event.screenX
41
- const screenY = isTouch ? event.touches[0].screenY : event.screenY
42
- let deltaX = 0
43
- let deltaY = 0
44
- if (first) {
45
- dragStartX.current = screenX
46
- dragStartY.current = screenY
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
  }