@tremolo-ui/react 0.1.5 → 0.1.6
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 +171 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +79 -32
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +80 -56
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +82 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -91
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Knob/index.css +54 -23
- package/src/components/Knob/index.tsx +30 -25
- package/src/components/NumberInput/index.tsx +3 -4
- package/src/components/Piano/context.tsx +1 -1
- package/src/components/Piano/index.css +24 -8
- package/src/components/Piano/index.tsx +253 -217
- package/src/components/Piano/key.tsx +3 -3
- package/src/components/Slider/Track.tsx +2 -2
- package/src/components/Slider/index.tsx +2 -2
- package/src/components/XYPad/index.tsx +2 -2
- package/src/hooks/useAnimationFrame.ts +3 -0
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +2 -1
- package/src/hooks/useDragWithElement.ts +2 -1
- package/src/hooks/useEventListener.ts +3 -0
- package/src/hooks/useInterval.ts +3 -0
- package/src/hooks/useLongPress.ts +3 -0
- package/src/hooks/useMIDIAccess.ts +40 -0
- package/src/hooks/useMIDIInput.ts +50 -0
- package/src/hooks/useMIDIMessage.ts +24 -0
- package/src/hooks/useRefCallbackEvent.ts +4 -0
- package/src/index.ts +19 -12
- package/src/styles/global.css +1 -1
|
@@ -3,11 +3,13 @@ import React, {
|
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
4
|
createRef,
|
|
5
5
|
CSSProperties,
|
|
6
|
+
forwardRef,
|
|
6
7
|
ReactElement,
|
|
7
8
|
ReactNode,
|
|
8
9
|
RefObject,
|
|
9
10
|
useCallback,
|
|
10
11
|
useEffect,
|
|
12
|
+
useImperativeHandle,
|
|
11
13
|
useMemo,
|
|
12
14
|
useRef,
|
|
13
15
|
useState,
|
|
@@ -35,13 +37,9 @@ import {
|
|
|
35
37
|
import { KeyboardShortcuts } from './keyboardShortcuts'
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* TODO:
|
|
41
|
-
* - add scale highlight
|
|
40
|
+
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
41
|
+
* @category Piano
|
|
42
42
|
*/
|
|
43
|
-
|
|
44
|
-
/** @category Piano */
|
|
45
43
|
export function getNoteRangeArray(noteRange: NoteRange) {
|
|
46
44
|
return Array.from(
|
|
47
45
|
{ length: noteRange.last - noteRange.first + 1 },
|
|
@@ -51,10 +49,8 @@ export function getNoteRangeArray(noteRange: NoteRange) {
|
|
|
51
49
|
|
|
52
50
|
/** @category Piano */
|
|
53
51
|
export interface PianoProps {
|
|
54
|
-
// required
|
|
55
52
|
noteRange: NoteRange
|
|
56
53
|
|
|
57
|
-
// optional
|
|
58
54
|
glissando?: boolean
|
|
59
55
|
midiMax?: number
|
|
60
56
|
keyboardShortcuts?: KeyboardShortcuts
|
|
@@ -62,11 +58,10 @@ export interface PianoProps {
|
|
|
62
58
|
whiteNoteWidth?: number
|
|
63
59
|
blackNoteWidth?: number
|
|
64
60
|
height?: number | string
|
|
65
|
-
|
|
66
61
|
style?: CSSProperties
|
|
67
62
|
|
|
68
|
-
onPlayNote?: (
|
|
69
|
-
onStopNote?: (
|
|
63
|
+
onPlayNote?: (note: number, velocity?: number) => void
|
|
64
|
+
onStopNote?: (note: number) => void
|
|
70
65
|
|
|
71
66
|
label?: (note: number, index: number) => ReactNode
|
|
72
67
|
|
|
@@ -78,236 +73,277 @@ export interface PianoProps {
|
|
|
78
73
|
|
|
79
74
|
const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth // 0.65
|
|
80
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @category Piano
|
|
78
|
+
*/
|
|
79
|
+
export interface PianoMethods {
|
|
80
|
+
playNote: (note: number, velocity?: number) => void
|
|
81
|
+
stopNote: (note: number) => void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type Props = PianoProps &
|
|
85
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof PianoProps>
|
|
86
|
+
|
|
81
87
|
/**
|
|
82
88
|
* Customizable piano component.
|
|
83
89
|
* @category Piano
|
|
84
90
|
*/
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
export const Piano = forwardRef<PianoMethods, Props>(
|
|
92
|
+
(
|
|
93
|
+
{
|
|
94
|
+
noteRange,
|
|
95
|
+
glissando = true,
|
|
96
|
+
midiMax = 127,
|
|
97
|
+
keyboardShortcuts,
|
|
98
|
+
fill = false,
|
|
99
|
+
height = fill ? '100%' : 160,
|
|
100
|
+
whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth,
|
|
101
|
+
style,
|
|
102
|
+
className,
|
|
103
|
+
onPlayNote,
|
|
104
|
+
onStopNote,
|
|
105
|
+
label,
|
|
106
|
+
children,
|
|
107
|
+
onPointerDown,
|
|
108
|
+
...props
|
|
109
|
+
},
|
|
110
|
+
forwardedRef,
|
|
111
|
+
) => {
|
|
112
|
+
// -- state and ref ---
|
|
113
|
+
const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth)
|
|
114
|
+
const keyRefs = useRef<RefObject<KeyMethods | null>[]>([])
|
|
115
|
+
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) {
|
|
116
|
+
keyRefs.current[i] = createRef<KeyMethods>()
|
|
117
|
+
}
|
|
118
|
+
const pianoRef = useRef<HTMLDivElement>(null)
|
|
119
|
+
const hitKeyIndex = useRef(-1)
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
// --- interpret props ---
|
|
122
|
+
const noteRangeArray = getNoteRangeArray(noteRange)
|
|
123
|
+
const whiteNotes = noteRangeArray.filter((v) => isWhiteKey(v))
|
|
124
|
+
const blackNotes = noteRangeArray.filter((v) => isBlackKey(v))
|
|
125
|
+
const whiteNoteCount = whiteNotes.length
|
|
126
|
+
const padding = 1
|
|
127
|
+
const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount
|
|
128
|
+
// const blackNoteShiftPercent = 0.3
|
|
119
129
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
const childrenWithProps = useMemo(() => {
|
|
131
|
+
return (
|
|
132
|
+
children &&
|
|
133
|
+
React.Children.map(children, (child, index) => {
|
|
134
|
+
if (React.isValidElement(child)) {
|
|
135
|
+
const __width =
|
|
136
|
+
child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * 0.65
|
|
137
|
+
const props = { ref: keyRefs.current[index], __width }
|
|
138
|
+
return React.cloneElement(child, props)
|
|
139
|
+
}
|
|
140
|
+
return child
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
}, [children, whiteNoteWidth])
|
|
144
|
+
|
|
145
|
+
// --- internal functions ---
|
|
146
|
+
const notePosition = useCallback(
|
|
147
|
+
(note: number) => {
|
|
148
|
+
const pitchPositions: Record<NoteKey, number> = {
|
|
149
|
+
C: 0,
|
|
150
|
+
'C#': 1,
|
|
151
|
+
D: 1,
|
|
152
|
+
'D#': 2,
|
|
153
|
+
E: 2,
|
|
154
|
+
F: 3,
|
|
155
|
+
'F#': 4,
|
|
156
|
+
G: 4,
|
|
157
|
+
'G#': 5,
|
|
158
|
+
A: 5,
|
|
159
|
+
'A#': 6,
|
|
160
|
+
B: 6,
|
|
129
161
|
}
|
|
130
|
-
return child
|
|
131
|
-
})
|
|
132
|
-
)
|
|
133
|
-
}, [children, whiteNoteWidth])
|
|
134
162
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
163
|
+
const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth
|
|
164
|
+
const padding = 1
|
|
165
|
+
const targetNoteKey = noteKey(note)
|
|
166
|
+
const firstNoteKey = noteKey(noteRange.first)
|
|
167
|
+
const octave = Math.floor((note - noteRange.first) / 12)
|
|
168
|
+
const octaveOffset =
|
|
169
|
+
noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey)
|
|
170
|
+
? 1
|
|
171
|
+
: 0
|
|
172
|
+
const w = whiteNoteWidth + padding
|
|
173
|
+
const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey]
|
|
174
|
+
const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0
|
|
175
|
+
return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset
|
|
176
|
+
},
|
|
177
|
+
[noteRange.first, whiteNoteWidth],
|
|
178
|
+
)
|
|
152
179
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
const getHitKeyIndex = useCallback(
|
|
181
|
+
(x: number, y: number) => {
|
|
182
|
+
if (!pianoRef.current) return -1
|
|
183
|
+
const containerHeight = pianoRef.current.clientHeight
|
|
184
|
+
const notes = [...blackNotes, ...whiteNotes]
|
|
185
|
+
for (let i = 0; i < notes.length; i++) {
|
|
186
|
+
const note = notes[i]
|
|
187
|
+
const pos = notePosition(note)
|
|
188
|
+
const w = isWhiteKey(note)
|
|
189
|
+
? whiteNoteWidth
|
|
190
|
+
: whiteNoteWidth * blackPerWhiteWidth
|
|
191
|
+
const h = isWhiteKey(note) ? containerHeight : containerHeight * 0.6
|
|
192
|
+
if (pos <= x && x < pos + w && 0 <= y && y < h) {
|
|
193
|
+
return note
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return -1
|
|
197
|
+
},
|
|
198
|
+
[blackNotes, notePosition, whiteNoteWidth, whiteNotes],
|
|
199
|
+
)
|
|
167
200
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
// TODO: 単一のポインターに対しては、useDragで対応可能だが、
|
|
202
|
+
// マルチタッチに対しては、TouchEventを使う必要がありそう
|
|
203
|
+
// 取り敢えず、シングルタッチだけ対応
|
|
204
|
+
const onDrag = useCallback(
|
|
205
|
+
(perX: number, perY: number) => {
|
|
206
|
+
if (!pianoRef.current) return
|
|
207
|
+
const x = perX * staticWidth
|
|
208
|
+
const y = perY * pianoRef.current.clientHeight
|
|
209
|
+
const note = getHitKeyIndex(x, y)
|
|
210
|
+
const index = noteRangeArray.indexOf(note)
|
|
211
|
+
if (index == -1) return
|
|
212
|
+
if (hitKeyIndex.current != index) {
|
|
213
|
+
keyRefs.current[hitKeyIndex.current]?.current?.stop()
|
|
214
|
+
keyRefs.current[index]?.current?.play()
|
|
215
|
+
hitKeyIndex.current = index
|
|
182
216
|
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
[blackNotes, notePosition, whiteNoteWidth, whiteNotes],
|
|
187
|
-
)
|
|
217
|
+
},
|
|
218
|
+
[getHitKeyIndex, noteRangeArray, staticWidth],
|
|
219
|
+
)
|
|
188
220
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// 取り敢えず、シングルタッチだけ対応
|
|
192
|
-
const onDrag = useCallback(
|
|
193
|
-
(perX: number, perY: number) => {
|
|
194
|
-
if (!pianoRef.current) return
|
|
195
|
-
const x = perX * staticWidth
|
|
196
|
-
const y = perY * pianoRef.current.clientHeight
|
|
197
|
-
const note = getHitKeyIndex(x, y)
|
|
198
|
-
const index = noteRangeArray.indexOf(note)
|
|
199
|
-
if (index == -1) return
|
|
200
|
-
if (hitKeyIndex.current != index) {
|
|
221
|
+
const onDragEnd = useCallback(() => {
|
|
222
|
+
if (keyRefs.current[hitKeyIndex.current]?.current?.played()) {
|
|
201
223
|
keyRefs.current[hitKeyIndex.current]?.current?.stop()
|
|
202
|
-
keyRefs.current[index]?.current?.play()
|
|
203
|
-
hitKeyIndex.current = index
|
|
204
224
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
225
|
+
hitKeyIndex.current = -1
|
|
226
|
+
}, [keyRefs])
|
|
208
227
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
// --- hooks ---
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (fill && pianoRef.current) {
|
|
231
|
+
const parent = pianoRef.current.parentElement
|
|
232
|
+
if (!parent) throw new Error("doesn't have a parent element.")
|
|
233
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
234
|
+
const w = pianoRef.current!.clientWidth
|
|
235
|
+
setWhiteNoteWidth(w / whiteNoteCount - padding)
|
|
236
|
+
})
|
|
237
|
+
resizeObserver.observe(parent)
|
|
238
|
+
return () => {
|
|
239
|
+
resizeObserver.unobserve(parent)
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
setWhiteNoteWidth(_whiteNoteWidth)
|
|
243
|
+
}
|
|
244
|
+
}, [fill, _whiteNoteWidth, whiteNoteCount])
|
|
215
245
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
222
|
-
const w = pianoRef.current!.clientWidth
|
|
223
|
-
setWhiteNoteWidth(w / whiteNoteCount - padding)
|
|
246
|
+
const [touchMoveRefCallback, pointerDownHandler] =
|
|
247
|
+
useDragWithElement<HTMLDivElement>({
|
|
248
|
+
baseElementRef: pianoRef,
|
|
249
|
+
onDrag: onDrag,
|
|
250
|
+
onDragEnd: onDragEnd,
|
|
224
251
|
})
|
|
225
|
-
resizeObserver.observe(parent)
|
|
226
|
-
return () => {
|
|
227
|
-
resizeObserver.unobserve(parent)
|
|
228
|
-
}
|
|
229
|
-
} else {
|
|
230
|
-
setWhiteNoteWidth(_whiteNoteWidth)
|
|
231
|
-
}
|
|
232
|
-
}, [fill, _whiteNoteWidth, whiteNoteCount])
|
|
233
252
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
253
|
+
useEventListener(globalThis.window, 'keydown', (e) => {
|
|
254
|
+
if (e.repeat) return
|
|
255
|
+
if (!keyboardShortcuts) return
|
|
256
|
+
const index = keyboardShortcuts.keys.indexOf(e.key)
|
|
257
|
+
if (index != -1) {
|
|
258
|
+
if (!keyRefs.current[index]?.current?.played()) {
|
|
259
|
+
keyRefs.current[index]?.current?.play()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
239
262
|
})
|
|
240
263
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
keyRefs.current[index]?.current?.play()
|
|
264
|
+
useEventListener(globalThis.window, 'keyup', (e) => {
|
|
265
|
+
if (e.repeat) return
|
|
266
|
+
if (!keyboardShortcuts) return
|
|
267
|
+
const index = keyboardShortcuts.keys.indexOf(e.key)
|
|
268
|
+
if (index != -1) {
|
|
269
|
+
keyRefs.current[index]?.current?.stop()
|
|
248
270
|
}
|
|
249
|
-
}
|
|
250
|
-
})
|
|
271
|
+
})
|
|
251
272
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
273
|
+
useImperativeHandle(forwardedRef, () => {
|
|
274
|
+
return {
|
|
275
|
+
playNote(note, velocity) {
|
|
276
|
+
const index = noteRangeArray.indexOf(note)
|
|
277
|
+
if (index != -1) {
|
|
278
|
+
if (!keyRefs.current[index]?.current?.played()) {
|
|
279
|
+
keyRefs.current[index]?.current?.play(velocity)
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
onPlayNote?.(note, velocity)
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
stopNote(note: number) {
|
|
286
|
+
const index = noteRangeArray.indexOf(note)
|
|
287
|
+
if (index != -1) {
|
|
288
|
+
keyRefs.current[index]?.current?.stop()
|
|
289
|
+
} else {
|
|
290
|
+
onStopNote?.(note)
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
}, [noteRangeArray, onPlayNote, onStopNote])
|
|
260
295
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
>
|
|
272
|
-
<div
|
|
273
|
-
ref={(div) => {
|
|
274
|
-
pianoRef.current = div
|
|
275
|
-
touchMoveRefCallback(div)
|
|
276
|
-
}}
|
|
277
|
-
className={clsx('tremolo-piano', className)}
|
|
278
|
-
style={{
|
|
279
|
-
width: fill ? '100%' : staticWidth,
|
|
280
|
-
height: height,
|
|
281
|
-
...style,
|
|
282
|
-
}}
|
|
283
|
-
onPointerDown={(event) => {
|
|
284
|
-
pointerDownHandler(event)
|
|
285
|
-
onPointerDown?.(event)
|
|
286
|
-
}}
|
|
287
|
-
{...props}
|
|
296
|
+
return (
|
|
297
|
+
<PianoProvider
|
|
298
|
+
notePosition={notePosition}
|
|
299
|
+
noteRange={noteRange}
|
|
300
|
+
glissando={glissando}
|
|
301
|
+
midiMax={midiMax}
|
|
302
|
+
fill={fill}
|
|
303
|
+
onPlayNote={onPlayNote}
|
|
304
|
+
onStopNote={onStopNote}
|
|
305
|
+
label={label}
|
|
288
306
|
>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
307
|
+
<div
|
|
308
|
+
ref={(div) => {
|
|
309
|
+
pianoRef.current = div
|
|
310
|
+
touchMoveRefCallback(div)
|
|
311
|
+
}}
|
|
312
|
+
className={clsx('tremolo-piano', className)}
|
|
313
|
+
style={{
|
|
314
|
+
width: fill ? '100%' : staticWidth,
|
|
315
|
+
height: height,
|
|
316
|
+
...style,
|
|
317
|
+
}}
|
|
318
|
+
onPointerDown={(event) => {
|
|
319
|
+
pointerDownHandler(event)
|
|
320
|
+
onPointerDown?.(event)
|
|
321
|
+
}}
|
|
322
|
+
{...props}
|
|
323
|
+
>
|
|
324
|
+
{childrenWithProps ||
|
|
325
|
+
noteRangeArray.map((note, index) =>
|
|
326
|
+
isWhiteKey(note) ? (
|
|
327
|
+
<WhiteKey
|
|
328
|
+
ref={keyRefs.current[index]}
|
|
329
|
+
key={note}
|
|
330
|
+
noteNumber={note}
|
|
331
|
+
__width={whiteNoteWidth}
|
|
332
|
+
/>
|
|
333
|
+
) : (
|
|
334
|
+
<BlackKey
|
|
335
|
+
ref={keyRefs.current[index]}
|
|
336
|
+
key={note}
|
|
337
|
+
noteNumber={note}
|
|
338
|
+
__width={whiteNoteWidth * blackPerWhiteWidth}
|
|
339
|
+
/>
|
|
340
|
+
),
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
343
|
+
</PianoProvider>
|
|
344
|
+
)
|
|
345
|
+
},
|
|
346
|
+
)
|
|
311
347
|
|
|
312
348
|
export { type KeyboardShortcuts, SHORTCUTS } from './keyboardShortcuts'
|
|
313
349
|
export { WhiteKey, BlackKey, type KeyProps, type KeyMethods } from './key'
|
|
@@ -40,7 +40,7 @@ export interface KeyProps {
|
|
|
40
40
|
* @category Piano
|
|
41
41
|
*/
|
|
42
42
|
export interface KeyMethods {
|
|
43
|
-
play: () => void
|
|
43
|
+
play: (velocity?: number) => void
|
|
44
44
|
stop: () => void
|
|
45
45
|
played: () => boolean
|
|
46
46
|
}
|
|
@@ -92,10 +92,10 @@ const KeyImpl = forwardRef<KeyMethods, ImplProps>(
|
|
|
92
92
|
|
|
93
93
|
useImperativeHandle(ref, () => {
|
|
94
94
|
return {
|
|
95
|
-
play() {
|
|
95
|
+
play(velocity) {
|
|
96
96
|
if (disabled) return
|
|
97
97
|
setPlayed(true)
|
|
98
|
-
if (onPlayNote) onPlayNote(noteNumber)
|
|
98
|
+
if (onPlayNote) onPlayNote(noteNumber, velocity)
|
|
99
99
|
},
|
|
100
100
|
stop() {
|
|
101
101
|
setPlayed(false)
|
|
@@ -64,8 +64,8 @@ export function SliderTrack({
|
|
|
64
64
|
: {
|
|
65
65
|
...colors,
|
|
66
66
|
background: xor(vertical, reverse)
|
|
67
|
-
? `linear-gradient(to ${direction}, var(--inactive
|
|
68
|
-
: `linear-gradient(to ${direction}, var(--active
|
|
67
|
+
? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)`
|
|
68
|
+
: `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
|
|
69
69
|
borderRadius: styleHelper(thickness!, '/', 2),
|
|
70
70
|
width: !vertical ? length : thickness,
|
|
71
71
|
height: vertical ? length : thickness,
|
|
@@ -112,7 +112,7 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
112
112
|
onKeyDown,
|
|
113
113
|
...props
|
|
114
114
|
}: Props,
|
|
115
|
-
|
|
115
|
+
forwardedRef,
|
|
116
116
|
) => {
|
|
117
117
|
// -- state and ref ---
|
|
118
118
|
const trackElementRef = useRef<HTMLDivElement>(null)
|
|
@@ -245,7 +245,7 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
245
245
|
[wheel, vertical, readonly, onChange, updateValueByEvent],
|
|
246
246
|
)
|
|
247
247
|
|
|
248
|
-
useImperativeHandle(
|
|
248
|
+
useImperativeHandle(forwardedRef, () => {
|
|
249
249
|
return {
|
|
250
250
|
focus() {
|
|
251
251
|
thumbRef.current?.focus()
|
|
@@ -106,7 +106,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
106
106
|
children,
|
|
107
107
|
...props
|
|
108
108
|
}: Props,
|
|
109
|
-
|
|
109
|
+
forwardedRef,
|
|
110
110
|
) => {
|
|
111
111
|
const x = useMemo(() => {
|
|
112
112
|
return { ...defaultValueOptions, ..._x }
|
|
@@ -276,7 +276,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
276
276
|
[x, y, onChange, readonly, updateValueByEvent],
|
|
277
277
|
)
|
|
278
278
|
|
|
279
|
-
useImperativeHandle(
|
|
279
|
+
useImperativeHandle(forwardedRef, () => {
|
|
280
280
|
return {
|
|
281
281
|
focus() {
|
|
282
282
|
thumbRef.current?.focus()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback, useInsertionEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Internal
|
|
5
|
+
* @private
|
|
6
6
|
*/
|
|
7
7
|
export function useCallbackRef<Args extends unknown[], Return>(
|
|
8
8
|
callback: ((...args: Args) => Return) | undefined,
|
package/src/hooks/useDrag.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { useRef, useCallback } from 'react'
|
|
|
3
3
|
import { useEventListener } from './useEventListener'
|
|
4
4
|
import { useRefCallbackEvent } from './useRefCallbackEvent'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
interface UseDragProps {
|
|
7
7
|
threshold?: number
|
|
8
8
|
|
|
9
9
|
onDrag: (x: number, y: number, deltaX: number, deltaY: number) => void
|
|
@@ -12,6 +12,7 @@ export interface UseDragProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
+
* @category hooks
|
|
15
16
|
* @returns [refCallback, pointerDownHandler]
|
|
16
17
|
*/
|
|
17
18
|
export function useDrag<T extends Element>({
|
|
@@ -5,7 +5,7 @@ import { normalizeValue } from '@tremolo-ui/functions'
|
|
|
5
5
|
import { useEventListener } from './useEventListener'
|
|
6
6
|
import { useRefCallbackEvent } from './useRefCallbackEvent'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
interface UseDragWithElementProps<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
|
|
@@ -13,6 +13,7 @@ export interface UseDragWithElementProps<T extends Element> {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
+
* @category hooks
|
|
16
17
|
* @returns [refCallback, pointerDownHandler]
|
|
17
18
|
*/
|
|
18
19
|
export function useDragWithElement<T extends Element>({
|