@tremolo-ui/react 0.0.11 → 0.0.12

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 (130) hide show
  1. package/dist/cjs/index.js +428 -196
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +2 -2
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/AnimationKnob/index.d.ts +10 -3
  6. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
  7. package/dist/cjs/types/components/Knob/index.d.ts +13 -3
  8. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  9. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  10. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  11. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  12. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  13. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts +18 -0
  14. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  15. package/dist/cjs/types/components/NumberInput/Stepper.d.ts +2 -0
  16. package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -1
  17. package/dist/cjs/types/components/NumberInput/context.d.ts +3 -2
  18. package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -1
  19. package/dist/cjs/types/components/NumberInput/index.d.ts +12 -3
  20. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  21. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -1
  22. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/index.d.ts +7 -4
  24. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  25. package/dist/cjs/types/components/Piano/key.d.ts +4 -0
  26. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  27. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  28. package/dist/cjs/types/components/Slider/Scale.d.ts +1 -3
  29. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
  30. package/dist/cjs/types/components/Slider/Thumb.d.ts +1 -0
  31. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  32. package/dist/cjs/types/components/Slider/Track.d.ts +2 -2
  33. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  34. package/dist/cjs/types/components/Slider/index.d.ts +5 -1
  35. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  36. package/dist/cjs/types/components/WheelObserver/index.d.ts +3 -3
  37. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
  38. package/dist/cjs/types/components/XYPad/Area.d.ts +3 -3
  39. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
  40. package/dist/cjs/types/components/XYPad/Thumb.d.ts +1 -0
  41. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
  42. package/dist/cjs/types/components/XYPad/index.d.ts +6 -1
  43. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  44. package/dist/cjs/types/hooks/useInterval.d.ts +2 -0
  45. package/dist/cjs/types/hooks/useInterval.d.ts.map +1 -0
  46. package/dist/cjs/types/hooks/useLongPress.d.ts +2 -0
  47. package/dist/cjs/types/hooks/useLongPress.d.ts.map +1 -0
  48. package/dist/cjs/types/index.d.ts +5 -0
  49. package/dist/cjs/types/index.d.ts.map +1 -1
  50. package/dist/esm/index.js +424 -197
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/types/components/AnimationCanvas/index.d.ts +2 -2
  53. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  54. package/dist/esm/types/components/AnimationKnob/index.d.ts +10 -3
  55. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
  56. package/dist/esm/types/components/Knob/index.d.ts +13 -3
  57. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  58. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  59. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  60. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  61. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  62. package/dist/esm/types/components/NumberInput/InternalInput.d.ts +18 -0
  63. package/dist/esm/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  64. package/dist/esm/types/components/NumberInput/Stepper.d.ts +2 -0
  65. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -1
  66. package/dist/esm/types/components/NumberInput/context.d.ts +3 -2
  67. package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -1
  68. package/dist/esm/types/components/NumberInput/index.d.ts +12 -3
  69. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  70. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
  71. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
  72. package/dist/esm/types/components/Piano/index.d.ts +7 -4
  73. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  74. package/dist/esm/types/components/Piano/key.d.ts +4 -0
  75. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  76. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  77. package/dist/esm/types/components/Slider/Scale.d.ts +1 -3
  78. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
  79. package/dist/esm/types/components/Slider/Thumb.d.ts +1 -0
  80. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  81. package/dist/esm/types/components/Slider/Track.d.ts +2 -2
  82. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  83. package/dist/esm/types/components/Slider/index.d.ts +5 -1
  84. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  85. package/dist/esm/types/components/WheelObserver/index.d.ts +3 -3
  86. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
  87. package/dist/esm/types/components/XYPad/Area.d.ts +3 -3
  88. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
  89. package/dist/esm/types/components/XYPad/Thumb.d.ts +1 -0
  90. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
  91. package/dist/esm/types/components/XYPad/index.d.ts +6 -1
  92. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  93. package/dist/esm/types/hooks/useInterval.d.ts +2 -0
  94. package/dist/esm/types/hooks/useInterval.d.ts.map +1 -0
  95. package/dist/esm/types/hooks/useLongPress.d.ts +2 -0
  96. package/dist/esm/types/hooks/useLongPress.d.ts.map +1 -0
  97. package/dist/esm/types/index.d.ts +5 -0
  98. package/dist/esm/types/index.d.ts.map +1 -1
  99. package/dist/styles/index.css +115 -33
  100. package/package.json +2 -2
  101. package/src/components/AnimationCanvas/index.tsx +14 -6
  102. package/src/components/AnimationKnob/index.tsx +216 -187
  103. package/src/components/Knob/index.tsx +230 -171
  104. package/src/components/NumberInput/DecrementStepper.tsx +9 -2
  105. package/src/components/NumberInput/IncrementStepper.tsx +9 -2
  106. package/src/components/NumberInput/InternalInput.tsx +219 -0
  107. package/src/components/NumberInput/Stepper.tsx +2 -0
  108. package/src/components/NumberInput/context.tsx +20 -8
  109. package/src/components/NumberInput/index.css +76 -22
  110. package/src/components/NumberInput/index.tsx +94 -162
  111. package/src/components/Piano/BlackKey.tsx +14 -0
  112. package/src/components/Piano/WhiteKey.tsx +14 -0
  113. package/src/components/Piano/index.css +22 -6
  114. package/src/components/Piano/index.tsx +19 -21
  115. package/src/components/Piano/key.ts +5 -0
  116. package/src/components/Piano/keyboardShortcuts.ts +2 -2
  117. package/src/components/Slider/Scale.tsx +1 -4
  118. package/src/components/Slider/Thumb.tsx +4 -0
  119. package/src/components/Slider/Track.tsx +5 -2
  120. package/src/components/Slider/index.css +5 -2
  121. package/src/components/Slider/index.tsx +51 -26
  122. package/src/components/WheelObserver/index.tsx +4 -2
  123. package/src/components/XYPad/Area.tsx +7 -6
  124. package/src/components/XYPad/Thumb.tsx +6 -0
  125. package/src/components/XYPad/index.css +12 -2
  126. package/src/components/XYPad/index.tsx +57 -41
  127. package/src/hooks/useInterval.ts +27 -0
  128. package/src/hooks/useLongPress.ts +31 -0
  129. package/src/index.ts +6 -0
  130. package/src/styles/index.css +0 -1
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import React, { useRef, useCallback, useEffect, useInsertionEffect, useMemo, createContext, useContext, useState, forwardRef, useImperativeHandle, createRef } from 'react';
2
+ import React, { useRef, useCallback, useEffect, useInsertionEffect, useMemo, forwardRef, useImperativeHandle, createContext, useContext, useState, createRef } from 'react';
3
3
 
4
4
  /******************************************************************************
5
5
  Copyright (c) Microsoft Corporation.
@@ -67,7 +67,7 @@ function setDprConfig(canvas, context, width, height, dpr) {
67
67
  canvas.style.height = `${height}px`;
68
68
  }
69
69
  function AnimationCanvas(_a) {
70
- var { width = 100, height = 100, relativeSize = false, reduceFlickering = true, options, init, draw, className } = _a, props = __rest(_a, ["width", "height", "relativeSize", "reduceFlickering", "options", "init", "draw", "className"]);
70
+ var { width = 100, height = 100, relativeSize = false, reduceFlickering = true, options, init, draw, className, onContextMenu = (event) => event.preventDefault() } = _a, props = __rest(_a, ["width", "height", "relativeSize", "reduceFlickering", "options", "init", "draw", "className", "onContextMenu"]);
71
71
  const canvasRef = useRef(null);
72
72
  const memoCanvasRef = useRef(null);
73
73
  const reqIdRef = useRef(-1);
@@ -116,7 +116,11 @@ function AnimationCanvas(_a) {
116
116
  for (const prop of drawingState) {
117
117
  context[prop] = contextMemo[prop];
118
118
  }
119
- if (reduceFlickering && memoContext) {
119
+ if (reduceFlickering &&
120
+ memoContext &&
121
+ memoCanvas &&
122
+ memoCanvas.width > 0 &&
123
+ memoCanvas.height > 0) {
120
124
  context.drawImage(memoContext.canvas, 0, 0);
121
125
  }
122
126
  }
@@ -148,7 +152,7 @@ function AnimationCanvas(_a) {
148
152
  };
149
153
  }
150
154
  }, [loop, init, options, reduceFlickering, relativeSize]);
151
- return (jsxs(Fragment, { children: [jsx("canvas", Object.assign({ className: clsx('tremolo-animation-canvas', className), width: relativeSize ? 0 : width, height: relativeSize ? 0 : height, ref: canvasRef }, props)), relativeSize && reduceFlickering && (jsx("canvas", { style: { display: 'none' }, ref: memoCanvasRef }))] }));
155
+ return (jsxs(Fragment, { children: [jsx("canvas", Object.assign({ className: clsx('tremolo-animation-canvas', className), width: relativeSize ? 0 : width, height: relativeSize ? 0 : height, ref: canvasRef, onContextMenu: onContextMenu }, props)), relativeSize && reduceFlickering && (jsx("canvas", { style: { display: 'none' }, ref: memoCanvasRef }))] }));
152
156
  }
153
157
 
154
158
  /**
@@ -256,7 +260,7 @@ function xor(a = false, b = false) {
256
260
  /**
257
261
  * @category MIDI
258
262
  */
