@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.cjs CHANGED
@@ -27,6 +27,7 @@ let react = require("react");
27
27
  react = __toESM(react);
28
28
  let react_jsx_runtime = require("react/jsx-runtime");
29
29
  let _tremolo_ui_functions = require("@tremolo-ui/functions");
30
+ let _tremolo_ui_dom = require("@tremolo-ui/dom");
30
31
  let zustand = require("zustand");
31
32
  //#region src/components/AnimationCanvas/canvas.ts
32
33
  const drawingState = [
@@ -179,110 +180,55 @@ function useCallbackRef(callback, deps = []) {
179
180
  return (0, react.useCallback)((...args) => callbackRef.current?.(...args), deps);
180
181
  }
181
182
  //#endregion
182
- //#region src/hooks/useEventListener.ts
183
- function useEventListener(target, event, handler, options) {
184
- const listener = useCallbackRef(handler);
183
+ //#region src/hooks/useDrag.ts
184
+ /**
185
+ * Track a pointer drag on an element.
186
+ *
187
+ * @returns a ref callback to attach to the element being dragged
188
+ */
189
+ function useDrag({ threshold = 1, cursor, onDrag, onDragStart, onDragEnd }) {
190
+ const dragHandler = useCallbackRef(onDrag);
191
+ const dragStartHandler = useCallbackRef(onDragStart);
192
+ const dragEndHandler = useCallbackRef(onDragEnd);
193
+ const [node, setNode] = (0, react.useState)(null);
185
194
  (0, react.useEffect)(() => {
186
- const node = typeof target === "function" ? target() : target ?? document;
187
195
  if (!node) return;
188
- node.addEventListener(event, listener, options);
189
- return () => {
190
- node.removeEventListener(event, listener, options);
191
- };
196
+ const instance = (0, _tremolo_ui_dom.createDrag)(node, {
197
+ threshold,
198
+ cursor,
199
+ onDragStart: () => dragStartHandler(),
200
+ onDrag: ({ x, y, deltaX, deltaY }) => dragHandler(x, y, deltaX, deltaY),
201
+ onDragEnd: () => dragEndHandler()
202
+ });
203
+ return () => instance.destroy();
192
204
  }, [
193
- event,
194
- target,
195
- options,
196
- listener
205
+ node,
206
+ threshold,
207
+ cursor,
208
+ dragHandler,
209
+ dragStartHandler,
210
+ dragEndHandler
197
211
  ]);
198
- return () => {
199
- (typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
200
- };
201
- }
202
- //#endregion
203
- //#region src/hooks/useRefCallbackEvent.ts
204
- function useRefCallbackEvent(event, handler, options, deps = []) {
205
- return (0, react.useMemo)(() => {
206
- let cleanup;
207
- return (node) => {
208
- if (!node) {
209
- cleanup?.();
210
- return;
211
- }
212
- node.addEventListener(event, handler, options);
213
- cleanup = () => {
214
- node.removeEventListener(event, handler, options);
215
- };
216
- };
217
- }, deps);
212
+ return setNode;
218
213
  }
219
214
  //#endregion
220
- //#region src/hooks/useDrag.ts
215
+ //#region src/hooks/useWheel.ts
221
216
  /**
222
- * @returns [refCallback, pointerDownHandler]
217
+ * Listen to wheel events on an element.
218
+ *
219
+ * The listener is not passive, so the handler may call `preventDefault()`.
220
+ *
221
+ * @returns a ref callback to attach to the element
223
222
  */
224
- function useDrag({ threshold: _threshold = 1, onDrag, onDragStart, onDragEnd }) {
225
- const dragOffsetX = (0, react.useRef)(void 0);
226
- const dragOffsetY = (0, react.useRef)(void 0);
227
- const dragStartX = (0, react.useRef)(0);
228
- const dragStartY = (0, react.useRef)(0);
229
- const threshold = Math.max(_threshold, 1);
230
- const handleDrag = (0, react.useCallback)((event, first = false) => {
231
- const isTouch = event instanceof TouchEvent;
232
- if (isTouch && event.cancelable) event.preventDefault();
233
- const screenX = isTouch ? event.touches[0].screenX : event.screenX;
234
- const screenY = isTouch ? event.touches[0].screenY : event.screenY;
235
- let deltaX = 0;
236
- let deltaY = 0;
237
- if (first) {
238
- dragStartX.current = screenX;
239
- dragStartY.current = screenY;
240
- }
241
- if (dragOffsetX.current) {
242
- deltaX = screenX - dragOffsetX.current;
243
- dragOffsetX.current = screenX;
244
- }
245
- if (dragOffsetY.current) {
246
- deltaY = screenY - dragOffsetY.current;
247
- dragOffsetY.current = screenY;
248
- }
249
- if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return;
250
- onDrag?.(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
251
- }, [threshold, onDrag]);
252
- const pointerDownHandler = (0, react.useCallback)((event) => {
253
- dragOffsetX.current = event.screenX;
254
- dragOffsetY.current = event.screenY;
255
- handleDrag(event, true);
256
- onDragStart?.();
257
- }, [handleDrag, onDragStart]);
258
- const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
259
- useEventListener(globalThis.window, "mousemove", handleDrag);
260
- useEventListener(globalThis.window, "pointerup", () => {
261
- dragOffsetX.current = void 0;
262
- dragOffsetY.current = void 0;
263
- onDragEnd?.();
264
- });
265
- return [refHandler, pointerDownHandler];
266
- }
267
- //#endregion
268
- //#region src/components/DragObserver/index.tsx
269
- function DragObserver(props) {
270
- const { as: Component = "div", threshold = 1, onDrag, onDragStart, onDragEnd, children, onPointerDown, ...attributes } = props;
271
- const [refCallback, pointerDownHandler] = useDrag({
272
- threshold,
273
- onDrag,
274
- onDragStart,
275
- onDragEnd
276
- });
277
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
278
- ref: refCallback,
279
- onPointerDown: (e) => {
280
- pointerDownHandler(e);
281
- onPointerDown?.(e);
282
- },
283
- ...attributes,
284
- children
285
- });
223
+ function useWheel(onWheel) {
224
+ const wheelHandler = useCallbackRef(onWheel);
225
+ const [node, setNode] = (0, react.useState)(null);
226
+ (0, react.useEffect)(() => {
227
+ if (!node) return;
228
+ const instance = (0, _tremolo_ui_dom.createWheel)(node, (event) => wheelHandler(event));
229
+ return () => instance.destroy();
230
+ }, [node, wheelHandler]);
231
+ return setNode;
286
232
  }
287
233
  //#endregion
288
234
  //#region src/components/_util/index.ts
@@ -292,22 +238,6 @@ function addUserSelectNone() {
292
238
  function removeUserSelectNone() {
293
239
  window.document.body.classList.remove("tremolo-user-select-none");
294
240
  }
295
- const cursorStyles = {
296
- grabbing: "tremolo-cursor-grabbing",
297
- grab: "tremolo-cursor-grab",
298
- pointer: "tremolo-cursor-pointer",
299
- move: "tremolo-cursor-move",
300
- none: "tremolo-cursor-none"
301
- };
302
- function setCursorStyle(type) {
303
- resetCursorStyle();
304
- window.document.body.classList.add(cursorStyles[type]);
305
- }
306
- function resetCursorStyle() {
307
- Object.values(cursorStyles).forEach((s) => {
308
- window.document.body.classList.remove(s);
309
- });
310
- }
311
241
  function calcDrawingState({ value, min, max, skew, startValue, angleRange }) {
312
242
  const p = (0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew);
313
243
  const s = (0, _tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
@@ -517,32 +447,26 @@ const Knob = {
517
447
  readonly,
518
448
  updateValueByEvent
519
449
  ]);
520
- const [touchMoveRefCallback, pointerDownHandler] = useDrag({
450
+ const dragRefCallback = useDrag({
451
+ cursor: readonly ? void 0 : externalStyles.cursor,
521
452
  onDrag,
522
453
  onDragStart: () => {
523
454
  setDragging(true);
524
455
  valueRef.current = (0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew);
525
456
  if (externalStyles.userSelectNone) addUserSelectNone();
526
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
527
457
  },
528
458
  onDragEnd: () => {
529
459
  setDragging(false);
530
460
  if (externalStyles.userSelectNone) removeUserSelectNone();
531
- if (externalStyles.cursor) resetCursorStyle();
532
461
  }
533
462
  });
534
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
463
+ const wheelRefCallback = useWheel((event) => {
535
464
  if (!wheel || readonly) return;
536
465
  event.preventDefault();
537
466
  if (!onChange || event.deltaY == 0) return;
538
467
  const x = Math.sign(event.deltaY) * -wheel[1];
539
468
  onChange(updateValueByEvent(wheel[0], x));
540
- }, { passive: false }, [
541
- wheel,
542
- onChange,
543
- readonly,
544
- updateValueByEvent
545
- ]);
469
+ });
546
470
  (0, react.useImperativeHandle)(forwardedRef, () => {
547
471
  return {
548
472
  focus() {
@@ -565,7 +489,7 @@ const Knob = {
565
489
  ref: (elm) => {
566
490
  elmRef.current = elm;
567
491
  wheelRefCallback(elm);
568
- touchMoveRefCallback(elm);
492
+ dragRefCallback(elm);
569
493
  },
570
494
  className: (0, clsx.default)("tremolo-knob", className),
571
495
  tabIndex: 0,
@@ -581,10 +505,7 @@ const Knob = {
581
505
  height: size,
582
506
  ...style
583
507
  },
584
- onPointerDown: (event) => {
585
- pointerDownHandler(event);
586
- onPointerDown?.(event);
587
- },
508
+ onPointerDown,
588
509
  onDoubleClick: (event) => {
589
510
  if (enableDoubleClickDefault && onChange) onChange(defaultValue);
590
511
  onDoubleClick?.(event);
@@ -721,6 +642,27 @@ function useNumberInputContext(selector) {
721
642
  return (0, zustand.useStore)(store, selector);
722
643
  }
723
644
  //#endregion
645
+ //#region src/hooks/useEventListener.ts
646
+ function useEventListener(target, event, handler, options) {
647
+ const listener = useCallbackRef(handler);
648
+ (0, react.useEffect)(() => {
649
+ const node = typeof target === "function" ? target() : target ?? document;
650
+ if (!node) return;
651
+ node.addEventListener(event, listener, options);
652
+ return () => {
653
+ node.removeEventListener(event, listener, options);
654
+ };
655
+ }, [
656
+ event,
657
+ target,
658
+ options,
659
+ listener
660
+ ]);
661
+ return () => {
662
+ (typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
663
+ };
664
+ }
665
+ //#endregion
724
666
  //#region src/hooks/useInterval.ts
725
667
  function useInterval(callback, delay) {
726
668
  const savedCallback = (0, react.useRef)(callback);
@@ -866,19 +808,14 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
866
808
  onChange,
867
809
  updateValueByEvent
868
810
  ]);
869
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
811
+ const wheelRefCallback = useWheel((event) => {
870
812
  if (!wheel || readonly) return;
871
813
  event.preventDefault();
872
814
  if (!onChange || event.deltaY == 0) return;
873
815
  const x = Math.sign(event.deltaY) * -wheel[1];
874
816
  const parsed = parseValue(String(updateValueByEvent(wheel[0], x)));
875
817
  onChange?.(parsed.rawValue, parsed.formatValue);
876
- }, { passive: false }, [
877
- wheel,
878
- readonly,
879
- onChange,
880
- updateValueByEvent
881
- ]);
818
+ });
882
819
  (0, react.useImperativeHandle)(ref, () => {
883
820
  return {
884
821
  focus() {
@@ -996,6 +933,23 @@ const NumberInput = {
996
933
  DecrementStepper
997
934
  };
998
935
  //#endregion
936
+ //#region src/hooks/useRefCallbackEvent.ts
937
+ function useRefCallbackEvent(event, handler, options, deps = []) {
938
+ return (0, react.useMemo)(() => {
939
+ let cleanup;
940
+ return (node) => {
941
+ if (!node) {
942
+ cleanup?.();
943
+ return;
944
+ }
945
+ node.addEventListener(event, handler, options);
946
+ cleanup = () => {
947
+ node.removeEventListener(event, handler, options);
948
+ };
949
+ };
950
+ }, deps);
951
+ }
952
+ //#endregion
999
953
  //#region src/hooks/usePianoDrag.ts
1000
954
  /**
1001
955
  * Internal
@@ -1452,42 +1406,57 @@ function Container({ className, children, ...props }) {
1452
1406
  }
1453
1407
  //#endregion
1454
1408
  //#region src/hooks/useDragWithElement.ts
1455
- function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1409
+ /**
1410
+ * Track a pointer drag, reporting the position normalized against
1411
+ * the bounding rect of `baseElementRef`.
1412
+ *
1413
+ * @returns a ref callback for the element that starts the drag, and whether a drag is in progress
1414
+ */
1415
+ function useDragWithElement({ baseElementRef, updateOnPointerDown = false, cursor, onDrag, onDragStart, onDragEnd }) {
1456
1416
  const [dragging, setDragging] = (0, react.useState)(false);
1457
1417
  const normalizedX = (0, react.useRef)(0);
1458
1418
  const normalizedY = (0, react.useRef)(0);
1459
- const handleDrag = (0, react.useCallback)((event) => {
1460
- if (!baseElementRef.current || !dragging) return;
1461
- const isTouch = event instanceof TouchEvent;
1462
- if (isTouch && event.cancelable) event.preventDefault();
1463
- const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
1464
- const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1465
- const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1466
- const nx = (0, _tremolo_ui_functions.normalizeValue)(mouseX, left, right);
1467
- const ny = (0, _tremolo_ui_functions.normalizeValue)(mouseY, top, bottom);
1468
- normalizedX.current = nx;
1469
- normalizedY.current = ny;
1470
- onDrag(nx, ny);
1419
+ const dragHandler = useCallbackRef(onDrag);
1420
+ const dragStartHandler = useCallbackRef(onDragStart);
1421
+ const dragEndHandler = useCallbackRef(onDragEnd);
1422
+ const update = (0, react.useCallback)((clientX, clientY) => {
1423
+ const base = baseElementRef.current;
1424
+ if (!base) return false;
1425
+ const { left, top, right, bottom } = base.getBoundingClientRect();
1426
+ normalizedX.current = (0, _tremolo_ui_functions.normalizeValue)(clientX, left, right);
1427
+ normalizedY.current = (0, _tremolo_ui_functions.normalizeValue)(clientY, top, bottom);
1428
+ return true;
1429
+ }, [baseElementRef]);
1430
+ const [node, setNode] = (0, react.useState)(null);
1431
+ (0, react.useEffect)(() => {
1432
+ if (!node) return;
1433
+ const instance = (0, _tremolo_ui_dom.createDrag)(node, {
1434
+ cursor,
1435
+ onDragStart: ({ clientX, clientY }) => {
1436
+ setDragging(true);
1437
+ if (update(clientX, clientY) && updateOnPointerDown) dragHandler(normalizedX.current, normalizedY.current);
1438
+ dragStartHandler(normalizedX.current, normalizedY.current);
1439
+ },
1440
+ onDrag: ({ clientX, clientY }) => {
1441
+ if (update(clientX, clientY)) dragHandler(normalizedX.current, normalizedY.current);
1442
+ },
1443
+ onDragEnd: () => {
1444
+ setDragging(false);
1445
+ dragEndHandler(normalizedX.current, normalizedY.current);
1446
+ }
1447
+ });
1448
+ return () => instance.destroy();
1471
1449
  }, [
1472
- onDrag,
1473
- baseElementRef,
1474
- dragging
1450
+ node,
1451
+ update,
1452
+ updateOnPointerDown,
1453
+ cursor,
1454
+ dragHandler,
1455
+ dragStartHandler,
1456
+ dragEndHandler
1475
1457
  ]);
1476
- const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
1477
- const pointerDownHandler = (0, react.useCallback)((event) => {
1478
- setDragging(true);
1479
- handleDrag(event);
1480
- onDragStart?.(normalizedX.current, normalizedY.current);
1481
- }, [handleDrag, onDragStart]);
1482
- useEventListener(globalThis.window, "pointermove", handleDrag);
1483
- useEventListener(globalThis.window, "pointerup", () => {
1484
- if (!dragging) return;
1485
- setDragging(false);
1486
- onDragEnd?.(normalizedX.current, normalizedY.current);
1487
- });
1488
1458
  return {
1489
- refHandler,
1490
- pointerDownHandler,
1459
+ refCallback: setNode,
1491
1460
  dragging
1492
1461
  };
1493
1462
  }
@@ -1505,8 +1474,9 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1505
1474
  const externalStyles = usePointsEditorContext((s) => s.externalStyles);
1506
1475
  const __disabled = usePointsEditorContext((s) => s.disabled);
1507
1476
  const __readonly = usePointsEditorContext((s) => s.readonly);
1508
- const { refHandler: touchMoveRefCallback, pointerDownHandler, dragging } = useDragWithElement({
1477
+ const { refCallback: dragRefCallback, dragging } = useDragWithElement({
1509
1478
  baseElementRef: containerElementRef,
1479
+ cursor: readonly ? void 0 : externalStyles.cursor,
1510
1480
  onDrag: (x, y) => {
1511
1481
  if (readonly) return;
1512
1482
  onChange?.(clampPoint({
@@ -1517,7 +1487,6 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1517
1487
  onDragStart: (x, y) => {
1518
1488
  if (readonly) return;
1519
1489
  if (externalStyles.userSelectNone) addUserSelectNone();
1520
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
1521
1490
  onDragStart?.(clampPoint({
1522
1491
  x,
1523
1492
  y
@@ -1526,7 +1495,6 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1526
1495
  onDragEnd: (x, y) => {
1527
1496
  if (readonly) return;
1528
1497
  if (externalStyles.userSelectNone) removeUserSelectNone();
1529
- if (externalStyles.cursor) resetCursorStyle();
1530
1498
  onDragEnd?.(clampPoint({
1531
1499
  x,
1532
1500
  y
@@ -1535,9 +1503,7 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1535
1503
  });
1536
1504
  const colors = { "--color": color };
1537
1505
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1538
- ref: (div) => {
1539
- touchMoveRefCallback(div);
1540
- },
1506
+ ref: dragRefCallback,
1541
1507
  className: (0, clsx.default)("tremolo-points-editor-point", className),
1542
1508
  tabIndex: 0,
1543
1509
  "aria-disabled": disabled != void 0 ? disabled : __disabled,
@@ -1551,10 +1517,7 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
1551
1517
  top: `${value.y * 100}%`,
1552
1518
  ...style
1553
1519
  },
1554
- onPointerDown: (event) => {
1555
- pointerDownHandler(event);
1556
- onPointerDown?.(event);
1557
- },
1520
+ onPointerDown,
1558
1521
  ...props
1559
1522
  });
1560
1523
  }
@@ -1855,24 +1818,24 @@ const Slider = {
1855
1818
  reverse,
1856
1819
  updateValueByEvent
1857
1820
  ]);
1858
- const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
1821
+ const { refCallback: dragRefCallback } = useDragWithElement({
1859
1822
  baseElementRef: trackElementRef,
1823
+ cursor: readonly ? void 0 : externalStyles.cursor,
1824
+ updateOnPointerDown: true,
1860
1825
  onDrag,
1861
1826
  onDragStart: (nx, ny) => {
1862
1827
  if (readonly) return;
1863
1828
  if (externalStyles.userSelectNone) addUserSelectNone();
1864
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
1865
1829
  thumbRef.current?.focus();
1866
1830
  onDragStart?.((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(vertical ? ny : nx, min, max, skew), step), min, max));
1867
1831
  },
1868
1832
  onDragEnd: (nx, ny) => {
1869
1833
  if (readonly) return;
1870
1834
  if (externalStyles.userSelectNone) removeUserSelectNone();
1871
- if (externalStyles.cursor) resetCursorStyle();
1872
1835
  onDragEnd?.((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(vertical ? ny : nx, min, max, skew), step), min, max));
1873
1836
  }
1874
1837
  });
1875
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1838
+ const wheelRefCallback = useWheel((event) => {
1876
1839
  if (!wheel || !onChange || readonly) return;
1877
1840
  event.preventDefault();
1878
1841
  let x;
@@ -1883,13 +1846,7 @@ const Slider = {
1883
1846
  }
1884
1847
  if (vertical && reverse) x *= -1;
1885
1848
  onChange(updateValueByEvent(wheel[0], x));
1886
- }, { passive: false }, [
1887
- wheel,
1888
- vertical,
1889
- readonly,
1890
- onChange,
1891
- updateValueByEvent
1892
- ]);
1849
+ });
1893
1850
  (0, react.useImperativeHandle)(forwardedRef, () => {
1894
1851
  return {
1895
1852
  focus() {
@@ -1913,7 +1870,7 @@ const Slider = {
1913
1870
  className: (0, clsx.default)("tremolo-slider", className),
1914
1871
  ref: (div) => {
1915
1872
  wheelRefCallback(div);
1916
- touchMoveRefCallback(div);
1873
+ dragRefCallback(div);
1917
1874
  },
1918
1875
  tabIndex: -1,
1919
1876
  role: "slider",
@@ -1927,10 +1884,7 @@ const Slider = {
1927
1884
  margin: `calc(${(0, _tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
1928
1885
  ...style
1929
1886
  },
1930
- onPointerDown: (event) => {
1931
- pointerDownHandler(event);
1932
- onPointerDown?.(event);
1933
- },
1887
+ onPointerDown,
1934
1888
  onKeyDown: (event) => {
1935
1889
  handleKeyDown(event);
1936
1890
  onKeyDown?.(event);
@@ -2005,29 +1959,6 @@ function composeRefs(...refs) {
2005
1959
  };
2006
1960
  }
2007
1961
  //#endregion
2008
- //#region src/components/WheelObserver/index.tsx
2009
- /**
2010
- * @example
2011
- * <WheelObserver
2012
- * onWheel={(event) => {
2013
- * event.preventDefault()
2014
- * console.log(event.deltaY)
2015
- * }
2016
- * >
2017
- * <div>Wheel here</div>
2018
- * </WheelObserver>
2019
- */
2020
- function WheelObserver(props) {
2021
- const { as: Component = "div", children, onWheel, ref, ...attributes } = props;
2022
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
2023
- ref: composeRefs(ref, useRefCallbackEvent("wheel", (event) => {
2024
- if (onWheel) onWheel(event);
2025
- }, { passive: false }, [onWheel])),
2026
- ...attributes,
2027
- children
2028
- });
2029
- }
2030
- //#endregion
2031
1962
  //#region src/components/XYPad/Area.tsx
2032
1963
  function Area({ width = 120, height = 120, color, children, className, style, __thumb, ...props }) {
2033
1964
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2179,13 +2110,14 @@ const XYPad = {
2179
2110
  y,
2180
2111
  updateValueByEvent
2181
2112
  ]);
2182
- const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
2113
+ const { refCallback: dragRefCallback } = useDragWithElement({
2183
2114
  baseElementRef: areaElementRef,
2115
+ cursor: readonly ? void 0 : externalStyles.cursor,
2116
+ updateOnPointerDown: true,
2184
2117
  onDrag,
2185
2118
  onDragStart: (nx, ny) => {
2186
2119
  if (readonly) return;
2187
2120
  if (externalStyles.userSelectNone) addUserSelectNone();
2188
- if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
2189
2121
  thumbRef.current?.focus();
2190
2122
  const valueX = (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
2191
2123
  const valueY = (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
@@ -2194,13 +2126,12 @@ const XYPad = {
2194
2126
  onDragEnd: (nx, ny) => {
2195
2127
  if (readonly) return;
2196
2128
  if (externalStyles.userSelectNone) removeUserSelectNone();
2197
- if (externalStyles.cursor) resetCursorStyle();
2198
2129
  const valueX = (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
2199
2130
  const valueY = (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
2200
2131
  onDragEnd?.(valueX, valueY);
2201
2132
  }
2202
2133
  });
2203
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
2134
+ const wheelRefCallback = useWheel((event) => {
2204
2135
  if (!onChange || readonly) return;
2205
2136
  const target = event.shiftKey ? x : y;
2206
2137
  if (!target.wheel) return;
@@ -2211,13 +2142,7 @@ const XYPad = {
2211
2142
  const newValue = updateValueByEvent(target.wheel[0], target, delta);
2212
2143
  if (event.shiftKey) onChange(newValue, y.value);
2213
2144
  else onChange(x.value, newValue);
2214
- }, { passive: false }, [
2215
- x,
2216
- y,
2217
- onChange,
2218
- readonly,
2219
- updateValueByEvent
2220
- ]);
2145
+ });
2221
2146
  (0, react.useImperativeHandle)(forwardedRef, () => {
2222
2147
  return {
2223
2148
  focus() {
@@ -2231,7 +2156,7 @@ const XYPad = {
2231
2156
  }, []);
2232
2157
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2233
2158
  className: (0, clsx.default)("tremolo-xy-pad", className),
2234
- ref: composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback),
2159
+ ref: composeRefs(rootRef, wheelRefCallback, dragRefCallback),
2235
2160
  tabIndex: -1,
2236
2161
  "aria-disabled": disabled,
2237
2162
  "aria-readonly": readonly,
@@ -2241,10 +2166,7 @@ const XYPad = {
2241
2166
  WebkitTapHighlightColor: "transparent",
2242
2167
  ...style
2243
2168
  },
2244
- onPointerDown: (event) => {
2245
- pointerDownHandler(event);
2246
- onPointerDown?.(event);
2247
- },
2169
+ onPointerDown,
2248
2170
  onKeyDown: (event) => {
2249
2171
  handleKeyDown(event);
2250
2172
  onKeyDown?.(event);
@@ -2295,83 +2217,75 @@ function useAnimationFrame(callback = () => {}, deps = []) {
2295
2217
  }
2296
2218
  //#endregion
2297
2219
  //#region src/hooks/useMIDIAccess.ts
2298
- /** @private */
2299
- const PERMISSION_DENIED = "PERMISSION_DENIED";
2300
- /** @private */
2301
- const NOT_SUPPORTED = "NOT_SUPPORTED";
2302
2220
  /**
2303
2221
  * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
2304
2222
  */
2305
2223
  function useMIDIAccess(requestOnMount = true) {
2306
- const [midiAccess, setMidiAccess] = (0, react.useState)(null);
2307
- const [error, setError] = (0, react.useState)(null);
2308
- const request = (0, react.useCallback)(() => {
2309
- if (navigator.requestMIDIAccess) navigator.requestMIDIAccess().then((access) => {
2310
- setMidiAccess(access);
2311
- setError(null);
2312
- }).catch(() => {
2313
- setError(PERMISSION_DENIED);
2314
- });
2315
- else setError(NOT_SUPPORTED);
2316
- }, []);
2224
+ const instance = (0, react.useMemo)(() => (0, _tremolo_ui_dom.createMIDIAccess)(), []);
2225
+ const { midiAccess, error } = (0, react.useSyncExternalStore)(instance.subscribe, instance.getState, instance.getServerState);
2317
2226
  (0, react.useEffect)(() => {
2318
- if (requestOnMount) request();
2319
- }, [requestOnMount, request]);
2227
+ if (requestOnMount) instance.request();
2228
+ }, [instance, requestOnMount]);
2229
+ (0, react.useEffect)(() => {
2230
+ return () => instance.destroy();
2231
+ }, [instance]);
2320
2232
  return {
2321
- request,
2233
+ request: instance.request,
2322
2234
  midiAccess,
2323
2235
  error
2324
2236
  };
2325
2237
  }
2326
2238
  //#endregion
2327
- //#region src/hooks/useMIDIMessage.ts
2328
- /**
2329
- * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2330
- */
2331
- function useMIDIMessage(midiAccess, onMIDIMessage) {
2332
- (0, react.useEffect)(() => {
2333
- if (!midiAccess) return;
2334
- for (const input of midiAccess.inputs.values()) input.addEventListener("midimessage", onMIDIMessage);
2335
- return () => {
2336
- for (const input of midiAccess.inputs.values()) input.removeEventListener("midimessage", onMIDIMessage);
2337
- };
2338
- }, [midiAccess, onMIDIMessage]);
2339
- }
2340
- //#endregion
2341
2239
  //#region src/hooks/useMIDIInput.ts
2342
- const MIDI_EVENT_TO_NUMBER = {
2343
- NOTE_ON: 144,
2344
- NOTE_OFF: 128,
2345
- PITCH_BEND: 224
2346
- };
2347
2240
  /**
2348
2241
  * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
2349
2242
  */
2350
2243
  function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
2351
- useMIDIMessage(midiAccess, (0, react.useCallback)((event) => {
2352
- if (!event.data) return;
2353
- const kind = event.data[0] & 240;
2354
- if (kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF || kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0) onNoteOffEvent?.(event.data[1]);
2355
- else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) onNoteOnEvent?.(event.data[1], event.data[2]);
2356
- else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) onPitchBendEvent?.(event.data[1], event.data[2]);
2244
+ (0, react.useEffect)(() => {
2245
+ const instance = (0, _tremolo_ui_dom.createMIDIInput)(midiAccess, {
2246
+ onNoteOnEvent,
2247
+ onNoteOffEvent,
2248
+ onPitchBendEvent
2249
+ });
2250
+ return () => instance.destroy();
2357
2251
  }, [
2358
- onNoteOffEvent,
2252
+ midiAccess,
2359
2253
  onNoteOnEvent,
2254
+ onNoteOffEvent,
2360
2255
  onPitchBendEvent
2361
- ]));
2256
+ ]);
2257
+ }
2258
+ //#endregion
2259
+ //#region src/hooks/useMIDIMessage.ts
2260
+ /**
2261
+ * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2262
+ */
2263
+ function useMIDIMessage(midiAccess, onMIDIMessage) {
2264
+ (0, react.useEffect)(() => {
2265
+ const instance = (0, _tremolo_ui_dom.createMIDIMessage)(midiAccess, onMIDIMessage);
2266
+ return () => instance.destroy();
2267
+ }, [midiAccess, onMIDIMessage]);
2362
2268
  }
2363
2269
  //#endregion
2364
2270
  exports.AnimationCanvas = AnimationCanvas;
2365
- exports.DragObserver = DragObserver;
2366
2271
  exports.Knob = Knob;
2367
- exports.NOT_SUPPORTED = NOT_SUPPORTED;
2272
+ Object.defineProperty(exports, "NOT_SUPPORTED", {
2273
+ enumerable: true,
2274
+ get: function() {
2275
+ return _tremolo_ui_dom.NOT_SUPPORTED;
2276
+ }
2277
+ });
2368
2278
  exports.NumberInput = NumberInput;
2369
- exports.PERMISSION_DENIED = PERMISSION_DENIED;
2279
+ Object.defineProperty(exports, "PERMISSION_DENIED", {
2280
+ enumerable: true,
2281
+ get: function() {
2282
+ return _tremolo_ui_dom.PERMISSION_DENIED;
2283
+ }
2284
+ });
2370
2285
  exports.Piano = Piano;
2371
2286
  exports.PointsEditor = PointsEditor;
2372
2287
  exports.SHORTCUTS = SHORTCUTS;
2373
2288
  exports.Slider = Slider;
2374
- exports.WheelObserver = WheelObserver;
2375
2289
  exports.XYPad = XYPad;
2376
2290
  exports.clampPoint = clampPoint;
2377
2291
  exports.getNoteRangeArray = getNoteRangeArray;
@@ -2385,5 +2299,6 @@ exports.useMIDIAccess = useMIDIAccess;
2385
2299
  exports.useMIDIInput = useMIDIInput;
2386
2300
  exports.useMIDIMessage = useMIDIMessage;
2387
2301
  exports.useSliderContext = useSliderContext;
2302
+ exports.useWheel = useWheel;
2388
2303
 
2389
2304
  //# sourceMappingURL=index.cjs.map