@tremolo-ui/react 0.1.4 → 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 +285 -155
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +80 -32
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +95 -60
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +97 -62
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +262 -139
- package/dist/index.js.map +1 -1
- package/package.json +14 -9
- package/src/components/AnimationCanvas/canvas.ts +16 -0
- package/src/components/AnimationCanvas/index.tsx +54 -71
- package/src/components/Knob/index.css +54 -23
- package/src/components/Knob/index.tsx +30 -25
- package/src/components/NumberInput/InternalInput.tsx +1 -1
- package/src/components/NumberInput/context.tsx +2 -1
- package/src/components/NumberInput/index.css +1 -0
- package/src/components/NumberInput/index.tsx +5 -5
- package/src/components/NumberInput/type.ts +58 -0
- package/src/components/Piano/context.tsx +1 -1
- package/src/components/Piano/index.css +24 -8
- package/src/components/Piano/index.tsx +254 -219
- package/src/components/Piano/key.tsx +3 -3
- package/src/components/Slider/Scale.tsx +1 -1
- package/src/components/Slider/ScaleOption.tsx +1 -1
- package/src/components/Slider/Track.tsx +2 -2
- package/src/components/Slider/index.tsx +3 -2
- package/src/components/Slider/type.ts +26 -0
- 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 +21 -13
- package/src/styles/{index.css → global.css} +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import React, { createContext, createRef, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useInsertionEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { clamp, isBlackKey, isWhiteKey, normalizeValue, noteKey, noteKeys, radian, rawValue, stepValue, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
|
|
4
|
+
import { clamp, decimalPart, isBlackKey, isWhiteKey, normalizeValue, noteKey, noteKeys, radian, rawValue, stepValue, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
|
|
5
5
|
import { createStore, useStore } from "zustand";
|
|
6
|
-
import { parseValue } from "@tremolo-ui/functions/NumberInput";
|
|
7
|
-
import { generateOptionsList } from "@tremolo-ui/functions/Slider";
|
|
8
6
|
|
|
9
7
|
//#region src/components/AnimationCanvas/canvas.ts
|
|
10
8
|
const drawingState = [
|
|
@@ -27,9 +25,6 @@ const drawingState = [
|
|
|
27
25
|
"direction",
|
|
28
26
|
"imageSmoothingEnabled"
|
|
29
27
|
];
|
|
30
|
-
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/components/AnimationCanvas/index.tsx
|
|
33
28
|
function setDprConfig(canvas, context, width, height, dpr) {
|
|
34
29
|
canvas.width = width * dpr;
|
|
35
30
|
canvas.height = height * dpr;
|
|
@@ -38,7 +33,10 @@ function setDprConfig(canvas, context, width, height, dpr) {
|
|
|
38
33
|
canvas.style.width = `${width}px`;
|
|
39
34
|
canvas.style.height = `${height}px`;
|
|
40
35
|
}
|
|
41
|
-
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/components/AnimationCanvas/index.tsx
|
|
39
|
+
function AnimationCanvas({ draw, init, animate = true, options, width: _width = 100, height: _height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(),...props }) {
|
|
42
40
|
const canvasRef = useRef(null);
|
|
43
41
|
const memoCanvasRef = useRef(null);
|
|
44
42
|
const reqIdRef = useRef(-1);
|
|
@@ -46,27 +44,40 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
|
|
|
46
44
|
const heightRef = useRef(0);
|
|
47
45
|
const deltaMemoRef = useRef(-1);
|
|
48
46
|
const startTimeRef = useRef(-1);
|
|
49
|
-
const loop = useCallback((context, width
|
|
47
|
+
const loop = useCallback((context, width, height, count) => {
|
|
50
48
|
const now = performance.now();
|
|
51
49
|
const deltaTime = now - deltaMemoRef.current;
|
|
52
50
|
const elapsedTime = now - startTimeRef.current;
|
|
53
51
|
deltaMemoRef.current = now;
|
|
54
|
-
reqIdRef.current = requestAnimationFrame(() => loop(context, width
|
|
52
|
+
if (animate) reqIdRef.current = requestAnimationFrame(() => loop(context, width, height, count + 1));
|
|
55
53
|
draw(context, {
|
|
56
|
-
width: width
|
|
57
|
-
height: height
|
|
54
|
+
width: width.current,
|
|
55
|
+
height: height.current,
|
|
58
56
|
count: count + 1,
|
|
59
57
|
deltaTime,
|
|
60
58
|
elapsedTime,
|
|
61
59
|
fps: 1e3 / deltaTime
|
|
62
60
|
});
|
|
63
|
-
}, [draw]);
|
|
61
|
+
}, [draw, animate]);
|
|
64
62
|
useEffect(() => {
|
|
65
63
|
if (!canvasRef.current) return;
|
|
66
64
|
const canvas = canvasRef.current;
|
|
67
65
|
const context = canvas.getContext("2d", options);
|
|
68
66
|
if (!context) throw new Error("Cannot get canvas context.");
|
|
69
67
|
const dpr = globalThis.devicePixelRatio;
|
|
68
|
+
const firstRendering = (width, height) => {
|
|
69
|
+
setDprConfig(canvas, context, width, height, dpr);
|
|
70
|
+
widthRef.current = width;
|
|
71
|
+
heightRef.current = height;
|
|
72
|
+
if (init) init(context, {
|
|
73
|
+
width,
|
|
74
|
+
height
|
|
75
|
+
});
|
|
76
|
+
const now = performance.now();
|
|
77
|
+
deltaMemoRef.current = now;
|
|
78
|
+
startTimeRef.current = now;
|
|
79
|
+
loop(context, widthRef, heightRef, -1);
|
|
80
|
+
};
|
|
70
81
|
if (relativeSize) {
|
|
71
82
|
const parent = canvas.parentElement;
|
|
72
83
|
if (!parent) throw new Error("Canvas doesn't have a parent element.");
|
|
@@ -74,52 +85,37 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
|
|
|
74
85
|
const memoContext = memoCanvas?.getContext("2d", options);
|
|
75
86
|
const ro = new ResizeObserver((entries) => {
|
|
76
87
|
for (const entry of entries) {
|
|
88
|
+
const w = entry.contentRect.width;
|
|
89
|
+
const h = entry.contentRect.height;
|
|
77
90
|
const contextMemo = {};
|
|
78
|
-
for (const prop of drawingState) contextMemo[prop] = context[prop];
|
|
79
|
-
const w$1 = entry.contentRect.width;
|
|
80
|
-
const h$1 = entry.contentRect.height;
|
|
81
91
|
if (reduceFlickering && memoCanvas && memoContext) {
|
|
82
|
-
|
|
83
|
-
memoCanvas.
|
|
92
|
+
for (const prop of drawingState) contextMemo[prop] = context[prop];
|
|
93
|
+
memoCanvas.width = w * dpr;
|
|
94
|
+
memoCanvas.height = h * dpr;
|
|
84
95
|
memoContext.scale(1 / dpr, 1 / dpr);
|
|
85
96
|
if (canvas.width > 0 && canvas.height > 0) memoContext.drawImage(canvas, 0, 0);
|
|
86
97
|
}
|
|
87
|
-
setDprConfig(canvas, context, w
|
|
88
|
-
widthRef.current = w
|
|
89
|
-
heightRef.current = h
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
setDprConfig(canvas, context, w, h, dpr);
|
|
99
|
+
widthRef.current = w;
|
|
100
|
+
heightRef.current = h;
|
|
101
|
+
if (reduceFlickering && memoContext && memoCanvas && memoCanvas.width > 0 && memoCanvas.height > 0) {
|
|
102
|
+
for (const prop of drawingState) context[prop] = contextMemo[prop];
|
|
103
|
+
context.drawImage(memoContext.canvas, 0, 0);
|
|
104
|
+
}
|
|
105
|
+
if (!animate) loop(context, widthRef, heightRef, -1);
|
|
92
106
|
}
|
|
93
107
|
});
|
|
94
108
|
ro.observe(parent);
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
if (init) init(context, {
|
|
99
|
-
width: widthRef.current,
|
|
100
|
-
height: heightRef.current
|
|
101
|
-
});
|
|
102
|
-
const now = performance.now();
|
|
103
|
-
deltaMemoRef.current = now;
|
|
104
|
-
startTimeRef.current = now;
|
|
105
|
-
loop(context, widthRef, heightRef, -1);
|
|
109
|
+
const width = parent.clientWidth;
|
|
110
|
+
const height = parent.clientHeight;
|
|
111
|
+
firstRendering(width, height);
|
|
106
112
|
return () => {
|
|
107
113
|
if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
|
|
108
114
|
ro.disconnect();
|
|
109
115
|
};
|
|
110
116
|
} else {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
widthRef.current = rect.width;
|
|
114
|
-
heightRef.current = rect.height;
|
|
115
|
-
if (init) init(context, {
|
|
116
|
-
width: widthRef.current,
|
|
117
|
-
height: heightRef.current
|
|
118
|
-
});
|
|
119
|
-
const now = performance.now();
|
|
120
|
-
deltaMemoRef.current = now;
|
|
121
|
-
startTimeRef.current = now;
|
|
122
|
-
loop(context, widthRef, heightRef, -1);
|
|
117
|
+
const { width, height } = canvas.getBoundingClientRect();
|
|
118
|
+
firstRendering(width, height);
|
|
123
119
|
return () => {
|
|
124
120
|
if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
|
|
125
121
|
};
|
|
@@ -129,12 +125,13 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
|
|
|
129
125
|
init,
|
|
130
126
|
options,
|
|
131
127
|
reduceFlickering,
|
|
132
|
-
relativeSize
|
|
128
|
+
relativeSize,
|
|
129
|
+
animate
|
|
133
130
|
]);
|
|
134
131
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("canvas", {
|
|
135
132
|
className: clsx("tremolo-animation-canvas", className),
|
|
136
|
-
width: relativeSize ? 0 :
|
|
137
|
-
height: relativeSize ? 0 :
|
|
133
|
+
width: relativeSize ? 0 : _width,
|
|
134
|
+
height: relativeSize ? 0 : _height,
|
|
138
135
|
ref: canvasRef,
|
|
139
136
|
onContextMenu,
|
|
140
137
|
...props
|
|
@@ -147,8 +144,8 @@ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relat
|
|
|
147
144
|
//#endregion
|
|
148
145
|
//#region src/hooks/useCallbackRef.ts
|
|
149
146
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
147
|
+
* Internal
|
|
148
|
+
* @private
|
|
152
149
|
*/
|
|
153
150
|
function useCallbackRef(callback, deps = []) {
|
|
154
151
|
const callbackRef = useRef(() => {
|
|
@@ -179,8 +176,7 @@ function useEventListener(target, event, handler, options) {
|
|
|
179
176
|
handler
|
|
180
177
|
]);
|
|
181
178
|
return () => {
|
|
182
|
-
|
|
183
|
-
node?.removeEventListener(event, listener, options);
|
|
179
|
+
(typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
|
|
184
180
|
};
|
|
185
181
|
}
|
|
186
182
|
|
|
@@ -205,6 +201,7 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
|
205
201
|
//#endregion
|
|
206
202
|
//#region src/hooks/useDrag.ts
|
|
207
203
|
/**
|
|
204
|
+
* @category hooks
|
|
208
205
|
* @returns [refCallback, pointerDownHandler]
|
|
209
206
|
*/
|
|
210
207
|
function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -271,9 +268,7 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
|
|
|
271
268
|
const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
|
|
272
269
|
const onDrag = useCallback((_x, y) => {
|
|
273
270
|
if (!onChange) return;
|
|
274
|
-
|
|
275
|
-
const v2 = clamp(stepValue(v, step), min, max);
|
|
276
|
-
onChange(v2);
|
|
271
|
+
onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
277
272
|
}, [
|
|
278
273
|
max,
|
|
279
274
|
min,
|
|
@@ -293,10 +288,8 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
|
|
|
293
288
|
event.preventDefault();
|
|
294
289
|
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
295
290
|
let v;
|
|
296
|
-
if (keyboard[0] == "normalized")
|
|
297
|
-
|
|
298
|
-
v = rawValue(n + x, min, max, skew);
|
|
299
|
-
} else v = value + x;
|
|
291
|
+
if (keyboard[0] == "normalized") v = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
292
|
+
else v = value + x;
|
|
300
293
|
onChange(clamp(stepValue(v, step), min, max));
|
|
301
294
|
}
|
|
302
295
|
}, [
|
|
@@ -323,10 +316,8 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
|
|
|
323
316
|
event.preventDefault();
|
|
324
317
|
const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
325
318
|
let v;
|
|
326
|
-
if (wheel[0] == "normalized")
|
|
327
|
-
|
|
328
|
-
v = rawValue(n + x, min, max, skew);
|
|
329
|
-
} else v = value + x;
|
|
319
|
+
if (wheel[0] == "normalized") v = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
320
|
+
else v = value + x;
|
|
330
321
|
if (onChange) onChange(clamp(stepValue(v, step), min, max));
|
|
331
322
|
}, { passive: false }, [
|
|
332
323
|
wheel,
|
|
@@ -438,20 +429,12 @@ function DragObserver(props) {
|
|
|
438
429
|
*
|
|
439
430
|
* @category Knob
|
|
440
431
|
*/
|
|
441
|
-
const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = min, startValue = min, size = 50, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, activeLine, inactiveLine, thumb, thumbLine, onChange, onKeyDown, onPointerDown, onDoubleClick, className, classes,...props },
|
|
432
|
+
const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = min, startValue = min, size = 50, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, activeLine, inactiveLine, thumb, thumbLine, thumbSize = 84, thumbLineWeight = 6, thumbLineLength = 35, lineWeight = 6, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, classes,...props }, forwardedRef) => {
|
|
442
433
|
const valueRef = useRef(0);
|
|
443
434
|
const elmRef = useRef(null);
|
|
444
|
-
const padding = 8;
|
|
445
|
-
const thumbLineWeight = 6;
|
|
446
|
-
const thumbLineLength = 35;
|
|
447
|
-
const lineWeight = size * .06;
|
|
448
|
-
const p = normalizeValue(value, min, max, skew);
|
|
449
|
-
const s = normalizeValue(startValue, min, max, skew);
|
|
450
435
|
const onDrag = useCallback((_x, y) => {
|
|
451
436
|
if (!onChange || readonly) return;
|
|
452
|
-
|
|
453
|
-
const v2 = clamp(stepValue(v, step), min, max);
|
|
454
|
-
onChange(v2);
|
|
437
|
+
onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
455
438
|
}, [
|
|
456
439
|
max,
|
|
457
440
|
min,
|
|
@@ -462,10 +445,8 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
462
445
|
]);
|
|
463
446
|
const updateValueByEvent = useCallback((eventType, x) => {
|
|
464
447
|
let newValue;
|
|
465
|
-
if (eventType == "normalized")
|
|
466
|
-
|
|
467
|
-
newValue = rawValue(n + x, min, max, skew);
|
|
468
|
-
} else newValue = value + x;
|
|
448
|
+
if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
449
|
+
else newValue = value + x;
|
|
469
450
|
return clamp(stepValue(newValue, step), min, max);
|
|
470
451
|
}, [
|
|
471
452
|
max,
|
|
@@ -515,7 +496,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
515
496
|
readonly,
|
|
516
497
|
updateValueByEvent
|
|
517
498
|
]);
|
|
518
|
-
useImperativeHandle(
|
|
499
|
+
useImperativeHandle(forwardedRef, () => {
|
|
519
500
|
return {
|
|
520
501
|
focus() {
|
|
521
502
|
elmRef.current?.focus();
|
|
@@ -525,11 +506,15 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
525
506
|
}
|
|
526
507
|
};
|
|
527
508
|
}, []);
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
const
|
|
509
|
+
const viewBoxSize = 100;
|
|
510
|
+
const center = viewBoxSize / 2;
|
|
511
|
+
const padding = (viewBoxSize - clamp(thumbSize, 0, 100)) / 2;
|
|
512
|
+
const p = normalizeValue(value, min, max, skew);
|
|
513
|
+
const s = normalizeValue(startValue, min, max, skew);
|
|
514
|
+
const r1 = -angleRange / 2;
|
|
515
|
+
const r2 = r1 + Math.min(p, s) * angleRange;
|
|
516
|
+
const r3 = r1 + Math.max(p, s) * angleRange;
|
|
517
|
+
const r4 = angleRange / 2;
|
|
533
518
|
const x1 = center + center * Math.cos(radian(r1 - 90));
|
|
534
519
|
const y1 = center + center * Math.sin(radian(r1 - 90));
|
|
535
520
|
const x2 = center + center * Math.cos(radian(r2 - 90));
|
|
@@ -539,12 +524,13 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
539
524
|
const x4 = center + center * Math.cos(radian(r4 - 90));
|
|
540
525
|
const y4 = center + center * Math.sin(radian(r4 - 90));
|
|
541
526
|
return /* @__PURE__ */ jsxs("svg", {
|
|
542
|
-
className: clsx("tremolo-knob", className),
|
|
543
527
|
ref: (div) => {
|
|
544
528
|
elmRef.current = div;
|
|
545
529
|
wheelRefCallback(div);
|
|
546
530
|
touchMoveRefCallback(div);
|
|
547
531
|
},
|
|
532
|
+
className: clsx("tremolo-knob", className),
|
|
533
|
+
viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
|
|
548
534
|
width: size,
|
|
549
535
|
height: size,
|
|
550
536
|
tabIndex: 0,
|
|
@@ -594,7 +580,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
594
580
|
children: [/* @__PURE__ */ jsx("circle", {
|
|
595
581
|
cx: "50%",
|
|
596
582
|
cy: "50%",
|
|
597
|
-
r: `${
|
|
583
|
+
r: `${thumbSize / 2}%`,
|
|
598
584
|
fill: thumb || "currentColor"
|
|
599
585
|
}), /* @__PURE__ */ jsx("line", {
|
|
600
586
|
className: clsx("tremolo-knob-thumb-line", classes?.thumbLine),
|
|
@@ -605,7 +591,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
605
591
|
stroke: thumbLine || "currentColor",
|
|
606
592
|
strokeWidth: `${thumbLineWeight}%`,
|
|
607
593
|
style: {
|
|
608
|
-
transform: `rotate(${
|
|
594
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
609
595
|
transformOrigin: "50% 50%"
|
|
610
596
|
}
|
|
611
597
|
})]
|
|
@@ -614,6 +600,47 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
614
600
|
});
|
|
615
601
|
});
|
|
616
602
|
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/components/NumberInput/type.ts
|
|
605
|
+
function selectUnit(units, value) {
|
|
606
|
+
let i = 0;
|
|
607
|
+
for (; i < units.length; i++) if (Math.abs(units[i][1]) > Math.abs(value)) break;
|
|
608
|
+
return units[Math.max(0, i - 1)];
|
|
609
|
+
}
|
|
610
|
+
function parseValue(inputString, units, digit) {
|
|
611
|
+
const str = inputString.trim();
|
|
612
|
+
if (!units || typeof units == "string") {
|
|
613
|
+
const m$1 = str.match(/-?\d+(\.\d+)?/);
|
|
614
|
+
let v = Number(m$1?.[0] ?? "0");
|
|
615
|
+
v = isNaN(v) ? 0 : v;
|
|
616
|
+
return {
|
|
617
|
+
rawValue: v,
|
|
618
|
+
formatValue: (digit != void 0 ? v.toFixed(digit) : v) + (units ?? ""),
|
|
619
|
+
unit: units ?? ""
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const unitList = units.map(([u, _s]) => u);
|
|
623
|
+
const scaleList = units.map(([_u, s]) => s);
|
|
624
|
+
let rawValue$1 = 0;
|
|
625
|
+
let unit = unitList[0];
|
|
626
|
+
let formatValue = (digit != void 0 ? rawValue$1.toFixed(digit) : rawValue$1) + unit;
|
|
627
|
+
const m = str.match(/^(-?\d+(\.\d+)?)\s*(\w*)$/);
|
|
628
|
+
if (m) {
|
|
629
|
+
rawValue$1 = Number(m[1]) || 0;
|
|
630
|
+
const uIndex = unitList.indexOf(m[3]);
|
|
631
|
+
rawValue$1 *= uIndex != -1 ? scaleList[uIndex] : 1;
|
|
632
|
+
const [u, scale] = selectUnit(units, rawValue$1);
|
|
633
|
+
const v = rawValue$1 / scale;
|
|
634
|
+
formatValue = (digit != void 0 ? v.toFixed(digit) : v) + u;
|
|
635
|
+
unit = u;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
rawValue: rawValue$1,
|
|
639
|
+
formatValue,
|
|
640
|
+
unit
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
617
644
|
//#endregion
|
|
618
645
|
//#region src/components/NumberInput/context.tsx
|
|
619
646
|
function safeClamp(value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
|
|
@@ -637,8 +664,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
637
664
|
valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
|
|
638
665
|
increment: () => set((state) => {
|
|
639
666
|
if (state.readonly) return {};
|
|
640
|
-
|
|
641
|
-
let next = current + (state.step ?? 1);
|
|
667
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue + (state.step ?? 1);
|
|
642
668
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
643
669
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
644
670
|
state?.onChange?.(next, v);
|
|
@@ -649,8 +675,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
649
675
|
}),
|
|
650
676
|
decrement: () => set((state) => {
|
|
651
677
|
if (state.readonly) return {};
|
|
652
|
-
|
|
653
|
-
let next = current - (state.step ?? 1);
|
|
678
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue - (state.step ?? 1);
|
|
654
679
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
655
680
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
656
681
|
state?.onChange?.(next, v);
|
|
@@ -722,8 +747,7 @@ const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnE
|
|
|
722
747
|
let newValue;
|
|
723
748
|
if (eventType == "normalized") {
|
|
724
749
|
if (!min || !max) throw new Error("required parameter: min, max");
|
|
725
|
-
|
|
726
|
-
newValue = rawValue(n + x, min, max);
|
|
750
|
+
newValue = rawValue(normalizeValue(valueAsNumber, min, max) + x, min, max);
|
|
727
751
|
return clamp(stepValue(newValue, step), min, max);
|
|
728
752
|
} else {
|
|
729
753
|
newValue = valueAsNumber + x;
|
|
@@ -839,6 +863,9 @@ function Stepper({ dynamic = true, children, className,...props }) {
|
|
|
839
863
|
|
|
840
864
|
//#endregion
|
|
841
865
|
//#region src/hooks/useInterval.ts
|
|
866
|
+
/**
|
|
867
|
+
* @category hooks
|
|
868
|
+
*/
|
|
842
869
|
function useInterval(callback, delay) {
|
|
843
870
|
const savedCallback = useRef(callback);
|
|
844
871
|
useEffect(() => {
|
|
@@ -857,6 +884,9 @@ function useInterval(callback, delay) {
|
|
|
857
884
|
|
|
858
885
|
//#endregion
|
|
859
886
|
//#region src/hooks/useLongPress.ts
|
|
887
|
+
/**
|
|
888
|
+
* @category hooks
|
|
889
|
+
*/
|
|
860
890
|
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
861
891
|
const [pressed, setPressed] = useState(false);
|
|
862
892
|
const [delay, setDelay] = useState(initialDelay);
|
|
@@ -882,8 +912,7 @@ function IncrementStepper({ size = 12, children, className,...props }) {
|
|
|
882
912
|
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
883
913
|
const readonly = useNumberInputContext((s) => s.readonly);
|
|
884
914
|
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
885
|
-
const
|
|
886
|
-
const press = useLongPress(increment);
|
|
915
|
+
const press = useLongPress(useNumberInputContext((s) => s.increment));
|
|
887
916
|
return /* @__PURE__ */ jsx("div", {
|
|
888
917
|
className: clsx("tremolo-number-input-increment-stepper", className),
|
|
889
918
|
role: "button",
|
|
@@ -914,8 +943,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
|
|
|
914
943
|
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
915
944
|
const readonly = useNumberInputContext((s) => s.readonly);
|
|
916
945
|
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
917
|
-
const
|
|
918
|
-
const press = useLongPress(decrement);
|
|
946
|
+
const press = useLongPress(useNumberInputContext((s) => s.decrement));
|
|
919
947
|
return /* @__PURE__ */ jsx("div", {
|
|
920
948
|
className: clsx("tremolo-number-input-decrement-stepper", className),
|
|
921
949
|
role: "button",
|
|
@@ -944,7 +972,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
|
|
|
944
972
|
* Input with some useful functions for entering numerical values.
|
|
945
973
|
* @category NumberInput
|
|
946
974
|
*/
|
|
947
|
-
const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props },
|
|
975
|
+
const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props }, forwardedRef) => {
|
|
948
976
|
const colors = { "--active-color": activeColor };
|
|
949
977
|
return /* @__PURE__ */ jsx(NumberInputProvider, {
|
|
950
978
|
value: String(value),
|
|
@@ -963,7 +991,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
|
|
|
963
991
|
"data-stepper": !!children,
|
|
964
992
|
"data-variant": variant,
|
|
965
993
|
children: [/* @__PURE__ */ jsx(InternalInput, {
|
|
966
|
-
ref,
|
|
994
|
+
ref: forwardedRef,
|
|
967
995
|
readonly,
|
|
968
996
|
disabled,
|
|
969
997
|
selectWithFocus,
|
|
@@ -984,6 +1012,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
|
|
|
984
1012
|
//#endregion
|
|
985
1013
|
//#region src/hooks/useDragWithElement.ts
|
|
986
1014
|
/**
|
|
1015
|
+
* @category hooks
|
|
987
1016
|
* @returns [refCallback, pointerDownHandler]
|
|
988
1017
|
*/
|
|
989
1018
|
function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -1063,9 +1092,7 @@ function usePianoContext(selector) {
|
|
|
1063
1092
|
//#region src/components/Piano/KeyLabel.tsx
|
|
1064
1093
|
/** @category Piano */
|
|
1065
1094
|
function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
|
|
1066
|
-
const
|
|
1067
|
-
const noteRangeArray = getNoteRangeArray(noteRange);
|
|
1068
|
-
const index = noteRangeArray.indexOf(__note);
|
|
1095
|
+
const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
|
|
1069
1096
|
const content = label ? label(__note, index) : __label && __label(__note, index);
|
|
1070
1097
|
return (content != void 0 || content != null) && /* @__PURE__ */ jsx("div", {
|
|
1071
1098
|
className: clsx("tremolo-piano-key-label-wrapper", wrapperClassName),
|
|
@@ -1091,8 +1118,7 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
|
|
|
1091
1118
|
const onPlayNote = usePianoContext((s) => s.onPlayNote);
|
|
1092
1119
|
const onStopNote = usePianoContext((s) => s.onStopNote);
|
|
1093
1120
|
const label = usePianoContext((s) => s.label);
|
|
1094
|
-
const
|
|
1095
|
-
const position = notePosition(noteNumber);
|
|
1121
|
+
const position = usePianoContext((s) => s.notePosition)(noteNumber);
|
|
1096
1122
|
const disabled = noteNumber > midiMax;
|
|
1097
1123
|
const colors = {
|
|
1098
1124
|
"--bg": bg,
|
|
@@ -1102,10 +1128,10 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
|
|
|
1102
1128
|
};
|
|
1103
1129
|
useImperativeHandle(ref, () => {
|
|
1104
1130
|
return {
|
|
1105
|
-
play() {
|
|
1131
|
+
play(velocity) {
|
|
1106
1132
|
if (disabled) return;
|
|
1107
1133
|
setPlayed(true);
|
|
1108
|
-
if (onPlayNote) onPlayNote(noteNumber);
|
|
1134
|
+
if (onPlayNote) onPlayNote(noteNumber, velocity);
|
|
1109
1135
|
},
|
|
1110
1136
|
stop() {
|
|
1111
1137
|
setPlayed(false);
|
|
@@ -1196,12 +1222,9 @@ const SHORTCUTS = { HOME_ROW: { keys: [
|
|
|
1196
1222
|
//#endregion
|
|
1197
1223
|
//#region src/components/Piano/index.tsx
|
|
1198
1224
|
/**
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1201
|
-
* TODO:
|
|
1202
|
-
* - add scale highlight
|
|
1225
|
+
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
1226
|
+
* @category Piano
|
|
1203
1227
|
*/
|
|
1204
|
-
/** @category Piano */
|
|
1205
1228
|
function getNoteRangeArray(noteRange) {
|
|
1206
1229
|
return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
1207
1230
|
}
|
|
@@ -1210,7 +1233,7 @@ const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
|
|
|
1210
1233
|
* Customizable piano component.
|
|
1211
1234
|
* @category Piano
|
|
1212
1235
|
*/
|
|
1213
|
-
|
|
1236
|
+
const Piano = forwardRef(({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }, forwardedRef) => {
|
|
1214
1237
|
const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth);
|
|
1215
1238
|
const keyRefs = useRef([]);
|
|
1216
1239
|
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = createRef();
|
|
@@ -1281,9 +1304,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1281
1304
|
]);
|
|
1282
1305
|
const onDrag = useCallback((perX, perY) => {
|
|
1283
1306
|
if (!pianoRef.current) return;
|
|
1284
|
-
const
|
|
1285
|
-
const y = perY * pianoRef.current.clientHeight;
|
|
1286
|
-
const note = getHitKeyIndex(x, y);
|
|
1307
|
+
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1287
1308
|
const index = noteRangeArray.indexOf(note);
|
|
1288
1309
|
if (index == -1) return;
|
|
1289
1310
|
if (hitKeyIndex.current != index) {
|
|
@@ -1337,6 +1358,25 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1337
1358
|
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1338
1359
|
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1339
1360
|
});
|
|
1361
|
+
useImperativeHandle(forwardedRef, () => {
|
|
1362
|
+
return {
|
|
1363
|
+
playNote(note, velocity) {
|
|
1364
|
+
const index = noteRangeArray.indexOf(note);
|
|
1365
|
+
if (index != -1) {
|
|
1366
|
+
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
|
|
1367
|
+
} else onPlayNote?.(note, velocity);
|
|
1368
|
+
},
|
|
1369
|
+
stopNote(note) {
|
|
1370
|
+
const index = noteRangeArray.indexOf(note);
|
|
1371
|
+
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1372
|
+
else onStopNote?.(note);
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
}, [
|
|
1376
|
+
noteRangeArray,
|
|
1377
|
+
onPlayNote,
|
|
1378
|
+
onStopNote
|
|
1379
|
+
]);
|
|
1340
1380
|
return /* @__PURE__ */ jsx(PianoProvider, {
|
|
1341
1381
|
notePosition,
|
|
1342
1382
|
noteRange,
|
|
@@ -1373,7 +1413,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1373
1413
|
}, note))
|
|
1374
1414
|
})
|
|
1375
1415
|
});
|
|
1376
|
-
}
|
|
1416
|
+
});
|
|
1377
1417
|
|
|
1378
1418
|
//#endregion
|
|
1379
1419
|
//#region src/components/Slider/context.tsx
|
|
@@ -1461,6 +1501,22 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
|
|
|
1461
1501
|
});
|
|
1462
1502
|
}
|
|
1463
1503
|
|
|
1504
|
+
//#endregion
|
|
1505
|
+
//#region src/components/Slider/type.ts
|
|
1506
|
+
function generateOptionsList(options, min, max, step) {
|
|
1507
|
+
const optionsList = [];
|
|
1508
|
+
const per = options[0] == "step" ? step : options[0];
|
|
1509
|
+
const count = Math.floor(max / per) - Math.ceil(min / per) + 1;
|
|
1510
|
+
for (let i = 0; i < count; i++) {
|
|
1511
|
+
const value = toFixed(per * (Math.ceil(min / per) + i), decimalPart(per)?.length);
|
|
1512
|
+
optionsList.push({
|
|
1513
|
+
value,
|
|
1514
|
+
type: options[1]
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
return optionsList;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1464
1520
|
//#endregion
|
|
1465
1521
|
//#region src/components/Slider/Scale.tsx
|
|
1466
1522
|
/** @category Slider */
|
|
@@ -1551,7 +1607,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1551
1607
|
"data-vertical": vertical,
|
|
1552
1608
|
style: !defaultStyle ? style : {
|
|
1553
1609
|
...colors,
|
|
1554
|
-
background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive
|
|
1610
|
+
background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
|
|
1555
1611
|
borderRadius: styleHelper(thickness, "/", 2),
|
|
1556
1612
|
width: !vertical ? length : thickness,
|
|
1557
1613
|
height: vertical ? length : thickness,
|
|
@@ -1568,7 +1624,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1568
1624
|
* Customizable slider
|
|
1569
1625
|
* @category Slider
|
|
1570
1626
|
*/
|
|
1571
|
-
const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown,...props },
|
|
1627
|
+
const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown,...props }, forwardedRef) => {
|
|
1572
1628
|
const trackElementRef = useRef(null);
|
|
1573
1629
|
const thumbRef = useRef(null);
|
|
1574
1630
|
const p = toFixed(normalizeValue(value, min, max, skew) * 100);
|
|
@@ -1588,9 +1644,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1588
1644
|
const onDrag = useCallback((nx, ny) => {
|
|
1589
1645
|
if (!onChange || readonly) return;
|
|
1590
1646
|
const n = vertical ? ny : nx;
|
|
1591
|
-
|
|
1592
|
-
const v2 = clamp(stepValue(v, step), min, max);
|
|
1593
|
-
onChange(v2);
|
|
1647
|
+
onChange(clamp(stepValue(rawValue(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
|
|
1594
1648
|
}, [
|
|
1595
1649
|
displayReversed,
|
|
1596
1650
|
max,
|
|
@@ -1603,10 +1657,8 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1603
1657
|
]);
|
|
1604
1658
|
const updateValueByEvent = useCallback((eventType, x) => {
|
|
1605
1659
|
let newValue;
|
|
1606
|
-
if (eventType == "normalized")
|
|
1607
|
-
|
|
1608
|
-
newValue = rawValue(n + x, min, max, skew);
|
|
1609
|
-
} else newValue = value + x;
|
|
1660
|
+
if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
1661
|
+
else newValue = value + x;
|
|
1610
1662
|
return clamp(stepValue(newValue, step), min, max);
|
|
1611
1663
|
}, [
|
|
1612
1664
|
max,
|
|
@@ -1669,7 +1721,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1669
1721
|
onChange,
|
|
1670
1722
|
updateValueByEvent
|
|
1671
1723
|
]);
|
|
1672
|
-
useImperativeHandle(
|
|
1724
|
+
useImperativeHandle(forwardedRef, () => {
|
|
1673
1725
|
return {
|
|
1674
1726
|
focus() {
|
|
1675
1727
|
thumbRef.current?.focus();
|
|
@@ -1753,11 +1805,10 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1753
1805
|
/** @category WheelObserver */
|
|
1754
1806
|
function WheelObserver(props) {
|
|
1755
1807
|
const { children, onWheel, as: Component = "div",...attributes } = props;
|
|
1756
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
1757
|
-
if (onWheel) onWheel(event);
|
|
1758
|
-
}, { passive: false }, [onWheel]);
|
|
1759
1808
|
return /* @__PURE__ */ jsx(Component, {
|
|
1760
|
-
ref:
|
|
1809
|
+
ref: useRefCallbackEvent("wheel", (event) => {
|
|
1810
|
+
if (onWheel) onWheel(event);
|
|
1811
|
+
}, { passive: false }, [onWheel]),
|
|
1761
1812
|
...attributes,
|
|
1762
1813
|
children
|
|
1763
1814
|
});
|
|
@@ -1830,7 +1881,7 @@ const defaultValueOptions = {
|
|
|
1830
1881
|
* Simple XYPad
|
|
1831
1882
|
* @category XYPad
|
|
1832
1883
|
*/
|
|
1833
|
-
const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props },
|
|
1884
|
+
const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
|
|
1834
1885
|
const x = useMemo(() => {
|
|
1835
1886
|
return {
|
|
1836
1887
|
...defaultValueOptions,
|
|
@@ -1861,9 +1912,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
1861
1912
|
if (!onChange || readonly) return;
|
|
1862
1913
|
const vx = rawValue(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
|
|
1863
1914
|
const vy = rawValue(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
|
|
1864
|
-
|
|
1865
|
-
const newY = clamp(stepValue(vy, y.step), y.min, y.max);
|
|
1866
|
-
onChange(newX, newY);
|
|
1915
|
+
onChange(clamp(stepValue(vx, x.step), x.min, x.max), clamp(stepValue(vy, y.step), y.min, y.max));
|
|
1867
1916
|
}, [
|
|
1868
1917
|
onChange,
|
|
1869
1918
|
readonly,
|
|
@@ -1880,10 +1929,8 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
1880
1929
|
]);
|
|
1881
1930
|
const updateValueByEvent = useCallback((eventType, target, x$1) => {
|
|
1882
1931
|
let v;
|
|
1883
|
-
if (eventType == "normalized")
|
|
1884
|
-
|
|
1885
|
-
v = rawValue(n + x$1, target.min, target.max, target.skew);
|
|
1886
|
-
} else v = target.value + x$1;
|
|
1932
|
+
if (eventType == "normalized") v = rawValue(normalizeValue(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
|
|
1933
|
+
else v = target.value + x$1;
|
|
1887
1934
|
return clamp(stepValue(v, target.step), target.min, target.max);
|
|
1888
1935
|
}, []);
|
|
1889
1936
|
const handleKeyDown = useCallback((event) => {
|
|
@@ -1950,7 +1997,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
1950
1997
|
readonly,
|
|
1951
1998
|
updateValueByEvent
|
|
1952
1999
|
]);
|
|
1953
|
-
useImperativeHandle(
|
|
2000
|
+
useImperativeHandle(forwardedRef, () => {
|
|
1954
2001
|
return {
|
|
1955
2002
|
focus() {
|
|
1956
2003
|
thumbRef.current?.focus();
|
|
@@ -2013,6 +2060,9 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
2013
2060
|
|
|
2014
2061
|
//#endregion
|
|
2015
2062
|
//#region src/hooks/useAnimationFrame.ts
|
|
2063
|
+
/**
|
|
2064
|
+
* @category hooks
|
|
2065
|
+
*/
|
|
2016
2066
|
function useAnimationFrame(callback = () => {}, deps = []) {
|
|
2017
2067
|
const reqIdRef = useRef(-1);
|
|
2018
2068
|
const loop = useCallback(() => {
|
|
@@ -2026,5 +2076,78 @@ function useAnimationFrame(callback = () => {}, deps = []) {
|
|
|
2026
2076
|
}
|
|
2027
2077
|
|
|
2028
2078
|
//#endregion
|
|
2029
|
-
|
|
2079
|
+
//#region src/hooks/useMIDIAccess.ts
|
|
2080
|
+
/** @private */
|
|
2081
|
+
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
2082
|
+
/** @private */
|
|
2083
|
+
const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
2084
|
+
/**
|
|
2085
|
+
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
2086
|
+
* @category hooks
|
|
2087
|
+
*/
|
|
2088
|
+
function useMIDIAccess(requestOnMount = true) {
|
|
2089
|
+
const [midiAccess, setMidiAccess] = useState(null);
|
|
2090
|
+
const [error, setError] = useState(null);
|
|
2091
|
+
const request = useCallback(() => {
|
|
2092
|
+
if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
|
|
2093
|
+
setMidiAccess(access);
|
|
2094
|
+
setError(null);
|
|
2095
|
+
}).catch(() => {
|
|
2096
|
+
setError(PERMISSION_DENIED);
|
|
2097
|
+
});
|
|
2098
|
+
else setError(NOT_SUPPORTED);
|
|
2099
|
+
}, []);
|
|
2100
|
+
useEffect(() => {
|
|
2101
|
+
if (requestOnMount) request();
|
|
2102
|
+
}, [requestOnMount, request]);
|
|
2103
|
+
return {
|
|
2104
|
+
request,
|
|
2105
|
+
midiAccess,
|
|
2106
|
+
error
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
//#endregion
|
|
2111
|
+
//#region src/hooks/useMIDIMessage.ts
|
|
2112
|
+
/**
|
|
2113
|
+
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
2114
|
+
* @category hooks
|
|
2115
|
+
*/
|
|
2116
|
+
function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
2117
|
+
useEffect(() => {
|
|
2118
|
+
if (!midiAccess) return;
|
|
2119
|
+
for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
|
|
2120
|
+
return () => {
|
|
2121
|
+
for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
|
|
2122
|
+
};
|
|
2123
|
+
}, [midiAccess, onMIDIMessage]);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
//#endregion
|
|
2127
|
+
//#region src/hooks/useMIDIInput.ts
|
|
2128
|
+
const MIDI_EVENT_TO_NUMBER = {
|
|
2129
|
+
NOTE_ON: 144,
|
|
2130
|
+
NOTE_OFF: 128,
|
|
2131
|
+
PITCH_BEND: 224
|
|
2132
|
+
};
|
|
2133
|
+
/**
|
|
2134
|
+
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
2135
|
+
* @category hooks
|
|
2136
|
+
*/
|
|
2137
|
+
function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
|
|
2138
|
+
useMIDIMessage(midiAccess, useCallback((event) => {
|
|
2139
|
+
if (!event.data) return;
|
|
2140
|
+
const kind = event.data[0] & 240;
|
|
2141
|
+
if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
|
|
2142
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
|
|
2143
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
|
|
2144
|
+
}, [
|
|
2145
|
+
onNoteOffEvent,
|
|
2146
|
+
onNoteOnEvent,
|
|
2147
|
+
onPitchBendEvent
|
|
2148
|
+
]));
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
//#endregion
|
|
2152
|
+
export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useSliderContext };
|
|
2030
2153
|
//# sourceMappingURL=index.js.map
|