@tremolo-ui/react 0.1.5 → 0.2.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 +325 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +149 -64
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +229 -199
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +229 -199
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +314 -223
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Knob/index.css +50 -25
- package/src/components/Knob/index.tsx +37 -32
- package/src/components/NumberInput/index.css +31 -15
- package/src/components/NumberInput/index.tsx +22 -14
- package/src/components/Piano/context.tsx +1 -1
- package/src/components/Piano/index.css +17 -8
- package/src/components/Piano/index.tsx +259 -220
- package/src/components/Piano/key.tsx +3 -3
- package/src/components/Slider/Thumb.tsx +1 -1
- package/src/components/Slider/Track.tsx +3 -3
- package/src/components/Slider/index.css +33 -8
- package/src/components/Slider/index.tsx +25 -25
- package/src/components/XYPad/Area.tsx +4 -3
- package/src/components/XYPad/Thumb.tsx +8 -4
- package/src/components/XYPad/index.css +18 -4
- package/src/components/XYPad/index.tsx +25 -19
- 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 +30 -35
- package/src/styles/global.css +0 -4
|
@@ -27,8 +27,9 @@ import { addNoSelect, removeNoSelect } from '../_util'
|
|
|
27
27
|
|
|
28
28
|
import { SliderProvider } from './context'
|
|
29
29
|
import { Scale } from './Scale'
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
30
|
+
import { ScaleOption } from './ScaleOption'
|
|
31
|
+
import { Thumb, SliderThumbMethods, SliderThumbProps } from './Thumb'
|
|
32
|
+
import { Track, SliderTrackProps } from './Track'
|
|
32
33
|
|
|
33
34
|
/** @category Slider */
|
|
34
35
|
export interface SliderProps {
|
|
@@ -81,11 +82,7 @@ export interface SliderMethods {
|
|
|
81
82
|
type Props = SliderProps &
|
|
82
83
|
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderProps>
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
* Customizable slider
|
|
86
|
-
* @category Slider
|
|
87
|
-
*/
|
|
88
|
-
export const Slider = forwardRef<SliderMethods, Props>(
|
|
85
|
+
const SliderImpl = forwardRef<SliderMethods, Props>(
|
|
89
86
|
(
|
|
90
87
|
{
|
|
91
88
|
value,
|
|
@@ -112,7 +109,7 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
112
109
|
onKeyDown,
|
|
113
110
|
...props
|
|
114
111
|
}: Props,
|
|
115
|
-
|
|
112
|
+
forwardedRef,
|
|
116
113
|
) => {
|
|
117
114
|
// -- state and ref ---
|
|
118
115
|
const trackElementRef = useRef<HTMLDivElement>(null)
|
|
@@ -138,9 +135,9 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
138
135
|
if (children != undefined) {
|
|
139
136
|
React.Children.forEach(children, (child) => {
|
|
140
137
|
if (React.isValidElement(child)) {
|
|
141
|
-
if (child.type ==
|
|
138
|
+
if (child.type == Thumb) {
|
|
142
139
|
thumbProps = child.props as SliderThumbProps
|
|
143
|
-
} else if (child.type ==
|
|
140
|
+
} else if (child.type == Track) {
|
|
144
141
|
trackProps = child.props as SliderTrackProps
|
|
145
142
|
} else if (child.type == Scale) {
|
|
146
143
|
scaleComponent = child
|
|
@@ -245,7 +242,7 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
245
242
|
[wheel, vertical, readonly, onChange, updateValueByEvent],
|
|
246
243
|
)
|
|
247
244
|
|
|
248
|
-
useImperativeHandle(
|
|
245
|
+
useImperativeHandle(forwardedRef, () => {
|
|
249
246
|
return {
|
|
250
247
|
focus() {
|
|
251
248
|
thumbRef.current?.focus()
|
|
@@ -312,14 +309,10 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
312
309
|
}}
|
|
313
310
|
>
|
|
314
311
|
<div className="tremolo-slider-track-wrapper" ref={trackElementRef}>
|
|
315
|
-
<
|
|
312
|
+
<Track
|
|
316
313
|
__percent={percent}
|
|
317
314
|
__thumb={
|
|
318
|
-
<
|
|
319
|
-
ref={thumbRef}
|
|
320
|
-
__percent={percent}
|
|
321
|
-
{...thumbProps}
|
|
322
|
-
/>
|
|
315
|
+
<Thumb ref={thumbRef} __percent={percent} {...thumbProps} />
|
|
323
316
|
}
|
|
324
317
|
{...trackProps}
|
|
325
318
|
/>
|
|
@@ -332,13 +325,20 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
332
325
|
},
|
|
333
326
|
)
|
|
334
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Customizable slider
|
|
330
|
+
* @category Slider
|
|
331
|
+
*/
|
|
332
|
+
export const Slider = Object.assign(SliderImpl, {
|
|
333
|
+
Thumb,
|
|
334
|
+
Track,
|
|
335
|
+
Scale,
|
|
336
|
+
ScaleOption,
|
|
337
|
+
})
|
|
338
|
+
|
|
335
339
|
export { useSliderContext } from './context'
|
|
336
|
-
export {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
} from './Thumb'
|
|
341
|
-
export { SliderTrack, type SliderTrackProps } from './Track'
|
|
342
|
-
export { Scale, type ScaleProps } from './Scale'
|
|
343
|
-
export { ScaleOption, type ScaleOptionProps } from './ScaleOption'
|
|
340
|
+
export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
341
|
+
export { type SliderTrackProps } from './Track'
|
|
342
|
+
export { type ScaleProps } from './Scale'
|
|
343
|
+
export { type ScaleOptionProps } from './ScaleOption'
|
|
344
344
|
export { type ScaleOptions, type ScaleType } from './type'
|
|
@@ -2,7 +2,7 @@ import clsx from 'clsx'
|
|
|
2
2
|
import { ComponentPropsWithoutRef, CSSProperties, ReactElement } from 'react'
|
|
3
3
|
|
|
4
4
|
/** @category XYPad */
|
|
5
|
-
export interface
|
|
5
|
+
export interface XYPadAreaProps {
|
|
6
6
|
width?: number | string
|
|
7
7
|
height?: number | string
|
|
8
8
|
color?: string
|
|
@@ -15,7 +15,7 @@ export interface AreaProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** @category XYPad */
|
|
18
|
-
export function
|
|
18
|
+
export function Area({
|
|
19
19
|
width = 120,
|
|
20
20
|
height = 120,
|
|
21
21
|
color,
|
|
@@ -24,7 +24,8 @@ export function XYPadArea({
|
|
|
24
24
|
style,
|
|
25
25
|
__thumb,
|
|
26
26
|
...props
|
|
27
|
-
}:
|
|
27
|
+
}: XYPadAreaProps &
|
|
28
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>) {
|
|
28
29
|
return (
|
|
29
30
|
<div
|
|
30
31
|
className={clsx('tremolo-xy-pad-area', className)}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from 'react'
|
|
10
10
|
|
|
11
11
|
/** @category XYPad */
|
|
12
|
-
export interface
|
|
12
|
+
export interface XYPadThumbProps {
|
|
13
13
|
size?: number | string
|
|
14
14
|
width?: number | string
|
|
15
15
|
height?: number | string
|
|
@@ -24,6 +24,8 @@ export interface ThumbProps {
|
|
|
24
24
|
/** @internal */
|
|
25
25
|
__disabled?: boolean
|
|
26
26
|
/** @internal */
|
|
27
|
+
__readonly?: boolean
|
|
28
|
+
/** @internal */
|
|
27
29
|
__css?: CSSProperties
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -33,11 +35,11 @@ export interface XYPadThumbMethods {
|
|
|
33
35
|
blur: () => void
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
type Props =
|
|
37
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof
|
|
38
|
+
type Props = XYPadThumbProps &
|
|
39
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadThumbProps>
|
|
38
40
|
|
|
39
41
|
/** @category XYPad */
|
|
40
|
-
export const
|
|
42
|
+
export const Thumb = forwardRef<XYPadThumbMethods, Props>(
|
|
41
43
|
(
|
|
42
44
|
{
|
|
43
45
|
size,
|
|
@@ -50,6 +52,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
50
52
|
className,
|
|
51
53
|
style,
|
|
52
54
|
__disabled,
|
|
55
|
+
__readonly,
|
|
53
56
|
__css,
|
|
54
57
|
...props
|
|
55
58
|
}: Props,
|
|
@@ -87,6 +90,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
87
90
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
88
91
|
tabIndex={0}
|
|
89
92
|
aria-disabled={__disabled}
|
|
93
|
+
aria-readonly={__readonly}
|
|
90
94
|
style={{
|
|
91
95
|
...{ '--color': color },
|
|
92
96
|
width: size ?? width,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.tremolo-xy-pad-area {
|
|
7
|
-
--color:
|
|
7
|
+
--color: oklch(95% 0 0);
|
|
8
8
|
|
|
9
9
|
position: relative;
|
|
10
10
|
background-color: var(--color);
|
|
@@ -17,18 +17,32 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.tremolo-xy-pad-thumb {
|
|
20
|
-
--color:
|
|
20
|
+
--color: oklch(59.5% 0.17548 266.373);
|
|
21
21
|
|
|
22
22
|
border-radius: 50%;
|
|
23
23
|
outline: none;
|
|
24
24
|
background-color: var(--color);
|
|
25
25
|
|
|
26
26
|
&[aria-disabled='true'] {
|
|
27
|
-
background-color:
|
|
27
|
+
background-color: oklch(50.462% 0.03307 270.021);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
&[aria-readonly='false']:focus {
|
|
31
31
|
box-shadow: 0px 0px 0px 3px
|
|
32
32
|
color-mix(in srgb, var(--color) 20%, transparent);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
:where(.dark, [data-theme='dark']) {
|
|
37
|
+
.tremolo-xy-pad-area {
|
|
38
|
+
--color: oklch(41.7% 0 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.tremolo-xy-pad-thumb {
|
|
42
|
+
--color: oklch(64.5% 0.17548 266.373);
|
|
43
|
+
|
|
44
|
+
&:hover {
|
|
45
|
+
--color: oklch(67.5% 0.17548 266.373);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -23,8 +23,8 @@ import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
|
23
23
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
24
24
|
import { addNoSelect, removeNoSelect } from '../_util'
|
|
25
25
|
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { Area, XYPadAreaProps } from './Area'
|
|
27
|
+
import { Thumb, XYPadThumbMethods, XYPadThumbProps } from './Thumb'
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Two-dimensional slider component.
|
|
@@ -82,11 +82,7 @@ const defaultValueOptions = {
|
|
|
82
82
|
type Props = XYPadProps &
|
|
83
83
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadProps>
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
* Simple XYPad
|
|
87
|
-
* @category XYPad
|
|
88
|
-
*/
|
|
89
|
-
export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
85
|
+
export const XYPadImpl = forwardRef<XYPadMethods, Props>(
|
|
90
86
|
(
|
|
91
87
|
{
|
|
92
88
|
x: _x,
|
|
@@ -106,7 +102,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
106
102
|
children,
|
|
107
103
|
...props
|
|
108
104
|
}: Props,
|
|
109
|
-
|
|
105
|
+
forwardedRef,
|
|
110
106
|
) => {
|
|
111
107
|
const x = useMemo(() => {
|
|
112
108
|
return { ...defaultValueOptions, ..._x }
|
|
@@ -125,15 +121,15 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
125
121
|
const percentX = toFixed((x.reverse ? 1 - nx : nx) * 100)
|
|
126
122
|
const percentY = toFixed((y.reverse ? 1 - ny : ny) * 100)
|
|
127
123
|
|
|
128
|
-
let areaProps:
|
|
129
|
-
let thumbProps:
|
|
124
|
+
let areaProps: XYPadAreaProps = {}
|
|
125
|
+
let thumbProps: XYPadThumbProps = {}
|
|
130
126
|
if (children != undefined) {
|
|
131
127
|
React.Children.forEach(children, (child) => {
|
|
132
128
|
if (React.isValidElement(child)) {
|
|
133
|
-
if (child.type ==
|
|
134
|
-
thumbProps = child.props as
|
|
135
|
-
} else if (child.type ==
|
|
136
|
-
areaProps = child.props as
|
|
129
|
+
if (child.type == Thumb) {
|
|
130
|
+
thumbProps = child.props as XYPadThumbProps
|
|
131
|
+
} else if (child.type == Area) {
|
|
132
|
+
areaProps = child.props as XYPadAreaProps
|
|
137
133
|
} else {
|
|
138
134
|
throw new Error('only <XYPadThumb> or <XYPadArea>')
|
|
139
135
|
}
|
|
@@ -276,7 +272,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
276
272
|
[x, y, onChange, readonly, updateValueByEvent],
|
|
277
273
|
)
|
|
278
274
|
|
|
279
|
-
useImperativeHandle(
|
|
275
|
+
useImperativeHandle(forwardedRef, () => {
|
|
280
276
|
return {
|
|
281
277
|
focus() {
|
|
282
278
|
thumbRef.current?.focus()
|
|
@@ -323,11 +319,12 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
323
319
|
{...props}
|
|
324
320
|
>
|
|
325
321
|
<div className="tremolo-xy-pad-area-wrapper" ref={areaElementRef}>
|
|
326
|
-
<
|
|
322
|
+
<Area
|
|
327
323
|
__thumb={
|
|
328
|
-
<
|
|
324
|
+
<Thumb
|
|
329
325
|
ref={thumbRef}
|
|
330
326
|
__disabled={disabled}
|
|
327
|
+
__readonly={readonly}
|
|
331
328
|
__css={{
|
|
332
329
|
top: `${percentY}%`,
|
|
333
330
|
left: `${percentX}%`,
|
|
@@ -343,5 +340,14 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
343
340
|
},
|
|
344
341
|
)
|
|
345
342
|
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Simple XYPad
|
|
345
|
+
* @category XYPad
|
|
346
|
+
*/
|
|
347
|
+
export const XYPad = Object.assign(XYPadImpl, {
|
|
348
|
+
Thumb,
|
|
349
|
+
Area,
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
export { type XYPadThumbProps, type XYPadThumbMethods } from './Thumb'
|
|
353
|
+
export { type XYPadAreaProps } from './Area'
|
|
@@ -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>({
|
|
@@ -5,6 +5,9 @@ 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
|
+
*/
|
|
8
11
|
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
9
12
|
target: Target,
|
|
10
13
|
event: K,
|
package/src/hooks/useInterval.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/** @private */
|
|
4
|
+
export const PERMISSION_DENIED = 'PERMISSION_DENIED'
|
|
5
|
+
/** @private */
|
|
6
|
+
export const NOT_SUPPORTED = 'NOT_SUPPORTED'
|
|
7
|
+
|
|
8
|
+
/** @private */
|
|
9
|
+
export type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED
|
|
10
|
+
|
|
11
|
+
/**
|
|
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
|
+
*/
|
|
15
|
+
export function useMIDIAccess(requestOnMount = true) {
|
|
16
|
+
const [midiAccess, setMidiAccess] = useState<MIDIAccess | null>(null)
|
|
17
|
+
const [error, setError] = useState<MIDIAccessError | null>(null)
|
|
18
|
+
|
|
19
|
+
const request = useCallback(() => {
|
|
20
|
+
if (navigator.requestMIDIAccess) {
|
|
21
|
+
navigator
|
|
22
|
+
.requestMIDIAccess()
|
|
23
|
+
.then((access) => {
|
|
24
|
+
setMidiAccess(access)
|
|
25
|
+
setError(null)
|
|
26
|
+
})
|
|
27
|
+
.catch(() => {
|
|
28
|
+
setError(PERMISSION_DENIED)
|
|
29
|
+
})
|
|
30
|
+
} else {
|
|
31
|
+
setError(NOT_SUPPORTED)
|
|
32
|
+
}
|
|
33
|
+
}, [])
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (requestOnMount) request()
|
|
37
|
+
}, [requestOnMount, request])
|
|
38
|
+
|
|
39
|
+
return { request, midiAccess, error }
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useMIDIMessage } from './useMIDIMessage'
|
|
4
|
+
|
|
5
|
+
const MIDI_EVENT_TO_NUMBER = {
|
|
6
|
+
NOTE_ON: 0x90,
|
|
7
|
+
NOTE_OFF: 0x80,
|
|
8
|
+
PITCH_BEND: 0xe0,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// const NUMBER_TO_MIDI_EVENT: Record<number, string> = {
|
|
12
|
+
// 0x90: 'NOTE_ON',
|
|
13
|
+
// 0x80: 'NOTE_OFF',
|
|
14
|
+
// 0xe0: 'PITCH_BEND',
|
|
15
|
+
// }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
19
|
+
* @category hooks
|
|
20
|
+
*/
|
|
21
|
+
export function useMIDIInput(
|
|
22
|
+
midiAccess: MIDIAccess | null,
|
|
23
|
+
onNoteOnEvent?: (note: number, velocity: number) => void,
|
|
24
|
+
onNoteOffEvent?: (note: number) => void,
|
|
25
|
+
onPitchBendEvent?: (msb: number, lsb: number) => void,
|
|
26
|
+
) {
|
|
27
|
+
const handleMIDIMessage = useCallback(
|
|
28
|
+
(event: MIDIMessageEvent) => {
|
|
29
|
+
if (!event.data) return
|
|
30
|
+
// event.data[0] ... command
|
|
31
|
+
// event.data[1] ... note, MSB (Most Significant Byte)
|
|
32
|
+
// event.data[2] ... velocity, LSB (Least Significant Byte)
|
|
33
|
+
const kind = event.data[0] & 0xf0
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF ||
|
|
37
|
+
(kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0)
|
|
38
|
+
) {
|
|
39
|
+
onNoteOffEvent?.(event.data[1])
|
|
40
|
+
} else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) {
|
|
41
|
+
onNoteOnEvent?.(event.data[1], event.data[2])
|
|
42
|
+
} else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) {
|
|
43
|
+
onPitchBendEvent?.(event.data[1], event.data[2])
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[onNoteOffEvent, onNoteOnEvent, onPitchBendEvent],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
useMIDIMessage(midiAccess, handleMIDIMessage)
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
5
|
+
* @category hooks
|
|
6
|
+
*/
|
|
7
|
+
export function useMIDIMessage(
|
|
8
|
+
midiAccess: MIDIAccess | null,
|
|
9
|
+
onMIDIMessage: (event: MIDIMessageEvent) => void,
|
|
10
|
+
) {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!midiAccess) return
|
|
13
|
+
|
|
14
|
+
for (const input of midiAccess.inputs.values()) {
|
|
15
|
+
input.addEventListener('midimessage', onMIDIMessage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
for (const input of midiAccess.inputs.values()) {
|
|
20
|
+
input.removeEventListener('midimessage', onMIDIMessage)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, [midiAccess, onMIDIMessage])
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,8 @@ import './components/Slider/index.css'
|
|
|
7
7
|
import './components/XYPad/index.css'
|
|
8
8
|
|
|
9
9
|
export {
|
|
10
|
-
type AbsoluteSizingProps,
|
|
11
10
|
AnimationCanvas,
|
|
11
|
+
type AbsoluteSizingProps,
|
|
12
12
|
type AnimationCanvasProps,
|
|
13
13
|
type CommonProps,
|
|
14
14
|
type DrawFunction,
|
|
@@ -17,72 +17,67 @@ export {
|
|
|
17
17
|
} from './components/AnimationCanvas'
|
|
18
18
|
export {
|
|
19
19
|
AnimationKnob,
|
|
20
|
-
type AnimationKnobMethods,
|
|
21
20
|
type AnimationKnobProps,
|
|
21
|
+
type AnimationKnobMethods,
|
|
22
22
|
} from './components/AnimationKnob'
|
|
23
23
|
export { DragObserver, type DragObserverProps } from './components/DragObserver'
|
|
24
|
-
export { Knob, type
|
|
24
|
+
export { Knob, type KnobProps, type KnobMethods } from './components/Knob'
|
|
25
25
|
export {
|
|
26
|
-
DecrementStepper,
|
|
27
|
-
type DecrementStepperProps,
|
|
28
|
-
IncrementStepper,
|
|
29
|
-
type IncrementStepperProps,
|
|
30
26
|
NumberInput,
|
|
31
|
-
type NumberInputMethods,
|
|
32
27
|
type NumberInputProps,
|
|
33
|
-
|
|
28
|
+
type NumberInputMethods,
|
|
34
29
|
type StepperProps,
|
|
30
|
+
type IncrementStepperProps,
|
|
31
|
+
type DecrementStepperProps,
|
|
35
32
|
} from './components/NumberInput'
|
|
36
33
|
export {
|
|
37
|
-
BlackKey,
|
|
38
|
-
KeyLabel,
|
|
39
|
-
type KeyLabelProps,
|
|
40
|
-
type KeyMethods,
|
|
41
|
-
type KeyProps,
|
|
42
|
-
type KeyboardShortcuts,
|
|
43
34
|
Piano,
|
|
44
|
-
type PianoProps,
|
|
45
|
-
SHORTCUTS,
|
|
46
|
-
WhiteKey,
|
|
47
35
|
getNoteRangeArray,
|
|
36
|
+
SHORTCUTS,
|
|
37
|
+
type PianoProps,
|
|
38
|
+
type PianoMethods,
|
|
39
|
+
type KeyProps,
|
|
40
|
+
type KeyMethods,
|
|
41
|
+
type KeyLabelProps,
|
|
42
|
+
type KeyboardShortcuts,
|
|
48
43
|
} from './components/Piano'
|
|
49
44
|
export {
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
Slider,
|
|
46
|
+
useSliderContext,
|
|
52
47
|
type ScaleOptionProps,
|
|
53
48
|
type ScaleProps,
|
|
54
|
-
Slider,
|
|
55
49
|
type SliderMethods,
|
|
56
50
|
type SliderProps,
|
|
57
|
-
SliderThumb,
|
|
58
51
|
type SliderThumbMethods,
|
|
59
52
|
type SliderThumbProps,
|
|
60
|
-
SliderTrack,
|
|
61
53
|
type SliderTrackProps,
|
|
62
|
-
useSliderContext,
|
|
63
54
|
} from './components/Slider'
|
|
64
55
|
export {
|
|
65
56
|
WheelObserver,
|
|
66
57
|
type WheelObserverProps,
|
|
67
58
|
} from './components/WheelObserver'
|
|
68
59
|
export {
|
|
69
|
-
type AreaProps,
|
|
70
|
-
type ThumbProps,
|
|
71
|
-
type ValueOptions,
|
|
72
60
|
XYPad,
|
|
73
|
-
XYPadArea,
|
|
74
|
-
type XYPadMethods,
|
|
75
61
|
type XYPadProps,
|
|
76
|
-
|
|
62
|
+
type XYPadMethods,
|
|
63
|
+
type XYPadAreaProps,
|
|
64
|
+
type XYPadThumbProps,
|
|
77
65
|
type XYPadThumbMethods,
|
|
66
|
+
type ValueOptions,
|
|
78
67
|
} from './components/XYPad'
|
|
79
68
|
|
|
69
|
+
// hooks
|
|
80
70
|
export { useAnimationFrame } from './hooks/useAnimationFrame'
|
|
81
|
-
export { useDrag
|
|
82
|
-
export {
|
|
83
|
-
useDragWithElement,
|
|
84
|
-
type UseDragWithElementProps,
|
|
85
|
-
} from './hooks/useDragWithElement'
|
|
71
|
+
export { useDrag } from './hooks/useDrag'
|
|
72
|
+
export { useDragWithElement } from './hooks/useDragWithElement'
|
|
86
73
|
export { useEventListener } from './hooks/useEventListener'
|
|
87
74
|
export { useInterval } from './hooks/useInterval'
|
|
88
75
|
export { useLongPress } from './hooks/useLongPress'
|
|
76
|
+
export {
|
|
77
|
+
useMIDIAccess,
|
|
78
|
+
NOT_SUPPORTED,
|
|
79
|
+
PERMISSION_DENIED,
|
|
80
|
+
type MIDIAccessError,
|
|
81
|
+
} from './hooks/useMIDIAccess'
|
|
82
|
+
export { useMIDIInput } from './hooks/useMIDIInput'
|
|
83
|
+
export { useMIDIMessage } from './hooks/useMIDIMessage'
|