@tremolo-ui/react 0.1.6 → 0.2.1

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 (68) hide show
  1. package/dist/index.cjs +1329 -1152
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +238 -183
  4. package/dist/index.d.cts +367 -366
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.ts +367 -366
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1313 -1125
  9. package/dist/index.js.map +1 -1
  10. package/package.json +17 -19
  11. package/src/components/AnimationCanvas/index.tsx +0 -7
  12. package/src/components/DragObserver/index.tsx +9 -7
  13. package/src/components/Knob/ActiveLine.tsx +29 -0
  14. package/src/components/Knob/InactiveLine.tsx +53 -0
  15. package/src/components/Knob/SVGRoot.tsx +39 -0
  16. package/src/components/Knob/Thumb.tsx +64 -0
  17. package/src/components/Knob/context.tsx +120 -0
  18. package/src/components/Knob/index.css +25 -27
  19. package/src/components/Knob/index.tsx +110 -164
  20. package/src/components/NumberInput/DecrementStepper.tsx +0 -2
  21. package/src/components/NumberInput/IncrementStepper.tsx +0 -2
  22. package/src/components/NumberInput/InternalInput.tsx +3 -1
  23. package/src/components/NumberInput/Stepper.tsx +0 -2
  24. package/src/components/NumberInput/context.tsx +0 -1
  25. package/src/components/NumberInput/index.css +31 -15
  26. package/src/components/NumberInput/index.tsx +21 -14
  27. package/src/components/Piano/KeyLabel.tsx +0 -2
  28. package/src/components/Piano/context.tsx +0 -2
  29. package/src/components/Piano/index.css +7 -14
  30. package/src/components/Piano/index.tsx +14 -15
  31. package/src/components/Piano/key.tsx +0 -10
  32. package/src/components/Piano/keyboardShortcuts.ts +0 -2
  33. package/src/components/PointsEditor/Background.tsx +17 -0
  34. package/src/components/PointsEditor/Container.tsx +26 -0
  35. package/src/components/PointsEditor/Point.tsx +137 -0
  36. package/src/components/PointsEditor/context.tsx +91 -0
  37. package/src/components/PointsEditor/index.css +30 -0
  38. package/src/components/PointsEditor/index.tsx +129 -0
  39. package/src/components/Slider/Scale.tsx +0 -2
  40. package/src/components/Slider/ScaleOption.tsx +0 -2
  41. package/src/components/Slider/Thumb.tsx +1 -5
  42. package/src/components/Slider/Track.tsx +1 -3
  43. package/src/components/Slider/context.tsx +0 -1
  44. package/src/components/Slider/index.css +33 -8
  45. package/src/components/Slider/index.tsx +61 -33
  46. package/src/components/WheelObserver/index.tsx +17 -19
  47. package/src/components/XYPad/Area.tsx +4 -5
  48. package/src/components/XYPad/Thumb.tsx +8 -7
  49. package/src/components/XYPad/index.css +18 -4
  50. package/src/components/XYPad/index.tsx +71 -38
  51. package/src/components/_util/composeRefs.tsx +63 -0
  52. package/src/components/_util/index.ts +23 -6
  53. package/src/components/_util/type.ts +1 -0
  54. package/src/hooks/useAnimationFrame.ts +0 -3
  55. package/src/hooks/useCallbackRef.ts +2 -2
  56. package/src/hooks/useDrag.ts +12 -11
  57. package/src/hooks/useDragWithElement.ts +12 -17
  58. package/src/hooks/useEventListener.ts +6 -9
  59. package/src/hooks/useInterval.ts +0 -3
  60. package/src/hooks/useLongPress.ts +0 -3
  61. package/src/hooks/useMIDIAccess.ts +0 -1
  62. package/src/hooks/useMIDIInput.ts +0 -1
  63. package/src/hooks/useMIDIMessage.ts +0 -1
  64. package/src/hooks/usePianoDrag.ts +75 -0
  65. package/src/index.ts +21 -30
  66. package/src/styles/global.css +16 -4
  67. package/dist/index.css.map +0 -1
  68. package/src/components/AnimationKnob/index.tsx +0 -314
package/dist/index.cjs CHANGED
@@ -1,4 +1,5 @@
1
- //#region rolldown:runtime
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -19,16 +20,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
19
20
  value: mod,
20
21
  enumerable: true
21
22
  }) : target, mod));
22
-
23
23
  //#endregion
24
24
  let clsx = require("clsx");
25
25
  clsx = __toESM(clsx);
26
26
  let react = require("react");
27
27
  react = __toESM(react);
28
28
  let react_jsx_runtime = require("react/jsx-runtime");
29
- let __tremolo_ui_functions = require("@tremolo-ui/functions");
29
+ let _tremolo_ui_functions = require("@tremolo-ui/functions");
30
30
  let zustand = require("zustand");
31
-
32
31
  //#region src/components/AnimationCanvas/canvas.ts
33
32
  const drawingState = [
34
33
  "strokeStyle",
@@ -58,10 +57,9 @@ function setDprConfig(canvas, context, width, height, dpr) {
58
57
  canvas.style.width = `${width}px`;
59
58
  canvas.style.height = `${height}px`;
60
59
  }
61
-
62
60
  //#endregion
63
61
  //#region src/components/AnimationCanvas/index.tsx
64
- function AnimationCanvas({ draw, init, animate = true, options, width: _width = 100, height: _height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(),...props }) {
62
+ function AnimationCanvas({ draw, init, animate = true, options, width: _width = 100, height: _height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(), ...props }) {
65
63
  const canvasRef = (0, react.useRef)(null);
66
64
  const memoCanvasRef = (0, react.useRef)(null);
67
65
  const reqIdRef = (0, react.useRef)(-1);
@@ -165,7 +163,6 @@ function AnimationCanvas({ draw, init, animate = true, options, width: _width =
165
163
  ref: memoCanvasRef
166
164
  })] });
167
165
  }
168
-
169
166
  //#endregion
170
167
  //#region src/hooks/useCallbackRef.ts
171
168
  /**
@@ -181,14 +178,13 @@ function useCallbackRef(callback, deps = []) {
181
178
  });
182
179
  return (0, react.useCallback)((...args) => callbackRef.current?.(...args), deps);
183
180
  }
184
-
185
181
  //#endregion
186
182
  //#region src/hooks/useEventListener.ts
187
183
  function useEventListener(target, event, handler, options) {
188
184
  const listener = useCallbackRef(handler);
189
185
  (0, react.useEffect)(() => {
190
186
  const node = typeof target === "function" ? target() : target ?? document;
191
- if (!handler || !node) return;
187
+ if (!node) return;
192
188
  node.addEventListener(event, listener, options);
193
189
  return () => {
194
190
  node.removeEventListener(event, listener, options);
@@ -197,14 +193,12 @@ function useEventListener(target, event, handler, options) {
197
193
  event,
198
194
  target,
199
195
  options,
200
- listener,
201
- handler
196
+ listener
202
197
  ]);
203
198
  return () => {
204
199
  (typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
205
200
  };
206
201
  }
207
-
208
202
  //#endregion
209
203
  //#region src/hooks/useRefCallbackEvent.ts
210
204
  function useRefCallbackEvent(event, handler, options, deps = []) {
@@ -222,18 +216,17 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
222
216
  };
223
217
  }, deps);
224
218
  }
225
-
226
219
  //#endregion
227
220
  //#region src/hooks/useDrag.ts
228
221
  /**
229
- * @category hooks
230
222
  * @returns [refCallback, pointerDownHandler]
231
223
  */
232
- function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
224
+ function useDrag({ threshold: _threshold = 1, onDrag, onDragStart, onDragEnd }) {
233
225
  const dragOffsetX = (0, react.useRef)(void 0);
234
226
  const dragOffsetY = (0, react.useRef)(void 0);
235
227
  const dragStartX = (0, react.useRef)(0);
236
228
  const dragStartY = (0, react.useRef)(0);
229
+ const threshold = Math.max(_threshold, 1);
237
230
  const handleDrag = (0, react.useCallback)((event, first = false) => {
238
231
  const isTouch = event instanceof TouchEvent;
239
232
  if (isTouch && event.cancelable) event.preventDefault();
@@ -254,15 +247,15 @@ function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
254
247
  dragOffsetY.current = screenY;
255
248
  }
256
249
  if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return;
257
- onDrag(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
250
+ onDrag?.(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
258
251
  }, [threshold, onDrag]);
259
- const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
260
252
  const pointerDownHandler = (0, react.useCallback)((event) => {
261
253
  dragOffsetX.current = event.screenX;
262
254
  dragOffsetY.current = event.screenY;
263
255
  handleDrag(event, true);
264
256
  onDragStart?.();
265
257
  }, [handleDrag, onDragStart]);
258
+ const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
266
259
  useEventListener(globalThis.window, "mousemove", handleDrag);
267
260
  useEventListener(globalThis.window, "pointerup", () => {
268
261
  dragOffsetX.current = void 0;
@@ -271,168 +264,10 @@ function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
271
264
  });
272
265
  return [refHandler, pointerDownHandler];
273
266
  }
274
-
275
- //#endregion
276
- //#region src/components/_util/index.ts
277
- function addNoSelect() {
278
- document.body.classList.add("tremolo-no-select");
279
- }
280
- function removeNoSelect() {
281
- document.body.classList.remove("tremolo-no-select");
282
- }
283
-
284
- //#endregion
285
- //#region src/components/AnimationKnob/index.tsx
286
- /**
287
- * Animatable rotary knob.
288
- * @category AnimationKnob
289
- */
290
- const AnimationKnob = (0, react.forwardRef)(({ 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,...props }, ref) => {
291
- const valueRef = (0, react.useRef)(0);
292
- const elmRef = (0, react.useRef)(null);
293
- const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
294
- const onDrag = (0, react.useCallback)((_x, y) => {
295
- if (!onChange) return;
296
- onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
297
- }, [
298
- max,
299
- min,
300
- onChange,
301
- skew,
302
- step
303
- ]);
304
- const handleKeyDown = (0, react.useCallback)((event) => {
305
- if (!keyboard || !onChange) return;
306
- const key = event.key;
307
- if ([
308
- "ArrowRight",
309
- "ArrowLeft",
310
- "ArrowUp",
311
- "ArrowDown"
312
- ].includes(key)) {
313
- event.preventDefault();
314
- const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
315
- let v;
316
- if (keyboard[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
317
- else v = value + x;
318
- onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
319
- }
320
- }, [
321
- value,
322
- min,
323
- max,
324
- step,
325
- skew,
326
- keyboard,
327
- onChange
328
- ]);
329
- const [touchMoveRefCallback, pointerDownHandler] = useDrag({
330
- onDrag,
331
- onDragStart: () => {
332
- valueRef.current = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
333
- if (bodyNoSelect) addNoSelect();
334
- },
335
- onDragEnd: () => {
336
- if (bodyNoSelect) removeNoSelect();
337
- }
338
- });
339
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
340
- if (!wheel) return;
341
- event.preventDefault();
342
- const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
343
- let v;
344
- if (wheel[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
345
- else v = value + x;
346
- if (onChange) onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
347
- }, { passive: false }, [
348
- wheel,
349
- value,
350
- min,
351
- max,
352
- skew,
353
- step,
354
- onChange
355
- ]);
356
- (0, react.useImperativeHandle)(ref, () => {
357
- return {
358
- focus() {
359
- elmRef.current?.focus();
360
- },
361
- blur() {
362
- elmRef.current?.blur();
363
- }
364
- };
365
- }, []);
366
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
367
- className: (0, clsx.default)("tremolo-animation-knob", className),
368
- ref: (div) => {
369
- elmRef.current = div;
370
- wheelRefCallback(div);
371
- touchMoveRefCallback(div);
372
- },
373
- role: "slider",
374
- tabIndex: 0,
375
- "aria-valuenow": value,
376
- "aria-valuemax": max,
377
- "aria-valuemin": min,
378
- style: {
379
- display: "inline-block",
380
- width: size ?? width,
381
- height: size ?? height,
382
- cursor: "pointer",
383
- WebkitTapHighlightColor: "transparent",
384
- ...style
385
- },
386
- onPointerDown: pointerDownHandler,
387
- onDoubleClick: () => {
388
- if (enableDoubleClickDefault && onChange) onChange(defaultValue);
389
- },
390
- onKeyDown: handleKeyDown,
391
- ...props,
392
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimationCanvas, {
393
- ...isRelativeSize ? { relativeSize: true } : {
394
- width: size ?? width,
395
- height: size ?? height
396
- },
397
- draw: draw ? draw : (ctx, { width: width$1, height: height$1 }) => {
398
- const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
399
- const startP = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
400
- const cx = width$1 / 2;
401
- const cy = height$1 / 2;
402
- const r = Math.min(cx, cy);
403
- if (r <= 0) return;
404
- ctx.clearRect(0, 0, width$1, height$1);
405
- ctx.lineCap = "butt";
406
- ctx.beginPath();
407
- ctx.fillStyle = bg;
408
- ctx.arc(cx, cy, r - .5, 0, 2 * Math.PI);
409
- ctx.fill();
410
- ctx.lineWidth = lineWeight;
411
- ctx.beginPath();
412
- ctx.strokeStyle = inactiveColor;
413
- ctx.arc(cx, cy, r - .5 - lineWeight / 2, (0, __tremolo_ui_functions.radian)(135), (0, __tremolo_ui_functions.radian)(45));
414
- ctx.stroke();
415
- ctx.beginPath();
416
- ctx.strokeStyle = activeColor;
417
- ctx.arc(cx, cy, r - .5 - lineWeight / 2, (0, __tremolo_ui_functions.radian)(135 + 270 * startP), (0, __tremolo_ui_functions.radian)(135 + 270 * p), p < startP);
418
- ctx.stroke();
419
- ctx.lineCap = "round";
420
- ctx.beginPath();
421
- ctx.moveTo(cx, cy);
422
- ctx.lineTo(cx + Math.cos((0, __tremolo_ui_functions.radian)(135 + 270 * p)) * (r - .5 - thumbWeight / 2), cy + Math.sin((0, __tremolo_ui_functions.radian)(135 + 270 * p)) * (r - .5 - thumbWeight / 2));
423
- ctx.strokeStyle = thumb;
424
- ctx.lineWidth = thumbWeight;
425
- ctx.stroke();
426
- }
427
- })
428
- });
429
- });
430
-
431
267
  //#endregion
432
268
  //#region src/components/DragObserver/index.tsx
433
- /** @category DragObserver */
434
269
  function DragObserver(props) {
435
- const { as: Component = "div", threshold = 1, children, onDrag, onDragStart, onDragEnd,...attributes } = props;
270
+ const { as: Component = "div", threshold = 1, onDrag, onDragStart, onDragEnd, children, onPointerDown, ...attributes } = props;
436
271
  const [refCallback, pointerDownHandler] = useDrag({
437
272
  threshold,
438
273
  onDrag,
@@ -441,190 +276,333 @@ function DragObserver(props) {
441
276
  });
442
277
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
443
278
  ref: refCallback,
444
- onPointerDown: pointerDownHandler,
279
+ onPointerDown: (e) => {
280
+ pointerDownHandler(e);
281
+ onPointerDown?.(e);
282
+ },
445
283
  ...attributes,
446
284
  children
447
285
  });
448
286
  }
449
-
450
287
  //#endregion