259
- const noteNames = [
263
+ const noteKeys = [
260
264
  'C',
261
265
  'C#',
262
266
  'D',
@@ -290,18 +294,16 @@ function parseNoteName(noteName) {
290
294
  */
291
295
  function noteNumber(noteName) {
292
296
  const { letter, accidental, octave } = parseNoteName(noteName);
293
- const noteIndex = noteNames.indexOf(letter.toLocaleUpperCase());
297
+ const noteIndex = noteKeys.indexOf(letter.toLocaleUpperCase());
294
298
  const accidentalValue = (accidental[0] == 'b' ? -1 : 1) * accidental.length;
295
299
  return noteIndex + 12 * (Number(octave) + 1) + accidentalValue;
296
300
  }
297
301
  /**
298
- * Convert noteNumber to noteName
302
+ * Convert noteNumber to noteKey
299
303
  * @category MIDI
300
304
  */
301
- function noteName(noteNumber) {
302
- const noteIndex = mod(noteNumber, 12);
303
- const octave = Math.floor(noteNumber / 12) - 1;
304
- return noteNames[noteIndex] + octave;
305
+ function noteKey(noteNumber) {
306
+ return noteKeys[mod(noteNumber, 12)];
305
307
  }
306
308
  /**
307
309
  * @param note noteNumber: 0 ~ 127 or noteName e.g. 'C3'
@@ -424,13 +426,14 @@ function removeNoSelect() {
424
426
  }
425
427
 
426
428
  /**
427
- * simple rotary Knob.
429
+ * Animatable rotary knob.
428
430
  * @category AnimationKnob
429
431
  */
430
- function AnimationKnob(_a) {
432
+ const AnimationKnob = forwardRef((_a, ref) => {
431
433
  var { value, min, max, defaultValue = min, startValue = min, step = 1, skew = 1, size, width = 50, height = 50, activeColor = '#7998ec', inactiveColor = '#eee', bg = '#ccc', thumb = '#4e76e6', lineWeight = 6, thumbWeight = 4, draw, bodyNoSelect = true, wheel = ['raw', 1], keyboard = ['raw', 1], enableDoubleClickDefault = true, style, className, onChange } = _a, props = __rest(_a, ["value", "min", "max", "defaultValue", "startValue", "step", "skew", "size", "width", "height", "activeColor", "inactiveColor", "bg", "thumb", "lineWeight", "thumbWeight", "draw", "bodyNoSelect", "wheel", "keyboard", "enableDoubleClickDefault", "style", "className", "onChange"]);
432
434
  // -- state and ref ---
433
435
  const valueRef = useRef(0);
436
+ const elmRef = useRef(null);
434
437
  // --- interpret props ---
435
438
  const isRelativeSize = typeof (size !== null && size !== void 0 ? size : width) == 'string' || typeof height == 'string';
436
439
  const onDrag = useCallback((_x, y) => {
@@ -487,14 +490,27 @@ function AnimationKnob(_a) {
487
490
  if (onChange)
488
491
  onChange(clamp(stepValue(v, step), min, max));
489
492
  }, { passive: false }, [wheel, value, min, max, skew, step, onChange]);
493
+ useImperativeHandle(ref, () => {
494
+ return {
495
+ focus() {
496
+ var _a;
497
+ (_a = elmRef.current) === null || _a === void 0 ? void 0 : _a.focus();
498
+ },
499
+ blur() {
500
+ var _a;
501
+ (_a = elmRef.current) === null || _a === void 0 ? void 0 : _a.blur();
502
+ },
503
+ };
504
+ }, []);
490
505
  return (jsx("div", Object.assign({ className: clsx('tremolo-animation-knob', className), ref: (div) => {
506
+ elmRef.current = div;
491
507
  wheelRefCallback(div);
492
508
  touchMoveRefCallback(div);
493
509
  }, role: "slider", tabIndex: 0, "aria-valuenow": value, "aria-valuemax": max, "aria-valuemin": min, style: Object.assign({ display: 'inline-block', width: size !== null && size !== void 0 ? size : width, height: size !== null && size !== void 0 ? size : height, cursor: 'pointer', WebkitTapHighlightColor: 'transparent' }, style), onPointerDown: pointerDownHandler, onDoubleClick: () => {
494
510
  if (enableDoubleClickDefault && onChange) {
495
511
  onChange(defaultValue);
496
512
  }
497
- }, onKeyDown: handleKeyDown, onContextMenu: (e) => e.preventDefault() }, props, { children: jsx(AnimationCanvas, Object.assign({}, (isRelativeSize
513
+ }, onKeyDown: handleKeyDown }, props, { children: jsx(AnimationCanvas, Object.assign({}, (isRelativeSize
498
514
  ? { relativeSize: true }
499
515
  : {
500
516
  width: size !== null && size !== void 0 ? size : width,
@@ -541,7 +557,7 @@ function AnimationKnob(_a) {
541
557
  ctx.lineWidth = thumbWeight;
542
558
  ctx.stroke();
543
559
  } })) })));
544
- }
560
+ });
545
561
 
546
562
  /** @category DragObserver */
547
563
  function DragObserver(props) {
@@ -555,15 +571,20 @@ function DragObserver(props) {
555
571
  return (jsx(Component, Object.assign({ ref: refCallback, onPointerDown: pointerDownHandler }, attributes, { children: children })));
556
572
  }
557
573
 
558
- /** @category Knob */
559
- function Knob(_a) {
560
- var { 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, className, classes } = _a, props = __rest(_a, ["value", "min", "max", "step", "skew", "defaultValue", "startValue", "size", "bodyNoSelect", "wheel", "keyboard", "enableDoubleClickDefault", "disabled", "readonly", "activeLine", "inactiveLine", "thumb", "thumbLine", "onChange", "className", "classes"]);
574
+ /**
575
+ * Interactive rotary knob component implemented in SVG.
576
+ *
577
+ * @category Knob
578
+ */
579
+ const Knob = forwardRef((_a, ref) => {
580
+ var { 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 } = _a, props = __rest(_a, ["value", "min", "max", "step", "skew", "defaultValue", "startValue", "size", "bodyNoSelect", "wheel", "keyboard", "enableDoubleClickDefault", "disabled", "readonly", "activeLine", "inactiveLine", "thumb", "thumbLine", "onChange", "onKeyDown", "onPointerDown", "onDoubleClick", "className", "classes"]);
561
581
  const valueRef = useRef(0);
582
+ const elmRef = useRef(null);
562
583
  // --- interpret props ---
563
584
  const padding = 8; // %
564
585
  const thumbLineWeight = 6; // %
565
- const thumbLength = 35; // %
566
- const lineWeight = 3; // px
586
+ const thumbLineLength = 35; // %
587
+ const lineWeight = size * 0.06; // px
567
588
  const p = normalizeValue(value, min, max, skew);
568
589
  const s = normalizeValue(startValue, min, max, skew);
569
590
  // --- internal functions ---
@@ -574,6 +595,17 @@ function Knob(_a) {
574
595
  const v2 = clamp(stepValue(v, step), min, max);
575
596
  onChange(v2);
576
597
  }, [max, min, onChange, readonly, skew, step]);
598
+ const updateValueByEvent = useCallback((eventType, x) => {
599
+ let newValue;
600
+ if (eventType == 'normalized') {
601
+ const n = normalizeValue(value, min, max, skew);
602
+ newValue = rawValue(n + x, min, max, skew);
603
+ }
604
+ else {
605
+ newValue = value + x;
606
+ }
607
+ return clamp(stepValue(newValue, step), min, max);
608
+ }, [max, min, skew, step, value]);
577
609
  const handleKeyDown = useCallback((event) => {
578
610
  if (!keyboard || !onChange || readonly)
579
611
  return;
@@ -581,17 +613,9 @@ function Knob(_a) {
581
613
  if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
582
614
  event.preventDefault();
583
615
  const x = key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1];
584
- let v;
585
- if (keyboard[0] == 'normalized') {
586
- const n = normalizeValue(value, min, max, skew);
587
- v = rawValue(n + x, min, max, skew);
588
- }
589
- else {
590
- v = value + x;
591
- }
592
- onChange(clamp(stepValue(v, step), min, max));
616
+ onChange(updateValueByEvent(keyboard[0], x));
593
617
  }
594
- }, [value, min, max, step, skew, keyboard, readonly, onChange]);
618
+ }, [keyboard, onChange, readonly, updateValueByEvent]);
595
619
  // --- hooks ---
596
620
  const [touchMoveRefCallback, pointerDownHandler] = useDrag({
597
621
  onDrag: onDrag,
@@ -606,21 +630,26 @@ function Knob(_a) {
606
630
  },
607
631
  });
608
632
  const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
609
- if (!wheel)
633
+ if (!wheel || readonly)
610
634
  return;
611
635
  event.preventDefault();
612
- const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
613
- let v;
614
- if (wheel[0] == 'normalized') {
615
- const n = normalizeValue(value, min, max, skew);
616
- v = rawValue(n + x, min, max, skew);
617
- }
618
- else {
619
- v = value + x;
620
- }
621
- if (onChange)
622
- onChange(clamp(stepValue(v, step), min, max));
623
- }, { passive: false }, [wheel, value, min, max, skew, step, onChange]);
636
+ if (!onChange || event.deltaY == 0)
637
+ return;
638
+ const x = Math.sign(event.deltaY) * -wheel[1];
639
+ onChange(updateValueByEvent(wheel[0], x));
640
+ }, { passive: false }, [wheel, onChange, readonly, updateValueByEvent]);
641
+ useImperativeHandle(ref, () => {
642
+ return {
643
+ focus() {
644
+ var _a;
645
+ (_a = elmRef.current) === null || _a === void 0 ? void 0 : _a.focus();
646
+ },
647
+ blur() {
648
+ var _a;
649
+ (_a = elmRef.current) === null || _a === void 0 ? void 0 : _a.blur();
650
+ },
651
+ };
652
+ }, []);
624
653
  const center = size / 2;
625
654
  const r1 = -135;
626
655
  const r2 = -135 + Math.min(p, s) * 270;
@@ -635,13 +664,62 @@ function Knob(_a) {
635
664
  const x4 = center + center * Math.cos(radian(r4 - 90));
636
665
  const y4 = center + center * Math.sin(radian(r4 - 90));
637
666
  return (jsxs("svg", Object.assign({ className: clsx('tremolo-knob', className), ref: (div) => {
667
+ elmRef.current = div;
638
668
  wheelRefCallback(div);
639
669
  touchMoveRefCallback(div);
640
- }, width: size, height: size, tabIndex: 0, "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled, "aria-readonly": readonly, onPointerDown: pointerDownHandler, onDoubleClick: () => {
670
+ }, width: size, height: size, tabIndex: 0, "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled, "aria-readonly": readonly, onPointerDown: (event) => {
671
+ pointerDownHandler(event);
672
+ onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
673
+ }, onDoubleClick: (event) => {
641
674
  if (enableDoubleClickDefault && onChange) {
642
675
  onChange(defaultValue);
643
676
  }
644
- }, onKeyDown: handleKeyDown, onContextMenu: (e) => e.preventDefault() }, props, { children: [startValue > min && (jsx("path", { className: clsx('tremolo-knob-inactive-line', classes === null || classes === void 0 ? void 0 : classes.inactiveLine), d: `M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`, fill: "none", stroke: inactiveLine || 'currentColor', strokeWidth: lineWeight })), jsx("path", { className: clsx('tremolo-knob-active-line', classes === null || classes === void 0 ? void 0 : classes.activeLine), d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`, fill: "none", stroke: activeLine || 'currentColor', strokeWidth: lineWeight }), startValue < max && (jsx("path", { className: clsx('tremolo-knob-inactive-line', classes === null || classes === void 0 ? void 0 : classes.inactiveLine), d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`, fill: "none", stroke: inactiveLine || 'currentColor', strokeWidth: lineWeight })), jsxs("svg", { className: clsx('tremolo-knob-thumb', classes === null || classes === void 0 ? void 0 : classes.thumb), children: [jsx("circle", { cx: "50%", cy: "50%", r: `${50 - padding}%`, fill: thumb || 'currentColor' }), jsx("line", { className: clsx('tremolo-knob-thumb-line', classes === null || classes === void 0 ? void 0 : classes.thumbLine), x1: "50%", y1: `${padding}%`, x2: "50%", y2: `${thumbLength}%`, stroke: thumbLine || 'currentColor', strokeWidth: `${thumbLineWeight}%`, transform: `rotate(${ -135 + p * 270})`, "transform-origin": "50% 50%" })] })] })));
677
+ onDoubleClick === null || onDoubleClick === void 0 ? void 0 : onDoubleClick(event);
678
+ }, onKeyDown: (event) => {
679
+ handleKeyDown(event);
680
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
681
+ } }, props, { children: [startValue > min && (jsx("path", { className: clsx('tremolo-knob-inactive-line', classes === null || classes === void 0 ? void 0 : classes.inactiveLine), d: `M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`, fill: "none", stroke: inactiveLine || 'currentColor', strokeWidth: lineWeight })), jsx("path", { className: clsx('tremolo-knob-active-line', classes === null || classes === void 0 ? void 0 : classes.activeLine), d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`, fill: "none", stroke: activeLine || 'currentColor', strokeWidth: lineWeight }), startValue < max && (jsx("path", { className: clsx('tremolo-knob-inactive-line', classes === null || classes === void 0 ? void 0 : classes.inactiveLine), d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`, fill: "none", stroke: inactiveLine || 'currentColor', strokeWidth: lineWeight })), jsxs("svg", { className: clsx('tremolo-knob-thumb', classes === null || classes === void 0 ? void 0 : classes.thumb), children: [jsx("circle", { cx: "50%", cy: "50%", r: `${50 - padding}%`, fill: thumb || 'currentColor' }), jsx("line", { className: clsx('tremolo-knob-thumb-line', classes === null || classes === void 0 ? void 0 : classes.thumbLine), x1: "50%", y1: `${padding}%`, x2: "50%", y2: `${thumbLineLength}%`, stroke: thumbLine || 'currentColor', strokeWidth: `${thumbLineWeight}%`,
682
+ // NOTE
683
+ // https://bugs.webkit.org/show_bug.cgi?id=201854
684
+ // https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
685
+ style: {
686
+ transform: `rotate(${ -135 + p * 270}deg)`,
687
+ transformOrigin: '50% 50%',
688
+ } })] })] })));
689
+ });
690
+
691
+ const createStoreImpl = (createState) => {
692
+ let state;
693
+ const listeners = /* @__PURE__ */ new Set();
694
+ const setState = (partial, replace) => {
695
+ const nextState = typeof partial === "function" ? partial(state) : partial;
696
+ if (!Object.is(nextState, state)) {
697
+ const previousState = state;
698
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
699
+ listeners.forEach((listener) => listener(state, previousState));
700
+ }
701
+ };
702
+ const getState = () => state;
703
+ const getInitialState = () => initialState;
704
+ const subscribe = (listener) => {
705
+ listeners.add(listener);
706
+ return () => listeners.delete(listener);
707
+ };
708
+ const api = { setState, getState, getInitialState, subscribe };
709
+ const initialState = state = createState(setState, getState, api);
710
+ return api;
711
+ };
712
+ const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
713
+
714
+ const identity = (arg) => arg;
715
+ function useStore(api, selector = identity) {
716
+ const slice = React.useSyncExternalStore(
717
+ api.subscribe,
718
+ () => selector(api.getState()),
719
+ () => selector(api.getInitialState())
720
+ );
721
+ React.useDebugValue(slice);
722
+ return slice;
645
723
  }
