@tremolo-ui/react 0.1.2 → 0.1.4

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 (159) hide show
  1. package/dist/index.cjs +2082 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/{styles/index.css → index.css} +3 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.d.cts +827 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.ts +827 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +2030 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +14 -14
  12. package/dist/cjs/index.js +0 -2041
  13. package/dist/cjs/index.js.map +0 -1
  14. package/dist/cjs/types/components/AnimationCanvas/canvas.d.ts +0 -6
  15. package/dist/cjs/types/components/AnimationCanvas/canvas.d.ts.map +0 -1
  16. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +0 -51
  17. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +0 -1
  18. package/dist/cjs/types/components/AnimationKnob/index.d.ts +0 -62
  19. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +0 -1
  20. package/dist/cjs/types/components/DragObserver/index.d.ts +0 -21
  21. package/dist/cjs/types/components/DragObserver/index.d.ts.map +0 -1
  22. package/dist/cjs/types/components/Knob/index.d.ts +0 -71
  23. package/dist/cjs/types/components/Knob/index.d.ts.map +0 -1
  24. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +0 -9
  25. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +0 -1
  26. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +0 -9
  27. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +0 -1
  28. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts +0 -18
  29. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts.map +0 -1
  30. package/dist/cjs/types/components/NumberInput/Stepper.d.ts +0 -10
  31. package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +0 -1
  32. package/dist/cjs/types/components/NumberInput/context.d.ts +0 -28
  33. package/dist/cjs/types/components/NumberInput/context.d.ts.map +0 -1
  34. package/dist/cjs/types/components/NumberInput/index.d.ts +0 -69
  35. package/dist/cjs/types/components/NumberInput/index.d.ts.map +0 -1
  36. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +0 -17
  37. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +0 -1
  38. package/dist/cjs/types/components/Piano/context.d.ts +0 -24
  39. package/dist/cjs/types/components/Piano/context.d.ts.map +0 -1
  40. package/dist/cjs/types/components/Piano/index.d.ts +0 -39
  41. package/dist/cjs/types/components/Piano/index.d.ts.map +0 -1
  42. package/dist/cjs/types/components/Piano/key.d.ts +0 -37
  43. package/dist/cjs/types/components/Piano/key.d.ts.map +0 -1
  44. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +0 -14
  45. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +0 -1
  46. package/dist/cjs/types/components/Slider/Scale.d.ts +0 -11
  47. package/dist/cjs/types/components/Slider/Scale.d.ts.map +0 -1
  48. package/dist/cjs/types/components/Slider/ScaleOption.d.ts +0 -27
  49. package/dist/cjs/types/components/Slider/ScaleOption.d.ts.map +0 -1
  50. package/dist/cjs/types/components/Slider/Thumb.d.ts +0 -23
  51. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +0 -1
  52. package/dist/cjs/types/components/Slider/Track.d.ts +0 -21
  53. package/dist/cjs/types/components/Slider/Track.d.ts.map +0 -1
  54. package/dist/cjs/types/components/Slider/context.d.ts +0 -16
  55. package/dist/cjs/types/components/Slider/context.d.ts.map +0 -1
  56. package/dist/cjs/types/components/Slider/index.d.ts +0 -55
  57. package/dist/cjs/types/components/Slider/index.d.ts.map +0 -1
  58. package/dist/cjs/types/components/WheelObserver/index.d.ts +0 -25
  59. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +0 -1
  60. package/dist/cjs/types/components/XYPad/Area.d.ts +0 -15
  61. package/dist/cjs/types/components/XYPad/Area.d.ts.map +0 -1
  62. package/dist/cjs/types/components/XYPad/Thumb.d.ts +0 -23
  63. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +0 -1
  64. package/dist/cjs/types/components/XYPad/index.d.ts +0 -48
  65. package/dist/cjs/types/components/XYPad/index.d.ts.map +0 -1
  66. package/dist/cjs/types/components/_util/index.d.ts +0 -4
  67. package/dist/cjs/types/components/_util/index.d.ts.map +0 -1
  68. package/dist/cjs/types/hooks/useAnimationFrame.d.ts +0 -3
  69. package/dist/cjs/types/hooks/useAnimationFrame.d.ts.map +0 -1
  70. package/dist/cjs/types/hooks/useCallbackRef.d.ts +0 -6
  71. package/dist/cjs/types/hooks/useCallbackRef.d.ts.map +0 -1
  72. package/dist/cjs/types/hooks/useDrag.d.ts +0 -14
  73. package/dist/cjs/types/hooks/useDrag.d.ts.map +0 -1
  74. package/dist/cjs/types/hooks/useDragWithElement.d.ts +0 -15
  75. package/dist/cjs/types/hooks/useDragWithElement.d.ts.map +0 -1
  76. package/dist/cjs/types/hooks/useEventListener.d.ts +0 -7
  77. package/dist/cjs/types/hooks/useEventListener.d.ts.map +0 -1
  78. package/dist/cjs/types/hooks/useInterval.d.ts +0 -2
  79. package/dist/cjs/types/hooks/useInterval.d.ts.map +0 -1
  80. package/dist/cjs/types/hooks/useLongPress.d.ts +0 -2
  81. package/dist/cjs/types/hooks/useLongPress.d.ts.map +0 -1
  82. package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts +0 -2
  83. package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts.map +0 -1
  84. package/dist/cjs/types/index.d.ts +0 -22
  85. package/dist/cjs/types/index.d.ts.map +0 -1
  86. package/dist/esm/index.js +0 -2010
  87. package/dist/esm/index.js.map +0 -1
  88. package/dist/esm/types/components/AnimationCanvas/canvas.d.ts +0 -6
  89. package/dist/esm/types/components/AnimationCanvas/canvas.d.ts.map +0 -1
  90. package/dist/esm/types/components/AnimationCanvas/index.d.ts +0 -51
  91. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +0 -1
  92. package/dist/esm/types/components/AnimationKnob/index.d.ts +0 -62
  93. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +0 -1
  94. package/dist/esm/types/components/DragObserver/index.d.ts +0 -21
  95. package/dist/esm/types/components/DragObserver/index.d.ts.map +0 -1
  96. package/dist/esm/types/components/Knob/index.d.ts +0 -71
  97. package/dist/esm/types/components/Knob/index.d.ts.map +0 -1
  98. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +0 -9
  99. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +0 -1
  100. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +0 -9
  101. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +0 -1
  102. package/dist/esm/types/components/NumberInput/InternalInput.d.ts +0 -18
  103. package/dist/esm/types/components/NumberInput/InternalInput.d.ts.map +0 -1
  104. package/dist/esm/types/components/NumberInput/Stepper.d.ts +0 -10
  105. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +0 -1
  106. package/dist/esm/types/components/NumberInput/context.d.ts +0 -28
  107. package/dist/esm/types/components/NumberInput/context.d.ts.map +0 -1
  108. package/dist/esm/types/components/NumberInput/index.d.ts +0 -69
  109. package/dist/esm/types/components/NumberInput/index.d.ts.map +0 -1
  110. package/dist/esm/types/components/Piano/KeyLabel.d.ts +0 -17
  111. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +0 -1
  112. package/dist/esm/types/components/Piano/context.d.ts +0 -24
  113. package/dist/esm/types/components/Piano/context.d.ts.map +0 -1
  114. package/dist/esm/types/components/Piano/index.d.ts +0 -39
  115. package/dist/esm/types/components/Piano/index.d.ts.map +0 -1
  116. package/dist/esm/types/components/Piano/key.d.ts +0 -37
  117. package/dist/esm/types/components/Piano/key.d.ts.map +0 -1
  118. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +0 -14
  119. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +0 -1
  120. package/dist/esm/types/components/Slider/Scale.d.ts +0 -11
  121. package/dist/esm/types/components/Slider/Scale.d.ts.map +0 -1
  122. package/dist/esm/types/components/Slider/ScaleOption.d.ts +0 -27
  123. package/dist/esm/types/components/Slider/ScaleOption.d.ts.map +0 -1
  124. package/dist/esm/types/components/Slider/Thumb.d.ts +0 -23
  125. package/dist/esm/types/components/Slider/Thumb.d.ts.map +0 -1
  126. package/dist/esm/types/components/Slider/Track.d.ts +0 -21
  127. package/dist/esm/types/components/Slider/Track.d.ts.map +0 -1
  128. package/dist/esm/types/components/Slider/context.d.ts +0 -16
  129. package/dist/esm/types/components/Slider/context.d.ts.map +0 -1
  130. package/dist/esm/types/components/Slider/index.d.ts +0 -55
  131. package/dist/esm/types/components/Slider/index.d.ts.map +0 -1
  132. package/dist/esm/types/components/WheelObserver/index.d.ts +0 -25
  133. package/dist/esm/types/components/WheelObserver/index.d.ts.map +0 -1
  134. package/dist/esm/types/components/XYPad/Area.d.ts +0 -15
  135. package/dist/esm/types/components/XYPad/Area.d.ts.map +0 -1
  136. package/dist/esm/types/components/XYPad/Thumb.d.ts +0 -23
  137. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +0 -1
  138. package/dist/esm/types/components/XYPad/index.d.ts +0 -48
  139. package/dist/esm/types/components/XYPad/index.d.ts.map +0 -1
  140. package/dist/esm/types/components/_util/index.d.ts +0 -4
  141. package/dist/esm/types/components/_util/index.d.ts.map +0 -1
  142. package/dist/esm/types/hooks/useAnimationFrame.d.ts +0 -3
  143. package/dist/esm/types/hooks/useAnimationFrame.d.ts.map +0 -1
  144. package/dist/esm/types/hooks/useCallbackRef.d.ts +0 -6
  145. package/dist/esm/types/hooks/useCallbackRef.d.ts.map +0 -1
  146. package/dist/esm/types/hooks/useDrag.d.ts +0 -14
  147. package/dist/esm/types/hooks/useDrag.d.ts.map +0 -1
  148. package/dist/esm/types/hooks/useDragWithElement.d.ts +0 -15
  149. package/dist/esm/types/hooks/useDragWithElement.d.ts.map +0 -1
  150. package/dist/esm/types/hooks/useEventListener.d.ts +0 -7
  151. package/dist/esm/types/hooks/useEventListener.d.ts.map +0 -1
  152. package/dist/esm/types/hooks/useInterval.d.ts +0 -2
  153. package/dist/esm/types/hooks/useInterval.d.ts.map +0 -1
  154. package/dist/esm/types/hooks/useLongPress.d.ts +0 -2
  155. package/dist/esm/types/hooks/useLongPress.d.ts.map +0 -1
  156. package/dist/esm/types/hooks/useRefCallbackEvent.d.ts +0 -2
  157. package/dist/esm/types/hooks/useRefCallbackEvent.d.ts.map +0 -1
  158. package/dist/esm/types/index.d.ts +0 -22
  159. package/dist/esm/types/index.d.ts.map +0 -1