451
- //#region src/components/Knob/index.tsx
452
- /**
453
- * Interactive rotary knob component implemented in SVG.
454
- *
455
- * @category Knob
456
- */
457
- const Knob = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defaultValue = min, startValue = min, size = 50, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, activeLine, inactiveLine, thumb, thumbLine, thumbSize = 84, thumbLineWeight = 6, thumbLineLength = 35, lineWeight = 6, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, classes,...props }, forwardedRef) => {
458
- const valueRef = (0, react.useRef)(0);
459
- const elmRef = (0, react.useRef)(null);
460
- const onDrag = (0, react.useCallback)((_x, y) => {
461
- if (!onChange || readonly) return;
462
- onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
463
- }, [
464
- max,
465
- min,
466
- onChange,
467
- readonly,
468
- skew,
469
- step
470
- ]);
471
- const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
472
- let newValue;
473
- if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
474
- else newValue = value + x;
475
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
476
- }, [
477
- max,
478
- min,
479
- skew,
480
- step,
481
- value
482
- ]);
483
- const handleKeyDown = (0, react.useCallback)((event) => {
484
- if (!keyboard || !onChange || readonly) return;
485
- const key = event.key;
486
- if ([
487
- "ArrowRight",
488
- "ArrowLeft",
489
- "ArrowUp",
490
- "ArrowDown"
491
- ].includes(key)) {
492
- event.preventDefault();
493
- const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
494
- onChange(updateValueByEvent(keyboard[0], x));
495
- }
496
- }, [
497
- keyboard,
498
- onChange,
499
- readonly,
500
- updateValueByEvent
501
- ]);
502
- const [touchMoveRefCallback, pointerDownHandler] = useDrag({
503
- onDrag,
504
- onDragStart: () => {
505
- valueRef.current = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
506
- if (bodyNoSelect) addNoSelect();
507
- },
508
- onDragEnd: () => {
509
- if (bodyNoSelect) removeNoSelect();
510
- }
288
+ //#region src/components/_util/index.ts
289
+ function addUserSelectNone() {
290
+ window.document.body.classList.add("tremolo-user-select-none");
291
+ }
292
+ function removeUserSelectNone() {
293
+ window.document.body.classList.remove("tremolo-user-select-none");
294
+ }
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);
511
309
  });