646
724
 
647
725
  /**
@@ -690,40 +768,6 @@ function parseValue(inputString, units, digit) {
690
768
  };
691
769
  }
692
770
 
693
- const createStoreImpl = (createState) => {
694
- let state;
695
- const listeners = /* @__PURE__ */ new Set();
696
- const setState = (partial, replace) => {
697
- const nextState = typeof partial === "function" ? partial(state) : partial;
698
- if (!Object.is(nextState, state)) {
699
- const previousState = state;
700
- state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
701
- listeners.forEach((listener) => listener(state, previousState));
702
- }
703
- };
704
- const getState = () => state;
705
- const getInitialState = () => initialState;
706
- const subscribe = (listener) => {
707
- listeners.add(listener);
708
- return () => listeners.delete(listener);
709
- };
710
- const api = { setState, getState, getInitialState, subscribe };
711
- const initialState = state = createState(setState, getState, api);
712
- return api;
713
- };
714
- const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
715
-
716
- const identity = (arg) => arg;
717
- function useStore(api, selector = identity) {
718
- const slice = React.useSyncExternalStore(
719
- api.subscribe,
720
- () => selector(api.getState()),
721
- () => selector(api.getInitialState())
722
- );
723
- React.useDebugValue(slice);
724
- return slice;
725
- }
726
-
727
771
  function safeClamp(value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
728
772
  return clamp(value, min, max);
729
773
  }
@@ -734,6 +778,7 @@ const createNumberInputStore = (initProps) => {
734
778
  min: 0,
735
779
  max: 0,
736
780
  step: 1,
781
+ readonly: false,
737
782
  keepWithinRange: true,
738
783
  onChange: (_v, _t) => { },
739
784
  };
