@tremolo-ui/react 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,
@@ -591,7 +580,7 @@ const Knob = forwardRef(({ value, min, max, step = 1, skew = 1, defaultValue = m
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);
@@ -760,8 +747,7 @@ const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnE
760
747
  let newValue;
761
748
  if (eventType == "normalized") {
762
749
  if (!min || !max) throw new Error("required parameter: min, max");
763
- const n = normalizeValue(valueAsNumber, min, max);
764
- newValue = rawValue(n + x, min, max);
750
+ newValue = rawValue(normalizeValue(valueAsNumber, min, max) + x, min, max);
765
751
  return clamp(stepValue(newValue, step), min, max);
766
752
  } else {
767
753
  newValue = valueAsNumber + x;
@@ -877,6 +863,9 @@ function Stepper({ dynamic = true, children, className,...props }) {
877
863
 
878
864
  //#endregion
879
865
  //#region src/hooks/useInterval.ts
866
+ /**
867
+ * @category hooks
868
+ */
880
869
  function useInterval(callback, delay) {
881
870
  const savedCallback = useRef(callback);
882
871
  useEffect(() => {
@@ -895,6 +884,9 @@ function useInterval(callback, delay) {
895
884
 
896
885
  //#endregion
897
886
  //#region src/hooks/useLongPress.ts
887
+ /**
888
+ * @category hooks
889
+ */
898
890
  function useLongPress(callback, initialDelay = 500, interval = 40) {
899
891
  const [pressed, setPressed] = useState(false);
900
892
  const [delay, setDelay] = useState(initialDelay);
@@ -920,8 +912,7 @@ function IncrementStepper({ size = 12, children, className,...props }) {
920
912
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
921
913
  const readonly = useNumberInputContext((s) => s.readonly);
922
914
  const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
923
- const increment = useNumberInputContext((s) => s.increment);
924
- const press = useLongPress(increment);
915
+ const press = useLongPress(useNumberInputContext((s) => s.increment));
925
916
  return /* @__PURE__ */ jsx("div", {
926
917
  className: clsx("tremolo-number-input-increment-stepper", className),
927
918
  role: "button",
@@ -952,8 +943,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
952
943
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
953
944
  const readonly = useNumberInputContext((s) => s.readonly);
954
945
  const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
955
- const decrement = useNumberInputContext((s) => s.decrement);
956
- const press = useLongPress(decrement);
946
+ const press = useLongPress(useNumberInputContext((s) => s.decrement));
957
947
  return /* @__PURE__ */ jsx("div", {
958
948
  className: clsx("tremolo-number-input-decrement-stepper", className),
959
949
  role: "button",
@@ -982,7 +972,7 @@ function DecrementStepper({ size = 12, children, className,...props }) {
982
972
  * Input with some useful functions for entering numerical values.
983
973
  * @category NumberInput
984
974
  */
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) => {
975
+ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props }, forwardedRef) => {
986
976
  const colors = { "--active-color": activeColor };
987
977
  return /* @__PURE__ */ jsx(NumberInputProvider, {
988
978
  value: String(value),
@@ -1001,7 +991,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
1001
991
  "data-stepper": !!children,
1002
992
  "data-variant": variant,
1003
993
  children: [/* @__PURE__ */ jsx(InternalInput, {
1004
- ref,
994
+ ref: forwardedRef,
1005
995
  readonly,
1006
996
  disabled,
1007
997
  selectWithFocus,
@@ -1022,6 +1012,7 @@ const NumberInput = forwardRef(({ value, min, max, step = 1, units, readonly = f
1022
1012
  //#endregion
1023
1013
  //#region src/hooks/useDragWithElement.ts
1024
1014
  /**
1015
+ * @category hooks
1025
1016
  * @returns [refCallback, pointerDownHandler]
1026
1017
  */
1027
1018
  function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
@@ -1101,9 +1092,7 @@ function usePianoContext(selector) {
1101
1092
  //#region src/components/Piano/KeyLabel.tsx
1102
1093
  /** @category Piano */
1103
1094
  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);
1095
+ const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
1107
1096
  const content = label ? label(__note, index) : __label && __label(__note, index);
1108
1097
  return (content != void 0 || content != null) && /* @__PURE__ */ jsx("div", {
1109
1098
  className: clsx("tremolo-piano-key-label-wrapper", wrapperClassName),
@@ -1129,8 +1118,7 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
1129
1118
  const onPlayNote = usePianoContext((s) => s.onPlayNote);
1130
1119
  const onStopNote = usePianoContext((s) => s.onStopNote);
1131
1120
  const label = usePianoContext((s) => s.label);
1132
- const notePosition = usePianoContext((s) => s.notePosition);
1133
- const position = notePosition(noteNumber);
1121
+ const position = usePianoContext((s) => s.notePosition)(noteNumber);
1134
1122
  const disabled = noteNumber > midiMax;
1135
1123
  const colors = {
1136
1124
  "--bg": bg,
@@ -1140,10 +1128,10 @@ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, act
1140
1128
  };
1141
1129
  useImperativeHandle(ref, () => {
1142
1130
  return {
1143
- play() {
1131
+ play(velocity) {
1144
1132
  if (disabled) return;
1145
1133
  setPlayed(true);
1146
- if (onPlayNote) onPlayNote(noteNumber);
1134
+ if (onPlayNote) onPlayNote(noteNumber, velocity);
1147
1135
  },
1148
1136
  stop() {
1149
1137
  setPlayed(false);
@@ -1234,12 +1222,9 @@ const SHORTCUTS = { HOME_ROW: { keys: [
1234
1222
  //#endregion
1235
1223
  //#region src/components/Piano/index.tsx
1236
1224
  /**
1237
- * Piano component
1238
- *
1239
- * TODO:
1240
- * - add scale highlight
1225
+ * [noteRange.first, noteRange.first + 1 ..., noteRange.last]
1226
+ * @category Piano
1241
1227
  */
1242
- /** @category Piano */
1243
1228
  function getNoteRangeArray(noteRange) {
1244
1229
  return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1245
1230
  }
@@ -1248,7 +1233,7 @@ const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
1248
1233
  * Customizable piano component.
1249
1234
  * @category Piano
1250
1235
  */
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 }) {
1236
+ const Piano = forwardRef(({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }, forwardedRef) => {
1252
1237
  const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth);
1253
1238
  const keyRefs = useRef([]);
1254
1239
  for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = createRef();
@@ -1319,9 +1304,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
1319
1304
  ]);
1320
1305
  const onDrag = useCallback((perX, perY) => {
1321
1306
  if (!pianoRef.current) return;
1322
- const x = perX * staticWidth;
1323
- const y = perY * pianoRef.current.clientHeight;
1324
- const note = getHitKeyIndex(x, y);
1307
+ const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
1325
1308
  const index = noteRangeArray.indexOf(note);
1326
1309
  if (index == -1) return;
1327
1310
  if (hitKeyIndex.current != index) {
@@ -1375,6 +1358,25 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
1375
1358
  const index = keyboardShortcuts.keys.indexOf(e.key);
1376
1359
  if (index != -1) keyRefs.current[index]?.current?.stop();
1377
1360
  });
1361
+ useImperativeHandle(forwardedRef, () => {
1362
+ return {
1363
+ playNote(note, velocity) {
1364
+ const index = noteRangeArray.indexOf(note);
1365
+ if (index != -1) {
1366
+ if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
1367
+ } else onPlayNote?.(note, velocity);
1368
+ },
1369
+ stopNote(note) {
1370
+ const index = noteRangeArray.indexOf(note);
1371
+ if (index != -1) keyRefs.current[index]?.current?.stop();
1372
+ else onStopNote?.(note);
1373
+ }
1374
+ };
1375
+ }, [
1376
+ noteRangeArray,
1377
+ onPlayNote,
1378
+ onStopNote
1379
+ ]);
1378
1380
  return /* @__PURE__ */ jsx(PianoProvider, {
1379
1381
  notePosition,
1380
1382
  noteRange,
@@ -1411,7 +1413,7 @@ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts,
1411
1413
  }, note))
1412
1414
  })
1413
1415
  });
1414
- }
1416
+ });
1415
1417
 
1416
1418
  //#endregion
1417
1419
  //#region src/components/Slider/context.tsx
@@ -1605,7 +1607,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1605
1607
  "data-vertical": vertical,
1606
1608
  style: !defaultStyle ? style : {
1607
1609
  ...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}%)`,
1610
+ background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
1609
1611
  borderRadius: styleHelper(thickness, "/", 2),
1610
1612
  width: !vertical ? length : thickness,
1611
1613
  height: vertical ? length : thickness,
@@ -1622,7 +1624,7 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1622
1624
  * Customizable slider
1623
1625
  * @category Slider
1624
1626
  */
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) => {
1627
+ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown,...props }, forwardedRef) => {
1626
1628
  const trackElementRef = useRef(null);
1627
1629
  const thumbRef = useRef(null);
1628
1630
  const p = toFixed(normalizeValue(value, min, max, skew) * 100);
@@ -1642,9 +1644,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1642
1644
  const onDrag = useCallback((nx, ny) => {
1643
1645
  if (!onChange || readonly) return;
1644
1646
  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);
1647
+ onChange(clamp(stepValue(rawValue(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
1648
1648
  }, [
1649
1649
  displayReversed,
1650
1650
  max,
@@ -1657,10 +1657,8 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1657
1657
  ]);
1658
1658
  const updateValueByEvent = useCallback((eventType, x) => {
1659
1659
  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;
1660
+ if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
1661
+ else newValue = value + x;
1664
1662
  return clamp(stepValue(newValue, step), min, max);
1665
1663
  }, [
1666
1664
  max,
@@ -1723,7 +1721,7 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1723
1721
  onChange,
1724
1722
  updateValueByEvent
1725
1723
  ]);
1726
- useImperativeHandle(ref, () => {
1724
+ useImperativeHandle(forwardedRef, () => {
1727
1725
  return {
1728
1726
  focus() {
1729
1727
  thumbRef.current?.focus();
@@ -1807,11 +1805,10 @@ const Slider = forwardRef(({ value, min, max, step = 1, skew = 1, vertical = fal
1807
1805
  /** @category WheelObserver */
1808
1806
  function WheelObserver(props) {
1809
1807
  const { children, onWheel, as: Component = "div",...attributes } = props;
1810
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1811
- if (onWheel) onWheel(event);
1812
- }, { passive: false }, [onWheel]);
1813
1808
  return /* @__PURE__ */ jsx(Component, {
1814
- ref: wheelRefCallback,
1809
+ ref: useRefCallbackEvent("wheel", (event) => {
1810
+ if (onWheel) onWheel(event);
1811
+ }, { passive: false }, [onWheel]),
1815
1812
  ...attributes,
1816
1813
  children
1817
1814
  });
@@ -1884,7 +1881,7 @@ const defaultValueOptions = {
1884
1881
  * Simple XYPad
1885
1882
  * @category XYPad
1886
1883
  */
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) => {
1884
+ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
1888
1885
  const x = useMemo(() => {
1889
1886
  return {
1890
1887
  ...defaultValueOptions,
@@ -1915,9 +1912,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
1915
1912
  if (!onChange || readonly) return;
1916
1913
  const vx = rawValue(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
1917
1914
  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);
1915
+ onChange(clamp(stepValue(vx, x.step), x.min, x.max), clamp(stepValue(vy, y.step), y.min, y.max));
1921
1916
  }, [
1922
1917
  onChange,
1923
1918
  readonly,
@@ -1934,10 +1929,8 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
1934
1929
  ]);
1935
1930
  const updateValueByEvent = useCallback((eventType, target, x$1) => {
1936
1931
  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;
1932
+ if (eventType == "normalized") v = rawValue(normalizeValue(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
1933
+ else v = target.value + x$1;
1941
1934
  return clamp(stepValue(v, target.step), target.min, target.max);
1942
1935
  }, []);
1943
1936
  const handleKeyDown = useCallback((event) => {
@@ -2004,7 +1997,7 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
2004
1997
  readonly,
2005
1998
  updateValueByEvent
2006
1999
  ]);
2007
- useImperativeHandle(ref, () => {
2000
+ useImperativeHandle(forwardedRef, () => {
2008
2001
  return {
2009
2002
  focus() {
2010
2003
  thumbRef.current?.focus();
@@ -2067,6 +2060,9 @@ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true,
2067
2060
 
2068
2061
  //#endregion
2069
2062
  //#region src/hooks/useAnimationFrame.ts
2063
+ /**
2064
+ * @category hooks
2065
+ */
2070
2066
  function useAnimationFrame(callback = () => {}, deps = []) {
2071
2067
  const reqIdRef = useRef(-1);
2072
2068
  const loop = useCallback(() => {
@@ -2080,5 +2076,78 @@ function useAnimationFrame(callback = () => {}, deps = []) {
2080
2076
  }
2081
2077
 
2082
2078
  //#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 };
2079
+ //#region src/hooks/useMIDIAccess.ts
2080
+ /** @private */
2081
+ const PERMISSION_DENIED = "PERMISSION_DENIED";
2082
+ /** @private */
2083
+ const NOT_SUPPORTED = "NOT_SUPPORTED";
2084
+ /**
2085
+ * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
2086
+ * @category hooks
2087
+ */
2088
+ function useMIDIAccess(requestOnMount = true) {
2089
+ const [midiAccess, setMidiAccess] = useState(null);
2090
+ const [error, setError] = useState(null);
2091
+ const request = useCallback(() => {
2092
+ if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
2093
+ setMidiAccess(access);
2094
+ setError(null);
2095
+ }).catch(() => {
2096
+ setError(PERMISSION_DENIED);
2097
+ });
2098
+ else setError(NOT_SUPPORTED);
2099
+ }, []);
2100
+ useEffect(() => {
2101
+ if (requestOnMount) request();
2102
+ }, [requestOnMount, request]);
2103
+ return {
2104
+ request,
2105
+ midiAccess,
2106
+ error
2107
+ };
2108
+ }
2109
+
2110
+ //#endregion
2111
+ //#region src/hooks/useMIDIMessage.ts
2112
+ /**
2113
+ * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2114
+ * @category hooks
2115
+ */
2116
+ function useMIDIMessage(midiAccess, onMIDIMessage) {
2117
+ useEffect(() => {
2118
+ if (!midiAccess) return;
2119
+ for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
2120
+ return () => {
2121
+ for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
2122
+ };
2123
+ }, [midiAccess, onMIDIMessage]);
2124
+ }
2125
+
2126
+ //#endregion
2127
+ //#region src/hooks/useMIDIInput.ts
2128
+ const MIDI_EVENT_TO_NUMBER = {
2129
+ NOTE_ON: 144,
2130
+ NOTE_OFF: 128,
2131
+ PITCH_BEND: 224
2132
+ };
2133
+ /**
2134
+ * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
2135
+ * @category hooks
2136
+ */
2137
+ function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
2138
+ useMIDIMessage(midiAccess, useCallback((event) => {
2139
+ if (!event.data) return;
2140
+ const kind = event.data[0] & 240;
2141
+ if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
2142
+ else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
2143
+ else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
2144
+ }, [
2145
+ onNoteOffEvent,
2146
+ onNoteOnEvent,
2147
+ onPitchBendEvent
2148
+ ]));
2149
+ }
2150
+
2151
+ //#endregion
2152
+ export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useSliderContext };
2084
2153
  //# sourceMappingURL=index.js.map