@tremolo-ui/react 0.2.1 → 0.4.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.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import clsx from "clsx";
2
- import React, { createContext, createRef, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useInsertionEffect, useMemo, useRef, useState } from "react";
2
+ import React, { createContext, createRef, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useInsertionEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { clamp, decimalPart, isBlackKey, isWhiteKey, normalizeValue, noteKey, noteKeys, radian, rawValue, stepValue, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
5
+ import { NOT_SUPPORTED, PERMISSION_DENIED, createDrag, createMIDIAccess, createMIDIInput, createMIDIMessage, createWheel } from "@tremolo-ui/dom";
5
6
  import { createStore, useStore } from "zustand";
6
7
  //#region src/components/AnimationCanvas/canvas.ts
7
8
  const drawingState = [
@@ -154,110 +155,55 @@ function useCallbackRef(callback, deps = []) {
154
155
  return useCallback((...args) => callbackRef.current?.(...args), deps);
155
156
  }
156
157
  //#endregion
157
- //#region src/hooks/useEventListener.ts
158
- function useEventListener(target, event, handler, options) {
159
- const listener = useCallbackRef(handler);
158
+ //#region src/hooks/useDrag.ts
159
+ /**
160
+ * Track a pointer drag on an element.
161
+ *
162
+ * @returns a ref callback to attach to the element being dragged
163
+ */
164
+ function useDrag({ threshold = 1, cursor, onDrag, onDragStart, onDragEnd }) {
165
+ const dragHandler = useCallbackRef(onDrag);
166
+ const dragStartHandler = useCallbackRef(onDragStart);
167
+ const dragEndHandler = useCallbackRef(onDragEnd);
168
+ const [node, setNode] = useState(null);
160
169
  useEffect(() => {
161
- const node = typeof target === "function" ? target() : target ?? document;
162
170
  if (!node) return;
163
- node.addEventListener(event, listener, options);
164
- return () => {
165
- node.removeEventListener(event, listener, options);
166
- };
171
+ const instance = createDrag(node, {
172
+ threshold,
173
+ cursor,
174
+ onDragStart: () => dragStartHandler(),
175
+ onDrag: ({ x, y, deltaX, deltaY }) => dragHandler(x, y, deltaX, deltaY),
176
+ onDragEnd: () => dragEndHandler()
177
+ });
178
+ return () => instance.destroy();
167
179
  }, [
168
- event,
169
- target,
170
- options,
171
- listener
180
+ node,
181
+ threshold,
182
+ cursor,
183
+ dragHandler,
184
+ dragStartHandler,
185
+ dragEndHandler
172
186
  ]);
173
- return () => {
174
- (typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
175
- };
187
+ return setNode;
176
188
  }
177
189
  //#endregion
178
- //#region src/hooks/useRefCallbackEvent.ts
179
- function useRefCallbackEvent(event, handler, options, deps = []) {
180
- return useMemo(() => {
181
- let cleanup;
182
- return (node) => {
183
- if (!node) {
184
- cleanup?.();
185
- return;
186
- }
187
- node.addEventListener(event, handler, options);
188
- cleanup = () => {
189
- node.removeEventListener(event, handler, options);
190
- };
191
- };
192
- }, deps);
193
- }
194
- //#endregion
195
- //#region src/hooks/useDrag.ts
190
+ //#region src/hooks/useWheel.ts
196
191
  /**
197
- * @returns [refCallback, pointerDownHandler]
192
+ * Listen to wheel events on an element.
193
+ *
194
+ * The listener is not passive, so the handler may call `preventDefault()`.
195
+ *
196
+ * @returns a ref callback to attach to the element
198
197
  */
199
- function useDrag({ threshold: _threshold = 1, onDrag, onDragStart, onDragEnd }) {
200
- const dragOffsetX = useRef(void 0);
201
- const dragOffsetY = useRef(void 0);
202
- const dragStartX = useRef(0);
203
- const dragStartY = useRef(0);
204
- const threshold = Math.max(_threshold, 1);
205
- const handleDrag = useCallback((event, first = false) => {
206
- const isTouch = event instanceof TouchEvent;
207
- if (isTouch && event.cancelable) event.preventDefault();
208
- const screenX = isTouch ? event.touches[0].screenX : event.screenX;
209
- const screenY = isTouch ? event.touches[0].screenY : event.screenY;
210
- let deltaX = 0;
211
- let deltaY = 0;
212
- if (first) {
213
- dragStartX.current = screenX;
214
- dragStartY.current = screenY;
215
- }
216
- if (dragOffsetX.current) {
217
- deltaX = screenX - dragOffsetX.current;
218
- dragOffsetX.current = screenX;
219
- }
220
- if (dragOffsetY.current) {
221
- deltaY = screenY - dragOffsetY.current;
222
- dragOffsetY.current = screenY;
223
- }
224
- if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return;
225
- onDrag?.(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
226
- }, [threshold, onDrag]);
227
- const pointerDownHandler = useCallback((event) => {
228
- dragOffsetX.current = event.screenX;
229
- dragOffsetY.current = event.screenY;
230
- handleDrag(event, true);
231
- onDragStart?.();
232
- }, [handleDrag, onDragStart]);
233
- const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
234
- useEventListener(globalThis.window, "mousemove", handleDrag);
235
- useEventListener(globalThis.window, "pointerup", () => {
236
- dragOffsetX.current = void 0;
237
- dragOffsetY.current = void 0;
238
- onDragEnd?.();
239
- });
240
- return [refHandler, pointerDownHandler];
241
- }
242
- //#endregion
243
- //#region src/components/DragObserver/index.tsx
244
- function DragObserver(props) {
245
- const { as: Component = "div", threshold = 1, onDrag, onDragStart, onDragEnd, children, onPointerDown, ...attributes } = props;
246
- const [refCallback, pointerDownHandler] = useDrag({
247
- threshold,
248
- onDrag,
249
- onDragStart,
250
- onDragEnd
251
- });
252
- return /* @__PURE__ */ jsx(Component, {
253
- ref: refCallback,
254
- onPointerDown: (e) => {
255
- pointerDownHandler(e);
256
- onPointerDown?.(e);
257
- },
258
- ...attributes,
259
- children
260
- });
198
+ function useWheel(onWheel) {
199
+ const wheelHandler = useCallbackRef(onWheel);
200
+ const [node, setNode] = useState(null);
201
+ useEffect(() => {
202
+ if (!node) return;
203
+ const instance = createWheel(node, (event) => wheelHandler(event));
204
+ return () => instance.destroy();
205
+ }, [node, wheelHandler]);
206
+ return setNode;
261
207
  }
262
208
  //#endregion
263
209
  //#region src/components/_util/index.ts
@@ -267,22 +213,6 @@ function addUserSelectNone() {
267
213
  function removeUserSelectNone() {
268
214
  window.document.body.classList.remove("tremolo-user-select-none");
269
215
  }
270
- const cursorStyles = {
271
- grabbing: "tremolo-cursor-grabbing",
272
- grab: "tremolo-cursor-grab",
273
- pointer: "tremolo-cursor-pointer",
274
- move: "tremolo-cursor-move",
275
- none: "tremolo-cursor-none"
276
- };
277
- function setCursorStyle(type) {
278
- resetCursorStyle();
279
- window.document.body.classList.add(cursorStyles[type]);
280
- }
281
- function resetCursorStyle() {
282
- Object.values(cursorStyles).forEach((s) => {
283
- window.document.body.classList.remove(s);
284
- });
285
- }
286
216
  function calcDrawingState({ value, min, max, skew, startValue, angleRange }) {
287
217
  const p = normalizeValue(value, min, max, skew);
288
218
  const s = normalizeValue(startValue, min, max, skew);
@@ -492,32 +422,26 @@ const Knob = {
492
422
  readonly,
493
423
  updateValueByEvent
494
424
  ]);
495
- const [touchMoveRefCallback, pointerDownHandler] = useDrag({
425
+ const dragRefCallback = useDrag({
426
+ cursor: readonly ? void 0 : externalStyles.cursor,
496
427
  onDrag,
497
428
  onDragStart: () => {
498
429
  setDragging(true);
499
430
  valueRef.current = normalizeValue(value, min, max, skew);
500
431
  if (externalStyles.userSelectNone) addUserSelectNone();
501
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
502
432
  },
503
433
  onDragEnd: () => {
504
434
  setDragging(false);
505
435
  if (externalStyles.userSelectNone) removeUserSelectNone();
506
- if (externalStyles.cursor) resetCursorStyle();
507
436
  }
508
437
  });
509
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
438
+ const wheelRefCallback = useWheel((event) => {
510
439
  if (!wheel || readonly) return;
511
440
  event.preventDefault();
512
441
  if (!onChange || event.deltaY == 0) return;
513
442
  const x = Math.sign(event.deltaY) * -wheel[1];
514
443
  onChange(updateValueByEvent(wheel[0], x));
515
- }, { passive: false }, [
516
- wheel,
517
- onChange,
518
- readonly,
519
- updateValueByEvent
520
- ]);
444
+ });
521
445
  useImperativeHandle(forwardedRef, () => {
522
446
  return {
523
447
  focus() {
@@ -540,7 +464,7 @@ const Knob = {
540
464
  ref: (elm) => {
541
465
  elmRef.current = elm;
542
466
  wheelRefCallback(elm);
543
- touchMoveRefCallback(elm);
467
+ dragRefCallback(elm);
544
468
  },
545
469
  className: clsx("tremolo-knob", className),
546
470
  tabIndex: 0,
@@ -556,10 +480,7 @@ const Knob = {
556
480
  height: size,
557
481
  ...style
558
482
  },
559
- onPointerDown: (event) => {
560
- pointerDownHandler(event);
561
- onPointerDown?.(event);
562
- },
483
+ onPointerDown,
563
484
  onDoubleClick: (event) => {
564
485
  if (enableDoubleClickDefault && onChange) onChange(defaultValue);
565
486
  onDoubleClick?.(event);
@@ -696,6 +617,27 @@ function useNumberInputContext(selector) {
696
617
  return useStore(store, selector);
697
618
  }
698
619
  //#endregion
620
+ //#region src/hooks/useEventListener.ts
621
+ function useEventListener(target, event, handler, options) {
622
+ const listener = useCallbackRef(handler);
623
+ useEffect(() => {
624
+ const node = typeof target === "function" ? target() : target ?? document;
625
+ if (!node) return;
626
+ node.addEventListener(event, listener, options);
627
+ return () => {
628
+ node.removeEventListener(event, listener, options);
629
+ };
630
+ }, [
631
+ event,
632
+ target,
633
+ options,
634
+ listener
635
+ ]);
636
+ return () => {
637
+ (typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
638
+ };
639
+ }
640
+ //#endregion
699
641
  //#region src/hooks/useInterval.ts
700
642
  function useInterval(callback, delay) {
701
643
  const savedCallback = useRef(callback);
@@ -841,19 +783,14 @@ const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnE
841
783
  onChange,
842
784
  updateValueByEvent
843
785
  ]);
844
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
786
+ const wheelRefCallback = useWheel((event) => {
845
787
  if (!wheel || readonly) return;
846
788
  event.preventDefault();
847
789
  if (!onChange || event.deltaY == 0) return;
848
790
  const x = Math.sign(event.deltaY) * -wheel[1];
849
791
  const parsed = parseValue(String(updateValueByEvent(wheel[0], x)));
850
792
  onChange?.(parsed.rawValue, parsed.formatValue);
851
- }, { passive: false }, [
852
- wheel,
853
- readonly,
854
- onChange,
855
- updateValueByEvent
856
- ]);
793
+ });
857
794
  useImperativeHandle(ref, () => {
858
795
  return {
859
796
  focus() {
@@ -971,6 +908,23 @@ const NumberInput = {
971
908
  DecrementStepper
972
909
  };
973
910
  //#endregion
911
+ //#region src/hooks/useRefCallbackEvent.ts
912
+ function useRefCallbackEvent(event, handler, options, deps = []) {
913
+ return useMemo(() => {
914
+ let cleanup;
915
+ return (node) => {
916
+ if (!node) {
917
+ cleanup?.();
918
+ return;
919
+ }
920
+ node.addEventListener(event, handler, options);
921
+ cleanup = () => {
922
+ node.removeEventListener(event, handler, options);
923
+ };
924
+ };
925
+ }, deps);
926
+ }
927
+ //#endregion
974
928
  //#region src/hooks/usePianoDrag.ts
975
929
  /**
976
930
  * Internal
@@ -1427,42 +1381,57 @@ function Container({ className, children, ...props }) {
1427
1381
  }
1428
1382
  //#endregion
1429
1383
  //#region src/hooks/useDragWithElement.ts
1430
- function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1384
+ /**
1385
+ * Track a pointer drag, reporting the position normalized against
1386
+ * the bounding rect of `baseElementRef`.
1387
+ *
1388
+ * @returns a ref callback for the element that starts the drag, and whether a drag is in progress
1389
+ */
1390
+ function useDragWithElement({ baseElementRef, updateOnPointerDown = false, cursor, onDrag, onDragStart, onDragEnd }) {
1431
1391
  const [dragging, setDragging] = useState(false);
1432
1392
  const normalizedX = useRef(0);
1433
1393
  const normalizedY = useRef(0);
1434
- const handleDrag = useCallback((event) => {
1435
- if (!baseElementRef.current || !dragging) return;
1436
- const isTouch = event instanceof TouchEvent;
1437
- if (isTouch && event.cancelable) event.preventDefault();
1438
- const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
1439
- const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1440
- const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1441
- const nx = normalizeValue(mouseX, left, right);
1442
- const ny = normalizeValue(mouseY, top, bottom);
1443
- normalizedX.current = nx;
1444
- normalizedY.current = ny;
1445
- onDrag(nx, ny);
1394
+ const dragHandler = useCallbackRef(onDrag);
1395
+ const dragStartHandler = useCallbackRef(onDragStart);
1396
+ const dragEndHandler = useCallbackRef(onDragEnd);
1397
+ const update = useCallback((clientX, clientY) => {
1398
+ const base = baseElementRef.current;
1399
+ if (!base) return false;
1400
+ const { left, top, right, bottom } = base.getBoundingClientRect();
1401
+ normalizedX.current = normalizeValue(clientX, left, right);
1402
+ normalizedY.current = normalizeValue(clientY, top, bottom);
1403
+ return true;
1404
+ }, [baseElementRef]);
1405
+ const [node, setNode] = useState(null);
1406
+ useEffect(() => {
1407
+ if (!node) return;
1408
+ const instance = createDrag(node, {
1409
+ cursor,
1410
+ onDragStart: ({ clientX, clientY }) => {
1411
+ setDragging(true);
1412
+ if (update(clientX, clientY) && updateOnPointerDown) dragHandler(normalizedX.current, normalizedY.current);
1413
+ dragStartHandler(normalizedX.current, normalizedY.current);
1414
+ },
1415
+ onDrag: ({ clientX, clientY }) => {
1416
+ if (update(clientX, clientY)) dragHandler(normalizedX.current, normalizedY.current);
1417
+ },
1418
+ onDragEnd: () => {
1419
+ setDragging(false);
1420
+ dragEndHandler(normalizedX.current, normalizedY.current);
1421
+ }
1422
+ });
1423
+ return () => instance.destroy();
1446
1424
  }, [
1447
- onDrag,
1448
- baseElementRef,
1449
- dragging
1425
+ node,
1426
+ update,
1427
+ updateOnPointerDown,
1428
+ cursor,
1429
+ dragHandler,
1430
+ dragStartHandler,
1431
+ dragEndHandler
1450
1432
  ]);
1451
- const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
1452
- const pointerDownHandler = useCallback((event) => {
1453
- setDragging(true);
1454
- handleDrag(event);
1455
- onDragStart?.(normalizedX.current, normalizedY.current);
1456
- }, [handleDrag, onDragStart]);
1457
- useEventListener(globalThis.window, "pointermove", handleDrag);
1458
- useEventListener(globalThis.window, "pointerup", () => {
1459
- if (!dragging) return;
1460
- setDragging(false);
1461
- onDragEnd?.(normalizedX.current, normalizedY.current);
1462
- });
1463
1433
  return {
1464
- refHandler,
1465
- pointerDownHandler,
1434
+ refCallback: setNode,
1466
1435
  dragging
1467
1436
  };
1468
1437
  }
@@ -1480,8 +1449,9 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1480
1449
  const externalStyles = usePointsEditorContext((s) => s.externalStyles);
1481
1450
  const __disabled = usePointsEditorContext((s) => s.disabled);
1482
1451
  const __readonly = usePointsEditorContext((s) => s.readonly);
1483
- const { refHandler: touchMoveRefCallback, pointerDownHandler, dragging } = useDragWithElement({
1452
+ const { refCallback: dragRefCallback, dragging } = useDragWithElement({
1484
1453
  baseElementRef: containerElementRef,
1454
+ cursor: readonly ? void 0 : externalStyles.cursor,
1485
1455
  onDrag: (x, y) => {
1486
1456
  if (readonly) return;
1487
1457
  onChange?.(clampPoint({
@@ -1492,7 +1462,6 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1492
1462
  onDragStart: (x, y) => {
1493
1463
  if (readonly) return;
1494
1464
  if (externalStyles.userSelectNone) addUserSelectNone();
1495
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
1496
1465
  onDragStart?.(clampPoint({
1497
1466
  x,
1498
1467
  y
@@ -1501,7 +1470,6 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1501
1470
  onDragEnd: (x, y) => {
1502
1471
  if (readonly) return;
1503
1472
  if (externalStyles.userSelectNone) removeUserSelectNone();
1504
- if (externalStyles.cursor) resetCursorStyle();
1505
1473
  onDragEnd?.(clampPoint({
1506
1474
  x,
1507
1475
  y
@@ -1510,9 +1478,7 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1510
1478
  });
1511
1479
  const colors = { "--color": color };
1512
1480
  return /* @__PURE__ */ jsx("div", {
1513
- ref: (div) => {
1514
- touchMoveRefCallback(div);
1515
- },
1481
+ ref: dragRefCallback,
1516
1482
  className: clsx("tremolo-points-editor-point", className),
1517
1483
  tabIndex: 0,
1518
1484
  "aria-disabled": disabled != void 0 ? disabled : __disabled,
@@ -1526,10 +1492,7 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1526
1492
  top: `${value.y * 100}%`,
1527
1493
  ...style
1528
1494
  },
1529
- onPointerDown: (event) => {
1530
- pointerDownHandler(event);
1531
- onPointerDown?.(event);
1532
- },
1495
+ onPointerDown,
1533
1496
  ...props
1534
1497
  });
1535
1498
  }
@@ -1830,24 +1793,24 @@ const Slider = {
1830
1793
  reverse,
1831
1794
  updateValueByEvent
1832
1795
  ]);
1833
- const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
1796
+ const { refCallback: dragRefCallback } = useDragWithElement({
1834
1797
  baseElementRef: trackElementRef,
1798
+ cursor: readonly ? void 0 : externalStyles.cursor,
1799
+ updateOnPointerDown: true,
1835
1800
  onDrag,
1836
1801
  onDragStart: (nx, ny) => {
1837
1802
  if (readonly) return;
1838
1803
  if (externalStyles.userSelectNone) addUserSelectNone();
1839
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
1840
1804
  thumbRef.current?.focus();
1841
1805
  onDragStart?.(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
1842
1806
  },
1843
1807
  onDragEnd: (nx, ny) => {
1844
1808
  if (readonly) return;
1845
1809
  if (externalStyles.userSelectNone) removeUserSelectNone();
1846
- if (externalStyles.cursor) resetCursorStyle();
1847
1810
  onDragEnd?.(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
1848
1811
  }
1849
1812
  });
1850
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1813
+ const wheelRefCallback = useWheel((event) => {
1851
1814
  if (!wheel || !onChange || readonly) return;
1852
1815
  event.preventDefault();
1853
1816
  let x;
@@ -1858,13 +1821,7 @@ const Slider = {
1858
1821
  }
1859
1822
  if (vertical && reverse) x *= -1;
1860
1823
  onChange(updateValueByEvent(wheel[0], x));
1861
- }, { passive: false }, [
1862
- wheel,
1863
- vertical,
1864
- readonly,
1865
- onChange,
1866
- updateValueByEvent
1867
- ]);
1824
+ });
1868
1825
  useImperativeHandle(forwardedRef, () => {
1869
1826
  return {
1870
1827
  focus() {
@@ -1888,7 +1845,7 @@ const Slider = {
1888
1845
  className: clsx("tremolo-slider", className),
1889
1846
  ref: (div) => {
1890
1847
  wheelRefCallback(div);
1891
- touchMoveRefCallback(div);
1848
+ dragRefCallback(div);
1892
1849
  },
1893
1850
  tabIndex: -1,
1894
1851
  role: "slider",
@@ -1902,10 +1859,7 @@ const Slider = {
1902
1859
  margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`,
1903
1860
  ...style
1904
1861
  },
1905
- onPointerDown: (event) => {
1906
- pointerDownHandler(event);
1907
- onPointerDown?.(event);
1908
- },
1862
+ onPointerDown,
1909
1863
  onKeyDown: (event) => {
1910
1864
  handleKeyDown(event);
1911
1865
  onKeyDown?.(event);
@@ -1980,29 +1934,6 @@ function composeRefs(...refs) {
1980
1934
  };
1981
1935
  }
1982
1936
  //#endregion
1983
- //#region src/components/WheelObserver/index.tsx
1984
- /**
1985
- * @example
1986
- * <WheelObserver
1987
- * onWheel={(event) => {
1988
- * event.preventDefault()
1989
- * console.log(event.deltaY)
1990
- * }
1991
- * >
1992
- * <div>Wheel here</div>
1993
- * </WheelObserver>
1994
- */
1995
- function WheelObserver(props) {
1996
- const { as: Component = "div", children, onWheel, ref, ...attributes } = props;
1997
- return /* @__PURE__ */ jsx(Component, {
1998
- ref: composeRefs(ref, useRefCallbackEvent("wheel", (event) => {
1999
- if (onWheel) onWheel(event);
2000
- }, { passive: false }, [onWheel])),
2001
- ...attributes,
2002
- children
2003
- });
2004
- }
2005
- //#endregion
2006
1937
  //#region src/components/XYPad/Area.tsx
2007
1938
  function Area({ width = 120, height = 120, color, children, className, style, __thumb, ...props }) {
2008
1939
  return /* @__PURE__ */ jsxs("div", {
@@ -2154,13 +2085,14 @@ const XYPad = {
2154
2085
  y,
2155
2086
  updateValueByEvent
2156
2087
  ]);
2157
- const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
2088
+ const { refCallback: dragRefCallback } = useDragWithElement({
2158
2089
  baseElementRef: areaElementRef,
2090
+ cursor: readonly ? void 0 : externalStyles.cursor,
2091
+ updateOnPointerDown: true,
2159
2092
  onDrag,
2160
2093
  onDragStart: (nx, ny) => {
2161
2094
  if (readonly) return;
2162
2095
  if (externalStyles.userSelectNone) addUserSelectNone();
2163
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
2164
2096
  thumbRef.current?.focus();
2165
2097
  const valueX = clamp(stepValue(rawValue(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
2166
2098
  const valueY = clamp(stepValue(rawValue(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
@@ -2169,13 +2101,12 @@ const XYPad = {
2169
2101
  onDragEnd: (nx, ny) => {
2170
2102
  if (readonly) return;
2171
2103
  if (externalStyles.userSelectNone) removeUserSelectNone();
2172
- if (externalStyles.cursor) resetCursorStyle();
2173
2104
  const valueX = clamp(stepValue(rawValue(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
2174
2105
  const valueY = clamp(stepValue(rawValue(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
2175
2106
  onDragEnd?.(valueX, valueY);
2176
2107
  }
2177
2108
  });
2178
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
2109
+ const wheelRefCallback = useWheel((event) => {
2179
2110
  if (!onChange || readonly) return;
2180
2111
  const target = event.shiftKey ? x : y;
2181
2112
  if (!target.wheel) return;
@@ -2186,13 +2117,7 @@ const XYPad = {
2186
2117
  const newValue = updateValueByEvent(target.wheel[0], target, delta);
2187
2118
  if (event.shiftKey) onChange(newValue, y.value);
2188
2119
  else onChange(x.value, newValue);
2189
- }, { passive: false }, [
2190
- x,
2191
- y,
2192
- onChange,
2193
- readonly,
2194
- updateValueByEvent
2195
- ]);
2120
+ });
2196
2121
  useImperativeHandle(forwardedRef, () => {
2197
2122
  return {
2198
2123
  focus() {
@@ -2206,7 +2131,7 @@ const XYPad = {
2206
2131
  }, []);
2207
2132
  return /* @__PURE__ */ jsx("div", {
2208
2133
  className: clsx("tremolo-xy-pad", className),
2209
- ref: composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback),
2134
+ ref: composeRefs(rootRef, wheelRefCallback, dragRefCallback),
2210
2135
  tabIndex: -1,
2211
2136
  "aria-disabled": disabled,
2212
2137
  "aria-readonly": readonly,
@@ -2216,10 +2141,7 @@ const XYPad = {
2216
2141
  WebkitTapHighlightColor: "transparent",
2217
2142
  ...style
2218
2143
  },
2219
- onPointerDown: (event) => {
2220
- pointerDownHandler(event);
2221
- onPointerDown?.(event);
2222
- },
2144
+ onPointerDown,
2223
2145
  onKeyDown: (event) => {
2224
2146
  handleKeyDown(event);
2225
2147
  onKeyDown?.(event);
@@ -2270,72 +2192,56 @@ function useAnimationFrame(callback = () => {}, deps = []) {
2270
2192
  }
2271
2193
  //#endregion
2272
2194
  //#region src/hooks/useMIDIAccess.ts
2273
- /** @private */
2274
- const PERMISSION_DENIED = "PERMISSION_DENIED";
2275
- /** @private */
2276
- const NOT_SUPPORTED = "NOT_SUPPORTED";
2277
2195
  /**
2278
2196
  * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
2279
2197
  */
2280
2198
  function useMIDIAccess(requestOnMount = true) {
2281
- const [midiAccess, setMidiAccess] = useState(null);
2282
- const [error, setError] = useState(null);
2283
- const request = useCallback(() => {
2284
- if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
2285
- setMidiAccess(access);
2286
- setError(null);
2287
- }).catch(() => {
2288
- setError(PERMISSION_DENIED);
2289
- });
2290
- else setError(NOT_SUPPORTED);
2291
- }, []);
2199
+ const instance = useMemo(() => createMIDIAccess(), []);
2200
+ const { midiAccess, error } = useSyncExternalStore(instance.subscribe, instance.getState, instance.getServerState);
2292
2201
  useEffect(() => {
2293
- if (requestOnMount) request();
2294
- }, [requestOnMount, request]);
2202
+ if (requestOnMount) instance.request();
2203
+ }, [instance, requestOnMount]);
2204
+ useEffect(() => {
2205
+ return () => instance.destroy();
2206
+ }, [instance]);
2295
2207
  return {
2296
- request,
2208
+ request: instance.request,
2297
2209
  midiAccess,
2298
2210
  error
2299
2211
  };
2300
2212
  }
2301
2213
  //#endregion
2302
- //#region src/hooks/useMIDIMessage.ts
2303
- /**
2304
- * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2305
- */
2306
- function useMIDIMessage(midiAccess, onMIDIMessage) {
2307
- useEffect(() => {
2308
- if (!midiAccess) return;
2309
- for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
2310
- return () => {
2311
- for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
2312
- };
2313
- }, [midiAccess, onMIDIMessage]);
2314
- }
2315
- //#endregion
2316
2214
  //#region src/hooks/useMIDIInput.ts
2317
- const MIDI_EVENT_TO_NUMBER = {
2318
- NOTE_ON: 144,
2319
- NOTE_OFF: 128,
2320
- PITCH_BEND: 224
2321
- };
2322
2215
  /**
2323
2216
  * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
2324
2217
  */
2325
2218
  function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
2326
- useMIDIMessage(midiAccess, useCallback((event) => {
2327
- if (!event.data) return;
2328
- const kind = event.data[0] & 240;
2329
- if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
2330
- else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
2331
- else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
2219
+ useEffect(() => {
2220
+ const instance = createMIDIInput(midiAccess, {
2221
+ onNoteOnEvent,
2222
+ onNoteOffEvent,
2223
+ onPitchBendEvent
2224
+ });
2225
+ return () => instance.destroy();
2332
2226
  }, [
2333
- onNoteOffEvent,
2227
+ midiAccess,
2334
2228
  onNoteOnEvent,
2229
+ onNoteOffEvent,
2335
2230
  onPitchBendEvent
2336
- ]));
2231
+ ]);
2232
+ }
2233
+ //#endregion
2234
+ //#region src/hooks/useMIDIMessage.ts
2235
+ /**
2236
+ * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2237
+ */
2238
+ function useMIDIMessage(midiAccess, onMIDIMessage) {
2239
+ useEffect(() => {
2240
+ const instance = createMIDIMessage(midiAccess, onMIDIMessage);
2241
+ return () => instance.destroy();
2242
+ }, [midiAccess, onMIDIMessage]);
2337
2243
  }
2338
2244
  //#endregion
2339
- export { AnimationCanvas, DragObserver, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, PointsEditor, SHORTCUTS, Slider, WheelObserver, XYPad, clampPoint, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useSliderContext };
2245
+ export { AnimationCanvas, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, PointsEditor, SHORTCUTS, Slider, XYPad, clampPoint, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useSliderContext, useWheel };
2340
2246
 
2341
2247
  //# sourceMappingURL=index.js.map