@tremolo-ui/react 0.2.0 → 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 (64) hide show
  1. package/dist/index.cjs +1203 -1036
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +176 -159
  4. package/dist/index.d.cts +241 -246
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.ts +241 -246
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1187 -1021
  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 +5 -1
  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.tsx +6 -8
  26. package/src/components/Piano/KeyLabel.tsx +0 -2
  27. package/src/components/Piano/context.tsx +0 -2
  28. package/src/components/Piano/index.tsx +7 -11
  29. package/src/components/Piano/key.tsx +0 -10
  30. package/src/components/Piano/keyboardShortcuts.ts +0 -2
  31. package/src/components/PointsEditor/Background.tsx +17 -0
  32. package/src/components/PointsEditor/Container.tsx +26 -0
  33. package/src/components/PointsEditor/Point.tsx +137 -0
  34. package/src/components/PointsEditor/context.tsx +91 -0
  35. package/src/components/PointsEditor/index.css +30 -0
  36. package/src/components/PointsEditor/index.tsx +129 -0
  37. package/src/components/Slider/Scale.tsx +0 -2
  38. package/src/components/Slider/ScaleOption.tsx +0 -2
  39. package/src/components/Slider/Thumb.tsx +0 -4
  40. package/src/components/Slider/Track.tsx +0 -2
  41. package/src/components/Slider/context.tsx +0 -1
  42. package/src/components/Slider/index.tsx +42 -14
  43. package/src/components/WheelObserver/index.tsx +17 -19
  44. package/src/components/XYPad/Area.tsx +0 -2
  45. package/src/components/XYPad/Thumb.tsx +0 -3
  46. package/src/components/XYPad/index.tsx +52 -25
  47. package/src/components/_util/composeRefs.tsx +63 -0
  48. package/src/components/_util/index.ts +23 -6
  49. package/src/components/_util/type.ts +1 -0
  50. package/src/hooks/useAnimationFrame.ts +0 -3
  51. package/src/hooks/useCallbackRef.ts +2 -2
  52. package/src/hooks/useDrag.ts +12 -11
  53. package/src/hooks/useDragWithElement.ts +12 -17
  54. package/src/hooks/useEventListener.ts +6 -9
  55. package/src/hooks/useInterval.ts +0 -3
  56. package/src/hooks/useLongPress.ts +0 -3
  57. package/src/hooks/useMIDIAccess.ts +0 -1
  58. package/src/hooks/useMIDIInput.ts +0 -1
  59. package/src/hooks/useMIDIMessage.ts +0 -1
  60. package/src/hooks/usePianoDrag.ts +75 -0
  61. package/src/index.ts +8 -5
  62. package/src/styles/global.css +17 -1
  63. package/dist/index.css.map +0 -1
  64. 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
- startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
590
- className: (0, clsx.default)("tremolo-knob-inactive-line", classes?.inactiveLine),
591
- d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`,
592
- fill: "none",
593
- stroke: inactiveLine || "currentColor",
594
- strokeWidth: lineWeight
595
- }),
596
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
597
- className: (0, clsx.default)("tremolo-knob-active-line", classes?.activeLine),
598
- d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
599
- fill: "none",
600
- stroke: activeLine || "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,18 +715,13 @@ 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
724
  //#region src/hooks/useInterval.ts
750
- /**
751
- * @category hooks
752
- */
753
725
  function useInterval(callback, delay) {
754
726
  const savedCallback = (0, react.useRef)(callback);
755
727
  (0, react.useEffect)(() => {
@@ -765,12 +737,8 @@ function useInterval(callback, delay) {
765
737
  };
766
738
  }, [delay]);
767
739
  }
768
-
769
740
  //#endregion
770
741
  //#region src/hooks/useLongPress.ts
771
- /**
772
- * @category hooks
773
- */
774
742
  function useLongPress(callback, initialDelay = 500, interval = 40) {
775
743
  const [pressed, setPressed] = (0, react.useState)(false);
776
744
  const [delay, setDelay] = (0, react.useState)(initialDelay);
@@ -787,11 +755,9 @@ function useLongPress(callback, initialDelay = 500, interval = 40) {
787
755
  setPressed(true);
788
756
  }, [callback]);
789
757
  }
790
-
791
758
  //#endregion
792
759
  //#region src/components/NumberInput/DecrementStepper.tsx
793
- /** @category NumberInput */
794
- function DecrementStepper({ size = 12, children, className,...props }) {
760
+ function DecrementStepper({ size = 12, children, className, ...props }) {
795
761
  const min = useNumberInputContext((s) => s.min);
796
762
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
797
763
  const readonly = useNumberInputContext((s) => s.readonly);
@@ -818,11 +784,9 @@ function DecrementStepper({ size = 12, children, className,...props }) {
818
784
  })
819
785
  });
820
786
  }
821
-
822
787
  //#endregion
823
788
  //#region src/components/NumberInput/IncrementStepper.tsx
824
- /** @category NumberInput */
825
- function IncrementStepper({ size = 12, children, className,...props }) {
789
+ function IncrementStepper({ size = 12, children, className, ...props }) {
826
790
  const max = useNumberInputContext((s) => s.max);
827
791
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
828
792
  const readonly = useNumberInputContext((s) => s.readonly);
@@ -849,10 +813,9 @@ function IncrementStepper({ size = 12, children, className,...props }) {
849
813
  })
850
814
  });
851
815
  }
852
-
853
816
  //#endregion
854
817
  //#region src/components/NumberInput/InternalInput.tsx
855
- const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children,...props }, ref) => {
818
+ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children, ...props }, ref) => {
856
819
  const elmRef = (0, react.useRef)(null);
857
820
  const [editing, setEditing] = (0, react.useState)(false);
858
821
  const value = useNumberInputContext((s) => s.value);
@@ -876,12 +839,12 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
876
839
  const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
877
840
  let newValue;
878
841
  if (eventType == "normalized") {
879
- if (!min || !max) throw new Error("required parameter: min, max");
880
- newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(valueAsNumber, min, max) + x, min, max);
881
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
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);
882
845
  } else {
883
846
  newValue = valueAsNumber + x;
884
- return (0, __tremolo_ui_functions.stepValue)(newValue, step);
847
+ return (0, _tremolo_ui_functions.stepValue)(newValue, step);
885
848
  }
886
849
  }, [
887
850
  max,
@@ -946,8 +909,8 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
946
909
  const v = event.currentTarget.value;
947
910
  const cursorPos = event.target.selectionStart;
948
911
  change(v);
949
- const valueAsNumber$1 = parseValue(v, units, digit).rawValue;
950
- onChange?.(valueAsNumber$1, v);
912
+ const valueAsNumber = parseValue(v, units, digit).rawValue;
913
+ onChange?.(valueAsNumber, v);
951
914
  if (cursorPos) {
952
915
  event.target.selectionStart = cursorPos;
953
916
  event.target.selectionEnd = cursorPos;
@@ -978,11 +941,9 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
978
941
  ...props
979
942
  });
980
943
  });
981
-
982
944
  //#endregion
983
945
  //#region src/components/NumberInput/Stepper.tsx
984
- /** @category NumberInput */
985
- function Stepper({ dynamic = true, children, className,...props }) {
946
+ function Stepper({ dynamic = true, children, className, ...props }) {
986
947
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
987
948
  className: (0, clsx.default)("tremolo-number-input-stepper", className),
988
949
  "data-dynamic": dynamic,
@@ -990,62 +951,58 @@ function Stepper({ dynamic = true, children, className,...props }) {
990
951
  children
991
952
  });
992
953
  }
993
-
994
- //#endregion
995
- //#region src/components/NumberInput/index.tsx
996
- const NumberInputImpl = (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) => {
997
- const colors = { "--active-color": activeColor };
998
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
999
- value: String(value),
1000
- min,
1001
- max,
1002
- step,
1003
- units,
1004
- digit,
1005
- readonly,
1006
- keepWithinRange,
1007
- onChange,
1008
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1009
- className: (0, clsx.default)("tremolo-number-input-wrapper", wrapperClassName),
1010
- tabIndex: 0,
1011
- style: { ...colors },
1012
- "data-stepper": !!children,
1013
- "data-variant": variant,
1014
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
1015
- ref: forwardedRef,
1016
- readonly,
1017
- disabled,
1018
- selectWithFocus,
1019
- blurOnEnter,
1020
- clampValueOnBlur,
1021
- wheel,
1022
- keyboard,
1023
- className,
1024
- onChange,
1025
- onFocus,
1026
- onBlur,
1027
- ...props
1028
- }), children]
1029
- })
1030
- });
1031
- });
1032
954
  /**
1033
955
  * Input with some useful functions for entering numerical values.
1034
- * @category NumberInput
1035
956
  */
1036
- const NumberInput = Object.assign(NumberInputImpl, {
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
+ }),
1037
994
  Stepper,
1038
995
  IncrementStepper,
1039
996
  DecrementStepper
1040
- });
1041
-
997
+ };
1042
998
  //#endregion
1043
- //#region src/hooks/useDragWithElement.ts
999
+ //#region src/hooks/usePianoDrag.ts
1044
1000
  /**
1045
- * @category hooks
1001
+ * Internal
1046
1002
  * @returns [refCallback, pointerDownHandler]
1003
+ * @private
1047
1004
  */
1048
- function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1005
+ function usePianoDrag({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
1049
1006
  const dragged = (0, react.useRef)(false);
1050
1007
  const normalizedX = (0, react.useRef)(0);
1051
1008
  const normalizedY = (0, react.useRef)(0);
@@ -1056,8 +1013,8 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd })
1056
1013
  const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
1057
1014
  const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1058
1015
  const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1059
- const nx = (0, __tremolo_ui_functions.normalizeValue)(mouseX, left, right);
1060
- 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);
1061
1018
  normalizedX.current = nx;
1062
1019
  normalizedY.current = ny;
1063
1020
  onDrag(nx, ny);
@@ -1074,9 +1031,11 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd })
1074
1031
  dragged.current = false;
1075
1032
  onDragEnd?.(normalizedX.current, normalizedY.current);
1076
1033
  });
1077
- return [refHandler, pointerDownHandler];
1034
+ return {
1035
+ refHandler,
1036
+ pointerDownHandler
1037
+ };
1078
1038
  }
1079
-
1080
1039
  //#endregion
1081
1040
  //#region src/components/Piano/context.tsx
1082
1041
  const createPianoStore = (initProps) => {
@@ -1099,7 +1058,7 @@ const createPianoStore = (initProps) => {
1099
1058
  }));
1100
1059
  };
1101
1060
  const PianoContext = (0, react.createContext)(null);
1102
- function PianoProvider({ children,...props }) {
1061
+ function PianoProvider({ children, ...props }) {
1103
1062
  const storeRef = (0, react.useRef)(null);
1104
1063
  if (!storeRef.current) storeRef.current = createPianoStore(props);
1105
1064
  (0, react.useEffect)(() => {
@@ -1111,17 +1070,14 @@ function PianoProvider({ children,...props }) {
1111
1070
  children
1112
1071
  });
1113
1072
  }
1114
- /** @category Piano */
1115
1073
  function usePianoContext(selector) {
1116
1074
  const store = (0, react.useContext)(PianoContext);
1117
1075
  if (!store) throw new Error("Missing PianoContext.Provider in the tree");
1118
1076
  return (0, zustand.useStore)(store, selector);
1119
1077
  }
1120
-
1121
1078
  //#endregion
1122
1079
  //#region src/components/Piano/KeyLabel.tsx
1123
- /** @category Piano */
1124
- function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
1080
+ function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label, ...props }) {
1125
1081
  const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
1126
1082
  const content = label ? label(__note, index) : __label && __label(__note, index);
1127
1083
  return (content != void 0 || content != null) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1134,14 +1090,7 @@ function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __
1134
1090
  })
1135
1091
  });
1136
1092
  }
1137
-
1138
- //#endregion
1139
- //#region src/components/Piano/key.tsx
1140
- /** @category Piano */
1141
- const defaultWhiteKeyWidth = 40;
1142
- /** @category Piano */
1143
- const defaultBlackKeyWidth = 26;
1144
- 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) => {
1145
1094
  const [played, setPlayed] = (0, react.useState)(false);
1146
1095
  const midiMax = usePianoContext((s) => s.midiMax);
1147
1096
  const fill = usePianoContext((s) => s.fill);
@@ -1203,8 +1152,7 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
1203
1152
  })
1204
1153
  });
1205
1154
  });
1206
- /** @category Piano */
1207
- 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) => {
1208
1156
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyImpl, {
1209
1157
  ref,
1210
1158
  keyType: "white",
@@ -1214,8 +1162,7 @@ const WhiteKey = (0, react.forwardRef)(({ width = defaultWhiteKeyWidth, height =
1214
1162
  ...props
1215
1163
  });
1216
1164
  });
1217
- /** @category Piano */
1218
- 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) => {
1219
1166
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyImpl, {
1220
1167
  ref,
1221
1168
  keyType: "black",
@@ -1225,10 +1172,8 @@ const BlackKey = (0, react.forwardRef)(({ width = defaultBlackKeyWidth, height =
1225
1172
  ...props
1226
1173
  });
1227
1174
  });
1228
-
1229
1175
  //#endregion
1230
1176
  //#region src/components/Piano/keyboardShortcuts.ts
1231
- /** @category Piano */
1232
1177
  const SHORTCUTS = { HOME_ROW: { keys: [
1233
1178
  "a",
1234
1179
  "w",
@@ -1248,208 +1193,406 @@ const SHORTCUTS = { HOME_ROW: { keys: [
1248
1193
  "p",
1249
1194
  ";"
1250
1195
  ] } };
1251
-
1252
1196
  //#endregion
1253
1197
  //#region src/components/Piano/index.tsx
1254
1198
  /**
1255
1199
  * [noteRange.first, noteRange.first + 1 ..., noteRange.last]
1256
- * @category Piano
1257
1200
  */
1258
1201
  function getNoteRangeArray(noteRange) {
1259
1202
  return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1260
1203
  }
1261
- const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
1262
- const PianoImpl = (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) => {
1263
- const [whiteNoteWidth, setWhiteNoteWidth] = (0, react.useState)(_whiteNoteWidth);
1264
- const keyRefs = (0, react.useRef)([]);
1265
- for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = (0, react.createRef)();
1266
- const pianoRef = (0, react.useRef)(null);
1267
- const hitKeyIndex = (0, react.useRef)(-1);
1268
- const noteRangeArray = getNoteRangeArray(noteRange);
1269
- const whiteNotes = noteRangeArray.filter((v) => (0, __tremolo_ui_functions.isWhiteKey)(v));
1270
- const blackNotes = noteRangeArray.filter((v) => (0, __tremolo_ui_functions.isBlackKey)(v));
1271
- const whiteNoteCount = whiteNotes.length;
1272
- const padding = 1;
1273
- const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
1274
- const childrenWithProps = (0, react.useMemo)(() => {
1275
- return children && react.default.Children.map(children, (child, index) => {
1276
- if (react.default.isValidElement(child)) {
1277
- const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * .65;
1278
- const props$1 = {
1279
- ref: keyRefs.current[index],
1280
- __width
1204
+ const blackPerWhiteWidth = 26 / 40;
1205
+ /**
1206
+ * Customizable piano component.
1207
+ */
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);
1281
1308
  };
1282
- 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();
1283
1326
  }
1284
- return child;
1285
1327
  });
1286
- }, [children, whiteNoteWidth]);
1287
- const notePosition = (0, react.useCallback)((note) => {
1288
- const pitchPositions = {
1289
- C: 0,
1290
- "C#": 1,
1291
- D: 1,
1292
- "D#": 2,
1293
- E: 2,
1294
- F: 3,
1295
- "F#": 4,
1296
- G: 4,
1297
- "G#": 5,
1298
- A: 5,
1299
- "A#": 6,
1300
- B: 6
1301
- };
1302
- const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
1303
- const padding$1 = 1;
1304
- const targetNoteKey = (0, __tremolo_ui_functions.noteKey)(note);
1305
- const firstNoteKey = (0, __tremolo_ui_functions.noteKey)(noteRange.first);
1306
- const octave = Math.floor((note - noteRange.first) / 12);
1307
- const octaveOffset = __tremolo_ui_functions.noteKeys.indexOf(firstNoteKey) > __tremolo_ui_functions.noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1308
- const w = whiteNoteWidth + padding$1;
1309
- const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1310
- const blackKeyOffset = (0, __tremolo_ui_functions.isBlackKey)(note) ? blackNoteWidth / 2 : 0;
1311
- return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1312
- }, [noteRange.first, whiteNoteWidth]);
1313
- const getHitKeyIndex = (0, react.useCallback)((x, y) => {
1314
- if (!pianoRef.current) return -1;
1315
- const containerHeight = pianoRef.current.clientHeight;
1316
- const notes = [...blackNotes, ...whiteNotes];
1317
- for (let i = 0; i < notes.length; i++) {
1318
- const note = notes[i];
1319
- const pos = notePosition(note);
1320
- const w = (0, __tremolo_ui_functions.isWhiteKey)(note) ? whiteNoteWidth : whiteNoteWidth * blackPerWhiteWidth;
1321
- const h = (0, __tremolo_ui_functions.isWhiteKey)(note) ? containerHeight : containerHeight * .6;
1322
- 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"
1323
1413
  }
1324
- return -1;
1325
- }, [
1326
- blackNotes,
1327
- notePosition,
1328
- whiteNoteWidth,
1329
- whiteNotes
1330
- ]);
1331
- const onDrag = (0, react.useCallback)((perX, perY) => {
1332
- if (!pianoRef.current) return;
1333
- const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
1334
- const index = noteRangeArray.indexOf(note);
1335
- if (index == -1) return;
1336
- if (hitKeyIndex.current != index) {
1337
- keyRefs.current[hitKeyIndex.current]?.current?.stop();
1338
- keyRefs.current[index]?.current?.play();
1339
- hitKeyIndex.current = index;
1414
+ };
1415
+ return (0, zustand.createStore)()((set) => ({
1416
+ ...DEFAULT_PROPS,
1417
+ ...initProps,
1418
+ setContainerElementRef: (containerElementRef) => {
1419
+ set(() => ({ containerElementRef }));
1340
1420
  }
1341
- }, [
1342
- getHitKeyIndex,
1343
- noteRangeArray,
1344
- staticWidth
1345
- ]);
1346
- const onDragEnd = (0, react.useCallback)(() => {
1347
- if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
1348
- hitKeyIndex.current = -1;
1349
- }, [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);
1350
1427
  (0, react.useEffect)(() => {
1351
- if (fill && pianoRef.current) {
1352
- const parent = pianoRef.current.parentElement;
1353
- if (!parent) throw new Error("doesn't have a parent element.");
1354
- const resizeObserver = new ResizeObserver(() => {
1355
- const w = pianoRef.current.clientWidth;
1356
- setWhiteNoteWidth(w / whiteNoteCount - padding);
1357
- });
1358
- resizeObserver.observe(parent);
1359
- return () => {
1360
- resizeObserver.unobserve(parent);
1361
- };
1362
- } 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);
1363
1471
  }, [
1364
- fill,
1365
- _whiteNoteWidth,
1366
- whiteNoteCount
1367
- ]);
1368
- const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1369
- baseElementRef: pianoRef,
1370
1472
  onDrag,
1371
- 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);
1372
1487
  });
1373
- useEventListener(globalThis.window, "keydown", (e) => {
1374
- if (e.repeat) return;
1375
- if (!keyboardShortcuts) return;
1376
- const index = keyboardShortcuts.keys.indexOf(e.key);
1377
- if (index != -1) {
1378
- 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));
1379
1534
  }
1380
1535
  });
1381
- useEventListener(globalThis.window, "keyup", (e) => {
1382
- if (e.repeat) return;
1383
- if (!keyboardShortcuts) return;
1384
- const index = keyboardShortcuts.keys.indexOf(e.key);
1385
- if (index != -1) keyRefs.current[index]?.current?.stop();
1386
- });
1387
- (0, react.useImperativeHandle)(forwardedRef, () => {
1388
- return {
1389
- playNote(note, velocity) {
1390
- const index = noteRangeArray.indexOf(note);
1391
- if (index != -1) {
1392
- if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
1393
- } else onPlayNote?.(note, velocity);
1394
- },
1395
- stopNote(note) {
1396
- const index = noteRangeArray.indexOf(note);
1397
- if (index != -1) keyRefs.current[index]?.current?.stop();
1398
- else onStopNote?.(note);
1399
- }
1400
- };
1401
- }, [
1402
- noteRangeArray,
1403
- onPlayNote,
1404
- onStopNote
1405
- ]);
1406
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
1407
- notePosition,
1408
- noteRange,
1409
- glissando,
1410
- midiMax,
1411
- fill,
1412
- onPlayNote,
1413
- onStopNote,
1414
- label,
1415
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1416
- ref: (div) => {
1417
- pianoRef.current = div;
1418
- touchMoveRefCallback(div);
1419
- },
1420
- className: (0, clsx.default)("tremolo-piano", className),
1421
- style: {
1422
- width: fill ? "100%" : staticWidth,
1423
- height,
1424
- ...style
1425
- },
1426
- onPointerDown: (event) => {
1427
- pointerDownHandler(event);
1428
- onPointerDown?.(event);
1429
- },
1430
- ...props,
1431
- children: childrenWithProps || noteRangeArray.map((note, index) => (0, __tremolo_ui_functions.isWhiteKey)(note) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhiteKey, {
1432
- ref: keyRefs.current[index],
1433
- noteNumber: note,
1434
- __width: whiteNoteWidth
1435
- }, note) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlackKey, {
1436
- ref: keyRefs.current[index],
1437
- noteNumber: note,
1438
- __width: whiteNoteWidth * blackPerWhiteWidth
1439
- }, note))
1440
- })
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
1441
1559
  });
1442
- });
1560
+ }
1561
+ //#endregion
1562
+ //#region src/components/PointsEditor/index.tsx
1563
+ const defaultExternalStyles$2 = {
1564
+ userSelectNone: true,
1565
+ cursor: "grabbing"
1566
+ };
1443
1567
  /**
1444
- * Customizable piano component.
1445
- * @category Piano
1568
+ * Multiple Point Controller
1446
1569
  */
1447
- const Piano = Object.assign(PianoImpl, {
1448
- WhiteKey,
1449
- BlackKey,
1450
- KeyLabel
1451
- });
1452
-
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
+ };
1453
1596
  //#endregion
1454
1597
  //#region src/components/Slider/context.tsx
1455
1598
  const createSliderStore = (initProps) => {
@@ -1469,7 +1612,7 @@ const createSliderStore = (initProps) => {
1469
1612
  }));
1470
1613
  };
1471
1614
  const SliderContext = (0, react.createContext)(null);
1472
- function SliderProvider({ children,...props }) {
1615
+ function SliderProvider({ children, ...props }) {
1473
1616
  const storeRef = (0, react.useRef)(null);
1474
1617
  if (!storeRef.current) storeRef.current = createSliderStore(props);
1475
1618
  (0, react.useEffect)(() => {
@@ -1481,24 +1624,21 @@ function SliderProvider({ children,...props }) {
1481
1624
  children
1482
1625
  });
1483
1626
  }
1484
- /** @category Slider */
1485
1627
  function useSliderContext(selector) {
1486
1628
  const store = (0, react.useContext)(SliderContext);
1487
1629
  if (!store) throw new Error("Missing SliderContext.Provider in the tree");
1488
1630
  return (0, zustand.useStore)(store, selector);
1489
1631
  }
1490
-
1491
1632
  //#endregion
1492
1633
  //#region src/components/Slider/ScaleOption.tsx
1493
- /** @category Slider */
1494
- 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 }) {
1495
1635
  const min = useSliderContext((s) => s.min);
1496
1636
  const max = useSliderContext((s) => s.max);
1497
1637
  const skew = useSliderContext((s) => s.skew);
1498
1638
  const vertical = useSliderContext((s) => s.vertical);
1499
1639
  const reverse = useSliderContext((s) => s.reverse);
1500
- const calcPercent = (0, react.useCallback)((rawValue$5) => {
1501
- 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);
1502
1642
  }, [
1503
1643
  vertical,
1504
1644
  reverse,
@@ -1535,7 +1675,6 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
1535
1675
  })]
1536
1676
  });
1537
1677
  }
1538
-
1539
1678
  //#endregion
1540
1679
  //#region src/components/Slider/type.ts
1541
1680
  function generateOptionsList(options, min, max, step) {
@@ -1543,7 +1682,7 @@ function generateOptionsList(options, min, max, step) {
1543
1682
  const per = options[0] == "step" ? step : options[0];
1544
1683
  const count = Math.floor(max / per) - Math.ceil(min / per) + 1;
1545
1684
  for (let i = 0; i < count; i++) {
1546
- 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);
1547
1686
  optionsList.push({
1548
1687
  value,
1549
1688
  type: options[1]
@@ -1551,18 +1690,16 @@ function generateOptionsList(options, min, max, step) {
1551
1690
  }
1552
1691
  return optionsList;
1553
1692
  }
1554
-
1555
1693
  //#endregion
1556
1694
  //#region src/components/Slider/Scale.tsx
1557
- /** @category Slider */
1558
- function Scale({ gap = 6, options, children, className, style,...props }) {
1695
+ function Scale({ gap = 6, options, children, className, style, ...props }) {
1559
1696
  const min = useSliderContext((s) => s.min);
1560
1697
  const max = useSliderContext((s) => s.max);
1561
1698
  const step = useSliderContext((s) => s.step);
1562
1699
  const vertical = useSliderContext((s) => s.vertical);
1563
1700
  const reverse = useSliderContext((s) => s.reverse);
1564
1701
  const optionsList = options ? generateOptionsList(options, min, max, step) : [];
1565
- if ((0, __tremolo_ui_functions.xor)(vertical, reverse)) optionsList.reverse();
1702
+ if ((0, _tremolo_ui_functions.xor)(vertical, reverse)) optionsList.reverse();
1566
1703
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1567
1704
  className: (0, clsx.default)("tremolo-slider-scale", className),
1568
1705
  style: {
@@ -1579,13 +1716,7 @@ function Scale({ gap = 6, options, children, className, style,...props }) {
1579
1716
  }) : children
1580
1717
  });
1581
1718
  }
1582
-
1583
- //#endregion
1584
- //#region src/components/Slider/Thumb.tsx
1585
- /** @category Slider */
1586
- const defaultThumbSize = 22;
1587
- /** @category Slider */
1588
- const Thumb$1 = (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) => {
1589
1720
  const wrapperRef = (0, react.useRef)(null);
1590
1721
  const vertical = useSliderContext((s) => s.vertical);
1591
1722
  const disabled = useSliderContext((s) => s.disabled);
@@ -1621,13 +1752,7 @@ const Thumb$1 = (0, react.forwardRef)(({ size, width = defaultThumbSize, height
1621
1752
  })
1622
1753
  });
1623
1754
  });
1624
-
1625
- //#endregion
1626
- //#region src/components/Slider/Track.tsx
1627
- const defaultLength = 140;
1628
- const defaultThickness = 10;
1629
- /** @category Slider */
1630
- function Track({ 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 }) {
1631
1756
  const vertical = useSliderContext((s) => s.vertical);
1632
1757
  const reverse = useSliderContext((s) => s.reverse);
1633
1758
  const disabled = useSliderContext((s) => s.disabled);
@@ -1642,8 +1767,8 @@ function Track({ length = defaultLength, thickness = defaultThickness, active, i
1642
1767
  "data-vertical": vertical,
1643
1768
  style: !defaultStyle ? style : {
1644
1769
  ...colors,
1645
- 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}%)`,
1646
- 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),
1647
1772
  width: !vertical ? length : thickness,
