@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.
Files changed (40) hide show
  1. package/dist/index.cjs +325 -239
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +149 -64
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +229 -199
  6. package/dist/index.d.cts.map +1 -1
  7. package/dist/index.d.ts +229 -199
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +314 -223
  10. package/dist/index.js.map +1 -1
  11. package/package.json +2 -2
  12. package/src/components/Knob/index.css +50 -25
  13. package/src/components/Knob/index.tsx +37 -32
  14. package/src/components/NumberInput/index.css +31 -15
  15. package/src/components/NumberInput/index.tsx +22 -14
  16. package/src/components/Piano/context.tsx +1 -1
  17. package/src/components/Piano/index.css +17 -8
  18. package/src/components/Piano/index.tsx +259 -220
  19. package/src/components/Piano/key.tsx +3 -3
  20. package/src/components/Slider/Thumb.tsx +1 -1
  21. package/src/components/Slider/Track.tsx +3 -3
  22. package/src/components/Slider/index.css +33 -8
  23. package/src/components/Slider/index.tsx +25 -25
  24. package/src/components/XYPad/Area.tsx +4 -3
  25. package/src/components/XYPad/Thumb.tsx +8 -4
  26. package/src/components/XYPad/index.css +18 -4
  27. package/src/components/XYPad/index.tsx +25 -19
  28. package/src/hooks/useAnimationFrame.ts +3 -0
  29. package/src/hooks/useCallbackRef.ts +2 -2
  30. package/src/hooks/useDrag.ts +2 -1
  31. package/src/hooks/useDragWithElement.ts +2 -1
  32. package/src/hooks/useEventListener.ts +3 -0
  33. package/src/hooks/useInterval.ts +3 -0
  34. package/src/hooks/useLongPress.ts +3 -0
  35. package/src/hooks/useMIDIAccess.ts +40 -0
  36. package/src/hooks/useMIDIInput.ts +50 -0
  37. package/src/hooks/useMIDIMessage.ts +24 -0
  38. package/src/hooks/useRefCallbackEvent.ts +4 -0
  39. package/src/index.ts +30 -35
  40. 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
- * This hook is user-land implementation of the experimental `useEffectEvent` hook.
148
- * React docs: https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event
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
- const node = typeof target === "function" ? target() : target ?? document;
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
- const v = rawValue(valueRef.current - y / 100, min, max, skew);
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
- const n = normalizeValue(value, min, max, skew);
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
- const n = normalizeValue(value, min, max, skew);
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 }, ref) => {
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
- const v = rawValue(valueRef.current - y / 100, min, max, skew);
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
- const n = normalizeValue(value, min, max, skew);
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(ref, () => {
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 center = size / 2;
526
- const r1 = -135;
527
- const r2 = -135 + Math.min(p, s) * 270;
528
- const r3 = -135 + Math.max(p, s) * 270;
529
- const r4 = 135;
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: `${50 - padding}%`,
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(${-135 + p * 270}deg)`,
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
- const current = parseValue(state.value, state.units, state.digit).rawValue;
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
- const current = parseValue(state.value, state.units, state.digit).rawValue;
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
- const n = normalizeValue(valueAsNumber, min, max);
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 noteRange = usePianoContext((s) => s.noteRange);
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 notePosition = usePianoContext((s) => s.notePosition);
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
- * Piano component
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 x = perX * staticWidth;
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 SliderThumb = forwardRef(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent }, ref) => {
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 SliderTrack({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0,...props }) {
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, #eee) ${__percent}%, var(--active, #7998ec) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active, #7998ec) ${__percent}%, var(--inactive, #eee) ${__percent}%)`,
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 == SliderThumb) thumbProps = child.props;
1637
- else if (child.type == SliderTrack) trackProps = child.props;
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
- const v = rawValue(displayReversed ? 1 - n : n, min, max, skew);
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
- const n = normalizeValue(value, min, max, skew);
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(ref, () => {
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(SliderTrack, {
1794
+ children: /* @__PURE__ */ jsx(Track, {
1791
1795
  __percent: percent,
1792
- __thumb: /* @__PURE__ */ jsx(SliderThumb, {
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: wheelRefCallback,
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 XYPadArea({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
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 XYPadThumb = forwardRef(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __css,...props }, ref) => {
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 == XYPadThumb) thumbProps = child.props;
1910
- else if (child.type == XYPadArea) areaProps = child.props;
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
- const newX = clamp(stepValue(vx, x.step), x.min, x.max);
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
- const n = normalizeValue(target.value, target.min, target.max, target.skew);
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(ref, () => {
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(XYPadArea, {
2053
- __thumb: /* @__PURE__ */ jsx(XYPadThumb, {
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
- export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NumberInput, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useSliderContext };
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