@@ -742,8 +787,10 @@ const createNumberInputStore = (initProps) => {
742
787
  valueAsNumber: parseValue(String((initProps === null || initProps === void 0 ? void 0 : initProps.value) || ''), initProps === null || initProps === void 0 ? void 0 : initProps.units, initProps === null || initProps === void 0 ? void 0 : initProps.digit).rawValue,
743
788
  }), { increment: () => set((state) => {
744
789
  var _a, _b;
745
- let next = parseValue(state.value, state.units, state.digit).rawValue +
746
- ((_a = state.step) !== null && _a !== void 0 ? _a : 1);
790
+ if (state.readonly)
791
+ return {};
792
+ const current = parseValue(state.value, state.units, state.digit).rawValue;
793
+ let next = current + ((_a = state.step) !== null && _a !== void 0 ? _a : 1);
747
794
  if (state.keepWithinRange) {
748
795
  next = safeClamp(next, state.min, state.max);
749
796
  }
@@ -755,8 +802,10 @@ const createNumberInputStore = (initProps) => {
755
802
  };
756
803
  }), decrement: () => set((state) => {
757
804
  var _a, _b;
758
- let next = parseValue(state.value, state.units, state.digit).rawValue -
759
- ((_a = state.step) !== null && _a !== void 0 ? _a : 1);
805
+ if (state.readonly)
806
+ return {};
807
+ const current = parseValue(state.value, state.units, state.digit).rawValue;
808
+ let next = current - ((_a = state.step) !== null && _a !== void 0 ? _a : 1);
760
809
  if (state.keepWithinRange) {
761
810
  next = safeClamp(next, state.min, state.max);
762
811
  }
@@ -767,6 +816,8 @@ const createNumberInputStore = (initProps) => {
767
816
  valueAsNumber: next,
768
817
  };
769
818
  }), change: (value) => set((state) => {
819
+ if (state.readonly)
820
+ return {};
770
821
  const v = value !== null && value !== void 0 ? value : '';
771
822
  return {
772
823
  value: v,
@@ -803,36 +854,17 @@ function useNumberInputContext(selector) {
803
854
  return useStore(store, selector);
804
855
  }
805
856
 
806
- function Stepper(_a) {
807
- var { dynamic = true, children, className } = _a, props = __rest(_a, ["dynamic", "children", "className"]);
808
- return (jsx("div", Object.assign({ className: clsx('tremolo-number-input-stepper', className), "data-dynamic": dynamic }, props, { children: children })));
809
- }
810
-
811
- function IncrementStepper(_a) {
812
- var { size = 12, children, className } = _a, props = __rest(_a, ["size", "children", "className"]);
813
- const max = useNumberInputContext((s) => s.max);
814
- const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
815
- const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
816
- const increment = useNumberInputContext((s) => s.increment);
817
- // TODO: add long press event
818
- return (jsx("div", Object.assign({ className: clsx('tremolo-number-input-increment-stepper', className), role: "button", tabIndex: -1, "aria-disabled": keepWithinRange && valueAsNumber >= (max !== null && max !== void 0 ? max : Number.MAX_SAFE_INTEGER), onPointerDown: increment }, props, { children: children || (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "18 15 12 9 6 15" }) })) })));
819
- }
820
-
821
- function DecrementStepper(_a) {
822
- var { size = 12, children, className } = _a, props = __rest(_a, ["size", "children", "className"]);
823
- const min = useNumberInputContext((s) => s.min);
824
- const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
825
- const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
826
- const decrement = useNumberInputContext((s) => s.decrement);
827
- // TODO: add long press event
828
- return (jsx("div", Object.assign({ className: clsx('tremolo-number-input-decrement-stepper', className), role: "button", tabIndex: -1, "aria-disabled": keepWithinRange && valueAsNumber <= (min !== null && min !== void 0 ? min : Number.MIN_SAFE_INTEGER), onPointerDown: decrement }, props, { children: children || (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "6 9 12 15 18 9" }) })) })));
829
- }
830
-
831
- function InternalInput(_a) {
832
- var { min, max, step, defaultValue, units, disabled, digit, selectWithFocus, blurOnEnter, keepWithinRange, clampValueOnBlur, wheel, keyboard, wrapperClassName, className, onChange, onFocus, onBlur, children: _children } = _a, props = __rest(_a, ["min", "max", "step", "defaultValue", "units", "disabled", "digit", "selectWithFocus", "blurOnEnter", "keepWithinRange", "clampValueOnBlur", "wheel", "keyboard", "wrapperClassName", "className", "onChange", "onFocus", "onBlur", "children"]);
857
+ const InternalInput = forwardRef((_a, ref) => {
858
+ var { disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children } = _a, props = __rest(_a, ["disabled", "readonly", "selectWithFocus", "blurOnEnter", "clampValueOnBlur", "wheel", "keyboard", "className", "onChange", "onFocus", "onBlur", "onKeyDown", "children"]);
859
+ const elmRef = useRef(null);
833
860
  const [editing, setEditing] = useState(false);
834
861
  const value = useNumberInputContext((s) => s.value);
835
862
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
863
+ const min = useNumberInputContext((s) => s.min);
864
+ const max = useNumberInputContext((s) => s.max);
865
+ const step = useNumberInputContext((s) => s.step);
866
+ const units = useNumberInputContext((s) => s.units);
867
+ const digit = useNumberInputContext((s) => s.digit);
836
868
  const change = useNumberInputContext((s) => s.change);
837
869
  const error = useMemo(() => {
838
870
  if (editing)
@@ -840,11 +872,71 @@ function InternalInput(_a) {
840
872
  const v = valueAsNumber;
841
873
  return (min !== null && min !== void 0 ? min : -Infinity) > v || v > (max !== null && max !== void 0 ? max : Infinity);
842
874
  }, [editing, max, min, valueAsNumber]);
843
- return (jsx("input", Object.assign({ className: clsx('tremolo-number-input', className), value: value, min: min, max: max, step: step, type: "text", disabled: disabled, tabIndex: -1, "data-error": error, onChange: (event) => {
875
+ const updateValueByEvent = useCallback((eventType, x) => {
876
+ let newValue;
877
+ if (eventType == 'normalized') {
878
+ if (!min || !max) {
879
+ throw new Error('required parameter: min, max'); // TODO
880
+ }
881
+ const n = normalizeValue(valueAsNumber, min, max);
882
+ newValue = rawValue(n + x, min, max);
883
+ return clamp(stepValue(newValue, step), min, max);
884
+ }
885
+ else {
886
+ newValue = valueAsNumber + x;
887
+ return stepValue(newValue, step);
888
+ }
889
+ }, [max, min, step, valueAsNumber]);
890
+ const handleKeyDown = useCallback((event) => {
891
+ if (!keyboard || !onChange || readonly)
892
+ return;
893
+ const key = event.key;
894
+ if (!['ArrowUp', 'ArrowDown'].includes(key))
895
+ return;
896
+ event.preventDefault();
897
+ const x = key == 'ArrowUp' ? keyboard[1] : -keyboard[1];
898
+ const parsed = parseValue(String(updateValueByEvent(keyboard[0], x)));
899
+ console.log('key control');
900
+ onChange === null || onChange === void 0 ? void 0 : onChange(parsed.rawValue, parsed.formatValue);
901
+ }, [keyboard, readonly, onChange, updateValueByEvent]);
902
+ const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
903
+ if (!wheel || readonly)
904
+ return;
905
+ event.preventDefault();
906
+ if (!onChange || event.deltaY == 0)
907
+ return;
908
+ const x = Math.sign(event.deltaY) * -wheel[1];
909
+ const parsed = parseValue(String(updateValueByEvent(wheel[0], x)));
910
+ console.log('wheel control');
911
+ onChange === null || onChange === void 0 ? void 0 : onChange(parsed.rawValue, parsed.formatValue);
912
+ }, { passive: false }, [wheel, readonly, onChange, updateValueByEvent]);
913
+ useImperativeHandle(ref, () => {
914
+ return {
915
+ focus() {
916
+ var _a;
917
+ (_a = elmRef.current) === null || _a === void 0 ? void 0 : _a.focus();
918
+ },
919
+ blur() {
920
+ var _a;
921
+ (_a = elmRef.current) === null || _a === void 0 ? void 0 : _a.blur();
922
+ },
923
+ };
924
+ }, []);
925
+ return (jsx("input", Object.assign({ ref: (input) => {
926
+ elmRef.current = input;
927
+ wheelRefCallback(input);
928
+ }, className: clsx('tremolo-number-input', className), value: value, min: min, max: max, step: step, type: "text", "aria-disabled": disabled, "aria-readonly": readonly, tabIndex: -1, "data-error": error, onChange: (event) => {
929
+ if (readonly)
930
+ return;
844
931
  const v = event.currentTarget.value;
932
+ const cursorPos = event.target.selectionStart;
845
933
  change(v);
846
934
  const valueAsNumber = parseValue(v, units, digit).rawValue;
847
935
  onChange === null || onChange === void 0 ? void 0 : onChange(valueAsNumber, v);
936
+ if (cursorPos) {
937
+ event.target.selectionStart = cursorPos;
938
+ event.target.selectionEnd = cursorPos;
939
+ }
848
940
  }, onFocus: (event) => {
849
941
  setEditing(true);
850
942
  const v = event.currentTarget.value;
@@ -873,18 +965,97 @@ function InternalInput(_a) {
873
965
  if (blurOnEnter && event.key == 'Enter') {
874
966
  event.currentTarget.blur();
875
967
  }
968
+ handleKeyDown(event);
969
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
876
970
  } }, props)));
971
+ });
972
+
973
+ /** @category NumberInput */
974
+ function Stepper(_a) {
975
+ var { dynamic = true, children, className } = _a, props = __rest(_a, ["dynamic", "children", "className"]);
976
+ return (jsx("div", Object.assign({ className: clsx('tremolo-number-input-stepper', className), "data-dynamic": dynamic }, props, { children: children })));
977
+ }
978
+
979
+ function useInterval(callback, delay) {
980
+ const savedCallback = useRef(callback);
981
+ // Remember the latest callback if it changes.
982
+ useEffect(() => {
983
+ savedCallback.current = callback;
984
+ }, [callback]);
985
+ // Set up the interval.
986
+ useEffect(() => {
987
+ // Don't schedule if no delay is specified.
988
+ // Note: 0 is a valid value for delay.
989
+ if (delay === null) {
990
+ return;
991
+ }
992
+ const id = setInterval(() => {
993
+ savedCallback.current();
994
+ }, delay);
995
+ return () => {
996
+ clearInterval(id);
997
+ };
998
+ }, [delay]);
999
+ }
1000
+
1001
+ function useLongPress(callback, initialDelay = 500, interval = 40) {
1002
+ const [pressed, setPressed] = useState(false);
1003
+ const [delay, setDelay] = useState(initialDelay);
1004
+ useInterval(() => {
1005
+ callback();
1006
+ setDelay(interval);
1007
+ }, pressed ? delay : null);
1008
+ useEventListener(globalThis.window, 'pointerup', () => {
1009
+ setPressed(false);
1010
+ setDelay(initialDelay);
1011
+ });
1012
+ return useCallback(() => {
1013
+ callback();
1014
+ setPressed(true);
1015
+ }, [callback]);
1016
+ }
1017
+
1018
+ /** @category NumberInput */
1019
+ function IncrementStepper(_a) {
1020
+ var { size = 12, children, className } = _a, props = __rest(_a, ["size", "children", "className"]);
1021
+ const max = useNumberInputContext((s) => s.max);
1022
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
1023
+ const readonly = useNumberInputContext((s) => s.readonly);
1024
+ const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
1025
+ const increment = useNumberInputContext((s) => s.increment);
1026
+ const press = useLongPress(increment);
1027
+ return (
1028
+ // eslint-disable-next-line jsx-a11y/role-supports-aria-props
1029
+ jsx("div", Object.assign({ className: clsx('tremolo-number-input-increment-stepper', className), role: "button", tabIndex: -1, "aria-disabled": keepWithinRange && valueAsNumber >= (max !== null && max !== void 0 ? max : Number.MAX_SAFE_INTEGER), "aria-readonly": readonly, onPointerDown: press }, props, { children: children || (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "18 15 12 9 6 15" }) })) })));
1030
+ }
1031
+
1032
+ /** @category NumberInput */
1033
+ function DecrementStepper(_a) {
1034
+ var { size = 12, children, className } = _a, props = __rest(_a, ["size", "children", "className"]);
1035
+ const min = useNumberInputContext((s) => s.min);
1036
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
1037
+ const readonly = useNumberInputContext((s) => s.readonly);
1038
+ const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
1039
+ const decrement = useNumberInputContext((s) => s.decrement);
1040
+ const press = useLongPress(decrement);
1041
+ return (
1042
+ // eslint-disable-next-line jsx-a11y/role-supports-aria-props
1043
+ jsx("div", Object.assign({ className: clsx('tremolo-number-input-decrement-stepper', className), role: "button", tabIndex: -1, "aria-disabled": keepWithinRange && valueAsNumber <= (min !== null && min !== void 0 ? min : Number.MIN_SAFE_INTEGER), "aria-readonly": readonly, onPointerDown: press }, props, { children: children || (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "6 9 12 15 18 9" }) })) })));
877
1044
  }