512
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
513
- if (!wheel || readonly) return;
514
- event.preventDefault();
515
- if (!onChange || event.deltaY == 0) return;
516
- const x = Math.sign(event.deltaY) * -wheel[1];
517
- onChange(updateValueByEvent(wheel[0], x));
518
- }, { passive: false }, [
519
- wheel,
520
- onChange,
521
- readonly,
522
- updateValueByEvent
523
- ]);
524
- (0, react.useImperativeHandle)(forwardedRef, () => {
525
- return {
526
- focus() {
527
- elmRef.current?.focus();
528
- },
529
- blur() {
530
- elmRef.current?.blur();
531
- }
532
- };
533
- }, []);
534
- const viewBoxSize = 100;
535
- const center = viewBoxSize / 2;
536
- const padding = (viewBoxSize - (0, __tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2;
537
- const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
538
- const s = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
310
+ }
311
+ function calcDrawingState({ value, min, max, skew, startValue, angleRange }) {
312
+ const p = (0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew);
313
+ const s = (0, _tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
539
314
  const r1 = -angleRange / 2;
540
315
  const r2 = r1 + Math.min(p, s) * angleRange;
541
316
  const r3 = r1 + Math.max(p, s) * angleRange;
542
317
  const r4 = angleRange / 2;
543
- const x1 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r1 - 90));
544
- const y1 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r1 - 90));
545
- const x2 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r2 - 90));
546
- const y2 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r2 - 90));
547
- const x3 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r3 - 90));
548
- const y3 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r3 - 90));
549
- const x4 = center + center * Math.cos((0, __tremolo_ui_functions.radian)(r4 - 90));
550
- const y4 = center + center * Math.sin((0, __tremolo_ui_functions.radian)(r4 - 90));
318
+ return {
319
+ p,
320
+ r1,
321
+ r2,
322
+ r3,
323
+ r4,
324
+ x1: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r1 - 90)),
325
+ y1: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r1 - 90)),
326
+ x2: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r2 - 90)),
327
+ y2: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r2 - 90)),
328
+ x3: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r3 - 90)),
329
+ y3: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r3 - 90)),
330
+ x4: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r4 - 90)),
331
+ y4: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r4 - 90))
332
+ };
333
+ }
334
+ const createKnobStore = (initProps) => {
335
+ return (0, zustand.createStore)()(() => ({
336
+ ...initProps,
337
+ ...calcDrawingState(initProps)
338
+ }));
339
+ };
340
+ const KnobContext = (0, react.createContext)(null);
341
+ function KnobProvider({ children, ...props }) {
342
+ const storeRef = (0, react.useRef)(null);
343
+ if (!storeRef.current) storeRef.current = createKnobStore(props);
344
+ (0, react.useEffect)(() => {
345
+ if (storeRef.current) storeRef.current.setState({
346
+ ...props,
347
+ ...calcDrawingState(props)
348
+ });
349
+ else storeRef.current = createKnobStore(props);
350
+ }, [props]);
351
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnobContext.Provider, {
352
+ value: storeRef.current,
353
+ children
354
+ });
355
+ }
356
+ function useKnobContext(selector) {
357
+ const store = (0, react.useContext)(KnobContext);
358
+ if (!store) throw new Error("Missing KnobContext.Provider in the tree");
359
+ return (0, zustand.useStore)(store, selector);
360
+ }
361
+ //#endregion
362
+ //#region src/components/Knob/ActiveLine.tsx
363
+ function ActiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...props }) {
364
+ const r2 = useKnobContext((s) => s.r2);
365
+ const r3 = useKnobContext((s) => s.r3);
366
+ const x2 = useKnobContext((s) => s.x2);
367
+ const y2 = useKnobContext((s) => s.y2);
368
+ const x3 = useKnobContext((s) => s.x3);
369
+ const y3 = useKnobContext((s) => s.y3);
370
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
371
+ className: (0, clsx.default)("tremolo-knob-active-line", className),
372
+ d: `M ${x2} ${y2} A 50 50 -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3} ${y3}`,
373
+ fill: "none",
374
+ stroke,
375
+ strokeWidth,
376
+ ...props
377
+ });
378
+ }
379
+ //#endregion
380
+ //#region src/components/Knob/InactiveLine.tsx
381
+ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...props }) {
382
+ const min = useKnobContext((s) => s.min);
383
+ const max = useKnobContext((s) => s.max);
384
+ const startValue = useKnobContext((s) => s.startValue);
385
+ const r1 = useKnobContext((s) => s.r1);
386
+ const r2 = useKnobContext((s) => s.r2);
387
+ const r3 = useKnobContext((s) => s.r3);
388
+ const r4 = useKnobContext((s) => s.r4);
389
+ const x1 = useKnobContext((s) => s.x1);
390
+ const y1 = useKnobContext((s) => s.y1);
391
+ const x2 = useKnobContext((s) => s.x2);
392
+ const y2 = useKnobContext((s) => s.y2);
393
+ const x3 = useKnobContext((s) => s.x3);
394
+ const y3 = useKnobContext((s) => s.y3);
395
+ const x4 = useKnobContext((s) => s.x4);
396
+ const y4 = useKnobContext((s) => s.y4);
397
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [startValue > min && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
398
+ className: (0, clsx.default)("tremolo-knob-inactive-line", className),
399
+ d: `M ${x1} ${y1} A 50 50 -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2} ${y2}`,
400
+ fill: "none",
401
+ stroke,
402
+ strokeWidth,
403
+ ...props
404
+ }), startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
405
+ className: (0, clsx.default)("tremolo-knob-inactive-line", className),
406
+ d: `M ${x3} ${y3} A 50 50 -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4} ${y4}`,
407
+ fill: "none",
408
+ stroke,
409
+ strokeWidth,
410
+ ...props
411
+ })] });
412
+ }
413
+ //#endregion
414
+ //#region src/components/Knob/Thumb.tsx
415
+ function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor", thumbSize = 84, thumbLineWeight = 6, thumbLineLength = 35, classes, ...props }) {
416
+ const angleRange = useKnobContext((s) => s.angleRange);
417
+ const p = useKnobContext((s) => s.p);
418
+ const r1 = useKnobContext((s) => s.r1);
551
419
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
552
- ref: (div) => {
553
- elmRef.current = div;
554
- wheelRefCallback(div);
555
- touchMoveRefCallback(div);
556
- },
557
- className: (0, clsx.default)("tremolo-knob", className),
558
- viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
559
- width: size,
560
- height: size,
561
- tabIndex: 0,
562
- role: "slider",
563
- "aria-valuenow": value,
564
- "aria-valuemin": min,
565
- "aria-valuemax": max,
566
- "aria-disabled": disabled,
567
- "aria-readonly": readonly,
568
- onPointerDown: (event) => {
569
- pointerDownHandler(event);
570
- onPointerDown?.(event);
571
- },
572
- onDoubleClick: (event) => {
573
- if (enableDoubleClickDefault && onChange) onChange(defaultValue);
574
- onDoubleClick?.(event);
575
- },
576
- onKeyDown: (event) => {
577
- handleKeyDown(event);
578
- onKeyDown?.(event);
420
+ className: (0, clsx.default)("tremolo-knob-thumb", classes?.thumb),
421
+ ...props,
422
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
423
+ cx: "50%",
424
+ cy: "50%",
425
+ r: `${thumbSize / 2}%`,
426
+ fill: thumb
427
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
428
+ className: (0, clsx.default)("tremolo-knob-thumb-line", classes?.thumbLine),
429
+ x1: "50%",
430
+ y1: `${(100 - (0, _tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2}%`,
431
+ x2: "50%",
432
+ y2: `${thumbLineLength}%`,
433
+ stroke: thumbLine,
434
+ strokeWidth: `${thumbLineWeight}%`,
435
+ style: {
436
+ transform: `rotate(${r1 + p * angleRange}deg)`,
437
+ transformOrigin: "50% 50%"
438
+ }
439
+ })]
440
+ });
441
+ }
442
+ //#endregion
443
+ //#region src/components/Knob/SVGRoot.tsx
444
+ function SVGRoot({ children, style, ...props }) {
445
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
446
+ viewBox: `0 0 100 100`,
447
+ style: {
448
+ display: "block",
449
+ overflow: "visible",
450
+ ...style
579
451
  },
580
452
  ...props,
581
- children: [
582
- startValue > min && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
583
- className: (0, clsx.default)("tremolo-knob-inactive-line", classes?.inactiveLine),
584
- d: `M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`,
585
- fill: "none",
586
- stroke: inactiveLine || "currentColor",
587
- strokeWidth: lineWeight
588
- }),
589
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
590
- className: (0, clsx.default)("tremolo-knob-active-line", classes?.activeLine),
591
- d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
592
- fill: "none",
593
- stroke: activeLine || "currentColor",
594
- strokeWidth: lineWeight
595
- }),
596
- startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
597
- className: (0, clsx.default)("tremolo-knob-inactive-line", classes?.inactiveLine),
598
- d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`,
599
- fill: "none",
600
- stroke: inactiveLine || "currentColor",
601
- strokeWidth: lineWeight
602
- }),
603
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
604
- className: (0, clsx.default)("tremolo-knob-thumb", classes?.thumb),
605
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
606
- cx: "50%",
607
- cy: "50%",
608
- r: `${thumbSize / 2}%`,
609
- fill: thumb || "currentColor"
610
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
611
- className: (0, clsx.default)("tremolo-knob-thumb-line", classes?.thumbLine),
612
- x1: "50%",
613
- y1: `${padding}%`,
614
- x2: "50%",
615
- y2: `${thumbLineLength}%`,
616
- stroke: thumbLine || "currentColor",
617
- strokeWidth: `${thumbLineWeight}%`,
618
- style: {
619
- transform: `rotate(${r1 + p * angleRange}deg)`,
620
- transformOrigin: "50% 50%"
621
- }
622
- })]
623
- })
624
- ]
453
+ children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
454
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InactiveLine, {}),
455
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActiveLine, {}),
456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb$2, {})
457
+ ] })
625
458
  });
626
- });
627
-
459
+ }
460
+ //#endregion
461
+ //#region src/components/Knob/index.tsx
462
+ const defaultExternalStyles$3 = {
463
+ userSelectNone: true,
464
+ cursor: "grabbing"
465
+ };
466
+ /**
467
+ * Interactive rotary knob component implemented in SVG.
468
+ */
469
+ const Knob = {
470
+ Root: (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, defaultValue = min, startValue = min, size, externalStyles: _externalStyles, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, style, children, ...props }, forwardedRef) => {
471
+ const [dragging, setDragging] = (0, react.useState)(false);
472
+ const valueRef = (0, react.useRef)(0);
473
+ const elmRef = (0, react.useRef)(null);
474
+ const externalStyles = {
475
+ ...defaultExternalStyles$3,
476
+ ..._externalStyles
477
+ };
478
+ const onDrag = (0, react.useCallback)((_x, y) => {
479
+ if (!onChange || readonly) return;
480
+ onChange((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(valueRef.current - y / 100, min, max, skew), step), min, max));
481
+ }, [
482
+ max,
483
+ min,
484
+ onChange,
485
+ readonly,
486
+ skew,
487
+ step
488
+ ]);
489
+ const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
490
+ let newValue;
491
+ if (eventType == "normalized") newValue = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
492
+ else newValue = value + x;
493
+ return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(newValue, step), min, max);
494
+ }, [
495
+ max,
496
+ min,
497
+ skew,
498
+ step,
499
+ value
500
+ ]);
501
+ const handleKeyDown = (0, react.useCallback)((event) => {
502
+ if (!keyboard || !onChange || readonly) return;
503
+ const key = event.key;
504
+ if ([
505
+ "ArrowRight",
506
+ "ArrowLeft",
507
+ "ArrowUp",
508
+ "ArrowDown"
509
+ ].includes(key)) {
510
+ event.preventDefault();
511
+ const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
512
+ onChange(updateValueByEvent(keyboard[0], x));
513
+ }
514
+ }, [
515
+ keyboard,
516
+ onChange,
517
+ readonly,
518
+ updateValueByEvent
519
+ ]);
520
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag({
521
+ onDrag,
522
+ onDragStart: () => {
523
+ setDragging(true);
524
+ valueRef.current = (0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew);
525
+ if (externalStyles.userSelectNone) addUserSelectNone();
526
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
527
+ },
528
+ onDragEnd: () => {
529
+ setDragging(false);
530
+ if (externalStyles.userSelectNone) removeUserSelectNone();
531
+ if (externalStyles.cursor) resetCursorStyle();
532
+ }
533
+ });
534
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
535
+ if (!wheel || readonly) return;
536
+ event.preventDefault();
537
+ if (!onChange || event.deltaY == 0) return;
538
+ const x = Math.sign(event.deltaY) * -wheel[1];
539
+ onChange(updateValueByEvent(wheel[0], x));
540
+ }, { passive: false }, [
541
+ wheel,
542
+ onChange,
543
+ readonly,
544
+ updateValueByEvent
545
+ ]);
546
+ (0, react.useImperativeHandle)(forwardedRef, () => {
547
+ return {
548
+ focus() {
549
+ elmRef.current?.focus();
550
+ },
551
+ blur() {
552
+ elmRef.current?.blur();
553
+ }
554
+ };
555
+ }, []);
556
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnobProvider, {
557
+ value,
558
+ min,
559
+ max,
560
+ step,
561
+ skew,
562
+ startValue,
563
+ angleRange,
564
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
565
+ ref: (elm) => {
566
+ elmRef.current = elm;
567
+ wheelRefCallback(elm);
568
+ touchMoveRefCallback(elm);
569
+ },
570
+ className: (0, clsx.default)("tremolo-knob", className),
571
+ tabIndex: 0,
572
+ role: "slider",
573
+ "aria-valuenow": value,
574
+ "aria-valuemin": min,
575
+ "aria-valuemax": max,
576
+ "aria-disabled": disabled,
577
+ "aria-readonly": readonly,
578
+ "data-dragging": dragging,
579
+ style: {
580
+ width: size,
581
+ height: size,
582
+ ...style
583
+ },
584
+ onPointerDown: (event) => {
585
+ pointerDownHandler(event);
586
+ onPointerDown?.(event);
587
+ },
588
+ onDoubleClick: (event) => {
589
+ if (enableDoubleClickDefault && onChange) onChange(defaultValue);
590
+ onDoubleClick?.(event);
591
+ },
592
+ onKeyDown: (event) => {
593
+ handleKeyDown(event);
594
+ onKeyDown?.(event);
595
+ },
596
+ ...props,
597
+ children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SVGRoot, {})
598
+ })
599
+ });
600
+ }),
601
+ SVGRoot,
602
+ InactiveLine,
603
+ ActiveLine,
604
+ Thumb: Thumb$2
605
+ };
628
606
  //#endregion
629
607
  //#region src/components/NumberInput/type.ts
630
608
  function selectUnit(units, value) {
@@ -635,8 +613,8 @@ function selectUnit(units, value) {
635
613
  function parseValue(inputString, units, digit) {
636
614
  const str = inputString.trim();
637
615
  if (!units || typeof units == "string") {
638
- const m$1 = str.match(/-?\d+(\.\d+)?/);
639
- let v = Number(m$1?.[0] ?? "0");
616
+ const m = str.match(/-?\d+(\.\d+)?/);
617
+ let v = Number(m?.[0] ?? "0");
640
618
  v = isNaN(v) ? 0 : v;
641
619
  return {
642
620
  rawValue: v,
@@ -646,30 +624,29 @@ function parseValue(inputString, units, digit) {
646
624
  }
647
625
  const unitList = units.map(([u, _s]) => u);
648
626
  const scaleList = units.map(([_u, s]) => s);
649
- let rawValue$5 = 0;
627
+ let rawValue = 0;
650
628
  let unit = unitList[0];
651
- let formatValue = (digit != void 0 ? rawValue$5.toFixed(digit) : rawValue$5) + unit;
629
+ let formatValue = (digit != void 0 ? rawValue.toFixed(digit) : rawValue) + unit;
652
630
  const m = str.match(/^(-?\d+(\.\d+)?)\s*(\w*)$/);
653
631
  if (m) {
654
- rawValue$5 = Number(m[1]) || 0;
632
+ rawValue = Number(m[1]) || 0;
655
633
  const uIndex = unitList.indexOf(m[3]);
656
- rawValue$5 *= uIndex != -1 ? scaleList[uIndex] : 1;
657
- const [u, scale] = selectUnit(units, rawValue$5);
658
- const v = rawValue$5 / scale;
634
+ rawValue *= uIndex != -1 ? scaleList[uIndex] : 1;
635
+ const [u, scale] = selectUnit(units, rawValue);
636
+ const v = rawValue / scale;
659
637
  formatValue = (digit != void 0 ? v.toFixed(digit) : v) + u;
660
638
  unit = u;
661
639
  }
662
640
  return {
663
- rawValue: rawValue$5,
641
+ rawValue,
664
642
  formatValue,
665
643
  unit
666
644
  };
667
645
  }
668
-
669
646
  //#endregion
670
647
  //#region src/components/NumberInput/context.tsx
671
648
  function safeClamp(value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
672
- return (0, __tremolo_ui_functions.clamp)(value, min, max);
649
+ return (0, _tremolo_ui_functions.clamp)(value, min, max);
673
650
  }
674
651
  const createNumberInputStore = (initProps) => {
675
652
  const DEFAULT_PROPS = {
@@ -720,7 +697,7 @@ const createNumberInputStore = (initProps) => {
720
697
  }));
721
698
  };
722
699
  const NumberInputContext = (0, react.createContext)(null);
723
- function NumberInputProvider({ children,...props }) {
700
+ function NumberInputProvider({ children, ...props }) {
724
701
  const storeRef = (0, react.useRef)(null);
725
702
  if (!storeRef.current) storeRef.current = createNumberInputStore(props);
726
703
  (0, react.useEffect)(() => {
@@ -738,51 +715,142 @@ function NumberInputProvider({ children,...props }) {
738
715
  children
739
716
  });
740
717
  }
741
- /** @category NumberInput */
742
718
  function useNumberInputContext(selector) {
743
719
  const store = (0, react.useContext)(NumberInputContext);
744
720
  if (!store) throw new Error("Missing NumberInputContext.Provider in the tree");
745
721
  return (0, zustand.useStore)(store, selector);
746
722
  }
747
-
748
723
  //#endregion
749
- //#region src/components/NumberInput/InternalInput.tsx
750
- const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children,...props }, ref) => {
751
- const elmRef = (0, react.useRef)(null);
752
- const [editing, setEditing] = (0, react.useState)(false);
753
- const value = useNumberInputContext((s) => s.value);
754
- const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
755
- const min = useNumberInputContext((s) => s.min);
756
- const max = useNumberInputContext((s) => s.max);
757
- const step = useNumberInputContext((s) => s.step);
758
- const units = useNumberInputContext((s) => s.units);
759
- const digit = useNumberInputContext((s) => s.digit);
760
- const change = useNumberInputContext((s) => s.change);
761
- const error = (0, react.useMemo)(() => {
762
- if (editing) return false;
763
- const v = valueAsNumber;
764
- return (min ?? -Infinity) > v || v > (max ?? Infinity);
765
- }, [
766
- editing,
767
- max,
768
- min,
769
- valueAsNumber
770
- ]);
771
- const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
772
- let newValue;
773
- if (eventType == "normalized") {
774
- if (!min || !max) throw new Error("required parameter: min, max");
775
- newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(valueAsNumber, min, max) + x, min, max);
776
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
777
- } else {
778
- newValue = valueAsNumber + x;
779
- return (0, __tremolo_ui_functions.stepValue)(newValue, step);
780
- }
781
- }, [
782
- max,
783
- min,
784
- step,
785
- valueAsNumber
724
+ //#region src/hooks/useInterval.ts
725
+ function useInterval(callback, delay) {
726
+ const savedCallback = (0, react.useRef)(callback);
727
+ (0, react.useEffect)(() => {
728
+ savedCallback.current = callback;
729
+ }, [callback]);
730
+ (0, react.useEffect)(() => {
731
+ if (delay === null) return;
732
+ const id = setInterval(() => {
733
+ savedCallback.current();
734
+ }, delay);
735
+ return () => {
736
+ clearInterval(id);
737
+ };
738
+ }, [delay]);
739
+ }
740
+ //#endregion
741
+ //#region src/hooks/useLongPress.ts
742
+ function useLongPress(callback, initialDelay = 500, interval = 40) {
743
+ const [pressed, setPressed] = (0, react.useState)(false);
744
+ const [delay, setDelay] = (0, react.useState)(initialDelay);
745
+ useInterval(() => {
746
+ callback();
747
+ setDelay(interval);
748
+ }, pressed ? delay : null);
749
+ useEventListener(globalThis.window, "pointerup", () => {
750
+ setPressed(false);
751
+ setDelay(initialDelay);
752
+ });
753
+ return (0, react.useCallback)(() => {
754
+ callback();
755
+ setPressed(true);
756
+ }, [callback]);
757
+ }
758
+ //#endregion
759
+ //#region src/components/NumberInput/DecrementStepper.tsx
760
+ function DecrementStepper({ size = 12, children, className, ...props }) {
761
+ const min = useNumberInputContext((s) => s.min);
762
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
763
+ const readonly = useNumberInputContext((s) => s.readonly);
764
+ const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
765
+ const press = useLongPress(useNumberInputContext((s) => s.decrement));
766
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
767
+ className: (0, clsx.default)("tremolo-number-input-decrement-stepper", className),
768
+ role: "button",
769
+ tabIndex: -1,
770
+ "aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
771
+ "aria-readonly": readonly,
772
+ onPointerDown: press,
773
+ ...props,
774
+ children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
775
+ width: size,
776
+ height: size,
777
+ viewBox: "0 0 24 24",
778
+ fill: "none",
779
+ stroke: "currentColor",
780
+ strokeWidth: "2",
781
+ strokeLinecap: "round",
782
+ strokeLinejoin: "round",
783
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "6 9 12 15 18 9" })
784
+ })
785
+ });
786
+ }
787
+ //#endregion
788
+ //#region src/components/NumberInput/IncrementStepper.tsx
789
+ function IncrementStepper({ size = 12, children, className, ...props }) {
790
+ const max = useNumberInputContext((s) => s.max);
791
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
792
+ const readonly = useNumberInputContext((s) => s.readonly);
793
+ const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
794
+ const press = useLongPress(useNumberInputContext((s) => s.increment));
795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
796
+ className: (0, clsx.default)("tremolo-number-input-increment-stepper", className),
797
+ role: "button",
798
+ tabIndex: -1,
799
+ "aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
800
+ "aria-readonly": readonly,
801
+ onPointerDown: press,
802
+ ...props,
803
+ children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
804
+ width: size,
805
+ height: size,
806
+ viewBox: "0 0 24 24",
807
+ fill: "none",
808
+ stroke: "currentColor",
809
+ strokeWidth: "2",
810
+ strokeLinecap: "round",
811
+ strokeLinejoin: "round",
812
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
813
+ })
814
+ });
815
+ }
816
+ //#endregion
817
+ //#region src/components/NumberInput/InternalInput.tsx
818
+ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children, ...props }, ref) => {
819
+ const elmRef = (0, react.useRef)(null);
820
+ const [editing, setEditing] = (0, react.useState)(false);
821
+ const value = useNumberInputContext((s) => s.value);
822
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
823
+ const min = useNumberInputContext((s) => s.min);
824
+ const max = useNumberInputContext((s) => s.max);
825
+ const step = useNumberInputContext((s) => s.step);
826
+ const units = useNumberInputContext((s) => s.units);
827
+ const digit = useNumberInputContext((s) => s.digit);
828
+ const change = useNumberInputContext((s) => s.change);
829
+ const error = (0, react.useMemo)(() => {
830
+ if (editing) return false;
831
+ const v = valueAsNumber;
832
+ return (min ?? -Infinity) > v || v > (max ?? Infinity);
833
+ }, [
834
+ editing,
835
+ max,
836
+ min,
837
+ valueAsNumber
838
+ ]);
839
+ const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
840
+ let newValue;
841
+ if (eventType == "normalized") {
842
+ if (!min || !max) throw new Error("[NumberInput] \"min\" and \"max\" are required when InputEventOption[0] is set to \"normalized\".");
843
+ newValue = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(valueAsNumber, min, max) + x, min, max);
844
+ return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(newValue, step), min, max);
845
+ } else {
846
+ newValue = valueAsNumber + x;
847
+ return (0, _tremolo_ui_functions.stepValue)(newValue, step);
848
+ }
849
+ }, [
850
+ max,
851
+ min,
852
+ step,
853
+ valueAsNumber
786
854
  ]);
787
855
  const handleKeyDown = (0, react.useCallback)((event) => {
788
856
  if (!keyboard || !onChange || readonly) return;
@@ -841,8 +909,8 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
841
909
  const v = event.currentTarget.value;
842
910
  const cursorPos = event.target.selectionStart;
843
911
  change(v);
844
- const valueAsNumber$1 = parseValue(v, units, digit).rawValue;
845
- onChange?.(valueAsNumber$1, v);
912
+ const valueAsNumber = parseValue(v, units, digit).rawValue;
913
+ onChange?.(valueAsNumber, v);
846
914
  if (cursorPos) {
847
915
  event.target.selectionStart = cursorPos;
848
916
  event.target.selectionEnd = cursorPos;
@@ -873,11 +941,9 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
873
941
  ...props
874
942
  });
875
943
  });
876
-
877
944
  //#endregion
878
945
  //#region src/components/NumberInput/Stepper.tsx
879
- /** @category NumberInput */
880
- function Stepper({ dynamic = true, children, className,...props }) {
946
+ function Stepper({ dynamic = true, children, className, ...props }) {
881
947
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
882
948
  className: (0, clsx.default)("tremolo-number-input-stepper", className),
883
949
  "data-dynamic": dynamic,
@@ -885,162 +951,58 @@ function Stepper({ dynamic = true, children, className,...props }) {
885
951
  children
886
952
  });
887
953
  }
888
-
889
- //#endregion
890
- //#region src/hooks/useInterval.ts
891
- /**
892
- * @category hooks
893
- */
894
- function useInterval(callback, delay) {
895
- const savedCallback = (0, react.useRef)(callback);
896
- (0, react.useEffect)(() => {
897
- savedCallback.current = callback;
898
- }, [callback]);
899
- (0, react.useEffect)(() => {
900
- if (delay === null) return;
901
- const id = setInterval(() => {
902
- savedCallback.current();
903
- }, delay);
904
- return () => {
905
- clearInterval(id);
906
- };
907
- }, [delay]);
908
- }
909
-
910
- //#endregion
911
- //#region src/hooks/useLongPress.ts
912
- /**
913
- * @category hooks
914
- */
915
- function useLongPress(callback, initialDelay = 500, interval = 40) {
916
- const [pressed, setPressed] = (0, react.useState)(false);
917
- const [delay, setDelay] = (0, react.useState)(initialDelay);
918
- useInterval(() => {
919
- callback();
920
- setDelay(interval);
921
- }, pressed ? delay : null);
922
- useEventListener(globalThis.window, "pointerup", () => {
923
- setPressed(false);
924
- setDelay(initialDelay);
925
- });
926
- return (0, react.useCallback)(() => {
927
- callback();
928
- setPressed(true);
929
- }, [callback]);
930
- }
931
-
932
- //#endregion
933
- //#region src/components/NumberInput/IncrementStepper.tsx
934
- /** @category NumberInput */
935
- function IncrementStepper({ size = 12, children, className,...props }) {
936
- const max = useNumberInputContext((s) => s.max);
937
- const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
938
- const readonly = useNumberInputContext((s) => s.readonly);
939
- const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
940
- const press = useLongPress(useNumberInputContext((s) => s.increment));
941
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
942
- className: (0, clsx.default)("tremolo-number-input-increment-stepper", className),
943
- role: "button",
944
- tabIndex: -1,
945
- "aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
946
- "aria-readonly": readonly,
947
- onPointerDown: press,
948
- ...props,
949
- children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
950
- width: size,
951
- height: size,
952
- viewBox: "0 0 24 24",
953
- fill: "none",
954
- stroke: "currentColor",
955
- strokeWidth: "2",
956
- strokeLinecap: "round",
957
- strokeLinejoin: "round",
958
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
959
- })
960
- });
961
- }
962
-
963
- //#endregion
964
- //#region src/components/NumberInput/DecrementStepper.tsx
965
- /** @category NumberInput */
966
- function DecrementStepper({ size = 12, children, className,...props }) {
967
- const min = useNumberInputContext((s) => s.min);
968
- const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
969
- const readonly = useNumberInputContext((s) => s.readonly);
970
- const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
971
- const press = useLongPress(useNumberInputContext((s) => s.decrement));
972
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
973
- className: (0, clsx.default)("tremolo-number-input-decrement-stepper", className),
974
- role: "button",
975
- tabIndex: -1,
976
- "aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
977
- "aria-readonly": readonly,
978
- onPointerDown: press,
979
- ...props,
980
- children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
981
- width: size,
982
- height: size,
983
- viewBox: "0 0 24 24",
984
- fill: "none",
985
- stroke: "currentColor",
986
- strokeWidth: "2",
987
- strokeLinecap: "round",
988
- strokeLinejoin: "round",
989
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "6 9 12 15 18 9" })
990
- })
991
- });
992
- }
993
-
994
- //#endregion
995
- //#region src/components/NumberInput/index.tsx
996
954
  /**
997
955
  * Input with some useful functions for entering numerical values.
998
- * @category NumberInput
999
956
  */
1000
- const NumberInput = (0, react.forwardRef)(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props }, forwardedRef) => {
1001
- const colors = { "--active-color": activeColor };
1002
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
1003
- value: String(value),
1004
- min,
1005
- max,
1006
- step,
1007
- units,
1008
- digit,
1009
- readonly,
1010
- keepWithinRange,
1011
- onChange,
1012
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1013
- className: (0, clsx.default)("tremolo-number-input-wrapper", wrapperClassName),
1014
- tabIndex: 0,
1015
- style: { ...colors },
1016
- "data-stepper": !!children,
1017
- "data-variant": variant,
1018
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
1019
- ref: forwardedRef,
1020
- readonly,
1021
- disabled,
1022
- selectWithFocus,
1023
- blurOnEnter,
1024
- clampValueOnBlur,
1025
- wheel,
1026
- keyboard,
1027
- className,
1028
- onChange,
1029
- onFocus,
1030
- onBlur,
1031
- ...props
1032
- }), children]
1033
- })
1034
- });
1035
- });
1036
-
957
+ const NumberInput = {
958
+ Root: (0, react.forwardRef)(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children, ...props }, forwardedRef) => {
959
+ const colors = { "--active-color": activeColor };
960
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
961
+ value: String(value),
962
+ min,
963
+ max,
964
+ step,
965
+ units,
966
+ digit,
967
+ readonly,
968
+ keepWithinRange,
969
+ onChange,
970
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
971
+ className: (0, clsx.default)("tremolo-number-input-wrapper", wrapperClassName),
972
+ tabIndex: 0,
973
+ style: { ...colors },
974
+ "data-stepper": !!children,
975
+ "data-variant": variant,
976
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
977
+ ref: forwardedRef,
978
+ readonly,
979
+ disabled,
980
+ selectWithFocus,
981
+ blurOnEnter,
982
+ clampValueOnBlur,
983
+ wheel,
984
+ keyboard,
985
+ className,
986
+ onChange,
987
+ onFocus,
988
+ onBlur,
989
+ ...props
990
+ }), children]
991
+ })
992
+ });
993
+ }),
994
+ Stepper,
995
+ IncrementStepper,
996
+ DecrementStepper
997
+ };
1037
998
  //#endregion
1038
- //#region src/hooks/useDragWithElement.ts
999
+ //#region src/hooks/usePianoDrag.ts
1039
1000
  /**
1040
- * @category hooks
1001
+ * Internal
1041
1002
  * @returns [refCallback, pointerDownHandler]
1003
+ * @private
1042
1004
  */
1043
- function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1005
+ function usePianoDrag({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1044
1006
  const dragged = (0, react.useRef)(false);
1045
1007
  const normalizedX = (0, react.useRef)(0);
1046
1008
  const normalizedY = (0, react.useRef)(0);
@@ -1051,8 +1013,8 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd })
1051
1013
  const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
1052
1014
  const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1053
1015
  const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1054
- const nx = (0, __tremolo_ui_functions.normalizeValue)(mouseX, left, right);
1055
- const ny = (0, __tremolo_ui_functions.normalizeValue)(mouseY, top, bottom);
1016
+ const nx = (0, _tremolo_ui_functions.normalizeValue)(mouseX, left, right);
1017
+ const ny = (0, _tremolo_ui_functions.normalizeValue)(mouseY, top, bottom);
1056
1018
  normalizedX.current = nx;
1057
1019
  normalizedY.current = ny;
1058
1020
  onDrag(nx, ny);
@@ -1069,9 +1031,11 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd })
1069
1031
  dragged.current = false;
1070
1032
  onDragEnd?.(normalizedX.current, normalizedY.current);
1071
1033
  });
1072
- return [refHandler, pointerDownHandler];
1034
+ return {
1035
+ refHandler,
1036
+ pointerDownHandler
1037
+ };
1073
1038
  }
1074
-
1075
1039
  //#endregion
1076
1040
  //#region src/components/Piano/context.tsx
1077
1041
  const createPianoStore = (initProps) => {
@@ -1094,7 +1058,7 @@ const createPianoStore = (initProps) => {
1094
1058
  }));
1095
1059
  };
1096
1060
  const PianoContext = (0, react.createContext)(null);
1097
- function PianoProvider({ children,...props }) {
1061
+ function PianoProvider({ children, ...props }) {
1098
1062
  const storeRef = (0, react.useRef)(null);
1099
1063
  if (!storeRef.current) storeRef.current = createPianoStore(props);
1100
1064
  (0, react.useEffect)(() => {
@@ -1106,17 +1070,14 @@ function PianoProvider({ children,...props }) {
1106
1070
  children
1107
1071
  });
1108
1072
  }
1109
- /** @category Piano */
1110
1073
  function usePianoContext(selector) {
1111
1074
  const store = (0, react.useContext)(PianoContext);
1112
1075
  if (!store) throw new Error("Missing PianoContext.Provider in the tree");
1113
1076
  return (0, zustand.useStore)(store, selector);
1114
1077
  }
1115
-
1116
1078
  //#endregion
1117
1079
  //#region src/components/Piano/KeyLabel.tsx
1118
- /** @category Piano */
1119
- function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
1080
+ function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label, ...props }) {
1120
1081
  const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
1121
1082
  const content = label ? label(__note, index) : __label && __label(__note, index);
1122
1083
  return (content != void 0 || content != null) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1129,14 +1090,7 @@ function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __
1129
1090
  })
1130
1091
  });
1131
1092
  }
1132
-
1133
- //#endregion
1134
- //#region src/components/Piano/key.tsx
1135
- /** @category Piano */
1136
- const defaultWhiteKeyWidth = 40;
1137
- /** @category Piano */
1138
- const defaultBlackKeyWidth = 26;
1139
- const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg, color, activeBg, activeColor, className, style, children, __width,...props }, ref) => {
1093
+ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg, color, activeBg, activeColor, className, style, children, __width, ...props }, ref) => {
1140
1094
  const [played, setPlayed] = (0, react.useState)(false);
1141
1095
  const midiMax = usePianoContext((s) => s.midiMax);
1142
1096
  const fill = usePianoContext((s) => s.fill);
@@ -1198,8 +1152,7 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
1198
1152
  })
1199
1153
  });
1200
1154
  });
1201
- /** @category Piano */
1202
- const WhiteKey = (0, react.forwardRef)(({ width = defaultWhiteKeyWidth, height = "100%", onContextMenu = (e) => e.preventDefault(),...props }, ref) => {
1155
+ const WhiteKey = (0, react.forwardRef)(({ width = 40, height = "100%", onContextMenu = (e) => e.preventDefault(), ...props }, ref) => {
1203
1156
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyImpl, {
1204
1157
  ref,
1205
1158
  keyType: "white",
@@ -1209,8 +1162,7 @@ const WhiteKey = (0, react.forwardRef)(({ width = defaultWhiteKeyWidth, height =
1209
1162
  ...props
1210
1163
  });
1211
1164
  });
1212
- /** @category Piano */
1213
- const BlackKey = (0, react.forwardRef)(({ width = defaultBlackKeyWidth, height = "60%", onContextMenu = (e) => e.preventDefault(),...props }, ref) => {
1165
+ const BlackKey = (0, react.forwardRef)(({ width = 26, height = "60%", onContextMenu = (e) => e.preventDefault(), ...props }, ref) => {
1214
1166
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyImpl, {
1215
1167
  ref,
1216
1168
  keyType: "black",
@@ -1220,10 +1172,8 @@ const BlackKey = (0, react.forwardRef)(({ width = defaultBlackKeyWidth, height =
1220
1172
  ...props
1221
1173
  });
1222
1174
  });
1223
-
1224
1175
  //#endregion
1225
1176
  //#region src/components/Piano/keyboardShortcuts.ts
1226
- /** @category Piano */
1227
1177
  const SHORTCUTS = { HOME_ROW: { keys: [
1228
1178
  "a",
1229
1179
  "w",
@@ -1243,203 +1193,406 @@ const SHORTCUTS = { HOME_ROW: { keys: [
1243
1193
  "p",
1244
1194
  ";"
1245
1195
  ] } };
1246
-
1247
1196
  //#endregion
1248
1197
  //#region src/components/Piano/index.tsx
1249
1198
  /**
1250
1199
  * [noteRange.first, noteRange.first + 1 ..., noteRange.last]
1251
- * @category Piano
1252
1200
  */
1253
1201
  function getNoteRangeArray(noteRange) {
1254
1202
  return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1255
1203
  }
1256
- const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
1204
+ const blackPerWhiteWidth = 26 / 40;
1257
1205
  /**
1258
1206
  * Customizable piano component.
1259
- * @category Piano
1260
1207
  */
1261
- const Piano = (0, react.forwardRef)(({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }, forwardedRef) => {
1262
- const [whiteNoteWidth, setWhiteNoteWidth] = (0, react.useState)(_whiteNoteWidth);
1263
- const keyRefs = (0, react.useRef)([]);
1264
- for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = (0, react.createRef)();
1265
- const pianoRef = (0, react.useRef)(null);
1266
- const hitKeyIndex = (0, react.useRef)(-1);
1267
- const noteRangeArray = getNoteRangeArray(noteRange);
1268
- const whiteNotes = noteRangeArray.filter((v) => (0, __tremolo_ui_functions.isWhiteKey)(v));
1269
- const blackNotes = noteRangeArray.filter((v) => (0, __tremolo_ui_functions.isBlackKey)(v));
1270
- const whiteNoteCount = whiteNotes.length;
1271
- const padding = 1;
1272
- const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
1273
- const childrenWithProps = (0, react.useMemo)(() => {
1274
- return children && react.default.Children.map(children, (child, index) => {
1275
- if (react.default.isValidElement(child)) {
1276
- const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * .65;
1277
- const props$1 = {
1278
- ref: keyRefs.current[index],
1279
- __width
1208
+ const Piano = {
1209
+ Root: (0, react.forwardRef)(({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = 40, style, className, onPlayNote, onStopNote, label, children, onPointerDown, ...props }, forwardedRef) => {
1210
+ const [whiteNoteWidth, setWhiteNoteWidth] = (0, react.useState)(_whiteNoteWidth);
1211
+ const keyRefs = (0, react.useRef)([]);
1212
+ for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = (0, react.createRef)();
1213
+ const pianoRef = (0, react.useRef)(null);
1214
+ const hitKeyIndex = (0, react.useRef)(-1);
1215
+ const noteRangeArray = getNoteRangeArray(noteRange);
1216
+ const whiteNotes = noteRangeArray.filter((v) => (0, _tremolo_ui_functions.isWhiteKey)(v));
1217
+ const blackNotes = noteRangeArray.filter((v) => (0, _tremolo_ui_functions.isBlackKey)(v));
1218
+ const whiteNoteCount = whiteNotes.length;
1219
+ const padding = 1;
1220
+ const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
1221
+ const childrenWithProps = (0, react.useMemo)(() => {
1222
+ return children && react.default.Children.map(children, (child, index) => {
1223
+ if (react.default.isValidElement(child)) {
1224
+ const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * .65;
1225
+ const props = {
1226
+ ref: keyRefs.current[index],
1227
+ __width
1228
+ };
1229
+ return react.default.cloneElement(child, props);
1230
+ }
1231
+ return child;
1232
+ });
1233
+ }, [children, whiteNoteWidth]);
1234
+ const notePosition = (0, react.useCallback)((note) => {
1235
+ const pitchPositions = {
1236
+ C: 0,
1237
+ "C#": 1,
1238
+ D: 1,
1239
+ "D#": 2,
1240
+ E: 2,
1241
+ F: 3,
1242
+ "F#": 4,
1243
+ G: 4,
1244
+ "G#": 5,
1245
+ A: 5,
1246
+ "A#": 6,
1247
+ B: 6
1248
+ };
1249
+ const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
1250
+ const padding = 1;
1251
+ const targetNoteKey = (0, _tremolo_ui_functions.noteKey)(note);
1252
+ const firstNoteKey = (0, _tremolo_ui_functions.noteKey)(noteRange.first);
1253
+ const octave = Math.floor((note - noteRange.first) / 12);
1254
+ const octaveOffset = _tremolo_ui_functions.noteKeys.indexOf(firstNoteKey) > _tremolo_ui_functions.noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1255
+ const w = whiteNoteWidth + padding;
1256
+ const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1257
+ const blackKeyOffset = (0, _tremolo_ui_functions.isBlackKey)(note) ? blackNoteWidth / 2 : 0;
1258
+ return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1259
+ }, [noteRange.first, whiteNoteWidth]);
1260
+ const getHitKeyIndex = (0, react.useCallback)((x, y) => {
1261
+ if (!pianoRef.current) return -1;
1262
+ const containerHeight = pianoRef.current.clientHeight;
1263
+ const notes = [...blackNotes, ...whiteNotes];
1264
+ for (let i = 0; i < notes.length; i++) {
1265
+ const note = notes[i];
1266
+ const pos = notePosition(note);
1267
+ const w = (0, _tremolo_ui_functions.isWhiteKey)(note) ? whiteNoteWidth : whiteNoteWidth * blackPerWhiteWidth;
1268
+ const h = (0, _tremolo_ui_functions.isWhiteKey)(note) ? containerHeight : containerHeight * .6;
1269
+ if (pos <= x && x < pos + w && 0 <= y && y < h) return note;
1270
+ }
1271
+ return -1;
1272
+ }, [
1273
+ blackNotes,
1274
+ notePosition,
1275
+ whiteNoteWidth,
1276
+ whiteNotes
1277
+ ]);
1278
+ const onDrag = (0, react.useCallback)((perX, perY) => {
1279
+ if (!pianoRef.current) return;
1280
+ const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
1281
+ const index = noteRangeArray.indexOf(note);
1282
+ if (index == -1) return;
1283
+ if (hitKeyIndex.current != index) {
1284
+ keyRefs.current[hitKeyIndex.current]?.current?.stop();
1285
+ keyRefs.current[index]?.current?.play();
1286
+ hitKeyIndex.current = index;
1287
+ }
1288
+ }, [
1289
+ getHitKeyIndex,
1290
+ noteRangeArray,
1291
+ staticWidth
1292
+ ]);
1293
+ const onDragEnd = (0, react.useCallback)(() => {
1294
+ if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
1295
+ hitKeyIndex.current = -1;
1296
+ }, [keyRefs]);
1297
+ (0, react.useEffect)(() => {
1298
+ if (fill && pianoRef.current) {
1299
+ const parent = pianoRef.current.parentElement;
1300
+ if (!parent) throw new Error("doesn't have a parent element.");
1301
+ const resizeObserver = new ResizeObserver(() => {
1302
+ const w = pianoRef.current.clientWidth;
1303
+ setWhiteNoteWidth(w / whiteNoteCount - padding);
1304
+ });
1305
+ resizeObserver.observe(parent);
1306
+ return () => {
1307
+ resizeObserver.unobserve(parent);
1280
1308
  };
1281
- return react.default.cloneElement(child, props$1);
1309
+ } else setWhiteNoteWidth(_whiteNoteWidth);
1310
+ }, [
1311
+ fill,
1312
+ _whiteNoteWidth,
1313
+ whiteNoteCount
1314
+ ]);
1315
+ const { refHandler: touchMoveRefCallback, pointerDownHandler } = usePianoDrag({
1316
+ baseElementRef: pianoRef,
1317
+ onDrag,
1318
+ onDragEnd
1319
+ });
1320
+ useEventListener(globalThis.window, "keydown", (e) => {
1321
+ if (e.repeat) return;
1322
+ if (!keyboardShortcuts) return;
1323
+ const index = keyboardShortcuts.keys.indexOf(e.key);
1324
+ if (index != -1) {
1325
+ if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play();
1282
1326
  }
1283
- return child;
1284
1327
  });
1285
- }, [children, whiteNoteWidth]);
1286
- const notePosition = (0, react.useCallback)((note) => {
1287
- const pitchPositions = {
1288
- C: 0,
1289
- "C#": 1,
1290
- D: 1,
1291
- "D#": 2,
1292
- E: 2,
1293
- F: 3,
1294
- "F#": 4,
1295
- G: 4,
1296
- "G#": 5,
1297
- A: 5,
1298
- "A#": 6,
1299
- B: 6
1300
- };
1301
- const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
1302
- const padding$1 = 1;
1303
- const targetNoteKey = (0, __tremolo_ui_functions.noteKey)(note);
1304
- const firstNoteKey = (0, __tremolo_ui_functions.noteKey)(noteRange.first);
1305
- const octave = Math.floor((note - noteRange.first) / 12);
1306
- const octaveOffset = __tremolo_ui_functions.noteKeys.indexOf(firstNoteKey) > __tremolo_ui_functions.noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1307
- const w = whiteNoteWidth + padding$1;
1308
- const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1309
- const blackKeyOffset = (0, __tremolo_ui_functions.isBlackKey)(note) ? blackNoteWidth / 2 : 0;
1310
- return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1311
- }, [noteRange.first, whiteNoteWidth]);
1312
- const getHitKeyIndex = (0, react.useCallback)((x, y) => {
1313
- if (!pianoRef.current) return -1;
1314
- const containerHeight = pianoRef.current.clientHeight;
1315
- const notes = [...blackNotes, ...whiteNotes];
1316
- for (let i = 0; i < notes.length; i++) {
1317
- const note = notes[i];
1318
- const pos = notePosition(note);
1319
- const w = (0, __tremolo_ui_functions.isWhiteKey)(note) ? whiteNoteWidth : whiteNoteWidth * blackPerWhiteWidth;
1320
- const h = (0, __tremolo_ui_functions.isWhiteKey)(note) ? containerHeight : containerHeight * .6;
1321
- if (pos <= x && x < pos + w && 0 <= y && y < h) return note;
1328
+ useEventListener(globalThis.window, "keyup", (e) => {
1329
+ if (e.repeat) return;
1330
+ if (!keyboardShortcuts) return;
1331
+ const index = keyboardShortcuts.keys.indexOf(e.key);
1332
+ if (index != -1) keyRefs.current[index]?.current?.stop();
1333
+ });
1334
+ (0, react.useImperativeHandle)(forwardedRef, () => {
1335
+ return {
1336
+ playNote(note, velocity) {
1337
+ const index = noteRangeArray.indexOf(note);
1338
+ if (index != -1) {
1339
+ if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
1340
+ } else onPlayNote?.(note, velocity);
1341
+ },
1342
+ stopNote(note) {
1343
+ const index = noteRangeArray.indexOf(note);
1344
+ if (index != -1) keyRefs.current[index]?.current?.stop();
1345
+ else onStopNote?.(note);
1346
+ }
1347
+ };
1348
+ }, [
1349
+ noteRangeArray,
1350
+ onPlayNote,
1351
+ onStopNote
1352
+ ]);
1353
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
1354
+ notePosition,
1355
+ noteRange,
1356
+ glissando,
1357
+ midiMax,
1358
+ fill,
1359
+ onPlayNote,
1360
+ onStopNote,
1361
+ label,
1362
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1363
+ ref: (div) => {
1364
+ pianoRef.current = div;
1365
+ touchMoveRefCallback(div);
1366
+ },
1367
+ className: (0, clsx.default)("tremolo-piano", className),
1368
+ style: {
1369
+ width: fill ? "100%" : staticWidth,
1370
+ height,
1371
+ ...style
1372
+ },
1373
+ onPointerDown: (event) => {
1374
+ pointerDownHandler(event);
1375
+ onPointerDown?.(event);
1376
+ },
1377
+ ...props,
1378
+ children: childrenWithProps || noteRangeArray.map((note, index) => (0, _tremolo_ui_functions.isWhiteKey)(note) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhiteKey, {
1379
+ ref: keyRefs.current[index],
1380
+ noteNumber: note,
1381
+ __width: whiteNoteWidth
1382
+ }, note) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlackKey, {
1383
+ ref: keyRefs.current[index],
1384
+ noteNumber: note,
1385
+ __width: whiteNoteWidth * blackPerWhiteWidth
1386
+ }, note))
1387
+ })
1388
+ });
1389
+ }),
1390
+ WhiteKey,
1391
+ BlackKey,
1392
+ KeyLabel
1393
+ };
1394
+ //#endregion
1395
+ //#region src/components/PointsEditor/Background.tsx
1396
+ function Background({ className, children, ...props }) {
1397
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1398
+ className: (0, clsx.default)("tremolo-points-editor-background", className),
1399
+ ...props,
1400
+ children
1401
+ });
1402
+ }
1403
+ //#endregion
1404
+ //#region src/components/PointsEditor/context.tsx
1405
+ const createPointsEditorStore = (initProps) => {
1406
+ const DEFAULT_PROPS = {
1407
+ containerElementRef: (0, react.createRef)(),
1408
+ disabled: false,
1409
+ readonly: false,
1410
+ externalStyles: {
1411
+ userSelectNone: true,
1412
+ cursor: "grabbing"
1322
1413
  }
1323
- return -1;
1324
- }, [
1325
- blackNotes,
1326
- notePosition,
1327
- whiteNoteWidth,
1328
- whiteNotes
1329
- ]);
1330
- const onDrag = (0, react.useCallback)((perX, perY) => {
1331
- if (!pianoRef.current) return;
1332
- const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
1333
- const index = noteRangeArray.indexOf(note);
1334
- if (index == -1) return;
1335
- if (hitKeyIndex.current != index) {
1336
- keyRefs.current[hitKeyIndex.current]?.current?.stop();
1337
- keyRefs.current[index]?.current?.play();
1338
- hitKeyIndex.current = index;
1414
+ };
1415
+ return (0, zustand.createStore)()((set) => ({
1416
+ ...DEFAULT_PROPS,
1417
+ ...initProps,
1418
+ setContainerElementRef: (containerElementRef) => {
1419
+ set(() => ({ containerElementRef }));
1339
1420
  }
1340
- }, [
1341
- getHitKeyIndex,
1342
- noteRangeArray,
1343
- staticWidth
1344
- ]);
1345
- const onDragEnd = (0, react.useCallback)(() => {
1346
- if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
1347
- hitKeyIndex.current = -1;
1348
- }, [keyRefs]);
1421
+ }));
1422
+ };
1423
+ const PointsEditorContext = (0, react.createContext)(null);
1424
+ function PointsEditorProvider({ children, ...props }) {
1425
+ const storeRef = (0, react.useRef)(null);
1426
+ if (!storeRef.current) storeRef.current = createPointsEditorStore(props);
1349
1427
  (0, react.useEffect)(() => {
1350
- if (fill && pianoRef.current) {
1351
- const parent = pianoRef.current.parentElement;
1352
- if (!parent) throw new Error("doesn't have a parent element.");
1353
- const resizeObserver = new ResizeObserver(() => {
1354
- const w = pianoRef.current.clientWidth;
1355
- setWhiteNoteWidth(w / whiteNoteCount - padding);
1356
- });
1357
- resizeObserver.observe(parent);
1358
- return () => {
1359
- resizeObserver.unobserve(parent);
1360
- };
1361
- } else setWhiteNoteWidth(_whiteNoteWidth);
1428
+ if (storeRef.current) storeRef.current.setState(props);
1429
+ else storeRef.current = createPointsEditorStore(props);
1430
+ }, [props]);
1431
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointsEditorContext.Provider, {
1432
+ value: storeRef.current,
1433
+ children
1434
+ });
1435
+ }
1436
+ function usePointsEditorContext(selector) {
1437
+ const store = (0, react.useContext)(PointsEditorContext);
1438
+ if (!store) throw new Error("Missing PointsEditorContext.Provider in the tree");
1439
+ return (0, zustand.useStore)(store, selector);
1440
+ }
1441
+ //#endregion
1442
+ //#region src/components/PointsEditor/Container.tsx
1443
+ function Container({ className, children, ...props }) {
1444
+ const containerElementRef = (0, react.useRef)(null);
1445
+ usePointsEditorContext((s) => s.setContainerElementRef)(containerElementRef);
1446
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1447
+ ref: containerElementRef,
1448
+ className: (0, clsx.default)("tremolo-points-editor-container", className),
1449
+ ...props,
1450
+ children
1451
+ });
1452
+ }
1453
+ //#endregion
1454
+ //#region src/hooks/useDragWithElement.ts
1455
+ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1456
+ const [dragging, setDragging] = (0, react.useState)(false);
1457
+ const normalizedX = (0, react.useRef)(0);
1458
+ 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);
1362
1471
  }, [
1363
- fill,
1364
- _whiteNoteWidth,
1365
- whiteNoteCount
1366
- ]);
1367
- const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1368
- baseElementRef: pianoRef,
1369
1472
  onDrag,
1370
- onDragEnd
1473
+ baseElementRef,
1474
+ dragging
1475
+ ]);
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);
1371
1487
  });
1372
- useEventListener(globalThis.window, "keydown", (e) => {
1373
- if (e.repeat) return;
1374
- if (!keyboardShortcuts) return;
1375
- const index = keyboardShortcuts.keys.indexOf(e.key);
1376
- if (index != -1) {
1377
- if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play();
1488
+ return {
1489
+ refHandler,
1490
+ pointerDownHandler,
1491
+ dragging
1492
+ };
1493
+ }
1494
+ //#endregion
1495
+ //#region src/components/PointsEditor/Point.tsx
1496
+ function clampPoint(point, min, max) {
1497
+ const { x, y } = point;
1498
+ return {
1499
+ x: (0, _tremolo_ui_functions.clamp)(x, min?.x ?? 0, max?.x ?? 1),
1500
+ y: (0, _tremolo_ui_functions.clamp)(y, min?.y ?? 0, max?.y ?? 1)
1501
+ };
1502
+ }
1503
+ function Point({ value, min, max, size, width = 16, height = 16, color, disabled, readonly, onChange, onDragStart, onDragEnd, className, style, onPointerDown, ...props }) {
1504
+ const containerElementRef = usePointsEditorContext((s) => s.containerElementRef);
1505
+ const externalStyles = usePointsEditorContext((s) => s.externalStyles);
1506
+ const __disabled = usePointsEditorContext((s) => s.disabled);
1507
+ const __readonly = usePointsEditorContext((s) => s.readonly);
1508
+ const { refHandler: touchMoveRefCallback, pointerDownHandler, dragging } = useDragWithElement({
1509
+ baseElementRef: containerElementRef,
1510
+ onDrag: (x, y) => {
1511
+ if (readonly) return;
1512
+ onChange?.(clampPoint({
1513
+ x,
1514
+ y
1515
+ }, min, max));
1516
+ },
1517
+ onDragStart: (x, y) => {
1518
+ if (readonly) return;
1519
+ if (externalStyles.userSelectNone) addUserSelectNone();
1520
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
1521
+ onDragStart?.(clampPoint({
1522
+ x,
1523
+ y
1524
+ }, min, max));
1525
+ },
1526
+ onDragEnd: (x, y) => {
1527
+ if (readonly) return;
1528
+ if (externalStyles.userSelectNone) removeUserSelectNone();
1529
+ if (externalStyles.cursor) resetCursorStyle();
1530
+ onDragEnd?.(clampPoint({
1531
+ x,
1532
+ y
1533
+ }, min, max));
1378
1534
  }
1379
1535
  });
1380
- useEventListener(globalThis.window, "keyup", (e) => {
1381
- if (e.repeat) return;
1382
- if (!keyboardShortcuts) return;
1383
- const index = keyboardShortcuts.keys.indexOf(e.key);
1384
- if (index != -1) keyRefs.current[index]?.current?.stop();
1385
- });
1386
- (0, react.useImperativeHandle)(forwardedRef, () => {
1387
- return {
1388
- playNote(note, velocity) {
1389
- const index = noteRangeArray.indexOf(note);
1390
- if (index != -1) {
1391
- if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
1392
- } else onPlayNote?.(note, velocity);
1393
- },
1394
- stopNote(note) {
1395
- const index = noteRangeArray.indexOf(note);
1396
- if (index != -1) keyRefs.current[index]?.current?.stop();
1397
- else onStopNote?.(note);
1398
- }
1399
- };
1400
- }, [
1401
- noteRangeArray,
1402
- onPlayNote,
1403
- onStopNote
1404
- ]);
1405
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
1406
- notePosition,
1407
- noteRange,
1408
- glissando,
1409
- midiMax,
1410
- fill,
1411
- onPlayNote,
1412
- onStopNote,
1413
- label,
1414
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1415
- ref: (div) => {
1416
- pianoRef.current = div;
1417
- touchMoveRefCallback(div);
1418
- },
1419
- className: (0, clsx.default)("tremolo-piano", className),
1420
- style: {
1421
- width: fill ? "100%" : staticWidth,
1422
- height,
1423
- ...style
1424
- },
1425
- onPointerDown: (event) => {
1426
- pointerDownHandler(event);
1427
- onPointerDown?.(event);
1428
- },
1429
- ...props,
1430
- children: childrenWithProps || noteRangeArray.map((note, index) => (0, __tremolo_ui_functions.isWhiteKey)(note) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhiteKey, {
1431
- ref: keyRefs.current[index],
1432
- noteNumber: note,
1433
- __width: whiteNoteWidth
1434
- }, note) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlackKey, {
1435
- ref: keyRefs.current[index],
1436
- noteNumber: note,
1437
- __width: whiteNoteWidth * blackPerWhiteWidth
1438
- }, note))
1439
- })
1536
+ const colors = { "--color": color };
1537
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1538
+ ref: (div) => {
1539
+ touchMoveRefCallback(div);
1540
+ },
1541
+ className: (0, clsx.default)("tremolo-points-editor-point", className),
1542
+ tabIndex: 0,
1543
+ "aria-disabled": disabled != void 0 ? disabled : __disabled,
1544
+ "aria-readonly": readonly != void 0 ? readonly : __readonly,
1545
+ "data-dragging": dragging,
1546
+ style: {
1547
+ ...colors,
1548
+ width: size ?? width,
1549
+ height: size ?? height,
1550
+ left: `${value.x * 100}%`,
1551
+ top: `${value.y * 100}%`,
1552
+ ...style
1553
+ },
1554
+ onPointerDown: (event) => {
1555
+ pointerDownHandler(event);
1556
+ onPointerDown?.(event);
1557
+ },
1558
+ ...props
1440
1559
  });
1441
- });
1442
-
1560
+ }
1561
+ //#endregion
1562
+ //#region src/components/PointsEditor/index.tsx
1563
+ const defaultExternalStyles$2 = {
1564
+ userSelectNone: true,
1565
+ cursor: "grabbing"
1566
+ };
1567
+ /**
1568
+ * Multiple Point Controller
1569
+ */
1570
+ const PointsEditor = {
1571
+ Root: (0, react.forwardRef)(({ width = 200, height = 100, disabled = false, readonly = false, externalStyles: _externalStyles, style, className, children, ...props }, forwardedRef) => {
1572
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointsEditorProvider, {
1573
+ disabled,
1574
+ readonly,
1575
+ externalStyles: {
1576
+ ...defaultExternalStyles$2,
1577
+ ..._externalStyles
1578
+ },
1579
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1580
+ ref: forwardedRef,
1581
+ className: (0, clsx.default)("tremolo-points-editor", className),
1582
+ style: {
1583
+ width,
1584
+ height,
1585
+ ...style
1586
+ },
1587
+ ...props,
1588
+ children
1589
+ })
1590
+ });
1591
+ }),
1592
+ Background,
1593
+ Container,
1594
+ Point
1595
+ };
1443
1596
  //#endregion
1444
1597
  //#region src/components/Slider/context.tsx
1445
1598
  const createSliderStore = (initProps) => {
@@ -1459,7 +1612,7 @@ const createSliderStore = (initProps) => {
1459
1612
  }));
1460
1613
  };
1461
1614
  const SliderContext = (0, react.createContext)(null);
1462
- function SliderProvider({ children,...props }) {
1615
+ function SliderProvider({ children, ...props }) {
1463
1616
  const storeRef = (0, react.useRef)(null);
1464
1617
  if (!storeRef.current) storeRef.current = createSliderStore(props);
1465
1618
  (0, react.useEffect)(() => {
@@ -1471,24 +1624,21 @@ function SliderProvider({ children,...props }) {
1471
1624
  children
1472
1625
  });
1473
1626
  }
1474
- /** @category Slider */
1475
1627
  function useSliderContext(selector) {
1476
1628
  const store = (0, react.useContext)(SliderContext);
1477
1629
  if (!store) throw new Error("Missing SliderContext.Provider in the tree");
1478
1630
  return (0, zustand.useStore)(store, selector);
1479
1631
  }
1480
-
1481
1632
  //#endregion
1482
1633
  //#region src/components/Slider/ScaleOption.tsx
1483
- /** @category Slider */
1484
- function ScaleOption({ value, type = "mark-number", label, thickness = 1, length = "0.5rem", gap = 2, labelWidth, classes, styles, className, style,...props }) {
1634
+ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length = "0.5rem", gap = 2, labelWidth, classes, styles, className, style, ...props }) {
1485
1635
  const min = useSliderContext((s) => s.min);
1486
1636
  const max = useSliderContext((s) => s.max);
1487
1637
  const skew = useSliderContext((s) => s.skew);
1488
1638
  const vertical = useSliderContext((s) => s.vertical);
1489
1639
  const reverse = useSliderContext((s) => s.reverse);
1490
- const calcPercent = (0, react.useCallback)((rawValue$5) => {
1491
- return (0, __tremolo_ui_functions.toFixed)((0, __tremolo_ui_functions.xor)(vertical, reverse) ? 100 - (0, __tremolo_ui_functions.normalizeValue)(rawValue$5, min, max, skew) * 100 : (0, __tremolo_ui_functions.normalizeValue)(rawValue$5, min, max, skew) * 100);
1640
+ const calcPercent = (0, react.useCallback)((rawValue) => {
1641
+ return (0, _tremolo_ui_functions.toFixed)((0, _tremolo_ui_functions.xor)(vertical, reverse) ? 100 - (0, _tremolo_ui_functions.normalizeValue)(rawValue, min, max, skew) * 100 : (0, _tremolo_ui_functions.normalizeValue)(rawValue, min, max, skew) * 100);
1492
1642
  }, [
1493
1643
  vertical,
1494
1644
  reverse,
@@ -1525,7 +1675,6 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
1525
1675
  })]
1526
1676
  });
1527
1677
  }
1528
-
1529
1678
  //#endregion
1530
1679
  //#region src/components/Slider/type.ts
1531
1680
  function generateOptionsList(options, min, max, step) {
@@ -1533,7 +1682,7 @@ function generateOptionsList(options, min, max, step) {
1533
1682
  const per = options[0] == "step" ? step : options[0];
1534
1683
  const count = Math.floor(max / per) - Math.ceil(min / per) + 1;
1535
1684
  for (let i = 0; i < count; i++) {
1536
- const value = (0, __tremolo_ui_functions.toFixed)(per * (Math.ceil(min / per) + i), (0, __tremolo_ui_functions.decimalPart)(per)?.length);
1685
+ const value = (0, _tremolo_ui_functions.toFixed)(per * (Math.ceil(min / per) + i), (0, _tremolo_ui_functions.decimalPart)(per)?.length);
1537
1686
  optionsList.push({
1538
1687
  value,
1539
1688
  type: options[1]
@@ -1541,18 +1690,16 @@ function generateOptionsList(options, min, max, step) {
1541
1690
  }
1542
1691
  return optionsList;
1543
1692
  }
1544
-
1545
1693
  //#endregion
1546
1694
  //#region src/components/Slider/Scale.tsx
1547
- /** @category Slider */
1548
- function Scale({ gap = 6, options, children, className, style,...props }) {
1695
+ function Scale({ gap = 6, options, children, className, style, ...props }) {
1549
1696
  const min = useSliderContext((s) => s.min);
1550
1697
  const max = useSliderContext((s) => s.max);
1551
1698
  const step = useSliderContext((s) => s.step);
1552
1699
  const vertical = useSliderContext((s) => s.vertical);
1553
1700
  const reverse = useSliderContext((s) => s.reverse);
1554
1701
  const optionsList = options ? generateOptionsList(options, min, max, step) : [];
1555
- if ((0, __tremolo_ui_functions.xor)(vertical, reverse)) optionsList.reverse();
1702
+ if ((0, _tremolo_ui_functions.xor)(vertical, reverse)) optionsList.reverse();
1556
1703
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1557
1704
  className: (0, clsx.default)("tremolo-slider-scale", className),
1558
1705
  style: {
@@ -1569,13 +1716,7 @@ function Scale({ gap = 6, options, children, className, style,...props }) {
1569
1716
  }) : children
1570
1717
  });
1571
1718
  }
1572
-
1573
- //#endregion
1574
- //#region src/components/Slider/Thumb.tsx
1575
- /** @category Slider */
1576
- const defaultThumbSize = 22;
1577
- /** @category Slider */
1578
- const SliderThumb = (0, react.forwardRef)(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent }, ref) => {
1719
+ const Thumb$1 = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, className, style, __percent }, ref) => {
1579
1720
  const wrapperRef = (0, react.useRef)(null);
1580
1721
  const vertical = useSliderContext((s) => s.vertical);
1581
1722
  const disabled = useSliderContext((s) => s.disabled);
@@ -1611,13 +1752,7 @@ const SliderThumb = (0, react.forwardRef)(({ size, width = defaultThumbSize, hei
1611
1752
  })
1612
1753
  });
1613
1754
  });
1614
-
1615
- //#endregion
1616
- //#region src/components/Slider/Track.tsx
1617
- const defaultLength = 140;
1618
- const defaultThickness = 10;
1619
- /** @category Slider */
1620
- function SliderTrack({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0,...props }) {
1755
+ function Track({ length = 140, thickness = 10, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0, ...props }) {
1621
1756
  const vertical = useSliderContext((s) => s.vertical);
1622
1757
  const reverse = useSliderContext((s) => s.reverse);
1623
1758
  const disabled = useSliderContext((s) => s.disabled);
@@ -1632,8 +1767,8 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1632
1767
  "data-vertical": vertical,
1633
1768
  style: !defaultStyle ? style : {
1634
1769
  ...colors,
1635
- background: (0, __tremolo_ui_functions.xor)(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
1636
- borderRadius: (0, __tremolo_ui_functions.styleHelper)(thickness, "/", 2),
1770
+ background: (0, _tremolo_ui_functions.xor)(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${__percent}%, var(--active) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active) ${__percent}%, var(--inactive) ${__percent}%)`,
1771
+ borderRadius: (0, _tremolo_ui_functions.styleHelper)(thickness, "/", 2),
1637
1772
  width: !vertical ? length : thickness,
