@tremolo-ui/react 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +666 -1171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -315
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +316 -315
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +643 -1123
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/components/AnimationCanvas/index.tsx +4 -18
- package/src/components/DropZone/index.tsx +2 -1
- package/src/components/FileInput/index.tsx +11 -13
- package/src/components/Knob/ActiveLine.tsx +5 -3
- package/src/components/Knob/InactiveLine.tsx +6 -4
- package/src/components/Knob/SVGRoot.tsx +3 -3
- package/src/components/Knob/Thumb.tsx +21 -20
- package/src/components/Knob/context.tsx +3 -74
- package/src/components/Knob/index.tsx +99 -52
- package/src/components/NumberInput/InputField.tsx +20 -33
- package/src/components/NumberInput/Stepper.tsx +76 -100
- package/src/components/NumberInput/StepperButton.tsx +15 -8
- package/src/components/NumberInput/context.tsx +26 -3
- package/src/components/NumberInput/index.tsx +175 -91
- package/src/components/Piano/index.tsx +54 -144
- package/src/components/PointsEditor/Container.tsx +15 -26
- package/src/components/PointsEditor/Point.tsx +94 -110
- package/src/components/PointsEditor/context.tsx +8 -60
- package/src/components/PointsEditor/index.tsx +41 -324
- package/src/components/Slider/Marks.tsx +5 -6
- package/src/components/Slider/MarksOption.tsx +9 -19
- package/src/components/Slider/Thumb.tsx +20 -11
- package/src/components/Slider/Track.tsx +16 -13
- package/src/components/Slider/context.tsx +3 -3
- package/src/components/Slider/index.tsx +110 -72
- package/src/components/XYPad/Area.tsx +27 -49
- package/src/components/XYPad/Thumb.tsx +20 -8
- package/src/components/XYPad/context.tsx +2 -4
- package/src/components/XYPad/index.tsx +102 -83
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
- package/src/hooks/_internal/useChangeGesture.ts +93 -0
- package/src/hooks/_internal/useCheckSteps.ts +9 -146
- package/src/hooks/useAnimationFrame.ts +37 -15
- package/src/hooks/useEventListener.ts +93 -22
- package/src/hooks/useLongPress.ts +35 -51
- package/src/hooks/useMIDIAccess.ts +0 -8
- package/src/hooks/useMIDIInput.ts +0 -3
- package/src/hooks/useWheel.ts +4 -4
- package/src/index.ts +11 -32
- package/src/components/Piano/keyboard-shortcuts.ts +0 -61
- package/src/components/Slider/type.ts +0 -46
- package/src/components/_util/css-length.ts +0 -13
- package/src/components/_util/decimal-digits.ts +0 -23
- package/src/components/_util/visually-hidden.ts +0 -25
- package/src/hooks/useInterval.ts +0 -27
- package/src/input-event.ts +0 -32
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ let react = require("react");
|
|
|
26
26
|
react = __toESM(react, 1);
|
|
27
27
|
let _tremolo_ui_dom = require("@tremolo-ui/dom");
|
|
28
28
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
let _tremolo_ui_dom_internal = require("@tremolo-ui/dom/internal");
|
|
29
30
|
let _tremolo_ui_functions = require("@tremolo-ui/functions");
|
|
30
31
|
//#region src/components/AnimationCanvas/index.tsx
|
|
31
32
|
/**
|
|
@@ -192,31 +193,6 @@ const DropZone = { Root: /* @__PURE__ */ (0, react.forwardRef)(({ accept, multip
|
|
|
192
193
|
});
|
|
193
194
|
}) };
|
|
194
195
|
//#endregion
|
|
195
|
-
//#region src/components/_util/visually-hidden.ts
|
|
196
|
-
/**
|
|
197
|
-
* Take an element out of sight while leaving it in the accessibility tree and
|
|
198
|
-
* in the tab order.
|
|
199
|
-
*
|
|
200
|
-
* `display: none` and `visibility: hidden` would remove it from both, and the
|
|
201
|
-
* native control underneath a headless component is what carries the ARIA and
|
|
202
|
-
* the keyboard behaviour. `pointer-events: none` is safe because nothing is
|
|
203
|
-
* ever clicked here directly: a `<label>` forwards its click, and a drag is
|
|
204
|
-
* handled by the part that is visible.
|
|
205
|
-
*/
|
|
206
|
-
const visuallyHiddenStyle = {
|
|
207
|
-
position: "absolute",
|
|
208
|
-
width: 1,
|
|
209
|
-
height: 1,
|
|
210
|
-
padding: 0,
|
|
211
|
-
margin: -1,
|
|
212
|
-
overflow: "hidden",
|
|
213
|
-
clip: "rect(0, 0, 0, 0)",
|
|
214
|
-
clipPath: "inset(50%)",
|
|
215
|
-
whiteSpace: "nowrap",
|
|
216
|
-
border: 0,
|
|
217
|
-
pointerEvents: "none"
|
|
218
|
-
};
|
|
219
|
-
//#endregion
|
|
220
196
|
//#region src/components/FileInput/context.tsx
|
|
221
197
|
const FileInputContext = /* @__PURE__ */ (0, react.createContext)(null);
|
|
222
198
|
const FileInputProvider = FileInputContext.Provider;
|
|
@@ -256,14 +232,10 @@ const FileInput = {
|
|
|
256
232
|
disabled
|
|
257
233
|
}), [inputId, disabled]);
|
|
258
234
|
function handleChange(event) {
|
|
259
|
-
const
|
|
235
|
+
const { accepted, rejected } = (0, _tremolo_ui_dom_internal.partitionByAccept)(Array.from(event.target.files ?? []), accept);
|
|
260
236
|
event.target.value = "";
|
|
261
|
-
const accepted = [];
|
|
262
|
-
const rejected = [];
|
|
263
|
-
for (const file of picked) if ((0, _tremolo_ui_dom.matchesAccept)(file, accept)) accepted.push(file);
|
|
264
|
-
else rejected.push(file);
|
|
265
|
-
if (rejected.length > 0) onReject?.(rejected);
|
|
266
237
|
if (accepted.length > 0) onChange?.(accepted);
|
|
238
|
+
if (rejected.length > 0) onReject?.(rejected);
|
|
267
239
|
}
|
|
268
240
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileInputProvider, {
|
|
269
241
|
value: context,
|
|
@@ -277,7 +249,7 @@ const FileInput = {
|
|
|
277
249
|
accept,
|
|
278
250
|
multiple,
|
|
279
251
|
disabled,
|
|
280
|
-
style: visuallyHiddenStyle,
|
|
252
|
+
style: _tremolo_ui_dom_internal.visuallyHiddenStyle,
|
|
281
253
|
"aria-label": ariaLabel,
|
|
282
254
|
"aria-labelledby": ariaLabelledby,
|
|
283
255
|
"aria-describedby": ariaDescribedby,
|
|
@@ -289,114 +261,75 @@ const FileInput = {
|
|
|
289
261
|
Trigger
|
|
290
262
|
};
|
|
291
263
|
//#endregion
|
|
292
|
-
//#region src/hooks/_internal/
|
|
293
|
-
/** Positions probed across the travel. The ends are left out so that the
|
|
294
|
-
* clamp at `min` and `max` cannot be mistaken for a press that does nothing. */
|
|
295
|
-
const PROBES = [
|
|
296
|
-
.1,
|
|
297
|
-
.2,
|
|
298
|
-
.3,
|
|
299
|
-
.4,
|
|
300
|
-
.5,
|
|
301
|
-
.6,
|
|
302
|
-
.7,
|
|
303
|
-
.8,
|
|
304
|
-
.9
|
|
305
|
-
];
|
|
306
|
-
const NONE = {
|
|
307
|
-
shiftKey: false,
|
|
308
|
-
altKey: false,
|
|
309
|
-
ctrlKey: false,
|
|
310
|
-
metaKey: false
|
|
311
|
-
};
|
|
312
|
-
const MODIFIER_STATE = {
|
|
313
|
-
shift: {
|
|
314
|
-
...NONE,
|
|
315
|
-
shiftKey: true
|
|
316
|
-
},
|
|
317
|
-
alt: {
|
|
318
|
-
...NONE,
|
|
319
|
-
altKey: true
|
|
320
|
-
},
|
|
321
|
-
ctrl: {
|
|
322
|
-
...NONE,
|
|
323
|
-
ctrlKey: true
|
|
324
|
-
},
|
|
325
|
-
meta: {
|
|
326
|
-
...NONE,
|
|
327
|
-
metaKey: true
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
/** Each entry of a modifier-aware input option, as a name and held keys. */
|
|
331
|
-
function entries(options) {
|
|
332
|
-
if (Array.isArray(options)) return [{
|
|
333
|
-
name: "",
|
|
334
|
-
modifiers: NONE
|
|
335
|
-
}];
|
|
336
|
-
return Object.keys(options).map((key) => ({
|
|
337
|
-
name: key === "default" ? "" : key,
|
|
338
|
-
modifiers: MODIFIER_STATE[key] ?? NONE
|
|
339
|
-
}));
|
|
340
|
-
}
|
|
264
|
+
//#region src/hooks/_internal/useChangeGesture.ts
|
|
341
265
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
266
|
+
* The core's change gesture, reporting the control's value to
|
|
267
|
+
* `onChangeStart` / `onChangeEnd`. The start gets the value before the change,
|
|
268
|
+
* the end the last one reported.
|
|
344
269
|
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* the pipeline is the only thing that knows how.
|
|
270
|
+
* Internal
|
|
271
|
+
* @private
|
|
348
272
|
*/
|
|
349
|
-
function
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
273
|
+
function useChangeGesture(value, { onChangeStart, onChangeEnd, changeEndDelay }, inactive) {
|
|
274
|
+
const [instance] = (0, react.useState)(() => (0, _tremolo_ui_dom.createChangeGesture)());
|
|
275
|
+
const latest = (0, react.useRef)({
|
|
276
|
+
value,
|
|
277
|
+
onChangeStart,
|
|
278
|
+
onChangeEnd
|
|
279
|
+
});
|
|
280
|
+
const lastValue = (0, react.useRef)(value);
|
|
281
|
+
(0, react.useInsertionEffect)(() => {
|
|
282
|
+
latest.current = {
|
|
283
|
+
value,
|
|
284
|
+
onChangeStart,
|
|
285
|
+
onChangeEnd
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
(0, react.useEffect)(() => {
|
|
289
|
+
instance.update({
|
|
290
|
+
endDelay: changeEndDelay,
|
|
291
|
+
onStart: (source) => {
|
|
292
|
+
lastValue.current = latest.current.value;
|
|
293
|
+
latest.current.onChangeStart?.(latest.current.value, source);
|
|
294
|
+
},
|
|
295
|
+
onEnd: (source) => latest.current.onChangeEnd?.(lastValue.current, source)
|
|
296
|
+
});
|
|
297
|
+
}, [instance, changeEndDelay]);
|
|
298
|
+
(0, react.useEffect)(() => {
|
|
299
|
+
if (inactive) instance.end();
|
|
300
|
+
}, [instance, inactive]);
|
|
301
|
+
(0, react.useEffect)(() => () => instance.end(), [instance]);
|
|
302
|
+
const changed = (0, react.useCallback)((next) => {
|
|
303
|
+
lastValue.current = next;
|
|
304
|
+
}, []);
|
|
305
|
+
return (0, react.useMemo)(() => ({
|
|
306
|
+
hold: instance.hold,
|
|
307
|
+
pulse: instance.pulse,
|
|
308
|
+
instant: instance.instant,
|
|
309
|
+
end: instance.end,
|
|
310
|
+
active: instance.active,
|
|
311
|
+
changed
|
|
312
|
+
}), [instance, changed]);
|
|
365
313
|
}
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/hooks/_internal/useCheckSteps.ts
|
|
366
316
|
/**
|
|
367
317
|
* Warn, in development only, when a key press or a wheel notch cannot produce
|
|
368
|
-
* a change the user can see.
|
|
369
|
-
*
|
|
370
|
-
* Two settings that are each fine on their own can cancel out, and nothing
|
|
371
|
-
* fails when they do — the control simply sits there:
|
|
372
|
-
*
|
|
373
|
-
* - **`step` coarser than the amount.** `keyboard={['raw', 0.1]}` with
|
|
374
|
-
* `step={1}` rounds every press straight back to where it started
|
|
375
|
-
* - **the display coarser than the amount.** A `format` showing two decimals
|
|
376
|
-
* of a kHz value cannot show a press worth 1 Hz
|
|
377
|
-
*
|
|
378
|
-
* The second is only reported when the press is invisible at *every* point
|
|
379
|
-
* along the travel. A display that rounds is a deliberate choice and is
|
|
380
|
-
* normally right — it is being too coarse everywhere that makes it a mistake.
|
|
318
|
+
* a change the user can see. The probing is `checkSteps` in the core.
|
|
381
319
|
*
|
|
382
320
|
* @internal
|
|
383
321
|
*/
|
|
384
|
-
function useCheckSteps(
|
|
322
|
+
function useCheckSteps(options) {
|
|
323
|
+
const { component, axis, range, keyboard, wheel } = options;
|
|
385
324
|
const { min, max, step, scale } = range ?? {};
|
|
386
|
-
const where = axis ? `${component} (${axis})` : component;
|
|
387
325
|
(0, react.useEffect)(() => {
|
|
388
326
|
try {
|
|
389
327
|
if (process.env.NODE_ENV === "production") return;
|
|
390
|
-
|
|
391
|
-
for (const [name, options] of [["keyboard", keyboard], ["wheel", wheel]]) {
|
|
392
|
-
if (!options) continue;
|
|
393
|
-
const { moved, visible } = probe(options, range, format);
|
|
394
|
-
if (!moved) console.warn(`[tremolo-ui] ${where}: \`${name}\` cannot move the value.` + (step !== void 0 ? ` Each press is smaller than \`step\` (${step}), so it rounds` : " Each press rounds") + " straight back to where it started.");
|
|
395
|
-
else if (format && !visible) console.warn(`[tremolo-ui] ${where}: \`${name}\` moves the value, but \`format\` shows the same text before and after, everywhere in the range. The display is too coarse for it to be seen.`);
|
|
396
|
-
}
|
|
328
|
+
for (const warning of (0, _tremolo_ui_dom_internal.checkSteps)(options)) console.warn(warning);
|
|
397
329
|
} catch {}
|
|
398
330
|
}, [
|
|
399
|
-
|
|
331
|
+
component,
|
|
332
|
+
axis,
|
|
400
333
|
min,
|
|
401
334
|
max,
|
|
402
335
|
step,
|
|
@@ -493,7 +426,10 @@ function useWheel(onWheel, { requireFocus = false, target } = {}) {
|
|
|
493
426
|
(0, react.useEffect)(() => {
|
|
494
427
|
const element = target ? target.current : node;
|
|
495
428
|
if (!element) return;
|
|
496
|
-
const instance = (0, _tremolo_ui_dom.createWheel)(element,
|
|
429
|
+
const instance = (0, _tremolo_ui_dom.createWheel)(element, {
|
|
430
|
+
onWheel: (event) => wheelHandler(event),
|
|
431
|
+
requireFocus
|
|
432
|
+
});
|
|
497
433
|
return () => instance.destroy();
|
|
498
434
|
}, [
|
|
499
435
|
node,
|
|
@@ -504,49 +440,6 @@ function useWheel(onWheel, { requireFocus = false, target } = {}) {
|
|
|
504
440
|
return setNode;
|
|
505
441
|
}
|
|
506
442
|
//#endregion
|
|
507
|
-
//#region src/input-event.ts
|
|
508
|
-
/**
|
|
509
|
-
* The keyboard amount used by Knob, NumberInput, Slider, and XYPad by default:
|
|
510
|
-
* 1 per press in the units of the value, and 0.1 with shift held. That is one
|
|
511
|
-
* `step` only while `step` is 1; a coarser `step` rounds 1 straight back, which
|
|
512
|
-
* `useCheckSteps` warns about.
|
|
513
|
-
*
|
|
514
|
-
* A modifier entry is not snapped to `step`, which is what lets the finer
|
|
515
|
-
* amount move at all — see `applyDelta` in `@tremolo-ui/dom`.
|
|
516
|
-
*/
|
|
517
|
-
const DEFAULT_KEYBOARD_OPTIONS = {
|
|
518
|
-
default: ["raw", 1],
|
|
519
|
-
shift: ["raw", .1]
|
|
520
|
-
};
|
|
521
|
-
/**
|
|
522
|
-
* The wheel amount used by Knob, NumberInput, Slider, and XYPad by default.
|
|
523
|
-
*
|
|
524
|
-
* Browsers turn shift+wheel into horizontal scrolling, which empties `deltaY`
|
|
525
|
-
* and fills `deltaX`, so no modifier is bound here.
|
|
526
|
-
*/
|
|
527
|
-
const DEFAULT_WHEEL_OPTIONS = ["raw", 1];
|
|
528
|
-
/**
|
|
529
|
-
* The drag sensitivity used by value controls by default. Shift makes the
|
|
530
|
-
* same movement cover a tenth of the range, matching the arrow keys.
|
|
531
|
-
*/
|
|
532
|
-
const DEFAULT_DRAG_SENSITIVITY = {
|
|
533
|
-
default: 1,
|
|
534
|
-
shift: .1
|
|
535
|
-
};
|
|
536
|
-
//#endregion
|
|
537
|
-
//#region src/components/_util/css-length.ts
|
|
538
|
-
/**
|
|
539
|
-
* A number as a CSS length, for a custom property.
|
|
540
|
-
*
|
|
541
|
-
* React appends `px` to a bare number for the properties it knows take a
|
|
542
|
-
* length, and a custom property is never one of them: `--size: 50` would come
|
|
543
|
-
* out as the invalid `50`. Anything already a string is passed through, so
|
|
544
|
-
* `'3rem'` and `'100%'` still work.
|
|
545
|
-
*/
|
|
546
|
-
function cssLength(value) {
|
|
547
|
-
return typeof value === "number" ? `${value}px` : value;
|
|
548
|
-
}
|
|
549
|
-
//#endregion
|
|
550
443
|
//#region src/components/_util/Placement.tsx
|
|
551
444
|
/**
|
|
552
445
|
* The subcomponent that established the current placement, if any.
|
|
@@ -588,49 +481,8 @@ function useCheckPlacement(child, parent) {
|
|
|
588
481
|
parent
|
|
589
482
|
]);
|
|
590
483
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
*
|
|
594
|
-
* 半径は線の太さの分だけ内側に取る必要があるが、太さは ActiveLine / InactiveLine が
|
|
595
|
-
* それぞれ持つため、座標はストアで先に計算せず各コンポーネント側で求める。
|
|
596
|
-
*/
|
|
597
|
-
function pointOnArc(angle, radius) {
|
|
598
|
-
return {
|
|
599
|
-
x: 50 + radius * Math.cos((0, _tremolo_ui_functions.radian)(angle - 90)),
|
|
600
|
-
y: 50 + radius * Math.sin((0, _tremolo_ui_functions.radian)(angle - 90))
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
/** 線の太さが viewBox からはみ出さないようにした半径 */
|
|
604
|
-
function arcRadius(strokeWidth) {
|
|
605
|
-
const width = typeof strokeWidth === "number" ? strokeWidth : Number.parseFloat(String(strokeWidth));
|
|
606
|
-
return Number.isFinite(width) ? 50 - width / 2 : 50;
|
|
607
|
-
}
|
|
608
|
-
/** Build an SVG path for an arc, splitting full turns into drawable segments. */
|
|
609
|
-
function arcPath(startAngle, endAngle, radius) {
|
|
610
|
-
const start = pointOnArc(startAngle, radius);
|
|
611
|
-
const sweep = endAngle - startAngle;
|
|
612
|
-
const segmentCount = Math.max(1, Math.ceil(Math.abs(sweep) / 180));
|
|
613
|
-
const segmentSweep = sweep / segmentCount;
|
|
614
|
-
let path = `M ${start.x} ${start.y}`;
|
|
615
|
-
for (let i = 1; i <= segmentCount; i += 1) {
|
|
616
|
-
const end = pointOnArc(startAngle + segmentSweep * i, radius);
|
|
617
|
-
path += ` A ${radius} ${radius} 0 0 ${segmentSweep >= 0 ? 1 : 0} ${end.x} ${end.y}`;
|
|
618
|
-
}
|
|
619
|
-
return path;
|
|
620
|
-
}
|
|
621
|
-
/** 設定から描画に必要な角度を導出する。レンダー中に呼ぶ。 */
|
|
622
|
-
function calcAngles({ value, min, max, scale, startValue, angleRange }) {
|
|
623
|
-
const p = scale.normalize(value, min, max);
|
|
624
|
-
const s = scale.normalize(startValue, min, max);
|
|
625
|
-
const r1 = -angleRange / 2;
|
|
626
|
-
return {
|
|
627
|
-
p,
|
|
628
|
-
r1,
|
|
629
|
-
r2: r1 + Math.min(p, s) * angleRange,
|
|
630
|
-
r3: r1 + Math.max(p, s) * angleRange,
|
|
631
|
-
r4: angleRange / 2
|
|
632
|
-
};
|
|
633
|
-
}
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/components/Knob/context.tsx
|
|
634
486
|
const KnobContext = /* @__PURE__ */ (0, react.createContext)(null);
|
|
635
487
|
const KnobProvider = KnobContext.Provider;
|
|
636
488
|
function useKnobContext(selector) {
|
|
@@ -644,10 +496,10 @@ function ActiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...pr
|
|
|
644
496
|
useCheckPlacement("Knob.ActiveLine", "Knob.SVGRoot");
|
|
645
497
|
const r2 = useKnobContext((s) => s.r2);
|
|
646
498
|
const r3 = useKnobContext((s) => s.r3);
|
|
647
|
-
const radius =
|
|
499
|
+
const radius = (0, _tremolo_ui_dom.knobArcRadius)(strokeWidth);
|
|
648
500
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
649
501
|
className,
|
|
650
|
-
d:
|
|
502
|
+
d: (0, _tremolo_ui_dom.knobArcPath)(r2, r3, radius),
|
|
651
503
|
fill: "none",
|
|
652
504
|
stroke,
|
|
653
505
|
strokeWidth,
|
|
@@ -665,17 +517,17 @@ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...
|
|
|
665
517
|
const r2 = useKnobContext((s) => s.r2);
|
|
666
518
|
const r3 = useKnobContext((s) => s.r3);
|
|
667
519
|
const r4 = useKnobContext((s) => s.r4);
|
|
668
|
-
const radius =
|
|
520
|
+
const radius = (0, _tremolo_ui_dom.knobArcRadius)(strokeWidth);
|
|
669
521
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [startValue > min && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
670
522
|
className,
|
|
671
|
-
d:
|
|
523
|
+
d: (0, _tremolo_ui_dom.knobArcPath)(r1, r2, radius),
|
|
672
524
|
fill: "none",
|
|
673
525
|
stroke,
|
|
674
526
|
strokeWidth,
|
|
675
527
|
...props
|
|
676
528
|
}), startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
677
529
|
className,
|
|
678
|
-
d:
|
|
530
|
+
d: (0, _tremolo_ui_dom.knobArcPath)(r3, r4, radius),
|
|
679
531
|
fill: "none",
|
|
680
532
|
stroke,
|
|
681
533
|
strokeWidth,
|
|
@@ -686,7 +538,7 @@ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...
|
|
|
686
538
|
//#region src/components/Knob/SVGRoot.tsx
|
|
687
539
|
function SVGRoot({ children, style, ...props }) {
|
|
688
540
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
689
|
-
viewBox: `0 0
|
|
541
|
+
viewBox: `0 0 ${_tremolo_ui_dom.KNOB_VIEWBOX_SIZE} ${_tremolo_ui_dom.KNOB_VIEWBOX_SIZE}`,
|
|
690
542
|
style: {
|
|
691
543
|
display: "block",
|
|
692
544
|
...style
|
|
@@ -700,7 +552,7 @@ function SVGRoot({ children, style, ...props }) {
|
|
|
700
552
|
}
|
|
701
553
|
//#endregion
|
|
702
554
|
//#region src/components/Knob/Thumb.tsx
|
|
703
|
-
function Thumb$2({ className,
|
|
555
|
+
function Thumb$2({ className, color = "currentColor", lineColor = "currentColor", size = 84, lineWeight = 6, lineLength = 35, classes, ...props }) {
|
|
704
556
|
useCheckPlacement("Knob.Thumb", "Knob.SVGRoot");
|
|
705
557
|
const angleRange = useKnobContext((s) => s.angleRange);
|
|
706
558
|
const p = useKnobContext((s) => s.p);
|
|
@@ -711,16 +563,16 @@ function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor"
|
|
|
711
563
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
712
564
|
cx: "50%",
|
|
713
565
|
cy: "50%",
|
|
714
|
-
r: `${
|
|
715
|
-
fill:
|
|
566
|
+
r: `${size / 2}%`,
|
|
567
|
+
fill: color
|
|
716
568
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
717
|
-
className: classes?.
|
|
569
|
+
className: classes?.line,
|
|
718
570
|
x1: "50%",
|
|
719
|
-
y1: `${(
|
|
571
|
+
y1: `${(_tremolo_ui_dom.KNOB_VIEWBOX_SIZE - (0, _tremolo_ui_functions.clamp)(size, 0, 100)) / 2}%`,
|
|
720
572
|
x2: "50%",
|
|
721
|
-
y2: `${
|
|
722
|
-
stroke:
|
|
723
|
-
strokeWidth: `${
|
|
573
|
+
y2: `${lineLength}%`,
|
|
574
|
+
stroke: lineColor,
|
|
575
|
+
strokeWidth: `${lineWeight}%`,
|
|
724
576
|
style: {
|
|
725
577
|
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
726
578
|
transformOrigin: "50% 50%"
|
|
@@ -730,7 +582,6 @@ function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor"
|
|
|
730
582
|
}
|
|
731
583
|
//#endregion
|
|
732
584
|
//#region src/components/Knob/index.tsx
|
|
733
|
-
const defaultExternalStyles$2 = { cursor: "grabbing" };
|
|
734
585
|
/**
|
|
735
586
|
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
736
587
|
* the control does not change its value.
|
|
@@ -740,13 +591,9 @@ const WHEEL_OPTIONS$2 = { requireFocus: true };
|
|
|
740
591
|
* Interactive rotary knob component implemented in SVG.
|
|
741
592
|
*/
|
|
742
593
|
const Knob = {
|
|
743
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale,
|
|
594
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, resetValue, startValue = min, size, dragCursor = "grabbing", wheel = _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS, keyboard = _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, pointerLock = false, disabled = false, readOnly = false, angleRange = 270, onChange, onChangeStart, onChangeEnd, changeEndDelay, onKeyDown, onPointerDown, onDoubleClick, className, style, children, actionsRef, ...props }, forwardedRef) => {
|
|
744
595
|
const elmRef = (0, react.useRef)(null);
|
|
745
|
-
const
|
|
746
|
-
...defaultExternalStyles$2,
|
|
747
|
-
..._externalStyles
|
|
748
|
-
};
|
|
749
|
-
const inactive = disabled || readonly;
|
|
596
|
+
const inactive = disabled || readOnly;
|
|
750
597
|
const range = (0, react.useMemo)(() => ({
|
|
751
598
|
min,
|
|
752
599
|
max,
|
|
@@ -764,24 +611,30 @@ const Knob = {
|
|
|
764
611
|
keyboard,
|
|
765
612
|
wheel
|
|
766
613
|
});
|
|
614
|
+
const gesture = useChangeGesture(value, {
|
|
615
|
+
onChangeStart,
|
|
616
|
+
onChangeEnd,
|
|
617
|
+
changeEndDelay
|
|
618
|
+
}, inactive);
|
|
619
|
+
const change = (0, react.useCallback)((next) => {
|
|
620
|
+
gesture.changed(next);
|
|
621
|
+
onChange?.(next);
|
|
622
|
+
}, [gesture, onChange]);
|
|
767
623
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
768
624
|
if (!keyboard || !onChange || inactive) return;
|
|
769
|
-
const
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
"ArrowDown"
|
|
775
|
-
].includes(key)) {
|
|
776
|
-
event.preventDefault();
|
|
777
|
-
onChange((0, _tremolo_ui_dom.applyDelta)(value, key === "ArrowRight" || key === "ArrowUp" ? 1 : -1, keyboard, range, event));
|
|
778
|
-
}
|
|
625
|
+
const direction = (0, _tremolo_ui_dom_internal.arrowKeyDirection)(event.key);
|
|
626
|
+
if (direction === null) return;
|
|
627
|
+
event.preventDefault();
|
|
628
|
+
gesture.pulse("keyboard");
|
|
629
|
+
change((0, _tremolo_ui_dom_internal.applyDelta)(value, direction, keyboard, range, event));
|
|
779
630
|
}, [
|
|
780
631
|
keyboard,
|
|
781
632
|
onChange,
|
|
782
633
|
inactive,
|
|
783
634
|
value,
|
|
784
|
-
range
|
|
635
|
+
range,
|
|
636
|
+
gesture,
|
|
637
|
+
change
|
|
785
638
|
]);
|
|
786
639
|
const { refCallback: dragRefCallback, dragging } = useDragValue({
|
|
787
640
|
axis: (0, react.useMemo)(() => [range, {
|
|
@@ -789,23 +642,30 @@ const Knob = {
|
|
|
789
642
|
reverse: true
|
|
790
643
|
}], [range]),
|
|
791
644
|
getValue: () => [value, value],
|
|
792
|
-
sensitivity: (state) => (0,
|
|
645
|
+
sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(dragSensitivity, state.event).value,
|
|
793
646
|
threshold: 1,
|
|
794
|
-
cursor: inactive ? void 0 :
|
|
647
|
+
cursor: inactive ? void 0 : dragCursor,
|
|
795
648
|
pointerLock: inactive ? false : pointerLock,
|
|
796
649
|
shouldStart: () => !inactive,
|
|
797
650
|
onChange: (v) => {
|
|
798
651
|
if (inactive) return;
|
|
799
|
-
|
|
800
|
-
}
|
|
652
|
+
change(v[1]);
|
|
653
|
+
},
|
|
654
|
+
onDragStart: () => {
|
|
655
|
+
if (!inactive) gesture.hold("pointer");
|
|
656
|
+
},
|
|
657
|
+
onDragEnd: () => gesture.end()
|
|
801
658
|
});
|
|
802
659
|
const wheelRefCallback = useWheel((event) => {
|
|
803
660
|
if (!wheel || inactive) return;
|
|
661
|
+
const direction = (0, _tremolo_ui_dom_internal.wheelDirection)(event);
|
|
662
|
+
if (direction === null) return;
|
|
804
663
|
event.preventDefault();
|
|
805
|
-
if (!onChange
|
|
806
|
-
|
|
664
|
+
if (!onChange) return;
|
|
665
|
+
gesture.pulse("wheel");
|
|
666
|
+
change((0, _tremolo_ui_dom_internal.applyDelta)(value, direction, wheel, range, event));
|
|
807
667
|
}, WHEEL_OPTIONS$2);
|
|
808
|
-
const rootRefCallback = require_compose_refs.useComposedRefs(elmRef, dragRefCallback, wheelRefCallback);
|
|
668
|
+
const rootRefCallback = require_compose_refs.useComposedRefs(forwardedRef, elmRef, dragRefCallback, wheelRefCallback);
|
|
809
669
|
const context = (0, react.useMemo)(() => {
|
|
810
670
|
const config = {
|
|
811
671
|
value,
|
|
@@ -818,7 +678,7 @@ const Knob = {
|
|
|
818
678
|
};
|
|
819
679
|
return {
|
|
820
680
|
...config,
|
|
821
|
-
...
|
|
681
|
+
...(0, _tremolo_ui_dom.knobAngles)(config)
|
|
822
682
|
};
|
|
823
683
|
}, [
|
|
824
684
|
value,
|
|
@@ -829,7 +689,7 @@ const Knob = {
|
|
|
829
689
|
startValue,
|
|
830
690
|
angleRange
|
|
831
691
|
]);
|
|
832
|
-
(0, react.useImperativeHandle)(
|
|
692
|
+
(0, react.useImperativeHandle)(actionsRef, () => {
|
|
833
693
|
return {
|
|
834
694
|
focus() {
|
|
835
695
|
if (!disabled) elmRef.current?.focus();
|
|
@@ -850,17 +710,17 @@ const Knob = {
|
|
|
850
710
|
"aria-valuemin": min,
|
|
851
711
|
"aria-valuemax": max,
|
|
852
712
|
"aria-disabled": disabled,
|
|
853
|
-
"aria-readonly":
|
|
713
|
+
"aria-readonly": readOnly,
|
|
854
714
|
"data-disabled": disabled ? "" : void 0,
|
|
855
|
-
"data-readonly":
|
|
715
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
856
716
|
"data-dragging": dragging ? "" : void 0,
|
|
857
717
|
style: {
|
|
858
|
-
"--knob-size": cssLength(size),
|
|
718
|
+
"--knob-size": (0, _tremolo_ui_dom_internal.cssLength)(size),
|
|
859
719
|
...style
|
|
860
720
|
},
|
|
861
721
|
onPointerDown,
|
|
862
722
|
onDoubleClick: (event) => {
|
|
863
|
-
if (!inactive &&
|
|
723
|
+
if (!inactive && resetValue !== null && onChange) gesture.instant("doubleClick", () => change(resetValue ?? startValue));
|
|
864
724
|
onDoubleClick?.(event);
|
|
865
725
|
},
|
|
866
726
|
onKeyDown: (event) => {
|
|
@@ -886,88 +746,47 @@ function useNumberInputContext(selector) {
|
|
|
886
746
|
if (!context) throw new Error("Missing NumberInputContext.Provider in the tree");
|
|
887
747
|
return selector ? selector(context) : context;
|
|
888
748
|
}
|
|
749
|
+
const NumberInputGestureContext = /* @__PURE__ */ (0, react.createContext)(null);
|
|
750
|
+
const NumberInputGestureProvider = NumberInputGestureContext.Provider;
|
|
751
|
+
function useNumberInputGesture() {
|
|
752
|
+
return (0, react.useContext)(NumberInputGestureContext);
|
|
753
|
+
}
|
|
889
754
|
const StepperContext = /* @__PURE__ */ (0, react.createContext)(null);
|
|
890
755
|
const StepperProvider = StepperContext.Provider;
|
|
891
756
|
function useStepperContext() {
|
|
892
757
|
return (0, react.useContext)(StepperContext);
|
|
893
758
|
}
|
|
894
759
|
//#endregion
|
|
895
|
-
//#region src/hooks/
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
cleanupRef.current = cleanup;
|
|
907
|
-
return () => {
|
|
908
|
-
cleanup();
|
|
909
|
-
if (cleanupRef.current === cleanup) cleanupRef.current = () => {};
|
|
910
|
-
};
|
|
911
|
-
}, [
|
|
912
|
-
event,
|
|
913
|
-
target,
|
|
914
|
-
options,
|
|
915
|
-
listener
|
|
916
|
-
]);
|
|
917
|
-
return (0, react.useCallback)(() => cleanupRef.current(), []);
|
|
918
|
-
}
|
|
919
|
-
//#endregion
|
|
920
|
-
//#region src/hooks/useInterval.ts
|
|
921
|
-
function useInterval(callback, delay) {
|
|
922
|
-
const savedCallback = (0, react.useRef)(callback);
|
|
760
|
+
//#region src/hooks/useLongPress.ts
|
|
761
|
+
/**
|
|
762
|
+
* Repeat `callback` while a pointer is held down: once on the press, then
|
|
763
|
+
* every `interval` after `initialDelay`. The repeat itself is
|
|
764
|
+
* `createLongPress` in `@tremolo-ui/dom`.
|
|
765
|
+
*
|
|
766
|
+
* Returns the function that starts a press, for an `onPointerDown`.
|
|
767
|
+
*/
|
|
768
|
+
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
769
|
+
const onPress = useCallbackRef(callback);
|
|
770
|
+
const instanceRef = (0, react.useRef)(null);
|
|
923
771
|
(0, react.useEffect)(() => {
|
|
924
|
-
|
|
925
|
-
|
|
772
|
+
instanceRef.current?.update({
|
|
773
|
+
delay: initialDelay,
|
|
774
|
+
interval
|
|
775
|
+
});
|
|
776
|
+
});
|
|
926
777
|
(0, react.useEffect)(() => {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
778
|
+
const instance = (0, _tremolo_ui_dom.createLongPress)({
|
|
779
|
+
onPress: () => onPress(),
|
|
780
|
+
delay: initialDelay,
|
|
781
|
+
interval
|
|
782
|
+
});
|
|
783
|
+
instanceRef.current = instance;
|
|
931
784
|
return () => {
|
|
932
|
-
|
|
785
|
+
instanceRef.current = null;
|
|
786
|
+
instance.destroy();
|
|
933
787
|
};
|
|
934
|
-
}, [
|
|
935
|
-
|
|
936
|
-
//#endregion
|
|
937
|
-
//#region src/hooks/useLongPress.ts
|
|
938
|
-
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
939
|
-
const [pressed, setPressed] = (0, react.useState)(false);
|
|
940
|
-
const [delay, setDelay] = (0, react.useState)(initialDelay);
|
|
941
|
-
const activePointerId = (0, react.useRef)(null);
|
|
942
|
-
const isPressed = (0, react.useRef)(false);
|
|
943
|
-
useInterval(() => {
|
|
944
|
-
callback();
|
|
945
|
-
setDelay(interval);
|
|
946
|
-
}, pressed ? delay : null);
|
|
947
|
-
const stop = (0, react.useCallback)((pointerId) => {
|
|
948
|
-
if (!isPressed.current) return;
|
|
949
|
-
if (pointerId !== void 0 && activePointerId.current !== null && activePointerId.current !== pointerId) return;
|
|
950
|
-
isPressed.current = false;
|
|
951
|
-
activePointerId.current = null;
|
|
952
|
-
setPressed(false);
|
|
953
|
-
setDelay(initialDelay);
|
|
954
|
-
}, [initialDelay]);
|
|
955
|
-
useEventListener(globalThis.window, "pointerup", (event) => {
|
|
956
|
-
stop(event.pointerId);
|
|
957
|
-
});
|
|
958
|
-
useEventListener(globalThis.window, "pointercancel", (event) => {
|
|
959
|
-
stop(event.pointerId);
|
|
960
|
-
});
|
|
961
|
-
useEventListener(globalThis.window, "blur", () => {
|
|
962
|
-
stop();
|
|
963
|
-
});
|
|
964
|
-
return (0, react.useCallback)((event) => {
|
|
965
|
-
if (isPressed.current || event && event.button !== 0) return;
|
|
966
|
-
isPressed.current = true;
|
|
967
|
-
activePointerId.current = event?.pointerId ?? null;
|
|
968
|
-
callback();
|
|
969
|
-
setPressed(true);
|
|
970
|
-
}, [callback]);
|
|
788
|
+
}, []);
|
|
789
|
+
return (0, react.useCallback)((event) => instanceRef.current?.start(event), []);
|
|
971
790
|
}
|
|
972
791
|
//#endregion
|
|
973
792
|
//#region src/components/NumberInput/StepperButton.tsx
|
|
@@ -976,10 +795,11 @@ function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
|
976
795
|
* in which way they move the value and which arrow they draw.
|
|
977
796
|
*/
|
|
978
797
|
function StepperButton({ direction, variant, icon, className, style, children, onPointerDown, ...props }) {
|
|
979
|
-
const { step, disabled,
|
|
798
|
+
const { step, disabled, readOnly, atMin, atMax, nudge } = useNumberInputContext();
|
|
980
799
|
const stepper = useStepperContext();
|
|
800
|
+
const gesture = useNumberInputGesture();
|
|
981
801
|
const press = useLongPress(() => {
|
|
982
|
-
if (stepper?.
|
|
802
|
+
if (stepper?.moved()) return;
|
|
983
803
|
nudge(direction, ["raw", step]);
|
|
984
804
|
});
|
|
985
805
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -989,10 +809,13 @@ function StepperButton({ direction, variant, icon, className, style, children, o
|
|
|
989
809
|
"aria-label": direction > 0 ? "Increment" : "Decrement",
|
|
990
810
|
"aria-disabled": disabled || (direction > 0 ? atMax : atMin),
|
|
991
811
|
"data-disabled": disabled || (direction > 0 ? atMax : atMin) ? "" : void 0,
|
|
992
|
-
"data-readonly":
|
|
812
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
993
813
|
style,
|
|
994
814
|
onPointerDown: (event) => {
|
|
995
|
-
if (!disabled && !
|
|
815
|
+
if (!disabled && !readOnly) {
|
|
816
|
+
gesture?.press();
|
|
817
|
+
press(event);
|
|
818
|
+
}
|
|
996
819
|
onPointerDown?.(event);
|
|
997
820
|
},
|
|
998
821
|
...props,
|
|
@@ -1038,21 +861,6 @@ function IncrementStepper(props) {
|
|
|
1038
861
|
}
|
|
1039
862
|
//#endregion
|
|
1040
863
|
//#region src/components/NumberInput/InputField.tsx
|
|
1041
|
-
/** The leading number of the displayed text, whatever the format put around it. */
|
|
1042
|
-
const NUMBER_PREFIX = /^\s*-?[\d.,]*/;
|
|
1043
|
-
const numberEnd = (text) => text.match(NUMBER_PREFIX)?.[0].length ?? text.length;
|
|
1044
|
-
/**
|
|
1045
|
-
* The index the caret is measured against: the decimal point, or where one
|
|
1046
|
-
* would go if the number has none.
|
|
1047
|
-
*
|
|
1048
|
-
* Measuring from an end instead would slide the caret across a digit whenever
|
|
1049
|
-
* the number changed length — `9.9` to `10.0` gains a character in front, `10`
|
|
1050
|
-
* to `9` loses one — which is exactly what stepping does.
|
|
1051
|
-
*/
|
|
1052
|
-
const decimalAnchor = (text) => {
|
|
1053
|
-
const dot = text.indexOf(".");
|
|
1054
|
-
return dot === -1 ? numberEnd(text) : dot;
|
|
1055
|
-
};
|
|
1056
864
|
/**
|
|
1057
865
|
* The text field of a `NumberInput`, and the only place the value can be typed.
|
|
1058
866
|
*
|
|
@@ -1062,8 +870,9 @@ const decimalAnchor = (text) => {
|
|
|
1062
870
|
* How it behaves on focus, Enter and the arrow keys is set on `Root`
|
|
1063
871
|
* (`selectOnFocus`, `unformatOnFocus`, `keepCaretOnStep`, `blurOnEnter`).
|
|
1064
872
|
*/
|
|
1065
|
-
const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ className, style, onFocus, onBlur, onKeyDown, ...props }, forwardedRef) {
|
|
1066
|
-
const { value, min, max, step, disabled,
|
|
873
|
+
const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ className, style, onChange, onFocus, onBlur, onKeyDown, ...props }, forwardedRef) {
|
|
874
|
+
const { value, min, max, step, disabled, readOnly, keyboard, selectOnFocus, unformatOnFocus, keepCaretOnStep, blurOnEnter, text, editing, outOfRange, setDraft, commitDraft, nudge, inputRef } = useNumberInputContext();
|
|
875
|
+
const gesture = useNumberInputGesture();
|
|
1067
876
|
const [focused, setFocused] = (0, react.useState)(false);
|
|
1068
877
|
const shown = unformatOnFocus && focused && !editing ? String(value) : text;
|
|
1069
878
|
(0, react.useEffect)(() => {
|
|
@@ -1071,7 +880,10 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1071
880
|
const input = inputRef.current;
|
|
1072
881
|
if (!input) return;
|
|
1073
882
|
if (selectOnFocus === "all") input.setSelectionRange(0, input.value.length);
|
|
1074
|
-
else
|
|
883
|
+
else {
|
|
884
|
+
const span = (0, _tremolo_ui_dom_internal.numberSpan)(input.value);
|
|
885
|
+
if (span) input.setSelectionRange(span.start, span.end);
|
|
886
|
+
}
|
|
1075
887
|
}, [
|
|
1076
888
|
focused,
|
|
1077
889
|
selectOnFocus,
|
|
@@ -1088,8 +900,7 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1088
900
|
caretAfterStep.current = null;
|
|
1089
901
|
const input = inputRef.current;
|
|
1090
902
|
if (!input || input.value === pending.from) return;
|
|
1091
|
-
const
|
|
1092
|
-
const caret = Math.max(0, Math.min(place, numberEnd(input.value)));
|
|
903
|
+
const caret = (0, _tremolo_ui_dom_internal.caretAtDecimalOffset)(input.value, pending.offset);
|
|
1093
904
|
input.setSelectionRange(caret, caret);
|
|
1094
905
|
});
|
|
1095
906
|
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, inputRef);
|
|
@@ -1101,11 +912,11 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1101
912
|
role: "spinbutton",
|
|
1102
913
|
value: shown,
|
|
1103
914
|
disabled,
|
|
1104
|
-
readOnly
|
|
915
|
+
readOnly,
|
|
1105
916
|
"aria-disabled": disabled,
|
|
1106
|
-
"aria-readonly":
|
|
917
|
+
"aria-readonly": readOnly,
|
|
1107
918
|
"data-disabled": disabled ? "" : void 0,
|
|
1108
|
-
"data-readonly":
|
|
919
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
1109
920
|
"aria-valuenow": value,
|
|
1110
921
|
"aria-valuemin": min,
|
|
1111
922
|
"aria-valuemax": max,
|
|
@@ -1116,6 +927,7 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1116
927
|
onChange: (event) => {
|
|
1117
928
|
caretAfterStep.current = null;
|
|
1118
929
|
setDraft(event.currentTarget.value);
|
|
930
|
+
onChange?.(event);
|
|
1119
931
|
},
|
|
1120
932
|
onFocus: (event) => {
|
|
1121
933
|
setFocused(true);
|
|
@@ -1131,13 +943,14 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1131
943
|
if (key === "Enter") {
|
|
1132
944
|
commitDraft();
|
|
1133
945
|
if (blurOnEnter) event.currentTarget.blur();
|
|
1134
|
-
} else if (keyboard && !disabled && !
|
|
946
|
+
} else if (keyboard && !disabled && !readOnly && (key === "ArrowUp" || key === "ArrowDown") && !event.nativeEvent.isComposing) {
|
|
1135
947
|
event.preventDefault();
|
|
1136
948
|
const input = event.currentTarget;
|
|
1137
949
|
if (keepCaretOnStep && input.selectionStart !== null) caretAfterStep.current = {
|
|
1138
|
-
offset: input.
|
|
950
|
+
offset: (0, _tremolo_ui_dom_internal.caretDecimalOffset)(input.value, input.selectionStart),
|
|
1139
951
|
from: input.value
|
|
1140
952
|
};
|
|
953
|
+
gesture?.key();
|
|
1141
954
|
nudge(key === "ArrowUp" ? 1 : -1, keyboard, event);
|
|
1142
955
|
}
|
|
1143
956
|
onKeyDown?.(event);
|
|
@@ -1146,123 +959,66 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1146
959
|
});
|
|
1147
960
|
});
|
|
1148
961
|
//#endregion
|
|
1149
|
-
//#region src/hooks/useDrag.ts
|
|
1150
|
-
/**
|
|
1151
|
-
* Track a pointer drag on an element.
|
|
1152
|
-
*
|
|
1153
|
-
* @returns a ref callback to attach to the element being dragged
|
|
1154
|
-
*/
|
|
1155
|
-
function useDrag({ threshold = 1, cursor, pointerLock, onDrag, onDragStart, onDragEnd }) {
|
|
1156
|
-
const dragHandler = useCallbackRef(onDrag);
|
|
1157
|
-
const dragStartHandler = useCallbackRef(onDragStart);
|
|
1158
|
-
const dragEndHandler = useCallbackRef(onDragEnd);
|
|
1159
|
-
const [node, setNode] = (0, react.useState)(null);
|
|
1160
|
-
(0, react.useEffect)(() => {
|
|
1161
|
-
if (!node) return;
|
|
1162
|
-
const instance = (0, _tremolo_ui_dom.createDrag)(node, {
|
|
1163
|
-
threshold,
|
|
1164
|
-
cursor,
|
|
1165
|
-
pointerLock,
|
|
1166
|
-
onDragStart: (state) => dragStartHandler(state),
|
|
1167
|
-
onDrag: (state) => dragHandler(state.x, state.y, state.deltaX, state.deltaY, state),
|
|
1168
|
-
onDragEnd: (state) => dragEndHandler(state)
|
|
1169
|
-
});
|
|
1170
|
-
return () => instance.destroy();
|
|
1171
|
-
}, [
|
|
1172
|
-
node,
|
|
1173
|
-
threshold,
|
|
1174
|
-
cursor,
|
|
1175
|
-
pointerLock,
|
|
1176
|
-
dragHandler,
|
|
1177
|
-
dragStartHandler,
|
|
1178
|
-
dragEndHandler
|
|
1179
|
-
]);
|
|
1180
|
-
return setNode;
|
|
1181
|
-
}
|
|
1182
|
-
//#endregion
|
|
1183
962
|
//#region src/components/NumberInput/Stepper.tsx
|
|
1184
963
|
/**
|
|
1185
964
|
* The area the steppers sit in, and a drag handle in its own right: dragging it
|
|
1186
|
-
* up and down moves the value one `step` every `drag` pixels.
|
|
965
|
+
* up and down moves the value one `step` every `drag` pixels. The counting is
|
|
966
|
+
* `createStepperDrag` in the core.
|
|
1187
967
|
*
|
|
1188
968
|
* The drag lives here rather than on `InputField` because `createDrag` turns
|
|
1189
969
|
* off text selection on whatever element it is attached to.
|
|
1190
970
|
*/
|
|
1191
|
-
const Stepper = /* @__PURE__ */ (0, react.forwardRef)(function Stepper({ className, style, children, ...props }, forwardedRef) {
|
|
1192
|
-
const { value,
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
* off the grid.
|
|
1200
|
-
*/
|
|
1201
|
-
const dragOptions = (0, react.useMemo)(() => (0, _tremolo_ui_dom.mapModifier)(dragSensitivity, (factor) => ["raw", step * factor]), [dragSensitivity, step]);
|
|
1202
|
-
/**
|
|
1203
|
-
* Set once the drag has actually moved the value, so that the press-and-hold
|
|
1204
|
-
* repeat of a stepper stands down and leaves the value to the drag. A ref,
|
|
1205
|
-
* because it is read from inside the repeat rather than rendered.
|
|
1206
|
-
*/
|
|
1207
|
-
const draggingRef = (0, react.useRef)(false);
|
|
1208
|
-
/**
|
|
1209
|
-
* Where the drag started, taken on the first move rather than on pointer
|
|
1210
|
-
* down: the steppers act on pointer down, so by then the value may already
|
|
1211
|
-
* have been nudged once, and the drag should carry on from there.
|
|
1212
|
-
*/
|
|
1213
|
-
const originRef = (0, react.useRef)(null);
|
|
1214
|
-
/**
|
|
1215
|
-
* Which modifier the drag is currently counting at, and where the previous
|
|
1216
|
-
* event was — a key produces no pointer event of its own, so a change is
|
|
1217
|
-
* only seen on the next move and has to be dated back to the one before it.
|
|
1218
|
-
*/
|
|
1219
|
-
const factorRef = (0, react.useRef)(1);
|
|
1220
|
-
const previousYRef = (0, react.useRef)(0);
|
|
1221
|
-
const dragRefCallback = useDrag({
|
|
1222
|
-
threshold: 1,
|
|
1223
|
-
cursor: inactive ? void 0 : "ns-resize",
|
|
1224
|
-
pointerLock: inactive ? false : pointerLock,
|
|
1225
|
-
onDragStart: (state) => {
|
|
1226
|
-
originRef.current = null;
|
|
1227
|
-
draggingRef.current = false;
|
|
1228
|
-
factorRef.current = (0, _tremolo_ui_dom.selectModifier)(dragSensitivity, state.event).value;
|
|
1229
|
-
},
|
|
1230
|
-
onDrag: (_x, y, _dx, _dy, state) => {
|
|
1231
|
-
if (inactive || drag === null) return;
|
|
1232
|
-
if (!originRef.current) {
|
|
1233
|
-
originRef.current = {
|
|
1234
|
-
y,
|
|
1235
|
-
value
|
|
1236
|
-
};
|
|
1237
|
-
previousYRef.current = y;
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
const factor = (0, _tremolo_ui_dom.selectModifier)(dragSensitivity, state.event).value;
|
|
1241
|
-
if (factor !== factorRef.current) {
|
|
1242
|
-
originRef.current = {
|
|
1243
|
-
y: previousYRef.current,
|
|
1244
|
-
value
|
|
1245
|
-
};
|
|
1246
|
-
factorRef.current = factor;
|
|
1247
|
-
}
|
|
1248
|
-
previousYRef.current = y;
|
|
1249
|
-
const steps = Math.round(-(y - originRef.current.y) / drag);
|
|
1250
|
-
if (steps === 0) return;
|
|
1251
|
-
draggingRef.current = true;
|
|
1252
|
-
changeValue((0, _tremolo_ui_dom.applyDelta)(originRef.current.value, steps, dragOptions, rawRange, state.event));
|
|
1253
|
-
},
|
|
1254
|
-
onDragEnd: () => {
|
|
1255
|
-
draggingRef.current = false;
|
|
1256
|
-
}
|
|
971
|
+
const Stepper = /* @__PURE__ */ (0, react.forwardRef)(function Stepper({ className, style, children, onPointerDown, ...props }, forwardedRef) {
|
|
972
|
+
const { value, disabled, readOnly, drag, dragSensitivity, pointerLock, dragCursor, rawRange, changeValue } = useNumberInputContext();
|
|
973
|
+
const enabled = drag !== null && !disabled && !readOnly;
|
|
974
|
+
const [node, setNode] = (0, react.useState)(null);
|
|
975
|
+
const instanceRef = (0, react.useRef)(null);
|
|
976
|
+
const latest = (0, react.useRef)({
|
|
977
|
+
value,
|
|
978
|
+
changeValue
|
|
1257
979
|
});
|
|
1258
|
-
const
|
|
1259
|
-
|
|
980
|
+
const options = {
|
|
981
|
+
range: rawRange,
|
|
982
|
+
pixels: drag ?? 1,
|
|
983
|
+
sensitivity: dragSensitivity,
|
|
984
|
+
pointerLock,
|
|
985
|
+
cursor: dragCursor
|
|
986
|
+
};
|
|
987
|
+
const optionsRef = (0, react.useRef)(options);
|
|
988
|
+
(0, react.useEffect)(() => {
|
|
989
|
+
latest.current = {
|
|
990
|
+
value,
|
|
991
|
+
changeValue
|
|
992
|
+
};
|
|
993
|
+
optionsRef.current = options;
|
|
994
|
+
instanceRef.current?.update(options);
|
|
995
|
+
});
|
|
996
|
+
(0, react.useEffect)(() => {
|
|
997
|
+
if (!node || !enabled) return;
|
|
998
|
+
const instance = (0, _tremolo_ui_dom_internal.createStepperDrag)(node, {
|
|
999
|
+
...optionsRef.current,
|
|
1000
|
+
getValue: () => latest.current.value,
|
|
1001
|
+
onChange: (next) => latest.current.changeValue(next)
|
|
1002
|
+
});
|
|
1003
|
+
instanceRef.current = instance;
|
|
1004
|
+
return () => {
|
|
1005
|
+
instanceRef.current = null;
|
|
1006
|
+
instance.destroy();
|
|
1007
|
+
};
|
|
1008
|
+
}, [node, enabled]);
|
|
1009
|
+
const gesture = useNumberInputGesture();
|
|
1010
|
+
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, setNode);
|
|
1011
|
+
const context = (0, react.useMemo)(() => ({ moved: () => instanceRef.current?.moved() ?? false }), []);
|
|
1260
1012
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepperProvider, {
|
|
1261
1013
|
value: context,
|
|
1262
1014
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1263
1015
|
ref: composedRef,
|
|
1264
1016
|
className,
|
|
1265
1017
|
style,
|
|
1018
|
+
onPointerDown: (event) => {
|
|
1019
|
+
if (enabled) gesture?.press();
|
|
1020
|
+
onPointerDown?.(event);
|
|
1021
|
+
},
|
|
1266
1022
|
...props,
|
|
1267
1023
|
children
|
|
1268
1024
|
})
|
|
@@ -1270,48 +1026,32 @@ const Stepper = /* @__PURE__ */ (0, react.forwardRef)(function Stepper({ classNa
|
|
|
1270
1026
|
});
|
|
1271
1027
|
//#endregion
|
|
1272
1028
|
//#region src/components/NumberInput/index.tsx
|
|
1273
|
-
/**
|
|
1274
|
-
* A number at the start of the text, and nothing read after it. A half-typed
|
|
1275
|
-
* entry still yields the number in front of it, but text with no number is
|
|
1276
|
-
* `NaN` rather than 0, so that "unreadable" and "the user typed 0" stay apart.
|
|
1277
|
-
*/
|
|
1278
|
-
const LEADING_NUMBER = /^\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;
|
|
1279
1029
|
const defaultFormat = (value) => String(value);
|
|
1280
|
-
const defaultParse = (text) => {
|
|
1281
|
-
const match = text.match(LEADING_NUMBER);
|
|
1282
|
-
return match ? Number(match[1]) : NaN;
|
|
1283
|
-
};
|
|
1284
1030
|
/**
|
|
1285
1031
|
* Input with some useful functions for entering numerical values.
|
|
1286
1032
|
*/
|
|
1287
1033
|
const NumberInput = {
|
|
1288
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, format: formatProp, parse: parseProp, clampValue = true, wheel = DEFAULT_WHEEL_OPTIONS, keyboard = DEFAULT_KEYBOARD_OPTIONS, drag = 1, dragSensitivity = DEFAULT_DRAG_SENSITIVITY, pointerLock = false, selectOnFocus = "none", unformatOnFocus = false, keepCaretOnStep = false, blurOnEnter = true, disabled = false,
|
|
1034
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, format: formatProp, parse: parseProp, clampValue = true, wheel = _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS, keyboard = _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS, drag = 1, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, pointerLock = false, dragCursor = "ns-resize", selectOnFocus = "none", unformatOnFocus = false, keepCaretOnStep = false, blurOnEnter = true, disabled = false, readOnly = false, className, style, onChange, onChangeStart, onChangeEnd, changeEndDelay, children, actionsRef, ...props }, forwardedRef) => {
|
|
1289
1035
|
const inputRef = (0, react.useRef)(null);
|
|
1290
1036
|
/**
|
|
1291
1037
|
* The text being typed. The only state here: it is not a copy of `value`,
|
|
1292
1038
|
* but the half-finished entry that has no value to be derived from yet.
|
|
1293
1039
|
*/
|
|
1294
1040
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
1295
|
-
const inactive = disabled ||
|
|
1041
|
+
const inactive = disabled || readOnly;
|
|
1042
|
+
const gesture = useChangeGesture(value, {
|
|
1043
|
+
onChangeStart,
|
|
1044
|
+
onChangeEnd,
|
|
1045
|
+
changeEndDelay
|
|
1046
|
+
}, inactive);
|
|
1296
1047
|
const format = formatProp ?? defaultFormat;
|
|
1297
|
-
const parse = parseProp ??
|
|
1298
|
-
const range = (0, react.useMemo)(() => ({
|
|
1299
|
-
min: (clampValue ? min : void 0) ?? Number.MIN_SAFE_INTEGER,
|
|
1300
|
-
max: (clampValue ? max : void 0) ?? Number.MAX_SAFE_INTEGER,
|
|
1301
|
-
step,
|
|
1302
|
-
scale
|
|
1303
|
-
}), [
|
|
1304
|
-
clampValue,
|
|
1048
|
+
const parse = parseProp ?? _tremolo_ui_dom_internal.parseNumberText;
|
|
1049
|
+
const { normalized: range, raw: rawRange } = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.numberInputRanges)({
|
|
1305
1050
|
min,
|
|
1306
1051
|
max,
|
|
1307
1052
|
step,
|
|
1308
|
-
scale
|
|
1309
|
-
|
|
1310
|
-
const rawRange = (0, react.useMemo)(() => ({
|
|
1311
|
-
min: (clampValue ? min : void 0) ?? -Number.MAX_VALUE,
|
|
1312
|
-
max: (clampValue ? max : void 0) ?? Number.MAX_VALUE,
|
|
1313
|
-
step,
|
|
1314
|
-
scale
|
|
1053
|
+
scale,
|
|
1054
|
+
clampValue
|
|
1315
1055
|
}), [
|
|
1316
1056
|
clampValue,
|
|
1317
1057
|
min,
|
|
@@ -1319,6 +1059,10 @@ const NumberInput = {
|
|
|
1319
1059
|
step,
|
|
1320
1060
|
scale
|
|
1321
1061
|
]);
|
|
1062
|
+
const ranges = (0, react.useMemo)(() => ({
|
|
1063
|
+
normalized: range,
|
|
1064
|
+
raw: rawRange
|
|
1065
|
+
}), [range, rawRange]);
|
|
1322
1066
|
useCheckSteps({
|
|
1323
1067
|
component: "NumberInput",
|
|
1324
1068
|
range: (0, react.useMemo)(() => min !== void 0 && max !== void 0 && min < max ? {
|
|
@@ -1338,37 +1082,56 @@ const NumberInput = {
|
|
|
1338
1082
|
});
|
|
1339
1083
|
const text = draft ?? format(value);
|
|
1340
1084
|
const editing = draft !== null;
|
|
1341
|
-
const
|
|
1085
|
+
const bounds = (0, _tremolo_ui_dom_internal.numberInputBounds)(value, {
|
|
1086
|
+
min,
|
|
1087
|
+
max,
|
|
1088
|
+
clampValue
|
|
1089
|
+
});
|
|
1090
|
+
const { atMin, atMax } = bounds;
|
|
1091
|
+
const outOfRange = !editing && bounds.outOfRange;
|
|
1342
1092
|
const handleDraft = (0, react.useCallback)((next) => {
|
|
1343
1093
|
if (inactive) return;
|
|
1344
1094
|
setDraft(next);
|
|
1345
1095
|
const parsed = parse(next);
|
|
1346
|
-
if (Number.isFinite(parsed))
|
|
1096
|
+
if (!Number.isFinite(parsed)) return;
|
|
1097
|
+
if (parsed !== value) {
|
|
1098
|
+
gesture.pulse("keyboard");
|
|
1099
|
+
gesture.changed(parsed);
|
|
1100
|
+
}
|
|
1101
|
+
onChange?.(parsed);
|
|
1347
1102
|
}, [
|
|
1348
1103
|
inactive,
|
|
1349
1104
|
parse,
|
|
1350
|
-
onChange
|
|
1105
|
+
onChange,
|
|
1106
|
+
value,
|
|
1107
|
+
gesture
|
|
1351
1108
|
]);
|
|
1352
1109
|
const changeValue = (0, react.useCallback)((next) => {
|
|
1353
1110
|
if (inactive) return;
|
|
1354
1111
|
setDraft(null);
|
|
1355
|
-
if (next
|
|
1112
|
+
if (next === value) return;
|
|
1113
|
+
gesture.changed(next);
|
|
1114
|
+
onChange?.(next);
|
|
1356
1115
|
}, [
|
|
1357
1116
|
inactive,
|
|
1358
1117
|
value,
|
|
1359
|
-
onChange
|
|
1118
|
+
onChange,
|
|
1119
|
+
gesture
|
|
1360
1120
|
]);
|
|
1361
1121
|
const commitDraft = (0, react.useCallback)(() => {
|
|
1362
1122
|
if (draft === null || inactive) return;
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1123
|
+
const committed = (0, _tremolo_ui_dom_internal.commitNumberInputText)(draft, parse, {
|
|
1124
|
+
min,
|
|
1125
|
+
max,
|
|
1126
|
+
clampValue
|
|
1127
|
+
});
|
|
1128
|
+
if (committed === null) {
|
|
1365
1129
|
setDraft(null);
|
|
1366
1130
|
return;
|
|
1367
1131
|
}
|
|
1368
|
-
|
|
1369
|
-
if (clampValue && min !== void 0) committed = Math.max(committed, min);
|
|
1370
|
-
if (clampValue && max !== void 0) committed = Math.min(committed, max);
|
|
1132
|
+
if (committed !== value) gesture.pulse("keyboard");
|
|
1371
1133
|
changeValue(committed);
|
|
1134
|
+
gesture.end();
|
|
1372
1135
|
}, [
|
|
1373
1136
|
draft,
|
|
1374
1137
|
inactive,
|
|
@@ -1376,21 +1139,23 @@ const NumberInput = {
|
|
|
1376
1139
|
clampValue,
|
|
1377
1140
|
min,
|
|
1378
1141
|
max,
|
|
1379
|
-
changeValue
|
|
1142
|
+
changeValue,
|
|
1143
|
+
value,
|
|
1144
|
+
gesture
|
|
1380
1145
|
]);
|
|
1381
1146
|
const nudge = (0, react.useCallback)((direction, option, modifiers) => {
|
|
1382
|
-
|
|
1383
|
-
changeValue((0, _tremolo_ui_dom.applyDelta)(value, direction, option, mode === "raw" ? rawRange : range, modifiers));
|
|
1147
|
+
changeValue((0, _tremolo_ui_dom_internal.nudgeNumberInput)(value, direction, option, ranges, modifiers));
|
|
1384
1148
|
}, [
|
|
1385
1149
|
changeValue,
|
|
1386
1150
|
value,
|
|
1387
|
-
|
|
1388
|
-
range
|
|
1151
|
+
ranges
|
|
1389
1152
|
]);
|
|
1390
1153
|
const wheelRefCallback = useWheel((event) => {
|
|
1391
|
-
|
|
1154
|
+
const direction = (0, _tremolo_ui_dom_internal.wheelDirection)(event);
|
|
1155
|
+
if (!wheel || inactive || direction === null) return;
|
|
1392
1156
|
event.preventDefault();
|
|
1393
|
-
|
|
1157
|
+
gesture.pulse("wheel");
|
|
1158
|
+
nudge(direction, wheel, event);
|
|
1394
1159
|
}, { requireFocus: true });
|
|
1395
1160
|
const context = (0, react.useMemo)(() => ({
|
|
1396
1161
|
value,
|
|
@@ -1399,7 +1164,7 @@ const NumberInput = {
|
|
|
1399
1164
|
step,
|
|
1400
1165
|
scale,
|
|
1401
1166
|
disabled,
|
|
1402
|
-
|
|
1167
|
+
readOnly,
|
|
1403
1168
|
clampValue,
|
|
1404
1169
|
range,
|
|
1405
1170
|
rawRange,
|
|
@@ -1409,12 +1174,13 @@ const NumberInput = {
|
|
|
1409
1174
|
outOfRange,
|
|
1410
1175
|
dragSensitivity,
|
|
1411
1176
|
pointerLock,
|
|
1177
|
+
dragCursor,
|
|
1412
1178
|
selectOnFocus,
|
|
1413
1179
|
unformatOnFocus,
|
|
1414
1180
|
keepCaretOnStep,
|
|
1415
1181
|
blurOnEnter,
|
|
1416
|
-
atMin
|
|
1417
|
-
atMax
|
|
1182
|
+
atMin,
|
|
1183
|
+
atMax,
|
|
1418
1184
|
drag,
|
|
1419
1185
|
format,
|
|
1420
1186
|
parse,
|
|
@@ -1430,7 +1196,7 @@ const NumberInput = {
|
|
|
1430
1196
|
step,
|
|
1431
1197
|
scale,
|
|
1432
1198
|
disabled,
|
|
1433
|
-
|
|
1199
|
+
readOnly,
|
|
1434
1200
|
clampValue,
|
|
1435
1201
|
range,
|
|
1436
1202
|
rawRange,
|
|
@@ -1438,9 +1204,12 @@ const NumberInput = {
|
|
|
1438
1204
|
text,
|
|
1439
1205
|
editing,
|
|
1440
1206
|
outOfRange,
|
|
1207
|
+
atMin,
|
|
1208
|
+
atMax,
|
|
1441
1209
|
drag,
|
|
1442
1210
|
dragSensitivity,
|
|
1443
1211
|
pointerLock,
|
|
1212
|
+
dragCursor,
|
|
1444
1213
|
selectOnFocus,
|
|
1445
1214
|
unformatOnFocus,
|
|
1446
1215
|
keepCaretOnStep,
|
|
@@ -1452,7 +1221,8 @@ const NumberInput = {
|
|
|
1452
1221
|
changeValue,
|
|
1453
1222
|
nudge
|
|
1454
1223
|
]);
|
|
1455
|
-
|
|
1224
|
+
const rootRefCallback = require_compose_refs.useComposedRefs(forwardedRef, wheelRefCallback);
|
|
1225
|
+
(0, react.useImperativeHandle)(actionsRef, () => {
|
|
1456
1226
|
return {
|
|
1457
1227
|
focus() {
|
|
1458
1228
|
if (!disabled) inputRef.current?.focus();
|
|
@@ -1462,16 +1232,39 @@ const NumberInput = {
|
|
|
1462
1232
|
}
|
|
1463
1233
|
};
|
|
1464
1234
|
}, [disabled]);
|
|
1235
|
+
const pressed = (0, react.useRef)(false);
|
|
1236
|
+
const gestureContext = (0, react.useMemo)(() => ({
|
|
1237
|
+
press: () => {
|
|
1238
|
+
if (inactive) return;
|
|
1239
|
+
gesture.hold("pointer");
|
|
1240
|
+
if (pressed.current) return;
|
|
1241
|
+
pressed.current = true;
|
|
1242
|
+
const release = () => {
|
|
1243
|
+
pressed.current = false;
|
|
1244
|
+
gesture.end();
|
|
1245
|
+
window.removeEventListener("pointerup", release);
|
|
1246
|
+
window.removeEventListener("pointercancel", release);
|
|
1247
|
+
};
|
|
1248
|
+
window.addEventListener("pointerup", release);
|
|
1249
|
+
window.addEventListener("pointercancel", release);
|
|
1250
|
+
},
|
|
1251
|
+
key: () => {
|
|
1252
|
+
if (!inactive) gesture.pulse("keyboard");
|
|
1253
|
+
}
|
|
1254
|
+
}), [gesture, inactive]);
|
|
1465
1255
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
|
|
1466
1256
|
value: context,
|
|
1467
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1257
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputGestureProvider, {
|
|
1258
|
+
value: gestureContext,
|
|
1259
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1260
|
+
ref: rootRefCallback,
|
|
1261
|
+
className,
|
|
1262
|
+
"data-disabled": disabled ? "" : void 0,
|
|
1263
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
1264
|
+
style,
|
|
1265
|
+
...props,
|
|
1266
|
+
children
|
|
1267
|
+
})
|
|
1475
1268
|
})
|
|
1476
1269
|
});
|
|
1477
1270
|
}),
|
|
@@ -1480,78 +1273,15 @@ const NumberInput = {
|
|
|
1480
1273
|
IncrementStepper,
|
|
1481
1274
|
DecrementStepper
|
|
1482
1275
|
};
|
|
1483
|
-
//#endregion
|
|
1484
|
-
//#region src/components/Piano/keyboard-shortcuts.ts
|
|
1485
|
-
/**
|
|
1486
|
-
* Ready-made keyboard layouts. Both assume `noteRange.first` is a C.
|
|
1487
|
-
*/
|
|
1488
|
-
const SHORTCUTS = {
|
|
1489
|
-
/** Every semitone from C, over the two rows of a QWERTY keyboard. */
|
|
1490
|
-
HOME_ROW: { keys: [
|
|
1491
|
-
"a",
|
|
1492
|
-
"w",
|
|
1493
|
-
"s",
|
|
1494
|
-
"e",
|
|
1495
|
-
"d",
|
|
1496
|
-
"f",
|
|
1497
|
-
"t",
|
|
1498
|
-
"g",
|
|
1499
|
-
"y",
|
|
1500
|
-
"h",
|
|
1501
|
-
"u",
|
|
1502
|
-
"j",
|
|
1503
|
-
"k",
|
|
1504
|
-
"o",
|
|
1505
|
-
"l",
|
|
1506
|
-
"p",
|
|
1507
|
-
";"
|
|
1508
|
-
] },
|
|
1509
|
-
/** The white keys only, on the home row. Black keys have no shortcut. */
|
|
1510
|
-
HOME_ROW_NATURAL: { keys: [
|
|
1511
|
-
"a",
|
|
1512
|
-
"",
|
|
1513
|
-
"s",
|
|
1514
|
-
"",
|
|
1515
|
-
"d",
|
|
1516
|
-
"f",
|
|
1517
|
-
"",
|
|
1518
|
-
"g",
|
|
1519
|
-
"",
|
|
1520
|
-
"h",
|
|
1521
|
-
"",
|
|
1522
|
-
"j",
|
|
1523
|
-
"k",
|
|
1524
|
-
"",
|
|
1525
|
-
"l",
|
|
1526
|
-
"",
|
|
1527
|
-
";"
|
|
1528
|
-
] }
|
|
1529
|
-
};
|
|
1530
|
-
//#endregion
|
|
1531
|
-
//#region src/components/Piano/index.tsx
|
|
1532
|
-
function isEditableTarget(target) {
|
|
1533
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
1534
|
-
if (target.matches("input, textarea, select")) return true;
|
|
1535
|
-
for (let element = target; element;) {
|
|
1536
|
-
const contentEditable = element.getAttribute("contenteditable");
|
|
1537
|
-
if (contentEditable !== null) return contentEditable.toLowerCase() !== "false";
|
|
1538
|
-
element = element.parentElement;
|
|
1539
|
-
}
|
|
1540
|
-
return false;
|
|
1541
|
-
}
|
|
1542
|
-
function shortcutKey(event) {
|
|
1543
|
-
return event.code || event.key;
|
|
1544
|
-
}
|
|
1545
1276
|
/**
|
|
1546
1277
|
* Customizable piano component.
|
|
1547
1278
|
*/
|
|
1548
|
-
const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, keyboardShortcutsScope = "root", resizable = false, whiteKeyWidth = 40, keyGap = 1, blackKeyWidthRatio = .65, blackKeyHeightRatio = .6,
|
|
1279
|
+
const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, keyboardShortcutsScope = "root", resizable = false, whiteKeyWidth = 40, keyGap = 1, blackKeyWidthRatio = .65, blackKeyHeightRatio = .6, style, className, label, keyProps, classes, onPlayNote, onStopNote, actionsRef, ...props }, forwardedRef) {
|
|
1549
1280
|
const [node, setNode] = (0, react.useState)(null);
|
|
1550
1281
|
const [activeNotes, setActiveNotes] = (0, react.useState)([]);
|
|
1551
1282
|
/** Set while `resizable` is on, measured from the parent. */
|
|
1552
1283
|
const [resizedKeyWidth, setResizedKeyWidth] = (0, react.useState)(whiteKeyWidth);
|
|
1553
|
-
const notes = (0, react.useMemo)(() => (0,
|
|
1554
|
-
const whiteKeyCount = (0, react.useMemo)(() => notes.filter(_tremolo_ui_functions.isWhiteKey).length, [notes]);
|
|
1284
|
+
const notes = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.getNoteRangeArray)(noteRange), [noteRange]);
|
|
1555
1285
|
const layout = (0, react.useMemo)(() => ({
|
|
1556
1286
|
noteRange,
|
|
1557
1287
|
whiteKeyWidth: resizable ? resizedKeyWidth : whiteKeyWidth,
|
|
@@ -1571,21 +1301,20 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1571
1301
|
layout,
|
|
1572
1302
|
glissando,
|
|
1573
1303
|
midiMax,
|
|
1304
|
+
keyboardShortcuts,
|
|
1305
|
+
keyboardShortcutsScope,
|
|
1574
1306
|
onPlayNote,
|
|
1575
1307
|
onStopNote
|
|
1576
1308
|
});
|
|
1577
1309
|
const instanceRef = (0, react.useRef)(null);
|
|
1578
|
-
const shortcutNotes = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1579
|
-
const releaseShortcutNotes = (0, react.useCallback)(() => {
|
|
1580
|
-
for (const { note, source } of shortcutNotes.current.values()) instanceRef.current?.noteOff(note, { source });
|
|
1581
|
-
shortcutNotes.current.clear();
|
|
1582
|
-
}, []);
|
|
1583
1310
|
(0, react.useEffect)(() => {
|
|
1584
1311
|
if (!node) return;
|
|
1585
1312
|
const instance = (0, _tremolo_ui_dom.createPianoInput)(node, {
|
|
1586
1313
|
layout: latest.current.layout,
|
|
1587
1314
|
glissando: latest.current.glissando,
|
|
1588
1315
|
midiMax: latest.current.midiMax,
|
|
1316
|
+
keyboardShortcuts: latest.current.keyboardShortcuts,
|
|
1317
|
+
keyboardShortcutsScope: latest.current.keyboardShortcutsScope,
|
|
1589
1318
|
onPlayNote: (note, velocity) => latest.current.onPlayNote?.(note, velocity),
|
|
1590
1319
|
onStopNote: (note) => latest.current.onStopNote?.(note),
|
|
1591
1320
|
onActiveNotesChange: setActiveNotes
|
|
@@ -1601,87 +1330,46 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1601
1330
|
layout,
|
|
1602
1331
|
glissando,
|
|
1603
1332
|
midiMax,
|
|
1333
|
+
keyboardShortcuts,
|
|
1334
|
+
keyboardShortcutsScope,
|
|
1604
1335
|
onPlayNote,
|
|
1605
1336
|
onStopNote
|
|
1606
1337
|
};
|
|
1607
1338
|
instanceRef.current?.update({
|
|
1608
1339
|
layout,
|
|
1609
1340
|
glissando,
|
|
1610
|
-
midiMax
|
|
1341
|
+
midiMax,
|
|
1342
|
+
keyboardShortcuts,
|
|
1343
|
+
keyboardShortcutsScope
|
|
1611
1344
|
});
|
|
1612
1345
|
});
|
|
1346
|
+
const { first, last } = noteRange;
|
|
1613
1347
|
(0, react.useEffect)(() => {
|
|
1614
1348
|
if (!resizable || !node) return;
|
|
1615
1349
|
const parent = node.parentElement;
|
|
1616
1350
|
if (!parent) throw new Error("doesn't have a parent element.");
|
|
1617
1351
|
const resizeObserver = new ResizeObserver(() => {
|
|
1618
|
-
setResizedKeyWidth(node.clientWidth
|
|
1352
|
+
setResizedKeyWidth((0, _tremolo_ui_dom_internal.fitWhiteKeyWidth)(node.clientWidth, {
|
|
1353
|
+
noteRange: {
|
|
1354
|
+
first,
|
|
1355
|
+
last
|
|
1356
|
+
},
|
|
1357
|
+
keyGap,
|
|
1358
|
+
blackKeyWidthRatio
|
|
1359
|
+
}));
|
|
1619
1360
|
});
|
|
1620
1361
|
resizeObserver.observe(parent);
|
|
1621
1362
|
return () => resizeObserver.disconnect();
|
|
1622
1363
|
}, [
|
|
1623
1364
|
resizable,
|
|
1624
1365
|
node,
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const hasShortcuts = shortcutKeys !== void 0;
|
|
1630
|
-
const shortcutMapping = shortcutKeys?.join("\0");
|
|
1631
|
-
(0, react.useEffect)(() => releaseShortcutNotes, [
|
|
1632
|
-
releaseShortcutNotes,
|
|
1633
|
-
shortcutMapping,
|
|
1634
|
-
keyboardShortcutsScope,
|
|
1635
|
-
noteRange.first,
|
|
1636
|
-
noteRange.last
|
|
1637
|
-
]);
|
|
1638
|
-
/** The note a shortcut key plays, or null when it has none. */
|
|
1639
|
-
function shortcutNote(key) {
|
|
1640
|
-
if (!shortcutKeys || key === "") return null;
|
|
1641
|
-
const index = shortcutKeys.indexOf(key);
|
|
1642
|
-
const note = noteRange.first + index;
|
|
1643
|
-
return index === -1 || note > noteRange.last ? null : note;
|
|
1644
|
-
}
|
|
1645
|
-
const shortcutTarget = (0, react.useMemo)(() => () => {
|
|
1646
|
-
if (!hasShortcuts) return null;
|
|
1647
|
-
return keyboardShortcutsScope === "window" ? globalThis.window : node;
|
|
1648
|
-
}, [
|
|
1649
|
-
hasShortcuts,
|
|
1650
|
-
keyboardShortcutsScope,
|
|
1651
|
-
node
|
|
1652
|
-
]);
|
|
1653
|
-
const focusOutTarget = (0, react.useMemo)(() => () => hasShortcuts && keyboardShortcutsScope === "root" ? node : null, [
|
|
1654
|
-
hasShortcuts,
|
|
1655
|
-
keyboardShortcutsScope,
|
|
1656
|
-
node
|
|
1366
|
+
first,
|
|
1367
|
+
last,
|
|
1368
|
+
keyGap,
|
|
1369
|
+
blackKeyWidthRatio
|
|
1657
1370
|
]);
|
|
1658
|
-
const
|
|
1659
|
-
|
|
1660
|
-
const key = shortcutKey(e);
|
|
1661
|
-
if (e.repeat || shortcutNotes.current.has(key)) return;
|
|
1662
|
-
if (isEditableTarget(e.target)) return;
|
|
1663
|
-
const note = shortcutNote(e.key);
|
|
1664
|
-
if (note === null) return;
|
|
1665
|
-
const source = `keyboard:${key}`;
|
|
1666
|
-
shortcutNotes.current.set(key, {
|
|
1667
|
-
note,
|
|
1668
|
-
source
|
|
1669
|
-
});
|
|
1670
|
-
instanceRef.current?.noteOn(note, { source });
|
|
1671
|
-
});
|
|
1672
|
-
useEventListener(shortcutTarget, "keyup", (e) => {
|
|
1673
|
-
const key = shortcutKey(e);
|
|
1674
|
-
const shortcut = shortcutNotes.current.get(key);
|
|
1675
|
-
if (!shortcut) return;
|
|
1676
|
-
shortcutNotes.current.delete(key);
|
|
1677
|
-
instanceRef.current?.noteOff(shortcut.note, { source: shortcut.source });
|
|
1678
|
-
});
|
|
1679
|
-
useEventListener(focusOutTarget, "focusout", (e) => {
|
|
1680
|
-
if (e.relatedTarget instanceof Node && node?.contains(e.relatedTarget)) return;
|
|
1681
|
-
releaseShortcutNotes();
|
|
1682
|
-
});
|
|
1683
|
-
useEventListener(windowBlurTarget, "blur", releaseShortcutNotes);
|
|
1684
|
-
(0, react.useImperativeHandle)(forwardedRef, () => ({
|
|
1371
|
+
const rootRefCallback = require_compose_refs.useComposedRefs(forwardedRef, setNode);
|
|
1372
|
+
(0, react.useImperativeHandle)(actionsRef, () => ({
|
|
1685
1373
|
playNote: (note, velocity) => instanceRef.current?.noteOn(note, {
|
|
1686
1374
|
source: "api",
|
|
1687
1375
|
velocity
|
|
@@ -1689,14 +1377,13 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1689
1377
|
stopNote: (note) => instanceRef.current?.noteOff(note, { source: "api" })
|
|
1690
1378
|
}), []);
|
|
1691
1379
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1692
|
-
ref:
|
|
1380
|
+
ref: rootRefCallback,
|
|
1693
1381
|
className,
|
|
1694
1382
|
"data-resizable": resizable ? "" : void 0,
|
|
1695
1383
|
role: "group",
|
|
1696
1384
|
tabIndex: 0,
|
|
1697
1385
|
style: {
|
|
1698
|
-
width: resizable ? "100%" : (0,
|
|
1699
|
-
"--height": cssLength(height),
|
|
1386
|
+
width: resizable ? "100%" : (0, _tremolo_ui_dom_internal.pianoWidth)(layout),
|
|
1700
1387
|
position: "relative",
|
|
1701
1388
|
...style
|
|
1702
1389
|
},
|
|
@@ -1723,7 +1410,7 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1723
1410
|
zIndex: keyType === "white" ? 1 : 2,
|
|
1724
1411
|
...keyStyle,
|
|
1725
1412
|
left: (0, _tremolo_ui_dom.notePosition)(note, layout),
|
|
1726
|
-
width: keyType === "white" ? layout.whiteKeyWidth : (0,
|
|
1413
|
+
width: keyType === "white" ? layout.whiteKeyWidth : (0, _tremolo_ui_dom_internal.blackKeyWidth)(layout),
|
|
1727
1414
|
height: keyType === "white" ? "100%" : `${blackKeyHeightRatio * 100}%`
|
|
1728
1415
|
},
|
|
1729
1416
|
children: content !== "" && content !== null && content !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1762,30 +1449,58 @@ function usePointsEditorContext(selector) {
|
|
|
1762
1449
|
return selector ? selector(context) : context;
|
|
1763
1450
|
}
|
|
1764
1451
|
//#endregion
|
|
1452
|
+
//#region src/components/PointsEditor/Container.tsx
|
|
1453
|
+
const Container = /* @__PURE__ */ (0, react.forwardRef)(function Container({ children, className, style, ...props }, forwardedRef) {
|
|
1454
|
+
const { containerRef, disabled, selectable, editor } = usePointsEditorContext();
|
|
1455
|
+
const { refCallback: dragRefCallback } = useDragValue({
|
|
1456
|
+
axis: _tremolo_ui_dom_internal.POINT_AXIS,
|
|
1457
|
+
baseElementRef: containerRef,
|
|
1458
|
+
shouldStart: (event) => !disabled && !editor.isPointElement(event.target),
|
|
1459
|
+
onDragStart: ([x, y], state) => {
|
|
1460
|
+
editor.beginSelectionBox({
|
|
1461
|
+
x,
|
|
1462
|
+
y
|
|
1463
|
+
}, state.event);
|
|
1464
|
+
},
|
|
1465
|
+
onChange: ([x, y]) => editor.moveSelectionBox({
|
|
1466
|
+
x,
|
|
1467
|
+
y
|
|
1468
|
+
}),
|
|
1469
|
+
onDragEnd: () => editor.endSelectionBox()
|
|
1470
|
+
});
|
|
1471
|
+
useWheel((event) => {
|
|
1472
|
+
const move = (0, _tremolo_ui_dom_internal.wheelMove)(event);
|
|
1473
|
+
if (!move) return;
|
|
1474
|
+
const axis = move.axis === 0 ? "x" : "y";
|
|
1475
|
+
if (editor.nudgeFocusedPoint(axis, move.direction, event)) event.preventDefault();
|
|
1476
|
+
}, { target: containerRef });
|
|
1477
|
+
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, containerRef, selectable && !disabled ? dragRefCallback : void 0);
|
|
1478
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1479
|
+
ref: composedRef,
|
|
1480
|
+
className,
|
|
1481
|
+
style: {
|
|
1482
|
+
position: "absolute",
|
|
1483
|
+
inset: 0,
|
|
1484
|
+
zIndex: 10,
|
|
1485
|
+
...style
|
|
1486
|
+
},
|
|
1487
|
+
...props,
|
|
1488
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Placement, {
|
|
1489
|
+
name: "PointsEditor.Container",
|
|
1490
|
+
children
|
|
1491
|
+
})
|
|
1492
|
+
});
|
|
1493
|
+
});
|
|
1494
|
+
//#endregion
|
|
1765
1495
|
//#region src/components/_util/VisuallyHiddenRangeInput.tsx
|
|
1766
1496
|
const VisuallyHiddenRangeInput = /* @__PURE__ */ (0, react.forwardRef)((props, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1767
1497
|
...props,
|
|
1768
1498
|
ref,
|
|
1769
1499
|
type: "range",
|
|
1770
|
-
style: visuallyHiddenStyle
|
|
1500
|
+
style: _tremolo_ui_dom_internal.visuallyHiddenStyle
|
|
1771
1501
|
}));
|
|
1772
1502
|
//#endregion
|
|
1773
1503
|
//#region src/components/PointsEditor/Point.tsx
|
|
1774
|
-
function clampPoint(point, min, max) {
|
|
1775
|
-
const { x, y } = point;
|
|
1776
|
-
return {
|
|
1777
|
-
x: (0, _tremolo_ui_functions.clamp)(x, min?.x ?? 0, max?.x ?? 1),
|
|
1778
|
-
y: (0, _tremolo_ui_functions.clamp)(y, min?.y ?? 0, max?.y ?? 1)
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
/**
|
|
1782
|
-
* A point is placed by its position within the container, so its value is a
|
|
1783
|
-
* position: 0..1 on each axis, with y growing downwards.
|
|
1784
|
-
*/
|
|
1785
|
-
const AXIS = {
|
|
1786
|
-
min: 0,
|
|
1787
|
-
max: 1
|
|
1788
|
-
};
|
|
1789
1504
|
/**
|
|
1790
1505
|
* One setting for both axes, or one per axis. The point holds a range input
|
|
1791
1506
|
* for each, so anything that names or describes it comes in a pair.
|
|
@@ -1793,17 +1508,27 @@ const AXIS = {
|
|
|
1793
1508
|
function perAxis(value, axis) {
|
|
1794
1509
|
return typeof value === "string" ? value : value?.[axis];
|
|
1795
1510
|
}
|
|
1796
|
-
function Point({ value, children, id: idProp, min, max,
|
|
1797
|
-
const { containerRef,
|
|
1511
|
+
function Point({ value, children, id: idProp, min, max, color, disabled: _disabled, readOnly: _readOnly, wheel: _wheel, keyboard: _keyboard, "aria-label": ariaLabel, "aria-valuetext": ariaValuetext, onChange, onChangeStart, onChangeEnd, changeEndDelay, className, style, onPointerDown, onKeyDown, onFocus, ...props }) {
|
|
1512
|
+
const { containerRef, dragCursor, disabled: rootDisabled, readOnly: rootReadOnly, wheel: rootWheel, keyboard: rootKeyboard, dragSensitivity, selection, editor } = usePointsEditorContext();
|
|
1798
1513
|
const generatedId = (0, react.useId)();
|
|
1799
1514
|
const id = idProp ?? generatedId;
|
|
1800
1515
|
const selected = selection.includes(id);
|
|
1801
1516
|
const disabled = _disabled ?? rootDisabled;
|
|
1802
|
-
const
|
|
1803
|
-
const inactive = disabled ||
|
|
1517
|
+
const readOnly = _readOnly ?? rootReadOnly;
|
|
1518
|
+
const inactive = disabled || readOnly;
|
|
1804
1519
|
const wheel = _wheel === void 0 ? rootWheel : _wheel;
|
|
1805
1520
|
const keyboard = _keyboard === void 0 ? rootKeyboard : _keyboard;
|
|
1806
1521
|
useCheckPlacement("PointsEditor.Point", "PointsEditor.Container");
|
|
1522
|
+
const current = (0, _tremolo_ui_dom_internal.clampPoint)(value, min, max);
|
|
1523
|
+
const gesture = useChangeGesture(current, {
|
|
1524
|
+
onChangeStart,
|
|
1525
|
+
onChangeEnd,
|
|
1526
|
+
changeEndDelay
|
|
1527
|
+
}, inactive);
|
|
1528
|
+
const handleChange = (next) => {
|
|
1529
|
+
gesture.changed(next);
|
|
1530
|
+
onChange?.(next);
|
|
1531
|
+
};
|
|
1807
1532
|
const [element, setElement] = (0, react.useState)(null);
|
|
1808
1533
|
const xInputRef = (0, react.useRef)(null);
|
|
1809
1534
|
const yInputRef = (0, react.useRef)(null);
|
|
@@ -1812,9 +1537,10 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1812
1537
|
min,
|
|
1813
1538
|
max,
|
|
1814
1539
|
readonly: inactive,
|
|
1815
|
-
onChange,
|
|
1540
|
+
onChange: onChange ? handleChange : void 0,
|
|
1816
1541
|
element,
|
|
1817
|
-
wheel
|
|
1542
|
+
wheel,
|
|
1543
|
+
beforeWheel: () => gesture.pulse("wheel")
|
|
1818
1544
|
});
|
|
1819
1545
|
(0, react.useEffect)(() => {
|
|
1820
1546
|
registration.current = {
|
|
@@ -1822,82 +1548,62 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1822
1548
|
min,
|
|
1823
1549
|
max,
|
|
1824
1550
|
readonly: inactive,
|
|
1825
|
-
onChange,
|
|
1551
|
+
onChange: onChange ? handleChange : void 0,
|
|
1826
1552
|
element,
|
|
1827
|
-
wheel
|
|
1553
|
+
wheel,
|
|
1554
|
+
beforeWheel: () => gesture.pulse("wheel")
|
|
1828
1555
|
};
|
|
1829
1556
|
});
|
|
1830
|
-
(0, react.useEffect)(() => registerPoint(id, registration), [id,
|
|
1557
|
+
(0, react.useEffect)(() => editor.registerPoint(id, () => registration.current), [id, editor]);
|
|
1831
1558
|
/** Where the pointer was when the drag started, to measure the move from. */
|
|
1832
1559
|
const pointerOrigin = (0, react.useRef)(null);
|
|
1833
1560
|
const { refCallback: dragRefCallback, dragging } = useDragValue({
|
|
1834
|
-
axis:
|
|
1561
|
+
axis: _tremolo_ui_dom_internal.POINT_AXIS,
|
|
1835
1562
|
baseElementRef: containerRef,
|
|
1836
|
-
sensitivity: (state) => (0,
|
|
1837
|
-
cursor: inactive ? void 0 :
|
|
1563
|
+
sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(dragSensitivity, state.event).value,
|
|
1564
|
+
cursor: inactive ? void 0 : dragCursor,
|
|
1838
1565
|
shouldStart: () => !disabled,
|
|
1839
1566
|
onChange: ([x, y]) => {
|
|
1840
1567
|
const origin = pointerOrigin.current;
|
|
1841
1568
|
if (!origin) return;
|
|
1842
|
-
movePointDrag({
|
|
1569
|
+
editor.movePointDrag({
|
|
1843
1570
|
x: x - origin.x,
|
|
1844
1571
|
y: y - origin.y
|
|
1845
1572
|
});
|
|
1846
1573
|
},
|
|
1847
1574
|
onDragStart: ([x, y], state) => {
|
|
1848
|
-
beginPointDrag(id, state.event);
|
|
1575
|
+
editor.beginPointDrag(id, state.event);
|
|
1849
1576
|
pointerOrigin.current = {
|
|
1850
1577
|
x,
|
|
1851
1578
|
y
|
|
1852
1579
|
};
|
|
1853
1580
|
xInputRef.current?.focus();
|
|
1854
|
-
if (inactive)
|
|
1855
|
-
onDragStart?.(clampPoint(value, min, max));
|
|
1581
|
+
if (!inactive) gesture.hold("pointer");
|
|
1856
1582
|
},
|
|
1857
1583
|
onDragEnd: () => {
|
|
1858
1584
|
pointerOrigin.current = null;
|
|
1859
|
-
|
|
1860
|
-
onDragEnd?.(clampPoint(value, min, max));
|
|
1585
|
+
gesture.end();
|
|
1861
1586
|
}
|
|
1862
1587
|
});
|
|
1863
|
-
const nudge = (0, react.useCallback)((axis, direction, option, modifiers) => {
|
|
1864
|
-
const next = (0, _tremolo_ui_dom.applyDelta)(value[axis], direction, option, AXIS, modifiers);
|
|
1865
|
-
nudgeSelection(id, {
|
|
1866
|
-
x: axis === "x" ? next - value.x : 0,
|
|
1867
|
-
y: axis === "y" ? next - value.y : 0
|
|
1868
|
-
});
|
|
1869
|
-
}, [
|
|
1870
|
-
value,
|
|
1871
|
-
id,
|
|
1872
|
-
nudgeSelection
|
|
1873
|
-
]);
|
|
1874
1588
|
const refCallback = require_compose_refs.useComposedRefs(dragRefCallback, setElement);
|
|
1875
1589
|
const handleKeyDown = (event) => {
|
|
1876
|
-
const
|
|
1877
|
-
if (!
|
|
1878
|
-
"ArrowRight",
|
|
1879
|
-
"ArrowLeft",
|
|
1880
|
-
"ArrowUp",
|
|
1881
|
-
"ArrowDown"
|
|
1882
|
-
].includes(key)) return;
|
|
1883
|
-
const axis = key === "ArrowRight" || key === "ArrowLeft" ? "x" : "y";
|
|
1590
|
+
const move = (0, _tremolo_ui_dom_internal.arrowKeyMove)(event.key);
|
|
1591
|
+
if (!move) return;
|
|
1884
1592
|
event.preventDefault();
|
|
1885
1593
|
if (!onChange || inactive || !keyboard) return;
|
|
1886
|
-
|
|
1594
|
+
gesture.pulse("keyboard");
|
|
1595
|
+
editor.nudgePoint(id, move.axis === 0 ? "x" : "y", move.direction, keyboard, event);
|
|
1887
1596
|
};
|
|
1888
|
-
const current = clampPoint(value, min, max);
|
|
1889
1597
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1890
1598
|
ref: refCallback,
|
|
1891
1599
|
className,
|
|
1892
1600
|
"data-disabled": disabled ? "" : void 0,
|
|
1893
|
-
"data-readonly":
|
|
1601
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
1894
1602
|
tabIndex: -1,
|
|
1895
1603
|
"data-dragging": dragging ? "" : void 0,
|
|
1896
1604
|
"data-selected": selected ? "" : void 0,
|
|
1897
1605
|
style: {
|
|
1898
1606
|
"--color": color,
|
|
1899
|
-
"--width": cssLength(size ?? width),
|
|
1900
|
-
"--height": cssLength(size ?? height),
|
|
1901
1607
|
position: "absolute",
|
|
1902
1608
|
translate: "var(--translate, -50% -50%)",
|
|
1903
1609
|
...style,
|
|
@@ -1922,17 +1628,17 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1922
1628
|
max: max?.[axis] ?? 1,
|
|
1923
1629
|
step: "any",
|
|
1924
1630
|
disabled,
|
|
1925
|
-
"aria-readonly":
|
|
1631
|
+
"aria-readonly": readOnly,
|
|
1926
1632
|
"aria-orientation": axis === "x" ? "horizontal" : "vertical",
|
|
1927
1633
|
"aria-label": perAxis(ariaLabel, axis) ?? axis,
|
|
1928
1634
|
"aria-valuetext": perAxis(ariaValuetext, axis),
|
|
1929
1635
|
onChange: (event) => {
|
|
1930
|
-
if (
|
|
1636
|
+
if (readOnly) {
|
|
1931
1637
|
event.currentTarget.value = String(current[axis]);
|
|
1932
1638
|
return;
|
|
1933
1639
|
}
|
|
1934
1640
|
const delta = event.currentTarget.valueAsNumber - value[axis];
|
|
1935
|
-
nudgeSelection(id, {
|
|
1641
|
+
editor.nudgeSelection(id, {
|
|
1936
1642
|
x: axis === "x" ? delta : 0,
|
|
1937
1643
|
y: axis === "y" ? delta : 0
|
|
1938
1644
|
});
|
|
@@ -1941,50 +1647,6 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1941
1647
|
});
|
|
1942
1648
|
}
|
|
1943
1649
|
//#endregion
|
|
1944
|
-
//#region src/components/PointsEditor/Container.tsx
|
|
1945
|
-
const Container = /* @__PURE__ */ (0, react.forwardRef)(function Container({ children, className, style, ...props }, forwardedRef) {
|
|
1946
|
-
const { containerRef, disabled, selectable, isPointElement, nudgeFocusedPoint, beginSelectionBox, moveSelectionBox, endSelectionBox } = usePointsEditorContext();
|
|
1947
|
-
const { refCallback: dragRefCallback } = useDragValue({
|
|
1948
|
-
axis: AXIS,
|
|
1949
|
-
baseElementRef: containerRef,
|
|
1950
|
-
shouldStart: (event) => !disabled && !isPointElement(event.target),
|
|
1951
|
-
onDragStart: ([x, y], state) => {
|
|
1952
|
-
beginSelectionBox({
|
|
1953
|
-
x,
|
|
1954
|
-
y
|
|
1955
|
-
}, state.event);
|
|
1956
|
-
},
|
|
1957
|
-
onChange: ([x, y]) => moveSelectionBox({
|
|
1958
|
-
x,
|
|
1959
|
-
y
|
|
1960
|
-
}),
|
|
1961
|
-
onDragEnd: endSelectionBox
|
|
1962
|
-
});
|
|
1963
|
-
useWheel((event) => {
|
|
1964
|
-
const horizontal = event.deltaX !== 0;
|
|
1965
|
-
const delta = horizontal ? event.deltaX : event.deltaY;
|
|
1966
|
-
if (delta === 0) return;
|
|
1967
|
-
const axis = horizontal || event.shiftKey ? "x" : "y";
|
|
1968
|
-
if (nudgeFocusedPoint(axis, delta < 0 ? -1 : 1, event)) event.preventDefault();
|
|
1969
|
-
}, { target: containerRef });
|
|
1970
|
-
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, containerRef, selectable && !disabled ? dragRefCallback : void 0);
|
|
1971
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1972
|
-
ref: composedRef,
|
|
1973
|
-
className,
|
|
1974
|
-
style: {
|
|
1975
|
-
position: "absolute",
|
|
1976
|
-
inset: 0,
|
|
1977
|
-
zIndex: 10,
|
|
1978
|
-
...style
|
|
1979
|
-
},
|
|
1980
|
-
...props,
|
|
1981
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Placement, {
|
|
1982
|
-
name: "PointsEditor.Container",
|
|
1983
|
-
children
|
|
1984
|
-
})
|
|
1985
|
-
});
|
|
1986
|
-
});
|
|
1987
|
-
//#endregion
|
|
1988
1650
|
//#region src/components/PointsEditor/SelectionBox.tsx
|
|
1989
1651
|
/**
|
|
1990
1652
|
* The box a drag on empty space draws, and what it covers is selected.
|
|
@@ -2017,220 +1679,50 @@ function SelectionBox({ children, className, style, ...props }) {
|
|
|
2017
1679
|
/** One array for every editor with selection turned off, so memos hold still. */
|
|
2018
1680
|
const EMPTY = [];
|
|
2019
1681
|
/**
|
|
2020
|
-
* How far a point may move before something in the selection leaves its range.
|
|
2021
|
-
*
|
|
2022
|
-
* Clamping each point on its own would break the shape of the selection: the
|
|
2023
|
-
* one that reached the edge would stop while the rest carried on. One amount
|
|
2024
|
-
* for all of them means the whole selection stops together.
|
|
2025
|
-
*/
|
|
2026
|
-
function allowedDelta(delta, entries) {
|
|
2027
|
-
let loX = -Infinity;
|
|
2028
|
-
let hiX = Infinity;
|
|
2029
|
-
let loY = -Infinity;
|
|
2030
|
-
let hiY = Infinity;
|
|
2031
|
-
for (const { start, registration } of entries) {
|
|
2032
|
-
loX = Math.max(loX, (registration.min?.x ?? 0) - start.x);
|
|
2033
|
-
hiX = Math.min(hiX, (registration.max?.x ?? 1) - start.x);
|
|
2034
|
-
loY = Math.max(loY, (registration.min?.y ?? 0) - start.y);
|
|
2035
|
-
hiY = Math.min(hiY, (registration.max?.y ?? 1) - start.y);
|
|
2036
|
-
}
|
|
2037
|
-
return {
|
|
2038
|
-
x: hiX < loX ? 0 : (0, _tremolo_ui_functions.clamp)(delta.x, loX, hiX),
|
|
2039
|
-
y: hiY < loY ? 0 : (0, _tremolo_ui_functions.clamp)(delta.y, loY, hiY)
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* A point moves over 0..1 in both axes, so a nudge of 0.01 crosses the editor
|
|
2044
|
-
* in a hundred steps whatever its pixel size.
|
|
2045
|
-
*/
|
|
2046
|
-
const DEFAULT_WHEEL = ["normalized", .01];
|
|
2047
|
-
/**
|
|
2048
|
-
* Shift is the fine-adjustment key everywhere else, so it is bound here too
|
|
2049
|
-
* — but only on the keyboard. On the wheel it already means the x axis, and
|
|
2050
|
-
* browsers hand shift+wheel over as horizontal scrolling anyway.
|
|
2051
|
-
*/
|
|
2052
|
-
const DEFAULT_KEYBOARD = {
|
|
2053
|
-
default: ["normalized", .01],
|
|
2054
|
-
shift: ["normalized", .001]
|
|
2055
|
-
};
|
|
2056
|
-
/**
|
|
2057
1682
|
* Multiple Point Controller
|
|
2058
1683
|
*/
|
|
2059
1684
|
const PointsEditor = {
|
|
2060
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({
|
|
1685
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ disabled = false, readOnly = false, wheel = _tremolo_ui_dom_internal.POINTS_EDITOR_DEFAULT_WHEEL, keyboard = _tremolo_ui_dom_internal.POINTS_EDITOR_DEFAULT_KEYBOARD, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, selectable = false, selection: selectionProp, defaultSelection, onSelectionChange, dragCursor = "grabbing", style, className, children, ...props }, forwardedRef) => {
|
|
2061
1686
|
const containerRef = (0, react.useRef)(null);
|
|
2062
|
-
const { cursor = "grabbing" } = externalStyles ?? {};
|
|
2063
1687
|
const controlled = selectionProp !== void 0;
|
|
2064
1688
|
const [ownSelection, setOwnSelection] = (0, react.useState)(defaultSelection ?? []);
|
|
2065
1689
|
const selection = selectable ? selectionProp ?? ownSelection : EMPTY;
|
|
2066
|
-
const selectionRef = (0, react.useRef)(selection);
|
|
2067
|
-
const changeHandlerRef = (0, react.useRef)(onSelectionChange);
|
|
2068
|
-
(0, react.useEffect)(() => {
|
|
2069
|
-
selectionRef.current = selection;
|
|
2070
|
-
changeHandlerRef.current = onSelectionChange;
|
|
2071
|
-
});
|
|
2072
|
-
const changeSelection = (0, react.useCallback)((next) => {
|
|
2073
|
-
selectionRef.current = next;
|
|
2074
|
-
if (!controlled) setOwnSelection(next);
|
|
2075
|
-
changeHandlerRef.current?.(next);
|
|
2076
|
-
}, [controlled]);
|
|
2077
|
-
/**
|
|
2078
|
-
* Every mounted point, by id. A registration is a ref rather than a value:
|
|
2079
|
-
* the value inside changes on every frame of a drag, and a registry keyed
|
|
2080
|
-
* on it would be rebuilt just as often.
|
|
2081
|
-
*/
|
|
2082
|
-
const points = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
2083
|
-
const registerPoint = (0, react.useCallback)((id, entry) => {
|
|
2084
|
-
points.current.set(id, entry);
|
|
2085
|
-
return () => {
|
|
2086
|
-
points.current.delete(id);
|
|
2087
|
-
};
|
|
2088
|
-
}, []);
|
|
2089
|
-
/** What the current drag picked up, and where those points started. */
|
|
2090
|
-
const dragRef = (0, react.useRef)([]);
|
|
2091
|
-
const snapshot = (0, react.useCallback)((ids) => {
|
|
2092
|
-
return ids.flatMap((id) => {
|
|
2093
|
-
const registration = points.current.get(id)?.current;
|
|
2094
|
-
return registration ? [{
|
|
2095
|
-
id,
|
|
2096
|
-
start: { ...registration.value }
|
|
2097
|
-
}] : [];
|
|
2098
|
-
});
|
|
2099
|
-
}, []);
|
|
2100
|
-
const applyDeltaTo = (0, react.useCallback)((entries, delta) => {
|
|
2101
|
-
const withRegistration = entries.flatMap((entry) => {
|
|
2102
|
-
const registration = points.current.get(entry.id)?.current;
|
|
2103
|
-
return registration ? [{
|
|
2104
|
-
...entry,
|
|
2105
|
-
registration
|
|
2106
|
-
}] : [];
|
|
2107
|
-
});
|
|
2108
|
-
const allowed = allowedDelta(delta, withRegistration);
|
|
2109
|
-
for (const { start, registration } of withRegistration) {
|
|
2110
|
-
if (registration.readonly) continue;
|
|
2111
|
-
registration.onChange?.({
|
|
2112
|
-
x: (0, _tremolo_ui_functions.toPrecision)(start.x + allowed.x),
|
|
2113
|
-
y: (0, _tremolo_ui_functions.toPrecision)(start.y + allowed.y)
|
|
2114
|
-
});
|
|
2115
|
-
}
|
|
2116
|
-
}, []);
|
|
2117
|
-
const beginPointDrag = (0, react.useCallback)((id, modifiers) => {
|
|
2118
|
-
if (!selectable) {
|
|
2119
|
-
dragRef.current = snapshot([id]);
|
|
2120
|
-
return;
|
|
2121
|
-
}
|
|
2122
|
-
const current = selectionRef.current;
|
|
2123
|
-
const additive = modifiers.ctrlKey || modifiers.metaKey;
|
|
2124
|
-
let next;
|
|
2125
|
-
if (additive) next = current.includes(id) ? current.filter((x) => x !== id) : [...current, id];
|
|
2126
|
-
else if (current.includes(id)) next = current;
|
|
2127
|
-
else next = [id];
|
|
2128
|
-
changeSelection([...next]);
|
|
2129
|
-
dragRef.current = next.includes(id) ? snapshot(next) : [];
|
|
2130
|
-
}, [
|
|
2131
|
-
selectable,
|
|
2132
|
-
changeSelection,
|
|
2133
|
-
snapshot
|
|
2134
|
-
]);
|
|
2135
|
-
const movePointDrag = (0, react.useCallback)((delta) => applyDeltaTo(dragRef.current, delta), [applyDeltaTo]);
|
|
2136
|
-
const nudgeSelection = (0, react.useCallback)((id, delta) => {
|
|
2137
|
-
const ids = selectionRef.current.includes(id) ? selectionRef.current : [id];
|
|
2138
|
-
applyDeltaTo(snapshot(ids), delta);
|
|
2139
|
-
}, [applyDeltaTo, snapshot]);
|
|
2140
|
-
const isPointElement = (0, react.useCallback)((element) => {
|
|
2141
|
-
if (!element) return false;
|
|
2142
|
-
for (const [, entry] of points.current) if (entry.current.element?.contains(element)) return true;
|
|
2143
|
-
return false;
|
|
2144
|
-
}, []);
|
|
2145
|
-
const nudgeFocusedPoint = (0, react.useCallback)((axis, direction, modifiers) => {
|
|
2146
|
-
const container = containerRef.current;
|
|
2147
|
-
const active = container?.ownerDocument.activeElement;
|
|
2148
|
-
if (!container || !active || !container.contains(active)) return false;
|
|
2149
|
-
for (const [id, entry] of points.current) {
|
|
2150
|
-
const { element, wheel, readonly, onChange, value } = entry.current;
|
|
2151
|
-
if (!element?.contains(active)) continue;
|
|
2152
|
-
if (!wheel || readonly || !onChange) return false;
|
|
2153
|
-
const next = (0, _tremolo_ui_dom.applyDelta)(value[axis], direction, wheel, AXIS, modifiers);
|
|
2154
|
-
nudgeSelection(id, {
|
|
2155
|
-
x: axis === "x" ? next - value.x : 0,
|
|
2156
|
-
y: axis === "y" ? next - value.y : 0
|
|
2157
|
-
});
|
|
2158
|
-
return true;
|
|
2159
|
-
}
|
|
2160
|
-
return false;
|
|
2161
|
-
}, [nudgeSelection]);
|
|
2162
1690
|
const [selectionBox, setSelectionBox] = (0, react.useState)(null);
|
|
2163
|
-
const
|
|
2164
|
-
selectionBoxRef.current ??= (0, _tremolo_ui_dom.createSelectionBox)({
|
|
2165
|
-
*items() {
|
|
2166
|
-
for (const [id, entry] of points.current) {
|
|
2167
|
-
const { x, y } = entry.current.value;
|
|
2168
|
-
yield [id, [x, y]];
|
|
2169
|
-
}
|
|
2170
|
-
},
|
|
2171
|
-
onBoxChange: setSelectionBox
|
|
2172
|
-
});
|
|
2173
|
-
(0, react.useEffect)(() => {
|
|
2174
|
-
selectionBoxRef.current?.update({ onSelectionChange: changeSelection });
|
|
2175
|
-
}, [changeSelection]);
|
|
1691
|
+
const [editor] = (0, react.useState)(() => (0, _tremolo_ui_dom.createPointsEditor)({ onSelectionBoxChange: setSelectionBox }));
|
|
2176
1692
|
(0, react.useEffect)(() => {
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
selection: selectionRef.current
|
|
1693
|
+
editor.update({
|
|
1694
|
+
selectable,
|
|
1695
|
+
selection,
|
|
1696
|
+
onSelectionChange: (next) => {
|
|
1697
|
+
if (!controlled) setOwnSelection(next);
|
|
1698
|
+
onSelectionChange?.(next);
|
|
1699
|
+
}
|
|
2185
1700
|
});
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
selectionBoxRef.current?.move([to.x, to.y]);
|
|
2189
|
-
}, []);
|
|
2190
|
-
const endSelectionBox = (0, react.useCallback)(() => {
|
|
2191
|
-
if (!selectionBoxRef.current?.end()) return;
|
|
2192
|
-
const [first] = selectionRef.current;
|
|
2193
|
-
if (first) points.current.get(first)?.current.element?.focus();
|
|
2194
|
-
}, []);
|
|
1701
|
+
});
|
|
1702
|
+
(0, react.useEffect)(() => () => editor.destroy(), [editor]);
|
|
2195
1703
|
const context = (0, react.useMemo)(() => ({
|
|
2196
1704
|
disabled,
|
|
2197
|
-
|
|
1705
|
+
readOnly,
|
|
2198
1706
|
wheel,
|
|
2199
1707
|
keyboard,
|
|
2200
1708
|
dragSensitivity,
|
|
2201
|
-
|
|
1709
|
+
dragCursor,
|
|
2202
1710
|
containerRef,
|
|
2203
1711
|
selectable,
|
|
2204
1712
|
selection,
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
beginPointDrag,
|
|
2208
|
-
movePointDrag,
|
|
2209
|
-
nudgeSelection,
|
|
2210
|
-
nudgeFocusedPoint,
|
|
2211
|
-
selectionBox,
|
|
2212
|
-
beginSelectionBox,
|
|
2213
|
-
moveSelectionBox,
|
|
2214
|
-
endSelectionBox
|
|
1713
|
+
editor,
|
|
1714
|
+
selectionBox
|
|
2215
1715
|
}), [
|
|
2216
1716
|
disabled,
|
|
2217
|
-
|
|
1717
|
+
readOnly,
|
|
2218
1718
|
wheel,
|
|
2219
1719
|
keyboard,
|
|
2220
1720
|
dragSensitivity,
|
|
2221
|
-
|
|
1721
|
+
dragCursor,
|
|
2222
1722
|
selectable,
|
|
2223
1723
|
selection,
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
beginPointDrag,
|
|
2227
|
-
movePointDrag,
|
|
2228
|
-
nudgeSelection,
|
|
2229
|
-
nudgeFocusedPoint,
|
|
2230
|
-
selectionBox,
|
|
2231
|
-
beginSelectionBox,
|
|
2232
|
-
moveSelectionBox,
|
|
2233
|
-
endSelectionBox
|
|
1724
|
+
editor,
|
|
1725
|
+
selectionBox
|
|
2234
1726
|
]);
|
|
2235
1727
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointsEditorProvider, {
|
|
2236
1728
|
value: context,
|
|
@@ -2238,10 +1730,8 @@ const PointsEditor = {
|
|
|
2238
1730
|
ref: forwardedRef,
|
|
2239
1731
|
className,
|
|
2240
1732
|
"data-disabled": disabled ? "" : void 0,
|
|
2241
|
-
"data-readonly":
|
|
1733
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
2242
1734
|
style: {
|
|
2243
|
-
"--width": cssLength(width),
|
|
2244
|
-
"--height": cssLength(height),
|
|
2245
1735
|
position: "relative",
|
|
2246
1736
|
...style
|
|
2247
1737
|
},
|
|
@@ -2266,36 +1756,30 @@ function useSliderContext(selector) {
|
|
|
2266
1756
|
}
|
|
2267
1757
|
//#endregion
|
|
2268
1758
|
//#region src/components/Slider/MarksOption.tsx
|
|
2269
|
-
function MarksOption({ value, mark = true, label, thickness, length, gap,
|
|
1759
|
+
function MarksOption({ value, mark = true, label, thickness, length, gap, classes, styles, className, style, ...props }) {
|
|
2270
1760
|
useCheckPlacement("Slider.MarksOption", "Slider.Marks");
|
|
2271
1761
|
const min = useSliderContext((s) => s.min);
|
|
2272
1762
|
const max = useSliderContext((s) => s.max);
|
|
2273
1763
|
const scale = useSliderContext((s) => s.scale);
|
|
2274
|
-
const vertical = useSliderContext((s) => s.vertical);
|
|
1764
|
+
const vertical = useSliderContext((s) => s.orientation === "vertical");
|
|
2275
1765
|
const reverse = useSliderContext((s) => s.reverse);
|
|
2276
|
-
const
|
|
2277
|
-
const percent = scale.normalize(value, min, max) * 100;
|
|
2278
|
-
return (0, _tremolo_ui_functions.toFixed)(vertical !== reverse ? 100 - percent : percent);
|
|
2279
|
-
}, [
|
|
2280
|
-
vertical,
|
|
2281
|
-
reverse,
|
|
2282
|
-
max,
|
|
1766
|
+
const percent = (0, _tremolo_ui_dom.valuePercent)(value, {
|
|
2283
1767
|
min,
|
|
1768
|
+
max,
|
|
2284
1769
|
scale
|
|
2285
|
-
|
|
1770
|
+
}, vertical !== reverse);
|
|
2286
1771
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
1772
|
className,
|
|
2288
1773
|
style: {
|
|
2289
|
-
"--thickness": cssLength(thickness),
|
|
2290
|
-
"--length": cssLength(length),
|
|
2291
|
-
"--gap": cssLength(gap),
|
|
2292
|
-
"--label-width": cssLength(labelWidth),
|
|
1774
|
+
"--thickness": (0, _tremolo_ui_dom_internal.cssLength)(thickness),
|
|
1775
|
+
"--length": (0, _tremolo_ui_dom_internal.cssLength)(length),
|
|
1776
|
+
"--gap": (0, _tremolo_ui_dom_internal.cssLength)(gap),
|
|
2293
1777
|
position: "absolute",
|
|
2294
1778
|
translate: vertical ? "var(--translate, 0 -50%)" : "var(--translate, -50% 0)",
|
|
2295
1779
|
zIndex: 10,
|
|
2296
1780
|
...style,
|
|
2297
|
-
left: !vertical ? `${
|
|
2298
|
-
top: vertical ? `${
|
|
1781
|
+
left: !vertical ? `${percent}%` : void 0,
|
|
1782
|
+
top: vertical ? `${percent}%` : void 0
|
|
2299
1783
|
},
|
|
2300
1784
|
"data-orientation": vertical ? "vertical" : "horizontal",
|
|
2301
1785
|
...props,
|
|
@@ -2311,58 +1795,20 @@ function MarksOption({ value, mark = true, label, thickness, length, gap, labelW
|
|
|
2311
1795
|
});
|
|
2312
1796
|
}
|
|
2313
1797
|
//#endregion
|
|
2314
|
-
//#region src/components/_util/decimal-digits.ts
|
|
2315
|
-
/**
|
|
2316
|
-
* How many digits a number carries after the decimal point.
|
|
2317
|
-
*
|
|
2318
|
-
* `Slider.Marks` builds its labels by multiplying an interval, which leaves
|
|
2319
|
-
* binary debris in the last digits (`0.1 * 3` is `0.30000000000000004`). The
|
|
2320
|
-
* count is what tells `toFixed` how far to round that back.
|
|
2321
|
-
*
|
|
2322
|
-
* The exponent form has to be handled separately: `String(1e-7)` is `'1e-7'`,
|
|
2323
|
-
* which has no decimal point at all, so reading the text after the point
|
|
2324
|
-
* reports no digits and the interval rounds away to whole numbers.
|
|
2325
|
-
*/
|
|
2326
|
-
function decimalDigits(x) {
|
|
2327
|
-
if (!Number.isFinite(x)) return 0;
|
|
2328
|
-
const text = String(x);
|
|
2329
|
-
const e = text.indexOf("e");
|
|
2330
|
-
if (e === -1) return text.split(".")[1]?.length ?? 0;
|
|
2331
|
-
const fraction = text.slice(0, e).split(".")[1]?.length ?? 0;
|
|
2332
|
-
return Math.max(0, fraction - Number(text.slice(e + 1)));
|
|
2333
|
-
}
|
|
2334
|
-
//#endregion
|
|
2335
|
-
//#region src/components/Slider/type.ts
|
|
2336
|
-
function generateOptionsList(options, min, max, step) {
|
|
2337
|
-
const { per, mark = true, label = true } = typeof options === "object" ? options : { per: options };
|
|
2338
|
-
const optionsList = [];
|
|
2339
|
-
const interval = per === "step" ? step : per;
|
|
2340
|
-
const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1;
|
|
2341
|
-
for (let i = 0; i < count; i++) {
|
|
2342
|
-
const value = (0, _tremolo_ui_functions.toFixed)(interval * (Math.ceil(min / interval) + i), decimalDigits(interval));
|
|
2343
|
-
optionsList.push({
|
|
2344
|
-
value,
|
|
2345
|
-
mark,
|
|
2346
|
-
label
|
|
2347
|
-
});
|
|
2348
|
-
}
|
|
2349
|
-
return optionsList;
|
|
2350
|
-
}
|
|
2351
|
-
//#endregion
|
|
2352
1798
|
//#region src/components/Slider/Marks.tsx
|
|
2353
1799
|
function Marks({ gap, options, children, className, style, ...props }) {
|
|
2354
1800
|
const min = useSliderContext((s) => s.min);
|
|
2355
1801
|
const max = useSliderContext((s) => s.max);
|
|
2356
1802
|
const step = useSliderContext((s) => s.step);
|
|
2357
|
-
const vertical = useSliderContext((s) => s.vertical);
|
|
1803
|
+
const vertical = useSliderContext((s) => s.orientation === "vertical");
|
|
2358
1804
|
const reverse = useSliderContext((s) => s.reverse);
|
|
2359
|
-
const optionsList = options ?
|
|
1805
|
+
const optionsList = options ? (0, _tremolo_ui_dom_internal.sliderMarks)(options, min, max, step) : [];
|
|
2360
1806
|
if (vertical !== reverse) optionsList.reverse();
|
|
2361
1807
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2362
1808
|
className,
|
|
2363
1809
|
"data-orientation": vertical ? "vertical" : "horizontal",
|
|
2364
1810
|
style: {
|
|
2365
|
-
"--gap": cssLength(gap),
|
|
1811
|
+
"--gap": (0, _tremolo_ui_dom_internal.cssLength)(gap),
|
|
2366
1812
|
position: "relative",
|
|
2367
1813
|
...style
|
|
2368
1814
|
},
|
|
@@ -2381,9 +1827,9 @@ function Marks({ gap, options, children, className, style, ...props }) {
|
|
|
2381
1827
|
}
|
|
2382
1828
|
//#endregion
|
|
2383
1829
|
//#region src/components/Slider/Thumb.tsx
|
|
2384
|
-
const Thumb$1 = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, children, className, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-valuetext": ariaValuetext, ...props }, forwardedRef) {
|
|
1830
|
+
const Thumb$1 = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, children, className, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-valuetext": ariaValuetext, actionsRef, ...props }, forwardedRef) {
|
|
2385
1831
|
const inputRef = (0, react.useRef)(null);
|
|
2386
|
-
const { value, min, max, step,
|
|
1832
|
+
const { value, min, max, step, orientation, disabled, readOnly, onChange, percent, thumbRef } = useSliderContext();
|
|
2387
1833
|
useCheckPlacement("Slider.Thumb", "Slider.Track");
|
|
2388
1834
|
const methods = () => ({
|
|
2389
1835
|
focus() {
|
|
@@ -2393,12 +1839,13 @@ const Thumb$1 = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, ch
|
|
|
2393
1839
|
inputRef.current?.blur();
|
|
2394
1840
|
}
|
|
2395
1841
|
});
|
|
2396
|
-
(0, react.useImperativeHandle)(
|
|
1842
|
+
(0, react.useImperativeHandle)(actionsRef, methods, [disabled]);
|
|
2397
1843
|
(0, react.useImperativeHandle)(thumbRef, methods, [disabled]);
|
|
2398
1844
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1845
|
+
ref: forwardedRef,
|
|
2399
1846
|
className,
|
|
2400
1847
|
"data-disabled": disabled ? "" : void 0,
|
|
2401
|
-
"data-readonly":
|
|
1848
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
2402
1849
|
...props,
|
|
2403
1850
|
style: {
|
|
2404
1851
|
"--color": color,
|
|
@@ -2406,8 +1853,8 @@ const Thumb$1 = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, ch
|
|
|
2406
1853
|
translate: "var(--translate, -50% -50%)",
|
|
2407
1854
|
zIndex: 100,
|
|
2408
1855
|
...style,
|
|
2409
|
-
top: vertical ? `${percent}%` : "50%",
|
|
2410
|
-
left:
|
|
1856
|
+
top: orientation === "vertical" ? `${percent}%` : "50%",
|
|
1857
|
+
left: orientation === "vertical" ? "50%" : `${percent}%`
|
|
2411
1858
|
},
|
|
2412
1859
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisuallyHiddenRangeInput, {
|
|
2413
1860
|
ref: inputRef,
|
|
@@ -2416,14 +1863,14 @@ const Thumb$1 = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, ch
|
|
|
2416
1863
|
max,
|
|
2417
1864
|
step,
|
|
2418
1865
|
disabled,
|
|
2419
|
-
"aria-readonly":
|
|
2420
|
-
"aria-orientation":
|
|
1866
|
+
"aria-readonly": readOnly,
|
|
1867
|
+
"aria-orientation": orientation,
|
|
2421
1868
|
"aria-label": ariaLabel,
|
|
2422
1869
|
"aria-labelledby": ariaLabelledby,
|
|
2423
1870
|
"aria-describedby": ariaDescribedby,
|
|
2424
1871
|
"aria-valuetext": ariaValuetext,
|
|
2425
1872
|
onChange: (event) => {
|
|
2426
|
-
if (
|
|
1873
|
+
if (readOnly) {
|
|
2427
1874
|
event.currentTarget.value = String(value);
|
|
2428
1875
|
return;
|
|
2429
1876
|
}
|
|
@@ -2434,20 +1881,20 @@ const Thumb$1 = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, ch
|
|
|
2434
1881
|
});
|
|
2435
1882
|
//#endregion
|
|
2436
1883
|
//#region src/components/Slider/Track.tsx
|
|
2437
|
-
const Track = /* @__PURE__ */ (0, react.forwardRef)(function Track({ length, thickness,
|
|
2438
|
-
const {
|
|
1884
|
+
const Track = /* @__PURE__ */ (0, react.forwardRef)(function Track({ length, thickness, activeColor, inactiveColor, children, className, style, ...props }, forwardedRef) {
|
|
1885
|
+
const { orientation, reverse, disabled, percent, trackRef } = useSliderContext();
|
|
2439
1886
|
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, trackRef);
|
|
2440
1887
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2441
1888
|
ref: composedRef,
|
|
2442
1889
|
className,
|
|
2443
1890
|
"data-disabled": disabled ? "" : void 0,
|
|
2444
|
-
"data-orientation":
|
|
2445
|
-
"data-flipped": vertical !== reverse ? "" : void 0,
|
|
1891
|
+
"data-orientation": orientation,
|
|
1892
|
+
"data-flipped": orientation === "vertical" !== reverse ? "" : void 0,
|
|
2446
1893
|
style: {
|
|
2447
|
-
"--active":
|
|
2448
|
-
"--inactive":
|
|
2449
|
-
"--length": cssLength(length),
|
|
2450
|
-
"--thickness": cssLength(thickness),
|
|
1894
|
+
"--active-color": activeColor,
|
|
1895
|
+
"--inactive-color": inactiveColor,
|
|
1896
|
+
"--length": (0, _tremolo_ui_dom_internal.cssLength)(length),
|
|
1897
|
+
"--thickness": (0, _tremolo_ui_dom_internal.cssLength)(thickness),
|
|
2451
1898
|
"--percent": `${percent}%`,
|
|
2452
1899
|
position: "relative",
|
|
2453
1900
|
...style
|
|
@@ -2461,7 +1908,6 @@ const Track = /* @__PURE__ */ (0, react.forwardRef)(function Track({ length, thi
|
|
|
2461
1908
|
});
|
|
2462
1909
|
//#endregion
|
|
2463
1910
|
//#region src/components/Slider/index.tsx
|
|
2464
|
-
const defaultExternalStyles$1 = { cursor: "pointer" };
|
|
2465
1911
|
/**
|
|
2466
1912
|
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
2467
1913
|
* the control does not change its value.
|
|
@@ -2471,18 +1917,17 @@ const WHEEL_OPTIONS$1 = { requireFocus: true };
|
|
|
2471
1917
|
* Customizable slider
|
|
2472
1918
|
*/
|
|
2473
1919
|
const Slider = {
|
|
2474
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale,
|
|
1920
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, orientation = "horizontal", reverse = false, dragCursor = "pointer", wheel = _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS, keyboard = _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, disabled = false, readOnly = false, onChange, onChangeStart, onChangeEnd, changeEndDelay, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown, actionsRef, ...props }, forwardedRef) => {
|
|
2475
1921
|
const trackRef = (0, react.useRef)(null);
|
|
2476
1922
|
const thumbRef = (0, react.useRef)(null);
|
|
2477
|
-
const
|
|
2478
|
-
|
|
2479
|
-
..._externalStyles
|
|
2480
|
-
};
|
|
2481
|
-
const inactive = disabled || readonly;
|
|
2482
|
-
const p = (0, _tremolo_ui_functions.toFixed)(scale.normalize(value, min, max) * 100);
|
|
2483
|
-
const rev = (0, _tremolo_ui_functions.toFixed)(100 - p);
|
|
1923
|
+
const inactive = disabled || readOnly;
|
|
1924
|
+
const vertical = orientation === "vertical";
|
|
2484
1925
|
const displayReversed = vertical !== reverse;
|
|
2485
|
-
const percent =
|
|
1926
|
+
const percent = (0, _tremolo_ui_dom.valuePercent)(value, {
|
|
1927
|
+
min,
|
|
1928
|
+
max,
|
|
1929
|
+
scale
|
|
1930
|
+
}, displayReversed);
|
|
2486
1931
|
const axis = (0, react.useMemo)(() => ({
|
|
2487
1932
|
min,
|
|
2488
1933
|
max,
|
|
@@ -2502,72 +1947,70 @@ const Slider = {
|
|
|
2502
1947
|
keyboard,
|
|
2503
1948
|
wheel
|
|
2504
1949
|
});
|
|
1950
|
+
const gesture = useChangeGesture(value, {
|
|
1951
|
+
onChangeStart,
|
|
1952
|
+
onChangeEnd,
|
|
1953
|
+
changeEndDelay
|
|
1954
|
+
}, inactive);
|
|
1955
|
+
const change = (0, react.useCallback)((next) => {
|
|
1956
|
+
gesture.changed(next);
|
|
1957
|
+
onChange?.(next);
|
|
1958
|
+
}, [gesture, onChange]);
|
|
2505
1959
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
2506
|
-
const
|
|
2507
|
-
if (
|
|
2508
|
-
"ArrowRight",
|
|
2509
|
-
"ArrowLeft",
|
|
2510
|
-
"ArrowUp",
|
|
2511
|
-
"ArrowDown"
|
|
2512
|
-
].includes(key)) return;
|
|
1960
|
+
const direction = (0, _tremolo_ui_dom_internal.arrowKeyDirection)(event.key);
|
|
1961
|
+
if (direction === null) return;
|
|
2513
1962
|
event.preventDefault();
|
|
2514
1963
|
if (!keyboard || !onChange || inactive) return;
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
onChange((0, _tremolo_ui_dom.applyDelta)(value, direction, keyboard, axis, event));
|
|
1964
|
+
gesture.pulse("keyboard");
|
|
1965
|
+
change((0, _tremolo_ui_dom_internal.applyDelta)(value, reverse ? -direction : direction, keyboard, axis, event));
|
|
2518
1966
|
}, [
|
|
2519
1967
|
keyboard,
|
|
2520
1968
|
onChange,
|
|
2521
1969
|
inactive,
|
|
2522
1970
|
reverse,
|
|
2523
1971
|
value,
|
|
2524
|
-
axis
|
|
1972
|
+
axis,
|
|
1973
|
+
gesture,
|
|
1974
|
+
change
|
|
2525
1975
|
]);
|
|
2526
1976
|
const valueOf = (v) => v[vertical ? 1 : 0];
|
|
2527
1977
|
const { refCallback: dragRefCallback } = useDragValue({
|
|
2528
1978
|
axis,
|
|
2529
1979
|
baseElementRef: trackRef,
|
|
2530
|
-
cursor: inactive ? void 0 :
|
|
1980
|
+
cursor: inactive ? void 0 : dragCursor,
|
|
2531
1981
|
shouldStart: () => !inactive,
|
|
2532
|
-
sensitivity: (state) => (0,
|
|
1982
|
+
sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(dragSensitivity, state.event).value,
|
|
2533
1983
|
updateOnPointerDown: true,
|
|
2534
1984
|
onChange: (v) => {
|
|
2535
1985
|
if (inactive) return;
|
|
2536
|
-
|
|
1986
|
+
change(valueOf(v));
|
|
2537
1987
|
},
|
|
2538
|
-
onDragStart: (
|
|
1988
|
+
onDragStart: () => {
|
|
2539
1989
|
if (inactive) return;
|
|
1990
|
+
gesture.hold("pointer");
|
|
2540
1991
|
thumbRef.current?.focus();
|
|
2541
|
-
onDragStart?.(valueOf(v));
|
|
2542
1992
|
},
|
|
2543
|
-
onDragEnd: (
|
|
2544
|
-
if (inactive) return;
|
|
2545
|
-
onDragEnd?.(valueOf(v));
|
|
2546
|
-
}
|
|
1993
|
+
onDragEnd: () => gesture.end()
|
|
2547
1994
|
});
|
|
2548
1995
|
const wheelRefCallback = useWheel((event) => {
|
|
2549
1996
|
if (!wheel || !onChange || inactive) return;
|
|
1997
|
+
const direction = (0, _tremolo_ui_dom_internal.wheelDirection)(event, { horizontal: !vertical });
|
|
1998
|
+
if (direction === null) return;
|
|
2550
1999
|
event.preventDefault();
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
else {
|
|
2554
|
-
if (event.deltaY === 0) return;
|
|
2555
|
-
direction = event.deltaY > 0 ? -1 : 1;
|
|
2556
|
-
}
|
|
2557
|
-
if (reverse) direction *= -1;
|
|
2558
|
-
onChange((0, _tremolo_ui_dom.applyDelta)(value, direction, wheel, axis, event));
|
|
2000
|
+
gesture.pulse("wheel");
|
|
2001
|
+
change((0, _tremolo_ui_dom_internal.applyDelta)(value, reverse ? -direction : direction, wheel, axis, event));
|
|
2559
2002
|
}, WHEEL_OPTIONS$1);
|
|
2560
|
-
const rootRefCallback = require_compose_refs.useComposedRefs(dragRefCallback, wheelRefCallback);
|
|
2003
|
+
const rootRefCallback = require_compose_refs.useComposedRefs(forwardedRef, dragRefCallback, wheelRefCallback);
|
|
2561
2004
|
const context = (0, react.useMemo)(() => ({
|
|
2562
2005
|
value,
|
|
2563
2006
|
min,
|
|
2564
2007
|
max,
|
|
2565
2008
|
step,
|
|
2566
2009
|
scale,
|
|
2567
|
-
|
|
2010
|
+
orientation,
|
|
2568
2011
|
reverse,
|
|
2569
2012
|
disabled,
|
|
2570
|
-
|
|
2013
|
+
readOnly,
|
|
2571
2014
|
onChange,
|
|
2572
2015
|
percent,
|
|
2573
2016
|
trackRef,
|
|
@@ -2578,14 +2021,14 @@ const Slider = {
|
|
|
2578
2021
|
max,
|
|
2579
2022
|
step,
|
|
2580
2023
|
scale,
|
|
2581
|
-
|
|
2024
|
+
orientation,
|
|
2582
2025
|
reverse,
|
|
2583
2026
|
disabled,
|
|
2584
|
-
|
|
2027
|
+
readOnly,
|
|
2585
2028
|
onChange,
|
|
2586
2029
|
percent
|
|
2587
2030
|
]);
|
|
2588
|
-
(0, react.useImperativeHandle)(
|
|
2031
|
+
(0, react.useImperativeHandle)(actionsRef, () => {
|
|
2589
2032
|
return {
|
|
2590
2033
|
focus() {
|
|
2591
2034
|
if (!disabled) thumbRef.current?.focus();
|
|
@@ -2602,9 +2045,9 @@ const Slider = {
|
|
|
2602
2045
|
ref: rootRefCallback,
|
|
2603
2046
|
role: "group",
|
|
2604
2047
|
tabIndex: -1,
|
|
2605
|
-
"data-orientation":
|
|
2048
|
+
"data-orientation": orientation,
|
|
2606
2049
|
"data-disabled": disabled ? "" : void 0,
|
|
2607
|
-
"data-readonly":
|
|
2050
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
2608
2051
|
style,
|
|
2609
2052
|
onPointerDown,
|
|
2610
2053
|
onKeyDown: (event) => {
|
|
@@ -2640,16 +2083,13 @@ function useXYPadContext(selector) {
|
|
|
2640
2083
|
}
|
|
2641
2084
|
//#endregion
|
|
2642
2085
|
//#region src/components/XYPad/Area.tsx
|
|
2643
|
-
const Area = /* @__PURE__ */ (0, react.forwardRef)(function Area({
|
|
2086
|
+
const Area = /* @__PURE__ */ (0, react.forwardRef)(function Area({ children, className, style, ...props }, forwardedRef) {
|
|
2644
2087
|
const { areaRef } = useXYPadContext();
|
|
2645
2088
|
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, areaRef);
|
|
2646
2089
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2647
2090
|
ref: composedRef,
|
|
2648
2091
|
className,
|
|
2649
2092
|
style: {
|
|
2650
|
-
"--color": color,
|
|
2651
|
-
"--width": cssLength(width),
|
|
2652
|
-
"--height": cssLength(height),
|
|
2653
2093
|
position: "relative",
|
|
2654
2094
|
...style
|
|
2655
2095
|
},
|
|
@@ -2662,15 +2102,15 @@ const Area = /* @__PURE__ */ (0, react.forwardRef)(function Area({ width, height
|
|
|
2662
2102
|
});
|
|
2663
2103
|
//#endregion
|
|
2664
2104
|
//#region src/components/XYPad/Thumb.tsx
|
|
2665
|
-
const Thumb = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, children, className, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-valuetext": ariaValuetext, ...props }, forwardedRef) {
|
|
2105
|
+
const Thumb = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, children, className, style, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-valuetext": ariaValuetext, actionsRef, ...props }, forwardedRef) {
|
|
2666
2106
|
const xInputRef = (0, react.useRef)(null);
|
|
2667
2107
|
const yInputRef = (0, react.useRef)(null);
|
|
2668
|
-
const { value, min, max, step, disabled,
|
|
2108
|
+
const { value, min, max, step, disabled, readOnly, onChange, percent, thumbRef } = useXYPadContext();
|
|
2669
2109
|
useCheckPlacement("XYPad.Thumb", "XYPad.Area");
|
|
2670
|
-
const labels = (0,
|
|
2671
|
-
const labelledby = (0,
|
|
2672
|
-
const describedby = (0,
|
|
2673
|
-
const valueText = (0,
|
|
2110
|
+
const labels = (0, _tremolo_ui_dom_internal.toXY)(ariaLabel);
|
|
2111
|
+
const labelledby = (0, _tremolo_ui_dom_internal.toXY)(ariaLabelledby);
|
|
2112
|
+
const describedby = (0, _tremolo_ui_dom_internal.toXY)(ariaDescribedby);
|
|
2113
|
+
const valueText = (0, _tremolo_ui_dom_internal.toXY)(ariaValuetext);
|
|
2674
2114
|
const methods = () => ({
|
|
2675
2115
|
focus() {
|
|
2676
2116
|
if (!disabled) xInputRef.current?.focus();
|
|
@@ -2680,12 +2120,13 @@ const Thumb = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, chil
|
|
|
2680
2120
|
yInputRef.current?.blur();
|
|
2681
2121
|
}
|
|
2682
2122
|
});
|
|
2683
|
-
(0, react.useImperativeHandle)(
|
|
2123
|
+
(0, react.useImperativeHandle)(actionsRef, methods, [disabled]);
|
|
2684
2124
|
(0, react.useImperativeHandle)(thumbRef, methods, [disabled]);
|
|
2685
2125
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2126
|
+
ref: forwardedRef,
|
|
2686
2127
|
className,
|
|
2687
2128
|
"data-disabled": disabled ? "" : void 0,
|
|
2688
|
-
"data-readonly":
|
|
2129
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
2689
2130
|
...props,
|
|
2690
2131
|
style: {
|
|
2691
2132
|
"--color": color,
|
|
@@ -2704,14 +2145,14 @@ const Thumb = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, chil
|
|
|
2704
2145
|
max: max[axis],
|
|
2705
2146
|
step: step[axis],
|
|
2706
2147
|
disabled,
|
|
2707
|
-
"aria-readonly":
|
|
2148
|
+
"aria-readonly": readOnly,
|
|
2708
2149
|
"aria-orientation": axis === 0 ? "horizontal" : "vertical",
|
|
2709
2150
|
"aria-label": labels[axis] ?? (axis === 0 ? "x" : "y"),
|
|
2710
2151
|
"aria-labelledby": labelledby[axis],
|
|
2711
2152
|
"aria-describedby": describedby[axis],
|
|
2712
2153
|
"aria-valuetext": valueText[axis],
|
|
2713
2154
|
onChange: (event) => {
|
|
2714
|
-
if (
|
|
2155
|
+
if (readOnly) {
|
|
2715
2156
|
event.currentTarget.value = String(value[axis]);
|
|
2716
2157
|
return;
|
|
2717
2158
|
}
|
|
@@ -2724,7 +2165,6 @@ const Thumb = /* @__PURE__ */ (0, react.forwardRef)(function Thumb({ color, chil
|
|
|
2724
2165
|
});
|
|
2725
2166
|
//#endregion
|
|
2726
2167
|
//#region src/components/XYPad/index.tsx
|
|
2727
|
-
const defaultExternalStyles = { cursor: "pointer" };
|
|
2728
2168
|
/**
|
|
2729
2169
|
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
2730
2170
|
* the control does not change its value.
|
|
@@ -2734,24 +2174,30 @@ const WHEEL_OPTIONS = { requireFocus: true };
|
|
|
2734
2174
|
* Simple XYPad
|
|
2735
2175
|
*/
|
|
2736
2176
|
const XYPad = {
|
|
2737
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min: _min, max: _max, step: _step = 1, scale: _scale = _tremolo_ui_functions.linearScale, reverse: _reverse = false, wheel = DEFAULT_WHEEL_OPTIONS, keyboard = DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = DEFAULT_DRAG_SENSITIVITY, className, style,
|
|
2177
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min: _min, max: _max, step: _step = 1, scale: _scale = _tremolo_ui_functions.linearScale, reverse: _reverse = false, wheel = _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS, keyboard = _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, className, style, dragCursor = "pointer", disabled = false, readOnly = false, onChange, onChangeStart, onChangeEnd, changeEndDelay, onPointerDown, onKeyDown, onFocus, onBlur, children, actionsRef, ...props }, forwardedRef) => {
|
|
2738
2178
|
const rootRef = (0, react.useRef)(null);
|
|
2739
2179
|
const areaRef = (0, react.useRef)(null);
|
|
2740
2180
|
const thumbRef = (0, react.useRef)(null);
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
const
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2181
|
+
const inactive = disabled || readOnly;
|
|
2182
|
+
const gesture = useChangeGesture(value, {
|
|
2183
|
+
onChangeStart,
|
|
2184
|
+
onChangeEnd,
|
|
2185
|
+
changeEndDelay
|
|
2186
|
+
}, inactive);
|
|
2187
|
+
const change = (0, react.useCallback)((next) => {
|
|
2188
|
+
gesture.changed(next);
|
|
2189
|
+
onChange?.(next);
|
|
2190
|
+
}, [gesture, onChange]);
|
|
2191
|
+
const min = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.toXY)(_min), [_min]);
|
|
2192
|
+
const max = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.toXY)(_max), [_max]);
|
|
2193
|
+
const step = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.toXY)(_step), [_step]);
|
|
2194
|
+
const scale = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.toXY)(_scale), [_scale]);
|
|
2195
|
+
const reverse = (0, react.useMemo)(() => (0, _tremolo_ui_dom_internal.toXY)(_reverse), [_reverse]);
|
|
2196
|
+
const percent = (0, react.useMemo)(() => [0, 1].map((i) => (0, _tremolo_ui_dom.valuePercent)(value[i], {
|
|
2197
|
+
min: min[i],
|
|
2198
|
+
max: max[i],
|
|
2199
|
+
scale: scale[i]
|
|
2200
|
+
}, reverse[i])), [
|
|
2755
2201
|
value,
|
|
2756
2202
|
min,
|
|
2757
2203
|
max,
|
|
@@ -2787,67 +2233,56 @@ const XYPad = {
|
|
|
2787
2233
|
});
|
|
2788
2234
|
const withAxis = (0, react.useCallback)((axis, next) => axis === 0 ? [next, value[1]] : [value[0], next], [value]);
|
|
2789
2235
|
/** @param i 0 = x, 1 = y */
|
|
2790
|
-
const nudge = (0, react.useCallback)((i, direction, option, modifiers) => withAxis(i, (0,
|
|
2236
|
+
const nudge = (0, react.useCallback)((i, direction, option, modifiers) => withAxis(i, (0, _tremolo_ui_dom_internal.applyDelta)(value[i], direction, option, axis[i], modifiers)), [
|
|
2791
2237
|
value,
|
|
2792
2238
|
axis,
|
|
2793
2239
|
withAxis
|
|
2794
2240
|
]);
|
|
2795
2241
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
2796
|
-
const
|
|
2797
|
-
if (!
|
|
2798
|
-
"ArrowRight",
|
|
2799
|
-
"ArrowLeft",
|
|
2800
|
-
"ArrowUp",
|
|
2801
|
-
"ArrowDown"
|
|
2802
|
-
].includes(key)) return;
|
|
2803
|
-
const i = key === "ArrowRight" || key === "ArrowLeft" ? 0 : 1;
|
|
2242
|
+
const move = (0, _tremolo_ui_dom_internal.arrowKeyMove)(event.key);
|
|
2243
|
+
if (!move) return;
|
|
2804
2244
|
event.preventDefault();
|
|
2805
2245
|
if (!onChange || inactive || !keyboard) return;
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
onChange(nudge(i, direction, keyboard, event));
|
|
2246
|
+
const { axis: i, direction } = move;
|
|
2247
|
+
gesture.pulse("keyboard");
|
|
2248
|
+
change(nudge(i, reverse[i] ? -direction : direction, keyboard, event));
|
|
2810
2249
|
}, [
|
|
2811
2250
|
onChange,
|
|
2812
2251
|
inactive,
|
|
2813
2252
|
keyboard,
|
|
2814
2253
|
reverse,
|
|
2815
|
-
nudge
|
|
2254
|
+
nudge,
|
|
2255
|
+
gesture,
|
|
2256
|
+
change
|
|
2816
2257
|
]);
|
|
2817
2258
|
const { refCallback: dragRefCallback } = useDragValue({
|
|
2818
2259
|
axis,
|
|
2819
2260
|
baseElementRef: areaRef,
|
|
2820
|
-
sensitivity: (state) => (0,
|
|
2261
|
+
sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(dragSensitivity, state.event).value,
|
|
2821
2262
|
updateOnPointerDown: true,
|
|
2822
|
-
cursor: inactive ? void 0 :
|
|
2263
|
+
cursor: inactive ? void 0 : dragCursor,
|
|
2823
2264
|
shouldStart: () => !inactive,
|
|
2824
2265
|
onChange: (v) => {
|
|
2825
2266
|
if (inactive) return;
|
|
2826
|
-
|
|
2267
|
+
change(v);
|
|
2827
2268
|
},
|
|
2828
|
-
onDragStart: (
|
|
2269
|
+
onDragStart: () => {
|
|
2829
2270
|
if (inactive) return;
|
|
2271
|
+
gesture.hold("pointer");
|
|
2830
2272
|
thumbRef.current?.focus();
|
|
2831
|
-
onDragStart?.(v);
|
|
2832
2273
|
},
|
|
2833
|
-
onDragEnd: (
|
|
2834
|
-
if (inactive) return;
|
|
2835
|
-
onDragEnd?.(v);
|
|
2836
|
-
}
|
|
2274
|
+
onDragEnd: () => gesture.end()
|
|
2837
2275
|
});
|
|
2838
2276
|
const wheelRefCallback = useWheel((event) => {
|
|
2839
2277
|
if (!onChange || inactive || !wheel) return;
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
if (delta === 0) return;
|
|
2843
|
-
const i = horizontal || event.shiftKey ? 0 : 1;
|
|
2278
|
+
const move = (0, _tremolo_ui_dom_internal.wheelMove)(event);
|
|
2279
|
+
if (!move) return;
|
|
2844
2280
|
event.preventDefault();
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
onChange(nudge(i, direction, wheel, event));
|
|
2281
|
+
const { axis: i, direction } = move;
|
|
2282
|
+
gesture.pulse("wheel");
|
|
2283
|
+
change(nudge(i, reverse[i] ? -direction : direction, wheel, event));
|
|
2849
2284
|
}, WHEEL_OPTIONS);
|
|
2850
|
-
const rootRefCallback = require_compose_refs.useComposedRefs(rootRef, dragRefCallback, wheelRefCallback);
|
|
2285
|
+
const rootRefCallback = require_compose_refs.useComposedRefs(forwardedRef, rootRef, dragRefCallback, wheelRefCallback);
|
|
2851
2286
|
const context = (0, react.useMemo)(() => ({
|
|
2852
2287
|
value,
|
|
2853
2288
|
min,
|
|
@@ -2856,7 +2291,7 @@ const XYPad = {
|
|
|
2856
2291
|
scale,
|
|
2857
2292
|
reverse,
|
|
2858
2293
|
disabled,
|
|
2859
|
-
|
|
2294
|
+
readOnly,
|
|
2860
2295
|
onChange,
|
|
2861
2296
|
percent,
|
|
2862
2297
|
areaRef,
|
|
@@ -2869,19 +2304,18 @@ const XYPad = {
|
|
|
2869
2304
|
scale,
|
|
2870
2305
|
reverse,
|
|
2871
2306
|
disabled,
|
|
2872
|
-
|
|
2307
|
+
readOnly,
|
|
2873
2308
|
onChange,
|
|
2874
2309
|
percent
|
|
2875
2310
|
]);
|
|
2876
|
-
(0, react.useImperativeHandle)(
|
|
2311
|
+
(0, react.useImperativeHandle)(actionsRef, () => {
|
|
2877
2312
|
return {
|
|
2878
2313
|
focus() {
|
|
2879
2314
|
if (!disabled) thumbRef.current?.focus();
|
|
2880
2315
|
},
|
|
2881
2316
|
blur() {
|
|
2882
2317
|
thumbRef.current?.blur();
|
|
2883
|
-
}
|
|
2884
|
-
original: rootRef
|
|
2318
|
+
}
|
|
2885
2319
|
};
|
|
2886
2320
|
}, [disabled]);
|
|
2887
2321
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XYPadProvider, {
|
|
@@ -2892,7 +2326,7 @@ const XYPad = {
|
|
|
2892
2326
|
role: "group",
|
|
2893
2327
|
tabIndex: -1,
|
|
2894
2328
|
"data-disabled": disabled ? "" : void 0,
|
|
2895
|
-
"data-readonly":
|
|
2329
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
2896
2330
|
style,
|
|
2897
2331
|
onPointerDown,
|
|
2898
2332
|
onKeyDown: (event) => {
|
|
@@ -2917,17 +2351,103 @@ const XYPad = {
|
|
|
2917
2351
|
};
|
|
2918
2352
|
//#endregion
|
|
2919
2353
|
//#region src/hooks/useAnimationFrame.ts
|
|
2920
|
-
|
|
2921
|
-
|
|
2354
|
+
/**
|
|
2355
|
+
* Call `callback` on every animation frame for as long as the component is
|
|
2356
|
+
* mounted.
|
|
2357
|
+
*
|
|
2358
|
+
* The callback is read on every frame, so it can be written inline and see
|
|
2359
|
+
* the latest render without restarting the loop.
|
|
2360
|
+
*
|
|
2361
|
+
* @param callback receives the frame's timestamp and the milliseconds since
|
|
2362
|
+
* the previous frame, which is `0` on the first one
|
|
2363
|
+
*/
|
|
2364
|
+
function useAnimationFrame(callback, { disabled = false } = {}) {
|
|
2922
2365
|
const runCallback = useCallbackRef(callback);
|
|
2923
2366
|
(0, react.useEffect)(() => {
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2367
|
+
if (disabled) return;
|
|
2368
|
+
let frameId = 0;
|
|
2369
|
+
let previous;
|
|
2370
|
+
const loop = (timestamp) => {
|
|
2371
|
+
frameId = requestAnimationFrame(loop);
|
|
2372
|
+
runCallback(timestamp, previous === void 0 ? 0 : timestamp - previous);
|
|
2373
|
+
previous = timestamp;
|
|
2927
2374
|
};
|
|
2928
|
-
|
|
2929
|
-
return () => cancelAnimationFrame(
|
|
2930
|
-
}, [runCallback,
|
|
2375
|
+
frameId = requestAnimationFrame(loop);
|
|
2376
|
+
return () => cancelAnimationFrame(frameId);
|
|
2377
|
+
}, [runCallback, disabled]);
|
|
2378
|
+
}
|
|
2379
|
+
//#endregion
|
|
2380
|
+
//#region src/hooks/useDrag.ts
|
|
2381
|
+
/**
|
|
2382
|
+
* Track a pointer drag on an element.
|
|
2383
|
+
*
|
|
2384
|
+
* @returns a ref callback to attach to the element being dragged
|
|
2385
|
+
*/
|
|
2386
|
+
function useDrag({ threshold = 1, cursor, pointerLock, onDrag, onDragStart, onDragEnd }) {
|
|
2387
|
+
const dragHandler = useCallbackRef(onDrag);
|
|
2388
|
+
const dragStartHandler = useCallbackRef(onDragStart);
|
|
2389
|
+
const dragEndHandler = useCallbackRef(onDragEnd);
|
|
2390
|
+
const [node, setNode] = (0, react.useState)(null);
|
|
2391
|
+
(0, react.useEffect)(() => {
|
|
2392
|
+
if (!node) return;
|
|
2393
|
+
const instance = (0, _tremolo_ui_dom.createDrag)(node, {
|
|
2394
|
+
threshold,
|
|
2395
|
+
cursor,
|
|
2396
|
+
pointerLock,
|
|
2397
|
+
onDragStart: (state) => dragStartHandler(state),
|
|
2398
|
+
onDrag: (state) => dragHandler(state.x, state.y, state.deltaX, state.deltaY, state),
|
|
2399
|
+
onDragEnd: (state) => dragEndHandler(state)
|
|
2400
|
+
});
|
|
2401
|
+
return () => instance.destroy();
|
|
2402
|
+
}, [
|
|
2403
|
+
node,
|
|
2404
|
+
threshold,
|
|
2405
|
+
cursor,
|
|
2406
|
+
pointerLock,
|
|
2407
|
+
dragHandler,
|
|
2408
|
+
dragStartHandler,
|
|
2409
|
+
dragEndHandler
|
|
2410
|
+
]);
|
|
2411
|
+
return setNode;
|
|
2412
|
+
}
|
|
2413
|
+
//#endregion
|
|
2414
|
+
//#region src/hooks/useEventListener.ts
|
|
2415
|
+
function useEventListener(target, event, handler, options) {
|
|
2416
|
+
const listener = useCallbackRef(handler);
|
|
2417
|
+
const registrationRef = (0, react.useRef)(null);
|
|
2418
|
+
const { capture = false, once, passive } = typeof options === "boolean" ? { capture: options } : options ?? {};
|
|
2419
|
+
(0, react.useEffect)(() => {
|
|
2420
|
+
const node = typeof target === "function" ? target() : target;
|
|
2421
|
+
const current = registrationRef.current;
|
|
2422
|
+
if (current && current.node === node && current.event === event && current.capture === capture && current.once === once && current.passive === passive) return;
|
|
2423
|
+
current?.remove();
|
|
2424
|
+
registrationRef.current = null;
|
|
2425
|
+
if (!node) return;
|
|
2426
|
+
const listenerOptions = {
|
|
2427
|
+
capture,
|
|
2428
|
+
once,
|
|
2429
|
+
passive
|
|
2430
|
+
};
|
|
2431
|
+
node.addEventListener(event, listener, listenerOptions);
|
|
2432
|
+
registrationRef.current = {
|
|
2433
|
+
node,
|
|
2434
|
+
event,
|
|
2435
|
+
capture,
|
|
2436
|
+
once,
|
|
2437
|
+
passive,
|
|
2438
|
+
remove: () => node.removeEventListener(event, listener, listenerOptions)
|
|
2439
|
+
};
|
|
2440
|
+
});
|
|
2441
|
+
(0, react.useEffect)(() => () => {
|
|
2442
|
+
registrationRef.current?.remove();
|
|
2443
|
+
registrationRef.current = null;
|
|
2444
|
+
}, []);
|
|
2445
|
+
return (0, react.useCallback)(() => {
|
|
2446
|
+
const current = registrationRef.current;
|
|
2447
|
+
if (!current) return;
|
|
2448
|
+
current.remove();
|
|
2449
|
+
current.remove = () => {};
|
|
2450
|
+
}, []);
|
|
2931
2451
|
}
|
|
2932
2452
|
//#endregion
|
|
2933
2453
|
//#region src/hooks/useMIDIAccess.ts
|
|
@@ -3067,45 +2587,20 @@ function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
|
3067
2587
|
}
|
|
3068
2588
|
//#endregion
|
|
3069
2589
|
exports.AnimationCanvas = AnimationCanvas;
|
|
3070
|
-
exports.DEFAULT_DRAG_SENSITIVITY = DEFAULT_DRAG_SENSITIVITY;
|
|
3071
|
-
exports.DEFAULT_KEYBOARD_OPTIONS = DEFAULT_KEYBOARD_OPTIONS;
|
|
3072
|
-
exports.DEFAULT_WHEEL_OPTIONS = DEFAULT_WHEEL_OPTIONS;
|
|
3073
2590
|
exports.DropZone = DropZone;
|
|
3074
2591
|
exports.FileInput = FileInput;
|
|
3075
2592
|
exports.Knob = Knob;
|
|
3076
|
-
Object.defineProperty(exports, "NOT_SUPPORTED", {
|
|
3077
|
-
enumerable: true,
|
|
3078
|
-
get: function() {
|
|
3079
|
-
return _tremolo_ui_dom.NOT_SUPPORTED;
|
|
3080
|
-
}
|
|
3081
|
-
});
|
|
3082
2593
|
exports.NumberInput = NumberInput;
|
|
3083
|
-
Object.defineProperty(exports, "PERMISSION_DENIED", {
|
|
3084
|
-
enumerable: true,
|
|
3085
|
-
get: function() {
|
|
3086
|
-
return _tremolo_ui_dom.PERMISSION_DENIED;
|
|
3087
|
-
}
|
|
3088
|
-
});
|
|
3089
|
-
exports.PITCH_BEND_CENTER = _tremolo_ui_dom.PITCH_BEND_CENTER;
|
|
3090
2594
|
exports.Piano = Piano;
|
|
3091
2595
|
exports.PointsEditor = PointsEditor;
|
|
3092
|
-
exports.SHORTCUTS = SHORTCUTS;
|
|
3093
2596
|
exports.Slider = Slider;
|
|
3094
|
-
Object.defineProperty(exports, "UNAVAILABLE", {
|
|
3095
|
-
enumerable: true,
|
|
3096
|
-
get: function() {
|
|
3097
|
-
return _tremolo_ui_dom.UNAVAILABLE;
|
|
3098
|
-
}
|
|
3099
|
-
});
|
|
3100
2597
|
exports.XYPad = XYPad;
|
|
3101
|
-
exports.clampPoint = clampPoint;
|
|
3102
2598
|
exports.useAnimationFrame = useAnimationFrame;
|
|
3103
2599
|
exports.useDrag = useDrag;
|
|
3104
2600
|
exports.useDragValue = useDragValue;
|
|
3105
2601
|
exports.useDropZone = useDropZone;
|
|
3106
2602
|
exports.useEventListener = useEventListener;
|
|
3107
2603
|
exports.useFileInputContext = useFileInputContext;
|
|
3108
|
-
exports.useInterval = useInterval;
|
|
3109
2604
|
exports.useKnobContext = useKnobContext;
|
|
3110
2605
|
exports.useLongPress = useLongPress;
|
|
3111
2606
|
exports.useMIDIAccess = useMIDIAccess;
|