1045
+
878
1046
  /**
879
1047
  * Input with some useful functions for entering numerical values.
880
1048
  * @category NumberInput
881
1049
  */
882
- function NumberInput(_a) {
883
- var { value, min, max, step = 1, defaultValue, units, disabled = false, digit, selectWithFocus = 'none', blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ['raw', 1], keyboard = ['raw', 1], wrapperClassName, className, onChange, onFocus, onBlur, children } = _a, props = __rest(_a, ["value", "min", "max", "step", "defaultValue", "units", "disabled", "digit", "selectWithFocus", "blurOnEnter", "keepWithinRange", "clampValueOnBlur", "wheel", "keyboard", "wrapperClassName", "className", "onChange", "onFocus", "onBlur", "children"]);
884
- return (jsx(NumberInputProvider, { value: String(value), min: min, max: max, step: step, units: units, digit: digit, keepWithinRange: keepWithinRange, onChange: onChange, children: jsxs("div", { className: clsx('tremolo-number-input-wrapper', wrapperClassName),
1050
+ const NumberInput = forwardRef((_a, ref) => {
1051
+ var { 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 } = _a, props = __rest(_a, ["value", "min", "max", "step", "units", "readonly", "disabled", "digit", "variant", "selectWithFocus", "blurOnEnter", "keepWithinRange", "clampValueOnBlur", "wheel", "keyboard", "activeColor", "wrapperClassName", "className", "onChange", "onFocus", "onBlur", "children"]);
1052
+ const colors = {
1053
+ '--active-color': activeColor,
1054
+ };
1055
+ return (jsx(NumberInputProvider, { value: String(value), min: min, max: max, step: step, units: units, digit: digit, readonly: readonly, keepWithinRange: keepWithinRange, onChange: onChange, children: jsxs("div", { className: clsx('tremolo-number-input-wrapper', wrapperClassName),
885
1056
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
886
- tabIndex: 0, "data-stepper": !!children, children: [jsx(InternalInput, Object.assign({ min: min, max: max, step: step, defaultValue: defaultValue, units: units, disabled: disabled, digit: digit, selectWithFocus: selectWithFocus, blurOnEnter: blurOnEnter, keepWithinRange: keepWithinRange, clampValueOnBlur: clampValueOnBlur, wheel: wheel, keyboard: keyboard, wrapperClassName: wrapperClassName, className: className, onChange: onChange, onFocus: onFocus, onBlur: onBlur }, props)), children] }) }));
887
- }
1057
+ tabIndex: 0, style: Object.assign({}, colors), "data-stepper": !!children, "data-variant": variant, children: [jsx(InternalInput, Object.assign({ ref: ref, readonly: readonly, disabled: disabled, selectWithFocus: selectWithFocus, blurOnEnter: blurOnEnter, clampValueOnBlur: clampValueOnBlur, wheel: wheel, keyboard: keyboard, className: className, onChange: onChange, onFocus: onFocus, onBlur: onBlur }, props)), children] }) }));
1058
+ });
888
1059
 
889
1060
  /** @category Piano */
