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