1638
1773
  height: vertical ? length : thickness,
1639
1774
  ...style
@@ -1642,207 +1777,259 @@ function SliderTrack({ length = defaultLength, thickness = defaultThickness, act
1642
1777
  children: [children, __thumb]
1643
1778
  });
1644
1779
  }
1645
-
1646
1780
  //#endregion
1647
1781
  //#region src/components/Slider/index.tsx
1782
+ const defaultExternalStyles$1 = {
1783
+ userSelectNone: true,
1784
+ cursor: "pointer"
1785
+ };
1648
1786
  /**
1649
1787
  * Customizable slider
1650
- * @category Slider
1651
1788
  */
1652
- const Slider = (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown,...props }, forwardedRef) => {
1653
- const trackElementRef = (0, react.useRef)(null);
1654
- const thumbRef = (0, react.useRef)(null);
1655
- const p = (0, __tremolo_ui_functions.toFixed)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) * 100);
1656
- const rev = (0, __tremolo_ui_functions.toFixed)(100 - p);
1657
- const displayReversed = (0, react.useMemo)(() => (0, __tremolo_ui_functions.xor)(vertical, reverse), [vertical, reverse]);
1658
- const percent = displayReversed ? rev : p;
1659
- let trackProps = {};
1660
- let thumbProps = {};
1661
- let scaleComponent = void 0;
1662
- if (children != void 0) react.default.Children.forEach(children, (child) => {
1663
- if (react.default.isValidElement(child)) if (child.type == SliderThumb) thumbProps = child.props;
1664
- else if (child.type == SliderTrack) trackProps = child.props;
1665
- else if (child.type == Scale) scaleComponent = child;
1666
- else throw new Error("only <SliderThumb> or <SliderTrack>");
1667
- else throw new Error("children is an invalid element.");
1668
- });
1669
- const onDrag = (0, react.useCallback)((nx, ny) => {
1670
- if (!onChange || readonly) return;
1671
- const n = vertical ? ny : nx;
1672
- onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
1673
- }, [
1674
- displayReversed,
1675
- max,
1676
- min,
1677
- onChange,
1678
- readonly,
1679
- skew,
1680
- step,
1681
- vertical
1682
- ]);
1683
- const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
1684
- let newValue;
1685
- if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
1686
- else newValue = value + x;
1687
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
1688
- }, [
1689
- max,
1690
- min,
1691
- skew,
1692
- step,
1693
- value
1694
- ]);
1695
- const handleKeyDown = (0, react.useCallback)((event) => {
1696
- if (!keyboard || !onChange || readonly) return;
1697
- const key = event.key;
1698
- if ([
1699
- "ArrowRight",
1700
- "ArrowLeft",
1701
- "ArrowUp",
1702
- "ArrowDown"
1703
- ].includes(key)) {
1704
- event.preventDefault();
1705
- let x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
1706
- if (reverse) x *= -1;
1707
- onChange(updateValueByEvent(keyboard[0], x));
1708
- }
1709
- }, [
1710
- keyboard,
1711
- onChange,
1712
- readonly,
1713
- reverse,
1714
- updateValueByEvent
1715
- ]);
1716
- const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1717
- baseElementRef: trackElementRef,
1718
- onDrag,
1719
- onDragStart: (nx, ny) => {
1720
- if (readonly) return;
1721
- if (bodyNoSelect) addNoSelect();
1722
- thumbRef.current?.focus();
1723
- 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));
1724
- },
1725
- onDragEnd: (nx, ny) => {
1726
- if (readonly) return;
1727
- if (bodyNoSelect) removeNoSelect();
1728
- 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));
1729
- }
1730
- });
1731
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1732
- if (!wheel || !onChange || readonly) return;
1733
- event.preventDefault();
1734
- let x;
1735
- if (!vertical && event.deltaX != 0) x = event.deltaX > 0 ? wheel[1] : -wheel[1];
1736
- else {
1737
- if (event.deltaY == 0) return;
1738
- x = event.deltaY > 0 ? -wheel[1] : wheel[1];
1739
- }
1740
- if (vertical && reverse) x *= -1;
1741
- onChange(updateValueByEvent(wheel[0], x));
1742
- }, { passive: false }, [
1743
- wheel,
1744
- vertical,
1745
- readonly,
1746
- onChange,
1747
- updateValueByEvent
1748
- ]);
1749
- (0, react.useImperativeHandle)(forwardedRef, () => {
1750
- return {
1751
- focus() {
1752
- thumbRef.current?.focus();
1753
- },
1754
- blur() {
1755
- thumbRef.current?.blur();
1756
- }
1789
+ const Slider = {
1790
+ Root: (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, vertical = false, reverse = false, externalStyles: _externalStyles, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown, ...props }, forwardedRef) => {
1791
+ const trackElementRef = (0, react.useRef)(null);
1792
+ const thumbRef = (0, react.useRef)(null);
1793
+ const externalStyles = {
1794
+ ...defaultExternalStyles$1,
1795
+ ..._externalStyles
1757
1796
  };
1758
- }, []);
1759
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderProvider, {
1760
- min,
1761
- max,
1762
- step,
1763
- skew,
1764
- vertical,
1765
- reverse,
1766
- disabled,
1767
- readonly,
1768
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1769
- className: (0, clsx.default)("tremolo-slider", className),
1770
- ref: (div) => {
1771
- wheelRefCallback(div);
1772
- touchMoveRefCallback(div);
1773
- },
1774
- tabIndex: -1,
1775
- role: "slider",
1776
- "aria-valuenow": value,
1777
- "aria-valuemin": min,
1778
- "aria-valuemax": max,
1779
- "aria-orientation": vertical ? "vertical" : "horizontal",
1780
- "aria-disabled": disabled,
1781
- "aria-readonly": readonly,
1782
- style: {
1783
- margin: `calc(${(0, __tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
1784
- ...style
1785
- },
1786
- onPointerDown: (event) => {
1787
- pointerDownHandler(event);
1788
- onPointerDown?.(event);
1789
- },
1790
- onKeyDown: (event) => {
1791
- handleKeyDown(event);
1792
- onKeyDown?.(event);
1793
- },
1794
- onFocus: (event) => {
1797
+ const p = (0, _tremolo_ui_functions.toFixed)((0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew) * 100);
1798
+ const rev = (0, _tremolo_ui_functions.toFixed)(100 - p);
1799
+ const displayReversed = (0, react.useMemo)(() => (0, _tremolo_ui_functions.xor)(vertical, reverse), [vertical, reverse]);
1800
+ const percent = displayReversed ? rev : p;
1801
+ let trackProps = {};
1802
+ let thumbProps = {};
1803
+ let scaleComponent = void 0;
1804
+ if (children != void 0) react.default.Children.forEach(children, (child) => {
1805
+ if (react.default.isValidElement(child)) if (child.type == Thumb$1) thumbProps = child.props;
1806
+ else if (child.type == Track) trackProps = child.props;
1807
+ else if (child.type == Scale) scaleComponent = child;
1808
+ else throw new Error("only <SliderThumb> or <SliderTrack>");
1809
+ else throw new Error("children is an invalid element.");
1810
+ });
1811
+ const onDrag = (0, react.useCallback)((nx, ny) => {
1812
+ if (!onChange || readonly) return;
1813
+ const n = vertical ? ny : nx;
1814
+ onChange((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
1815
+ }, [
1816
+ displayReversed,
1817
+ max,
1818
+ min,
1819
+ onChange,
1820
+ readonly,
1821
+ skew,
1822
+ step,
1823
+ vertical
1824
+ ]);
1825
+ const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
1826
+ let newValue;
1827
+ if (eventType == "normalized") newValue = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
1828
+ else newValue = value + x;
1829
+ return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(newValue, step), min, max);
1830
+ }, [
1831
+ max,
1832
+ min,
1833
+ skew,
1834
+ step,
1835
+ value
1836
+ ]);
1837
+ const handleKeyDown = (0, react.useCallback)((event) => {
1838
+ if (!keyboard || !onChange || readonly) return;
1839
+ const key = event.key;
1840
+ if ([
1841
+ "ArrowRight",
1842
+ "ArrowLeft",
1843
+ "ArrowUp",
1844
+ "ArrowDown"
1845
+ ].includes(key)) {
1846
+ event.preventDefault();
1847
+ let x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
1848
+ if (reverse) x *= -1;
1849
+ onChange(updateValueByEvent(keyboard[0], x));
1850
+ }
1851
+ }, [
1852
+ keyboard,
1853
+ onChange,
1854
+ readonly,
1855
+ reverse,
1856
+ updateValueByEvent
1857
+ ]);
1858
+ const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
1859
+ baseElementRef: trackElementRef,
1860
+ onDrag,
1861
+ onDragStart: (nx, ny) => {
1862
+ if (readonly) return;
1863
+ if (externalStyles.userSelectNone) addUserSelectNone();
1864
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
1795
1865
  thumbRef.current?.focus();
1796
- onFocus?.(event);
1797
- },
1798
- onBlur: (event) => {
1799
- thumbRef.current?.blur();
1800
- onBlur?.(event);
1866
+ 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));
1801
1867
  },
1802
- ...props,
1803
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1868
+ onDragEnd: (nx, ny) => {
1869
+ if (readonly) return;
1870
+ if (externalStyles.userSelectNone) removeUserSelectNone();
1871
+ if (externalStyles.cursor) resetCursorStyle();
1872
+ 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
+ }
1874
+ });
1875
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1876
+ if (!wheel || !onChange || readonly) return;
1877
+ event.preventDefault();
1878
+ let x;
1879
+ if (!vertical && event.deltaX != 0) x = event.deltaX > 0 ? wheel[1] : -wheel[1];
1880
+ else {
1881
+ if (event.deltaY == 0) return;
1882
+ x = event.deltaY > 0 ? -wheel[1] : wheel[1];
1883
+ }
1884
+ if (vertical && reverse) x *= -1;
1885
+ onChange(updateValueByEvent(wheel[0], x));
1886
+ }, { passive: false }, [
1887
+ wheel,
1888
+ vertical,
1889
+ readonly,
1890
+ onChange,
1891
+ updateValueByEvent
1892
+ ]);
1893
+ (0, react.useImperativeHandle)(forwardedRef, () => {
1894
+ return {
1895
+ focus() {
1896
+ thumbRef.current?.focus();
1897
+ },
1898
+ blur() {
1899
+ thumbRef.current?.blur();
1900
+ }
1901
+ };
1902
+ }, []);
1903
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderProvider, {
1904
+ min,
1905
+ max,
1906
+ step,
1907
+ skew,
1908
+ vertical,
1909
+ reverse,
1910
+ disabled,
1911
+ readonly,
1912
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1913
+ className: (0, clsx.default)("tremolo-slider", className),
1914
+ ref: (div) => {
1915
+ wheelRefCallback(div);
1916
+ touchMoveRefCallback(div);
1917
+ },
1918
+ tabIndex: -1,
1919
+ role: "slider",
1920
+ "aria-valuenow": value,
1921
+ "aria-valuemin": min,
1922
+ "aria-valuemax": max,
1923
+ "aria-orientation": vertical ? "vertical" : "horizontal",
1924
+ "aria-disabled": disabled,
1925
+ "aria-readonly": readonly,
1804
1926
  style: {
1805
- display: "flex",
1806
- flexDirection: vertical ? "row" : "column",
1807
- width: !vertical ? "100%" : void 0,
1808
- height: vertical ? "100%" : void 0
1927
+ margin: `calc(${(0, _tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
1928
+ ...style
1929
+ },
1930
+ onPointerDown: (event) => {
1931
+ pointerDownHandler(event);
1932
+ onPointerDown?.(event);
1809
1933
  },
1810
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1811
- className: "tremolo-slider-track-wrapper",
1812
- ref: trackElementRef,
1813
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderTrack, {
1814
- __percent: percent,
1815
- __thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderThumb, {
1816
- ref: thumbRef,
1934
+ onKeyDown: (event) => {
1935
+ handleKeyDown(event);
1936
+ onKeyDown?.(event);
1937
+ },
1938
+ onFocus: (event) => {
1939
+ thumbRef.current?.focus();
1940
+ onFocus?.(event);
1941
+ },
1942
+ onBlur: (event) => {
1943
+ thumbRef.current?.blur();
1944
+ onBlur?.(event);
1945
+ },
1946
+ ...props,
1947
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1948
+ style: {
1949
+ display: "flex",
1950
+ flexDirection: vertical ? "row" : "column",
1951
+ width: !vertical ? "100%" : void 0,
1952
+ height: vertical ? "100%" : void 0
1953
+ },
1954
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1955
+ className: "tremolo-slider-track-wrapper",
1956
+ ref: trackElementRef,
1957
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Track, {
1817
1958
  __percent: percent,
1818
- ...thumbProps
1819
- }),
1820
- ...trackProps
1821
- })
1822
- }), scaleComponent]
1959
+ __thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb$1, {
1960
+ ref: thumbRef,
1961
+ __percent: percent,
1962
+ ...thumbProps
1963
+ }),
1964
+ ...trackProps
1965
+ })
1966
+ }), scaleComponent]
1967
+ })
1823
1968
  })