890
1061
  function KeyLabel(_a) {
@@ -899,8 +1070,14 @@ function KeyLabel(_a) {
899
1070
  const defaultWhiteKeyWidth = 40;
900
1071
  /** @category Piano */
901
1072
  const WhiteKey = forwardRef((_a, ref) => {
902
- var { width = defaultWhiteKeyWidth, height = '100%', className, style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __onPlayNote, __onStopNote, __label } = _a, props = __rest(_a, ["width", "height", "className", "style", "children", "__glissando", "__position", "__note", "__disabled", "__index", "__fill", "__width", "__onPlayNote", "__onStopNote", "__label"]);
1073
+ var { width = defaultWhiteKeyWidth, height = '100%', bg, color, activeBg, activeColor, className, style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __onPlayNote, __onStopNote, __label, onContextMenu = (e) => e.preventDefault() } = _a, props = __rest(_a, ["width", "height", "bg", "color", "activeBg", "activeColor", "className", "style", "children", "__glissando", "__position", "__note", "__disabled", "__index", "__fill", "__width", "__onPlayNote", "__onStopNote", "__label", "onContextMenu"]);
903
1074
  const [played, setPlayed] = useState(false);
1075
+ const colors = {
1076
+ '--bg': bg,
1077
+ '--color': color,
1078
+ '--active-bg': activeBg,
1079
+ '--active-color': activeColor,
1080
+ };
904
1081
  useImperativeHandle(ref, () => {
905
1082
  return {
906
1083
  play() {
@@ -936,7 +1113,7 @@ const WhiteKey = forwardRef((_a, ref) => {
936
1113
  }
937
1114
  });
938
1115
  }
939
- return (jsx("div", Object.assign({ className: clsx(`tremolo-piano-white-key`, className), "aria-disabled": __disabled, "data-active": played, style: Object.assign({ left: __position, width: __fill ? __width : (width !== null && width !== void 0 ? width : __width), height: height }, style), onPointerDown: () => {
1116
+ return (jsx("div", Object.assign({ className: clsx(`tremolo-piano-white-key`, className), "aria-disabled": __disabled, "data-active": played, style: Object.assign(Object.assign(Object.assign({}, colors), { left: __position, width: __fill ? __width : (width !== null && width !== void 0 ? width : __width), height: height }), style), onPointerDown: () => {
940
1117
  if (__disabled)
941
1118
  return;
942
1119
  setPlayed(true);
@@ -964,13 +1141,19 @@ const WhiteKey = forwardRef((_a, ref) => {
964
1141
  if (__onStopNote)
965
1142
  __onStopNote(__note);
966
1143
  }
967
- } }, props, { children: jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) })));
1144
+ }, onContextMenu: onContextMenu }, props, { children: jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) })));
968
1145
  });
969
1146
 
970
1147
  /** @category Piano */
971
1148
  const BlackKey = forwardRef((_a, ref) => {
972
- var { width = defaultWhiteKeyWidth * 0.65, height = '60%', className, style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __onPlayNote, __onStopNote, __label } = _a, props = __rest(_a, ["width", "height", "className", "style", "children", "__glissando", "__position", "__note", "__disabled", "__index", "__fill", "__width", "__onPlayNote", "__onStopNote", "__label"]);
1149
+ var { width = defaultWhiteKeyWidth * 0.65, height = '60%', bg, color, activeBg, activeColor, className, style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __onPlayNote, __onStopNote, __label, onContextMenu = (e) => e.preventDefault() } = _a, props = __rest(_a, ["width", "height", "bg", "color", "activeBg", "activeColor", "className", "style", "children", "__glissando", "__position", "__note", "__disabled", "__index", "__fill", "__width", "__onPlayNote", "__onStopNote", "__label", "onContextMenu"]);
973
1150
  const [played, setPlayed] = useState(false);
1151
+ const colors = {
1152
+ '--bg': bg,
1153
+ '--color': color,
1154
+ '--active-bg': activeBg,
1155
+ '--active-color': activeColor,
1156
+ };
974
1157
  useImperativeHandle(ref, () => {
975
1158
  return {
976
1159
  play() {
@@ -1006,7 +1189,7 @@ const BlackKey = forwardRef((_a, ref) => {
1006
1189
  }
1007
1190
  });
1008
1191
  }
1009
- return (jsx("div", Object.assign({ className: clsx(`tremolo-piano-black-key`, className), "aria-disabled": __disabled, "data-active": played, style: Object.assign({ left: __position, width: __fill ? __width : (width !== null && width !== void 0 ? width : __width), height: height }, style), onPointerDown: () => {
1192
+ return (jsx("div", Object.assign({ className: clsx(`tremolo-piano-black-key`, className), "aria-disabled": __disabled, "data-active": played, style: Object.assign(Object.assign(Object.assign({}, colors), { left: __position, width: __fill ? __width : (width !== null && width !== void 0 ? width : __width), height: height }), style), onPointerDown: () => {
1010
1193
  if (__disabled)
1011
1194
  return;
1012
1195
  setPlayed(true);
@@ -1034,11 +1217,11 @@ const BlackKey = forwardRef((_a, ref) => {
1034
1217
  if (__onStopNote)
1035
1218
  __onStopNote(__note);
1036
1219
  }
1037
- } }, props, { children: jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) })));
1220
+ }, onContextMenu: onContextMenu }, props, { children: jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) })));
1038
1221
  });
1039
1222
 
1040
1223
  /** @category Piano */
1041
- const Shortcuts = {
1224
+ const SHORTCUTS = {
1042
1225
  HOME_ROW: {
1043
1226
  keys: [
1044
1227
  'a',
@@ -1062,9 +1245,12 @@ const Shortcuts = {
1062
1245
  },
1063
1246
  };
1064
1247
 
1065
- /** @category Piano */
1248
+ /**
1249
+ * Customizable piano component.
1250
+ * @category Piano
1251
+ */
1066
1252
  function Piano(_a) {
1067
- var { noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? '100%' : 160, style, className, onPlayNote, onStopNote, label, children } = _a, props = __rest(_a, ["noteRange", "glissando", "midiMax", "keyboardShortcuts", "fill", "height", "style", "className", "onPlayNote", "onStopNote", "label", "children"]);
1253
+ var { noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? '100%' : 160, style, className, onPlayNote, onStopNote, label, children, onPointerUp, onPointerDown } = _a, props = __rest(_a, ["noteRange", "glissando", "midiMax", "keyboardShortcuts", "fill", "height", "style", "className", "onPlayNote", "onStopNote", "label", "children", "onPointerUp", "onPointerDown"]);
1068
1254
  // -- state and ref ---
1069
1255
  const [pressed, setPressed] = useState(false);
1070
1256
  const [whiteNoteWidth, setWhiteNoteWidth] = useState(-1);
@@ -1111,15 +1297,11 @@ function Piano(_a) {
1111
1297
  'A#': 5.85,
1112
1298
  B: 6,
1113
1299
  };
1114
- const toNoteName = (n) => {
1115
- const { letter, accidental } = parseNoteName(noteName(n));
1116
- return (letter + accidental);
1117
- };
1118
- const _noteName = toNoteName(note);
1119
- const firstNoteName = toNoteName(noteRange.first);
1120
- const pos = pitchPositions[_noteName] - pitchPositions[firstNoteName];
1300
+ const targetNoteKey = noteKey(note);
1301
+ const firstNoteKey = noteKey(noteRange.first);
1302
+ const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1121
1303
  const octave = Math.floor((note - noteRange.first) / 12);
1122
- const octaveOffset = noteNames.indexOf(firstNoteName) > noteNames.indexOf(_noteName) ? 1 : 0;
1304
+ const octaveOffset = noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1123
1305
  const length = whiteNoteWidth + 3;
1124
1306
  return pos * length + (octave + octaveOffset) * 7 * length;
1125
1307
  }
@@ -1134,6 +1316,9 @@ function Piano(_a) {
1134
1316
  setWhiteNoteWidth(w / whiteNoteCount - 3);
1135
1317
  });
1136
1318
  resizeObserver.observe(parent);
1319
+ return () => {
1320
+ resizeObserver.unobserve(parent);
1321
+ };
1137
1322
  }
1138
1323
  else {
1139
1324
  setWhiteNoteWidth((whiteKeyProps === null || whiteKeyProps === void 0 ? void 0 : whiteKeyProps.width) || defaultWhiteKeyWidth);
@@ -1164,13 +1349,11 @@ function Piano(_a) {
1164
1349
  }
1165
1350
  });
1166
1351
  return (jsx("div", Object.assign({ ref: pianoRef, className: clsx('tremolo-piano', className), style: Object.assign({ width: fill ? '100%' : (whiteNoteWidth + 3) * whiteNoteCount, height: height }, style), onPointerDown: (e) => {
1167
- if (props.onPointerDown)
1168
- props.onPointerDown(e);
1169
1352
  setPressed(true);
1353
+ onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(e);
1170
1354
  }, onPointerUp: (e) => {
1171
- if (props.onPointerUp)
1172
- props.onPointerUp(e);
1173
1355
  setPressed(false);
1356
+ onPointerUp === null || onPointerUp === void 0 ? void 0 : onPointerUp(e);
1174
1357
  } }, props, { children: noteRangeArray.map((note, index) => {
1175
1358
  return isWhiteKey(note) ? (jsx(WhiteKey, Object.assign({ ref: keyRefs.current[index], __glissando: glissando && pressed, __position: notePosition(note), __note: note, __disabled: note > midiMax, __index: index, __fill: fill, __width: whiteNoteWidth, __onPlayNote: onPlayNote, __onStopNote: onStopNote, __label: label }, whiteKeyProps), note)) : (jsx(BlackKey, Object.assign({ ref: keyRefs.current[index], __glissando: glissando && pressed, __position: notePosition(note), __note: note, __disabled: note > midiMax, __index: index, __fill: fill, __width: whiteNoteWidth * 0.65, __onPlayNote: onPlayNote, __onStopNote: onStopNote, __label: label }, blackKeyProps), note));
1176
1359
  }) })));
@@ -1287,7 +1470,7 @@ function Scale(_a) {
1287
1470
  /** @category Slider */
1288
1471
  const defaultThumbSize = 22;
1289
1472
  /** @category Slider */
1290
- const SliderThumb = forwardRef(({ size, width = defaultThumbSize, height = defaultThumbSize, children, className, style, __percent, }, ref) => {
1473
+ const SliderThumb = forwardRef(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent, }, ref) => {
1291
1474
  const wrapperRef = useRef(null);
1292
1475
  const vertical = useSliderContext((s) => s.vertical);
1293
1476
  const disabled = useSliderContext((s) => s.disabled);
@@ -1311,13 +1494,14 @@ const SliderThumb = forwardRef(({ size, width = defaultThumbSize, height = defau
1311
1494
  // default slider thumb
1312
1495
  jsx("div", { ref: wrapperRef, className: clsx('tremolo-slider-thumb', className),
1313
1496
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
1314
- tabIndex: 0, "aria-disabled": disabled, "aria-readonly": readonly, style: Object.assign({ width: size !== null && size !== void 0 ? size : width, height: size !== null && size !== void 0 ? size : height }, style) })) }));
1497
+ tabIndex: 0, "aria-disabled": disabled, "aria-readonly": readonly, style: Object.assign(Object.assign({ '--color': color }, { width: size !== null && size !== void 0 ? size : width, height: size !== null && size !== void 0 ? size : height }), style) })) }));
1315
1498
  });
1316
1499
 
1317
1500
  const defaultLength = 140;
1318
1501
  const defaultThickness = 10;
1319
1502
  /** @category Slider */