package/dist/index.js ADDED
@@ -0,0 +1,2030 @@
1
+ import clsx from "clsx";
2
+ import React, { createContext, createRef, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useInsertionEffect, useMemo, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { clamp, isBlackKey, isWhiteKey, normalizeValue, noteKey, noteKeys, radian, rawValue, stepValue, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
5
+ import { createStore, useStore } from "zustand";
6
+ import { parseValue } from "@tremolo-ui/functions/NumberInput";
7
+ import { generateOptionsList } from "@tremolo-ui/functions/Slider";
8
+
9
+ //#region src/components/AnimationCanvas/canvas.ts
10
+ const drawingState = [
11
+ "strokeStyle",
12
+ "fillStyle",
13
+ "globalAlpha",
14
+ "lineWidth",
15
+ "lineCap",
16
+ "lineJoin",
17
+ "miterLimit",
18
+ "lineDashOffset",
19
+ "shadowOffsetX",
20
+ "shadowOffsetY",
21
+ "shadowBlur",
22
+ "shadowColor",
23
+ "globalCompositeOperation",
24
+ "font",
25
+ "textAlign",
26
+ "textBaseline",
27
+ "direction",
28
+ "imageSmoothingEnabled"
29
+ ];
30
+
31
+ //#endregion
32
+ //#region src/components/AnimationCanvas/index.tsx
33
+ function setDprConfig(canvas, context, width, height, dpr) {
34
+ canvas.width = width * dpr;
35
+ canvas.height = height * dpr;
36
+ context.setTransform(1, 0, 0, 1, 0, 0);
37
+ context.scale(dpr, dpr);
38
+ canvas.style.width = `${width}px`;
39
+ canvas.style.height = `${height}px`;
40
+ }
41
+ function AnimationCanvas({ options, init, draw, width = 100, height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(),...props }) {
42
+ const canvasRef = useRef(null);
43
+ const memoCanvasRef = useRef(null);
44
+ const reqIdRef = useRef(-1);
45
+ const widthRef = useRef(0);
46
+ const heightRef = useRef(0);
47
+ const deltaMemoRef = useRef(-1);
48
+ const startTimeRef = useRef(-1);
49
+ const loop = useCallback((context, width$1, height$1, count) => {
50
+ const now = performance.now();
51
+ const deltaTime = now - deltaMemoRef.current;
52
+ const elapsedTime = now - startTimeRef.current;
53
+ deltaMemoRef.current = now;
54
+ reqIdRef.current = requestAnimationFrame(() => loop(context, width$1, height$1, count + 1));
55
+ draw(context, {
56
+ width: width$1.current,
57
+ height: height$1.current,
58
+ count: count + 1,
59
+ deltaTime,
60
+ elapsedTime,
61
+ fps: 1e3 / deltaTime
62
+ });
63
+ }, [draw]);
64
+ useEffect(() => {
65
+ if (!canvasRef.current) return;
66
+ const canvas = canvasRef.current;
67
+ const context = canvas.getContext("2d", options);
68
+ if (!context) throw new Error("Cannot get canvas context.");
69
+ const dpr = globalThis.devicePixelRatio;
70
+ if (relativeSize) {
71
+ const parent = canvas.parentElement;
72
+ if (!parent) throw new Error("Canvas doesn't have a parent element.");
73
+ const memoCanvas = memoCanvasRef.current;
74
+ const memoContext = memoCanvas?.getContext("2d", options);
75
+ const ro = new ResizeObserver((entries) => {
76
+ for (const entry of entries) {
77
+ const contextMemo = {};
78
+ for (const prop of drawingState) contextMemo[prop] = context[prop];
79
+ const w$1 = entry.contentRect.width;
80
+ const h$1 = entry.contentRect.height;
81
+ if (reduceFlickering && memoCanvas && memoContext) {
82
+ memoCanvas.width = w$1 * dpr;
83
+ memoCanvas.height = h$1 * dpr;
84
+ memoContext.scale(1 / dpr, 1 / dpr);
85
+ if (canvas.width > 0 && canvas.height > 0) memoContext.drawImage(canvas, 0, 0);
86
+ }
87
+ setDprConfig(canvas, context, w$1, h$1, dpr);
88
+ widthRef.current = w$1;
89
+ heightRef.current = h$1;
90
+ for (const prop of drawingState) context[prop] = contextMemo[prop];
91
+ if (reduceFlickering && memoContext && memoCanvas && memoCanvas.width > 0 && memoCanvas.height > 0) context.drawImage(memoContext.canvas, 0, 0);
92
+ }
93
+ });
94
+ ro.observe(parent);
95
+ const w = parent.clientWidth;
96
+ const h = parent.clientHeight;
97
+ setDprConfig(canvas, context, w, h, dpr);
98
+ if (init) init(context, {
99
+ width: widthRef.current,
100
+ height: heightRef.current
101
+ });
102
+ const now = performance.now();
103
+ deltaMemoRef.current = now;
104
+ startTimeRef.current = now;
105
+ loop(context, widthRef, heightRef, -1);
106
+ return () => {
107
+ if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
108
+ ro.disconnect();
109
+ };
110
+ } else {
111
+ const rect = canvas.getBoundingClientRect();
112
+ setDprConfig(canvas, context, rect.width, rect.height, dpr);
113
+ widthRef.current = rect.width;
114
+ heightRef.current = rect.height;
115
+ if (init) init(context, {
116
+ width: widthRef.current,
117
+ height: heightRef.current
118
+ });
119
+ const now = performance.now();
120
+ deltaMemoRef.current = now;
121
+ startTimeRef.current = now;
122
+ loop(context, widthRef, heightRef, -1);
123
+ return () => {
124
+ if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
125
+ };
126
+ }
127
+ }, [
128
+ loop,
129
+ init,
130
+ options,
131
+ reduceFlickering,
132
+ relativeSize
133
+ ]);
134
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("canvas", {
135
+ className: clsx("tremolo-animation-canvas", className),
136
+ width: relativeSize ? 0 : width,
137
+ height: relativeSize ? 0 : height,
138
+ ref: canvasRef,
139
+ onContextMenu,
140
+ ...props
141
+ }), relativeSize && reduceFlickering && /* @__PURE__ */ jsx("canvas", {
142
+ style: { display: "none" },
143
+ ref: memoCanvasRef
144
+ })] });
145
+ }
146
+
147
+ //#endregion
148
+ //#region src/hooks/useCallbackRef.ts
149
+ /**
150
+ * This hook is user-land implementation of the experimental `useEffectEvent` hook.
151
+ * React docs: https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event
152
+ */
153
+ function useCallbackRef(callback, deps = []) {
154
+ const callbackRef = useRef(() => {
155
+ throw new Error("Cannot call an event handler while rendering.");
156
+ });
157
+ useInsertionEffect(() => {
158
+ callbackRef.current = callback;
159
+ });
160
+ return useCallback((...args) => callbackRef.current?.(...args), deps);
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/hooks/useEventListener.ts
165
+ function useEventListener(target, event, handler, options) {
166
+ const listener = useCallbackRef(handler);
167
+ useEffect(() => {
168
+ const node = typeof target === "function" ? target() : target ?? document;
169
+ if (!handler || !node) return;
170
+ node.addEventListener(event, listener, options);
171
+ return () => {
172
+ node.removeEventListener(event, listener, options);
173
+ };
174
+ }, [
175
+ event,
176
+ target,
177
+ options,
178
+ listener,
179
+ handler
180
+ ]);
181
+ return () => {
182
+ const node = typeof target === "function" ? target() : target ?? document;
183
+ node?.removeEventListener(event, listener, options);
184
+ };
185
+ }
186
+
187
+ //#endregion
188
+ //#region src/hooks/useRefCallbackEvent.ts
189
+ function useRefCallbackEvent(event, handler, options, deps = []) {
190
+ return useMemo(() => {
191
+ let cleanup;
192
+ return (node) => {
193
+ if (!node) {
194
+ cleanup?.();
195
+ return;
196
+ }
197
+ node.addEventListener(event, handler, options);
198
+ cleanup = () => {
199
+ node.removeEventListener(event, handler, options);
200
+ };
201
+ };
202
+ }, deps);
203
+ }
204
+
205
+ //#endregion
206
+ //#region src/hooks/useDrag.ts
207
+ /**
208
+ * @returns [refCallback, pointerDownHandler]
209
+ */
210
+ function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
211
+ const dragOffsetX = useRef(void 0);
212
+ const dragOffsetY = useRef(void 0);
213
+ const dragStartX = useRef(0);
214
+ const dragStartY = useRef(0);
215
+ const handleDrag = useCallback((event, first = false) => {
216
+ const isTouch = event instanceof TouchEvent;
217
+ if (isTouch && event.cancelable) event.preventDefault();
218
+ const screenX = isTouch ? event.touches[0].screenX : event.screenX;
219
+ const screenY = isTouch ? event.touches[0].screenY : event.screenY;
220
+ let deltaX = 0;
221
+ let deltaY = 0;
222
+ if (first) {
223
+ dragStartX.current = screenX;
224
+ dragStartY.current = screenY;
225
+ }
226
+ if (dragOffsetX.current) {
227
+ deltaX = screenX - dragOffsetX.current;
228
+ dragOffsetX.current = screenX;
229
+ }
230
+ if (dragOffsetY.current) {
231
+ deltaY = screenY - dragOffsetY.current;
232
+ dragOffsetY.current = screenY;
233
+ }
234
+ if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return;
235
+ onDrag(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
236
+ }, [threshold, onDrag]);
237
+ const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
238
+ const pointerDownHandler = useCallback((event) => {
239
+ dragOffsetX.current = event.screenX;
240
+ dragOffsetY.current = event.screenY;
241
+ handleDrag(event, true);
242
+ onDragStart?.();
243
+ }, [handleDrag, onDragStart]);
244
+ useEventListener(globalThis.window, "mousemove", handleDrag);
245
+ useEventListener(globalThis.window, "pointerup", () => {
246
+ dragOffsetX.current = void 0;
247
+ dragOffsetY.current = void 0;
248
+ onDragEnd?.();
249
+ });
250
+ return [refHandler, pointerDownHandler];
251
+ }
252
+
253
+ //#endregion
254
+ //#region src/components/_util/index.ts
255
+ function addNoSelect() {
256
+ document.body.classList.add("tremolo-no-select");
257
+ }
258
+ function removeNoSelect() {
259
+ document.body.classList.remove("tremolo-no-select");
260
+ }
261
+
262
+ //#endregion
263
+ //#region src/components/AnimationKnob/index.tsx
264
+ /**
265
+ * Animatable rotary knob.
266
+ * @category AnimationKnob
267
+ */
268
+ const AnimationKnob = 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) => {
269
+ const valueRef = useRef(0);
270
+ const elmRef = useRef(null);
271
+ const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
272
+ const onDrag = useCallback((_x, y) => {
273
+ if (!onChange) return;
274
+ const v = rawValue(valueRef.current - y / 100, min, max, skew);
275
+ const v2 = clamp(stepValue(v, step), min, max);
276
+ onChange(v2);
277
+ }, [
278
+ max,
279
+ min,
280
+ onChange,
281
+ skew,
282
+ step
283
+ ]);
284
+ const handleKeyDown = useCallback((event) => {
285
+ if (!keyboard || !onChange) return;
286
+ const key = event.key;
287
+ if ([
288
+ "ArrowRight",
289
+ "ArrowLeft",
290
+ "ArrowUp",
291
+ "ArrowDown"
292
+ ].includes(key)) {
293
+ event.preventDefault();
294
+ const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
295
+ let v;
296
+ if (keyboard[0] == "normalized") {
297
+ const n = normalizeValue(value, min, max, skew);
298
+ v = rawValue(n + x, min, max, skew);
299
+ } else v = value + x;
300
+ onChange(clamp(stepValue(v, step), min, max));
301
+ }
302
+ }, [
303
+ value,
304
+ min,
305
+ max,
306
+ step,
307
+ skew,
308
+ keyboard,
309
+ onChange
310
+ ]);
311
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag({
312
+ onDrag,
313
+ onDragStart: () => {
314
+ valueRef.current = normalizeValue(value, min, max, skew);
315
+ if (bodyNoSelect) addNoSelect();
316
+ },
317
+ onDragEnd: () => {
318
+ if (bodyNoSelect) removeNoSelect();
319
+ }
320
+ });
321
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
322
+ if (!wheel) return;
323
+ event.preventDefault();
324
+ const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
325
+ let v;
326
+ if (wheel[0] == "normalized") {
327
+ const n = normalizeValue(value, min, max, skew);
328
+ v = rawValue(n + x, min, max, skew);
329
+ } else v = value + x;
330
+ if (onChange) onChange(clamp(stepValue(v, step), min, max));
331
+ }, { passive: false }, [
332
+ wheel,
333
+ value,
334
+ min,
335
+ max,
336
+ skew,
337
+ step,
338
+ onChange
339
+ ]);
340
+ useImperativeHandle(ref, () => {
341
+ return {
342
+ focus() {
343
+ elmRef.current?.focus();
344
+ },
345
+ blur() {
346
+ elmRef.current?.blur();
347
+ }
348
+ };
349
+ }, []);
350
+ return /* @__PURE__ */ jsx("div", {
351
+ className: clsx("tremolo-animation-knob", className),
352
+ ref: (div) => {
353
+ elmRef.current = div;
354
+ wheelRefCallback(div);
355
+ touchMoveRefCallback(div);
356
+ },
357
+ role: "slider",
358
+ tabIndex: 0,
359
+ "aria-valuenow": value,
360
+ "aria-valuemax": max,
361
+ "aria-valuemin": min,
362
+ style: {
363
+ display: "inline-block",
364
+ width: size ?? width,
365
+ height: size ?? height,
366
+ cursor: "pointer",
367
+ WebkitTapHighlightColor: "transparent",
368
+ ...style
369
+ },
370
+ onPointerDown: pointerDownHandler,
371
+ onDoubleClick: () => {
372
+ if (enableDoubleClickDefault && onChange) onChange(defaultValue);
373
+ },
374
+ onKeyDown: handleKeyDown,
375
+ ...props,
376
+ children: /* @__PURE__ */ jsx(AnimationCanvas, {
377
+ ...isRelativeSize ? { relativeSize: true } : {
378
+ width: size ?? width,
379
+ height: size ?? height
380
+ },
381
+ draw: draw ? draw : (ctx, { width: width$1, height: height$1 }) => {
382
+ const p = normalizeValue(value, min, max, skew);
383
+ const startP = normalizeValue(startValue, min, max, skew);
384
+ const cx = width$1 / 2;
385
+ const cy = height$1 / 2;
386
+ const r = Math.min(cx, cy);
387
+ if (r <= 0) return;
388
+ ctx.clearRect(0, 0, width$1, height$1);
389
+ ctx.lineCap = "butt";
390
+ ctx.beginPath();
391
+ ctx.fillStyle = bg;
392
+ ctx.arc(cx, cy, r - .5, 0, 2 * Math.PI);
393
+ ctx.fill();
394
+ ctx.lineWidth = lineWeight;
395
+ ctx.beginPath();
396
+ ctx.strokeStyle = inactiveColor;
397
+ ctx.arc(cx, cy, r - .5 - lineWeight / 2, radian(135), radian(45));
398
+ ctx.stroke();
399
+ ctx.beginPath();
400
+ ctx.strokeStyle = activeColor;
401
+ ctx.arc(cx, cy, r - .5 - lineWeight / 2, radian(135 + 270 * startP), radian(135 + 270 * p), p < startP);
402
+ ctx.stroke();
403
+ ctx.lineCap = "round";
404
+ ctx.beginPath();
405
+ ctx.moveTo(cx, cy);
406
+ ctx.lineTo(cx + Math.cos(radian(135 + 270 * p)) * (r - .5 - thumbWeight / 2), cy + Math.sin(radian(135 + 270 * p)) * (r - .5 - thumbWeight / 2));
407
+ ctx.strokeStyle = thumb;
408
+ ctx.lineWidth = thumbWeight;
409
+ ctx.stroke();
410
+ }
411
+ })
412
+ });
413
+ });
414
+
415
+ //#endregion
416
+ //#region src/components/DragObserver/index.tsx
417
+ /** @category DragObserver */
418
+ function DragObserver(props) {
419
+ const { as: Component = "div", threshold = 1, children, onDrag, onDragStart, onDragEnd,...attributes } = props;
420
+ const [refCallback, pointerDownHandler] = useDrag({
421
+ threshold,
422
+ onDrag,
423
+ onDragStart,
424
+ onDragEnd
425
+ });
426
+ return /* @__PURE__ */ jsx(Component, {
427
+ ref: refCallback,
428
+ onPointerDown: pointerDownHandler,
429
+ ...attributes,
430
+ children
431
+ });
432
+ }
433
+
434
+ //#endregion
435
+ //#region src/components/Knob/index.tsx
436
+ /**
437
+ * Interactive rotary knob component implemented in SVG.
438
+ *
439
+ * @category Knob
440
+ */
441
+ const Knob = 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, onChange, onKeyDown, onPointerDown, onDoubleClick, className, classes,...props }, ref) => {
442
+ const valueRef = useRef(0);
443
+ const elmRef = useRef(null);
444
+ const padding = 8;
445
+ const thumbLineWeight = 6;
446
+ const thumbLineLength = 35;
447
+ const lineWeight = size * .06;
448
+ const p = normalizeValue(value, min, max, skew);
449
+ const s = normalizeValue(startValue, min, max, skew);
450
+ const onDrag = useCallback((_x, y) => {
451
+ if (!onChange || readonly) return;
452
+ const v = rawValue(valueRef.current - y / 100, min, max, skew);
453
+ const v2 = clamp(stepValue(v, step), min, max);
454
+ onChange(v2);
455
+ }, [
456
+ max,
457
+ min,
458
+ onChange,
459
+ readonly,
460
+ skew,
461
+ step
462
+ ]);
463
+ const updateValueByEvent = useCallback((eventType, x) => {
464
+ let newValue;
465
+ if (eventType == "normalized") {
466
+ const n = normalizeValue(value, min, max, skew);
467
+ newValue = rawValue(n + x, min, max, skew);
468
+ } else newValue = value + x;
469
+ return clamp(stepValue(newValue, step), min, max);
470
+ }, [
471
+ max,
472
+ min,
473
+ skew,
474
+ step,
475
+ value
476
+ ]);
477
+ const handleKeyDown = useCallback((event) => {
478
+ if (!keyboard || !onChange || readonly) return;
479
+ const key = event.key;
480
+ if ([
481
+ "ArrowRight",
482
+ "ArrowLeft",
483
+ "ArrowUp",
484
+ "ArrowDown"
485
+ ].includes(key)) {
486
+ event.preventDefault();
487
+ const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
488
+ onChange(updateValueByEvent(keyboard[0], x));
489
+ }
490
+ }, [
491
+ keyboard,
492
+ onChange,
493
+ readonly,
494
+ updateValueByEvent
495
+ ]);
496
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag({
497
+ onDrag,
498
+ onDragStart: () => {
499
+ valueRef.current = normalizeValue(value, min, max, skew);
500
+ if (bodyNoSelect) addNoSelect();
501
+ },
502
+ onDragEnd: () => {
503
+ if (bodyNoSelect) removeNoSelect();
504
+ }
505
+ });
506
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
507
+ if (!wheel || readonly) return;
508
+ event.preventDefault();
509
+ if (!onChange || event.deltaY == 0) return;
510
+ const x = Math.sign(event.deltaY) * -wheel[1];
511
+ onChange(updateValueByEvent(wheel[0], x));
512
+ }, { passive: false }, [
513
+ wheel,
514
+ onChange,
515
+ readonly,
516
+ updateValueByEvent
517
+ ]);
518
+ useImperativeHandle(ref, () => {
519
+ return {
520
+ focus() {
521
+ elmRef.current?.focus();
522
+ },
523
+ blur() {
524
+ elmRef.current?.blur();
525
+ }
526
+ };
527
+ }, []);
528
+ const center = size / 2;
529
+ const r1 = -135;
530
+ const r2 = -135 + Math.min(p, s) * 270;
531
+ const r3 = -135 + Math.max(p, s) * 270;
532
+ const r4 = 135;
533
+ const x1 = center + center * Math.cos(radian(r1 - 90));
534
+ const y1 = center + center * Math.sin(radian(r1 - 90));
535
+ const x2 = center + center * Math.cos(radian(r2 - 90));
536
+ const y2 = center + center * Math.sin(radian(r2 - 90));
537
+ const x3 = center + center * Math.cos(radian(r3 - 90));
538
+ const y3 = center + center * Math.sin(radian(r3 - 90));
539
+ const x4 = center + center * Math.cos(radian(r4 - 90));
540
+ const y4 = center + center * Math.sin(radian(r4 - 90));
541
+ return /* @__PURE__ */ jsxs("svg", {
542
+ className: clsx("tremolo-knob", className),
543
+ ref: (div) => {
544
+ elmRef.current = div;
545
+ wheelRefCallback(div);
546
+ touchMoveRefCallback(div);
547
+ },
548
+ width: size,
549
+ height: size,
550
+ tabIndex: 0,
551
+ role: "slider",
552
+ "aria-valuenow": value,
553
+ "aria-valuemin": min,
554
+ "aria-valuemax": max,
555
+ "aria-disabled": disabled,
556
+ "aria-readonly": readonly,
557
+ onPointerDown: (event) => {
558
+ pointerDownHandler(event);
559
+ onPointerDown?.(event);
560
+ },
561
+ onDoubleClick: (event) => {
562
+ if (enableDoubleClickDefault && onChange) onChange(defaultValue);
563
+ onDoubleClick?.(event);
564
+ },
565
+ onKeyDown: (event) => {
566
+ handleKeyDown(event);
567
+ onKeyDown?.(event);
568
+ },
569
+ ...props,
570
+ children: [
571
+ startValue > min && /* @__PURE__ */ jsx("path", {
572
+ className: clsx("tremolo-knob-inactive-line", classes?.inactiveLine),
573
+ d: `M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`,
574
+ fill: "none",
575
+ stroke: inactiveLine || "currentColor",
576
+ strokeWidth: lineWeight
577
+ }),
578
+ /* @__PURE__ */ jsx("path", {
579
+ className: clsx("tremolo-knob-active-line", classes?.activeLine),
580
+ d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
581
+ fill: "none",
582
+ stroke: activeLine || "currentColor",
583
+ strokeWidth: lineWeight
584
+ }),
585
+ startValue < max && /* @__PURE__ */ jsx("path", {
586
+ className: clsx("tremolo-knob-inactive-line", classes?.inactiveLine),
587
+ d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`,
588
+ fill: "none",
589
+ stroke: inactiveLine || "currentColor",
590
+ strokeWidth: lineWeight
591
+ }),
592
+ /* @__PURE__ */ jsxs("svg", {
593
+ className: clsx("tremolo-knob-thumb", classes?.thumb),
594
+ children: [/* @__PURE__ */ jsx("circle", {
595
+ cx: "50%",
596
+ cy: "50%",
597
+ r: `${50 - padding}%`,
598
+ fill: thumb || "currentColor"
599
+ }), /* @__PURE__ */ jsx("line", {
600
+ className: clsx("tremolo-knob-thumb-line", classes?.thumbLine),
601
+ x1: "50%",
602
+ y1: `${padding}%`,
603
+ x2: "50%",
604
+ y2: `${thumbLineLength}%`,
605
+ stroke: thumbLine || "currentColor",
606
+ strokeWidth: `${thumbLineWeight}%`,
607
+ style: {
608
+ transform: `rotate(${-135 + p * 270}deg)`,
609
+ transformOrigin: "50% 50%"
610
+ }
611
+ })]
612
+ })
613
+ ]
614
+ });
615
+ });
616
+
617
+ //#endregion
618
+ //#region src/components/NumberInput/context.tsx
619
+ function safeClamp(value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
620
+ return clamp(value, min, max);
621
+ }
622
+ const createNumberInputStore = (initProps) => {
623
+ const DEFAULT_PROPS = {
624
+ value: "",
625
+ valueAsNumber: 0,
626
+ min: 0,
627
+ max: 0,
628
+ step: 1,
629
+ readonly: false,
630
+ keepWithinRange: true,
631
+ onChange: (_v, _t) => {}
632
+ };
633
+ return createStore()((set) => ({
634
+ ...DEFAULT_PROPS,
635
+ ...initProps,
636
+ value: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).formatValue,
637
+ valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
638
+ increment: () => set((state) => {
639
+ if (state.readonly) return {};
640
+ const current = parseValue(state.value, state.units, state.digit).rawValue;
641
+ let next = current + (state.step ?? 1);
642
+ if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
643
+ const v = parseValue(String(next), state.units, state.digit).formatValue;
644
+ state?.onChange?.(next, v);
645
+ return {
646
+ value: v,
647
+ valueAsNumber: next
648
+ };
649
+ }),
650
+ decrement: () => set((state) => {
651
+ if (state.readonly) return {};
652
+ const current = parseValue(state.value, state.units, state.digit).rawValue;
653
+ let next = current - (state.step ?? 1);
654
+ if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
655
+ const v = parseValue(String(next), state.units, state.digit).formatValue;
656
+ state?.onChange?.(next, v);
657
+ return {
658
+ value: v,
659
+ valueAsNumber: next
660
+ };
661
+ }),
662
+ change: (value) => set((state) => {
663
+ if (state.readonly) return {};
664
+ const v = value ?? "";
665
+ return {
666
+ value: v,
667
+ valueAsNumber: parseValue(v, state.units, state.digit).rawValue
668
+ };
669
+ })
670
+ }));
671
+ };
672
+ const NumberInputContext = createContext(null);
673
+ function NumberInputProvider({ children,...props }) {
674
+ const storeRef = useRef(null);
675
+ if (!storeRef.current) storeRef.current = createNumberInputStore(props);
676
+ useEffect(() => {
677
+ if (storeRef.current) {
678
+ const parsed = parseValue(String(props?.value || ""), props?.units, props?.digit);
679
+ storeRef.current.setState({
680
+ ...props,
681
+ value: parsed.formatValue,
682
+ valueAsNumber: parsed.rawValue
683
+ });
684
+ } else storeRef.current = createNumberInputStore(props);
685
+ }, [props]);
686
+ return /* @__PURE__ */ jsx(NumberInputContext.Provider, {
687
+ value: storeRef.current,
688
+ children
689
+ });
690
+ }
691
+ /** @category NumberInput */
692
+ function useNumberInputContext(selector) {
693
+ const store = useContext(NumberInputContext);
694
+ if (!store) throw new Error("Missing NumberInputContext.Provider in the tree");
695
+ return useStore(store, selector);
696
+ }
697
+
698
+ //#endregion
699
+ //#region src/components/NumberInput/InternalInput.tsx
700
+ const InternalInput = forwardRef(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children,...props }, ref) => {
701
+ const elmRef = useRef(null);
702
+ const [editing, setEditing] = useState(false);
703
+ const value = useNumberInputContext((s) => s.value);
704
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
705
+ const min = useNumberInputContext((s) => s.min);
706
+ const max = useNumberInputContext((s) => s.max);
707
+ const step = useNumberInputContext((s) => s.step);
708
+ const units = useNumberInputContext((s) => s.units);
709
+ const digit = useNumberInputContext((s) => s.digit);
710
+ const change = useNumberInputContext((s) => s.change);
711
+ const error = useMemo(() => {
712
+ if (editing) return false;
713
+ const v = valueAsNumber;
714
+ return (min ?? -Infinity) > v || v > (max ?? Infinity);
715
+ }, [
716
+ editing,
717
+ max,
718
+ min,
719
+ valueAsNumber
720
+ ]);
721
+ const updateValueByEvent = useCallback((eventType, x) => {
722
+ let newValue;
723
+ if (eventType == "normalized") {
724
+ if (!min || !max) throw new Error("required parameter: min, max");
725
+ const n = normalizeValue(valueAsNumber, min, max);
726
+ newValue = rawValue(n + x, min, max);
727
+ return clamp(stepValue(newValue, step), min, max);
728
+ } else {
729
+ newValue = valueAsNumber + x;
730
+ return stepValue(newValue, step);
731
+ }
732
+ }, [
733
+ max,
734
+ min,
735
+ step,
736
+ valueAsNumber
737
+ ]);
738
+ const handleKeyDown = useCallback((event) => {
739
+ if (!keyboard || !onChange || readonly) return;
740
+ const key = event.key;
741
+ if (!["ArrowUp", "ArrowDown"].includes(key)) return;
742
+ event.preventDefault();
743
+ const x = key == "ArrowUp" ? keyboard[1] : -keyboard[1];
744
+ const parsed = parseValue(String(updateValueByEvent(keyboard[0], x)));
745
+ onChange?.(parsed.rawValue, parsed.formatValue);
746
+ }, [
747
+ keyboard,
748
+ readonly,
749
+ onChange,
750
+ updateValueByEvent
751
+ ]);
752
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
753
+ if (!wheel || readonly) return;
754
+ event.preventDefault();
755
+ if (!onChange || event.deltaY == 0) return;
756
+ const x = Math.sign(event.deltaY) * -wheel[1];
757
+ const parsed = parseValue(String(updateValueByEvent(wheel[0], x)));
758
+ onChange?.(parsed.rawValue, parsed.formatValue);
759
+ }, { passive: false }, [
760
+ wheel,
761
+ readonly,
762
+ onChange,
763
+ updateValueByEvent
764
+ ]);
765
+ useImperativeHandle(ref, () => {
766
+ return {
767
+ focus() {
768
+ elmRef.current?.focus();
769
+ },
770
+ blur() {
771
+ elmRef.current?.blur();
772
+ }
773
+ };
774
+ }, []);
775
+ return /* @__PURE__ */ jsx("input", {
776
+ ref: (input) => {
777
+ elmRef.current = input;
778
+ wheelRefCallback(input);
779
+ },
780
+ className: clsx("tremolo-number-input", className),
781
+ value,
782
+ min,
783
+ max,
784
+ step,
785
+ type: "text",
786
+ "aria-disabled": disabled,
787
+ "aria-readonly": readonly,
788
+ tabIndex: -1,
789
+ "data-error": error,
790
+ onChange: (event) => {
791
+ if (readonly) return;
792
+ const v = event.currentTarget.value;
793
+ const cursorPos = event.target.selectionStart;
794
+ change(v);
795
+ const valueAsNumber$1 = parseValue(v, units, digit).rawValue;
796
+ onChange?.(valueAsNumber$1, v);
797
+ if (cursorPos) {
798
+ event.target.selectionStart = cursorPos;
799
+ event.target.selectionEnd = cursorPos;
800
+ }
801
+ },
802
+ onFocus: (event) => {
803
+ setEditing(true);
804
+ const v = event.currentTarget.value;
805
+ const parsed = parseValue(v, units, digit);
806
+ if (selectWithFocus == "all") event.currentTarget.setSelectionRange(0, v.length);
807
+ else if (selectWithFocus == "number") event.currentTarget.setSelectionRange(0, parsed.formatValue.length - parsed.unit.length);
808
+ onFocus?.(parsed.rawValue, parsed.formatValue, event);
809
+ },
810
+ onBlur: (event) => {
811
+ setEditing(false);
812
+ let v = parseValue(event.currentTarget.value, units, digit).rawValue;
813
+ if (clampValueOnBlur) v = safeClamp(v, min, max);
814
+ const parsed = parseValue(String(v), units, digit);
815
+ change(parsed.formatValue);
816
+ if (clampValueOnBlur) onChange?.(parsed.rawValue, parsed.formatValue);
817
+ onBlur?.(parsed.rawValue, parsed.formatValue, event);
818
+ },
819
+ onKeyDown: (event) => {
820
+ if (blurOnEnter && event.key == "Enter") event.currentTarget.blur();
821
+ handleKeyDown(event);
822
+ onKeyDown?.(event);
823
+ },
824
+ ...props
825
+ });
826
+ });
827
+
828
+ //#endregion
829
+ //#region src/components/NumberInput/Stepper.tsx
830
+ /** @category NumberInput */
831
+ function Stepper({ dynamic = true, children, className,...props }) {
832
+ return /* @__PURE__ */ jsx("div", {
833
+ className: clsx("tremolo-number-input-stepper", className),
834
+ "data-dynamic": dynamic,
835
+ ...props,
836
+ children
837
+ });
838
+ }
839
+
840
+ //#endregion
841
+ //#region src/hooks/useInterval.ts
842
+ function useInterval(callback, delay) {
843
+ const savedCallback = useRef(callback);
844
+ useEffect(() => {
845
+ savedCallback.current = callback;
846
+ }, [callback]);
847
+ useEffect(() => {
848
+ if (delay === null) return;
849
+ const id = setInterval(() => {
850
+ savedCallback.current();
851
+ }, delay);
852
+ return () => {
853
+ clearInterval(id);
854
+ };
855
+ }, [delay]);
856
+ }
857
+
858
+ //#endregion
859
+ //#region src/hooks/useLongPress.ts
860
+ function useLongPress(callback, initialDelay = 500, interval = 40) {
861
+ const [pressed, setPressed] = useState(false);
862
+ const [delay, setDelay] = useState(initialDelay);
863
+ useInterval(() => {
864
+ callback();
865
+ setDelay(interval);
866
+ }, pressed ? delay : null);
867
+ useEventListener(globalThis.window, "pointerup", () => {
868
+ setPressed(false);
869
+ setDelay(initialDelay);
870
+ });
871
+ return useCallback(() => {
872
+ callback();
873
+ setPressed(true);
874
+ }, [callback]);
875
+ }
876
+
877
+ //#endregion
878
+ //#region src/components/NumberInput/IncrementStepper.tsx
879
+ /** @category NumberInput */
880
+ function IncrementStepper({ size = 12, children, className,...props }) {
881
+ const max = useNumberInputContext((s) => s.max);
882
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
883
+ const readonly = useNumberInputContext((s) => s.readonly);
884
+ const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
885
+ const increment = useNumberInputContext((s) => s.increment);
886
+ const press = useLongPress(increment);
887
+ return /* @__PURE__ */ jsx("div", {
888
+ className: clsx("tremolo-number-input-increment-stepper", className),
889
+ role: "button",
890
+ tabIndex: -1,
891
+ "aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
892
+ "aria-readonly": readonly,
893
+ onPointerDown: press,
894
+ ...props,
895
+ children: children || /* @__PURE__ */ jsx("svg", {
896
+ width: size,
897
+ height: size,
898
+ viewBox: "0 0 24 24",
899
+ fill: "none",
900
+ stroke: "currentColor",
901
+ strokeWidth: "2",
902
+ strokeLinecap: "round",
903
+ strokeLinejoin: "round",
904
+ children: /* @__PURE__ */ jsx("polyline", { points: "18 15 12 9 6 15" })
905
+ })
906
+ });
907
+ }
908
+
909
+ //#endregion
910
+ //#region src/components/NumberInput/DecrementStepper.tsx
911
+ /** @category NumberInput */
912
+ function DecrementStepper({ size = 12, children, className,...props }) {
913
+ const min = useNumberInputContext((s) => s.min);
914
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
915
+ const readonly = useNumberInputContext((s) => s.readonly);
916
+ const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange);
917
+ const decrement = useNumberInputContext((s) => s.decrement);
918
+ const press = useLongPress(decrement);
919
+ return /* @__PURE__ */ jsx("div", {
920
+ className: clsx("tremolo-number-input-decrement-stepper", className),
921
+ role: "button",
922
+ tabIndex: -1,
923
+ "aria-disabled": keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER),
924
+ "aria-readonly": readonly,
925
+ onPointerDown: press,
926
+ ...props,
927
+ children: children || /* @__PURE__ */ jsx("svg", {
928
+ width: size,
929
+ height: size,
930
+ viewBox: "0 0 24 24",
931
+ fill: "none",
932
+ stroke: "currentColor",
933
+ strokeWidth: "2",
934
+ strokeLinecap: "round",
935
+ strokeLinejoin: "round",
936
+ children: /* @__PURE__ */ jsx("polyline", { points: "6 9 12 15 18 9" })
937
+ })
938
+ });
939
+ }
940
+
941
+ //#endregion
942
+ //#region src/components/NumberInput/index.tsx
943
+ /**
944
+ * Input with some useful functions for entering numerical values.
945
+ * @category NumberInput
946
+ */
947
+ const NumberInput = 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 }, ref) => {
948
+ const colors = { "--active-color": activeColor };
949
+ return /* @__PURE__ */ jsx(NumberInputProvider, {
950
+ value: String(value),
951
+ min,
952
+ max,
953
+ step,
954
+ units,
955
+ digit,
956
+ readonly,
957
+ keepWithinRange,
958
+ onChange,
959
+ children: /* @__PURE__ */ jsxs("div", {
960
+ className: clsx("tremolo-number-input-wrapper", wrapperClassName),
961
+ tabIndex: 0,
962
+ style: { ...colors },
963
+ "data-stepper": !!children,
964
+ "data-variant": variant,
965
+ children: [/* @__PURE__ */ jsx(InternalInput, {
966
+ ref,
967
+ readonly,
968
+ disabled,
969
+ selectWithFocus,
970
+ blurOnEnter,
971
+ clampValueOnBlur,
972
+ wheel,
973
+ keyboard,
974
+ className,
975
+ onChange,
976
+ onFocus,
977
+ onBlur,
978
+ ...props
979
+ }), children]
980
+ })
981
+ });
982
+ });
983
+
984
+ //#endregion
985
+ //#region src/hooks/useDragWithElement.ts
986
+ /**
987
+ * @returns [refCallback, pointerDownHandler]
988
+ */
989
+ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
990
+ const dragged = useRef(false);
991
+ const normalizedX = useRef(0);
992
+ const normalizedY = useRef(0);
993
+ const handleDrag = useCallback((event) => {
994
+ if (!baseElementRef.current || !dragged.current) return;
995
+ const isTouch = event instanceof TouchEvent;
996
+ if (isTouch && event.cancelable) event.preventDefault();
997
+ const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
998
+ const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
999
+ const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1000
+ const nx = normalizeValue(mouseX, left, right);
1001
+ const ny = normalizeValue(mouseY, top, bottom);
1002
+ normalizedX.current = nx;
1003
+ normalizedY.current = ny;
1004
+ onDrag(nx, ny);
1005
+ }, [onDrag, baseElementRef]);
1006
+ const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
1007
+ const pointerDownHandler = useCallback((event) => {
1008
+ dragged.current = true;
1009
+ handleDrag(event);
1010
+ onDragStart?.(normalizedX.current, normalizedY.current);
1011
+ }, [handleDrag, onDragStart]);
1012
+ useEventListener(globalThis.window, "pointermove", handleDrag);
1013
+ useEventListener(globalThis.window, "pointerup", () => {
1014
+ if (!dragged.current) return;
1015
+ dragged.current = false;
1016
+ onDragEnd?.(normalizedX.current, normalizedY.current);
1017
+ });
1018
+ return [refHandler, pointerDownHandler];
1019
+ }
1020
+
1021
+ //#endregion
1022
+ //#region src/components/Piano/context.tsx
1023
+ const createPianoStore = (initProps) => {
1024
+ const DEFAULT_PROPS = {
1025
+ noteRange: {
1026
+ first: 0,
1027
+ last: 127
1028
+ },
1029
+ glissando: true,
1030
+ midiMax: 127,
1031
+ fill: false,
1032
+ label: () => void 0,
1033
+ onPlayNote: () => {},
1034
+ onStopNote: () => {}
1035
+ };
1036
+ return createStore()(() => ({
1037
+ ...DEFAULT_PROPS,
1038
+ ...initProps,
1039
+ notePosition: initProps.notePosition
1040
+ }));
1041
+ };
1042
+ const PianoContext = createContext(null);
1043
+ function PianoProvider({ children,...props }) {
1044
+ const storeRef = useRef(null);
1045
+ if (!storeRef.current) storeRef.current = createPianoStore(props);
1046
+ useEffect(() => {
1047
+ if (storeRef.current) storeRef.current.setState(props);
1048
+ else storeRef.current = createPianoStore(props);
1049
+ }, [props]);
1050
+ return /* @__PURE__ */ jsx(PianoContext.Provider, {
1051
+ value: storeRef.current,
1052
+ children
1053
+ });
1054
+ }
1055
+ /** @category Piano */
1056
+ function usePianoContext(selector) {
1057
+ const store = useContext(PianoContext);
1058
+ if (!store) throw new Error("Missing PianoContext.Provider in the tree");
1059
+ return useStore(store, selector);
1060
+ }
1061
+
1062
+ //#endregion
1063
+ //#region src/components/Piano/KeyLabel.tsx
1064
+ /** @category Piano */
1065
+ function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
1066
+ const noteRange = usePianoContext((s) => s.noteRange);
1067
+ const noteRangeArray = getNoteRangeArray(noteRange);
1068
+ const index = noteRangeArray.indexOf(__note);
1069
+ const content = label ? label(__note, index) : __label && __label(__note, index);
1070
+ return (content != void 0 || content != null) && /* @__PURE__ */ jsx("div", {
1071
+ className: clsx("tremolo-piano-key-label-wrapper", wrapperClassName),
1072
+ style: wrapperStyle,
1073
+ children: /* @__PURE__ */ jsx("div", {
1074
+ className: clsx("tremolo-piano-key-label", className),
1075
+ ...props,
1076
+ children: content
1077
+ })
1078
+ });
1079
+ }
1080
+
1081
+ //#endregion
1082
+ //#region src/components/Piano/key.tsx
1083
+ /** @category Piano */
1084
+ const defaultWhiteKeyWidth = 40;
1085
+ /** @category Piano */
1086
+ const defaultBlackKeyWidth = 26;
1087
+ const KeyImpl = forwardRef(({ keyType, noteNumber, width, height, bg, color, activeBg, activeColor, className, style, children, __width,...props }, ref) => {
1088
+ const [played, setPlayed] = useState(false);
1089
+ const midiMax = usePianoContext((s) => s.midiMax);
1090
+ const fill = usePianoContext((s) => s.fill);
1091
+ const onPlayNote = usePianoContext((s) => s.onPlayNote);
1092
+ const onStopNote = usePianoContext((s) => s.onStopNote);
1093
+ const label = usePianoContext((s) => s.label);
1094
+ const notePosition = usePianoContext((s) => s.notePosition);
1095
+ const position = notePosition(noteNumber);
1096
+ const disabled = noteNumber > midiMax;
1097
+ const colors = {
1098
+ "--bg": bg,
1099
+ "--color": color,
1100
+ "--active-bg": activeBg,
1101
+ "--active-color": activeColor
1102
+ };
1103
+ useImperativeHandle(ref, () => {
1104
+ return {
1105
+ play() {
1106
+ if (disabled) return;
1107
+ setPlayed(true);
1108
+ if (onPlayNote) onPlayNote(noteNumber);
1109
+ },
1110
+ stop() {
1111
+ setPlayed(false);
1112
+ if (onStopNote) onStopNote(noteNumber);
1113
+ },
1114
+ played() {
1115
+ return played;
1116
+ }
1117
+ };
1118
+ }, [
1119
+ disabled,
1120
+ noteNumber,
1121
+ onPlayNote,
1122
+ onStopNote,
1123
+ played
1124
+ ]);
1125
+ let keyLabelProps = {};
1126
+ if (children != void 0) React.Children.forEach(children, (child) => {
1127
+ if (React.isValidElement(child)) if (child.type == KeyLabel) keyLabelProps = child.props;
1128
+ else throw new Error("only <KeyLabel>");
1129
+ else throw new Error("children is an invalid element.");
1130
+ });
1131
+ return /* @__PURE__ */ jsx("div", {
1132
+ className: clsx(`tremolo-piano-${keyType}-key`, className),
1133
+ "aria-disabled": disabled,
1134
+ "data-active": played,
1135
+ style: {
1136
+ ...colors,
1137
+ left: position,
1138
+ width: fill ? __width : width,
1139
+ height,
1140
+ ...style
1141
+ },
1142
+ ...props,
1143
+ children: /* @__PURE__ */ jsx(KeyLabel, {
1144
+ label,
1145
+ __note: noteNumber,
1146
+ ...keyLabelProps
1147
+ })
1148
+ });
1149
+ });
1150
+ /** @category Piano */
1151
+ const WhiteKey = forwardRef(({ width = defaultWhiteKeyWidth, height = "100%", onContextMenu = (e) => e.preventDefault(),...props }, ref) => {
1152
+ return /* @__PURE__ */ jsx(KeyImpl, {
1153
+ ref,
1154
+ keyType: "white",
1155
+ width,
1156
+ height,
1157
+ onContextMenu,
1158
+ ...props
1159
+ });
1160
+ });
1161
+ /** @category Piano */
1162
+ const BlackKey = forwardRef(({ width = defaultBlackKeyWidth, height = "60%", onContextMenu = (e) => e.preventDefault(),...props }, ref) => {
1163
+ return /* @__PURE__ */ jsx(KeyImpl, {
1164
+ ref,
1165
+ keyType: "black",
1166
+ width,
1167
+ height,
1168
+ onContextMenu,
1169
+ ...props
1170
+ });
1171
+ });
1172
+
1173
+ //#endregion
1174
+ //#region src/components/Piano/keyboardShortcuts.ts
1175
+ /** @category Piano */
1176
+ const SHORTCUTS = { HOME_ROW: { keys: [
1177
+ "a",
1178
+ "w",
1179
+ "s",
1180
+ "e",
1181
+ "d",
1182
+ "f",
1183
+ "t",
1184
+ "g",
1185
+ "y",
1186
+ "h",
1187
+ "u",
1188
+ "j",
1189
+ "k",
1190
+ "o",
1191
+ "l",
1192
+ "p",
1193
+ ";"
1194
+ ] } };
1195
+
1196
+ //#endregion
1197
+ //#region src/components/Piano/index.tsx
1198
+ /**
1199
+ * Piano component
1200
+ *
1201
+ * TODO:
1202
+ * - add scale highlight
1203
+ */
1204
+ /** @category Piano */
1205
+ function getNoteRangeArray(noteRange) {
1206
+ return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1207
+ }
1208
+ const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth;
1209
+ /**
1210
+ * Customizable piano component.
1211
+ * @category Piano
1212
+ */
1213
+ function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown,...props }) {
1214
+ const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth);
1215
+ const keyRefs = useRef([]);
1216
+ for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = createRef();
1217
+ const pianoRef = useRef(null);
1218
+ const hitKeyIndex = useRef(-1);
1219
+ const noteRangeArray = getNoteRangeArray(noteRange);
1220
+ const whiteNotes = noteRangeArray.filter((v) => isWhiteKey(v));
1221
+ const blackNotes = noteRangeArray.filter((v) => isBlackKey(v));
1222
+ const whiteNoteCount = whiteNotes.length;
1223
+ const padding = 1;
1224
+ const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
1225
+ const childrenWithProps = useMemo(() => {
1226
+ return children && React.Children.map(children, (child, index) => {
1227
+ if (React.isValidElement(child)) {
1228
+ const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * .65;
1229
+ const props$1 = {
1230
+ ref: keyRefs.current[index],
1231
+ __width
1232
+ };
1233
+ return React.cloneElement(child, props$1);
1234
+ }
1235
+ return child;
1236
+ });
1237
+ }, [children, whiteNoteWidth]);
1238
+ const notePosition = useCallback((note) => {
1239
+ const pitchPositions = {
1240
+ C: 0,
1241
+ "C#": 1,
1242
+ D: 1,
1243
+ "D#": 2,
1244
+ E: 2,
1245
+ F: 3,
1246
+ "F#": 4,
1247
+ G: 4,
1248
+ "G#": 5,
1249
+ A: 5,
1250
+ "A#": 6,
1251
+ B: 6
1252
+ };
1253
+ const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
1254
+ const padding$1 = 1;
1255
+ const targetNoteKey = noteKey(note);
1256
+ const firstNoteKey = noteKey(noteRange.first);
1257
+ const octave = Math.floor((note - noteRange.first) / 12);
1258
+ const octaveOffset = noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1259
+ const w = whiteNoteWidth + padding$1;
1260
+ const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1261
+ const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0;
1262
+ return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1263
+ }, [noteRange.first, whiteNoteWidth]);
1264
+ const getHitKeyIndex = useCallback((x, y) => {
1265
+ if (!pianoRef.current) return -1;
1266
+ const containerHeight = pianoRef.current.clientHeight;
1267
+ const notes = [...blackNotes, ...whiteNotes];
1268
+ for (let i = 0; i < notes.length; i++) {
1269
+ const note = notes[i];
1270
+ const pos = notePosition(note);
1271
+ const w = isWhiteKey(note) ? whiteNoteWidth : whiteNoteWidth * blackPerWhiteWidth;
1272
+ const h = isWhiteKey(note) ? containerHeight : containerHeight * .6;
1273
+ if (pos <= x && x < pos + w && 0 <= y && y < h) return note;
1274
+ }
1275
+ return -1;
1276
+ }, [
1277
+ blackNotes,
1278
+ notePosition,
1279
+ whiteNoteWidth,
1280
+ whiteNotes
1281
+ ]);
1282
+ const onDrag = useCallback((perX, perY) => {
1283
+ if (!pianoRef.current) return;
1284
+ const x = perX * staticWidth;
1285
+ const y = perY * pianoRef.current.clientHeight;
1286
+ const note = getHitKeyIndex(x, y);
1287
+ const index = noteRangeArray.indexOf(note);
1288
+ if (index == -1) return;
1289
+ if (hitKeyIndex.current != index) {
1290
+ keyRefs.current[hitKeyIndex.current]?.current?.stop();
1291
+ keyRefs.current[index]?.current?.play();
1292
+ hitKeyIndex.current = index;
1293
+ }
1294
+ }, [
1295
+ getHitKeyIndex,
1296
+ noteRangeArray,
1297
+ staticWidth
1298
+ ]);
1299
+ const onDragEnd = useCallback(() => {
1300
+ if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
1301
+ hitKeyIndex.current = -1;
1302
+ }, [keyRefs]);
1303
+ useEffect(() => {
1304
+ if (fill && pianoRef.current) {
1305
+ const parent = pianoRef.current.parentElement;
1306
+ if (!parent) throw new Error("doesn't have a parent element.");
1307
+ const resizeObserver = new ResizeObserver(() => {
1308
+ const w = pianoRef.current.clientWidth;
1309
+ setWhiteNoteWidth(w / whiteNoteCount - padding);
1310
+ });
1311
+ resizeObserver.observe(parent);
1312
+ return () => {
1313
+ resizeObserver.unobserve(parent);
1314
+ };
1315
+ } else setWhiteNoteWidth(_whiteNoteWidth);
1316
+ }, [
1317
+ fill,
1318
+ _whiteNoteWidth,
1319
+ whiteNoteCount
1320
+ ]);
1321
+ const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1322
+ baseElementRef: pianoRef,
1323
+ onDrag,
1324
+ onDragEnd
1325
+ });
1326
+ useEventListener(globalThis.window, "keydown", (e) => {
1327
+ if (e.repeat) return;
1328
+ if (!keyboardShortcuts) return;
1329
+ const index = keyboardShortcuts.keys.indexOf(e.key);
1330
+ if (index != -1) {
1331
+ if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play();
1332
+ }
1333
+ });
1334
+ useEventListener(globalThis.window, "keyup", (e) => {
1335
+ if (e.repeat) return;
1336
+ if (!keyboardShortcuts) return;
1337
+ const index = keyboardShortcuts.keys.indexOf(e.key);
1338
+ if (index != -1) keyRefs.current[index]?.current?.stop();
1339
+ });
1340
+ return /* @__PURE__ */ jsx(PianoProvider, {
1341
+ notePosition,
1342
+ noteRange,
1343
+ glissando,
1344
+ midiMax,
1345
+ fill,
1346
+ onPlayNote,
1347
+ onStopNote,
1348
+ label,
1349
+ children: /* @__PURE__ */ jsx("div", {
1350
+ ref: (div) => {
1351
+ pianoRef.current = div;
1352
+ touchMoveRefCallback(div);
1353
+ },
1354
+ className: clsx("tremolo-piano", className),
1355
+ style: {
1356
+ width: fill ? "100%" : staticWidth,
1357
+ height,
1358
+ ...style
1359
+ },
1360
+ onPointerDown: (event) => {
1361
+ pointerDownHandler(event);
1362
+ onPointerDown?.(event);
1363
+ },
1364
+ ...props,
1365
+ children: childrenWithProps || noteRangeArray.map((note, index) => isWhiteKey(note) ? /* @__PURE__ */ jsx(WhiteKey, {
1366
+ ref: keyRefs.current[index],
1367
+ noteNumber: note,
1368
+ __width: whiteNoteWidth
1369
+ }, note) : /* @__PURE__ */ jsx(BlackKey, {
1370
+ ref: keyRefs.current[index],
1371
+ noteNumber: note,
1372
+ __width: whiteNoteWidth * blackPerWhiteWidth
1373
+ }, note))
1374
+ })
1375
+ });
1376
+ }
1377
+
1378
+ //#endregion
1379
+ //#region src/components/Slider/context.tsx
1380
+ const createSliderStore = (initProps) => {
1381
+ const DEFAULT_PROPS = {
1382
+ min: 0,
1383
+ max: 0,
1384
+ step: 0,
1385
+ skew: 0,
1386
+ vertical: false,
1387
+ reverse: false,
1388
+ disabled: false,
1389
+ readonly: false
1390
+ };
1391
+ return createStore()(() => ({
1392
+ ...DEFAULT_PROPS,
1393
+ ...initProps
1394
+ }));
1395
+ };
1396
+ const SliderContext = createContext(null);
1397
+ function SliderProvider({ children,...props }) {
1398
+ const storeRef = useRef(null);
1399
+ if (!storeRef.current) storeRef.current = createSliderStore(props);
1400
+ useEffect(() => {
1401
+ if (storeRef.current) storeRef.current.setState(props);
1402
+ else storeRef.current = createSliderStore(props);
1403
+ }, [props]);
1404
+ return /* @__PURE__ */ jsx(SliderContext.Provider, {
1405
+ value: storeRef.current,
1406
+ children
1407
+ });
1408
+ }
1409
+ /** @category Slider */
1410
+ function useSliderContext(selector) {
1411
+ const store = useContext(SliderContext);
1412
+ if (!store) throw new Error("Missing SliderContext.Provider in the tree");
1413
+ return useStore(store, selector);
1414
+ }
1415
+
1416
+ //#endregion
1417
+ //#region src/components/Slider/ScaleOption.tsx
1418
+ /** @category Slider */
1419
+ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length = "0.5rem", gap = 2, labelWidth, classes, styles, className, style,...props }) {
1420
+ const min = useSliderContext((s) => s.min);
1421
+ const max = useSliderContext((s) => s.max);
1422
+ const skew = useSliderContext((s) => s.skew);
1423
+ const vertical = useSliderContext((s) => s.vertical);
1424
+ const reverse = useSliderContext((s) => s.reverse);
1425
+ const calcPercent = useCallback((rawValue$1) => {
1426
+ return toFixed(xor(vertical, reverse) ? 100 - normalizeValue(rawValue$1, min, max, skew) * 100 : normalizeValue(rawValue$1, min, max, skew) * 100);
1427
+ }, [
1428
+ vertical,
1429
+ reverse,
1430
+ max,
1431
+ min,
1432
+ skew
1433
+ ]);
1434
+ return /* @__PURE__ */ jsxs("div", {
1435
+ className: clsx("tremolo-slider-scale-option", className),
1436
+ style: {
1437
+ left: !vertical ? `${calcPercent(value)}%` : void 0,
1438
+ top: vertical ? `${calcPercent(value)}%` : void 0,
1439
+ ...style
1440
+ },
1441
+ "data-vertical": vertical,
1442
+ ...props,
1443
+ children: [type != "number" && /* @__PURE__ */ jsx("div", {
1444
+ className: clsx("tremolo-slider-scale-option-mark", classes?.mark),
1445
+ style: {
1446
+ width: !vertical ? thickness : length,
1447
+ height: vertical ? thickness : length,
1448
+ marginBottom: !vertical ? gap : void 0,
1449
+ marginRight: vertical ? gap : void 0,
1450
+ ...styles?.mark
1451
+ },
1452
+ "data-vertical": vertical
1453
+ }), type != "mark" && /* @__PURE__ */ jsx("div", {
1454
+ className: clsx("tremolo-slider-scale-option-label", classes?.label),
1455
+ style: {
1456
+ width: labelWidth,
1457
+ ...styles?.label
1458
+ },
1459
+ children: label || value
1460
+ })]
1461
+ });
1462
+ }
1463
+
1464
+ //#endregion
1465
+ //#region src/components/Slider/Scale.tsx
1466
+ /** @category Slider */
1467
+ function Scale({ gap = 6, options, children, className, style,...props }) {
1468
+ const min = useSliderContext((s) => s.min);
1469
+ const max = useSliderContext((s) => s.max);
1470
+ const step = useSliderContext((s) => s.step);
1471
+ const vertical = useSliderContext((s) => s.vertical);
1472
+ const reverse = useSliderContext((s) => s.reverse);
1473
+ const optionsList = options ? generateOptionsList(options, min, max, step) : [];
1474
+ if (xor(vertical, reverse)) optionsList.reverse();
1475
+ return /* @__PURE__ */ jsx("div", {
1476
+ className: clsx("tremolo-slider-scale", className),
1477
+ style: {
1478
+ marginLeft: vertical ? gap : void 0,
1479
+ marginTop: !vertical ? gap : void 0,
1480
+ ...style
1481
+ },
1482
+ ...props,
1483
+ children: options ? optionsList.map(({ value, type }, index) => {
1484
+ return /* @__PURE__ */ jsx(ScaleOption, {
1485
+ value,
1486
+ type
1487
+ }, index);
1488
+ }) : children
1489
+ });
1490
+ }
1491
+
1492
+ //#endregion
1493
+ //#region src/components/Slider/Thumb.tsx
1494
+ /** @category Slider */
1495
+ const defaultThumbSize = 22;
1496
+ /** @category Slider */
1497
+ const SliderThumb = forwardRef(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent }, ref) => {
1498
+ const wrapperRef = useRef(null);
1499
+ const vertical = useSliderContext((s) => s.vertical);
1500
+ const disabled = useSliderContext((s) => s.disabled);
1501
+ const readonly = useSliderContext((s) => s.readonly);
1502
+ useImperativeHandle(ref, () => {
1503
+ return {
1504
+ focus() {
1505
+ wrapperRef.current?.focus();
1506
+ },
1507
+ blur() {
1508
+ wrapperRef.current?.blur();
1509
+ }
1510
+ };
1511
+ }, []);
1512
+ return /* @__PURE__ */ jsx("div", {
1513
+ className: "tremolo-slider-thumb-wrapper",
1514
+ style: {
1515
+ top: vertical ? `${__percent}%` : "50%",
1516
+ left: !vertical ? `${__percent}%` : "50%"
1517
+ },
1518
+ children: children ? children : /* @__PURE__ */ jsx("div", {
1519
+ ref: wrapperRef,
1520
+ className: clsx("tremolo-slider-thumb", className),
1521
+ tabIndex: 0,
1522
+ "aria-disabled": disabled,
1523
+ "aria-readonly": readonly,
1524
+ style: {
1525
+ "--color": color,
1526
+ width: size ?? width,
1527
+ height: size ?? height,
1528
+ ...style
1529
+ }
1530
+ })
1531
+ });
1532
+ });
1533
+
1534
+ //#endregion
1535
+ //#region src/components/Slider/Track.tsx
1536
+ const defaultLength = 140;
1537
+ const defaultThickness = 10;
1538
+ /** @category Slider */
1539
+ function SliderTrack({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0,...props }) {
1540
+ const vertical = useSliderContext((s) => s.vertical);
1541
+ const reverse = useSliderContext((s) => s.reverse);
1542
+ const disabled = useSliderContext((s) => s.disabled);
1543
+ const direction = vertical ? "bottom" : "right";
1544
+ const colors = {
1545
+ "--active": active,
1546
+ "--inactive": inactive
1547
+ };
1548
+ return /* @__PURE__ */ jsxs("div", {
1549
+ className: clsx("tremolo-slider-track", className),
1550
+ "aria-disabled": disabled,
1551
+ "data-vertical": vertical,
1552
+ style: !defaultStyle ? style : {
1553
+ ...colors,
1554
+ background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive, #eee) ${__percent}%, var(--active, #7998ec) ${__percent}%)` : `linear-gradient(to ${direction}, var(--active, #7998ec) ${__percent}%, var(--inactive, #eee) ${__percent}%)`,
1555
+ borderRadius: styleHelper(thickness, "/", 2),
1556
+ width: !vertical ? length : thickness,
1557
+ height: vertical ? length : thickness,
1558
+ ...style
1559
+ },
1560
+ ...props,
1561
+ children: [children, __thumb]
1562
+ });
1563
+ }
1564
+
1565
+ //#endregion
1566
+ //#region src/components/Slider/index.tsx
1567
+ /**
1568
+ * Customizable slider
1569
+ * @category Slider
1570
+ */
1571
+ const Slider = 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 }, ref) => {
1572
+ const trackElementRef = useRef(null);
1573
+ const thumbRef = useRef(null);
1574
+ const p = toFixed(normalizeValue(value, min, max, skew) * 100);
1575
+ const rev = toFixed(100 - p);
1576
+ const displayReversed = useMemo(() => xor(vertical, reverse), [vertical, reverse]);
1577
+ const percent = displayReversed ? rev : p;
1578
+ let trackProps = {};
1579
+ let thumbProps = {};
1580
+ let scaleComponent = void 0;
1581
+ if (children != void 0) React.Children.forEach(children, (child) => {
1582
+ if (React.isValidElement(child)) if (child.type == SliderThumb) thumbProps = child.props;
1583
+ else if (child.type == SliderTrack) trackProps = child.props;
1584
+ else if (child.type == Scale) scaleComponent = child;
1585
+ else throw new Error("only <SliderThumb> or <SliderTrack>");
1586
+ else throw new Error("children is an invalid element.");
1587
+ });
1588
+ const onDrag = useCallback((nx, ny) => {
1589
+ if (!onChange || readonly) return;
1590
+ const n = vertical ? ny : nx;
1591
+ const v = rawValue(displayReversed ? 1 - n : n, min, max, skew);
1592
+ const v2 = clamp(stepValue(v, step), min, max);
1593
+ onChange(v2);
1594
+ }, [
1595
+ displayReversed,
1596
+ max,
1597
+ min,
1598
+ onChange,
1599
+ readonly,
1600
+ skew,
1601
+ step,
1602
+ vertical
1603
+ ]);
1604
+ const updateValueByEvent = useCallback((eventType, x) => {
1605
+ let newValue;
1606
+ if (eventType == "normalized") {
1607
+ const n = normalizeValue(value, min, max, skew);
1608
+ newValue = rawValue(n + x, min, max, skew);
1609
+ } else newValue = value + x;
1610
+ return clamp(stepValue(newValue, step), min, max);
1611
+ }, [
1612
+ max,
1613
+ min,
1614
+ skew,
1615
+ step,
1616
+ value
1617
+ ]);
1618
+ const handleKeyDown = useCallback((event) => {
1619
+ if (!keyboard || !onChange || readonly) return;
1620
+ const key = event.key;
1621
+ if ([
1622
+ "ArrowRight",
1623
+ "ArrowLeft",
1624
+ "ArrowUp",
1625
+ "ArrowDown"
1626
+ ].includes(key)) {
1627
+ event.preventDefault();
1628
+ let x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
1629
+ if (reverse) x *= -1;
1630
+ onChange(updateValueByEvent(keyboard[0], x));
1631
+ }
1632
+ }, [
1633
+ keyboard,
1634
+ onChange,
1635
+ readonly,
1636
+ reverse,
1637
+ updateValueByEvent
1638
+ ]);
1639
+ const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1640
+ baseElementRef: trackElementRef,
1641
+ onDrag,
1642
+ onDragStart: (nx, ny) => {
1643
+ if (readonly) return;
1644
+ if (bodyNoSelect) addNoSelect();
1645
+ thumbRef.current?.focus();
1646
+ onDragStart?.(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
1647
+ },
1648
+ onDragEnd: (nx, ny) => {
1649
+ if (readonly) return;
1650
+ if (bodyNoSelect) removeNoSelect();
1651
+ onDragEnd?.(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
1652
+ }
1653
+ });
1654
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1655
+ if (!wheel || !onChange || readonly) return;
1656
+ event.preventDefault();
1657
+ let x;
1658
+ if (!vertical && event.deltaX != 0) x = event.deltaX > 0 ? wheel[1] : -wheel[1];
1659
+ else {
1660
+ if (event.deltaY == 0) return;
1661
+ x = event.deltaY > 0 ? -wheel[1] : wheel[1];
1662
+ }
1663
+ if (vertical && reverse) x *= -1;
1664
+ onChange(updateValueByEvent(wheel[0], x));
1665
+ }, { passive: false }, [
1666
+ wheel,
1667
+ vertical,
1668
+ readonly,
1669
+ onChange,
1670
+ updateValueByEvent
1671
+ ]);
1672
+ useImperativeHandle(ref, () => {
1673
+ return {
1674
+ focus() {
1675
+ thumbRef.current?.focus();
1676
+ },
1677
+ blur() {
1678
+ thumbRef.current?.blur();
1679
+ }
1680
+ };
1681
+ }, []);
1682
+ return /* @__PURE__ */ jsx(SliderProvider, {
1683
+ min,
1684
+ max,
1685
+ step,
1686
+ skew,
1687
+ vertical,
1688
+ reverse,
1689
+ disabled,
1690
+ readonly,
1691
+ children: /* @__PURE__ */ jsx("div", {
1692
+ className: clsx("tremolo-slider", className),
1693
+ ref: (div) => {
1694
+ wheelRefCallback(div);
1695
+ touchMoveRefCallback(div);
1696
+ },
1697
+ tabIndex: -1,
1698
+ role: "slider",
1699
+ "aria-valuenow": value,
1700
+ "aria-valuemin": min,
1701
+ "aria-valuemax": max,
1702
+ "aria-orientation": vertical ? "vertical" : "horizontal",
1703
+ "aria-disabled": disabled,
1704
+ "aria-readonly": readonly,
1705
+ style: {
1706
+ margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`,
1707
+ ...style
1708
+ },
1709
+ onPointerDown: (event) => {
1710
+ pointerDownHandler(event);
1711
+ onPointerDown?.(event);
1712
+ },
1713
+ onKeyDown: (event) => {
1714
+ handleKeyDown(event);
1715
+ onKeyDown?.(event);
1716
+ },
1717
+ onFocus: (event) => {
1718
+ thumbRef.current?.focus();
1719
+ onFocus?.(event);
1720
+ },
1721
+ onBlur: (event) => {
1722
+ thumbRef.current?.blur();
1723
+ onBlur?.(event);
1724
+ },
1725
+ ...props,
1726
+ children: /* @__PURE__ */ jsxs("div", {
1727
+ style: {
1728
+ display: "flex",
1729
+ flexDirection: vertical ? "row" : "column",
1730
+ width: !vertical ? "100%" : void 0,
1731
+ height: vertical ? "100%" : void 0
1732
+ },
1733
+ children: [/* @__PURE__ */ jsx("div", {
1734
+ className: "tremolo-slider-track-wrapper",
1735
+ ref: trackElementRef,
1736
+ children: /* @__PURE__ */ jsx(SliderTrack, {
1737
+ __percent: percent,
1738
+ __thumb: /* @__PURE__ */ jsx(SliderThumb, {
1739
+ ref: thumbRef,
1740
+ __percent: percent,
1741
+ ...thumbProps
1742
+ }),
1743
+ ...trackProps
1744
+ })
1745
+ }), scaleComponent]
1746
+ })
1747
+ })
1748
+ });
1749
+ });
1750
+
1751
+ //#endregion
1752
+ //#region src/components/WheelObserver/index.tsx
1753
+ /** @category WheelObserver */
1754
+ function WheelObserver(props) {
1755
+ const { children, onWheel, as: Component = "div",...attributes } = props;
1756
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1757
+ if (onWheel) onWheel(event);
1758
+ }, { passive: false }, [onWheel]);
1759
+ return /* @__PURE__ */ jsx(Component, {
1760
+ ref: wheelRefCallback,
1761
+ ...attributes,
1762
+ children
1763
+ });
1764
+ }
1765
+
1766
+ //#endregion
1767
+ //#region src/components/XYPad/Area.tsx
1768
+ /** @category XYPad */
1769
+ function XYPadArea({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
1770
+ return /* @__PURE__ */ jsxs("div", {
1771
+ className: clsx("tremolo-xy-pad-area", className),
1772
+ style: {
1773
+ "--color": color,
1774
+ width,
1775
+ height,
1776
+ ...style
1777
+ },
1778
+ ...props,
1779
+ children: [children, __thumb]
1780
+ });
1781
+ }
1782
+
1783
+ //#endregion
1784
+ //#region src/components/XYPad/Thumb.tsx
1785
+ /** @category XYPad */
1786
+ const XYPadThumb = forwardRef(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __css,...props }, ref) => {
1787
+ const wrapperRef = useRef(null);
1788
+ useImperativeHandle(ref, () => {
1789
+ return {
1790
+ focus() {
1791
+ wrapperRef.current?.focus();
1792
+ },
1793
+ blur() {
1794
+ wrapperRef.current?.blur();
1795
+ }
1796
+ };
1797
+ }, []);
1798
+ return /* @__PURE__ */ jsx("div", {
1799
+ className: clsx("tremolo-xy-pad-thumb-wrapper", wrapperClassName),
1800
+ style: {
1801
+ ...__css,
1802
+ ...wrapperStyle
1803
+ },
1804
+ ...props,
1805
+ children: children ? children : /* @__PURE__ */ jsx("div", {
1806
+ ref: wrapperRef,
1807
+ className: clsx("tremolo-xy-pad-thumb", className),
1808
+ tabIndex: 0,
1809
+ "aria-disabled": __disabled,
1810
+ style: {
1811
+ "--color": color,
1812
+ width: size ?? width,
1813
+ height: size ?? height,
1814
+ ...style
1815
+ }
1816
+ })
1817
+ });
1818
+ });
1819
+
1820
+ //#endregion
1821
+ //#region src/components/XYPad/index.tsx
1822
+ const defaultValueOptions = {
1823
+ skew: 1,
1824
+ step: 1,
1825
+ reverse: false,
1826
+ wheel: ["raw", 1],
1827
+ keyboard: ["raw", 1]
1828
+ };
1829
+ /**
1830
+ * Simple XYPad
1831
+ * @category XYPad
1832
+ */
1833
+ const XYPad = forwardRef(({ x: _x, y: _y, className, style, bodyNoSelect = true, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children,...props }, ref) => {
1834
+ const x = useMemo(() => {
1835
+ return {
1836
+ ...defaultValueOptions,
1837
+ ..._x
1838
+ };
1839
+ }, [_x]);
1840
+ const y = useMemo(() => {
1841
+ return {
1842
+ ...defaultValueOptions,
1843
+ ..._y
1844
+ };
1845
+ }, [_y]);
1846
+ const areaElementRef = useRef(null);
1847
+ const thumbRef = useRef(null);
1848
+ const nx = normalizeValue(x.value, x.min, x.max, x.skew);
1849
+ const ny = normalizeValue(y.value, y.min, y.max, y.skew);
1850
+ const percentX = toFixed((x.reverse ? 1 - nx : nx) * 100);
1851
+ const percentY = toFixed((y.reverse ? 1 - ny : ny) * 100);
1852
+ let areaProps = {};
1853
+ let thumbProps = {};
1854
+ if (children != void 0) React.Children.forEach(children, (child) => {
1855
+ if (React.isValidElement(child)) if (child.type == XYPadThumb) thumbProps = child.props;
1856
+ else if (child.type == XYPadArea) areaProps = child.props;
1857
+ else throw new Error("only <XYPadThumb> or <XYPadArea>");
1858
+ else throw new Error("children is an invalid element.");
1859
+ });
1860
+ const onDrag = useCallback((nx$1, ny$1) => {
1861
+ if (!onChange || readonly) return;
1862
+ const vx = rawValue(x.reverse ? 1 - nx$1 : nx$1, x.min, x.max, x.skew);
1863
+ const vy = rawValue(y.reverse ? 1 - ny$1 : ny$1, y.min, y.max, y.skew);
1864
+ const newX = clamp(stepValue(vx, x.step), x.min, x.max);
1865
+ const newY = clamp(stepValue(vy, y.step), y.min, y.max);
1866
+ onChange(newX, newY);
1867
+ }, [
1868
+ onChange,
1869
+ readonly,
1870
+ x.max,
1871
+ x.min,
1872
+ x.reverse,
1873
+ x.skew,
1874
+ x.step,
1875
+ y.max,
1876
+ y.min,
1877
+ y.reverse,
1878
+ y.skew,
1879
+ y.step
1880
+ ]);
1881
+ const updateValueByEvent = useCallback((eventType, target, x$1) => {
1882
+ let v;
1883
+ if (eventType == "normalized") {
1884
+ const n = normalizeValue(target.value, target.min, target.max, target.skew);
1885
+ v = rawValue(n + x$1, target.min, target.max, target.skew);
1886
+ } else v = target.value + x$1;
1887
+ return clamp(stepValue(v, target.step), target.min, target.max);
1888
+ }, []);
1889
+ const handleKeyDown = useCallback((event) => {
1890
+ if (!onChange || readonly) return;
1891
+ const key = event.key;
1892
+ if ([
1893
+ "ArrowRight",
1894
+ "ArrowLeft",
1895
+ "ArrowUp",
1896
+ "ArrowDown"
1897
+ ].includes(key)) {
1898
+ const isHorizontal = key == "ArrowRight" || key == "ArrowLeft";
1899
+ const target = isHorizontal ? x : y;
1900
+ if (!target.keyboard) return;
1901
+ event.preventDefault();
1902
+ let delta = target.keyboard[1];
1903
+ if (key == "ArrowLeft" || key == "ArrowUp") delta *= -1;
1904
+ if (target.reverse) delta *= -1;
1905
+ const newValue = updateValueByEvent(target.keyboard[0], target, delta);
1906
+ if (isHorizontal) onChange(newValue, y.value);
1907
+ else onChange(x.value, newValue);
1908
+ }
1909
+ }, [
1910
+ onChange,
1911
+ readonly,
1912
+ x,
1913
+ y,
1914
+ updateValueByEvent
1915
+ ]);
1916
+ const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1917
+ baseElementRef: areaElementRef,
1918
+ onDrag,
1919
+ onDragStart: (nx$1, ny$1) => {
1920
+ if (readonly) return;
1921
+ if (bodyNoSelect) addNoSelect();
1922
+ thumbRef.current?.focus();
1923
+ const valueX = clamp(stepValue(rawValue(nx$1, x.min, x.max, x.skew), x.step), x.min, x.max);
1924
+ const valueY = clamp(stepValue(rawValue(ny$1, y.min, y.max, y.skew), y.step), y.min, y.max);
1925
+ onDragStart?.(valueX, valueY);
1926
+ },
1927
+ onDragEnd: (nx$1, ny$1) => {
1928
+ if (readonly) return;
1929
+ if (bodyNoSelect) removeNoSelect();
1930
+ const valueX = clamp(stepValue(rawValue(nx$1, x.min, x.max, x.skew), x.step), x.min, x.max);
1931
+ const valueY = clamp(stepValue(rawValue(ny$1, y.min, y.max, y.skew), y.step), y.min, y.max);
1932
+ onDragEnd?.(valueX, valueY);
1933
+ }
1934
+ });
1935
+ const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
1936
+ if (!onChange || readonly) return;
1937
+ const target = event.shiftKey ? x : y;
1938
+ if (!target.wheel) return;
1939
+ event.preventDefault();
1940
+ let delta = target.wheel[1];
1941
+ if (event.deltaY < 0) delta *= -1;
1942
+ if (target.reverse) delta *= -1;
1943
+ const newValue = updateValueByEvent(target.wheel[0], target, delta);
1944
+ if (event.shiftKey) onChange(newValue, y.value);
1945
+ else onChange(x.value, newValue);
1946
+ }, { passive: false }, [
1947
+ x,
1948
+ y,
1949
+ onChange,
1950
+ readonly,
1951
+ updateValueByEvent
1952
+ ]);
1953
+ useImperativeHandle(ref, () => {
1954
+ return {
1955
+ focus() {
1956
+ thumbRef.current?.focus();
1957
+ },
1958
+ blur() {
1959
+ thumbRef.current?.blur();
1960
+ }
1961
+ };
1962
+ }, []);
1963
+ return /* @__PURE__ */ jsx("div", {
1964
+ className: clsx("tremolo-xy-pad", className),
1965
+ ref: (div) => {
1966
+ wheelRefCallback(div);
1967
+ touchMoveRefCallback(div);
1968
+ },
1969
+ tabIndex: -1,
1970
+ "aria-disabled": disabled,
1971
+ "aria-readonly": readonly,
1972
+ style: {
1973
+ margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`,
1974
+ cursor: readonly ? "not-allowed" : "pointer",
1975
+ WebkitTapHighlightColor: "transparent",
1976
+ ...style
1977
+ },
1978
+ onPointerDown: (event) => {
1979
+ pointerDownHandler(event);
1980
+ onPointerDown?.(event);
1981
+ },
1982
+ onKeyDown: (event) => {
1983
+ handleKeyDown(event);
1984
+ onKeyDown?.(event);
1985
+ },
1986
+ onFocus: (event) => {
1987
+ thumbRef.current?.focus();
1988
+ onFocus?.(event);
1989
+ },
1990
+ onBlur: (event) => {
1991
+ thumbRef.current?.blur();
1992
+ onBlur?.(event);
1993
+ },
1994
+ ...props,
1995
+ children: /* @__PURE__ */ jsx("div", {
1996
+ className: "tremolo-xy-pad-area-wrapper",
1997
+ ref: areaElementRef,
1998
+ children: /* @__PURE__ */ jsx(XYPadArea, {
1999
+ __thumb: /* @__PURE__ */ jsx(XYPadThumb, {
2000
+ ref: thumbRef,
2001
+ __disabled: disabled,
2002
+ __css: {
2003
+ top: `${percentY}%`,
2004
+ left: `${percentX}%`
2005
+ },
2006
+ ...thumbProps
2007
+ }),
2008
+ ...areaProps
2009
+ })
2010
+ })
2011
+ });
2012
+ });
2013
+
2014
+ //#endregion
2015
+ //#region src/hooks/useAnimationFrame.ts
2016
+ function useAnimationFrame(callback = () => {}, deps = []) {
2017
+ const reqIdRef = useRef(-1);
2018
+ const loop = useCallback(() => {
2019
+ reqIdRef.current = requestAnimationFrame(loop);
2020
+ callback();
2021
+ }, [callback, ...deps]);
2022
+ useEffect(() => {
2023
+ reqIdRef.current = requestAnimationFrame(loop);
2024
+ return () => cancelAnimationFrame(reqIdRef.current);
2025
+ }, [loop]);
2026
+ }
2027
+
2028
+ //#endregion
2029
+ export { AnimationCanvas, AnimationKnob, BlackKey, DecrementStepper, DragObserver, IncrementStepper, KeyLabel, Knob, NumberInput, Piano, SHORTCUTS, Scale, ScaleOption, Slider, SliderThumb, SliderTrack, Stepper, WheelObserver, WhiteKey, XYPad, XYPadArea, XYPadThumb, getNoteRangeArray, useAnimationFrame, useDrag, useDragWithElement, useEventListener, useInterval, useLongPress, useSliderContext };
2030
+ //# sourceMappingURL=index.js.map