1824
- })
1825
- });
1826
- });
1827
-
1969
+ });
1970
+ }),
1971
+ Thumb: Thumb$1,
1972
+ Track,
1973
+ Scale,
1974
+ ScaleOption
1975
+ };
1976
+ //#endregion
1977
+ //#region src/components/_util/composeRefs.tsx
1978
+ /**
1979
+ * Set a given ref to a given value
1980
+ * This utility takes care of different types of refs: callback refs and RefObject(s)
1981
+ */
1982
+ function setRef(ref, value) {
1983
+ if (typeof ref === "function") return ref(value);
1984
+ else if (ref !== null && ref !== void 0) ref.current = value;
1985
+ }
1986
+ /**
1987
+ * A utility to compose multiple refs together
1988
+ * Accepts callback refs and RefObject(s)
1989
+ */
1990
+ function composeRefs(...refs) {
1991
+ return (node) => {
1992
+ let hasCleanup = false;
1993
+ const cleanups = refs.map((ref) => {
1994
+ const cleanup = setRef(ref, node);
1995
+ if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
1996
+ return cleanup;
1997
+ });
1998
+ if (hasCleanup) return () => {
1999
+ for (let i = 0; i < cleanups.length; i++) {
2000
+ const cleanup = cleanups[i];
2001
+ if (typeof cleanup == "function") cleanup();
2002
+ else setRef(refs[i], null);
2003
+ }
2004
+ };
2005
+ };
2006
+ }
1828
2007
  //#endregion