1320
- function SliderTrack({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0, }) {
1503
+ function SliderTrack(_a) {
1504
+ var { length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0 } = _a, props = __rest(_a, ["length", "thickness", "active", "inactive", "children", "className", "style", "defaultStyle", "__thumb", "__percent"]);
1321
1505
  const vertical = useSliderContext((s) => s.vertical);
1322
1506
  const reverse = useSliderContext((s) => s.reverse);
1323
1507
  const disabled = useSliderContext((s) => s.disabled);
@@ -1326,11 +1510,11 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1326
1510
  '--active': active,
1327
1511
  '--inactive': inactive,
1328
1512
  };
1329
- return (jsxs("div", { className: clsx('tremolo-slider-track', className), "aria-disabled": disabled, "data-vertical": vertical, style: !defaultStyle
1513
+ return (jsxs("div", Object.assign({ className: clsx('tremolo-slider-track', className), "aria-disabled": disabled, "data-vertical": vertical, style: !defaultStyle
1330
1514
  ? style
1331
1515
  : Object.assign(Object.assign(Object.assign({}, colors), { background: xor(vertical, reverse)
1332
1516
  ? `linear-gradient(to ${direction}, var(--inactive, #eee) ${__percent}%, var(--active, #7998ec) ${__percent}%)`
1333
- : `linear-gradient(to ${direction}, var(--active, #7998ec) ${__percent}%, var(--inactive, #eee) ${__percent}%)`, borderRadius: styleHelper(thickness, '/', 2), width: !vertical ? length : thickness, height: vertical ? length : thickness }), style), children: [children, __thumb] }));
1517
+ : `linear-gradient(to ${direction}, var(--active, #7998ec) ${__percent}%, var(--inactive, #eee) ${__percent}%)`, borderRadius: styleHelper(thickness, '/', 2), width: !vertical ? length : thickness, height: vertical ? length : thickness }), style) }, props, { children: [children, __thumb] })));
1334
1518
  }
1335
1519
 
1336
1520
  /**
@@ -1338,8 +1522,8 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1338
1522
  * @category Slider
1339
1523
  */
1340
1524
  const Slider = forwardRef((_a, ref) => {
1341
- var _b, _c, _d;
1342
- var { value, min, max, step = 1, skew = 1, vertical = false, reverse = false, className, style, bodyNoSelect = true, wheel = ['raw', 1], keyboard = ['raw', 1], disabled = false, readonly = false, onChange, children } = _a, props = __rest(_a, ["value", "min", "max", "step", "skew", "vertical", "reverse", "className", "style", "bodyNoSelect", "wheel", "keyboard", "disabled", "readonly", "onChange", "children"]);
1525
+ var _b;
1526
+ var { 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 } = _a, props = __rest(_a, ["value", "min", "max", "step", "skew", "vertical", "reverse", "bodyNoSelect", "wheel", "keyboard", "disabled", "readonly", "onChange", "onDragStart", "onDragEnd", "className", "style", "children", "onFocus", "onBlur", "onPointerDown", "onKeyDown"]);
1343
1527
  // -- state and ref ---
1344
1528
  const trackElementRef = useRef(null);
1345
1529
  const thumbRef = useRef(null);
@@ -1398,8 +1582,21 @@ const Slider = forwardRef((_a, ref) => {
1398
1582
  ? normalizeValue(mouseY, y1, y2)
1399
1583
  : normalizeValue(mouseX, x1, x2);
1400
1584
  const v = rawValue(displayReversed ? 1 - n : n, min, max, skew);
1401
- onChange(clamp(stepValue(v, step), min, max));
1585
+ const v2 = clamp(stepValue(v, step), min, max);
1586
+ onChange(v2);
1587
+ return v2;
1402
1588
  };
1589
+ const updateValueByEvent = useCallback((eventType, x) => {
1590
+ let newValue;
1591
+ if (eventType == 'normalized') {
1592
+ const n = normalizeValue(value, min, max, skew);
1593
+ newValue = rawValue(n + x, min, max, skew);
1594
+ }
1595
+ else {
1596
+ newValue = value + x;
1597
+ }
1598
+ return clamp(stepValue(newValue, step), min, max);
1599
+ }, [max, min, skew, step, value]);
1403
1600
  const handleKeyDown = useCallback((event) => {
1404
1601
  if (!keyboard || !onChange || readonly)
1405
1602
  return;
@@ -1409,17 +1606,9 @@ const Slider = forwardRef((_a, ref) => {
1409
1606
  let x = key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1];
1410
1607
  if (reverse)
1411
1608
  x *= -1;
1412
- let v;
1413
- if (keyboard[0] == 'normalized') {
1414
- const n = normalizeValue(value, min, max, skew);
1415
- v = rawValue(n + x, min, max, skew);
1416
- }
1417
- else {
1418
- v = value + x;
1419
- }
1420
- onChange(clamp(stepValue(v, step), min, max));
1609
+ onChange(updateValueByEvent(keyboard[0], x));
1421
1610
  }
1422
- }, [value, min, max, step, skew, reverse, keyboard, onChange, readonly]);
1611
+ }, [keyboard, onChange, readonly, reverse, updateValueByEvent]);
1423
1612
  // --- hooks ---
1424
1613
  const touchMoveRefCallback = useRefCallbackEvent('touchmove', handleValue, { passive: false }, [min, max, skew, step, vertical, reverse, onChange, readonly]);
1425
1614
  const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
@@ -1437,23 +1626,18 @@ const Slider = forwardRef((_a, ref) => {
1437
1626
  }
1438
1627
  if (vertical && reverse)
1439
1628
  x *= -1;
1440
- let v;
1441
- if (wheel[0] == 'normalized') {
1442
- const n = normalizeValue(value, min, max, skew);
1443
- v = rawValue(n + x, min, max, skew);
1444
- }
1445
- else {
1446
- v = value + x;
1447
- }
1448
- onChange(clamp(stepValue(v, step), min, max));
1629
+ onChange(updateValueByEvent(wheel[0], x));
1449
1630
  }, {
1450
1631
  passive: false,
1451
- }, [wheel, value, min, max, skew, step]);
1632
+ }, [wheel, vertical, readonly, onChange, updateValueByEvent]);
1452
1633
  useEventListener(globalThis.window, 'mousemove', (event) => {
1453
1634
  handleValue(event);
1454
1635
  });
1455
1636
  useEventListener(globalThis.window, 'mouseup', () => {
1637
+ if (!thumbDragged.current || readonly)
1638
+ return;
1456
1639
  thumbDragged.current = false;
1640
+ onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(value);
1457
1641
  if (bodyNoSelect)
1458
1642
  removeNoSelect();
1459
1643
  });
