@tremolo-ui/react 0.1.1 → 0.1.3

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