@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
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
}) : target, mod));
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
let clsx = require("clsx");
|
|
25
|
+
clsx = __toESM(clsx);
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react);
|
|
28
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
let __tremolo_ui_functions = require("@tremolo-ui/functions");
|
|
30
|
+
let zustand = require("zustand");
|
|
29
31
|
|
|
30
32
|
//#region src/components/AnimationCanvas/canvas.ts
|
|
31
33
|
const drawingState = [
|
|
@@ -167,8 +169,8 @@ function AnimationCanvas({ draw, init, animate = true, options, width: _width =
|
|
|
167
169
|
//#endregion
|
|
168
170
|
//#region src/hooks/useCallbackRef.ts
|
|
169
171
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
+
* Internal
|
|
173
|
+
* @private
|
|
172
174
|
*/
|
|
173
175
|
function useCallbackRef(callback, deps = []) {
|
|
174
176
|
const callbackRef = (0, react.useRef)(() => {
|
|
@@ -199,8 +201,7 @@ function useEventListener(target, event, handler, options) {
|
|
|
199
201
|
handler
|
|
200
202
|
]);
|
|
201
203
|
return () => {
|
|
202
|
-
|
|
203
|
-
node?.removeEventListener(event, listener, options);
|
|
204
|
+
(typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -225,6 +226,7 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
|
225
226
|
//#endregion
|
|
226
227
|
//#region src/hooks/useDrag.ts
|
|
227
228
|
/**
|
|
229
|
+
* @category hooks
|
|
228
230
|
* @returns [refCallback, pointerDownHandler]
|
|
229
231
|
*/
|
|
230
232
|
function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -291,9 +293,7 @@ const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = m
|
|
|
291
293
|
const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
|
|
292
294
|
const onDrag = (0, react.useCallback)((_x, y) => {
|
|
293
295
|
if (!onChange) return;
|
|
294
|
-
|
|
295
|
-
const v2 = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max);
|
|
296
|
-
onChange(v2);
|
|
296
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
297
297
|
}, [
|
|
298
298
|
max,
|
|
299
299
|
min,
|
|
@@ -313,10 +313,8 @@ const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = m
|
|
|
313
313
|
event.preventDefault();
|
|
314
314
|
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
315
315
|
let v;
|
|
316
|
-
if (keyboard[0] == "normalized")
|
|
317
|
-
|
|
318
|
-
v = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
319
|
-
} else v = value + x;
|
|
316
|
+
if (keyboard[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
317
|
+
else v = value + x;
|
|
320
318
|
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
|
|
321
319
|
}
|
|
322
320
|
}, [
|
|
@@ -343,10 +341,8 @@ const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = m
|
|
|
343
341
|
event.preventDefault();
|
|
344
342
|
const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
345
343
|
let v;
|
|
346
|
-
if (wheel[0] == "normalized")
|
|
347
|
-
|
|
348
|
-
v = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
349
|
-
} else v = value + x;
|
|
344
|
+
if (wheel[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
345
|
+
else v = value + x;
|
|
350
346
|
if (onChange) onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
|
|
351
347
|
}, { passive: false }, [
|
|
352
348
|
wheel,
|
|
@@ -458,20 +454,12 @@ function DragObserver(props) {
|
|
|
458
454
|
*
|
|
459
455
|
* @category Knob
|
|
460
456
|
*/
|
|
461
|
-
const Knob = (0, react.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 },
|
|
457
|
+
const Knob = (0, react.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) => {
|
|
462
458
|
const valueRef = (0, react.useRef)(0);
|
|
463
459
|
const elmRef = (0, react.useRef)(null);
|
|
464
|
-
const padding = 8;
|
|
465
|
-
const thumbLineWeight = 6;
|
|
466
|
-
const thumbLineLength = 35;
|
|
467
|
-
const lineWeight = size * .06;
|
|
468
|
-
const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
469
|
-
const s = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
470
460
|
const onDrag = (0, react.useCallback)((_x, y) => {
|
|
471
461
|
if (!onChange || readonly) return;
|
|
472
|
-
|
|
473
|
-
const v2 = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max);
|
|
474
|
-
onChange(v2);
|
|
462
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
475
463
|
}, [
|
|
476
464
|
max,
|
|
477
465
|
min,
|
|
@@ -482,10 +470,8 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
482
470
|
]);
|
|
483
471
|
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
484
472
|
let newValue;
|
|
485
|
-
if (eventType == "normalized")
|
|
486
|
-
|
|
487
|
-
newValue = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
488
|
-
} else newValue = value + x;
|
|
473
|
+
if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
474
|
+
else newValue = value + x;
|
|
489
475
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
490
476
|
}, [
|
|
491
477
|
max,
|
|
@@ -535,7 +521,7 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
535
521
|
readonly,
|
|
536
522
|
updateValueByEvent
|
|
537
523
|
]);
|
|
538
|
-
(0, react.useImperativeHandle)(
|
|
524
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
539
525
|
return {
|
|
540
526
|
focus() {
|
|
541
527
|
elmRef.current?.focus();
|
|
@@ -545,11 +531,15 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
545
531
|
}
|
|
546
532
|
};
|
|
547
533
|
}, []);
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const
|
|
534
|
+
const viewBoxSize = 100;
|
|
535
|
+
const center = viewBoxSize / 2;
|
|
536
|
+
const padding = (viewBoxSize - (0, __tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2;
|
|
537
|
+
const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
538
|
+
const s = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
539
|
+
const r1 = -angleRange / 2;
|
|
540
|
+
const r2 = r1 + Math.min(p, s) * angleRange;
|
|
541
|
+
const r3 = r1 + Math.max(p, s) * angleRange;
|
|
542
|
+
const r4 = angleRange / 2;
|
|
553
543
|
const x1 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r1 - 90));
|
|
554
544
|
const y1 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r1 - 90));
|
|
555
545
|
const x2 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r2 - 90));
|
|
@@ -559,12 +549,13 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
559
549
|
const x4 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r4 - 90));
|
|
560
550
|
const y4 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r4 - 90));
|
|
561
551
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
562
|
-
className: (0, clsx.default)("tremolo-knob", className),
|
|
563
552
|
ref: (div) => {
|
|
564
553
|
elmRef.current = div;
|
|
565
554
|
wheelRefCallback(div);
|
|
566
555
|
touchMoveRefCallback(div);
|
|
567
556
|
},
|
|
557
|
+
className: (0, clsx.default)("tremolo-knob", className),
|
|
558
|
+
viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
|
|
568
559
|
width: size,
|
|
569
560
|
height: size,
|
|
570
561
|
tabIndex: 0,
|
|
@@ -595,13 +586,6 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
595
586
|
stroke: inactiveLine || "currentColor",
|
|
596
587
|
strokeWidth: lineWeight
|
|
597
588
|
}),
|
|
598
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
599
|
-
className: (0, clsx.default)("tremolo-knob-active-line", classes?.activeLine),
|
|
600
|
-
d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
|
|
601
|
-
fill: "none",
|
|
602
|
-
stroke: activeLine || "currentColor",
|
|
603
|
-
strokeWidth: lineWeight
|
|
604
|
-
}),
|
|
605
589
|
startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
606
590
|
className: (0, clsx.default)("tremolo-knob-inactive-line", classes?.inactiveLine),
|
|
607
591
|
d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`,
|
|
@@ -609,12 +593,19 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
609
593
|
stroke: inactiveLine || "currentColor",
|
|
610
594
|
strokeWidth: lineWeight
|
|
611
595
|
}),
|
|
596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
597
|
+
className: (0, clsx.default)("tremolo-knob-active-line", classes?.activeLine),
|
|
598
|
+
d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
|
|
599
|
+
fill: "none",
|
|
600
|
+
stroke: activeLine || "currentColor",
|
|
601
|
+
strokeWidth: lineWeight
|
|
602
|
+
}),
|
|
612
603
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
613
604
|
className: (0, clsx.default)("tremolo-knob-thumb", classes?.thumb),
|
|
614
605
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
615
606
|
cx: "50%",
|
|
616
607
|
cy: "50%",
|
|
617
|
-
r: `${
|
|
608
|
+
r: `${thumbSize / 2}%`,
|
|
618
609
|
fill: thumb || "currentColor"
|
|
619
610
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
620
611
|
className: (0, clsx.default)("tremolo-knob-thumb-line", classes?.thumbLine),
|
|
@@ -625,7 +616,7 @@ const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defau
|
|
|
625
616
|
stroke: thumbLine || "currentColor",
|
|
626
617
|
strokeWidth: `${thumbLineWeight}%`,
|
|
627
618
|
style: {
|
|
628
|
-
transform: `rotate(${
|
|
619
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
629
620
|
transformOrigin: "50% 50%"
|
|
630
621
|
}
|
|
631
622
|
})]
|
|
@@ -698,8 +689,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
698
689
|
valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
|
|
699
690
|
increment: () => set((state) => {
|
|
700
691
|
if (state.readonly) return {};
|
|
701
|
-
|
|
702
|
-
let next = current + (state.step ?? 1);
|
|
692
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue + (state.step ?? 1);
|
|
703
693
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
704
694
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
705
695
|
state?.onChange?.(next, v);
|
|
@@ -710,8 +700,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
710
700
|
}),
|
|
711
701
|
decrement: () => set((state) => {
|
|
712
702
|
if (state.readonly) return {};
|
|
713
|
-
|
|
714
|
-
let next = current - (state.step ?? 1);
|
|
703
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue - (state.step ?? 1);
|
|
715
704
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
716
705
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
717
706
|
state?.onChange?.(next, v);
|
|
@@ -756,6 +745,111 @@ function useNumberInputContext(selector) {
|
|
|
756
745
|
return (0, zustand.useStore)(store, selector);
|
|
757
746
|
}
|
|
758
747
|
|
|
748
|
+
//#endregion
|
|
749
|
+
//#region src/hooks/useInterval.ts
|
|
750
|
+
/**
|
|
751
|
+
* @category hooks
|
|
752
|
+
*/
|
|
753
|
+
function useInterval(callback, delay) {
|
|
754
|
+
const savedCallback = (0, react.useRef)(callback);
|
|
755
|
+
(0, react.useEffect)(() => {
|
|
756
|
+
savedCallback.current = callback;
|
|
757
|
+
}, [callback]);
|
|
758
|
+
(0, react.useEffect)(() => {
|
|
759
|
+
if (delay === null) return;
|
|
760
|
+
const id = setInterval(() => {
|
|
761
|
+
savedCallback.current();
|
|
762
|
+
}, delay);
|
|
763
|
+
return () => {
|
|
764
|
+
clearInterval(id);
|
|
765
|
+
};
|
|
766
|
+
}, [delay]);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
//#endregion
|
|
770
|
+
//#region src/hooks/useLongPress.ts
|
|
771
|
+
/**
|
|
772
|
+
* @category hooks
|
|
773
|
+
*/
|
|
774
|
+
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
775
|
+
const [pressed, setPressed] = (0, react.useState)(false);
|
|
776
|
+
const [delay, setDelay] = (0, react.useState)(initialDelay);
|
|
777
|
+
useInterval(() => {
|
|
778
|
+
callback();
|
|
779
|
+
setDelay(interval);
|
|
780
|
+
}, pressed ? delay : null);
|
|
781
|
+
useEventListener(globalThis.window, "pointerup", () => {
|
|
782
|
+
setPressed(false);
|
|
783
|
+
setDelay(initialDelay);
|
|
784
|
+
});
|
|
785
|
+
return (0, react.useCallback)(() => {
|
|
786
|
+
callback();
|
|
787
|
+
setPressed(true);
|
|
788
|
+
}, [callback]);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region src/components/NumberInput/DecrementStepper.tsx
|
|
793
|
+
/** @category NumberInput */
|
|
794
|
+
function DecrementStepper({ size = 12, children, className,...props }) {
|
|
795
|
+
const min = useNumberInputContext((s) => s.min);
|
|
796
|
+
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
797
|
+
const readonly = useNumberInputContext((s) => s.readonly);
|
|
798
|
+
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
799
|
+
const press = useLongPress(useNumberInputContext((s) => s.decrement));
|
|
800
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
801
|
+
className: (0, clsx.default)("tremolo-number-input-decrement-stepper", className),
|
|
802
|
+
role: "button",
|
|
803
|
+
tabIndex: -1,
|
|
804
|
+
"aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
|
|
805
|
+
"aria-readonly": readonly,
|
|
806
|
+
onPointerDown: press,
|
|
807
|
+
...props,
|
|
808
|
+
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
809
|
+
width: size,
|
|
810
|
+
height: size,
|
|
811
|
+
viewBox: "0 0 24 24",
|
|
812
|
+
fill: "none",
|
|
813
|
+
stroke: "currentColor",
|
|
814
|
+
strokeWidth: "2",
|
|
815
|
+
strokeLinecap: "round",
|
|
816
|
+
strokeLinejoin: "round",
|
|
817
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "6 9 12 15 18 9" })
|
|
818
|
+
})
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
//#endregion
|
|
823
|
+
//#region src/components/NumberInput/IncrementStepper.tsx
|
|
824
|
+
/** @category NumberInput */
|
|
825
|
+
function IncrementStepper({ size = 12, children, className,...props }) {
|
|
826
|
+
const max = useNumberInputContext((s) => s.max);
|
|
827
|
+
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
828
|
+
const readonly = useNumberInputContext((s) => s.readonly);
|
|
829
|
+
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
830
|
+
const press = useLongPress(useNumberInputContext((s) => s.increment));
|
|
831
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
832
|
+
className: (0, clsx.default)("tremolo-number-input-increment-stepper", className),
|
|
833
|
+
role: "button",
|
|
834
|
+
tabIndex: -1,
|
|
835
|
+
"aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
|
|
836
|
+
"aria-readonly": readonly,
|
|
837
|
+
onPointerDown: press,
|
|
838
|
+
...props,
|
|
839
|
+
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
840
|
+
width: size,
|
|
841
|
+
height: size,
|
|
842
|
+
viewBox: "0 0 24 24",
|
|
843
|
+
fill: "none",
|
|
844
|
+
stroke: "currentColor",
|
|
845
|
+
strokeWidth: "2",
|
|
846
|
+
strokeLinecap: "round",
|
|
847
|
+
strokeLinejoin: "round",
|
|
848
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
|
|
849
|
+
})
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
759
853
|
//#endregion
|
|
760
854
|
//#region src/components/NumberInput/InternalInput.tsx
|
|
761
855
|
const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children,...props }, ref) => {
|
|
@@ -783,8 +877,7 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
|
|
|
783
877
|
let newValue;
|
|
784
878
|
if (eventType == "normalized") {
|
|
785
879
|
if (!min || !max) throw new Error("required parameter: min, max");
|
|
786
|
-
|
|
787
|
-
newValue = (0, __tremolo_ui_functions.rawValue)(n + x, min, max);
|
|
880
|
+
newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(valueAsNumber, min, max) + x, min, max);
|
|
788
881
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
789
882
|
} else {
|
|
790
883
|
newValue = valueAsNumber + x;
|
|
@@ -898,114 +991,9 @@ function Stepper({ dynamic = true, children, className,...props }) {
|
|
|
898
991
|
});
|
|
899
992
|
}
|
|
900
993
|
|
|
901
|
-
//#endregion
|
|
902
|
-
//#region src/hooks/useInterval.ts
|
|
903
|
-
function useInterval(callback, delay) {
|
|
904
|
-
const savedCallback = (0, react.useRef)(callback);
|
|
905
|
-
(0, react.useEffect)(() => {
|
|
906
|
-
savedCallback.current = callback;
|
|
907
|
-
}, [callback]);
|
|
908
|
-
(0, react.useEffect)(() => {
|
|
909
|
-
if (delay === null) return;
|
|
910
|
-
const id = setInterval(() => {
|
|
911
|
-
savedCallback.current();
|
|
912
|
-
}, delay);
|
|
913
|
-
return () => {
|
|
914
|
-
clearInterval(id);
|
|
915
|
-
};
|
|
916
|
-
}, [delay]);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
//#endregion
|
|
920
|
-
//#region src/hooks/useLongPress.ts
|
|
921
|
-
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
922
|
-
const [pressed, setPressed] = (0, react.useState)(false);
|
|
923
|
-
const [delay, setDelay] = (0, react.useState)(initialDelay);
|
|
924
|
-
useInterval(() => {
|
|
925
|
-
callback();
|
|
926
|
-
setDelay(interval);
|
|
927
|
-
}, pressed ? delay : null);
|
|
928
|
-
useEventListener(globalThis.window, "pointerup", () => {
|
|
929
|
-
setPressed(false);
|
|
930
|
-
setDelay(initialDelay);
|
|
931
|
-
});
|
|
932
|
-
return (0, react.useCallback)(() => {
|
|
933
|
-
callback();
|
|
934
|
-
setPressed(true);
|
|
935
|
-
}, [callback]);
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
//#endregion
|
|
939
|
-
//#region src/components/NumberInput/IncrementStepper.tsx
|
|
940
|
-
/** @category NumberInput */
|
|
941
|
-
function IncrementStepper({ size = 12, children, className,...props }) {
|
|
942
|
-
const max = useNumberInputContext((s) => s.max);
|
|
943
|
-
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
944
|
-
const readonly = useNumberInputContext((s) => s.readonly);
|
|
945
|
-
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
946
|
-
const increment = useNumberInputContext((s) => s.increment);
|
|
947
|
-
const press = useLongPress(increment);
|
|
948
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
949
|
-
className: (0, clsx.default)("tremolo-number-input-increment-stepper", className),
|
|
950
|
-
role: "button",
|
|
951
|
-
tabIndex: -1,
|
|
952
|
-
"aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
|
|
953
|
-
"aria-readonly": readonly,
|
|
954
|
-
onPointerDown: press,
|
|
955
|
-
...props,
|
|
956
|
-
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
957
|
-
width: size,
|
|
958
|
-
height: size,
|
|
959
|
-
viewBox: "0 0 24 24",
|
|
960
|
-
fill: "none",
|
|
961
|
-
stroke: "currentColor",
|
|
962
|
-
strokeWidth: "2",
|
|
963
|
-
strokeLinecap: "round",
|
|
964
|
-
strokeLinejoin: "round",
|
|
965
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
|
|
966
|
-
})
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
//#endregion
|
|
971
|
-
//#region src/components/NumberInput/DecrementStepper.tsx
|
|
972
|
-
/** @category NumberInput */
|
|
973
|
-
function DecrementStepper({ size = 12, children, className,...props }) {
|
|
974
|
-
const min = useNumberInputContext((s) => s.min);
|
|
975
|
-
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
976
|
-
const readonly = useNumberInputContext((s) => s.readonly);
|
|
977
|
-
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
978
|
-
const decrement = useNumberInputContext((s) => s.decrement);
|
|
979
|
-
const press = useLongPress(decrement);
|
|
980
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
981
|
-
className: (0, clsx.default)("tremolo-number-input-decrement-stepper", className),
|
|
982
|
-
role: "button",
|
|
983
|
-
tabIndex: -1,
|
|
984
|
-
"aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
|
|
985
|
-
"aria-readonly": readonly,
|
|
986
|
-
onPointerDown: press,
|
|
987
|
-
...props,
|
|
988
|
-
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
989
|
-
width: size,
|
|
990
|
-
height: size,
|
|
991
|
-
viewBox: "0 0 24 24",
|
|
992
|
-
fill: "none",
|
|
993
|
-
stroke: "currentColor",
|
|
994
|
-
strokeWidth: "2",
|
|
995
|
-
strokeLinecap: "round",
|
|
996
|
-
strokeLinejoin: "round",
|
|
997
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "6 9 12 15 18 9" })
|
|
998
|
-
})
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
994
|
//#endregion
|
|
1003
995
|
//#region src/components/NumberInput/index.tsx
|
|
1004
|
-
|
|
1005
|
-
* Input with some useful functions for entering numerical values.
|
|
1006
|
-
* @category NumberInput
|
|
1007
|
-
*/
|
|
1008
|
-
const NumberInput = (0, react.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 }, ref) => {
|
|
996
|
+
const NumberInputImpl = (0, react.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) => {
|
|
1009
997
|
const colors = { "--active-color": activeColor };
|
|
1010
998
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
|
|
1011
999
|
value: String(value),
|
|
@@ -1024,7 +1012,7 @@ const NumberInput = (0, react.forwardRef)(({ value, min, max, step = 1, units, r
|
|
|
1024
1012
|
"data-stepper": !!children,
|
|
1025
1013
|
"data-variant": variant,
|
|
1026
1014
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
|
|
1027
|
-
ref,
|
|
1015
|
+
ref: forwardedRef,
|
|
1028
1016
|
readonly,
|
|
1029
1017
|
disabled,
|
|
1030
1018
|
selectWithFocus,
|
|
@@ -1041,10 +1029,20 @@ const NumberInput = (0, react.forwardRef)(({ value, min, max, step = 1, units, r
|
|
|
1041
1029
|
})
|
|
1042
1030
|
});
|
|
1043
1031
|
});
|
|
1032
|
+
/**
|
|
1033
|
+
* Input with some useful functions for entering numerical values.
|
|
1034
|
+
* @category NumberInput
|
|
1035
|
+
*/
|
|
1036
|
+
const NumberInput = Object.assign(NumberInputImpl, {
|
|
1037
|
+
Stepper,
|
|
1038
|
+
IncrementStepper,
|
|
1039
|
+
DecrementStepper
|
|
1040
|
+
});
|
|
1044
1041
|
|
|
1045
1042
|
//#endregion
|
|
1046
1043
|
//#region src/hooks/useDragWithElement.ts
|
|
1047
1044
|
/**
|
|
1045
|
+
* @category hooks
|
|
1048
1046
|
* @returns [refCallback, pointerDownHandler]
|
|
1049
1047
|
*/
|
|
1050
1048
|
function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -1124,9 +1122,7 @@ function usePianoContext(selector) {
|
|
|
1124
1122
|
//#region src/components/Piano/KeyLabel.tsx
|
|
1125
1123
|
/** @category Piano */
|
|
1126
1124
|
function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
|
|
1127
|
-
const
|
|
1128
|
-
const noteRangeArray = getNoteRangeArray(noteRange);
|
|
1129
|
-
const index = noteRangeArray.indexOf(__note);
|
|
1125
|
+
const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
|
|
1130
1126
|
const content = label ? label(__note, index) : __label && __label(__note, index);
|
|
1131
1127
|
return (content != void 0 || content != null) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1132
1128
|
className: (0, clsx.default)("tremolo-piano-key-label-wrapper", wrapperClassName),
|
|
@@ -1152,8 +1148,7 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
|
|
|
1152
1148
|
const onPlayNote = usePianoContext((s) => s.onPlayNote);
|
|
1153
1149
|
const onStopNote = usePianoContext((s) => s.onStopNote);
|
|
1154
1150
|
const label = usePianoContext((s) => s.label);
|
|
1155
|
-
const
|
|
1156
|
-
const position = notePosition(noteNumber);
|
|
1151
|
+
const position = usePianoContext((s) => s.notePosition)(noteNumber);
|
|
1157
1152
|
const disabled = noteNumber > midiMax;
|
|
1158
1153
|
const colors = {
|
|
1159
1154
|
"--bg": bg,
|
|
@@ -1163,10 +1158,10 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
|
|
|
1163
1158
|
};
|
|
1164
1159
|
(0, react.useImperativeHandle)(ref, () => {
|
|
1165
1160
|
return {
|
|
1166
|
-
play() {
|
|
1161
|
+
play(velocity) {
|
|
1167
1162
|
if (disabled) return;
|
|
1168
1163
|
setPlayed(true);
|
|
1169
|
-
if (onPlayNote) onPlayNote(noteNumber);
|
|
1164
|
+
if (onPlayNote) onPlayNote(noteNumber, velocity);
|
|
1170
1165
|
},
|
|
1171
1166
|
stop() {
|
|
1172
1167
|
setPlayed(false);
|
|
@@ -1257,21 +1252,14 @@ const SHORTCUTS = { HOME_ROW: { keys: [
|
|
|
1257
1252
|
//#endregion
|
|
1258
1253
|
//#region src/components/Piano/index.tsx
|
|
1259
1254
|
/**
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
* TODO:
|
|
1263
|
-
* - add scale highlight
|
|
1255
|
+
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
1256
|
+
* @category Piano
|
|
1264
1257
|
*/
|
|
1265
|
-
/** @category Piano */
|
|
1266
1258
|
function getNoteRangeArray(noteRange) {
|
|
1267
1259
|
return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
1268
1260
|
}
|
|
1269
1261
|
const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
|
|
1270
|
-
|
|
1271
|
-
* Customizable piano component.
|
|
1272
|
-
* @category Piano
|
|
1273
|
-
*/
|
|
1274
|
-
function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }) {
|
|
1262
|
+
const PianoImpl = (0, react.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) => {
|
|
1275
1263
|
const [whiteNoteWidth, setWhiteNoteWidth] = (0, react.useState)(_whiteNoteWidth);
|
|
1276
1264
|
const keyRefs = (0, react.useRef)([]);
|
|
1277
1265
|
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = (0, react.createRef)();
|
|
@@ -1342,9 +1330,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1342
1330
|
]);
|
|
1343
1331
|
const onDrag = (0, react.useCallback)((perX, perY) => {
|
|
1344
1332
|
if (!pianoRef.current) return;
|
|
1345
|
-
const
|
|
1346
|
-
const y = perY * pianoRef.current.clientHeight;
|
|
1347
|
-
const note = getHitKeyIndex(x, y);
|
|
1333
|
+
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1348
1334
|
const index = noteRangeArray.indexOf(note);
|
|
1349
1335
|
if (index == -1) return;
|
|
1350
1336
|
if (hitKeyIndex.current != index) {
|
|
@@ -1398,6 +1384,25 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1398
1384
|
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1399
1385
|
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1400
1386
|
});
|
|
1387
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1388
|
+
return {
|
|
1389
|
+
playNote(note, velocity) {
|
|
1390
|
+
const index = noteRangeArray.indexOf(note);
|
|
1391
|
+
if (index != -1) {
|
|
1392
|
+
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
|
|
1393
|
+
} else onPlayNote?.(note, velocity);
|
|
1394
|
+
},
|
|
1395
|
+
stopNote(note) {
|
|
1396
|
+
const index = noteRangeArray.indexOf(note);
|
|
1397
|
+
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1398
|
+
else onStopNote?.(note);
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}, [
|
|
1402
|
+
noteRangeArray,
|
|
1403
|
+
onPlayNote,
|
|
1404
|
+
onStopNote
|
|
1405
|
+
]);
|
|
1401
1406
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
|
|
1402
1407
|
notePosition,
|
|
1403
1408
|
noteRange,
|
|
@@ -1434,7 +1439,16 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1434
1439
|
}, note))
|
|
1435
1440
|
})
|
|
1436
1441
|
});
|
|
1437
|
-
}
|
|
1442
|
+
});
|
|
1443
|
+
/**
|
|
1444
|
+
* Customizable piano component.
|
|
1445
|
+
* @category Piano
|
|
1446
|
+
*/
|
|
1447
|
+
const Piano = Object.assign(PianoImpl, {
|
|
1448
|
+
WhiteKey,
|
|
1449
|
+
BlackKey,
|
|
1450
|
+
KeyLabel
|
|
1451
|
+
});
|
|
1438
1452
|
|
|
1439
1453
|
//#endregion
|
|
1440
1454
|
//#region src/components/Slider/context.tsx
|
|
@@ -1571,7 +1585,7 @@ function Scale({ gap = 6, options, children, className, style,...props }) {
|
|
|
1571
1585
|
/** @category Slider */
|
|
1572
1586
|
const defaultThumbSize = 22;
|
|
1573
1587
|
/** @category Slider */
|
|
1574
|
-
const
|
|
1588
|
+
const Thumb$1 = (0, react.forwardRef)(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent }, ref) => {
|
|
1575
1589
|
const wrapperRef = (0, react.useRef)(null);
|
|
1576
1590
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1577
1591
|
const disabled = useSliderContext((s) => s.disabled);
|
|
@@ -1613,7 +1627,7 @@ const SliderThumb = (0, react.forwardRef)(({ size, width = defaultThumbSize, hei
|
|
|
1613
1627
|
const defaultLength = 140;
|
|
1614
1628
|
const defaultThickness = 10;
|
|
1615
1629
|
/** @category Slider */
|
|
1616
|
-
function
|
|
1630
|
+
function Track({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0,...props }) {
|
|
1617
1631
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1618
1632
|
const reverse = useSliderContext((s) => s.reverse);
|
|
1619
1633
|
const disabled = useSliderContext((s) => s.disabled);
|
|
@@ -1628,7 +1642,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1628
1642
|
"data-vertical": vertical,
|
|
1629
1643
|
style: !defaultStyle ? style : {
|
|
1630
1644
|
...colors,
|
|
1631
|
-
background: (0, __tremolo_ui_functions.xor)(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive
|
|
1645
|
+
background: (0, __tremolo_ui_functions.xor)(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
|
|
1632
1646
|
borderRadius: (0, __tremolo_ui_functions.styleHelper)(thickness, "/", 2),
|
|
1633
1647
|
width: !vertical ? length : thickness,
|
|
1634
1648
|
height: vertical ? length : thickness,
|
|
@@ -1641,11 +1655,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1641
1655
|
|
|
1642
1656
|
//#endregion
|
|
1643
1657
|
//#region src/components/Slider/index.tsx
|
|
1644
|
-
|
|
1645
|
-
* Customizable slider
|
|
1646
|
-
* @category Slider
|
|
1647
|
-
*/
|
|
1648
|
-
const Slider = (0, react.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 }, ref) => {
|
|
1658
|
+
const SliderImpl = (0, react.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) => {
|
|
1649
1659
|
const trackElementRef = (0, react.useRef)(null);
|
|
1650
1660
|
const thumbRef = (0, react.useRef)(null);
|
|
1651
1661
|
const p = (0, __tremolo_ui_functions.toFixed)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) * 100);
|
|
@@ -1656,8 +1666,8 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1656
1666
|
let thumbProps = {};
|
|
1657
1667
|
let scaleComponent = void 0;
|
|
1658
1668
|
if (children != void 0) react.default.Children.forEach(children, (child) => {
|
|
1659
|
-
if (react.default.isValidElement(child)) if (child.type ==
|
|
1660
|
-
else if (child.type ==
|
|
1669
|
+
if (react.default.isValidElement(child)) if (child.type == Thumb$1) thumbProps = child.props;
|
|
1670
|
+
else if (child.type == Track) trackProps = child.props;
|
|
1661
1671
|
else if (child.type == Scale) scaleComponent = child;
|
|
1662
1672
|
else throw new Error("only <SliderThumb> or <SliderTrack>");
|
|
1663
1673
|
else throw new Error("children is an invalid element.");
|
|
@@ -1665,9 +1675,7 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1665
1675
|
const onDrag = (0, react.useCallback)((nx, ny) => {
|
|
1666
1676
|
if (!onChange || readonly) return;
|
|
1667
1677
|
const n = vertical ? ny : nx;
|
|
1668
|
-
|
|
1669
|
-
const v2 = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max);
|
|
1670
|
-
onChange(v2);
|
|
1678
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
|
|
1671
1679
|
}, [
|
|
1672
1680
|
displayReversed,
|
|
1673
1681
|
max,
|
|
@@ -1680,10 +1688,8 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1680
1688
|
]);
|
|
1681
1689
|
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
1682
1690
|
let newValue;
|
|
1683
|
-
if (eventType == "normalized")
|
|
1684
|
-
|
|
1685
|
-
newValue = (0, __tremolo_ui_functions.rawValue)(n + x, min, max, skew);
|
|
1686
|
-
} else newValue = value + x;
|
|
1691
|
+
if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
1692
|
+
else newValue = value + x;
|
|
1687
1693
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
1688
1694
|
}, [
|
|
1689
1695
|
max,
|
|
@@ -1746,7 +1752,7 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1746
1752
|
onChange,
|
|
1747
1753
|
updateValueByEvent
|
|
1748
1754
|
]);
|
|
1749
|
-
(0, react.useImperativeHandle)(
|
|
1755
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1750
1756
|
return {
|
|
1751
1757
|
focus() {
|
|
1752
1758
|
thumbRef.current?.focus();
|
|
@@ -1810,9 +1816,9 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1810
1816
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1811
1817
|
className: "tremolo-slider-track-wrapper",
|
|
1812
1818
|
ref: trackElementRef,
|
|
1813
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1819
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Track, {
|
|
1814
1820
|
__percent: percent,
|
|
1815
|
-
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1821
|
+
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb$1, {
|
|
1816
1822
|
ref: thumbRef,
|
|
1817
1823
|
__percent: percent,
|
|
1818
1824
|
...thumbProps
|
|
@@ -1824,17 +1830,26 @@ const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, ver
|
|
|
1824
1830
|
})
|
|
1825
1831
|
});
|
|
1826
1832
|
});
|
|
1833
|
+
/**
|
|
1834
|
+
* Customizable slider
|
|
1835
|
+
* @category Slider
|
|
1836
|
+
*/
|
|
1837
|
+
const Slider = Object.assign(SliderImpl, {
|
|
1838
|
+
Thumb: Thumb$1,
|
|
1839
|
+
Track,
|
|
1840
|
+
Scale,
|
|
1841
|
+
ScaleOption
|
|
1842
|
+
});
|
|
1827
1843
|
|
|
1828
1844
|
//#endregion
|
|
1829
1845
|
//#region src/components/WheelObserver/index.tsx
|
|
1830
1846
|
/** @category WheelObserver */
|
|
1831
1847
|
function WheelObserver(props) {
|
|
1832
1848
|
const { children, onWheel, as: Component = "div",...attributes } = props;
|
|
1833
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
1834
|
-
if (onWheel) onWheel(event);
|
|
1835
|
-
}, { passive: false }, [onWheel]);
|
|
1836
1849
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
1837
|
-
ref:
|
|
1850
|
+
ref: useRefCallbackEvent("wheel", (event) => {
|
|
1851
|
+
if (onWheel) onWheel(event);
|
|
1852
|
+
}, { passive: false }, [onWheel]),
|
|
1838
1853
|
...attributes,
|
|
1839
1854
|
children
|
|
1840
1855
|
});
|
|
@@ -1843,7 +1858,7 @@ function WheelObserver(props) {
|
|
|
1843
1858
|
//#endregion
|
|
1844
1859
|
//#region src/components/XYPad/Area.tsx
|
|
1845
1860
|
/** @category XYPad */
|
|
1846
|
-
function
|
|
1861
|
+
function Area({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
|
|
1847
1862
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1848
1863
|
className: (0, clsx.default)("tremolo-xy-pad-area", className),
|
|
1849
1864
|
style: {
|
|
@@ -1860,7 +1875,7 @@ function XYPadArea({ width = 120, height = 120, color, children, className, styl
|
|
|
1860
1875
|
//#endregion
|
|
1861
1876
|
//#region src/components/XYPad/Thumb.tsx
|
|
1862
1877
|
/** @category XYPad */
|
|
1863
|
-
const
|
|
1878
|
+
const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __readonly, __css,...props }, ref) => {
|
|
1864
1879
|
const wrapperRef = (0, react.useRef)(null);
|
|
1865
1880
|
(0, react.useImperativeHandle)(ref, () => {
|
|
1866
1881
|
return {
|
|
@@ -1884,6 +1899,7 @@ const XYPadThumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color
|
|
|
1884
1899
|
className: (0, clsx.default)("tremolo-xy-pad-thumb", className),
|
|
1885
1900
|
tabIndex: 0,
|
|
1886
1901
|
"aria-disabled": __disabled,
|
|
1902
|
+
"aria-readonly": __readonly,
|
|
1887
1903
|
style: {
|
|
1888
1904
|
"--color": color,
|
|
1889
1905
|
width: size ?? width,
|
|
@@ -1903,11 +1919,7 @@ const defaultValueOptions = {
|
|
|
1903
1919
|
wheel: ["raw", 1],
|
|
1904
1920
|
keyboard: ["raw", 1]
|
|
1905
1921
|
};
|
|
1906
|
-
|
|
1907
|
-
* Simple XYPad
|
|
1908
|
-
* @category XYPad
|
|
1909
|
-
*/
|
|
1910
|
-
const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, ref) => {
|
|
1922
|
+
const XYPadImpl = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
|
|
1911
1923
|
const x = (0, react.useMemo)(() => {
|
|
1912
1924
|
return {
|
|
1913
1925
|
...defaultValueOptions,
|
|
@@ -1929,8 +1941,8 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
1929
1941
|
let areaProps = {};
|
|
1930
1942
|
let thumbProps = {};
|
|
1931
1943
|
if (children != void 0) react.default.Children.forEach(children, (child) => {
|
|
1932
|
-
if (react.default.isValidElement(child)) if (child.type ==
|
|
1933
|
-
else if (child.type ==
|
|
1944
|
+
if (react.default.isValidElement(child)) if (child.type == Thumb) thumbProps = child.props;
|
|
1945
|
+
else if (child.type == Area) areaProps = child.props;
|
|
1934
1946
|
else throw new Error("only <XYPadThumb> or <XYPadArea>");
|
|
1935
1947
|
else throw new Error("children is an invalid element.");
|
|
1936
1948
|
});
|
|
@@ -1938,9 +1950,7 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
1938
1950
|
if (!onChange || readonly) return;
|
|
1939
1951
|
const vx = (0, __tremolo_ui_functions.rawValue)(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
|
|
1940
1952
|
const vy = (0, __tremolo_ui_functions.rawValue)(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
|
|
1941
|
-
|
|
1942
|
-
const newY = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vy, y.step), y.min, y.max);
|
|
1943
|
-
onChange(newX, newY);
|
|
1953
|
+
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vx, x.step), x.min, x.max), (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vy, y.step), y.min, y.max));
|
|
1944
1954
|
}, [
|
|
1945
1955
|
onChange,
|
|
1946
1956
|
readonly,
|
|
@@ -1957,10 +1967,8 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
1957
1967
|
]);
|
|
1958
1968
|
const updateValueByEvent = (0, react.useCallback)((eventType, target, x$1) => {
|
|
1959
1969
|
let v;
|
|
1960
|
-
if (eventType == "normalized")
|
|
1961
|
-
|
|
1962
|
-
v = (0, __tremolo_ui_functions.rawValue)(n + x$1, target.min, target.max, target.skew);
|
|
1963
|
-
} else v = target.value + x$1;
|
|
1970
|
+
if (eventType == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
|
|
1971
|
+
else v = target.value + x$1;
|
|
1964
1972
|
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, target.step), target.min, target.max);
|
|
1965
1973
|
}, []);
|
|
1966
1974
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
@@ -2027,7 +2035,7 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
2027
2035
|
readonly,
|
|
2028
2036
|
updateValueByEvent
|
|
2029
2037
|
]);
|
|
2030
|
-
(0, react.useImperativeHandle)(
|
|
2038
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
2031
2039
|
return {
|
|
2032
2040
|
focus() {
|
|
2033
2041
|
thumbRef.current?.focus();
|
|
@@ -2072,10 +2080,11 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
2072
2080
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2073
2081
|
className: "tremolo-xy-pad-area-wrapper",
|
|
2074
2082
|
ref: areaElementRef,
|
|
2075
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2076
|
-
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2083
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Area, {
|
|
2084
|
+
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb, {
|
|
2077
2085
|
ref: thumbRef,
|
|
2078
2086
|
__disabled: disabled,
|
|
2087
|
+
__readonly: readonly,
|
|
2079
2088
|
__css: {
|
|
2080
2089
|
top: `${percentY}%`,
|
|
2081
2090
|
left: `${percentX}%`
|
|
@@ -2087,9 +2096,20 @@ const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSel
|
|
|
2087
2096
|
})
|
|
2088
2097
|
});
|
|
2089
2098
|
});
|
|
2099
|
+
/**
|
|
2100
|
+
* Simple XYPad
|
|
2101
|
+
* @category XYPad
|
|
2102
|
+
*/
|
|
2103
|
+
const XYPad = Object.assign(XYPadImpl, {
|
|
2104
|
+
Thumb,
|
|
2105
|
+
Area
|
|
2106
|
+
});
|
|
2090
2107
|
|
|
2091
2108
|
//#endregion
|
|
2092
2109
|
//#region src/hooks/useAnimationFrame.ts
|
|
2110
|
+
/**
|
|
2111
|
+
* @category hooks
|
|
2112
|
+
*/
|
|
2093
2113
|
function useAnimationFrame(callback = () => {}, deps = []) {
|
|
2094
2114
|
const reqIdRef = (0, react.useRef)(-1);
|
|
2095
2115
|
const loop = (0, react.useCallback)(() => {
|
|
@@ -2102,29 +2122,92 @@ function useAnimationFrame(callback = () => {}, deps = []) {
|
|
|
2102
2122
|
}, [loop]);
|
|
2103
2123
|
}
|
|
2104
2124
|
|
|
2125
|
+
//#endregion
|
|
2126
|
+
//#region src/hooks/useMIDIAccess.ts
|
|
2127
|
+
/** @private */
|
|
2128
|
+
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
2129
|
+
/** @private */
|
|
2130
|
+
const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
2131
|
+
/**
|
|
2132
|
+
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
2133
|
+
* @category hooks
|
|
2134
|
+
*/
|
|
2135
|
+
function useMIDIAccess(requestOnMount = true) {
|
|
2136
|
+
const [midiAccess, setMidiAccess] = (0, react.useState)(null);
|
|
2137
|
+
const [error, setError] = (0, react.useState)(null);
|
|
2138
|
+
const request = (0, react.useCallback)(() => {
|
|
2139
|
+
if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
|
|
2140
|
+
setMidiAccess(access);
|
|
2141
|
+
setError(null);
|
|
2142
|
+
}).catch(() => {
|
|
2143
|
+
setError(PERMISSION_DENIED);
|
|
2144
|
+
});
|
|
2145
|
+
else setError(NOT_SUPPORTED);
|
|
2146
|
+
}, []);
|
|
2147
|
+
(0, react.useEffect)(() => {
|
|
2148
|
+
if (requestOnMount) request();
|
|
2149
|
+
}, [requestOnMount, request]);
|
|
2150
|
+
return {
|
|
2151
|
+
request,
|
|
2152
|
+
midiAccess,
|
|
2153
|
+
error
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
//#endregion
|
|
2158
|
+
//#region src/hooks/useMIDIMessage.ts
|
|
2159
|
+
/**
|
|
2160
|
+
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
2161
|
+
* @category hooks
|
|
2162
|
+
*/
|
|
2163
|
+
function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
2164
|
+
(0, react.useEffect)(() => {
|
|
2165
|
+
if (!midiAccess) return;
|
|
2166
|
+
for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
|
|
2167
|
+
return () => {
|
|
2168
|
+
for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
|
|
2169
|
+
};
|
|
2170
|
+
}, [midiAccess, onMIDIMessage]);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
//#endregion
|
|
2174
|
+
//#region src/hooks/useMIDIInput.ts
|
|
2175
|
+
const MIDI_EVENT_TO_NUMBER = {
|
|
2176
|
+
NOTE_ON: 144,
|
|
2177
|
+
NOTE_OFF: 128,
|
|
2178
|
+
PITCH_BEND: 224
|
|
2179
|
+
};
|
|
2180
|
+
/**
|
|
2181
|
+
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
2182
|
+
* @category hooks
|
|
2183
|
+
*/
|
|
2184
|
+
function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
|
|
2185
|
+
useMIDIMessage(midiAccess, (0, react.useCallback)((event) => {
|
|
2186
|
+
if (!event.data) return;
|
|
2187
|
+
const kind = event.data[0] & 240;
|
|
2188
|
+
if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
|
|
2189
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
|
|
2190
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
|
|
2191
|
+
}, [
|
|
2192
|
+
onNoteOffEvent,
|
|
2193
|
+
onNoteOnEvent,
|
|
2194
|
+
onPitchBendEvent
|
|
2195
|
+
]));
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2105
2198
|
//#endregion
|
|
2106
2199
|
exports.AnimationCanvas = AnimationCanvas;
|
|
2107
2200
|
exports.AnimationKnob = AnimationKnob;
|
|
2108
|
-
exports.BlackKey = BlackKey;
|
|
2109
|
-
exports.DecrementStepper = DecrementStepper;
|
|
2110
2201
|
exports.DragObserver = DragObserver;
|
|
2111
|
-
exports.IncrementStepper = IncrementStepper;
|
|
2112
|
-
exports.KeyLabel = KeyLabel;
|
|
2113
2202
|
exports.Knob = Knob;
|
|
2203
|
+
exports.NOT_SUPPORTED = NOT_SUPPORTED;
|
|
2114
2204
|
exports.NumberInput = NumberInput;
|
|
2205
|
+
exports.PERMISSION_DENIED = PERMISSION_DENIED;
|
|
2115
2206
|
exports.Piano = Piano;
|
|
2116
2207
|
exports.SHORTCUTS = SHORTCUTS;
|
|
2117
|
-
exports.Scale = Scale;
|
|
2118
|
-
exports.ScaleOption = ScaleOption;
|
|
2119
2208
|
exports.Slider = Slider;
|
|
2120
|
-
exports.SliderThumb = SliderThumb;
|
|
2121
|
-
exports.SliderTrack = SliderTrack;
|
|
2122
|
-
exports.Stepper = Stepper;
|
|
2123
2209
|
exports.WheelObserver = WheelObserver;
|
|
2124
|
-
exports.WhiteKey = WhiteKey;
|
|
2125
2210
|
exports.XYPad = XYPad;
|
|
2126
|
-
exports.XYPadArea = XYPadArea;
|
|
2127
|
-
exports.XYPadThumb = XYPadThumb;
|
|
2128
2211
|
exports.getNoteRangeArray = getNoteRangeArray;
|
|
2129
2212
|
exports.useAnimationFrame = useAnimationFrame;
|
|
2130
2213
|
exports.useDrag = useDrag;
|
|
@@ -2132,5 +2215,8 @@ exports.useDragWithElement = useDragWithElement;
|
|
|
2132
2215
|
exports.useEventListener = useEventListener;
|
|
2133
2216
|
exports.useInterval = useInterval;
|
|
2134
2217
|
exports.useLongPress = useLongPress;
|
|
2218
|
+
exports.useMIDIAccess = useMIDIAccess;
|
|
2219
|
+
exports.useMIDIInput = useMIDIInput;
|
|
2220
|
+
exports.useMIDIMessage = useMIDIMessage;
|
|
2135
2221
|
exports.useSliderContext = useSliderContext;
|
|
2136
2222
|
//# sourceMappingURL=index.cjs.map
|