1648
1773
  height: vertical ? length : thickness,
1649
1774
  ...style
@@ -1652,213 +1777,259 @@ function Track({ length = defaultLength, thickness = defaultThickness, active, i
1652
1777
  children: [children, __thumb]
1653
1778
  });
1654
1779
  }
1655
-
1656
1780
  //#endregion
1657
1781
  //#region src/components/Slider/index.tsx
1658
- const SliderImpl = (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) => {
1659
- const trackElementRef = (0, react.useRef)(null);
1660
- const thumbRef = (0, react.useRef)(null);
1661
- const p = (0, __tremolo_ui_functions.toFixed)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) * 100);
1662
- const rev = (0, __tremolo_ui_functions.toFixed)(100 - p);
1663
- const displayReversed = (0, react.useMemo)(() => (0, __tremolo_ui_functions.xor)(vertical, reverse), [vertical, reverse]);
1664
- const percent = displayReversed ? rev : p;
1665
- let trackProps = {};
1666
- let thumbProps = {};
1667
- let scaleComponent = void 0;
1668
- if (children != void 0) react.default.Children.forEach(children, (child) => {
1669
- if (react.default.isValidElement(child)) if (child.type == Thumb$1) thumbProps = child.props;
1670
- else if (child.type == Track) trackProps = child.props;
1671
- else if (child.type == Scale) scaleComponent = child;
1672
- else throw new Error("only <SliderThumb> or <SliderTrack>");
1673
- else throw new Error("children is an invalid element.");
1674
- });
1675
- const onDrag = (0, react.useCallback)((nx, ny) => {
1676
- if (!onChange || readonly) return;
1677
- const n = vertical ? ny : nx;
1678
- 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));
1679
- }, [
1680
- displayReversed,
1681
- max,
1682
- min,
1683
- onChange,
1684
- readonly,
1685
- skew,
1686
- step,
1687
- vertical
1688
- ]);
1689
- const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
1690
- let newValue;
1691
- if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
1692
- else newValue = value + x;
1693
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
1694
- }, [
1695
- max,
1696
- min,
1697
- skew,
1698
- step,
1699
- value
1700
- ]);
1701
- const handleKeyDown = (0, react.useCallback)((event) => {
1702
- if (!keyboard || !onChange || readonly) return;
1703
- const key = event.key;
1704
- if ([
1705
- "ArrowRight",
1706
- "ArrowLeft",
1707
- "ArrowUp",
1708
- "ArrowDown"
1709
- ].includes(key)) {
1710
- event.preventDefault();
1711
- let x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
1712
- if (reverse) x *= -1;
1713
- onChange(updateValueByEvent(keyboard[0], x));
1714
- }
1715
- }, [
1716
- keyboard,
1717
- onChange,
1718
- readonly,
1719
- reverse,
1720
- updateValueByEvent
1721
- ]);
1722
- const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1723
- baseElementRef: trackElementRef,
1724
- onDrag,
1725
- onDragStart: (nx, ny) => {
1726
- if (readonly) return;
1727
- if (bodyNoSelect) addNoSelect();
1728
- thumbRef.current?.focus();
1729
- 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));
1730
- },
1731
- onDragEnd: (nx, ny) => {
1732
- if (readonly) return;
1733
- if (bodyNoSelect) removeNoSelect();
1734
- 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));
1735
- }
1736
- });
1737
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1738
- if (!wheel || !onChange || readonly) return;
1739
- event.preventDefault();
1740
- let x;
1741
- if (!vertical && event.deltaX != 0) x = event.deltaX > 0 ? wheel[1] : -wheel[1];
1742
- else {
1743
- if (event.deltaY == 0) return;
1744
- x = event.deltaY > 0 ? -wheel[1] : wheel[1];
1745
- }
1746
- if (vertical && reverse) x *= -1;
1747
- onChange(updateValueByEvent(wheel[0], x));
1748
- }, { passive: false }, [
1749
- wheel,
1750
- vertical,
1751
- readonly,
1752
- onChange,
1753
- updateValueByEvent
1754
- ]);
1755
- (0, react.useImperativeHandle)(forwardedRef, () => {
1756
- return {
1757
- focus() {
1758
- thumbRef.current?.focus();
1759
- },
1760
- blur() {
1761
- thumbRef.current?.blur();
1762
- }
1782
+ const defaultExternalStyles$1 = {
1783
+ userSelectNone: true,
1784
+ cursor: "pointer"
1785
+ };
1786
+ /**
1787
+ * Customizable slider
1788
+ */
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
1763
1796
  };
1764
- }, []);
1765
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderProvider, {
1766
- min,
1767
- max,
1768
- step,
1769
- skew,
1770
- vertical,
1771
- reverse,
1772
- disabled,
1773
- readonly,
1774
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1775
- className: (0, clsx.default)("tremolo-slider", className),
1776
- ref: (div) => {
1777
- wheelRefCallback(div);
1778
- touchMoveRefCallback(div);
1779
- },
1780
- tabIndex: -1,
1781
- role: "slider",
1782
- "aria-valuenow": value,
1783
- "aria-valuemin": min,
1784
- "aria-valuemax": max,
1785
- "aria-orientation": vertical ? "vertical" : "horizontal",
1786
- "aria-disabled": disabled,
1787
- "aria-readonly": readonly,
1788
- style: {
1789
- margin: `calc(${(0, __tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
1790
- ...style
1791
- },
1792
- onPointerDown: (event) => {
1793
- pointerDownHandler(event);
1794
- onPointerDown?.(event);
1795
- },
1796
- onKeyDown: (event) => {
1797
- handleKeyDown(event);
1798
- onKeyDown?.(event);
1799
- },
1800
- 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);
1801
1865
  thumbRef.current?.focus();
1802
- onFocus?.(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));
1803
1867
  },
1804
- onBlur: (event) => {
1805
- thumbRef.current?.blur();
1806
- onBlur?.(event);
1807
- },
1808
- ...props,
1809
- 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,
1810
1926
  style: {
1811
- display: "flex",
1812
- flexDirection: vertical ? "row" : "column",
1813
- width: !vertical ? "100%" : void 0,
1814
- height: vertical ? "100%" : void 0
1927
+ margin: `calc(${(0, _tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
1928
+ ...style
1815
1929
  },
1816
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1817
- className: "tremolo-slider-track-wrapper",
1818
- ref: trackElementRef,
1819
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Track, {
1820
- __percent: percent,
1821
- __thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb$1, {
1822
- ref: thumbRef,
1930
+ onPointerDown: (event) => {
1931
+ pointerDownHandler(event);
1932
+ onPointerDown?.(event);
1933
+ },
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, {
1823
1958
  __percent: percent,
1824
- ...thumbProps
1825
- }),
1826
- ...trackProps
1827
- })
1828
- }), 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
+ })
1829
1968
  })
