@tremolo-ui/react 0.2.0 → 0.3.0

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