@tremolo-ui/react 0.7.0 → 0.8.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 +346 -1015
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -221
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +76 -221
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +333 -978
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/DropZone/index.tsx +2 -1
- package/src/components/FileInput/index.tsx +8 -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 +3 -2
- package/src/components/Knob/context.tsx +3 -74
- package/src/components/Knob/index.tsx +19 -16
- package/src/components/NumberInput/InputField.tsx +12 -27
- package/src/components/NumberInput/Stepper.tsx +49 -97
- package/src/components/NumberInput/StepperButton.tsx +3 -4
- package/src/components/NumberInput/context.tsx +2 -1
- package/src/components/NumberInput/index.tsx +40 -71
- package/src/components/Piano/index.tsx +35 -142
- package/src/components/PointsEditor/Container.tsx +15 -26
- package/src/components/PointsEditor/Point.tsx +38 -85
- package/src/components/PointsEditor/context.tsx +5 -56
- package/src/components/PointsEditor/index.tsx +29 -311
- package/src/components/Slider/Marks.tsx +3 -5
- package/src/components/Slider/MarksOption.tsx +7 -18
- package/src/components/Slider/Track.tsx +2 -1
- package/src/components/Slider/index.tsx +29 -28
- package/src/components/XYPad/Area.tsx +27 -49
- package/src/components/XYPad/Thumb.tsx +3 -1
- package/src/components/XYPad/context.tsx +1 -3
- package/src/components/XYPad/index.tsx +33 -46
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
- package/src/hooks/_internal/useCheckSteps.ts +9 -146
- package/src/hooks/useLongPress.ts +35 -51
- package/src/hooks/useMIDIAccess.ts +0 -8
- package/src/hooks/useMIDIInput.ts +0 -3
- package/src/index.ts +2 -27
- 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/input-event.ts +0 -32
package/dist/index.cjs
CHANGED
|
@@ -192,31 +192,6 @@ const DropZone = { Root: /* @__PURE__ */ (0, react.forwardRef)(({ accept, multip
|
|
|
192
192
|
});
|
|
193
193
|
}) };
|
|
194
194
|
//#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
195
|
//#region src/components/FileInput/context.tsx
|
|
221
196
|
const FileInputContext = /* @__PURE__ */ (0, react.createContext)(null);
|
|
222
197
|
const FileInputProvider = FileInputContext.Provider;
|
|
@@ -256,14 +231,10 @@ const FileInput = {
|
|
|
256
231
|
disabled
|
|
257
232
|
}), [inputId, disabled]);
|
|
258
233
|
function handleChange(event) {
|
|
259
|
-
const
|
|
234
|
+
const { accepted, rejected } = (0, _tremolo_ui_dom.partitionByAccept)(Array.from(event.target.files ?? []), accept);
|
|
260
235
|
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
236
|
if (accepted.length > 0) onChange?.(accepted);
|
|
237
|
+
if (rejected.length > 0) onReject?.(rejected);
|
|
267
238
|
}
|
|
268
239
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileInputProvider, {
|
|
269
240
|
value: context,
|
|
@@ -277,7 +248,7 @@ const FileInput = {
|
|
|
277
248
|
accept,
|
|
278
249
|
multiple,
|
|
279
250
|
disabled,
|
|
280
|
-
style: visuallyHiddenStyle,
|
|
251
|
+
style: _tremolo_ui_dom.visuallyHiddenStyle,
|
|
281
252
|
"aria-label": ariaLabel,
|
|
282
253
|
"aria-labelledby": ariaLabelledby,
|
|
283
254
|
"aria-describedby": ariaDescribedby,
|
|
@@ -290,113 +261,23 @@ const FileInput = {
|
|
|
290
261
|
};
|
|
291
262
|
//#endregion
|
|
292
263
|
//#region src/hooks/_internal/useCheckSteps.ts
|
|
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
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Press every entry of `options` at nine points along the travel and report
|
|
343
|
-
* whether anything came of it.
|
|
344
|
-
*
|
|
345
|
-
* Run against `applyDelta` itself rather than against a reading of `step`:
|
|
346
|
-
* the whole point is that the amount, the step and the scale interact, and
|
|
347
|
-
* the pipeline is the only thing that knows how.
|
|
348
|
-
*/
|
|
349
|
-
function probe(options, range, format) {
|
|
350
|
-
const { min, max, scale = _tremolo_ui_functions.linearScale } = range;
|
|
351
|
-
const outcome = {
|
|
352
|
-
moved: false,
|
|
353
|
-
visible: false
|
|
354
|
-
};
|
|
355
|
-
for (const { modifiers } of entries(options)) for (const position of PROBES) {
|
|
356
|
-
const value = scale.denormalize(position, min, max);
|
|
357
|
-
const up = (0, _tremolo_ui_dom.applyDelta)(value, 1, options, range, modifiers);
|
|
358
|
-
const down = (0, _tremolo_ui_dom.applyDelta)(value, -1, options, range, modifiers);
|
|
359
|
-
if (up !== value || down !== value) outcome.moved = true;
|
|
360
|
-
if (!format) continue;
|
|
361
|
-
const shown = format(value);
|
|
362
|
-
if (format(up) !== shown || format(down) !== shown) outcome.visible = true;
|
|
363
|
-
}
|
|
364
|
-
return outcome;
|
|
365
|
-
}
|
|
366
264
|
/**
|
|
367
265
|
* 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.
|
|
266
|
+
* a change the user can see. The probing is `checkSteps` in the core.
|
|
381
267
|
*
|
|
382
268
|
* @internal
|
|
383
269
|
*/
|
|
384
|
-
function useCheckSteps(
|
|
270
|
+
function useCheckSteps(options) {
|
|
271
|
+
const { component, axis, range, keyboard, wheel } = options;
|
|
385
272
|
const { min, max, step, scale } = range ?? {};
|
|
386
|
-
const where = axis ? `${component} (${axis})` : component;
|
|
387
273
|
(0, react.useEffect)(() => {
|
|
388
274
|
try {
|
|
389
275
|
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
|
-
}
|
|
276
|
+
for (const warning of (0, _tremolo_ui_dom.checkSteps)(options)) console.warn(warning);
|
|
397
277
|
} catch {}
|
|
398
278
|
}, [
|
|
399
|
-
|
|
279
|
+
component,
|
|
280
|
+
axis,
|
|
400
281
|
min,
|
|
401
282
|
max,
|
|
402
283
|
step,
|
|
@@ -504,49 +385,6 @@ function useWheel(onWheel, { requireFocus = false, target } = {}) {
|
|
|
504
385
|
return setNode;
|
|
505
386
|
}
|
|
506
387
|
//#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
388
|
//#region src/components/_util/Placement.tsx
|
|
551
389
|
/**
|
|
552
390
|
* The subcomponent that established the current placement, if any.
|
|
@@ -588,49 +426,8 @@ function useCheckPlacement(child, parent) {
|
|
|
588
426
|
parent
|
|
589
427
|
]);
|
|
590
428
|
}
|
|
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
|
-
}
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/components/Knob/context.tsx
|
|
634
431
|
const KnobContext = /* @__PURE__ */ (0, react.createContext)(null);
|
|
635
432
|
const KnobProvider = KnobContext.Provider;
|
|
636
433
|
function useKnobContext(selector) {
|
|
@@ -644,10 +441,10 @@ function ActiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...pr
|
|
|
644
441
|
useCheckPlacement("Knob.ActiveLine", "Knob.SVGRoot");
|
|
645
442
|
const r2 = useKnobContext((s) => s.r2);
|
|
646
443
|
const r3 = useKnobContext((s) => s.r3);
|
|
647
|
-
const radius =
|
|
444
|
+
const radius = (0, _tremolo_ui_dom.knobArcRadius)(strokeWidth);
|
|
648
445
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
649
446
|
className,
|
|
650
|
-
d:
|
|
447
|
+
d: (0, _tremolo_ui_dom.knobArcPath)(r2, r3, radius),
|
|
651
448
|
fill: "none",
|
|
652
449
|
stroke,
|
|
653
450
|
strokeWidth,
|
|
@@ -665,17 +462,17 @@ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...
|
|
|
665
462
|
const r2 = useKnobContext((s) => s.r2);
|
|
666
463
|
const r3 = useKnobContext((s) => s.r3);
|
|
667
464
|
const r4 = useKnobContext((s) => s.r4);
|
|
668
|
-
const radius =
|
|
465
|
+
const radius = (0, _tremolo_ui_dom.knobArcRadius)(strokeWidth);
|
|
669
466
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [startValue > min && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
670
467
|
className,
|
|
671
|
-
d:
|
|
468
|
+
d: (0, _tremolo_ui_dom.knobArcPath)(r1, r2, radius),
|
|
672
469
|
fill: "none",
|
|
673
470
|
stroke,
|
|
674
471
|
strokeWidth,
|
|
675
472
|
...props
|
|
676
473
|
}), startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
677
474
|
className,
|
|
678
|
-
d:
|
|
475
|
+
d: (0, _tremolo_ui_dom.knobArcPath)(r3, r4, radius),
|
|
679
476
|
fill: "none",
|
|
680
477
|
stroke,
|
|
681
478
|
strokeWidth,
|
|
@@ -686,7 +483,7 @@ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...
|
|
|
686
483
|
//#region src/components/Knob/SVGRoot.tsx
|
|
687
484
|
function SVGRoot({ children, style, ...props }) {
|
|
688
485
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
689
|
-
viewBox: `0 0
|
|
486
|
+
viewBox: `0 0 ${_tremolo_ui_dom.KNOB_VIEWBOX_SIZE} ${_tremolo_ui_dom.KNOB_VIEWBOX_SIZE}`,
|
|
690
487
|
style: {
|
|
691
488
|
display: "block",
|
|
692
489
|
...style
|
|
@@ -716,7 +513,7 @@ function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor"
|
|
|
716
513
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
717
514
|
className: classes?.thumbLine,
|
|
718
515
|
x1: "50%",
|
|
719
|
-
y1: `${(
|
|
516
|
+
y1: `${(_tremolo_ui_dom.KNOB_VIEWBOX_SIZE - (0, _tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2}%`,
|
|
720
517
|
x2: "50%",
|
|
721
518
|
y2: `${thumbLineLength}%`,
|
|
722
519
|
stroke: thumbLine,
|
|
@@ -740,7 +537,7 @@ const WHEEL_OPTIONS$2 = { requireFocus: true };
|
|
|
740
537
|
* Interactive rotary knob component implemented in SVG.
|
|
741
538
|
*/
|
|
742
539
|
const Knob = {
|
|
743
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, defaultValue = min, startValue = min, size, externalStyles: _externalStyles, wheel = DEFAULT_WHEEL_OPTIONS, keyboard = DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = DEFAULT_DRAG_SENSITIVITY, pointerLock = false, enableDoubleClickDefault = true, disabled = false, readonly = false, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, style, children, ...props }, forwardedRef) => {
|
|
540
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, defaultValue = min, startValue = min, size, externalStyles: _externalStyles, wheel = _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS, keyboard = _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, pointerLock = false, enableDoubleClickDefault = true, disabled = false, readonly = false, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, style, children, ...props }, forwardedRef) => {
|
|
744
541
|
const elmRef = (0, react.useRef)(null);
|
|
745
542
|
const externalStyles = {
|
|
746
543
|
...defaultExternalStyles$2,
|
|
@@ -766,16 +563,10 @@ const Knob = {
|
|
|
766
563
|
});
|
|
767
564
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
768
565
|
if (!keyboard || !onChange || inactive) return;
|
|
769
|
-
const
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
"ArrowUp",
|
|
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
|
-
}
|
|
566
|
+
const direction = (0, _tremolo_ui_dom.arrowKeyDirection)(event.key);
|
|
567
|
+
if (direction === null) return;
|
|
568
|
+
event.preventDefault();
|
|
569
|
+
onChange((0, _tremolo_ui_dom.applyDelta)(value, direction, keyboard, range, event));
|
|
779
570
|
}, [
|
|
780
571
|
keyboard,
|
|
781
572
|
onChange,
|
|
@@ -801,9 +592,11 @@ const Knob = {
|
|
|
801
592
|
});
|
|
802
593
|
const wheelRefCallback = useWheel((event) => {
|
|
803
594
|
if (!wheel || inactive) return;
|
|
595
|
+
const direction = (0, _tremolo_ui_dom.wheelDirection)(event);
|
|
596
|
+
if (direction === null) return;
|
|
804
597
|
event.preventDefault();
|
|
805
|
-
if (!onChange
|
|
806
|
-
onChange((0, _tremolo_ui_dom.applyDelta)(value,
|
|
598
|
+
if (!onChange) return;
|
|
599
|
+
onChange((0, _tremolo_ui_dom.applyDelta)(value, direction, wheel, range, event));
|
|
807
600
|
}, WHEEL_OPTIONS$2);
|
|
808
601
|
const rootRefCallback = require_compose_refs.useComposedRefs(elmRef, dragRefCallback, wheelRefCallback);
|
|
809
602
|
const context = (0, react.useMemo)(() => {
|
|
@@ -818,7 +611,7 @@ const Knob = {
|
|
|
818
611
|
};
|
|
819
612
|
return {
|
|
820
613
|
...config,
|
|
821
|
-
...
|
|
614
|
+
...(0, _tremolo_ui_dom.knobAngles)(config)
|
|
822
615
|
};
|
|
823
616
|
}, [
|
|
824
617
|
value,
|
|
@@ -855,7 +648,7 @@ const Knob = {
|
|
|
855
648
|
"data-readonly": readonly ? "" : void 0,
|
|
856
649
|
"data-dragging": dragging ? "" : void 0,
|
|
857
650
|
style: {
|
|
858
|
-
"--knob-size": cssLength(size),
|
|
651
|
+
"--knob-size": (0, _tremolo_ui_dom.cssLength)(size),
|
|
859
652
|
...style
|
|
860
653
|
},
|
|
861
654
|
onPointerDown,
|
|
@@ -892,82 +685,36 @@ function useStepperContext() {
|
|
|
892
685
|
return (0, react.useContext)(StepperContext);
|
|
893
686
|
}
|
|
894
687
|
//#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);
|
|
688
|
+
//#region src/hooks/useLongPress.ts
|
|
689
|
+
/**
|
|
690
|
+
* Repeat `callback` while a pointer is held down: once on the press, then
|
|
691
|
+
* every `interval` after `initialDelay`. The repeat itself is
|
|
692
|
+
* `createLongPress` in `@tremolo-ui/dom`.
|
|
693
|
+
*
|
|
694
|
+
* Returns the function that starts a press, for an `onPointerDown`.
|
|
695
|
+
*/
|
|
696
|
+
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
697
|
+
const onPress = useCallbackRef(callback);
|
|
698
|
+
const instanceRef = (0, react.useRef)(null);
|
|
923
699
|
(0, react.useEffect)(() => {
|
|
924
|
-
|
|
925
|
-
|
|
700
|
+
instanceRef.current?.update({
|
|
701
|
+
delay: initialDelay,
|
|
702
|
+
interval
|
|
703
|
+
});
|
|
704
|
+
});
|
|
926
705
|
(0, react.useEffect)(() => {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
706
|
+
const instance = (0, _tremolo_ui_dom.createLongPress)({
|
|
707
|
+
onPress: () => onPress(),
|
|
708
|
+
delay: initialDelay,
|
|
709
|
+
interval
|
|
710
|
+
});
|
|
711
|
+
instanceRef.current = instance;
|
|
931
712
|
return () => {
|
|
932
|
-
|
|
713
|
+
instanceRef.current = null;
|
|
714
|
+
instance.destroy();
|
|
933
715
|
};
|
|
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]);
|
|
716
|
+
}, []);
|
|
717
|
+
return (0, react.useCallback)((event) => instanceRef.current?.start(event), []);
|
|
971
718
|
}
|
|
972
719
|
//#endregion
|
|
973
720
|
//#region src/components/NumberInput/StepperButton.tsx
|
|
@@ -979,7 +726,7 @@ function StepperButton({ direction, variant, icon, className, style, children, o
|
|
|
979
726
|
const { step, disabled, readonly, atMin, atMax, nudge } = useNumberInputContext();
|
|
980
727
|
const stepper = useStepperContext();
|
|
981
728
|
const press = useLongPress(() => {
|
|
982
|
-
if (stepper?.
|
|
729
|
+
if (stepper?.moved()) return;
|
|
983
730
|
nudge(direction, ["raw", step]);
|
|
984
731
|
});
|
|
985
732
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1038,21 +785,6 @@ function IncrementStepper(props) {
|
|
|
1038
785
|
}
|
|
1039
786
|
//#endregion
|
|
1040
787
|
//#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
788
|
/**
|
|
1057
789
|
* The text field of a `NumberInput`, and the only place the value can be typed.
|
|
1058
790
|
*
|
|
@@ -1062,7 +794,7 @@ const decimalAnchor = (text) => {
|
|
|
1062
794
|
* How it behaves on focus, Enter and the arrow keys is set on `Root`
|
|
1063
795
|
* (`selectOnFocus`, `unformatOnFocus`, `keepCaretOnStep`, `blurOnEnter`).
|
|
1064
796
|
*/
|
|
1065
|
-
const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ className, style, onFocus, onBlur, onKeyDown, ...props }, forwardedRef) {
|
|
797
|
+
const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ className, style, onChange, onFocus, onBlur, onKeyDown, ...props }, forwardedRef) {
|
|
1066
798
|
const { value, min, max, step, disabled, readonly, keyboard, selectOnFocus, unformatOnFocus, keepCaretOnStep, blurOnEnter, text, editing, outOfRange, setDraft, commitDraft, nudge, inputRef } = useNumberInputContext();
|
|
1067
799
|
const [focused, setFocused] = (0, react.useState)(false);
|
|
1068
800
|
const shown = unformatOnFocus && focused && !editing ? String(value) : text;
|
|
@@ -1071,7 +803,10 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1071
803
|
const input = inputRef.current;
|
|
1072
804
|
if (!input) return;
|
|
1073
805
|
if (selectOnFocus === "all") input.setSelectionRange(0, input.value.length);
|
|
1074
|
-
else
|
|
806
|
+
else {
|
|
807
|
+
const span = (0, _tremolo_ui_dom.numberSpan)(input.value);
|
|
808
|
+
if (span) input.setSelectionRange(span.start, span.end);
|
|
809
|
+
}
|
|
1075
810
|
}, [
|
|
1076
811
|
focused,
|
|
1077
812
|
selectOnFocus,
|
|
@@ -1088,8 +823,7 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1088
823
|
caretAfterStep.current = null;
|
|
1089
824
|
const input = inputRef.current;
|
|
1090
825
|
if (!input || input.value === pending.from) return;
|
|
1091
|
-
const
|
|
1092
|
-
const caret = Math.max(0, Math.min(place, numberEnd(input.value)));
|
|
826
|
+
const caret = (0, _tremolo_ui_dom.caretAtDecimalOffset)(input.value, pending.offset);
|
|
1093
827
|
input.setSelectionRange(caret, caret);
|
|
1094
828
|
});
|
|
1095
829
|
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, inputRef);
|
|
@@ -1116,6 +850,7 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1116
850
|
onChange: (event) => {
|
|
1117
851
|
caretAfterStep.current = null;
|
|
1118
852
|
setDraft(event.currentTarget.value);
|
|
853
|
+
onChange?.(event);
|
|
1119
854
|
},
|
|
1120
855
|
onFocus: (event) => {
|
|
1121
856
|
setFocused(true);
|
|
@@ -1135,7 +870,7 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1135
870
|
event.preventDefault();
|
|
1136
871
|
const input = event.currentTarget;
|
|
1137
872
|
if (keepCaretOnStep && input.selectionStart !== null) caretAfterStep.current = {
|
|
1138
|
-
offset: input.
|
|
873
|
+
offset: (0, _tremolo_ui_dom.caretDecimalOffset)(input.value, input.selectionStart),
|
|
1139
874
|
from: input.value
|
|
1140
875
|
};
|
|
1141
876
|
nudge(key === "ArrowUp" ? 1 : -1, keyboard, event);
|
|
@@ -1146,117 +881,54 @@ const InputField = /* @__PURE__ */ (0, react.forwardRef)(function InputField({ c
|
|
|
1146
881
|
});
|
|
1147
882
|
});
|
|
1148
883
|
//#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
884
|
//#region src/components/NumberInput/Stepper.tsx
|
|
1184
885
|
/**
|
|
1185
886
|
* 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.
|
|
887
|
+
* up and down moves the value one `step` every `drag` pixels. The counting is
|
|
888
|
+
* `createStepperDrag` in the core.
|
|
1187
889
|
*
|
|
1188
890
|
* The drag lives here rather than on `InputField` because `createDrag` turns
|
|
1189
891
|
* off text selection on whatever element it is attached to.
|
|
1190
892
|
*/
|
|
1191
893
|
const Stepper = /* @__PURE__ */ (0, react.forwardRef)(function Stepper({ className, style, children, ...props }, forwardedRef) {
|
|
1192
|
-
const { value,
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
}
|
|
894
|
+
const { value, disabled, readonly, drag, dragSensitivity, pointerLock, rawRange, changeValue } = useNumberInputContext();
|
|
895
|
+
const enabled = drag !== null && !disabled && !readonly;
|
|
896
|
+
const [node, setNode] = (0, react.useState)(null);
|
|
897
|
+
const instanceRef = (0, react.useRef)(null);
|
|
898
|
+
const latest = (0, react.useRef)({
|
|
899
|
+
value,
|
|
900
|
+
changeValue
|
|
901
|
+
});
|
|
902
|
+
const options = {
|
|
903
|
+
range: rawRange,
|
|
904
|
+
pixels: drag ?? 1,
|
|
905
|
+
sensitivity: dragSensitivity,
|
|
906
|
+
pointerLock
|
|
907
|
+
};
|
|
908
|
+
const optionsRef = (0, react.useRef)(options);
|
|
909
|
+
(0, react.useEffect)(() => {
|
|
910
|
+
latest.current = {
|
|
911
|
+
value,
|
|
912
|
+
changeValue
|
|
913
|
+
};
|
|
914
|
+
optionsRef.current = options;
|
|
915
|
+
instanceRef.current?.update(options);
|
|
1257
916
|
});
|
|
1258
|
-
|
|
1259
|
-
|
|
917
|
+
(0, react.useEffect)(() => {
|
|
918
|
+
if (!node || !enabled) return;
|
|
919
|
+
const instance = (0, _tremolo_ui_dom.createStepperDrag)(node, {
|
|
920
|
+
...optionsRef.current,
|
|
921
|
+
getValue: () => latest.current.value,
|
|
922
|
+
onChange: (next) => latest.current.changeValue(next)
|
|
923
|
+
});
|
|
924
|
+
instanceRef.current = instance;
|
|
925
|
+
return () => {
|
|
926
|
+
instanceRef.current = null;
|
|
927
|
+
instance.destroy();
|
|
928
|
+
};
|
|
929
|
+
}, [node, enabled]);
|
|
930
|
+
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, setNode);
|
|
931
|
+
const context = (0, react.useMemo)(() => ({ moved: () => instanceRef.current?.moved() ?? false }), []);
|
|
1260
932
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepperProvider, {
|
|
1261
933
|
value: context,
|
|
1262
934
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1270,22 +942,12 @@ const Stepper = /* @__PURE__ */ (0, react.forwardRef)(function Stepper({ classNa
|
|
|
1270
942
|
});
|
|
1271
943
|
//#endregion
|
|
1272
944
|
//#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
945
|
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
946
|
/**
|
|
1285
947
|
* Input with some useful functions for entering numerical values.
|
|
1286
948
|
*/
|
|
1287
949
|
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, readonly = false, className, style, onChange, children, ...props }, forwardedRef) => {
|
|
950
|
+
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, selectOnFocus = "none", unformatOnFocus = false, keepCaretOnStep = false, blurOnEnter = true, disabled = false, readonly = false, className, style, onChange, children, ...props }, forwardedRef) => {
|
|
1289
951
|
const inputRef = (0, react.useRef)(null);
|
|
1290
952
|
/**
|
|
1291
953
|
* The text being typed. The only state here: it is not a copy of `value`,
|
|
@@ -1294,24 +956,13 @@ const NumberInput = {
|
|
|
1294
956
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
1295
957
|
const inactive = disabled || readonly;
|
|
1296
958
|
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,
|
|
959
|
+
const parse = parseProp ?? _tremolo_ui_dom.parseNumberText;
|
|
960
|
+
const { normalized: range, raw: rawRange } = (0, react.useMemo)(() => (0, _tremolo_ui_dom.numberInputRanges)({
|
|
1305
961
|
min,
|
|
1306
962
|
max,
|
|
1307
963
|
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
|
|
964
|
+
scale,
|
|
965
|
+
clampValue
|
|
1315
966
|
}), [
|
|
1316
967
|
clampValue,
|
|
1317
968
|
min,
|
|
@@ -1319,6 +970,10 @@ const NumberInput = {
|
|
|
1319
970
|
step,
|
|
1320
971
|
scale
|
|
1321
972
|
]);
|
|
973
|
+
const ranges = (0, react.useMemo)(() => ({
|
|
974
|
+
normalized: range,
|
|
975
|
+
raw: rawRange
|
|
976
|
+
}), [range, rawRange]);
|
|
1322
977
|
useCheckSteps({
|
|
1323
978
|
component: "NumberInput",
|
|
1324
979
|
range: (0, react.useMemo)(() => min !== void 0 && max !== void 0 && min < max ? {
|
|
@@ -1338,7 +993,13 @@ const NumberInput = {
|
|
|
1338
993
|
});
|
|
1339
994
|
const text = draft ?? format(value);
|
|
1340
995
|
const editing = draft !== null;
|
|
1341
|
-
const
|
|
996
|
+
const bounds = (0, _tremolo_ui_dom.numberInputBounds)(value, {
|
|
997
|
+
min,
|
|
998
|
+
max,
|
|
999
|
+
clampValue
|
|
1000
|
+
});
|
|
1001
|
+
const { atMin, atMax } = bounds;
|
|
1002
|
+
const outOfRange = !editing && bounds.outOfRange;
|
|
1342
1003
|
const handleDraft = (0, react.useCallback)((next) => {
|
|
1343
1004
|
if (inactive) return;
|
|
1344
1005
|
setDraft(next);
|
|
@@ -1360,15 +1021,13 @@ const NumberInput = {
|
|
|
1360
1021
|
]);
|
|
1361
1022
|
const commitDraft = (0, react.useCallback)(() => {
|
|
1362
1023
|
if (draft === null || inactive) return;
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
if (clampValue && max !== void 0) committed = Math.min(committed, max);
|
|
1371
|
-
changeValue(committed);
|
|
1024
|
+
const committed = (0, _tremolo_ui_dom.commitNumberInputText)(draft, parse, {
|
|
1025
|
+
min,
|
|
1026
|
+
max,
|
|
1027
|
+
clampValue
|
|
1028
|
+
});
|
|
1029
|
+
if (committed === null) setDraft(null);
|
|
1030
|
+
else changeValue(committed);
|
|
1372
1031
|
}, [
|
|
1373
1032
|
draft,
|
|
1374
1033
|
inactive,
|
|
@@ -1379,18 +1038,17 @@ const NumberInput = {
|
|
|
1379
1038
|
changeValue
|
|
1380
1039
|
]);
|
|
1381
1040
|
const nudge = (0, react.useCallback)((direction, option, modifiers) => {
|
|
1382
|
-
|
|
1383
|
-
changeValue((0, _tremolo_ui_dom.applyDelta)(value, direction, option, mode === "raw" ? rawRange : range, modifiers));
|
|
1041
|
+
changeValue((0, _tremolo_ui_dom.nudgeNumberInput)(value, direction, option, ranges, modifiers));
|
|
1384
1042
|
}, [
|
|
1385
1043
|
changeValue,
|
|
1386
1044
|
value,
|
|
1387
|
-
|
|
1388
|
-
range
|
|
1045
|
+
ranges
|
|
1389
1046
|
]);
|
|
1390
1047
|
const wheelRefCallback = useWheel((event) => {
|
|
1391
|
-
|
|
1048
|
+
const direction = (0, _tremolo_ui_dom.wheelDirection)(event);
|
|
1049
|
+
if (!wheel || inactive || direction === null) return;
|
|
1392
1050
|
event.preventDefault();
|
|
1393
|
-
nudge(
|
|
1051
|
+
nudge(direction, wheel, event);
|
|
1394
1052
|
}, { requireFocus: true });
|
|
1395
1053
|
const context = (0, react.useMemo)(() => ({
|
|
1396
1054
|
value,
|
|
@@ -1413,8 +1071,8 @@ const NumberInput = {
|
|
|
1413
1071
|
unformatOnFocus,
|
|
1414
1072
|
keepCaretOnStep,
|
|
1415
1073
|
blurOnEnter,
|
|
1416
|
-
atMin
|
|
1417
|
-
atMax
|
|
1074
|
+
atMin,
|
|
1075
|
+
atMax,
|
|
1418
1076
|
drag,
|
|
1419
1077
|
format,
|
|
1420
1078
|
parse,
|
|
@@ -1438,6 +1096,8 @@ const NumberInput = {
|
|
|
1438
1096
|
text,
|
|
1439
1097
|
editing,
|
|
1440
1098
|
outOfRange,
|
|
1099
|
+
atMin,
|
|
1100
|
+
atMax,
|
|
1441
1101
|
drag,
|
|
1442
1102
|
dragSensitivity,
|
|
1443
1103
|
pointerLock,
|
|
@@ -1480,78 +1140,15 @@ const NumberInput = {
|
|
|
1480
1140
|
IncrementStepper,
|
|
1481
1141
|
DecrementStepper
|
|
1482
1142
|
};
|
|
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
1143
|
/**
|
|
1546
1144
|
* Customizable piano component.
|
|
1547
1145
|
*/
|
|
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,
|
|
1146
|
+
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, ...props }, forwardedRef) {
|
|
1549
1147
|
const [node, setNode] = (0, react.useState)(null);
|
|
1550
1148
|
const [activeNotes, setActiveNotes] = (0, react.useState)([]);
|
|
1551
1149
|
/** Set while `resizable` is on, measured from the parent. */
|
|
1552
1150
|
const [resizedKeyWidth, setResizedKeyWidth] = (0, react.useState)(whiteKeyWidth);
|
|
1553
1151
|
const notes = (0, react.useMemo)(() => (0, _tremolo_ui_dom.getNoteRangeArray)(noteRange), [noteRange]);
|
|
1554
|
-
const whiteKeyCount = (0, react.useMemo)(() => notes.filter(_tremolo_ui_functions.isWhiteKey).length, [notes]);
|
|
1555
1152
|
const layout = (0, react.useMemo)(() => ({
|
|
1556
1153
|
noteRange,
|
|
1557
1154
|
whiteKeyWidth: resizable ? resizedKeyWidth : whiteKeyWidth,
|
|
@@ -1571,21 +1168,20 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1571
1168
|
layout,
|
|
1572
1169
|
glissando,
|
|
1573
1170
|
midiMax,
|
|
1171
|
+
keyboardShortcuts,
|
|
1172
|
+
keyboardShortcutsScope,
|
|
1574
1173
|
onPlayNote,
|
|
1575
1174
|
onStopNote
|
|
1576
1175
|
});
|
|
1577
1176
|
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
1177
|
(0, react.useEffect)(() => {
|
|
1584
1178
|
if (!node) return;
|
|
1585
1179
|
const instance = (0, _tremolo_ui_dom.createPianoInput)(node, {
|
|
1586
1180
|
layout: latest.current.layout,
|
|
1587
1181
|
glissando: latest.current.glissando,
|
|
1588
1182
|
midiMax: latest.current.midiMax,
|
|
1183
|
+
keyboardShortcuts: latest.current.keyboardShortcuts,
|
|
1184
|
+
keyboardShortcutsScope: latest.current.keyboardShortcutsScope,
|
|
1589
1185
|
onPlayNote: (note, velocity) => latest.current.onPlayNote?.(note, velocity),
|
|
1590
1186
|
onStopNote: (note) => latest.current.onStopNote?.(note),
|
|
1591
1187
|
onActiveNotesChange: setActiveNotes
|
|
@@ -1601,86 +1197,44 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1601
1197
|
layout,
|
|
1602
1198
|
glissando,
|
|
1603
1199
|
midiMax,
|
|
1200
|
+
keyboardShortcuts,
|
|
1201
|
+
keyboardShortcutsScope,
|
|
1604
1202
|
onPlayNote,
|
|
1605
1203
|
onStopNote
|
|
1606
1204
|
};
|
|
1607
1205
|
instanceRef.current?.update({
|
|
1608
1206
|
layout,
|
|
1609
1207
|
glissando,
|
|
1610
|
-
midiMax
|
|
1208
|
+
midiMax,
|
|
1209
|
+
keyboardShortcuts,
|
|
1210
|
+
keyboardShortcutsScope
|
|
1611
1211
|
});
|
|
1612
1212
|
});
|
|
1213
|
+
const { first, last } = noteRange;
|
|
1613
1214
|
(0, react.useEffect)(() => {
|
|
1614
1215
|
if (!resizable || !node) return;
|
|
1615
1216
|
const parent = node.parentElement;
|
|
1616
1217
|
if (!parent) throw new Error("doesn't have a parent element.");
|
|
1617
1218
|
const resizeObserver = new ResizeObserver(() => {
|
|
1618
|
-
setResizedKeyWidth(node.clientWidth
|
|
1219
|
+
setResizedKeyWidth((0, _tremolo_ui_dom.fitWhiteKeyWidth)(node.clientWidth, {
|
|
1220
|
+
noteRange: {
|
|
1221
|
+
first,
|
|
1222
|
+
last
|
|
1223
|
+
},
|
|
1224
|
+
keyGap,
|
|
1225
|
+
blackKeyWidthRatio
|
|
1226
|
+
}));
|
|
1619
1227
|
});
|
|
1620
1228
|
resizeObserver.observe(parent);
|
|
1621
1229
|
return () => resizeObserver.disconnect();
|
|
1622
1230
|
}, [
|
|
1623
1231
|
resizable,
|
|
1624
1232
|
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
|
|
1233
|
+
first,
|
|
1234
|
+
last,
|
|
1235
|
+
keyGap,
|
|
1236
|
+
blackKeyWidthRatio
|
|
1657
1237
|
]);
|
|
1658
|
-
const windowBlurTarget = (0, react.useMemo)(() => () => hasShortcuts ? globalThis.window : null, [hasShortcuts]);
|
|
1659
|
-
useEventListener(shortcutTarget, "keydown", (e) => {
|
|
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
1238
|
(0, react.useImperativeHandle)(forwardedRef, () => ({
|
|
1685
1239
|
playNote: (note, velocity) => instanceRef.current?.noteOn(note, {
|
|
1686
1240
|
source: "api",
|
|
@@ -1696,7 +1250,6 @@ const Piano = { Root: /* @__PURE__ */ (0, react.forwardRef)(function Root({ note
|
|
|
1696
1250
|
tabIndex: 0,
|
|
1697
1251
|
style: {
|
|
1698
1252
|
width: resizable ? "100%" : (0, _tremolo_ui_dom.pianoWidth)(layout),
|
|
1699
|
-
"--height": cssLength(height),
|
|
1700
1253
|
position: "relative",
|
|
1701
1254
|
...style
|
|
1702
1255
|
},
|
|
@@ -1762,30 +1315,58 @@ function usePointsEditorContext(selector) {
|
|
|
1762
1315
|
return selector ? selector(context) : context;
|
|
1763
1316
|
}
|
|
1764
1317
|
//#endregion
|
|
1318
|
+
//#region src/components/PointsEditor/Container.tsx
|
|
1319
|
+
const Container = /* @__PURE__ */ (0, react.forwardRef)(function Container({ children, className, style, ...props }, forwardedRef) {
|
|
1320
|
+
const { containerRef, disabled, selectable, editor } = usePointsEditorContext();
|
|
1321
|
+
const { refCallback: dragRefCallback } = useDragValue({
|
|
1322
|
+
axis: _tremolo_ui_dom.POINT_AXIS,
|
|
1323
|
+
baseElementRef: containerRef,
|
|
1324
|
+
shouldStart: (event) => !disabled && !editor.isPointElement(event.target),
|
|
1325
|
+
onDragStart: ([x, y], state) => {
|
|
1326
|
+
editor.beginSelectionBox({
|
|
1327
|
+
x,
|
|
1328
|
+
y
|
|
1329
|
+
}, state.event);
|
|
1330
|
+
},
|
|
1331
|
+
onChange: ([x, y]) => editor.moveSelectionBox({
|
|
1332
|
+
x,
|
|
1333
|
+
y
|
|
1334
|
+
}),
|
|
1335
|
+
onDragEnd: () => editor.endSelectionBox()
|
|
1336
|
+
});
|
|
1337
|
+
useWheel((event) => {
|
|
1338
|
+
const move = (0, _tremolo_ui_dom.wheelMove)(event);
|
|
1339
|
+
if (!move) return;
|
|
1340
|
+
const axis = move.axis === 0 ? "x" : "y";
|
|
1341
|
+
if (editor.nudgeFocusedPoint(axis, move.direction, event)) event.preventDefault();
|
|
1342
|
+
}, { target: containerRef });
|
|
1343
|
+
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, containerRef, selectable && !disabled ? dragRefCallback : void 0);
|
|
1344
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1345
|
+
ref: composedRef,
|
|
1346
|
+
className,
|
|
1347
|
+
style: {
|
|
1348
|
+
position: "absolute",
|
|
1349
|
+
inset: 0,
|
|
1350
|
+
zIndex: 10,
|
|
1351
|
+
...style
|
|
1352
|
+
},
|
|
1353
|
+
...props,
|
|
1354
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Placement, {
|
|
1355
|
+
name: "PointsEditor.Container",
|
|
1356
|
+
children
|
|
1357
|
+
})
|
|
1358
|
+
});
|
|
1359
|
+
});
|
|
1360
|
+
//#endregion
|
|
1765
1361
|
//#region src/components/_util/VisuallyHiddenRangeInput.tsx
|
|
1766
1362
|
const VisuallyHiddenRangeInput = /* @__PURE__ */ (0, react.forwardRef)((props, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1767
1363
|
...props,
|
|
1768
1364
|
ref,
|
|
1769
1365
|
type: "range",
|
|
1770
|
-
style: visuallyHiddenStyle
|
|
1366
|
+
style: _tremolo_ui_dom.visuallyHiddenStyle
|
|
1771
1367
|
}));
|
|
1772
1368
|
//#endregion
|
|
1773
1369
|
//#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
1370
|
/**
|
|
1790
1371
|
* One setting for both axes, or one per axis. The point holds a range input
|
|
1791
1372
|
* for each, so anything that names or describes it comes in a pair.
|
|
@@ -1793,8 +1374,8 @@ const AXIS = {
|
|
|
1793
1374
|
function perAxis(value, axis) {
|
|
1794
1375
|
return typeof value === "string" ? value : value?.[axis];
|
|
1795
1376
|
}
|
|
1796
|
-
function Point({ value, children, id: idProp, min, max,
|
|
1797
|
-
const { containerRef, externalStyles, disabled: rootDisabled, readonly: rootReadonly, wheel: rootWheel, keyboard: rootKeyboard, dragSensitivity, selection,
|
|
1377
|
+
function Point({ value, children, id: idProp, min, max, color, disabled: _disabled, readonly: _readonly, wheel: _wheel, keyboard: _keyboard, "aria-label": ariaLabel, "aria-valuetext": ariaValuetext, onChange, onDragStart, onDragEnd, className, style, onPointerDown, onKeyDown, onFocus, ...props }) {
|
|
1378
|
+
const { containerRef, externalStyles, disabled: rootDisabled, readonly: rootReadonly, wheel: rootWheel, keyboard: rootKeyboard, dragSensitivity, selection, editor } = usePointsEditorContext();
|
|
1798
1379
|
const generatedId = (0, react.useId)();
|
|
1799
1380
|
const id = idProp ?? generatedId;
|
|
1800
1381
|
const selected = selection.includes(id);
|
|
@@ -1827,11 +1408,11 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1827
1408
|
wheel
|
|
1828
1409
|
};
|
|
1829
1410
|
});
|
|
1830
|
-
(0, react.useEffect)(() => registerPoint(id, registration), [id,
|
|
1411
|
+
(0, react.useEffect)(() => editor.registerPoint(id, () => registration.current), [id, editor]);
|
|
1831
1412
|
/** Where the pointer was when the drag started, to measure the move from. */
|
|
1832
1413
|
const pointerOrigin = (0, react.useRef)(null);
|
|
1833
1414
|
const { refCallback: dragRefCallback, dragging } = useDragValue({
|
|
1834
|
-
axis:
|
|
1415
|
+
axis: _tremolo_ui_dom.POINT_AXIS,
|
|
1835
1416
|
baseElementRef: containerRef,
|
|
1836
1417
|
sensitivity: (state) => (0, _tremolo_ui_dom.selectModifier)(dragSensitivity, state.event).value,
|
|
1837
1418
|
cursor: inactive ? void 0 : externalStyles.cursor,
|
|
@@ -1839,53 +1420,36 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1839
1420
|
onChange: ([x, y]) => {
|
|
1840
1421
|
const origin = pointerOrigin.current;
|
|
1841
1422
|
if (!origin) return;
|
|
1842
|
-
movePointDrag({
|
|
1423
|
+
editor.movePointDrag({
|
|
1843
1424
|
x: x - origin.x,
|
|
1844
1425
|
y: y - origin.y
|
|
1845
1426
|
});
|
|
1846
1427
|
},
|
|
1847
1428
|
onDragStart: ([x, y], state) => {
|
|
1848
|
-
beginPointDrag(id, state.event);
|
|
1429
|
+
editor.beginPointDrag(id, state.event);
|
|
1849
1430
|
pointerOrigin.current = {
|
|
1850
1431
|
x,
|
|
1851
1432
|
y
|
|
1852
1433
|
};
|
|
1853
1434
|
xInputRef.current?.focus();
|
|
1854
1435
|
if (inactive) return;
|
|
1855
|
-
onDragStart?.(clampPoint(value, min, max));
|
|
1436
|
+
onDragStart?.((0, _tremolo_ui_dom.clampPoint)(value, min, max));
|
|
1856
1437
|
},
|
|
1857
1438
|
onDragEnd: () => {
|
|
1858
1439
|
pointerOrigin.current = null;
|
|
1859
1440
|
if (inactive) return;
|
|
1860
|
-
onDragEnd?.(clampPoint(value, min, max));
|
|
1441
|
+
onDragEnd?.((0, _tremolo_ui_dom.clampPoint)(value, min, max));
|
|
1861
1442
|
}
|
|
1862
1443
|
});
|
|
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
1444
|
const refCallback = require_compose_refs.useComposedRefs(dragRefCallback, setElement);
|
|
1875
1445
|
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";
|
|
1446
|
+
const move = (0, _tremolo_ui_dom.arrowKeyMove)(event.key);
|
|
1447
|
+
if (!move) return;
|
|
1884
1448
|
event.preventDefault();
|
|
1885
1449
|
if (!onChange || inactive || !keyboard) return;
|
|
1886
|
-
|
|
1450
|
+
editor.nudgePoint(id, move.axis === 0 ? "x" : "y", move.direction, keyboard, event);
|
|
1887
1451
|
};
|
|
1888
|
-
const current = clampPoint(value, min, max);
|
|
1452
|
+
const current = (0, _tremolo_ui_dom.clampPoint)(value, min, max);
|
|
1889
1453
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1890
1454
|
ref: refCallback,
|
|
1891
1455
|
className,
|
|
@@ -1896,8 +1460,6 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1896
1460
|
"data-selected": selected ? "" : void 0,
|
|
1897
1461
|
style: {
|
|
1898
1462
|
"--color": color,
|
|
1899
|
-
"--width": cssLength(size ?? width),
|
|
1900
|
-
"--height": cssLength(size ?? height),
|
|
1901
1463
|
position: "absolute",
|
|
1902
1464
|
translate: "var(--translate, -50% -50%)",
|
|
1903
1465
|
...style,
|
|
@@ -1932,7 +1494,7 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1932
1494
|
return;
|
|
1933
1495
|
}
|
|
1934
1496
|
const delta = event.currentTarget.valueAsNumber - value[axis];
|
|
1935
|
-
nudgeSelection(id, {
|
|
1497
|
+
editor.nudgeSelection(id, {
|
|
1936
1498
|
x: axis === "x" ? delta : 0,
|
|
1937
1499
|
y: axis === "y" ? delta : 0
|
|
1938
1500
|
});
|
|
@@ -1941,50 +1503,6 @@ function Point({ value, children, id: idProp, min, max, size, width, height, col
|
|
|
1941
1503
|
});
|
|
1942
1504
|
}
|
|
1943
1505
|
//#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
1506
|
//#region src/components/PointsEditor/SelectionBox.tsx
|
|
1989
1507
|
/**
|
|
1990
1508
|
* The box a drag on empty space draws, and what it covers is selected.
|
|
@@ -2017,181 +1535,28 @@ function SelectionBox({ children, className, style, ...props }) {
|
|
|
2017
1535
|
/** One array for every editor with selection turned off, so memos hold still. */
|
|
2018
1536
|
const EMPTY = [];
|
|
2019
1537
|
/**
|
|
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
1538
|
* Multiple Point Controller
|
|
2058
1539
|
*/
|
|
2059
1540
|
const PointsEditor = {
|
|
2060
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({
|
|
1541
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ disabled = false, readonly = false, wheel = _tremolo_ui_dom.POINTS_EDITOR_DEFAULT_WHEEL, keyboard = _tremolo_ui_dom.POINTS_EDITOR_DEFAULT_KEYBOARD, dragSensitivity = _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY, selectable = false, selection: selectionProp, defaultSelection, onSelectionChange, externalStyles, style, className, children, ...props }, forwardedRef) => {
|
|
2061
1542
|
const containerRef = (0, react.useRef)(null);
|
|
2062
1543
|
const { cursor = "grabbing" } = externalStyles ?? {};
|
|
2063
1544
|
const controlled = selectionProp !== void 0;
|
|
2064
1545
|
const [ownSelection, setOwnSelection] = (0, react.useState)(defaultSelection ?? []);
|
|
2065
1546
|
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
1547
|
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
|
-
});
|
|
1548
|
+
const [editor] = (0, react.useState)(() => (0, _tremolo_ui_dom.createPointsEditor)({ onSelectionBoxChange: setSelectionBox }));
|
|
2173
1549
|
(0, react.useEffect)(() => {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
if (!selectable) return;
|
|
2182
|
-
selectionBoxRef.current?.begin([at.x, at.y], {
|
|
2183
|
-
additive: modifiers.ctrlKey || modifiers.metaKey,
|
|
2184
|
-
selection: selectionRef.current
|
|
1550
|
+
editor.update({
|
|
1551
|
+
selectable,
|
|
1552
|
+
selection,
|
|
1553
|
+
onSelectionChange: (next) => {
|
|
1554
|
+
if (!controlled) setOwnSelection(next);
|
|
1555
|
+
onSelectionChange?.(next);
|
|
1556
|
+
}
|
|
2185
1557
|
});
|
|
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
|
-
}, []);
|
|
1558
|
+
});
|
|
1559
|
+
(0, react.useEffect)(() => () => editor.destroy(), [editor]);
|
|
2195
1560
|
const context = (0, react.useMemo)(() => ({
|
|
2196
1561
|
disabled,
|
|
2197
1562
|
readonly,
|
|
@@ -2202,16 +1567,8 @@ const PointsEditor = {
|
|
|
2202
1567
|
containerRef,
|
|
2203
1568
|
selectable,
|
|
2204
1569
|
selection,
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
beginPointDrag,
|
|
2208
|
-
movePointDrag,
|
|
2209
|
-
nudgeSelection,
|
|
2210
|
-
nudgeFocusedPoint,
|
|
2211
|
-
selectionBox,
|
|
2212
|
-
beginSelectionBox,
|
|
2213
|
-
moveSelectionBox,
|
|
2214
|
-
endSelectionBox
|
|
1570
|
+
editor,
|
|
1571
|
+
selectionBox
|
|
2215
1572
|
}), [
|
|
2216
1573
|
disabled,
|
|
2217
1574
|
readonly,
|
|
@@ -2221,16 +1578,8 @@ const PointsEditor = {
|
|
|
2221
1578
|
cursor,
|
|
2222
1579
|
selectable,
|
|
2223
1580
|
selection,
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
beginPointDrag,
|
|
2227
|
-
movePointDrag,
|
|
2228
|
-
nudgeSelection,
|
|
2229
|
-
nudgeFocusedPoint,
|
|
2230
|
-
selectionBox,
|
|
2231
|
-
beginSelectionBox,
|
|
2232
|
-
moveSelectionBox,
|
|
2233
|
-
endSelectionBox
|
|
1581
|
+
editor,
|
|
1582
|
+
selectionBox
|
|
2234
1583
|
]);
|
|
2235
1584
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointsEditorProvider, {
|
|
2236
1585
|
value: context,
|
|
@@ -2240,8 +1589,6 @@ const PointsEditor = {
|
|
|
2240
1589
|
"data-disabled": disabled ? "" : void 0,
|
|
2241
1590
|
"data-readonly": readonly ? "" : void 0,
|
|
2242
1591
|
style: {
|
|
2243
|
-
"--width": cssLength(width),
|
|
2244
|
-
"--height": cssLength(height),
|
|
2245
1592
|
position: "relative",
|
|
2246
1593
|
...style
|
|
2247
1594
|
},
|
|
@@ -2266,36 +1613,30 @@ function useSliderContext(selector) {
|
|
|
2266
1613
|
}
|
|
2267
1614
|
//#endregion
|
|
2268
1615
|
//#region src/components/Slider/MarksOption.tsx
|
|
2269
|
-
function MarksOption({ value, mark = true, label, thickness, length, gap,
|
|
1616
|
+
function MarksOption({ value, mark = true, label, thickness, length, gap, classes, styles, className, style, ...props }) {
|
|
2270
1617
|
useCheckPlacement("Slider.MarksOption", "Slider.Marks");
|
|
2271
1618
|
const min = useSliderContext((s) => s.min);
|
|
2272
1619
|
const max = useSliderContext((s) => s.max);
|
|
2273
1620
|
const scale = useSliderContext((s) => s.scale);
|
|
2274
1621
|
const vertical = useSliderContext((s) => s.vertical);
|
|
2275
1622
|
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,
|
|
1623
|
+
const percent = (0, _tremolo_ui_dom.valuePercent)(value, {
|
|
2283
1624
|
min,
|
|
1625
|
+
max,
|
|
2284
1626
|
scale
|
|
2285
|
-
|
|
1627
|
+
}, vertical !== reverse);
|
|
2286
1628
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
1629
|
className,
|
|
2288
1630
|
style: {
|
|
2289
|
-
"--thickness": cssLength(thickness),
|
|
2290
|
-
"--length": cssLength(length),
|
|
2291
|
-
"--gap": cssLength(gap),
|
|
2292
|
-
"--label-width": cssLength(labelWidth),
|
|
1631
|
+
"--thickness": (0, _tremolo_ui_dom.cssLength)(thickness),
|
|
1632
|
+
"--length": (0, _tremolo_ui_dom.cssLength)(length),
|
|
1633
|
+
"--gap": (0, _tremolo_ui_dom.cssLength)(gap),
|
|
2293
1634
|
position: "absolute",
|
|
2294
1635
|
translate: vertical ? "var(--translate, 0 -50%)" : "var(--translate, -50% 0)",
|
|
2295
1636
|
zIndex: 10,
|
|
2296
1637
|
...style,
|
|
2297
|
-
left: !vertical ? `${
|
|
2298
|
-
top: vertical ? `${
|
|
1638
|
+
left: !vertical ? `${percent}%` : void 0,
|
|
1639
|
+
top: vertical ? `${percent}%` : void 0
|
|
2299
1640
|
},
|
|
2300
1641
|
"data-orientation": vertical ? "vertical" : "horizontal",
|
|
2301
1642
|
...props,
|
|
@@ -2311,44 +1652,6 @@ function MarksOption({ value, mark = true, label, thickness, length, gap, labelW
|
|
|
2311
1652
|
});
|
|
2312
1653
|
}
|
|
2313
1654
|
//#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
1655
|
//#region src/components/Slider/Marks.tsx
|
|
2353
1656
|
function Marks({ gap, options, children, className, style, ...props }) {
|
|
2354
1657
|
const min = useSliderContext((s) => s.min);
|
|
@@ -2356,13 +1659,13 @@ function Marks({ gap, options, children, className, style, ...props }) {
|
|
|
2356
1659
|
const step = useSliderContext((s) => s.step);
|
|
2357
1660
|
const vertical = useSliderContext((s) => s.vertical);
|
|
2358
1661
|
const reverse = useSliderContext((s) => s.reverse);
|
|
2359
|
-
const optionsList = options ?
|
|
1662
|
+
const optionsList = options ? (0, _tremolo_ui_dom.sliderMarks)(options, min, max, step) : [];
|
|
2360
1663
|
if (vertical !== reverse) optionsList.reverse();
|
|
2361
1664
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2362
1665
|
className,
|
|
2363
1666
|
"data-orientation": vertical ? "vertical" : "horizontal",
|
|
2364
1667
|
style: {
|
|
2365
|
-
"--gap": cssLength(gap),
|
|
1668
|
+
"--gap": (0, _tremolo_ui_dom.cssLength)(gap),
|
|
2366
1669
|
position: "relative",
|
|
2367
1670
|
...style
|
|
2368
1671
|
},
|
|
@@ -2446,8 +1749,8 @@ const Track = /* @__PURE__ */ (0, react.forwardRef)(function Track({ length, thi
|
|
|
2446
1749
|
style: {
|
|
2447
1750
|
"--active": active,
|
|
2448
1751
|
"--inactive": inactive,
|
|
2449
|
-
"--length": cssLength(length),
|
|
2450
|
-
"--thickness": cssLength(thickness),
|
|
1752
|
+
"--length": (0, _tremolo_ui_dom.cssLength)(length),
|
|
1753
|
+
"--thickness": (0, _tremolo_ui_dom.cssLength)(thickness),
|
|
2451
1754
|
"--percent": `${percent}%`,
|
|
2452
1755
|
position: "relative",
|
|
2453
1756
|
...style
|
|
@@ -2471,7 +1774,7 @@ const WHEEL_OPTIONS$1 = { requireFocus: true };
|
|
|
2471
1774
|
* Customizable slider
|
|
2472
1775
|
*/
|
|
2473
1776
|
const Slider = {
|
|
2474
|
-
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, vertical = false, reverse = false, externalStyles: _externalStyles, wheel = DEFAULT_WHEEL_OPTIONS, keyboard = DEFAULT_KEYBOARD_OPTIONS, dragSensitivity = DEFAULT_DRAG_SENSITIVITY, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown, ...props }, forwardedRef) => {
|
|
1777
|
+
Root: /* @__PURE__ */ (0, react.forwardRef)(({ value, min, max, step = 1, scale = _tremolo_ui_functions.linearScale, vertical = false, reverse = false, externalStyles: _externalStyles, 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, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown, ...props }, forwardedRef) => {
|
|
2475
1778
|
const trackRef = (0, react.useRef)(null);
|
|
2476
1779
|
const thumbRef = (0, react.useRef)(null);
|
|
2477
1780
|
const externalStyles = {
|
|
@@ -2479,10 +1782,12 @@ const Slider = {
|
|
|
2479
1782
|
..._externalStyles
|
|
2480
1783
|
};
|
|
2481
1784
|
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);
|
|
2484
1785
|
const displayReversed = vertical !== reverse;
|
|
2485
|
-
const percent =
|
|
1786
|
+
const percent = (0, _tremolo_ui_dom.valuePercent)(value, {
|
|
1787
|
+
min,
|
|
1788
|
+
max,
|
|
1789
|
+
scale
|
|
1790
|
+
}, displayReversed);
|
|
2486
1791
|
const axis = (0, react.useMemo)(() => ({
|
|
2487
1792
|
min,
|
|
2488
1793
|
max,
|
|
@@ -2503,18 +1808,11 @@ const Slider = {
|
|
|
2503
1808
|
wheel
|
|
2504
1809
|
});
|
|
2505
1810
|
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
2506
|
-
const
|
|
2507
|
-
if (
|
|
2508
|
-
"ArrowRight",
|
|
2509
|
-
"ArrowLeft",
|
|
2510
|
-
"ArrowUp",
|
|
2511
|
-
"ArrowDown"
|
|
2512
|
-
].includes(key)) return;
|
|
1811
|
+
const direction = (0, _tremolo_ui_dom.arrowKeyDirection)(event.key);
|
|
1812
|
+
if (direction === null) return;
|
|
2513
1813
|
event.preventDefault();
|
|
2514
1814
|
if (!keyboard || !onChange || inactive) return;
|
|
2515
|
-
|
|
2516
|
-
if (reverse) direction *= -1;
|
|
2517
|
-
onChange((0, _tremolo_ui_dom.applyDelta)(value, direction, keyboard, axis, event));
|
|
1815
|
+
onChange((0, _tremolo_ui_dom.applyDelta)(value, reverse ? -direction : direction, keyboard, axis, event));
|
|
2518
1816
|
}, [
|
|
2519
1817
|
keyboard,
|
|
2520
1818
|
onChange,
|
|
@@ -2547,15 +1845,10 @@ const Slider = {
|
|
|
2547
1845
|
});
|
|
2548
1846
|
const wheelRefCallback = useWheel((event) => {
|
|
2549
1847
|
if (!wheel || !onChange || inactive) return;
|
|
1848
|
+
const direction = (0, _tremolo_ui_dom.wheelDirection)(event, { horizontal: !vertical });
|
|
1849
|
+
if (direction === null) return;
|
|
2550
1850
|
event.preventDefault();
|
|
2551
|
-
|
|
2552
|
-
if (!vertical && event.deltaX !== 0) direction = event.deltaX > 0 ? 1 : -1;
|
|
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));
|
|
1851
|
+
onChange((0, _tremolo_ui_dom.applyDelta)(value, reverse ? -direction : direction, wheel, axis, event));
|
|
2559
1852
|
}, WHEEL_OPTIONS$1);
|
|
2560
1853
|
const rootRefCallback = require_compose_refs.useComposedRefs(dragRefCallback, wheelRefCallback);
|
|
2561
1854
|
const context = (0, react.useMemo)(() => ({
|
|
@@ -2640,16 +1933,13 @@ function useXYPadContext(selector) {
|
|
|
2640
1933
|
}
|
|
2641
1934
|
//#endregion
|
|
2642
1935
|
//#region src/components/XYPad/Area.tsx
|
|
2643
|
-
const Area = /* @__PURE__ */ (0, react.forwardRef)(function Area({
|
|
1936
|
+
const Area = /* @__PURE__ */ (0, react.forwardRef)(function Area({ children, className, style, ...props }, forwardedRef) {
|
|
2644
1937
|
const { areaRef } = useXYPadContext();
|
|
2645
1938
|
const composedRef = require_compose_refs.useComposedRefs(forwardedRef, areaRef);
|
|
2646
1939
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2647
1940
|
ref: composedRef,
|
|
2648
1941
|
className,
|
|
2649
1942
|
style: {
|
|
2650
|
-
"--color": color,
|
|
2651
|
-
"--width": cssLength(width),
|
|
2652
|
-
"--height": cssLength(height),
|
|
2653
1943
|
position: "relative",
|
|
2654
1944
|
...style
|
|
2655
1945
|
},
|
|
@@ -2734,7 +2024,7 @@ const WHEEL_OPTIONS = { requireFocus: true };
|
|
|
2734
2024
|
* Simple XYPad
|
|
2735
2025
|
*/
|
|
2736
2026
|
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, externalStyles: _externalStyles, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
2027
|
+
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, externalStyles: _externalStyles, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
2738
2028
|
const rootRef = (0, react.useRef)(null);
|
|
2739
2029
|
const areaRef = (0, react.useRef)(null);
|
|
2740
2030
|
const thumbRef = (0, react.useRef)(null);
|
|
@@ -2748,10 +2038,11 @@ const XYPad = {
|
|
|
2748
2038
|
const step = (0, react.useMemo)(() => (0, _tremolo_ui_dom.toXY)(_step), [_step]);
|
|
2749
2039
|
const scale = (0, react.useMemo)(() => (0, _tremolo_ui_dom.toXY)(_scale), [_scale]);
|
|
2750
2040
|
const reverse = (0, react.useMemo)(() => (0, _tremolo_ui_dom.toXY)(_reverse), [_reverse]);
|
|
2751
|
-
const percent = (0, react.useMemo)(() => {
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2041
|
+
const percent = (0, react.useMemo)(() => [0, 1].map((i) => (0, _tremolo_ui_dom.valuePercent)(value[i], {
|
|
2042
|
+
min: min[i],
|
|
2043
|
+
max: max[i],
|
|
2044
|
+
scale: scale[i]
|
|
2045
|
+
}, reverse[i])), [
|
|
2755
2046
|
value,
|
|
2756
2047
|
min,
|
|
2757
2048
|
max,
|
|
@@ -2793,20 +2084,12 @@ const XYPad = {
|
|
|
2793
2084
|
withAxis
|
|
2794
2085
|
]);
|
|
2795
2086
|
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;
|
|
2087
|
+
const move = (0, _tremolo_ui_dom.arrowKeyMove)(event.key);
|
|
2088
|
+
if (!move) return;
|
|
2804
2089
|
event.preventDefault();
|
|
2805
2090
|
if (!onChange || inactive || !keyboard) return;
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
if (reverse[i]) direction *= -1;
|
|
2809
|
-
onChange(nudge(i, direction, keyboard, event));
|
|
2091
|
+
const { axis: i, direction } = move;
|
|
2092
|
+
onChange(nudge(i, reverse[i] ? -direction : direction, keyboard, event));
|
|
2810
2093
|
}, [
|
|
2811
2094
|
onChange,
|
|
2812
2095
|
inactive,
|
|
@@ -2837,15 +2120,11 @@ const XYPad = {
|
|
|
2837
2120
|
});
|
|
2838
2121
|
const wheelRefCallback = useWheel((event) => {
|
|
2839
2122
|
if (!onChange || inactive || !wheel) return;
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
if (delta === 0) return;
|
|
2843
|
-
const i = horizontal || event.shiftKey ? 0 : 1;
|
|
2123
|
+
const move = (0, _tremolo_ui_dom.wheelMove)(event);
|
|
2124
|
+
if (!move) return;
|
|
2844
2125
|
event.preventDefault();
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
if (reverse[i]) direction *= -1;
|
|
2848
|
-
onChange(nudge(i, direction, wheel, event));
|
|
2126
|
+
const { axis: i, direction } = move;
|
|
2127
|
+
onChange(nudge(i, reverse[i] ? -direction : direction, wheel, event));
|
|
2849
2128
|
}, WHEEL_OPTIONS);
|
|
2850
2129
|
const rootRefCallback = require_compose_refs.useComposedRefs(rootRef, dragRefCallback, wheelRefCallback);
|
|
2851
2130
|
const context = (0, react.useMemo)(() => ({
|
|
@@ -2930,6 +2209,82 @@ function useAnimationFrame(callback = () => {}, deps = []) {
|
|
|
2930
2209
|
}, [runCallback, ...deps]);
|
|
2931
2210
|
}
|
|
2932
2211
|
//#endregion
|
|
2212
|
+
//#region src/hooks/useDrag.ts
|
|
2213
|
+
/**
|
|
2214
|
+
* Track a pointer drag on an element.
|
|
2215
|
+
*
|
|
2216
|
+
* @returns a ref callback to attach to the element being dragged
|
|
2217
|
+
*/
|
|
2218
|
+
function useDrag({ threshold = 1, cursor, pointerLock, onDrag, onDragStart, onDragEnd }) {
|
|
2219
|
+
const dragHandler = useCallbackRef(onDrag);
|
|
2220
|
+
const dragStartHandler = useCallbackRef(onDragStart);
|
|
2221
|
+
const dragEndHandler = useCallbackRef(onDragEnd);
|
|
2222
|
+
const [node, setNode] = (0, react.useState)(null);
|
|
2223
|
+
(0, react.useEffect)(() => {
|
|
2224
|
+
if (!node) return;
|
|
2225
|
+
const instance = (0, _tremolo_ui_dom.createDrag)(node, {
|
|
2226
|
+
threshold,
|
|
2227
|
+
cursor,
|
|
2228
|
+
pointerLock,
|
|
2229
|
+
onDragStart: (state) => dragStartHandler(state),
|
|
2230
|
+
onDrag: (state) => dragHandler(state.x, state.y, state.deltaX, state.deltaY, state),
|
|
2231
|
+
onDragEnd: (state) => dragEndHandler(state)
|
|
2232
|
+
});
|
|
2233
|
+
return () => instance.destroy();
|
|
2234
|
+
}, [
|
|
2235
|
+
node,
|
|
2236
|
+
threshold,
|
|
2237
|
+
cursor,
|
|
2238
|
+
pointerLock,
|
|
2239
|
+
dragHandler,
|
|
2240
|
+
dragStartHandler,
|
|
2241
|
+
dragEndHandler
|
|
2242
|
+
]);
|
|
2243
|
+
return setNode;
|
|
2244
|
+
}
|
|
2245
|
+
//#endregion
|
|
2246
|
+
//#region src/hooks/useEventListener.ts
|
|
2247
|
+
function useEventListener(target, event, handler, options) {
|
|
2248
|
+
const listener = useCallbackRef(handler);
|
|
2249
|
+
const cleanupRef = (0, react.useRef)(() => {});
|
|
2250
|
+
(0, react.useEffect)(() => {
|
|
2251
|
+
const node = typeof target === "function" ? target() : target ?? document;
|
|
2252
|
+
if (!node) return;
|
|
2253
|
+
node.addEventListener(event, listener, options);
|
|
2254
|
+
const cleanup = () => {
|
|
2255
|
+
node.removeEventListener(event, listener, options);
|
|
2256
|
+
};
|
|
2257
|
+
cleanupRef.current = cleanup;
|
|
2258
|
+
return () => {
|
|
2259
|
+
cleanup();
|
|
2260
|
+
if (cleanupRef.current === cleanup) cleanupRef.current = () => {};
|
|
2261
|
+
};
|
|
2262
|
+
}, [
|
|
2263
|
+
event,
|
|
2264
|
+
target,
|
|
2265
|
+
options,
|
|
2266
|
+
listener
|
|
2267
|
+
]);
|
|
2268
|
+
return (0, react.useCallback)(() => cleanupRef.current(), []);
|
|
2269
|
+
}
|
|
2270
|
+
//#endregion
|
|
2271
|
+
//#region src/hooks/useInterval.ts
|
|
2272
|
+
function useInterval(callback, delay) {
|
|
2273
|
+
const savedCallback = (0, react.useRef)(callback);
|
|
2274
|
+
(0, react.useEffect)(() => {
|
|
2275
|
+
savedCallback.current = callback;
|
|
2276
|
+
}, [callback]);
|
|
2277
|
+
(0, react.useEffect)(() => {
|
|
2278
|
+
if (delay === null) return;
|
|
2279
|
+
const id = setInterval(() => {
|
|
2280
|
+
savedCallback.current();
|
|
2281
|
+
}, delay);
|
|
2282
|
+
return () => {
|
|
2283
|
+
clearInterval(id);
|
|
2284
|
+
};
|
|
2285
|
+
}, [delay]);
|
|
2286
|
+
}
|
|
2287
|
+
//#endregion
|
|
2933
2288
|
//#region src/hooks/useMIDIAccess.ts
|
|
2934
2289
|
const INITIAL_STATE = {
|
|
2935
2290
|
midiAccess: null,
|
|
@@ -3067,38 +2422,14 @@ function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
|
3067
2422
|
}
|
|
3068
2423
|
//#endregion
|
|
3069
2424
|
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
2425
|
exports.DropZone = DropZone;
|
|
3074
2426
|
exports.FileInput = FileInput;
|
|
3075
2427
|
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
2428
|
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
2429
|
exports.Piano = Piano;
|
|
3091
2430
|
exports.PointsEditor = PointsEditor;
|
|
3092
|
-
exports.SHORTCUTS = SHORTCUTS;
|
|
3093
2431
|
exports.Slider = Slider;
|
|
3094
|
-
Object.defineProperty(exports, "UNAVAILABLE", {
|
|
3095
|
-
enumerable: true,
|
|
3096
|
-
get: function() {
|
|
3097
|
-
return _tremolo_ui_dom.UNAVAILABLE;
|
|
3098
|
-
}
|
|
3099
|
-
});
|
|
3100
2432
|
exports.XYPad = XYPad;
|
|
3101
|
-
exports.clampPoint = clampPoint;
|
|
3102
2433
|
exports.useAnimationFrame = useAnimationFrame;
|
|
3103
2434
|
exports.useDrag = useDrag;
|
|
3104
2435
|
exports.useDragValue = useDragValue;
|