1830
- })
1831
- });
1832
- });
1833
- /**
1834
- * Customizable slider
1835
- * @category Slider
1836
- */
1837
- const Slider = Object.assign(SliderImpl, {
1969
+ });
1970
+ }),
1838
1971
  Thumb: Thumb$1,
1839
1972
  Track,
1840
1973
  Scale,
1841
1974
  ScaleOption
1842
- });
1843
-
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
+ }
1844
2007
  //#endregion
1845
2008
  //#region src/components/WheelObserver/index.tsx
1846
- /** @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
+ */
1847
2020
  function WheelObserver(props) {
1848
- const { children, onWheel, as: Component = "div",...attributes } = props;
2021
+ const { as: Component = "div", children, onWheel, ref, ...attributes } = props;
1849
2022
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
1850
- ref: useRefCallbackEvent("wheel", (event) => {
2023
+ ref: composeRefs(ref, useRefCallbackEvent("wheel", (event) => {
1851
2024
  if (onWheel) onWheel(event);
1852
- }, { passive: false }, [onWheel]),
2025
+ }, { passive: false }, [onWheel])),
1853
2026
  ...attributes,
1854
2027
  children
1855
2028
  });
1856
2029
  }
1857
-
1858
2030
  //#endregion
1859
2031
  //#region src/components/XYPad/Area.tsx
