@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.js
CHANGED
|
@@ -144,8 +144,8 @@ function AnimationCanvas({ draw, init, animate = true, options, width: _width =
|
|
|
144
144
|
//#endregion
|
|
145
145
|
//#region src/hooks/useCallbackRef.ts
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
147
|
+
* Internal
|
|
148
|
+
* @private
|
|
149
149
|
*/
|
|
150
150
|
function useCallbackRef(callback, deps = []) {
|
|
151
151
|
const callbackRef = useRef(() => {
|
|
@@ -176,8 +176,7 @@ function useEventListener(target, event, handler, options) {
|
|
|
176
176
|
handler
|
|
177
177
|
]);
|
|
178
178
|
return () => {
|
|
179
|
-
|
|
180
|
-
node?.removeEventListener(event, listener, options);
|
|
179
|
+
(typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
|
|
181
180
|
};
|
|
182
181
|
}
|
|
183
182
|
|
|
@@ -202,6 +201,7 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
|
202
201
|
//#endregion
|
|
203
202
|
//#region src/hooks/useDrag.ts
|
|
204
203
|
/**
|
|
204
|
+
* @category hooks
|
|
205
205
|
* @returns [refCallback, pointerDownHandler]
|
|
206
206
|
*/
|
|
207
207
|
function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -268,9 +268,7 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
|
|
|
268
268
|
const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
|
|
269
269
|
const onDrag = useCallback((_x, y) => {
|
|
270
270
|
if (!onChange) return;
|
|
271
|
-
|
|
272
|
-
const v2 = clamp(stepValue(v, step), min, max);
|
|
273
|
-
onChange(v2);
|
|
271
|
+
onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
274
272
|
}, [
|
|
275
273
|
max,
|
|
276
274
|
min,
|
|
@@ -290,10 +288,8 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
|
|
|
290
288
|
event.preventDefault();
|
|
291
289
|
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
292
290
|
let v;
|
|
293
|
-
if (keyboard[0] == "normalized")
|
|
294
|
-
|
|
295
|
-
v = rawValue(n + x, min, max, skew);
|
|
296
|
-
} 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;
|
|
297
293
|
onChange(clamp(stepValue(v, step), min, max));
|
|
298
294
|
}
|
|
299
295
|
}, [
|
|
@@ -320,10 +316,8 @@ const AnimationKnob = forwardRef(({ value, min, max, defaultValue = min, startVa
|
|
|
320
316
|
event.preventDefault();
|
|
321
317
|
const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
322
318
|
let v;
|
|
323
|
-
if (wheel[0] == "normalized")
|
|
324
|
-
|
|
325
|
-
v = rawValue(n + x, min, max, skew);
|
|
326
|
-
} 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;
|
|
327
321
|
if (onChange) onChange(clamp(stepValue(v, step), min, max));
|
|
328
322
|
}, { passive: false }, [
|
|
329
323
|
wheel,
|
|
@@ -435,20 +429,12 @@ function DragObserver(props) {
|
|
|
435
429
|
*
|
|
436
430
|
* @category Knob
|
|
437
431
|
*/
|
|
438
|
-
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) => {
|
|
439
433
|
const valueRef = useRef(0);
|
|
440
434
|
const elmRef = useRef(null);
|
|
441
|
-
const padding = 8;
|
|
442
|
-
const thumbLineWeight = 6;
|
|
443
|
-
const thumbLineLength = 35;
|
|
444
|
-
const lineWeight = size * .06;
|
|
445
|
-
const p = normalizeValue(value, min, max, skew);
|
|
446
|
-
const s = normalizeValue(startValue, min, max, skew);
|
|
447
435
|
const onDrag = useCallback((_x, y) => {
|
|
448
436
|
if (!onChange || readonly) return;
|
|
449
|
-
|
|
450
|
-
const v2 = clamp(stepValue(v, step), min, max);
|
|
451
|
-
onChange(v2);
|
|
437
|
+
onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
452
438
|
}, [
|
|
453
439
|
max,
|
|
454
440
|
min,
|
|
@@ -459,10 +445,8 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
459
445
|
]);
|
|
460
446
|
const updateValueByEvent = useCallback((eventType, x) => {
|
|
461
447
|
let newValue;
|
|
462
|
-
if (eventType == "normalized")
|
|
463
|
-
|
|
464
|
-
newValue = rawValue(n + x, min, max, skew);
|
|
465
|
-
} else newValue = value + x;
|
|
448
|
+
if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
449
|
+
else newValue = value + x;
|
|
466
450
|
return clamp(stepValue(newValue, step), min, max);
|
|
467
451
|
}, [
|
|
468
452
|
max,
|
|
@@ -512,7 +496,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
512
496
|
readonly,
|
|
513
497
|
updateValueByEvent
|
|
514
498
|
]);
|
|
515
|
-
useImperativeHandle(
|
|
499
|
+
useImperativeHandle(forwardedRef, () => {
|
|
516
500
|
return {
|
|
517
501
|
focus() {
|
|
518
502
|
elmRef.current?.focus();
|
|
@@ -522,11 +506,15 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
522
506
|
}
|
|
523
507
|
};
|
|
524
508
|
}, []);
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
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;
|
|
530
518
|
const x1 = center + center * Math.cos(radian(r1 - 90));
|
|
531
519
|
const y1 = center + center * Math.sin(radian(r1 - 90));
|
|
532
520
|
const x2 = center + center * Math.cos(radian(r2 - 90));
|
|
@@ -536,12 +524,13 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
536
524
|
const x4 = center + center * Math.cos(radian(r4 - 90));
|
|
537
525
|
const y4 = center + center * Math.sin(radian(r4 - 90));
|
|
538
526
|
return /* @__PURE__ */ jsxs("svg", {
|
|
539
|
-
className: clsx("tremolo-knob", className),
|
|
540
527
|
ref: (div) => {
|
|
541
528
|
elmRef.current = div;
|
|
542
529
|
wheelRefCallback(div);
|
|
543
530
|
touchMoveRefCallback(div);
|
|
544
531
|
},
|
|
532
|
+
className: clsx("tremolo-knob", className),
|
|
533
|
+
viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
|
|
545
534
|
width: size,
|
|
546
535
|
height: size,
|
|
547
536
|
tabIndex: 0,
|
|
@@ -572,13 +561,6 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
572
561
|
stroke: inactiveLine || "currentColor",
|
|
573
562
|
strokeWidth: lineWeight
|
|
574
563
|
}),
|
|
575
|
-
/* @__PURE__ */ jsx("path", {
|
|
576
|
-
className: clsx("tremolo-knob-active-line", classes?.activeLine),
|
|
577
|
-
d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
|
|
578
|
-
fill: "none",
|
|
579
|
-
stroke: activeLine || "currentColor",
|
|
580
|
-
strokeWidth: lineWeight
|
|
581
|
-
}),
|
|
582
564
|
startValue < max && /* @__PURE__ */ jsx("path", {
|
|
583
565
|
className: clsx("tremolo-knob-inactive-line", classes?.inactiveLine),
|
|
584
566
|
d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`,
|
|
@@ -586,12 +568,19 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
586
568
|
stroke: inactiveLine || "currentColor",
|
|
587
569
|
strokeWidth: lineWeight
|
|
588
570
|
}),
|
|
571
|
+
/* @__PURE__ */ jsx("path", {
|
|
572
|
+
className: clsx("tremolo-knob-active-line", classes?.activeLine),
|
|
573
|
+
d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
|
|
574
|
+
fill: "none",
|
|
575
|
+
stroke: activeLine || "currentColor",
|
|
576
|
+
strokeWidth: lineWeight
|
|
577
|
+
}),
|
|
589
578
|
/* @__PURE__ */ jsxs("svg", {
|
|
590
579
|
className: clsx("tremolo-knob-thumb", classes?.thumb),
|
|
591
580
|
children: [/* @__PURE__ */ jsx("circle", {
|
|
592
581
|
cx: "50%",
|
|
593
582
|
cy: "50%",
|
|
594
|
-
r: `${
|
|
583
|
+
r: `${thumbSize / 2}%`,
|
|
595
584
|
fill: thumb || "currentColor"
|
|
596
585
|
}), /* @__PURE__ */ jsx("line", {
|
|
597
586
|
className: clsx("tremolo-knob-thumb-line", classes?.thumbLine),
|
|
@@ -602,7 +591,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
|
|
|
602
591
|
stroke: thumbLine || "currentColor",
|
|
603
592
|
strokeWidth: `${thumbLineWeight}%`,
|
|
604
593
|
style: {
|
|
605
|
-
transform: `rotate(${
|
|
594
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
606
595
|
transformOrigin: "50% 50%"
|
|
607
596
|
}
|
|
608
597
|
})]
|
|
@@ -675,8 +664,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
675
664
|
valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
|
|
676
665
|
increment: () => set((state) => {
|
|
677
666
|
if (state.readonly) return {};
|
|
678
|
-
|
|
679
|
-
let next = current + (state.step ?? 1);
|
|
667
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue + (state.step ?? 1);
|
|
680
668
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
681
669
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
682
670
|
state?.onChange?.(next, v);
|
|
@@ -687,8 +675,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
687
675
|
}),
|
|
688
676
|
decrement: () => set((state) => {
|
|
689
677
|
if (state.readonly) return {};
|
|
690
|
-
|
|
691
|
-
let next = current - (state.step ?? 1);
|
|
678
|
+
let next = parseValue(state.value, state.units, state.digit).rawValue - (state.step ?? 1);
|
|
692
679
|
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
693
680
|
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
694
681
|
state?.onChange?.(next, v);
|
|
@@ -733,6 +720,111 @@ function useNumberInputContext(selector) {
|
|
|
733
720
|
return useStore(store, selector);
|
|
734
721
|
}
|
|
735
722
|
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/hooks/useInterval.ts
|
|
725
|
+
/**
|
|
726
|
+
* @category hooks
|
|
727
|
+
*/
|
|
728
|
+
function useInterval(callback, delay) {
|
|
729
|
+
const savedCallback = useRef(callback);
|
|
730
|
+
useEffect(() => {
|
|
731
|
+
savedCallback.current = callback;
|
|
732
|
+
}, [callback]);
|
|
733
|
+
useEffect(() => {
|
|
734
|
+
if (delay === null) return;
|
|
735
|
+
const id = setInterval(() => {
|
|
736
|
+
savedCallback.current();
|
|
737
|
+
}, delay);
|
|
738
|
+
return () => {
|
|
739
|
+
clearInterval(id);
|
|
740
|
+
};
|
|
741
|
+
}, [delay]);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/hooks/useLongPress.ts
|
|
746
|
+
/**
|
|
747
|
+
* @category hooks
|
|
748
|
+
*/
|
|
749
|
+
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
750
|
+
const [pressed, setPressed] = useState(false);
|
|
751
|
+
const [delay, setDelay] = useState(initialDelay);
|
|
752
|
+
useInterval(() => {
|
|
753
|
+
callback();
|
|
754
|
+
setDelay(interval);
|
|
755
|
+
}, pressed ? delay : null);
|
|
756
|
+
useEventListener(globalThis.window, "pointerup", () => {
|
|
757
|
+
setPressed(false);
|
|
758
|
+
setDelay(initialDelay);
|
|
759
|
+
});
|
|
760
|
+
return useCallback(() => {
|
|
761
|
+
callback();
|
|
762
|
+
setPressed(true);
|
|
763
|
+
}, [callback]);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region src/components/NumberInput/DecrementStepper.tsx
|
|
768
|
+
/** @category NumberInput */
|
|
769
|
+
function DecrementStepper({ size = 12, children, className,...props }) {
|
|
770
|
+
const min = useNumberInputContext((s) => s.min);
|
|
771
|
+
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
772
|
+
const readonly = useNumberInputContext((s) => s.readonly);
|
|
773
|
+
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
774
|
+
const press = useLongPress(useNumberInputContext((s) => s.decrement));
|
|
775
|
+
return /* @__PURE__ */ jsx("div", {
|
|
776
|
+
className: clsx("tremolo-number-input-decrement-stepper", className),
|
|
777
|
+
role: "button",
|
|
778
|
+
tabIndex: -1,
|
|
779
|
+
"aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
|
|
780
|
+
"aria-readonly": readonly,
|
|
781
|
+
onPointerDown: press,
|
|
782
|
+
...props,
|
|
783
|
+
children: children || /* @__PURE__ */ jsx("svg", {
|
|
784
|
+
width: size,
|
|
785
|
+
height: size,
|
|
786
|
+
viewBox: "0 0 24 24",
|
|
787
|
+
fill: "none",
|
|
788
|
+
stroke: "currentColor",
|
|
789
|
+
strokeWidth: "2",
|
|
790
|
+
strokeLinecap: "round",
|
|
791
|
+
strokeLinejoin: "round",
|
|
792
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "6 9 12 15 18 9" })
|
|
793
|
+
})
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region src/components/NumberInput/IncrementStepper.tsx
|
|
799
|
+
/** @category NumberInput */
|
|
800
|
+
function IncrementStepper({ size = 12, children, className,...props }) {
|
|
801
|
+
const max = useNumberInputContext((s) => s.max);
|
|
802
|
+
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
803
|
+
const readonly = useNumberInputContext((s) => s.readonly);
|
|
804
|
+
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
805
|
+
const press = useLongPress(useNumberInputContext((s) => s.increment));
|
|
806
|
+
return /* @__PURE__ */ jsx("div", {
|
|
807
|
+
className: clsx("tremolo-number-input-increment-stepper", className),
|
|
808
|
+
role: "button",
|
|
809
|
+
tabIndex: -1,
|
|
810
|
+
"aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
|
|
811
|
+
"aria-readonly": readonly,
|
|
812
|
+
onPointerDown: press,
|
|
813
|
+
...props,
|
|
814
|
+
children: children || /* @__PURE__ */ jsx("svg", {
|
|
815
|
+
width: size,
|
|
816
|
+
height: size,
|
|
817
|
+
viewBox: "0 0 24 24",
|
|
818
|
+
fill: "none",
|
|
819
|
+
stroke: "currentColor",
|
|
820
|
+
strokeWidth: "2",
|
|
821
|
+
strokeLinecap: "round",
|
|
822
|
+
strokeLinejoin: "round",
|
|
823
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "18 15 12 9 6 15" })
|
|
824
|
+
})
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
736
828
|
//#endregion
|
|
737
829
|
//#region src/components/NumberInput/InternalInput.tsx
|
|
738
830
|
const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children,...props }, ref) => {
|
|
@@ -760,8 +852,7 @@ const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnE
|
|
|
760
852
|
let newValue;
|
|
761
853
|
if (eventType == "normalized") {
|
|
762
854
|
if (!min || !max) throw new Error("required parameter: min, max");
|
|
763
|
-
|
|
764
|
-
newValue = rawValue(n + x, min, max);
|
|
855
|
+
newValue = rawValue(normalizeValue(valueAsNumber, min, max) + x, min, max);
|
|
765
856
|
return clamp(stepValue(newValue, step), min, max);
|
|
766
857
|
} else {
|
|
767
858
|
newValue = valueAsNumber + x;
|
|
@@ -875,114 +966,9 @@ function Stepper({ dynamic = true, children, className,...props }) {
|
|
|
875
966
|
});
|
|
876
967
|
}
|
|
877
968
|
|
|
878
|
-
//#endregion
|
|
879
|
-
//#region src/hooks/useInterval.ts
|
|
880
|
-
function useInterval(callback, delay) {
|
|
881
|
-
const savedCallback = useRef(callback);
|
|
882
|
-
useEffect(() => {
|
|
883
|
-
savedCallback.current = callback;
|
|
884
|
-
}, [callback]);
|
|
885
|
-
useEffect(() => {
|
|
886
|
-
if (delay === null) return;
|
|
887
|
-
const id = setInterval(() => {
|
|
888
|
-
savedCallback.current();
|
|
889
|
-
}, delay);
|
|
890
|
-
return () => {
|
|
891
|
-
clearInterval(id);
|
|
892
|
-
};
|
|
893
|
-
}, [delay]);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
//#endregion
|
|
897
|
-
//#region src/hooks/useLongPress.ts
|
|
898
|
-
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
899
|
-
const [pressed, setPressed] = useState(false);
|
|
900
|
-
const [delay, setDelay] = useState(initialDelay);
|
|
901
|
-
useInterval(() => {
|
|
902
|
-
callback();
|
|
903
|
-
setDelay(interval);
|
|
904
|
-
}, pressed ? delay : null);
|
|
905
|
-
useEventListener(globalThis.window, "pointerup", () => {
|
|
906
|
-
setPressed(false);
|
|
907
|
-
setDelay(initialDelay);
|
|
908
|
-
});
|
|
909
|
-
return useCallback(() => {
|
|
910
|
-
callback();
|
|
911
|
-
setPressed(true);
|
|
912
|
-
}, [callback]);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
//#endregion
|
|
916
|
-
//#region src/components/NumberInput/IncrementStepper.tsx
|
|
917
|
-
/** @category NumberInput */
|
|
918
|
-
function IncrementStepper({ size = 12, children, className,...props }) {
|
|
919
|
-
const max = useNumberInputContext((s) => s.max);
|
|
920
|
-
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
921
|
-
const readonly = useNumberInputContext((s) => s.readonly);
|
|
922
|
-
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
923
|
-
const increment = useNumberInputContext((s) => s.increment);
|
|
924
|
-
const press = useLongPress(increment);
|
|
925
|
-
return /* @__PURE__ */ jsx("div", {
|
|
926
|
-
className: clsx("tremolo-number-input-increment-stepper", className),
|
|
927
|
-
role: "button",
|
|
928
|
-
tabIndex: -1,
|
|
929
|
-
"aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
|
|
930
|
-
"aria-readonly": readonly,
|
|
931
|
-
onPointerDown: press,
|
|
932
|
-
...props,
|
|
933
|
-
children: children || /* @__PURE__ */ jsx("svg", {
|
|
934
|
-
width: size,
|
|
935
|
-
height: size,
|
|
936
|
-
viewBox: "0 0 24 24",
|
|
937
|
-
fill: "none",
|
|
938
|
-
stroke: "currentColor",
|
|
939
|
-
strokeWidth: "2",
|
|
940
|
-
strokeLinecap: "round",
|
|
941
|
-
strokeLinejoin: "round",
|
|
942
|
-
children: /* @__PURE__ */ jsx("polyline", { points: "18 15 12 9 6 15" })
|
|
943
|
-
})
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
//#endregion
|
|
948
|
-
//#region src/components/NumberInput/DecrementStepper.tsx
|
|
949
|
-
/** @category NumberInput */
|
|
950
|
-
function DecrementStepper({ size = 12, children, className,...props }) {
|
|
951
|
-
const min = useNumberInputContext((s) => s.min);
|
|
952
|
-
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
953
|
-
const readonly = useNumberInputContext((s) => s.readonly);
|
|
954
|
-
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
|
|
955
|
-
const decrement = useNumberInputContext((s) => s.decrement);
|
|
956
|
-
const press = useLongPress(decrement);
|
|
957
|
-
return /* @__PURE__ */ jsx("div", {
|
|
958
|
-
className: clsx("tremolo-number-input-decrement-stepper", className),
|
|
959
|
-
role: "button",
|
|
960
|
-
tabIndex: -1,
|
|
961
|
-
"aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
|
|
962
|
-
"aria-readonly": readonly,
|
|
963
|
-
onPointerDown: press,
|
|
964
|
-
...props,
|
|
965
|
-
children: children || /* @__PURE__ */ jsx("svg", {
|
|
966
|
-
width: size,
|
|
967
|
-
height: size,
|
|
968
|
-
viewBox: "0 0 24 24",
|
|
969
|
-
fill: "none",
|
|
970
|
-
stroke: "currentColor",
|
|
971
|
-
strokeWidth: "2",
|
|
972
|
-
strokeLinecap: "round",
|
|
973
|
-
strokeLinejoin: "round",
|
|
974
|
-
children: /* @__PURE__ */ jsx("polyline", { points: "6 9 12 15 18 9" })
|
|
975
|
-
})
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
|
|
979
969
|
//#endregion
|
|
980
970
|
//#region src/components/NumberInput/index.tsx
|
|
981
|
-
|
|
982
|
-
* Input with some useful functions for entering numerical values.
|
|
983
|
-
* @category NumberInput
|
|
984
|
-
*/
|
|
985
|
-
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 }, ref) => {
|
|
971
|
+
const NumberInputImpl = 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) => {
|
|
986
972
|
const colors = { "--active-color": activeColor };
|
|
987
973
|
return /* @__PURE__ */ jsx(NumberInputProvider, {
|
|
988
974
|
value: String(value),
|
|
@@ -1001,7 +987,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
|
|
|
1001
987
|
"data-stepper": !!children,
|
|
1002
988
|
"data-variant": variant,
|
|
1003
989
|
children: [/* @__PURE__ */ jsx(InternalInput, {
|
|
1004
|
-
ref,
|
|
990
|
+
ref: forwardedRef,
|
|
1005
991
|
readonly,
|
|
1006
992
|
disabled,
|
|
1007
993
|
selectWithFocus,
|
|
@@ -1018,10 +1004,20 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
|
|
|
1018
1004
|
})
|
|
1019
1005
|
});
|
|
1020
1006
|
});
|
|
1007
|
+
/**
|
|
1008
|
+
* Input with some useful functions for entering numerical values.
|
|
1009
|
+
* @category NumberInput
|
|
1010
|
+
*/
|
|
1011
|
+
const NumberInput = Object.assign(NumberInputImpl, {
|
|
1012
|
+
Stepper,
|
|
1013
|
+
IncrementStepper,
|
|
1014
|
+
DecrementStepper
|
|
1015
|
+
});
|
|
1021
1016
|
|
|
1022
1017
|
//#endregion
|
|
1023
1018
|
//#region src/hooks/useDragWithElement.ts
|
|
1024
1019
|
/**
|
|
1020
|
+
* @category hooks
|
|
1025
1021
|
* @returns [refCallback, pointerDownHandler]
|
|
1026
1022
|
*/
|
|
1027
1023
|
function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
@@ -1101,9 +1097,7 @@ function usePianoContext(selector) {
|
|
|
1101
1097
|
//#region src/components/Piano/KeyLabel.tsx
|
|
1102
1098
|
/** @category Piano */
|
|
1103
1099
|
function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
|
|
1104
|
-
const
|
|
1105
|
-
const noteRangeArray = getNoteRangeArray(noteRange);
|
|
1106
|
-
const index = noteRangeArray.indexOf(__note);
|
|
1100
|
+
const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
|
|
1107
1101
|
const content = label ? label(__note, index) : __label && __label(__note, index);
|
|
1108
1102
|
return (content != void 0 || content != null) && /* @__PURE__ */ jsx("div", {
|
|
1109
1103
|
className: clsx("tremolo-piano-key-label-wrapper", wrapperClassName),
|
|
@@ -1129,8 +1123,7 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
|
|
|
1129
1123
|
const onPlayNote = usePianoContext((s) => s.onPlayNote);
|
|
1130
1124
|
const onStopNote = usePianoContext((s) => s.onStopNote);
|
|
1131
1125
|
const label = usePianoContext((s) => s.label);
|
|
1132
|
-
const
|
|
1133
|
-
const position = notePosition(noteNumber);
|
|
1126
|
+
const position = usePianoContext((s) => s.notePosition)(noteNumber);
|
|
1134
1127
|
const disabled = noteNumber > midiMax;
|
|
1135
1128
|
const colors = {
|
|
1136
1129
|
"--bg": bg,
|
|
@@ -1140,10 +1133,10 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
|
|
|
1140
1133
|
};
|
|
1141
1134
|
useImperativeHandle(ref, () => {
|
|
1142
1135
|
return {
|
|
1143
|
-
play() {
|
|
1136
|
+
play(velocity) {
|
|
1144
1137
|
if (disabled) return;
|
|
1145
1138
|
setPlayed(true);
|
|
1146
|
-
if (onPlayNote) onPlayNote(noteNumber);
|
|
1139
|
+
if (onPlayNote) onPlayNote(noteNumber, velocity);
|
|
1147
1140
|
},
|
|
1148
1141
|
stop() {
|
|
1149
1142
|
setPlayed(false);
|
|
@@ -1234,21 +1227,14 @@ const SHORTCUTS = { HOME_ROW: { keys: [
|
|
|
1234
1227
|
//#endregion
|
|
1235
1228
|
//#region src/components/Piano/index.tsx
|
|
1236
1229
|
/**
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1239
|
-
* TODO:
|
|
1240
|
-
* - add scale highlight
|
|
1230
|
+
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
1231
|
+
* @category Piano
|
|
1241
1232
|
*/
|
|
1242
|
-
/** @category Piano */
|
|
1243
1233
|
function getNoteRangeArray(noteRange) {
|
|
1244
1234
|
return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
1245
1235
|
}
|
|
1246
1236
|
const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
|
|
1247
|
-
|
|
1248
|
-
* Customizable piano component.
|
|
1249
|
-
* @category Piano
|
|
1250
|
-
*/
|
|
1251
|
-
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 }) {
|
|
1237
|
+
const PianoImpl = 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) => {
|
|
1252
1238
|
const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth);
|
|
1253
1239
|
const keyRefs = useRef([]);
|
|
1254
1240
|
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = createRef();
|
|
@@ -1319,9 +1305,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1319
1305
|
]);
|
|
1320
1306
|
const onDrag = useCallback((perX, perY) => {
|
|
1321
1307
|
if (!pianoRef.current) return;
|
|
1322
|
-
const
|
|
1323
|
-
const y = perY * pianoRef.current.clientHeight;
|
|
1324
|
-
const note = getHitKeyIndex(x, y);
|
|
1308
|
+
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1325
1309
|
const index = noteRangeArray.indexOf(note);
|
|
1326
1310
|
if (index == -1) return;
|
|
1327
1311
|
if (hitKeyIndex.current != index) {
|
|
@@ -1375,6 +1359,25 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1375
1359
|
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1376
1360
|
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1377
1361
|
});
|
|
1362
|
+
useImperativeHandle(forwardedRef, () => {
|
|
1363
|
+
return {
|
|
1364
|
+
playNote(note, velocity) {
|
|
1365
|
+
const index = noteRangeArray.indexOf(note);
|
|
1366
|
+
if (index != -1) {
|
|
1367
|
+
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
|
|
1368
|
+
} else onPlayNote?.(note, velocity);
|
|
1369
|
+
},
|
|
1370
|
+
stopNote(note) {
|
|
1371
|
+
const index = noteRangeArray.indexOf(note);
|
|
1372
|
+
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1373
|
+
else onStopNote?.(note);
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
}, [
|
|
1377
|
+
noteRangeArray,
|
|
1378
|
+
onPlayNote,
|
|
1379
|
+
onStopNote
|
|
1380
|
+
]);
|
|
1378
1381
|
return /* @__PURE__ */ jsx(PianoProvider, {
|
|
1379
1382
|
notePosition,
|
|
1380
1383
|
noteRange,
|
|
@@ -1411,7 +1414,16 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
|
|
|
1411
1414
|
}, note))
|
|
1412
1415
|
})
|
|
1413
1416
|
});
|
|
1414
|
-
}
|
|
1417
|
+
});
|
|
1418
|
+
/**
|
|
1419
|
+
* Customizable piano component.
|
|
1420
|
+
* @category Piano
|
|
1421
|
+
*/
|
|
1422
|
+
const Piano = Object.assign(PianoImpl, {
|
|
1423
|
+
WhiteKey,
|
|
1424
|
+
BlackKey,
|
|
1425
|
+
KeyLabel
|
|
1426
|
+
});
|
|
1415
1427
|
|
|
1416
1428
|
//#endregion
|
|
1417
1429
|
//#region src/components/Slider/context.tsx
|
|
@@ -1548,7 +1560,7 @@ function Scale({ gap = 6, options, children, className, style,...props }) {
|
|
|
1548
1560
|
/** @category Slider */
|
|
1549
1561
|
const defaultThumbSize = 22;
|
|
1550
1562
|
/** @category Slider */
|
|
1551
|
-
const
|
|
1563
|
+
const Thumb$1 = forwardRef(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent }, ref) => {
|
|
1552
1564
|
const wrapperRef = useRef(null);
|
|
1553
1565
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1554
1566
|
const disabled = useSliderContext((s) => s.disabled);
|
|
@@ -1590,7 +1602,7 @@ const SliderThumb = forwardRef(({ size, width = defaultThumbSize, height = defau
|
|
|
1590
1602
|
const defaultLength = 140;
|
|
1591
1603
|
const defaultThickness = 10;
|
|
1592
1604
|
/** @category Slider */
|
|
1593
|
-
function
|
|
1605
|
+
function Track({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0,...props }) {
|
|
1594
1606
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1595
1607
|
const reverse = useSliderContext((s) => s.reverse);
|
|
1596
1608
|
const disabled = useSliderContext((s) => s.disabled);
|
|
@@ -1605,7 +1617,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1605
1617
|
"data-vertical": vertical,
|
|
1606
1618
|
style: !defaultStyle ? style : {
|
|
1607
1619
|
...colors,
|
|
1608
|
-
background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive
|
|
1620
|
+
background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
|
|
1609
1621
|
borderRadius: styleHelper(thickness, "/", 2),
|
|
1610
1622
|
width: !vertical ? length : thickness,
|
|
1611
1623
|
height: vertical ? length : thickness,
|
|
@@ -1618,11 +1630,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
|
|
|
1618
1630
|
|
|
1619
1631
|
//#endregion
|
|
1620
1632
|
//#region src/components/Slider/index.tsx
|
|
1621
|
-
|
|
1622
|
-
* Customizable slider
|
|
1623
|
-
* @category Slider
|
|
1624
|
-
*/
|
|
1625
|
-
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 }, ref) => {
|
|
1633
|
+
const SliderImpl = 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) => {
|
|
1626
1634
|
const trackElementRef = useRef(null);
|
|
1627
1635
|
const thumbRef = useRef(null);
|
|
1628
1636
|
const p = toFixed(normalizeValue(value, min, max, skew) * 100);
|
|
@@ -1633,8 +1641,8 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1633
1641
|
let thumbProps = {};
|
|
1634
1642
|
let scaleComponent = void 0;
|
|
1635
1643
|
if (children != void 0) React.Children.forEach(children, (child) => {
|
|
1636
|
-
if (React.isValidElement(child)) if (child.type ==
|
|
1637
|
-
else if (child.type ==
|
|
1644
|
+
if (React.isValidElement(child)) if (child.type == Thumb$1) thumbProps = child.props;
|
|
1645
|
+
else if (child.type == Track) trackProps = child.props;
|
|
1638
1646
|
else if (child.type == Scale) scaleComponent = child;
|
|
1639
1647
|
else throw new Error("only <SliderThumb> or <SliderTrack>");
|
|
1640
1648
|
else throw new Error("children is an invalid element.");
|
|
@@ -1642,9 +1650,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1642
1650
|
const onDrag = useCallback((nx, ny) => {
|
|
1643
1651
|
if (!onChange || readonly) return;
|
|
1644
1652
|
const n = vertical ? ny : nx;
|
|
1645
|
-
|
|
1646
|
-
const v2 = clamp(stepValue(v, step), min, max);
|
|
1647
|
-
onChange(v2);
|
|
1653
|
+
onChange(clamp(stepValue(rawValue(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
|
|
1648
1654
|
}, [
|
|
1649
1655
|
displayReversed,
|
|
1650
1656
|
max,
|
|
@@ -1657,10 +1663,8 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1657
1663
|
]);
|
|
1658
1664
|
const updateValueByEvent = useCallback((eventType, x) => {
|
|
1659
1665
|
let newValue;
|
|
1660
|
-
if (eventType == "normalized")
|
|
1661
|
-
|
|
1662
|
-
newValue = rawValue(n + x, min, max, skew);
|
|
1663
|
-
} else newValue = value + x;
|
|
1666
|
+
if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
1667
|
+
else newValue = value + x;
|
|
1664
1668
|
return clamp(stepValue(newValue, step), min, max);
|
|
1665
1669
|
}, [
|
|
1666
1670
|
max,
|
|
@@ -1723,7 +1727,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1723
1727
|
onChange,
|
|
1724
1728
|
updateValueByEvent
|
|
1725
1729
|
]);
|
|
1726
|
-
useImperativeHandle(
|
|
1730
|
+
useImperativeHandle(forwardedRef, () => {
|
|
1727
1731
|
return {
|
|
1728
1732
|
focus() {
|
|
1729
1733
|
thumbRef.current?.focus();
|
|
@@ -1787,9 +1791,9 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1787
1791
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1788
1792
|
className: "tremolo-slider-track-wrapper",
|
|
1789
1793
|
ref: trackElementRef,
|
|
1790
|
-
children: /* @__PURE__ */ jsx(
|
|
1794
|
+
children: /* @__PURE__ */ jsx(Track, {
|
|
1791
1795
|
__percent: percent,
|
|
1792
|
-
__thumb: /* @__PURE__ */ jsx(
|
|
1796
|
+
__thumb: /* @__PURE__ */ jsx(Thumb$1, {
|
|
1793
1797
|
ref: thumbRef,
|
|
1794
1798
|
__percent: percent,
|
|
1795
1799
|
...thumbProps
|
|
@@ -1801,17 +1805,26 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
|
|
|
1801
1805
|
})
|
|
1802
1806
|
});
|
|
1803
1807
|
});
|
|
1808
|
+
/**
|
|
1809
|
+
* Customizable slider
|
|
1810
|
+
* @category Slider
|
|
1811
|
+
*/
|
|
1812
|
+
const Slider = Object.assign(SliderImpl, {
|
|
1813
|
+
Thumb: Thumb$1,
|
|
1814
|
+
Track,
|
|
1815
|
+
Scale,
|
|
1816
|
+
ScaleOption
|
|
1817
|
+
});
|
|
1804
1818
|
|
|
1805
1819
|
//#endregion
|
|
1806
1820
|
//#region src/components/WheelObserver/index.tsx
|
|
1807
1821
|
/** @category WheelObserver */
|
|
1808
1822
|
function WheelObserver(props) {
|
|
1809
1823
|
const { children, onWheel, as: Component = "div",...attributes } = props;
|
|
1810
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
1811
|
-
if (onWheel) onWheel(event);
|
|
1812
|
-
}, { passive: false }, [onWheel]);
|
|
1813
1824
|
return /* @__PURE__ */ jsx(Component, {
|
|
1814
|
-
ref:
|
|
1825
|
+
ref: useRefCallbackEvent("wheel", (event) => {
|
|
1826
|
+
if (onWheel) onWheel(event);
|
|
1827
|
+
}, { passive: false }, [onWheel]),
|
|
1815
1828
|
...attributes,
|
|
1816
1829
|
children
|
|
1817
1830
|
});
|
|
@@ -1820,7 +1833,7 @@ function WheelObserver(props) {
|
|
|
1820
1833
|
//#endregion
|
|
1821
1834
|
//#region src/components/XYPad/Area.tsx
|
|
1822
1835
|
/** @category XYPad */
|
|
1823
|
-
function
|
|
1836
|
+
function Area({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
|
|
1824
1837
|
return /* @__PURE__ */ jsxs("div", {
|
|
1825
1838
|
className: clsx("tremolo-xy-pad-area", className),
|
|
1826
1839
|
style: {
|
|
@@ -1837,7 +1850,7 @@ function XYPadArea({ width = 120, height = 120, color, children, className, styl
|
|
|
1837
1850
|
//#endregion
|
|
1838
1851
|
//#region src/components/XYPad/Thumb.tsx
|
|
1839
1852
|
/** @category XYPad */
|
|
1840
|
-
const
|
|
1853
|
+
const Thumb = forwardRef(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __readonly, __css,...props }, ref) => {
|
|
1841
1854
|
const wrapperRef = useRef(null);
|
|
1842
1855
|
useImperativeHandle(ref, () => {
|
|
1843
1856
|
return {
|
|
@@ -1861,6 +1874,7 @@ const XYPadThumb = forwardRef(({ size, width = 22, height = 22, color, children,
|
|
|
1861
1874
|
className: clsx("tremolo-xy-pad-thumb", className),
|
|
1862
1875
|
tabIndex: 0,
|
|
1863
1876
|
"aria-disabled": __disabled,
|
|
1877
|
+
"aria-readonly": __readonly,
|
|
1864
1878
|
style: {
|
|
1865
1879
|
"--color": color,
|
|
1866
1880
|
width: size ?? width,
|
|
@@ -1880,11 +1894,7 @@ const defaultValueOptions = {
|
|
|
1880
1894
|
wheel: ["raw", 1],
|
|
1881
1895
|
keyboard: ["raw", 1]
|
|
1882
1896
|
};
|
|
1883
|
-
|
|
1884
|
-
* Simple XYPad
|
|
1885
|
-
* @category XYPad
|
|
1886
|
-
*/
|
|
1887
|
-
const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, ref) => {
|
|
1897
|
+
const XYPadImpl = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
|
|
1888
1898
|
const x = useMemo(() => {
|
|
1889
1899
|
return {
|
|
1890
1900
|
...defaultValueOptions,
|
|
@@ -1906,8 +1916,8 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
1906
1916
|
let areaProps = {};
|
|
1907
1917
|
let thumbProps = {};
|
|
1908
1918
|
if (children != void 0) React.Children.forEach(children, (child) => {
|
|
1909
|
-
if (React.isValidElement(child)) if (child.type ==
|
|
1910
|
-
else if (child.type ==
|
|
1919
|
+
if (React.isValidElement(child)) if (child.type == Thumb) thumbProps = child.props;
|
|
1920
|
+
else if (child.type == Area) areaProps = child.props;
|
|
1911
1921
|
else throw new Error("only <XYPadThumb> or <XYPadArea>");
|
|
1912
1922
|
else throw new Error("children is an invalid element.");
|
|
1913
1923
|
});
|
|
@@ -1915,9 +1925,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
1915
1925
|
if (!onChange || readonly) return;
|
|
1916
1926
|
const vx = rawValue(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
|
|
1917
1927
|
const vy = rawValue(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
|
|
1918
|
-
|
|
1919
|
-
const newY = clamp(stepValue(vy, y.step), y.min, y.max);
|
|
1920
|
-
onChange(newX, newY);
|
|
1928
|
+
onChange(clamp(stepValue(vx, x.step), x.min, x.max), clamp(stepValue(vy, y.step), y.min, y.max));
|
|
1921
1929
|
}, [
|
|
1922
1930
|
onChange,
|
|
1923
1931
|
readonly,
|
|
@@ -1934,10 +1942,8 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
1934
1942
|
]);
|
|
1935
1943
|
const updateValueByEvent = useCallback((eventType, target, x$1) => {
|
|
1936
1944
|
let v;
|
|
1937
|
-
if (eventType == "normalized")
|
|
1938
|
-
|
|
1939
|
-
v = rawValue(n + x$1, target.min, target.max, target.skew);
|
|
1940
|
-
} else v = target.value + x$1;
|
|
1945
|
+
if (eventType == "normalized") v = rawValue(normalizeValue(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
|
|
1946
|
+
else v = target.value + x$1;
|
|
1941
1947
|
return clamp(stepValue(v, target.step), target.min, target.max);
|
|
1942
1948
|
}, []);
|
|
1943
1949
|
const handleKeyDown = useCallback((event) => {
|
|
@@ -2004,7 +2010,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
2004
2010
|
readonly,
|
|
2005
2011
|
updateValueByEvent
|
|
2006
2012
|
]);
|
|
2007
|
-
useImperativeHandle(
|
|
2013
|
+
useImperativeHandle(forwardedRef, () => {
|
|
2008
2014
|
return {
|
|
2009
2015
|
focus() {
|
|
2010
2016
|
thumbRef.current?.focus();
|
|
@@ -2049,10 +2055,11 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
2049
2055
|
children: /* @__PURE__ */ jsx("div", {
|
|
2050
2056
|
className: "tremolo-xy-pad-area-wrapper",
|
|
2051
2057
|
ref: areaElementRef,
|
|
2052
|
-
children: /* @__PURE__ */ jsx(
|
|
2053
|
-
__thumb: /* @__PURE__ */ jsx(
|
|
2058
|
+
children: /* @__PURE__ */ jsx(Area, {
|
|
2059
|
+
__thumb: /* @__PURE__ */ jsx(Thumb, {
|
|
2054
2060
|
ref: thumbRef,
|
|
2055
2061
|
__disabled: disabled,
|
|
2062
|
+
__readonly: readonly,
|
|
2056
2063
|
__css: {
|
|
2057
2064
|
top: `${percentY}%`,
|
|
2058
2065
|
left: `${percentX}%`
|
|
@@ -2064,9 +2071,20 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
|
|
|
2064
2071
|
})
|
|
2065
2072
|
});
|
|
2066
2073
|
});
|
|
2074
|
+
/**
|
|
2075
|
+
* Simple XYPad
|
|
2076
|
+
* @category XYPad
|
|
2077
|
+
*/
|
|
2078
|
+
const XYPad = Object.assign(XYPadImpl, {
|
|
2079
|
+
Thumb,
|
|
2080
|
+
Area
|
|
2081
|
+
});
|
|
2067
2082
|
|
|
2068
2083
|
//#endregion
|
|
2069
2084
|
//#region src/hooks/useAnimationFrame.ts
|
|
2085
|
+
/**
|
|
2086
|
+
* @category hooks
|
|
2087
|
+
*/
|
|
2070
2088
|
function useAnimationFrame(callback = () => {}, deps = []) {
|
|
2071
2089
|
const reqIdRef = useRef(-1);
|
|
2072
2090
|
const loop = useCallback(() => {
|
|
@@ -2080,5 +2098,78 @@ function useAnimationFrame(callback = () => {}, deps = []) {
|
|
|
2080
2098
|
}
|
|
2081
2099
|
|
|
2082
2100
|
//#endregion
|
|
2083
|
-
|
|
2101
|
+
//#region src/hooks/useMIDIAccess.ts
|
|
2102
|
+
/** @private */
|
|
2103
|
+
const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
2104
|
+
/** @private */
|
|
2105
|
+
const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
2106
|
+
/**
|
|
2107
|
+
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
2108
|
+
* @category hooks
|
|
2109
|
+
*/
|
|
2110
|
+
function useMIDIAccess(requestOnMount = true) {
|
|
2111
|
+
const [midiAccess, setMidiAccess] = useState(null);
|
|
2112
|
+
const [error, setError] = useState(null);
|
|
2113
|
+
const request = useCallback(() => {
|
|
2114
|
+
if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
|
|
2115
|
+
setMidiAccess(access);
|
|
2116
|
+
setError(null);
|
|
2117
|
+
}).catch(() => {
|
|
2118
|
+
setError(PERMISSION_DENIED);
|
|
2119
|
+
});
|
|
2120
|
+
else setError(NOT_SUPPORTED);
|
|
2121
|
+
}, []);
|
|
2122
|
+
useEffect(() => {
|
|
2123
|
+
if (requestOnMount) request();
|
|
2124
|
+
}, [requestOnMount, request]);
|
|
2125
|
+
return {
|
|
2126
|
+
request,
|
|
2127
|
+
midiAccess,
|
|
2128
|
+
error
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
//#endregion
|
|
2133
|
+
//#region src/hooks/useMIDIMessage.ts
|
|
2134
|
+
/**
|
|
2135
|
+
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
2136
|
+
* @category hooks
|
|
2137
|
+
*/
|
|
2138
|
+
function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
2139
|
+
useEffect(() => {
|
|
2140
|
+
if (!midiAccess) return;
|
|
2141
|
+
for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
|
|
2142
|
+
return () => {
|
|
2143
|
+
for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
|
|
2144
|
+
};
|
|
2145
|
+
}, [midiAccess, onMIDIMessage]);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
//#endregion
|
|
2149
|
+
//#region src/hooks/useMIDIInput.ts
|
|
2150
|
+
const MIDI_EVENT_TO_NUMBER = {
|
|
2151
|
+
NOTE_ON: 144,
|
|
2152
|
+
NOTE_OFF: 128,
|
|
2153
|
+
PITCH_BEND: 224
|
|
2154
|
+
};
|
|
2155
|
+
/**
|
|
2156
|
+
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
2157
|
+
* @category hooks
|
|
2158
|
+
*/
|
|
2159
|
+
function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
|
|
2160
|
+
useMIDIMessage(midiAccess, useCallback((event) => {
|
|
2161
|
+
if (!event.data) return;
|
|
2162
|
+
const kind = event.data[0] & 240;
|
|
2163
|
+
if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
|
|
2164
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
|
|
2165
|
+
else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
|
|
2166
|
+
}, [
|
|
2167
|
+
onNoteOffEvent,
|
|
2168
|
+
onNoteOnEvent,
|
|
2169
|
+
onPitchBendEvent
|
|
2170
|
+
]));
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
//#endregion
|
|
2174
|
+
export { AnimationCanvas, AnimationKnob, DragObserver, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, SHORTCUTS, Slider, WheelObserver, XYPad, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useSliderContext };
|
|
2084
2175
|
//# sourceMappingURL=index.js.map
|