@@ -1474,8 +1658,22 @@ const Slider = forwardRef((_a, ref) => {
1474
1658
  touchMoveRefCallback(div);
1475
1659
  }, tabIndex: -1, role: "slider", "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-orientation": vertical ? 'vertical' : 'horizontal', "aria-disabled": disabled, "aria-readonly": readonly, style: Object.assign({ margin: `calc(${styleHelper((_b = thumbProps === null || thumbProps === void 0 ? void 0 : thumbProps.size) !== null && _b !== void 0 ? _b : 22)} / 2)` }, style), onPointerDown: (event) => {
1476
1660
  thumbDragged.current = true;
1477
- handleValue(event);
1478
- }, onKeyDown: handleKeyDown, onFocus: (_c = thumbRef.current) === null || _c === void 0 ? void 0 : _c.focus, onBlur: (_d = thumbRef.current) === null || _d === void 0 ? void 0 : _d.blur }, props, { children: jsxs("div", { style: {
1661
+ const v = handleValue(event);
1662
+ if (!readonly)
1663
+ onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(v !== null && v !== void 0 ? v : value);
1664
+ onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
1665
+ }, onKeyDown: (event) => {
1666
+ handleKeyDown(event);
1667
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
1668
+ }, onFocus: (event) => {
1669
+ var _a;
1670
+ (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1671
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
1672
+ }, onBlur: (event) => {
1673
+ var _a;
1674
+ (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.blur();
1675
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
1676
+ } }, props, { children: jsxs("div", { style: {
1479
1677
  display: 'flex',
1480
1678
  flexDirection: vertical ? 'row' : 'column',
1481
1679
  width: !vertical ? '100%' : undefined,
@@ -1494,12 +1692,14 @@ function WheelObserver(props) {
1494
1692
  }
1495
1693
 
1496
1694
  /** @category XYPad */
1497
- function XYPadArea({ width = 120, height = 120, bg = '#eee', children, className, style, __thumb, }) {
1498
- return (jsxs("div", { className: clsx('tremolo-xy-pad-area', className), style: Object.assign({ position: 'relative', width: width, height: height, background: bg }, style), children: [children, __thumb] }));
1695
+ function XYPadArea(_a) {
1696
+ var { width = 120, height = 120, color, children, className, style, __thumb } = _a, props = __rest(_a, ["width", "height", "color", "children", "className", "style", "__thumb"]);
1697
+ return (jsxs("div", Object.assign({ className: clsx('tremolo-xy-pad-area', className), style: Object.assign(Object.assign({ '--color': color }, { width: width, height: height }), style) }, props, { children: [children, __thumb] })));
1499
1698
  }
1500
1699
 
1501
1700
  /** @category XYPad */
1502
- const XYPadThumb = forwardRef(({ size, width = 22, height = 22, children, wrapperClassName, wrapperStyle, className, style, __disabled, __css, }, ref) => {
1701
+ const XYPadThumb = forwardRef((_a, ref) => {
1702
+ var { size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __css } = _a, props = __rest(_a, ["size", "width", "height", "color", "children", "wrapperClassName", "wrapperStyle", "className", "style", "__disabled", "__css"]);
1503
1703
  const wrapperRef = useRef(null);
1504
1704
  useImperativeHandle(ref, () => {
1505
1705
  return {
@@ -1513,11 +1713,11 @@ const XYPadThumb = forwardRef(({ size, width = 22, height = 22, children, wrappe
1513
1713
  },
1514
1714
  };
1515
1715
  }, []);
1516
- return (jsx("div", { className: clsx('tremolo-xy-pad-thumb-wrapper', wrapperClassName), style: Object.assign(Object.assign({}, __css), wrapperStyle), children: children ? (children) : (
1716
+ return (jsx("div", Object.assign({ className: clsx('tremolo-xy-pad-thumb-wrapper', wrapperClassName), style: Object.assign(Object.assign({}, __css), wrapperStyle) }, props, { children: children ? (children) : (
1517
1717
  // default thumb
1518
1718
  jsx("div", { ref: wrapperRef, className: clsx('tremolo-xy-pad-thumb', className),
1519
1719
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
1520
- tabIndex: 0, "aria-disabled": __disabled, style: Object.assign({ width: size !== null && size !== void 0 ? size : width, height: size !== null && size !== void 0 ? size : height }, style) })) }));
1720
+ tabIndex: 0, "aria-disabled": __disabled, style: Object.assign(Object.assign({ '--color': color }, { width: size !== null && size !== void 0 ? size : width, height: size !== null && size !== void 0 ? size : height }), style) })) })));
1521
1721
  });
1522
1722
 
1523
1723
  const defaultValueOptions = {
@@ -1532,8 +1732,8 @@ const defaultValueOptions = {
1532
1732
  * @category XYPad
1533
1733
  */
1534
1734
  const XYPad = forwardRef((_a, ref) => {
1535
- var _b, _c, _d;
1536
- var { x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, children } = _a, props = __rest(_a, ["x", "y", "className", "style", "bodyNoSelect", "disabled", "readonly", "onChange", "children"]);
1735
+ var _b;
1736
+ var { x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children } = _a, props = __rest(_a, ["x", "y", "className", "style", "bodyNoSelect", "disabled", "readonly", "onChange", "onDragStart", "onDragEnd", "onPointerDown", "onKeyDown", "onFocus", "onBlur", "children"]);
1537
1737
  const x = useMemo(() => {
1538
1738
  return Object.assign(Object.assign({}, defaultValueOptions), _x);
1539
1739
  }, [_x]);
@@ -1590,7 +1790,19 @@ const XYPad = forwardRef((_a, ref) => {
1590
1790
  const newX = clamp(stepValue(vx, x.step), x.min, x.max);
1591
1791
  const newY = clamp(stepValue(vy, y.step), y.min, y.max);
1592
1792
  onChange(newX, newY);
1793
+ return [newX, newY];
1593
1794
  };
1795
+ const updateValueByEvent = useCallback((eventType, target, x) => {
1796
+ let v;
1797
+ if (eventType == 'normalized') {
1798
+ const n = normalizeValue(target.value, target.min, target.max, target.skew);
1799
+ v = rawValue(n + x, target.min, target.max, target.skew);
1800
+ }
1801
+ else {
1802
+ v = target.value + x;
1803
+ }
1804
+ return clamp(stepValue(v, target.step), target.min, target.max);
1805
+ }, []);
1594
1806
  const handleKeyDown = useCallback((event) => {
1595
1807
  if (!onChange || readonly)
1596
1808
  return;
@@ -1606,15 +1818,7 @@ const XYPad = forwardRef((_a, ref) => {
1606
1818
  delta *= -1;
1607
1819
  if (target.reverse)
1608
1820
  delta *= -1;
1609
- let v;
1610
- if (target.keyboard[0] == 'normalized') {
1611
- const n = normalizeValue(target.value, target.min, target.max, target.skew);
1612
- v = rawValue(n + delta, target.min, target.max, target.skew);
1613
- }
1614
- else {
1615
- v = target.value + delta;
1616
- }
1617
- const newValue = clamp(stepValue(v, target.step), target.min, target.max);
1821
+ const newValue = updateValueByEvent(target.keyboard[0], target, delta);
1618
1822
  if (isHorizontal) {
1619
1823
  onChange(newValue, y.value);
1620
1824
  }
@@ -1622,7 +1826,7 @@ const XYPad = forwardRef((_a, ref) => {
1622
1826
  onChange(x.value, newValue);
1623
1827
  }
1624
1828
  }
1625
- }, [x, y, onChange, readonly]);
1829
+ }, [onChange, readonly, x, y, updateValueByEvent]);
1626
1830
  // --- hooks ---
1627
1831
  const touchMoveRefCallback = useRefCallbackEvent('touchmove', handleValue, { passive: false }, [x, onChange, readonly]);
1628
1832
  const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
@@ -1637,15 +1841,7 @@ const XYPad = forwardRef((_a, ref) => {
1637
1841
  delta *= -1;
1638
1842
  if (target.reverse)
1639
1843
  delta *= -1;
1640
- let v;
1641
- if (target.wheel[0] == 'normalized') {
1642
- const n = normalizeValue(target.value, target.min, target.max, target.skew);
1643
- v = rawValue(n + delta, target.min, target.max, target.skew);
1644
- }
1645
- else {
1646
- v = target.value + delta;
1647
- }
1648
- const newValue = clamp(stepValue(v, target.step), target.min, target.max);
1844
+ const newValue = updateValueByEvent(target.wheel[0], target, delta);
1649
1845
  if (event.shiftKey) {
1650
1846
  onChange(newValue, y.value);
1651
1847
  }
@@ -1654,12 +1850,15 @@ const XYPad = forwardRef((_a, ref) => {
1654
1850
  }
1655
1851
  }, {
1656
1852
  passive: false,
1657
- }, [x, y]);
1853
+ }, [x, y, onChange, readonly, updateValueByEvent]);
1658
1854
  useEventListener(globalThis.window, 'mousemove', (event) => {
1659
1855
  handleValue(event);
1660
1856
  });
1661
1857
  useEventListener(globalThis.window, 'mouseup', () => {
1858
+ if (!thumbDragged.current || readonly)
1859
+ return;
1662
1860
  thumbDragged.current = false;
1861
+ onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(x.value, y.value);
1663
1862
  if (bodyNoSelect)
1664
1863
  removeNoSelect();
1665
1864
  });
@@ -1681,13 +1880,41 @@ const XYPad = forwardRef((_a, ref) => {
1681
1880
  wheelRefCallback(div);
1682
1881
  touchMoveRefCallback(div);
1683
1882
  }, tabIndex: -1, "aria-disabled": disabled, "aria-readonly": readonly, style: Object.assign({ margin: `calc(${styleHelper((_b = thumbProps === null || thumbProps === void 0 ? void 0 : thumbProps.size) !== null && _b !== void 0 ? _b : 22)} / 2)`, cursor: readonly ? 'not-allowed' : 'pointer', WebkitTapHighlightColor: 'transparent' }, style), onPointerDown: (event) => {
1883
+ var _a, _b;
1684
1884
  thumbDragged.current = true;
1685
- handleValue(event);
1686
- }, onKeyDown: handleKeyDown, onFocus: (_c = thumbRef.current) === null || _c === void 0 ? void 0 : _c.focus, onBlur: (_d = thumbRef.current) === null || _d === void 0 ? void 0 : _d.blur }, props, { children: jsx("div", { className: "tremolo-xy-pad-area-wrapper", ref: areaElementRef, children: jsx(XYPadArea, Object.assign({ __thumb: jsx(XYPadThumb, Object.assign({ ref: thumbRef, __disabled: disabled, __css: {
1885
+ const v = handleValue(event);
1886
+ if (!readonly)
1887
+ onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart((_a = v === null || v === void 0 ? void 0 : v[0]) !== null && _a !== void 0 ? _a : x.value, (_b = v === null || v === void 0 ? void 0 : v[1]) !== null && _b !== void 0 ? _b : y.value);
1888
+ onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
1889
+ }, onKeyDown: (event) => {
1890
+ handleKeyDown(event);
1891
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
1892
+ }, onFocus: (event) => {
1893
+ var _a;
1894
+ (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1895
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
1896
+ }, onBlur: (event) => {
1897
+ var _a;
1898
+ (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.blur();
1899
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
1900
+ } }, props, { children: jsx("div", { className: "tremolo-xy-pad-area-wrapper", ref: areaElementRef, children: jsx(XYPadArea, Object.assign({ __thumb: jsx(XYPadThumb, Object.assign({ ref: thumbRef, __disabled: disabled, __css: {
1687
1901
  top: `${percentY}%`,
1688
1902
  left: `${percentX}%`,
1689
1903
  } }, thumbProps)) }, areaProps)) }) })));
1690
1904
  });
1691
1905
 
1692
- export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NumberInput, Piano, Scale, ScaleOption, Shortcuts, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, useSliderContext };
1906
+ const useAnimationFrame = (callback = () => { }, deps = []) => {
1907
+ const reqIdRef = useRef(-1);
1908
+ const loop = useCallback(() => {
1909
+ reqIdRef.current = requestAnimationFrame(loop);
1910
+ callback();
1911
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1912
+ }, [callback, ...deps]);
1913
+ useEffect(() => {
1914
+ reqIdRef.current = requestAnimationFrame(loop);
1915
+ return () => cancelAnimationFrame(reqIdRef.current);
1916
+ }, [loop]);
1917
+ };
1918
+
1919
+ export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NumberInput, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, useAnimationFrame, useDrag, useEventListener, useInterval, useLongPress, useSliderContext };
1693
1920
  //# sourceMappingURL=index.js.map