1860
- /** @category XYPad */
1861
- function Area({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
2032
+ function Area({ width = 120, height = 120, color, children, className, style, __thumb, ...props }) {
1862
2033
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1863
2034
  className: (0, clsx.default)("tremolo-xy-pad-area", className),
1864
2035
  style: {
@@ -1871,11 +2042,9 @@ function Area({ width = 120, height = 120, color, children, className, style, __
1871
2042
  children: [children, __thumb]
1872
2043
  });
1873
2044
  }
1874
-
1875
2045
  //#endregion
1876
2046
  //#region src/components/XYPad/Thumb.tsx
1877
- /** @category XYPad */
1878
- const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __readonly, __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) => {
1879
2048
  const wrapperRef = (0, react.useRef)(null);
1880
2049
  (0, react.useImperativeHandle)(ref, () => {
1881
2050
  return {
@@ -1909,7 +2078,6 @@ const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, chi
1909
2078
  })
1910
2079
  });
1911
2080
  });
1912
-
1913
2081
  //#endregion
1914
2082
  //#region src/components/XYPad/index.tsx
1915
2083
  const defaultValueOptions = {
@@ -1919,197 +2087,201 @@ const defaultValueOptions = {
1919
2087
  wheel: ["raw", 1],
1920
2088
  keyboard: ["raw", 1]
1921
2089
  };
1922
- const XYPadImpl = (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) => {
1923
- const x = (0, react.useMemo)(() => {
1924
- return {
1925
- ...defaultValueOptions,
1926
- ..._x
1927
- };
1928
- }, [_x]);
1929
- const y = (0, react.useMemo)(() => {
1930
- return {
1931
- ...defaultValueOptions,
1932
- ..._y
2090
+ const defaultExternalStyles = {
2091
+ userSelectNone: true,
2092
+ cursor: "pointer"
2093
+ };
2094
+ /**
2095
+ * Simple XYPad
2096
+ */
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
1933
2117
  };
1934
- }, [_y]);
1935
- const areaElementRef = (0, react.useRef)(null);
1936
- const thumbRef = (0, react.useRef)(null);
1937
- const nx = (0, __tremolo_ui_functions.normalizeValue)(x.value, x.min, x.max, x.skew);
1938
- const ny = (0, __tremolo_ui_functions.normalizeValue)(y.value, y.min, y.max, y.skew);
1939
- const percentX = (0, __tremolo_ui_functions.toFixed)((x.reverse ? 1 - nx : nx) * 100);
1940
- const percentY = (0, __tremolo_ui_functions.toFixed)((y.reverse ? 1 - ny : ny) * 100);
1941
- let areaProps = {};
1942
- let thumbProps = {};
1943
- if (children != void 0) react.default.Children.forEach(children, (child) => {
1944
- if (react.default.isValidElement(child)) if (child.type == Thumb) thumbProps = child.props;
1945
- else if (child.type == Area) areaProps = child.props;
1946
- else throw new Error("only <XYPadThumb> or <XYPadArea>");
1947
- else throw new Error("children is an invalid element.");
1948
- });
1949
- const onDrag = (0, react.useCallback)((nx$1, ny$1) => {
1950
- if (!onChange || readonly) return;
1951
- const vx = (0, __tremolo_ui_functions.rawValue)(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
1952
- const vy = (0, __tremolo_ui_functions.rawValue)(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
1953
- 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));
1954
- }, [
1955
- onChange,
1956
- readonly,
1957
- x.max,
1958
- x.min,
1959
- x.reverse,
1960
- x.skew,
1961
- x.step,
1962
- y.max,
1963
- y.min,
1964
- y.reverse,
1965
- y.skew,
1966
- y.step
1967
- ]);
1968
- const updateValueByEvent = (0, react.useCallback)((eventType, target, x$1) => {
1969
- let v;
1970
- 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);
1971
- else v = target.value + x$1;
1972
- return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, target.step), target.min, target.max);
1973
- }, []);
1974
- const handleKeyDown = (0, react.useCallback)((event) => {
1975
- if (!onChange || readonly) return;
1976
- const key = event.key;
1977
- if ([
1978
- "ArrowRight",
1979
- "ArrowLeft",
1980
- "ArrowUp",
1981
- "ArrowDown"
1982
- ].includes(key)) {
1983
- const isHorizontal = key == "ArrowRight" || key == "ArrowLeft";
1984
- const target = isHorizontal ? x : y;
1985
- 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;
1986
2207
  event.preventDefault();
1987
- let delta = target.keyboard[1];
1988
- if (key == "ArrowLeft" || key == "ArrowUp") delta *= -1;
2208
+ let delta = target.wheel[1];
2209
+ if (event.deltaY < 0) delta *= -1;
1989
2210
  if (target.reverse) delta *= -1;
1990
- const newValue = updateValueByEvent(target.keyboard[0], target, delta);
1991
- if (isHorizontal) onChange(newValue, y.value);
2211
+ const newValue = updateValueByEvent(target.wheel[0], target, delta);
2212
+ if (event.shiftKey) onChange(newValue, y.value);
1992
2213
  else onChange(x.value, newValue);
1993
- }
1994
- }, [
1995
- onChange,
1996
- readonly,
1997
- x,
1998
- y,
1999
- updateValueByEvent
2000
- ]);
2001
- const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
2002
- baseElementRef: areaElementRef,
2003
- onDrag,
2004
- onDragStart: (nx$1, ny$1) => {
2005
- if (readonly) return;
2006
- if (bodyNoSelect) addNoSelect();
2007
- thumbRef.current?.focus();
2008
- 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);
2009
- 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);
2010
- onDragStart?.(valueX, valueY);
2011
- },
2012
- onDragEnd: (nx$1, ny$1) => {
2013
- if (readonly) return;
2014
- if (bodyNoSelect) removeNoSelect();
2015
- 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);
2016
- 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);
2017
- onDragEnd?.(valueX, valueY);
2018
- }
2019
- });
2020
- const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
2021
- if (!onChange || readonly) return;
2022
- const target = event.shiftKey ? x : y;
2023
- if (!target.wheel) return;
2024
- event.preventDefault();
2025
- let delta = target.wheel[1];
2026
- if (event.deltaY < 0) delta *= -1;
2027
- if (target.reverse) delta *= -1;
2028
- const newValue = updateValueByEvent(target.wheel[0], target, delta);
2029
- if (event.shiftKey) onChange(newValue, y.value);
2030
- else onChange(x.value, newValue);
2031
- }, { passive: false }, [
2032
- x,
2033
- y,
2034
- onChange,
2035
- readonly,
2036
- updateValueByEvent
2037
- ]);
2038
- (0, react.useImperativeHandle)(forwardedRef, () => {
2039
- return {
2040
- 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) => {
2041
2253
  thumbRef.current?.focus();
2254
+ onFocus?.(event);
2042
2255
  },
2043
- blur() {
2256
+ onBlur: (event) => {
2044
2257
  thumbRef.current?.blur();
2045
- }
2046
- };
2047
- }, []);
2048
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2049
- className: (0, clsx.default)("tremolo-xy-pad", className),
2050
- ref: (div) => {
2051
- wheelRefCallback(div);
2052
- touchMoveRefCallback(div);
2053
- },
2054
- tabIndex: -1,
2055
- "aria-disabled": disabled,
2056
- "aria-readonly": readonly,
2057
- style: {
2058
- margin: `calc(${(0, __tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
2059
- cursor: readonly ? "not-allowed" : "pointer",
2060
- WebkitTapHighlightColor: "transparent",
2061
- ...style
2062
- },
2063
- onPointerDown: (event) => {
2064
- pointerDownHandler(event);
2065
- onPointerDown?.(event);
2066
- },
2067
- onKeyDown: (event) => {
2068
- handleKeyDown(event);
2069
- onKeyDown?.(event);
2070
- },
2071
- onFocus: (event) => {
2072
- thumbRef.current?.focus();
2073
- onFocus?.(event);
2074
- },
2075
- onBlur: (event) => {
2076
- thumbRef.current?.blur();
2077
- onBlur?.(event);
2078
- },
2079
- ...props,
2080
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2081
- className: "tremolo-xy-pad-area-wrapper",
2082
- ref: areaElementRef,
2083
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Area, {
2084
- __thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb, {
2085
- ref: thumbRef,
2086
- __disabled: disabled,
2087
- __readonly: readonly,
2088
- __css: {
2089
- top: `${percentY}%`,
2090
- left: `${percentX}%`
2091
- },
2092
- ...thumbProps
2093
- }),
2094
- ...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
+ })
2095
2277
  })
2096
- })
2097
- });
2098
- });
2099
- /**
2100
- * Simple XYPad
2101
- * @category XYPad
2102
- */
2103
- const XYPad = Object.assign(XYPadImpl, {
2278
+ });
2279
+ }),
2104
2280
  Thumb,
2105
2281
  Area
2106
- });
2107
-
2282
+ };
2108
2283
  //#endregion
2109
2284
  //#region src/hooks/useAnimationFrame.ts
2110
- /**
2111
- * @category hooks
2112
- */
2113
2285
  function useAnimationFrame(callback = () => {}, deps = []) {
2114
2286
  const reqIdRef = (0, react.useRef)(-1);
2115
2287
  const loop = (0, react.useCallback)(() => {
@@ -2121,7 +2293,6 @@ function useAnimationFrame(callback = () => {}, deps = []) {
2121
2293
  return () => cancelAnimationFrame(reqIdRef.current);
2122
2294
  }, [loop]);
2123
2295
  }
2124
-
2125
2296
  //#endregion
2126
2297
  //#region src/hooks/useMIDIAccess.ts
2127
2298
  /** @private */
@@ -2130,7 +2301,6 @@ const PERMISSION_DENIED = "PERMISSION_DENIED";
2130
2301
  const NOT_SUPPORTED = "NOT_SUPPORTED";
2131
2302
  /**
2132
2303
  * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
2133
- * @category hooks
2134
2304
  */
2135
2305
  function useMIDIAccess(requestOnMount = true) {
2136
2306
  const [midiAccess, setMidiAccess] = (0, react.useState)(null);
@@ -2153,12 +2323,10 @@ function useMIDIAccess(requestOnMount = true) {
2153
2323
  error
2154
2324
  };
2155
2325
  }
2156
-
2157
2326
  //#endregion
2158
2327
  //#region src/hooks/useMIDIMessage.ts
2159
2328
  /**
2160
2329
  * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
2161
- * @category hooks
2162
2330
  */
2163
2331
  function useMIDIMessage(midiAccess, onMIDIMessage) {
2164
2332
  (0, react.useEffect)(() => {
@@ -2169,7 +2337,6 @@ function useMIDIMessage(midiAccess, onMIDIMessage) {
2169
2337
  };
2170
2338
  }, [midiAccess, onMIDIMessage]);
2171
2339
  }
2172
-
2173
2340
  //#endregion
2174
2341
  //#region src/hooks/useMIDIInput.ts
2175
2342
  const MIDI_EVENT_TO_NUMBER = {
@@ -2179,7 +2346,6 @@ const MIDI_EVENT_TO_NUMBER = {
2179
2346
  };
2180
2347
  /**
2181
2348
  * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
2182
- * @category hooks
2183
2349
  */
2184
2350
  function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
2185
2351
  useMIDIMessage(midiAccess, (0, react.useCallback)((event) => {
@@ -2194,20 +2360,20 @@ function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEven
2194
2360
  onPitchBendEvent
2195
2361
  ]));
2196
2362
  }
2197
-
2198
2363
  //#endregion
2199
2364
  exports.AnimationCanvas = AnimationCanvas;
2200
- exports.AnimationKnob = AnimationKnob;
2201
2365
  exports.DragObserver = DragObserver;
2202
2366
  exports.Knob = Knob;
2203
2367
  exports.NOT_SUPPORTED = NOT_SUPPORTED;
2204
2368
  exports.NumberInput = NumberInput;
2205
2369
  exports.PERMISSION_DENIED = PERMISSION_DENIED;
2206
2370
  exports.Piano = Piano;
2371
+ exports.PointsEditor = PointsEditor;
2207
2372
  exports.SHORTCUTS = SHORTCUTS;
2208
2373
  exports.Slider = Slider;
2209
2374
  exports.WheelObserver = WheelObserver;
2210
2375
  exports.XYPad = XYPad;
2376
+ exports.clampPoint = clampPoint;
2211
2377
  exports.getNoteRangeArray = getNoteRangeArray;
2212
2378
  exports.useAnimationFrame = useAnimationFrame;
2213
2379
  exports.useDrag = useDrag;
@@ -2219,4 +2385,5 @@ exports.useMIDIAccess = useMIDIAccess;
2219
2385
  exports.useMIDIInput = useMIDIInput;
2220
2386
  exports.useMIDIMessage = useMIDIMessage;
2221
2387
  exports.useSliderContext = useSliderContext;
2388
+
2222
2389
  //# sourceMappingURL=index.cjs.map