1829
2008
  //#region src/components/WheelObserver/index.tsx
1830
- /** @category WheelObserver */
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
+ */
1831
2020
  function WheelObserver(props) {
1832
- const { children, onWheel, as: Component = "div",...attributes } = props;
2021
+ const { as: Component = "div", children, onWheel, ref, ...attributes } = props;
1833
2022
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
1834
- ref: useRefCallbackEvent("wheel", (event) => {
2023
+ ref: composeRefs(ref, useRefCallbackEvent("wheel", (event) => {
1835
2024
  if (onWheel) onWheel(event);
1836
- }, { passive: false }, [onWheel]),
2025
+ }, { passive: false }, [onWheel])),
1837
2026
  ...attributes,
1838
2027
  children
1839
2028
  });
1840
2029
  }
1841
-
1842
2030
  //#endregion
1843
2031
  //#region src/components/XYPad/Area.tsx
1844
- /** @category XYPad */
1845
- function XYPadArea({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
2032
+ function Area({ width = 120, height = 120, color, children, className, style, __thumb, ...props }) {
1846
2033
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1847
2034
  className: (0, clsx.default)("tremolo-xy-pad-area", className),
1848
2035
  style: {
@@ -1855,11 +2042,9 @@ function XYPadArea({ width = 120, height = 120, color, children, className, styl
1855
2042
  children: [children, __thumb]
1856
2043
  });
1857
2044
  }
1858
-
1859
2045
  //#endregion
1860
2046
  //#region src/components/XYPad/Thumb.tsx
1861
- /** @category XYPad */
1862
- const XYPadThumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __css,...props }, ref) => {
2047
+ const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __readonly, __css, ...props }, ref) => {
1863
2048
  const wrapperRef = (0, react.useRef)(null);
1864
2049
  (0, react.useImperativeHandle)(ref, () => {
1865
2050
  return {
@@ -1883,6 +2068,7 @@ const XYPadThumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color
1883
2068
  className: (0, clsx.default)("tremolo-xy-pad-thumb", className),
1884
2069
  tabIndex: 0,
1885
2070
  "aria-disabled": __disabled,
2071
+ "aria-readonly": __readonly,
1886
2072
  style: {
1887
2073
  "--color": color,
1888
2074
  width: size ?? width,
@@ -1892,7 +2078,6 @@ const XYPadThumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color
1892
2078
  })
1893
2079
  });
1894
2080
  });
