@tremolo-ui/react 0.4.0 → 0.6.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/compose-refs.cjs +72 -0
- package/dist/compose-refs.cjs.map +1 -0
- package/dist/compose-refs.d.cts +43 -0
- package/dist/compose-refs.d.cts.map +1 -0
- package/dist/compose-refs.d.ts +43 -0
- package/dist/compose-refs.d.ts.map +1 -0
- package/dist/compose-refs.js +70 -0
- package/dist/compose-refs.js.map +1 -0
- package/dist/index.cjs +2272 -1650
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1461 -475
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1461 -475
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2258 -1648
- package/dist/index.js.map +1 -1
- package/package.json +32 -31
- package/src/components/AnimationCanvas/index.tsx +162 -194
- package/src/components/Knob/ActiveLine.tsx +9 -8
- package/src/components/Knob/InactiveLine.tsx +11 -14
- package/src/components/Knob/SVGRoot.tsx +10 -17
- package/src/components/Knob/Thumb.tsx +33 -11
- package/src/components/Knob/context.tsx +73 -89
- package/src/components/Knob/index.tsx +227 -105
- package/src/components/NumberInput/DecrementStepper.tsx +21 -50
- package/src/components/NumberInput/IncrementStepper.tsx +22 -50
- package/src/components/NumberInput/InputField.tsx +206 -0
- package/src/components/NumberInput/Stepper.tsx +150 -21
- package/src/components/NumberInput/StepperButton.tsx +95 -0
- package/src/components/NumberInput/context.tsx +104 -149
- package/src/components/NumberInput/index.tsx +461 -110
- package/src/components/Piano/index.tsx +413 -263
- package/src/components/Piano/keyboard-shortcuts.ts +61 -0
- package/src/components/PointsEditor/Background.tsx +19 -4
- package/src/components/PointsEditor/Container.tsx +92 -22
- package/src/components/PointsEditor/Point.tsx +293 -53
- package/src/components/PointsEditor/SelectionBox.tsx +54 -0
- package/src/components/PointsEditor/context.tsx +97 -75
- package/src/components/PointsEditor/index.tsx +497 -62
- package/src/components/Slider/Marks.tsx +84 -0
- package/src/components/Slider/MarksOption.tsx +115 -0
- package/src/components/Slider/Thumb.tsx +99 -54
- package/src/components/Slider/Track.tsx +73 -62
- package/src/components/Slider/context.tsx +40 -53
- package/src/components/Slider/index.tsx +237 -190
- package/src/components/Slider/type.ts +27 -9
- package/src/components/XYPad/Area.tsx +52 -33
- package/src/components/XYPad/Thumb.tsx +121 -60
- package/src/components/XYPad/context.tsx +49 -0
- package/src/components/XYPad/index.tsx +338 -246
- package/src/components/_util/Placement.tsx +68 -0
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +24 -0
- package/src/components/_util/css-length.ts +13 -0
- package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
- package/src/hooks/{useCallbackRef.ts → _internal/useCallbackRef.ts} +3 -1
- package/src/hooks/_internal/useCheckSteps.ts +170 -0
- package/src/hooks/useAnimationFrame.ts +17 -7
- package/src/hooks/useDrag.ts +39 -9
- package/src/hooks/useDragValue.ts +194 -0
- package/src/hooks/useEventListener.ts +11 -7
- package/src/hooks/useLongPress.ts +42 -8
- package/src/hooks/useMIDIAccess.ts +82 -11
- package/src/hooks/useMIDIInput.ts +45 -13
- package/src/hooks/useMIDIMessage.ts +23 -5
- package/src/hooks/useWheel.ts +29 -9
- package/src/index.ts +50 -27
- package/src/input-event.ts +32 -0
- package/dist/index.css +0 -536
- package/src/Globals.d.ts +0 -3
- package/src/components/AnimationCanvas/canvas.ts +0 -46
- package/src/components/Knob/index.css +0 -77
- package/src/components/NumberInput/InternalInput.tsx +0 -216
- package/src/components/NumberInput/index.css +0 -185
- package/src/components/NumberInput/type.ts +0 -58
- package/src/components/Piano/KeyLabel.tsx +0 -51
- package/src/components/Piano/context.tsx +0 -73
- package/src/components/Piano/index.css +0 -90
- package/src/components/Piano/key.tsx +0 -205
- package/src/components/Piano/keyboardShortcuts.ts +0 -31
- package/src/components/PointsEditor/index.css +0 -30
- package/src/components/Slider/Scale.tsx +0 -52
- package/src/components/Slider/ScaleOption.tsx +0 -103
- package/src/components/Slider/index.css +0 -112
- package/src/components/XYPad/index.css +0 -48
- package/src/components/_util/index.ts +0 -28
- package/src/components/_util/type.ts +0 -1
- package/src/hooks/useDragWithElement.ts +0 -100
- package/src/hooks/usePianoDrag.ts +0 -75
- package/src/hooks/useRefCallbackEvent.ts +0 -34
- package/src/styles/global.css +0 -19
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
import { useCallbackRef } from './useCallbackRef'
|
|
3
|
+
import { useCallbackRef } from './_internal/useCallbackRef'
|
|
4
4
|
|
|
5
5
|
type Target = EventTarget | null | (() => EventTarget | null)
|
|
6
6
|
type Options = boolean | AddEventListenerOptions
|
|
@@ -30,6 +30,7 @@ export function useEventListener(
|
|
|
30
30
|
options?: Options,
|
|
31
31
|
) {
|
|
32
32
|
const listener = useCallbackRef(handler)
|
|
33
|
+
const cleanupRef = useRef<VoidFunction>(() => {})
|
|
33
34
|
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
const node = typeof target === 'function' ? target() : (target ?? document)
|
|
@@ -37,13 +38,16 @@ export function useEventListener(
|
|
|
37
38
|
if (!node) return
|
|
38
39
|
|
|
39
40
|
node.addEventListener(event, listener, options)
|
|
40
|
-
|
|
41
|
+
const cleanup = () => {
|
|
41
42
|
node.removeEventListener(event, listener, options)
|
|
42
43
|
}
|
|
44
|
+
cleanupRef.current = cleanup
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
cleanup()
|
|
48
|
+
if (cleanupRef.current === cleanup) cleanupRef.current = () => {}
|
|
49
|
+
}
|
|
43
50
|
}, [event, target, options, listener])
|
|
44
51
|
|
|
45
|
-
return () =>
|
|
46
|
-
const node = typeof target === 'function' ? target() : (target ?? document)
|
|
47
|
-
node?.removeEventListener(event, listener, options)
|
|
48
|
-
}
|
|
52
|
+
return useCallback(() => cleanupRef.current(), [])
|
|
49
53
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useState } from 'react'
|
|
1
|
+
import { useCallback, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useEventListener } from './useEventListener'
|
|
4
4
|
import { useInterval } from './useInterval'
|
|
@@ -10,6 +10,8 @@ export function useLongPress(
|
|
|
10
10
|
) {
|
|
11
11
|
const [pressed, setPressed] = useState(false)
|
|
12
12
|
const [delay, setDelay] = useState(initialDelay)
|
|
13
|
+
const activePointerId = useRef<number | null>(null)
|
|
14
|
+
const isPressed = useRef(false)
|
|
13
15
|
|
|
14
16
|
useInterval(
|
|
15
17
|
() => {
|
|
@@ -19,13 +21,45 @@ export function useLongPress(
|
|
|
19
21
|
pressed ? delay : null,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const stop = useCallback(
|
|
25
|
+
(pointerId?: number) => {
|
|
26
|
+
if (!isPressed.current) return
|
|
27
|
+
if (
|
|
28
|
+
pointerId !== undefined &&
|
|
29
|
+
activePointerId.current !== null &&
|
|
30
|
+
activePointerId.current !== pointerId
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
isPressed.current = false
|
|
35
|
+
activePointerId.current = null
|
|
36
|
+
setPressed(false)
|
|
37
|
+
setDelay(initialDelay)
|
|
38
|
+
},
|
|
39
|
+
[initialDelay],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
useEventListener(globalThis.window, 'pointerup', (event) => {
|
|
43
|
+
stop(event.pointerId)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
useEventListener(globalThis.window, 'pointercancel', (event) => {
|
|
47
|
+
stop(event.pointerId)
|
|
25
48
|
})
|
|
26
49
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
useEventListener(globalThis.window, 'blur', () => {
|
|
51
|
+
stop()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return useCallback(
|
|
55
|
+
(event?: Pick<PointerEvent, 'button' | 'pointerId'>) => {
|
|
56
|
+
if (isPressed.current || (event && event.button !== 0)) return
|
|
57
|
+
|
|
58
|
+
isPressed.current = true
|
|
59
|
+
activePointerId.current = event?.pointerId ?? null
|
|
60
|
+
callback()
|
|
61
|
+
setPressed(true)
|
|
62
|
+
},
|
|
63
|
+
[callback],
|
|
64
|
+
)
|
|
31
65
|
}
|
|
@@ -1,32 +1,103 @@
|
|
|
1
1
|
import { useEffect, useMemo, useSyncExternalStore } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createMIDIAccess,
|
|
5
|
+
type MIDIAccessInstance,
|
|
6
|
+
type MIDIAccessOptions,
|
|
7
|
+
type MIDIAccessState,
|
|
8
|
+
} from '@tremolo-ui/dom'
|
|
4
9
|
|
|
5
10
|
export {
|
|
6
11
|
NOT_SUPPORTED,
|
|
7
12
|
PERMISSION_DENIED,
|
|
13
|
+
UNAVAILABLE,
|
|
8
14
|
type MIDIAccessError,
|
|
15
|
+
type MIDIAccessOptions,
|
|
9
16
|
} from '@tremolo-ui/dom'
|
|
10
17
|
|
|
18
|
+
const INITIAL_STATE: MIDIAccessState = {
|
|
19
|
+
midiAccess: null,
|
|
20
|
+
error: null,
|
|
21
|
+
inputs: [],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createMIDIAccessStore() {
|
|
25
|
+
let instance: MIDIAccessInstance | null = null
|
|
26
|
+
let unsubscribe: VoidFunction | null = null
|
|
27
|
+
let state = INITIAL_STATE
|
|
28
|
+
const listeners = new Set<VoidFunction>()
|
|
29
|
+
|
|
30
|
+
function emit() {
|
|
31
|
+
for (const listener of listeners) listener()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function connect() {
|
|
35
|
+
const nextInstance = createMIDIAccess()
|
|
36
|
+
instance = nextInstance
|
|
37
|
+
state = nextInstance.getState()
|
|
38
|
+
unsubscribe = nextInstance.subscribe(() => {
|
|
39
|
+
state = nextInstance.getState()
|
|
40
|
+
emit()
|
|
41
|
+
})
|
|
42
|
+
emit()
|
|
43
|
+
return nextInstance
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function disconnect(currentInstance: MIDIAccessInstance) {
|
|
47
|
+
if (instance !== currentInstance) return
|
|
48
|
+
|
|
49
|
+
unsubscribe?.()
|
|
50
|
+
unsubscribe = null
|
|
51
|
+
instance = null
|
|
52
|
+
state = INITIAL_STATE
|
|
53
|
+
currentInstance.destroy()
|
|
54
|
+
emit()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function request(options?: MIDIAccessOptions) {
|
|
58
|
+
instance?.request(options)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
connect,
|
|
63
|
+
disconnect,
|
|
64
|
+
request,
|
|
65
|
+
getState: () => state,
|
|
66
|
+
getServerState: () => INITIAL_STATE,
|
|
67
|
+
subscribe: (listener: VoidFunction) => {
|
|
68
|
+
listeners.add(listener)
|
|
69
|
+
return () => listeners.delete(listener)
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
11
74
|
/**
|
|
12
75
|
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
76
|
+
*
|
|
77
|
+
* `inputs` follows the devices: it changes as one is plugged in or unplugged,
|
|
78
|
+
* so a list of devices needs no `statechange` listener of its own.
|
|
79
|
+
*
|
|
80
|
+
* `request` takes `{ sysex: true }` when the app needs system exclusive
|
|
81
|
+
* messages. Browsers treat that as a separate, more sensitive permission, so
|
|
82
|
+
* ask for it only when it is actually used.
|
|
13
83
|
*/
|
|
14
84
|
export function useMIDIAccess(requestOnMount = true) {
|
|
15
|
-
const
|
|
85
|
+
const store = useMemo(() => createMIDIAccessStore(), [])
|
|
16
86
|
|
|
17
|
-
const { midiAccess, error } = useSyncExternalStore(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
87
|
+
const { midiAccess, error, inputs } = useSyncExternalStore(
|
|
88
|
+
store.subscribe,
|
|
89
|
+
store.getState,
|
|
90
|
+
store.getServerState,
|
|
21
91
|
)
|
|
22
92
|
|
|
23
93
|
useEffect(() => {
|
|
24
|
-
|
|
25
|
-
|
|
94
|
+
const instance = store.connect()
|
|
95
|
+
return () => store.disconnect(instance)
|
|
96
|
+
}, [store])
|
|
26
97
|
|
|
27
98
|
useEffect(() => {
|
|
28
|
-
|
|
29
|
-
}, [
|
|
99
|
+
if (requestOnMount) store.request()
|
|
100
|
+
}, [store, requestOnMount])
|
|
30
101
|
|
|
31
|
-
return { request:
|
|
102
|
+
return { request: store.request, midiAccess, error, inputs }
|
|
32
103
|
}
|
|
@@ -1,22 +1,54 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createMIDIInput,
|
|
5
|
+
PITCH_BEND_CENTER,
|
|
6
|
+
type MIDIInputHandlers,
|
|
7
|
+
type MIDIInputInstance,
|
|
8
|
+
} from '@tremolo-ui/dom'
|
|
9
|
+
|
|
10
|
+
export { PITCH_BEND_CENTER, type MIDIInputHandlers }
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
|
-
*
|
|
13
|
+
* Handle MIDI input events. To be used with {@link useMIDIAccess}.
|
|
14
|
+
*
|
|
15
|
+
* The handlers are read fresh on every event, so writing them inline is fine:
|
|
16
|
+
* the listeners are attached once per `midiAccess` and stay attached. Devices
|
|
17
|
+
* plugged in later are picked up without anything having to be rebuilt.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { midiAccess } = useMIDIAccess()
|
|
21
|
+
* useMIDIInput(midiAccess, {
|
|
22
|
+
* onNoteOnEvent: (note, velocity) => play(note, velocity / 127),
|
|
23
|
+
* onNoteOffEvent: (note) => stop(note),
|
|
24
|
+
* onControlChangeEvent: (controller, value) => {
|
|
25
|
+
* if (controller === 1) setModulation(value / 127)
|
|
26
|
+
* },
|
|
27
|
+
* })
|
|
7
28
|
*/
|
|
8
29
|
export function useMIDIInput(
|
|
9
30
|
midiAccess: MIDIAccess | null,
|
|
10
|
-
|
|
11
|
-
onNoteOffEvent?: (note: number) => void,
|
|
12
|
-
onPitchBendEvent?: (msb: number, lsb: number) => void,
|
|
31
|
+
handlers: MIDIInputHandlers,
|
|
13
32
|
) {
|
|
33
|
+
// Read when the instance is created. The effect below keeps it current, and
|
|
34
|
+
// runs right after, so a stale handler is replaced within the same commit.
|
|
35
|
+
const latest = useRef(handlers)
|
|
36
|
+
const instanceRef = useRef<MIDIInputInstance | null>(null)
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const instance = createMIDIInput(midiAccess, latest.current)
|
|
40
|
+
instanceRef.current = instance
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
instanceRef.current = null
|
|
44
|
+
instance.destroy()
|
|
45
|
+
}
|
|
46
|
+
}, [midiAccess])
|
|
47
|
+
|
|
48
|
+
// Runs after every render: handlers come from props and are cheap to push,
|
|
49
|
+
// and updating in place leaves the listeners untouched.
|
|
14
50
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
onPitchBendEvent,
|
|
19
|
-
})
|
|
20
|
-
return () => instance.destroy()
|
|
21
|
-
}, [midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent])
|
|
51
|
+
latest.current = handlers
|
|
52
|
+
instanceRef.current?.update(handlers)
|
|
53
|
+
})
|
|
22
54
|
}
|
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
import { createMIDIMessage } from '@tremolo-ui/dom'
|
|
3
|
+
import { createMIDIMessage, type MIDIMessageInstance } from '@tremolo-ui/dom'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
7
|
+
*
|
|
8
|
+
* The handler is read fresh on every event, so writing it inline is fine: the
|
|
9
|
+
* listeners are attached once per `midiAccess` and stay attached, and devices
|
|
10
|
+
* plugged in later are picked up.
|
|
7
11
|
*/
|
|
8
12
|
export function useMIDIMessage(
|
|
9
13
|
midiAccess: MIDIAccess | null,
|
|
10
14
|
onMIDIMessage: (event: MIDIMessageEvent) => void,
|
|
11
15
|
) {
|
|
16
|
+
const latest = useRef(onMIDIMessage)
|
|
17
|
+
const instanceRef = useRef<MIDIMessageInstance | null>(null)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const instance = createMIDIMessage(midiAccess, (event) =>
|
|
21
|
+
latest.current(event),
|
|
22
|
+
)
|
|
23
|
+
instanceRef.current = instance
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
instanceRef.current = null
|
|
27
|
+
instance.destroy()
|
|
28
|
+
}
|
|
29
|
+
}, [midiAccess])
|
|
30
|
+
|
|
12
31
|
useEffect(() => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}, [midiAccess, onMIDIMessage])
|
|
32
|
+
latest.current = onMIDIMessage
|
|
33
|
+
})
|
|
16
34
|
}
|
package/src/hooks/useWheel.ts
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
import { createWheel } from '@tremolo-ui/dom'
|
|
4
|
-
|
|
5
|
-
import { useCallbackRef } from './useCallbackRef'
|
|
1
|
+
import { RefObject, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { createWheel, type WheelOptions } from '@tremolo-ui/dom'
|
|
4
|
+
|
|
5
|
+
import { useCallbackRef } from './_internal/useCallbackRef'
|
|
6
|
+
|
|
7
|
+
export interface UseWheelOptions extends WheelOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Listen on this element rather than on the one the returned ref callback is
|
|
10
|
+
* attached to, and ignore that callback.
|
|
11
|
+
*
|
|
12
|
+
* For a control made of several movable parts: a wheel event only reaches
|
|
13
|
+
* what the cursor is over, so a listener per part responds only while the
|
|
14
|
+
* cursor is over that same part. Listening on the element they share lets
|
|
15
|
+
* every part see the event, and each one decide whether it is the one to act.
|
|
16
|
+
*
|
|
17
|
+
* The element is read when the listener is attached, so a ref filled in by a
|
|
18
|
+
* parent is fine as long as the parent is above in the tree.
|
|
19
|
+
*/
|
|
20
|
+
target?: RefObject<Element | null>
|
|
21
|
+
}
|
|
6
22
|
|
|
7
23
|
/**
|
|
8
24
|
* Listen to wheel events on an element.
|
|
9
25
|
*
|
|
10
26
|
* The listener is not passive, so the handler may call `preventDefault()`.
|
|
11
27
|
*
|
|
12
|
-
* @returns a ref callback to attach to the element
|
|
28
|
+
* @returns a ref callback to attach to the element, unused when `target` is given
|
|
13
29
|
*/
|
|
14
30
|
export function useWheel<T extends Element>(
|
|
15
31
|
onWheel: (event: WheelEvent) => void,
|
|
32
|
+
{ requireFocus = false, target }: UseWheelOptions = {},
|
|
16
33
|
): (node: T | null) => void {
|
|
17
34
|
const wheelHandler = useCallbackRef(onWheel)
|
|
18
35
|
|
|
@@ -20,12 +37,15 @@ export function useWheel<T extends Element>(
|
|
|
20
37
|
const [node, setNode] = useState<T | null>(null)
|
|
21
38
|
|
|
22
39
|
useEffect(() => {
|
|
23
|
-
|
|
40
|
+
const element = target ? target.current : node
|
|
41
|
+
if (!element) return
|
|
24
42
|
|
|
25
|
-
const instance = createWheel(
|
|
43
|
+
const instance = createWheel(element, (event) => wheelHandler(event), {
|
|
44
|
+
requireFocus,
|
|
45
|
+
})
|
|
26
46
|
|
|
27
47
|
return () => instance.destroy()
|
|
28
|
-
}, [node, wheelHandler])
|
|
48
|
+
}, [node, target, wheelHandler, requireFocus])
|
|
29
49
|
|
|
30
50
|
return setNode
|
|
31
51
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,53 +1,61 @@
|
|
|
1
|
-
// For bundled CSS (@tremolo-ui/react/styles/index.css)
|
|
2
|
-
import './styles/global.css'
|
|
3
|
-
import './components/Knob/index.css'
|
|
4
|
-
import './components/NumberInput/index.css'
|
|
5
|
-
import './components/Piano/index.css'
|
|
6
|
-
import './components/PointsEditor/index.css'
|
|
7
|
-
import './components/Slider/index.css'
|
|
8
|
-
import './components/XYPad/index.css'
|
|
9
|
-
|
|
10
1
|
export {
|
|
11
2
|
AnimationCanvas,
|
|
12
|
-
type
|
|
3
|
+
type AnimationCanvasCommonProps,
|
|
4
|
+
type AnimationCanvasFixedProps,
|
|
13
5
|
type AnimationCanvasProps,
|
|
14
|
-
type
|
|
6
|
+
type AnimationCanvasResizableProps,
|
|
15
7
|
type DrawFunction,
|
|
16
8
|
type InitFunction,
|
|
17
|
-
type RelativeSizingProps,
|
|
18
9
|
} from './components/AnimationCanvas'
|
|
19
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
Knob,
|
|
12
|
+
useKnobContext,
|
|
13
|
+
type KnobContextValue,
|
|
14
|
+
type KnobProps,
|
|
15
|
+
type KnobMethods,
|
|
16
|
+
type KnobSVGRootProps,
|
|
17
|
+
type KnobThumbProps,
|
|
18
|
+
} from './components/Knob'
|
|
20
19
|
export {
|
|
21
20
|
NumberInput,
|
|
21
|
+
useNumberInputContext,
|
|
22
|
+
type NumberInputContextValue,
|
|
22
23
|
type NumberInputProps,
|
|
23
24
|
type NumberInputMethods,
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
type
|
|
25
|
+
type NumberInputInputFieldProps,
|
|
26
|
+
type NumberInputStepperProps,
|
|
27
|
+
type NumberInputIncrementStepperProps,
|
|
28
|
+
type NumberInputDecrementStepperProps,
|
|
27
29
|
} from './components/NumberInput'
|
|
28
30
|
export {
|
|
29
31
|
Piano,
|
|
30
|
-
getNoteRangeArray,
|
|
31
32
|
SHORTCUTS,
|
|
32
33
|
type PianoProps,
|
|
33
34
|
type PianoMethods,
|
|
34
|
-
type
|
|
35
|
-
type
|
|
36
|
-
type
|
|
35
|
+
type KeyState,
|
|
36
|
+
type KeyAttributes,
|
|
37
|
+
type CSSVariables,
|
|
37
38
|
type KeyboardShortcuts,
|
|
38
39
|
} from './components/Piano'
|
|
39
40
|
export {
|
|
40
41
|
PointsEditor,
|
|
42
|
+
usePointsEditorContext,
|
|
41
43
|
clampPoint,
|
|
42
44
|
type PointsEditorProps,
|
|
43
|
-
type
|
|
45
|
+
type PointsEditorContextValue,
|
|
46
|
+
type PointsEditorBackgroundProps,
|
|
47
|
+
type PointsEditorContainerProps,
|
|
48
|
+
type PointsEditorSelectionBoxProps,
|
|
49
|
+
type PointsEditorPointProps,
|
|
44
50
|
type PointBaseType,
|
|
45
51
|
} from './components/PointsEditor'
|
|
46
52
|
export {
|
|
47
53
|
Slider,
|
|
48
54
|
useSliderContext,
|
|
49
|
-
type
|
|
50
|
-
type
|
|
55
|
+
type SliderContextValue,
|
|
56
|
+
type SliderMarksOptionProps,
|
|
57
|
+
type MarksOptions,
|
|
58
|
+
type SliderMarksProps,
|
|
51
59
|
type SliderMethods,
|
|
52
60
|
type SliderProps,
|
|
53
61
|
type SliderThumbMethods,
|
|
@@ -56,18 +64,21 @@ export {
|
|
|
56
64
|
} from './components/Slider'
|
|
57
65
|
export {
|
|
58
66
|
XYPad,
|
|
67
|
+
useXYPadContext,
|
|
68
|
+
type XYPadContextValue,
|
|
59
69
|
type XYPadProps,
|
|
60
70
|
type XYPadMethods,
|
|
61
71
|
type XYPadAreaProps,
|
|
62
72
|
type XYPadThumbProps,
|
|
63
73
|
type XYPadThumbMethods,
|
|
64
|
-
type
|
|
74
|
+
type XY,
|
|
75
|
+
type XYInput,
|
|
65
76
|
} from './components/XYPad'
|
|
66
77
|
|
|
67
78
|
// hooks
|
|
68
79
|
export { useAnimationFrame } from './hooks/useAnimationFrame'
|
|
69
80
|
export { useDrag } from './hooks/useDrag'
|
|
70
|
-
export {
|
|
81
|
+
export { useDragValue, type UseDragValueOptions } from './hooks/useDragValue'
|
|
71
82
|
export { useEventListener } from './hooks/useEventListener'
|
|
72
83
|
export { useInterval } from './hooks/useInterval'
|
|
73
84
|
export { useLongPress } from './hooks/useLongPress'
|
|
@@ -75,8 +86,20 @@ export {
|
|
|
75
86
|
useMIDIAccess,
|
|
76
87
|
NOT_SUPPORTED,
|
|
77
88
|
PERMISSION_DENIED,
|
|
89
|
+
UNAVAILABLE,
|
|
78
90
|
type MIDIAccessError,
|
|
91
|
+
type MIDIAccessOptions,
|
|
79
92
|
} from './hooks/useMIDIAccess'
|
|
80
|
-
export {
|
|
93
|
+
export {
|
|
94
|
+
useMIDIInput,
|
|
95
|
+
PITCH_BEND_CENTER,
|
|
96
|
+
type MIDIInputHandlers,
|
|
97
|
+
} from './hooks/useMIDIInput'
|
|
81
98
|
export { useMIDIMessage } from './hooks/useMIDIMessage'
|
|
82
|
-
export { useWheel } from './hooks/useWheel'
|
|
99
|
+
export { useWheel, type UseWheelOptions } from './hooks/useWheel'
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
103
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
104
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
105
|
+
} from './input-event'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { InputEventOption, ModifierValue } from '@tremolo-ui/functions'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The keyboard amount used by Knob, NumberInput, Slider, and XYPad by default:
|
|
5
|
+
* 1 per press in the units of the value, and 0.1 with shift held. That is one
|
|
6
|
+
* `step` only while `step` is 1; a coarser `step` rounds 1 straight back, which
|
|
7
|
+
* `useCheckSteps` warns about.
|
|
8
|
+
*
|
|
9
|
+
* A modifier entry is not snapped to `step`, which is what lets the finer
|
|
10
|
+
* amount move at all — see `applyDelta` in `@tremolo-ui/functions`.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_KEYBOARD_OPTIONS: ModifierValue<InputEventOption> = {
|
|
13
|
+
default: ['raw', 1],
|
|
14
|
+
shift: ['raw', 0.1],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The wheel amount used by Knob, NumberInput, Slider, and XYPad by default.
|
|
19
|
+
*
|
|
20
|
+
* Browsers turn shift+wheel into horizontal scrolling, which empties `deltaY`
|
|
21
|
+
* and fills `deltaX`, so no modifier is bound here.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_WHEEL_OPTIONS: ModifierValue<InputEventOption> = ['raw', 1]
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The drag sensitivity used by value controls by default. Shift makes the
|
|
27
|
+
* same movement cover a tenth of the range, matching the arrow keys.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_DRAG_SENSITIVITY: ModifierValue<number> = {
|
|
30
|
+
default: 1,
|
|
31
|
+
shift: 0.1,
|
|
32
|
+
}
|