@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.
@@ -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,
@@ -1,5 +1,8 @@
1
1
  import { useEffect, useRef } from 'react'
2
2
 
3
+ /**
4
+ * @category hooks
5
+ */
3
6
  export function useInterval(callback: () => void, delay: number | null) {
4
7
  const savedCallback = useRef(callback)
5
8
 
@@ -3,6 +3,9 @@ import { useCallback, useState } from 'react'
3
3
  import { useEventListener } from './useEventListener'
4
4
  import { useInterval } from './useInterval'
5
5
 
6
+ /**
7
+ * @category hooks
8
+ */
6
9
  export function useLongPress(
7
10
  callback: () => void,
8
11
  initialDelay = 500,
@@ -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
+ }
@@ -1,5 +1,9 @@
1
1
  import { useMemo } from 'react'
2
2
 
3
+ /**
4
+ * Internal
5
+ * @private
6
+ */
3
7
  export function useRefCallbackEvent<K extends keyof DocumentEventMap>(
4
8
  event: K,
5
9
  handler: (event: DocumentEventMap[K]) => void,
package/src/index.ts CHANGED
@@ -34,17 +34,18 @@ export {
34
34
  type StepperProps,
35
35
  } from './components/NumberInput'
36
36
  export {
37
+ Piano,
38
+ type PianoProps,
39
+ type PianoMethods,
40
+ getNoteRangeArray,
41
+ WhiteKey,
37
42
  BlackKey,
43
+ type KeyProps,
44
+ type KeyMethods,
38
45
  KeyLabel,
39
46
  type KeyLabelProps,
40
- type KeyMethods,
41
- type KeyProps,
42
- type KeyboardShortcuts,
43
- Piano,
44
- type PianoProps,
45
47
  SHORTCUTS,
46
- WhiteKey,
47
- getNoteRangeArray,
48
+ type KeyboardShortcuts,
48
49
  } from './components/Piano'
49
50
  export {
50
51
  Scale,
@@ -77,12 +78,18 @@ export {
77
78
  type XYPadThumbMethods,
78
79
  } from './components/XYPad'
79
80
 
81
+ // hooks
80
82
  export { useAnimationFrame } from './hooks/useAnimationFrame'
81
- export { useDrag, type UseDragProps } from './hooks/useDrag'
82
- export {
83
- useDragWithElement,
84
- type UseDragWithElementProps,
85
- } from './hooks/useDragWithElement'
83
+ export { useDrag } from './hooks/useDrag'
84
+ export { useDragWithElement } from './hooks/useDragWithElement'
86
85
  export { useEventListener } from './hooks/useEventListener'
87
86
  export { useInterval } from './hooks/useInterval'
88
87
  export { useLongPress } from './hooks/useLongPress'
88
+ export {
89
+ useMIDIAccess,
90
+ NOT_SUPPORTED,
91
+ PERMISSION_DENIED,
92
+ type MIDIAccessError,
93
+ } from './hooks/useMIDIAccess'
94
+ export { useMIDIInput } from './hooks/useMIDIInput'
95
+ export { useMIDIMessage } from './hooks/useMIDIMessage'
@@ -1,5 +1,5 @@
1
1
  :root {
2
- --tremolo-theme-color: #4e76e6;
2
+ --tremolo-theme-color: oklch(59.5% 0.17548 266.373);
3
3
  }
4
4
 
5
5
  .tremolo-no-select {