1895
-
1896
2081
  //#endregion
1897
2082
  //#region src/components/XYPad/index.tsx
1898
2083
  const defaultValueOptions = {
@@ -1902,192 +2087,201 @@ const defaultValueOptions = {
1902
2087
  wheel: ["raw", 1],
1903
2088
  keyboard: ["raw", 1]
1904
2089
  };
2090
+ const defaultExternalStyles = {
2091
+ userSelectNone: true,
2092
+ cursor: "pointer"
2093
+ };
1905
2094
  /**
1906
2095
  * Simple XYPad
1907
- * @category XYPad
1908
2096
  */
1909
- const XYPad = (0, react.forwardRef)(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, forwardedRef) => {
1910
- const x = (0, react.useMemo)(() => {
1911
- return {
1912
- ...defaultValueOptions,
1913
- ..._x
1914
- };
1915
- }, [_x]);
1916
- const y = (0, react.useMemo)(() => {
1917
- return {
1918
- ...defaultValueOptions,
1919
- ..._y
2097
+ const XYPad = {
2098
+ Root: (0, react.forwardRef)(({ x: _x, y: _y, className, style, externalStyles: _externalStyles, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
2099
+ const x = (0, react.useMemo)(() => {
2100
+ return {
2101
+ ...defaultValueOptions,
2102
+ ..._x
2103
+ };
2104
+ }, [_x]);
2105
+ const y = (0, react.useMemo)(() => {
2106
+ return {
2107
+ ...defaultValueOptions,
2108
+ ..._y
2109
+ };
2110
+ }, [_y]);
2111
+ const rootRef = (0, react.useRef)(null);
2112
+ const areaElementRef = (0, react.useRef)(null);
2113
+ const thumbRef = (0, react.useRef)(null);
2114
+ const externalStyles = {
2115
+ ...defaultExternalStyles,
2116
+ ..._externalStyles
1920
2117
  };
1921
- }, [_y]);
1922
- const areaElementRef = (0, react.useRef)(null);
1923
- const thumbRef = (0, react.useRef)(null);
1924
- const nx = (0, __tremolo_ui_functions.normalizeValue)(x.value, x.min, x.max, x.skew);
1925
- const ny = (0, __tremolo_ui_functions.normalizeValue)(y.value, y.min, y.max, y.skew);
1926
- const percentX = (0, __tremolo_ui_functions.toFixed)((x.reverse ? 1 - nx : nx) * 100);
1927
- const percentY = (0, __tremolo_ui_functions.toFixed)((y.reverse ? 1 - ny : ny) * 100);
1928
- let areaProps = {};
1929
- let thumbProps = {};
1930
- if (children != void 0) react.default.Children.forEach(children, (child) => {
1931
- if (react.default.isValidElement(child)) if (child.type == XYPadThumb) thumbProps = child.props;
1932
- else if (child.type == XYPadArea) areaProps = child.props;
1933
- else throw new Error("only <XYPadThumb> or <XYPadArea>");
1934
- else throw new Error("children is an invalid element.");
1935
- });
1936
- const onDrag = (0, react.useCallback)((nx$1, ny$1) => {
1937
- if (!onChange || readonly) return;
1938
- const vx = (0, __tremolo_ui_functions.rawValue)(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
1939
- const vy = (0, __tremolo_ui_functions.rawValue)(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
1940
- onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vx, x.step), x.min, x.max), (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(vy, y.step), y.min, y.max));
1941
- }, [
1942
- onChange,
1943
- readonly,
1944
- x.max,
1945
- x.min,
1946
- x.reverse,
1947
- x.skew,
1948
- x.step,
1949
- y.max,
1950
- y.min,
1951
- y.reverse,
1952
- y.skew,
1953
- y.step
1954
- ]);
1955
- const updateValueByEvent = (0, react.useCallback)((eventType, target, x$1) => {
1956
- let v;
1957
- if (eventType == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(target.value, target.min, target.max, target.skew) + x$1, target.min, target.max, target.skew);
1958
- else v = target.value + x$1;
1959
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, target.step), target.min, target.max);
1960
- }, []);
1961
- const handleKeyDown = (0, react.useCallback)((event) => {
1962
- if (!onChange || readonly) return;
1963
- const key = event.key;
1964
- if ([
1965
- "ArrowRight",
1966
- "ArrowLeft",
1967
- "ArrowUp",
1968
- "ArrowDown"
1969
- ].includes(key)) {
1970
- const isHorizontal = key == "ArrowRight" || key == "ArrowLeft";
1971
- const target = isHorizontal ? x : y;
1972
- if (!target.keyboard) return;
2118
+ const nx = (0, _tremolo_ui_functions.normalizeValue)(x.value, x.min, x.max, x.skew);
2119
+ const ny = (0, _tremolo_ui_functions.normalizeValue)(y.value, y.min, y.max, y.skew);
2120
+ const percentX = (0, _tremolo_ui_functions.toFixed)((x.reverse ? 1 - nx : nx) * 100);
2121
+ const percentY = (0, _tremolo_ui_functions.toFixed)((y.reverse ? 1 - ny : ny) * 100);
2122
+ let areaProps = {};
2123
+ let thumbProps = {};
2124
+ if (children != void 0) react.default.Children.forEach(children, (child) => {
2125
+ if (react.default.isValidElement(child)) if (child.type == Thumb) thumbProps = child.props;
2126
+ else if (child.type == Area) areaProps = child.props;
2127
+ else throw new Error("only <XYPadThumb> or <XYPadArea>");
2128
+ else throw new Error("children is an invalid element.");
2129
+ });
2130
+ const onDrag = (0, react.useCallback)((nx, ny) => {
2131
+ if (!onChange || readonly) return;
2132
+ const vx = (0, _tremolo_ui_functions.rawValue)(x.reverse ? 1 - nx : nx, x.min, x.max, x.skew);
2133
+ const vy = (0, _tremolo_ui_functions.rawValue)(y.reverse ? 1 - ny : ny, y.min, y.max, y.skew);
2134
+ onChange((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(vx, x.step), x.min, x.max), (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(vy, y.step), y.min, y.max));
2135
+ }, [
2136
+ onChange,
2137
+ readonly,
2138
+ x.max,
2139
+ x.min,
2140
+ x.reverse,
2141
+ x.skew,
2142
+ x.step,
2143
+ y.max,
2144
+ y.min,
2145
+ y.reverse,
2146
+ y.skew,
2147
+ y.step
2148
+ ]);
2149
+ const updateValueByEvent = (0, react.useCallback)((eventType, target, x) => {
2150
+ let v;
2151
+ if (eventType == "normalized") v = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(target.value, target.min, target.max, target.skew) + x, target.min, target.max, target.skew);
2152
+ else v = target.value + x;
2153
+ return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(v, target.step), target.min, target.max);
2154
+ }, []);
2155
+ const handleKeyDown = (0, react.useCallback)((event) => {
2156
+ if (!onChange || readonly) return;
2157
+ const key = event.key;
2158
+ if ([
2159
+ "ArrowRight",
2160
+ "ArrowLeft",
2161
+ "ArrowUp",
2162
+ "ArrowDown"
2163
+ ].includes(key)) {
2164
+ const isHorizontal = key == "ArrowRight" || key == "ArrowLeft";
2165
+ const target = isHorizontal ? x : y;
2166
+ if (!target.keyboard) return;
2167
+ event.preventDefault();
2168
+ let delta = target.keyboard[1];
2169
+ if (key == "ArrowLeft" || key == "ArrowUp") delta *= -1;
2170
+ if (target.reverse) delta *= -1;
2171
+ const newValue = updateValueByEvent(target.keyboard[0], target, delta);
2172
+ if (isHorizontal) onChange(newValue, y.value);
2173
+ else onChange(x.value, newValue);
2174
+ }
2175
+ }, [
2176
+ onChange,
2177
+ readonly,
2178
+ x,
2179
+ y,
2180
+ updateValueByEvent
2181
+ ]);
2182
+ const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
2183
+ baseElementRef: areaElementRef,
2184
+ onDrag,
2185
+ onDragStart: (nx, ny) => {
2186
+ if (readonly) return;
2187
+ if (externalStyles.userSelectNone) addUserSelectNone();
2188
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
2189
+ thumbRef.current?.focus();
2190
+ 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
+ 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);
2192
+ onDragStart?.(valueX, valueY);
2193
+ },
2194
+ onDragEnd: (nx, ny) => {
2195
+ if (readonly) return;
2196
+ if (externalStyles.userSelectNone) removeUserSelectNone();
2197
+ if (externalStyles.cursor) resetCursorStyle();
2198
+ 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
+ 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
+ onDragEnd?.(valueX, valueY);
2201
+ }
2202
+ });
2203
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
2204
+ if (!onChange || readonly) return;
2205
+ const target = event.shiftKey ? x : y;
2206
+ if (!target.wheel) return;
1973
2207
  event.preventDefault();
1974
- let delta = target.keyboard[1];
1975
- if (key == "ArrowLeft" || key == "ArrowUp") delta *= -1;
2208
+ let delta = target.wheel[1];
2209
+ if (event.deltaY < 0) delta *= -1;
1976
2210
  if (target.reverse) delta *= -1;
1977
- const newValue = updateValueByEvent(target.keyboard[0], target, delta);
1978
- if (isHorizontal) onChange(newValue, y.value);
2211
+ const newValue = updateValueByEvent(target.wheel[0], target, delta);
2212
+ if (event.shiftKey) onChange(newValue, y.value);
1979
2213
  else onChange(x.value, newValue);
1980
- }
1981
- }, [
1982
- onChange,
1983
- readonly,
1984
- x,
1985
- y,
1986
- updateValueByEvent
1987
- ]);
1988
- const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1989
- baseElementRef: areaElementRef,
1990
- onDrag,
1991
- onDragStart: (nx$1, ny$1) => {
1992
- if (readonly) return;
1993
- if (bodyNoSelect) addNoSelect();
1994
- thumbRef.current?.focus();
1995
- const valueX = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(nx$1, x.min, x.max, x.skew), x.step), x.min, x.max);
1996
- const valueY = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(ny$1, y.min, y.max, y.skew), y.step), y.min, y.max);
1997
- onDragStart?.(valueX, valueY);
1998
- },
1999
- onDragEnd: (nx$1, ny$1) => {
2000
- if (readonly) return;
2001
- if (bodyNoSelect) removeNoSelect();
2002
- const valueX = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(nx$1, x.min, x.max, x.skew), x.step), x.min, x.max);
2003
- const valueY = (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(ny$1, y.min, y.max, y.skew), y.step), y.min, y.max);
2004
- onDragEnd?.(valueX, valueY);
2005
- }
2006
- });
2007
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
2008
- if (!onChange || readonly) return;
2009
- const target = event.shiftKey ? x : y;
2010
- if (!target.wheel) return;
2011
- event.preventDefault();
2012
- let delta = target.wheel[1];
2013
- if (event.deltaY < 0) delta *= -1;
2014
- if (target.reverse) delta *= -1;
2015
- const newValue = updateValueByEvent(target.wheel[0], target, delta);
2016
- if (event.shiftKey) onChange(newValue, y.value);
2017
- else onChange(x.value, newValue);
2018
- }, { passive: false }, [
2019
- x,
2020
- y,
2021
- onChange,
2022
- readonly,
2023
- updateValueByEvent
2024
- ]);
2025
- (0, react.useImperativeHandle)(forwardedRef, () => {
2026
- return {
2027
- focus() {
2214
+ }, { passive: false }, [
2215
+ x,
2216
+ y,
2217
+ onChange,
2218
+ readonly,
2219
+ updateValueByEvent
2220
+ ]);
2221
+ (0, react.useImperativeHandle)(forwardedRef, () => {
2222
+ return {
2223
+ focus() {
2224
+ thumbRef.current?.focus();
2225
+ },
2226
+ blur() {
2227
+ thumbRef.current?.blur();
2228
+ },
2229
+ original: rootRef
2230
+ };
2231
+ }, []);
2232
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2233
+ className: (0, clsx.default)("tremolo-xy-pad", className),
2234
+ ref: composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback),
2235
+ tabIndex: -1,
2236
+ "aria-disabled": disabled,
2237
+ "aria-readonly": readonly,
2238
+ style: {
2239
+ margin: `calc(${(0, _tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
2240
+ cursor: readonly ? "not-allowed" : "pointer",
2241
+ WebkitTapHighlightColor: "transparent",
2242
+ ...style
2243
+ },
2244
+ onPointerDown: (event) => {
2245
+ pointerDownHandler(event);
2246
+ onPointerDown?.(event);
2247
+ },
2248
+ onKeyDown: (event) => {
2249
+ handleKeyDown(event);
2250
+ onKeyDown?.(event);
2251
+ },
2252
+ onFocus: (event) => {
2028
2253
  thumbRef.current?.focus();
2254
+ onFocus?.(event);
2029
2255
  },
2030
- blur() {
2256
+ onBlur: (event) => {
2031
2257
  thumbRef.current?.blur();
2032
- }
2033
- };
2034
- }, []);
2035
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2036
- className: (0, clsx.default)("tremolo-xy-pad", className),
2037
- ref: (div) => {
2038
- wheelRefCallback(div);
2039
- touchMoveRefCallback(div);
2040
- },
2041
- tabIndex: -1,
2042
- "aria-disabled": disabled,
2043
- "aria-readonly": readonly,
2044
- style: {
2045
- margin: `calc(${(0, __tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
2046
- cursor: readonly ? "not-allowed" : "pointer",
2047
- WebkitTapHighlightColor: "transparent",
2048
- ...style
2049
- },
2050
- onPointerDown: (event) => {
2051
- pointerDownHandler(event);
2052
- onPointerDown?.(event);
2053
- },
2054
- onKeyDown: (event) => {
2055
- handleKeyDown(event);
2056
- onKeyDown?.(event);
2057
- },
2058
- onFocus: (event) => {
2059
- thumbRef.current?.focus();
2060
- onFocus?.(event);
2061
- },
2062
- onBlur: (event) => {
2063
- thumbRef.current?.blur();
2064
- onBlur?.(event);
2065
- },
2066
- ...props,
2067
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2068
- className: "tremolo-xy-pad-area-wrapper",
2069
- ref: areaElementRef,
2070
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XYPadArea, {
2071
- __thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XYPadThumb, {
2072
- ref: thumbRef,
2073
- __disabled: disabled,
2074
- __css: {
2075
- top: `${percentY}%`,
2076
- left: `${percentX}%`
2077
- },
2078
- ...thumbProps
2079
- }),
2080
- ...areaProps
2258
+ onBlur?.(event);
2259
+ },
2260
+ ...props,
2261
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2262
+ className: "tremolo-xy-pad-area-wrapper",
2263
+ ref: areaElementRef,
2264
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Area, {
2265
+ __thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb, {
2266
+ ref: thumbRef,
2267
+ __disabled: disabled,
2268
+ __readonly: readonly,
2269
+ __css: {
2270
+ top: `${percentY}%`,
2271
+ left: `${percentX}%`
2272
+ },
2273
+ ...thumbProps
2274
+ }),
2275
+ ...areaProps
2276
+ })
2081
2277
  })
