@tremolo-ui/react 0.2.0 → 0.3.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 +1245 -1083
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +176 -159
- package/dist/index.d.cts +244 -253
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +244 -253
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1218 -1067
- package/dist/index.js.map +1 -1
- package/package.json +18 -19
- package/src/components/AnimationCanvas/index.tsx +0 -7
- package/src/components/DragObserver/index.tsx +9 -7
- package/src/components/Knob/ActiveLine.tsx +29 -0
- package/src/components/Knob/InactiveLine.tsx +53 -0
- package/src/components/Knob/SVGRoot.tsx +39 -0
- package/src/components/Knob/Thumb.tsx +64 -0
- package/src/components/Knob/context.tsx +120 -0
- package/src/components/Knob/index.css +5 -1
- package/src/components/Knob/index.tsx +110 -164
- package/src/components/NumberInput/DecrementStepper.tsx +0 -2
- package/src/components/NumberInput/IncrementStepper.tsx +0 -2
- package/src/components/NumberInput/InternalInput.tsx +3 -1
- package/src/components/NumberInput/Stepper.tsx +0 -2
- package/src/components/NumberInput/context.tsx +0 -1
- package/src/components/NumberInput/index.tsx +6 -8
- package/src/components/Piano/KeyLabel.tsx +0 -2
- package/src/components/Piano/context.tsx +0 -2
- package/src/components/Piano/index.tsx +7 -11
- package/src/components/Piano/key.tsx +0 -10
- package/src/components/Piano/keyboardShortcuts.ts +0 -2
- package/src/components/PointsEditor/Background.tsx +17 -0
- package/src/components/PointsEditor/Container.tsx +26 -0
- package/src/components/PointsEditor/Point.tsx +137 -0
- package/src/components/PointsEditor/context.tsx +91 -0
- package/src/components/PointsEditor/index.css +30 -0
- package/src/components/PointsEditor/index.tsx +129 -0
- package/src/components/Slider/Scale.tsx +0 -2
- package/src/components/Slider/ScaleOption.tsx +0 -2
- package/src/components/Slider/Thumb.tsx +0 -4
- package/src/components/Slider/Track.tsx +0 -2
- package/src/components/Slider/context.tsx +0 -1
- package/src/components/Slider/index.tsx +42 -14
- package/src/components/WheelObserver/index.tsx +17 -19
- package/src/components/XYPad/Area.tsx +0 -2
- package/src/components/XYPad/Thumb.tsx +0 -3
- package/src/components/XYPad/index.tsx +52 -25
- package/src/components/_util/composeRefs.tsx +63 -0
- package/src/components/_util/index.ts +23 -6
- package/src/components/_util/type.ts +1 -0
- package/src/hooks/useAnimationFrame.ts +0 -3
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +12 -11
- package/src/hooks/useDragWithElement.ts +12 -17
- package/src/hooks/useEventListener.ts +6 -9
- package/src/hooks/useInterval.ts +0 -3
- package/src/hooks/useLongPress.ts +0 -3
- package/src/hooks/useMIDIAccess.ts +20 -28
- package/src/hooks/useMIDIInput.ts +10 -38
- package/src/hooks/useMIDIMessage.ts +4 -12
- package/src/hooks/usePianoDrag.ts +75 -0
- package/src/index.ts +8 -5
- package/src/styles/global.css +17 -1
- package/dist/index.css.map +0 -1
- package/src/components/AnimationKnob/index.tsx +0 -314
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { ComponentPropsWithoutRef, CSSProperties, ReactElement } from 'react'
|
|
3
3
|
|
|
4
|
-
/** @category XYPad */
|
|
5
4
|
export interface XYPadAreaProps {
|
|
6
5
|
width?: number | string
|
|
7
6
|
height?: number | string
|
|
@@ -14,7 +13,6 @@ export interface XYPadAreaProps {
|
|
|
14
13
|
__thumb?: ReactElement
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
/** @category XYPad */
|
|
18
16
|
export function Area({
|
|
19
17
|
width = 120,
|
|
20
18
|
height = 120,
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
useRef,
|
|
9
9
|
} from 'react'
|
|
10
10
|
|
|
11
|
-
/** @category XYPad */
|
|
12
11
|
export interface XYPadThumbProps {
|
|
13
12
|
size?: number | string
|
|
14
13
|
width?: number | string
|
|
@@ -29,7 +28,6 @@ export interface XYPadThumbProps {
|
|
|
29
28
|
__css?: CSSProperties
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
/** @category XYPad */
|
|
33
31
|
export interface XYPadThumbMethods {
|
|
34
32
|
focus: () => void
|
|
35
33
|
blur: () => void
|
|
@@ -38,7 +36,6 @@ export interface XYPadThumbMethods {
|
|
|
38
36
|
type Props = XYPadThumbProps &
|
|
39
37
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadThumbProps>
|
|
40
38
|
|
|
41
|
-
/** @category XYPad */
|
|
42
39
|
export const Thumb = forwardRef<XYPadThumbMethods, Props>(
|
|
43
40
|
(
|
|
44
41
|
{
|
|
@@ -3,6 +3,7 @@ import React, {
|
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
4
|
forwardRef,
|
|
5
5
|
ReactElement,
|
|
6
|
+
Ref,
|
|
6
7
|
useCallback,
|
|
7
8
|
useImperativeHandle,
|
|
8
9
|
useMemo,
|
|
@@ -21,14 +22,33 @@ import {
|
|
|
21
22
|
|
|
22
23
|
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
23
24
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
addUserSelectNone,
|
|
27
|
+
Cursor,
|
|
28
|
+
removeUserSelectNone,
|
|
29
|
+
resetCursorStyle,
|
|
30
|
+
setCursorStyle,
|
|
31
|
+
} from '../_util'
|
|
32
|
+
import { composeRefs } from '../_util/composeRefs'
|
|
25
33
|
|
|
26
34
|
import { Area, XYPadAreaProps } from './Area'
|
|
27
35
|
import { Thumb, XYPadThumbMethods, XYPadThumbProps } from './Thumb'
|
|
28
36
|
|
|
37
|
+
const defaultValueOptions = {
|
|
38
|
+
skew: 1,
|
|
39
|
+
step: 1,
|
|
40
|
+
reverse: false,
|
|
41
|
+
wheel: ['raw', 1] as InputEventOption,
|
|
42
|
+
keyboard: ['raw', 1] as InputEventOption,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const defaultExternalStyles: XYPadProps['externalStyles'] = {
|
|
46
|
+
userSelectNone: true,
|
|
47
|
+
cursor: 'pointer',
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
/**
|
|
30
51
|
* Two-dimensional slider component.
|
|
31
|
-
* @category XYPad
|
|
32
52
|
*/
|
|
33
53
|
export interface ValueOptions {
|
|
34
54
|
// required
|
|
@@ -50,14 +70,18 @@ export interface ValueOptions {
|
|
|
50
70
|
keyboard?: InputEventOption | null
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
/** @category XYPad */
|
|
54
73
|
export interface XYPadProps {
|
|
55
74
|
x: ValueOptions
|
|
56
75
|
y: ValueOptions
|
|
57
76
|
|
|
58
|
-
|
|
77
|
+
externalStyles?: {
|
|
78
|
+
userSelectNone?: boolean
|
|
79
|
+
cursor?: Cursor
|
|
80
|
+
}
|
|
81
|
+
|
|
59
82
|
disabled?: boolean
|
|
60
83
|
readonly?: boolean
|
|
84
|
+
|
|
61
85
|
onChange?: (valueX: number, valueY: number) => void
|
|
62
86
|
onDragStart?: (valueX: number, valueY: number) => void
|
|
63
87
|
onDragEnd?: (valueX: number, valueY: number) => void
|
|
@@ -65,31 +89,23 @@ export interface XYPadProps {
|
|
|
65
89
|
children?: ReactElement | ReactElement[]
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
/** @category XYPad */
|
|
69
92
|
export interface XYPadMethods {
|
|
70
93
|
focus: () => void
|
|
71
94
|
blur: () => void
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const defaultValueOptions = {
|
|
75
|
-
skew: 1,
|
|
76
|
-
step: 1,
|
|
77
|
-
reverse: false,
|
|
78
|
-
wheel: ['raw', 1] as InputEventOption,
|
|
79
|
-
keyboard: ['raw', 1] as InputEventOption,
|
|
95
|
+
original: Ref<HTMLDivElement>
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
type Props = XYPadProps &
|
|
83
99
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadProps>
|
|
84
100
|
|
|
85
|
-
export const
|
|
101
|
+
export const Root = forwardRef<XYPadMethods, Props>(
|
|
86
102
|
(
|
|
87
103
|
{
|
|
88
104
|
x: _x,
|
|
89
105
|
y: _y,
|
|
90
106
|
className,
|
|
91
107
|
style,
|
|
92
|
-
|
|
108
|
+
externalStyles: _externalStyles,
|
|
93
109
|
disabled = false,
|
|
94
110
|
readonly = false,
|
|
95
111
|
onChange,
|
|
@@ -112,10 +128,13 @@ export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
|
112
128
|
}, [_y])
|
|
113
129
|
|
|
114
130
|
// -- state and ref ---
|
|
131
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
115
132
|
const areaElementRef = useRef<HTMLDivElement>(null)
|
|
116
133
|
const thumbRef = useRef<XYPadThumbMethods>(null)
|
|
117
134
|
|
|
118
135
|
// --- interpret props ---
|
|
136
|
+
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
137
|
+
|
|
119
138
|
const nx = normalizeValue(x.value, x.min, x.max, x.skew)
|
|
120
139
|
const ny = normalizeValue(y.value, y.min, y.max, y.skew)
|
|
121
140
|
const percentX = toFixed((x.reverse ? 1 - nx : nx) * 100)
|
|
@@ -212,14 +231,18 @@ export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
|
212
231
|
)
|
|
213
232
|
|
|
214
233
|
// --- hooks ---
|
|
215
|
-
const
|
|
234
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } =
|
|
216
235
|
useDragWithElement<HTMLDivElement>({
|
|
217
236
|
baseElementRef: areaElementRef,
|
|
218
237
|
onDrag: onDrag,
|
|
219
238
|
onDragStart: (nx, ny) => {
|
|
220
239
|
if (readonly) return
|
|
221
|
-
|
|
240
|
+
|
|
241
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
242
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
243
|
+
|
|
222
244
|
thumbRef.current?.focus()
|
|
245
|
+
|
|
223
246
|
const valueX = clamp(
|
|
224
247
|
stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
|
|
225
248
|
x.min,
|
|
@@ -230,11 +253,17 @@ export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
|
230
253
|
y.min,
|
|
231
254
|
y.max,
|
|
232
255
|
)
|
|
256
|
+
|
|
257
|
+
// TODO: ドラッグせず、pointer downだけの場合でも、onChange を発火させるべき
|
|
258
|
+
// onChange()
|
|
233
259
|
onDragStart?.(valueX, valueY)
|
|
234
260
|
},
|
|
235
261
|
onDragEnd: (nx, ny) => {
|
|
236
262
|
if (readonly) return
|
|
237
|
-
|
|
263
|
+
|
|
264
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
265
|
+
if (externalStyles.cursor) resetCursorStyle()
|
|
266
|
+
|
|
238
267
|
const valueX = clamp(
|
|
239
268
|
stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
|
|
240
269
|
x.min,
|
|
@@ -280,6 +309,7 @@ export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
|
280
309
|
blur() {
|
|
281
310
|
thumbRef.current?.blur()
|
|
282
311
|
},
|
|
312
|
+
original: rootRef,
|
|
283
313
|
}
|
|
284
314
|
}, [])
|
|
285
315
|
|
|
@@ -287,10 +317,7 @@ export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
|
287
317
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
288
318
|
<div
|
|
289
319
|
className={clsx('tremolo-xy-pad', className)}
|
|
290
|
-
ref={(
|
|
291
|
-
wheelRefCallback(div)
|
|
292
|
-
touchMoveRefCallback(div)
|
|
293
|
-
}}
|
|
320
|
+
ref={composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback)}
|
|
294
321
|
tabIndex={-1}
|
|
295
322
|
aria-disabled={disabled}
|
|
296
323
|
aria-readonly={readonly}
|
|
@@ -342,12 +369,12 @@ export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
|
342
369
|
|
|
343
370
|
/**
|
|
344
371
|
* Simple XYPad
|
|
345
|
-
* @category XYPad
|
|
346
372
|
*/
|
|
347
|
-
export const XYPad =
|
|
373
|
+
export const XYPad = {
|
|
374
|
+
Root,
|
|
348
375
|
Thumb,
|
|
349
376
|
Area,
|
|
350
|
-
}
|
|
377
|
+
}
|
|
351
378
|
|
|
352
379
|
export { type XYPadThumbProps, type XYPadThumbMethods } from './Thumb'
|
|
353
380
|
export { type XYPadAreaProps } from './Area'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ref:
|
|
2
|
+
// https://github.com/radix-ui/primitives/blob/2bab24a811e45c7a83198659272097f6cfa5a165/packages/react/compose-refs/src/compose-refs.tsx
|
|
3
|
+
|
|
4
|
+
import { useCallback } from 'react'
|
|
5
|
+
|
|
6
|
+
type PossibleRef<T> = React.Ref<T> | undefined
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Set a given ref to a given value
|
|
10
|
+
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
|
11
|
+
*/
|
|
12
|
+
function setRef<T>(ref: PossibleRef<T>, value: T) {
|
|
13
|
+
if (typeof ref === 'function') {
|
|
14
|
+
return ref(value)
|
|
15
|
+
} else if (ref !== null && ref !== undefined) {
|
|
16
|
+
ref.current = value
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A utility to compose multiple refs together
|
|
22
|
+
* Accepts callback refs and RefObject(s)
|
|
23
|
+
*/
|
|
24
|
+
function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
|
25
|
+
return (node) => {
|
|
26
|
+
let hasCleanup = false
|
|
27
|
+
const cleanups = refs.map((ref) => {
|
|
28
|
+
const cleanup = setRef(ref, node)
|
|
29
|
+
if (!hasCleanup && typeof cleanup == 'function') {
|
|
30
|
+
hasCleanup = true
|
|
31
|
+
}
|
|
32
|
+
return cleanup
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// React <19 will log an error to the console if a callback ref returns a
|
|
36
|
+
// value. We don't use ref cleanups internally so this will only happen if a
|
|
37
|
+
// user's ref callback returns a value, which we only expect if they are
|
|
38
|
+
// using the cleanup functionality added in React 19.
|
|
39
|
+
if (hasCleanup) {
|
|
40
|
+
return () => {
|
|
41
|
+
for (let i = 0; i < cleanups.length; i++) {
|
|
42
|
+
const cleanup = cleanups[i]
|
|
43
|
+
if (typeof cleanup == 'function') {
|
|
44
|
+
cleanup()
|
|
45
|
+
} else {
|
|
46
|
+
setRef(refs[i], null)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A custom hook that composes multiple refs
|
|
56
|
+
* Accepts callback refs and RefObject(s)
|
|
57
|
+
*/
|
|
58
|
+
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
return useCallback(composeRefs(...refs), refs)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { composeRefs, useComposedRefs }
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
export function
|
|
2
|
-
document.body.classList.add('tremolo-
|
|
1
|
+
export function addUserSelectNone() {
|
|
2
|
+
window.document.body.classList.add('tremolo-user-select-none')
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function
|
|
6
|
-
document.body.classList.remove('tremolo-
|
|
5
|
+
export function removeUserSelectNone() {
|
|
6
|
+
window.document.body.classList.remove('tremolo-user-select-none')
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const cursorStyles = {
|
|
10
|
+
grabbing: 'tremolo-cursor-grabbing',
|
|
11
|
+
grab: 'tremolo-cursor-grab',
|
|
12
|
+
pointer: 'tremolo-cursor-pointer',
|
|
13
|
+
move: 'tremolo-cursor-move',
|
|
14
|
+
none: 'tremolo-cursor-none',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Cursor = keyof typeof cursorStyles
|
|
18
|
+
|
|
19
|
+
export function setCursorStyle(type: Cursor) {
|
|
20
|
+
resetCursorStyle()
|
|
21
|
+
window.document.body.classList.add(cursorStyles[type])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resetCursorStyle() {
|
|
25
|
+
Object.values(cursorStyles).forEach((s) => {
|
|
26
|
+
window.document.body.classList.remove(s)
|
|
27
|
+
})
|
|
11
28
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Override<T, U> = T & Omit<U, keyof T>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useInsertionEffect, useRef } from 'react'
|
|
1
|
+
import { DependencyList, useCallback, useInsertionEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Internal
|
|
@@ -6,7 +6,7 @@ import { useCallback, useInsertionEffect, useRef } from 'react'
|
|
|
6
6
|
*/
|
|
7
7
|
export function useCallbackRef<Args extends unknown[], Return>(
|
|
8
8
|
callback: ((...args: Args) => Return) | undefined,
|
|
9
|
-
deps:
|
|
9
|
+
deps: DependencyList = [],
|
|
10
10
|
) {
|
|
11
11
|
const callbackRef = useRef<typeof callback>(() => {
|
|
12
12
|
throw new Error('Cannot call an event handler while rendering.')
|
package/src/hooks/useDrag.ts
CHANGED
|
@@ -6,17 +6,16 @@ import { useRefCallbackEvent } from './useRefCallbackEvent'
|
|
|
6
6
|
interface UseDragProps {
|
|
7
7
|
threshold?: number
|
|
8
8
|
|
|
9
|
-
onDrag
|
|
9
|
+
onDrag?: (x: number, y: number, deltaX: number, deltaY: number) => void
|
|
10
10
|
onDragStart?: () => void
|
|
11
11
|
onDragEnd?: () => void
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @category hooks
|
|
16
15
|
* @returns [refCallback, pointerDownHandler]
|
|
17
16
|
*/
|
|
18
17
|
export function useDrag<T extends Element>({
|
|
19
|
-
threshold = 1,
|
|
18
|
+
threshold: _threshold = 1,
|
|
20
19
|
onDrag,
|
|
21
20
|
onDragStart,
|
|
22
21
|
onDragEnd,
|
|
@@ -29,6 +28,8 @@ export function useDrag<T extends Element>({
|
|
|
29
28
|
const dragStartX = useRef(0)
|
|
30
29
|
const dragStartY = useRef(0)
|
|
31
30
|
|
|
31
|
+
const threshold = Math.max(_threshold, 1)
|
|
32
|
+
|
|
32
33
|
const handleDrag = useCallback(
|
|
33
34
|
(
|
|
34
35
|
event: MouseEvent | React.MouseEvent<T, MouseEvent> | TouchEvent,
|
|
@@ -53,7 +54,7 @@ export function useDrag<T extends Element>({
|
|
|
53
54
|
dragOffsetY.current = screenY
|
|
54
55
|
}
|
|
55
56
|
if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return
|
|
56
|
-
onDrag(
|
|
57
|
+
onDrag?.(
|
|
57
58
|
screenX - dragStartX.current,
|
|
58
59
|
screenY - dragStartY.current,
|
|
59
60
|
deltaX,
|
|
@@ -63,13 +64,6 @@ export function useDrag<T extends Element>({
|
|
|
63
64
|
[threshold, onDrag],
|
|
64
65
|
)
|
|
65
66
|
|
|
66
|
-
const refHandler = useRefCallbackEvent(
|
|
67
|
-
'touchmove',
|
|
68
|
-
handleDrag,
|
|
69
|
-
{ passive: false },
|
|
70
|
-
[onDrag],
|
|
71
|
-
)
|
|
72
|
-
|
|
73
67
|
const pointerDownHandler = useCallback(
|
|
74
68
|
(event: React.PointerEvent<T>) => {
|
|
75
69
|
dragOffsetX.current = event.screenX
|
|
@@ -80,6 +74,13 @@ export function useDrag<T extends Element>({
|
|
|
80
74
|
[handleDrag, onDragStart],
|
|
81
75
|
)
|
|
82
76
|
|
|
77
|
+
const refHandler = useRefCallbackEvent(
|
|
78
|
+
'touchmove',
|
|
79
|
+
handleDrag,
|
|
80
|
+
{ passive: false },
|
|
81
|
+
[onDrag],
|
|
82
|
+
)
|
|
83
|
+
|
|
83
84
|
useEventListener(globalThis.window, 'mousemove', handleDrag)
|
|
84
85
|
|
|
85
86
|
useEventListener(globalThis.window, 'pointerup', () => {
|
|
@@ -1,37 +1,30 @@
|
|
|
1
|
-
import { useRef, useCallback, RefObject } from 'react'
|
|
1
|
+
import { useRef, useCallback, RefObject, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { normalizeValue } from '@tremolo-ui/functions'
|
|
4
4
|
|
|
5
5
|
import { useEventListener } from './useEventListener'
|
|
6
6
|
import { useRefCallbackEvent } from './useRefCallbackEvent'
|
|
7
7
|
|
|
8
|
-
interface
|
|
8
|
+
interface UseDragWithElement<T extends Element> {
|
|
9
9
|
baseElementRef: RefObject<T | null>
|
|
10
10
|
onDrag: (normalizedX: number, normalizedY: number) => void
|
|
11
11
|
onDragStart?: (normalizedX: number, normalizedY: number) => void
|
|
12
12
|
onDragEnd?: (normalizedX: number, normalizedY: number) => void
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* @category hooks
|
|
17
|
-
* @returns [refCallback, pointerDownHandler]
|
|
18
|
-
*/
|
|
19
15
|
export function useDragWithElement<T extends Element>({
|
|
20
16
|
baseElementRef,
|
|
21
17
|
onDrag,
|
|
22
18
|
onDragStart,
|
|
23
19
|
onDragEnd,
|
|
24
|
-
}:
|
|
25
|
-
|
|
26
|
-
(event: React.PointerEvent<T>) => void,
|
|
27
|
-
] {
|
|
28
|
-
const dragged = useRef(false)
|
|
20
|
+
}: UseDragWithElement<T>) {
|
|
21
|
+
const [dragging, setDragging] = useState(false)
|
|
29
22
|
const normalizedX = useRef(0)
|
|
30
23
|
const normalizedY = useRef(0)
|
|
31
24
|
|
|
32
25
|
const handleDrag = useCallback(
|
|
33
26
|
(event: MouseEvent | React.PointerEvent<T> | TouchEvent) => {
|
|
34
|
-
if (!baseElementRef.current || !
|
|
27
|
+
if (!baseElementRef.current || !dragging) {
|
|
35
28
|
return
|
|
36
29
|
}
|
|
37
30
|
const isTouch = event instanceof TouchEvent
|
|
@@ -46,7 +39,7 @@ export function useDragWithElement<T extends Element>({
|
|
|
46
39
|
normalizedY.current = ny
|
|
47
40
|
onDrag(nx, ny)
|
|
48
41
|
},
|
|
49
|
-
[onDrag, baseElementRef],
|
|
42
|
+
[onDrag, baseElementRef, dragging],
|
|
50
43
|
)
|
|
51
44
|
|
|
52
45
|
const refHandler = useRefCallbackEvent(
|
|
@@ -58,7 +51,8 @@ export function useDragWithElement<T extends Element>({
|
|
|
58
51
|
|
|
59
52
|
const pointerDownHandler = useCallback(
|
|
60
53
|
(event: React.PointerEvent<T>) => {
|
|
61
|
-
|
|
54
|
+
// dragging.current = true
|
|
55
|
+
setDragging(true)
|
|
62
56
|
handleDrag(event)
|
|
63
57
|
onDragStart?.(normalizedX.current, normalizedY.current)
|
|
64
58
|
},
|
|
@@ -68,10 +62,11 @@ export function useDragWithElement<T extends Element>({
|
|
|
68
62
|
useEventListener(globalThis.window, 'pointermove', handleDrag)
|
|
69
63
|
|
|
70
64
|
useEventListener(globalThis.window, 'pointerup', () => {
|
|
71
|
-
if (!
|
|
72
|
-
|
|
65
|
+
if (!dragging) return
|
|
66
|
+
// dragging.current = false
|
|
67
|
+
setDragging(false)
|
|
73
68
|
onDragEnd?.(normalizedX.current, normalizedY.current)
|
|
74
69
|
})
|
|
75
70
|
|
|
76
|
-
return
|
|
71
|
+
return { refHandler, pointerDownHandler, dragging }
|
|
77
72
|
}
|
|
@@ -5,31 +5,28 @@ import { useCallbackRef } from './useCallbackRef'
|
|
|
5
5
|
type Target = EventTarget | null | (() => EventTarget | null)
|
|
6
6
|
type Options = boolean | AddEventListenerOptions
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* @category hooks
|
|
10
|
-
*/
|
|
11
8
|
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
12
9
|
target: Target,
|
|
13
10
|
event: K,
|
|
14
|
-
handler
|
|
11
|
+
handler: (event: DocumentEventMap[K]) => void,
|
|
15
12
|
options?: Options,
|
|
16
13
|
): VoidFunction
|
|
17
14
|
export function useEventListener<K extends keyof WindowEventMap>(
|
|
18
15
|
target: Target,
|
|
19
16
|
event: K,
|
|
20
|
-
handler
|
|
17
|
+
handler: (event: WindowEventMap[K]) => void,
|
|
21
18
|
options?: Options,
|
|
22
19
|
): VoidFunction
|
|
23
20
|
export function useEventListener<K extends keyof GlobalEventHandlersEventMap>(
|
|
24
21
|
target: Target,
|
|
25
22
|
event: K,
|
|
26
|
-
handler
|
|
23
|
+
handler: (event: GlobalEventHandlersEventMap[K]) => void,
|
|
27
24
|
options?: Options,
|
|
28
25
|
): VoidFunction
|
|
29
26
|
export function useEventListener(
|
|
30
27
|
target: Target,
|
|
31
28
|
event: string,
|
|
32
|
-
handler: (
|
|
29
|
+
handler: (event: Event) => void,
|
|
33
30
|
options?: Options,
|
|
34
31
|
) {
|
|
35
32
|
const listener = useCallbackRef(handler)
|
|
@@ -37,13 +34,13 @@ export function useEventListener(
|
|
|
37
34
|
useEffect(() => {
|
|
38
35
|
const node = typeof target === 'function' ? target() : (target ?? document)
|
|
39
36
|
|
|
40
|
-
if (!
|
|
37
|
+
if (!node) return
|
|
41
38
|
|
|
42
39
|
node.addEventListener(event, listener, options)
|
|
43
40
|
return () => {
|
|
44
41
|
node.removeEventListener(event, listener, options)
|
|
45
42
|
}
|
|
46
|
-
}, [event, target, options, listener
|
|
43
|
+
}, [event, target, options, listener])
|
|
47
44
|
|
|
48
45
|
return () => {
|
|
49
46
|
const node = typeof target === 'function' ? target() : (target ?? document)
|
package/src/hooks/useInterval.ts
CHANGED
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useSyncExternalStore } from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
export const PERMISSION_DENIED = 'PERMISSION_DENIED'
|
|
5
|
-
/** @private */
|
|
6
|
-
export const NOT_SUPPORTED = 'NOT_SUPPORTED'
|
|
3
|
+
import { createMIDIAccess } from '@tremolo-ui/dom'
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
export {
|
|
6
|
+
NOT_SUPPORTED,
|
|
7
|
+
PERMISSION_DENIED,
|
|
8
|
+
type MIDIAccessError,
|
|
9
|
+
} from '@tremolo-ui/dom'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
13
|
-
* @category hooks
|
|
14
13
|
*/
|
|
15
14
|
export function useMIDIAccess(requestOnMount = true) {
|
|
16
|
-
const
|
|
17
|
-
const [error, setError] = useState<MIDIAccessError | null>(null)
|
|
15
|
+
const instance = useMemo(() => createMIDIAccess(), [])
|
|
18
16
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
setMidiAccess(access)
|
|
25
|
-
setError(null)
|
|
26
|
-
})
|
|
27
|
-
.catch(() => {
|
|
28
|
-
setError(PERMISSION_DENIED)
|
|
29
|
-
})
|
|
30
|
-
} else {
|
|
31
|
-
setError(NOT_SUPPORTED)
|
|
32
|
-
}
|
|
33
|
-
}, [])
|
|
17
|
+
const { midiAccess, error } = useSyncExternalStore(
|
|
18
|
+
instance.subscribe,
|
|
19
|
+
instance.getState,
|
|
20
|
+
instance.getServerState,
|
|
21
|
+
)
|
|
34
22
|
|
|
35
23
|
useEffect(() => {
|
|
36
|
-
if (requestOnMount) request()
|
|
37
|
-
}, [
|
|
24
|
+
if (requestOnMount) instance.request()
|
|
25
|
+
}, [instance, requestOnMount])
|
|
38
26
|
|
|
39
|
-
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
return () => instance.destroy()
|
|
29
|
+
}, [instance])
|
|
30
|
+
|
|
31
|
+
return { request: instance.request, midiAccess, error }
|
|
40
32
|
}
|