2082
- })
2083
- });
2084
- });
2085
-
2278
+ });
2279
+ }),
2280
+ Thumb,
2281
+ Area
2282
+ };
2086
2283
  //#endregion
2087
2284
  //#region src/hooks/useAnimationFrame.ts
2088
- /**
2089
- * @category hooks
2090
- */
2091
2285
  function useAnimationFrame(callback = () => {}, deps = []) {
2092
2286
  const reqIdRef = (0, react.useRef)(-1);
2093
2287
  const loop = (0, react.useCallback)(() => {
@@ -2099,7 +2293,6 @@ function useAnimationFrame(callback = () => {}, deps = []) {
2099
2293
  return () => cancelAnimationFrame(reqIdRef.current);
2100
2294
  }, [loop]);
2101
2295
  }
2102
-
2103
2296
  //#endregion
2104
2297
  //#region src/hooks/useMIDIAccess.ts
2105
2298
  /** @private */
@@ -2108,7 +2301,6 @@ const PERMISSION_DENIED = "PERMISSION_DENIED";
2108
2301
  const NOT_SUPPORTED = "NOT_SUPPORTED";
2109
2302
  /**
2110
2303
  * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
2111
- * @category hooks
2112
2304
  */
2113
2305
  function useMIDIAccess(requestOnMount = true) {
2114
2306
  const [midiAccess, setMidiAccess] = (0, react.useState)(null);
@@ -2131,12 +2323,10 @@ function useMIDIAccess(requestOnMount = true) {
2131
2323
  error
2132
2324
  };
2133
2325
  }
2134
-
2135
2326
  //#endregion
2136
2327
  //#region src/hooks/useMIDIMessage.ts
2137
2328
  /**
2138
2329
  * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2139
- * @category hooks
2140
2330
  */
2141
2331
  function useMIDIMessage(midiAccess, onMIDIMessage) {
2142
2332
  (0, react.useEffect)(() => {
@@ -2147,7 +2337,6 @@ function useMIDIMessage(midiAccess, onMIDIMessage) {
2147
2337
  };
2148
2338
  }, [midiAccess, onMIDIMessage]);
2149
2339
  }
2150
-
2151
2340
  //#endregion
2152
2341
  //#region src/hooks/useMIDIInput.ts
2153
2342
  const MIDI_EVENT_TO_NUMBER = {
@@ -2157,7 +2346,6 @@ const MIDI_EVENT_TO_NUMBER = {
2157
2346
  };
2158
2347
  /**
2159
2348
  * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
2160
- * @category hooks
2161
2349
  */
2162
2350
  function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
2163
2351
  useMIDIMessage(midiAccess, (0, react.useCallback)((event) => {
@@ -2172,32 +2360,20 @@ function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEven
2172
2360
  onPitchBendEvent
2173
2361
  ]));
2174
2362
  }
2175
-
2176
2363
  //#endregion
2177
2364
  exports.AnimationCanvas = AnimationCanvas;
2178
- exports.AnimationKnob = AnimationKnob;
2179
- exports.BlackKey = BlackKey;
2180
- exports.DecrementStepper = DecrementStepper;
2181
2365
  exports.DragObserver = DragObserver;
2182
- exports.IncrementStepper = IncrementStepper;
2183
- exports.KeyLabel = KeyLabel;
2184
2366
  exports.Knob = Knob;
2185
2367
  exports.NOT_SUPPORTED = NOT_SUPPORTED;
2186
2368
  exports.NumberInput = NumberInput;
2187
2369
  exports.PERMISSION_DENIED = PERMISSION_DENIED;
2188
2370
  exports.Piano = Piano;
2371
+ exports.PointsEditor = PointsEditor;
2189
2372
  exports.SHORTCUTS = SHORTCUTS;
2190
- exports.Scale = Scale;
2191
- exports.ScaleOption = ScaleOption;
2192
2373
  exports.Slider = Slider;
2193
- exports.SliderThumb = SliderThumb;
2194
- exports.SliderTrack = SliderTrack;
2195
- exports.Stepper = Stepper;
2196
2374
  exports.WheelObserver = WheelObserver;
2197
- exports.WhiteKey = WhiteKey;
2198
2375
  exports.XYPad = XYPad;
2199
- exports.XYPadArea = XYPadArea;
2200
- exports.XYPadThumb = XYPadThumb;
2376
+ exports.clampPoint = clampPoint;
2201
2377
  exports.getNoteRangeArray = getNoteRangeArray;
2202
2378
  exports.useAnimationFrame = useAnimationFrame;
2203
2379
  exports.useDrag = useDrag;
@@ -2209,4 +2385,5 @@ exports.useMIDIAccess = useMIDIAccess;
2209
2385
  exports.useMIDIInput = useMIDIInput;
2210
2386
  exports.useMIDIMessage = useMIDIMessage;
2211
2387
  exports.useSliderContext = useSliderContext;
2388
+
2212
2389
  //# sourceMappingURL=index.cjs.map