@tremolo-ui/vue 0.8.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.
Files changed (47) hide show
  1. package/dist/index.cjs +865 -611
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +954 -189
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +954 -189
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +339 -128
  8. package/dist/index.js.map +1 -1
  9. package/package.json +7 -7
  10. package/src/components/AnimationCanvas/AnimationCanvas.ts +27 -30
  11. package/src/components/AnimationCanvas/index.ts +1 -5
  12. package/src/components/DropZone/DropZone.ts +19 -9
  13. package/src/components/DropZone/index.ts +1 -1
  14. package/src/components/FileInput/FileInput.ts +28 -14
  15. package/src/components/FileInput/index.ts +1 -1
  16. package/src/components/Knob/Knob.ts +107 -57
  17. package/src/components/Knob/KnobThumb.ts +32 -22
  18. package/src/components/Knob/index.ts +2 -2
  19. package/src/components/NumberInput/NumberInput.ts +136 -53
  20. package/src/components/NumberInput/NumberInputField.ts +5 -2
  21. package/src/components/NumberInput/NumberInputStepper.ts +25 -4
  22. package/src/components/NumberInput/context.ts +12 -0
  23. package/src/components/NumberInput/index.ts +1 -1
  24. package/src/components/NumberInput/stepper-button.ts +8 -1
  25. package/src/components/Piano/Piano.ts +51 -43
  26. package/src/components/Piano/index.ts +6 -1
  27. package/src/components/PointsEditor/PointsEditor.ts +43 -35
  28. package/src/components/PointsEditor/PointsEditorContainer.ts +2 -6
  29. package/src/components/PointsEditor/PointsEditorPoint.ts +82 -42
  30. package/src/components/PointsEditor/context.ts +1 -1
  31. package/src/components/PointsEditor/index.ts +5 -2
  32. package/src/components/Slider/Slider.ts +92 -49
  33. package/src/components/Slider/SliderMarks.ts +28 -14
  34. package/src/components/Slider/SliderMarksOption.ts +54 -33
  35. package/src/components/Slider/SliderThumb.ts +26 -8
  36. package/src/components/Slider/SliderTrack.ts +31 -16
  37. package/src/components/Slider/context.ts +1 -1
  38. package/src/components/Slider/index.ts +8 -5
  39. package/src/components/XYPad/XYPad.ts +94 -56
  40. package/src/components/XYPad/XYPadThumb.ts +21 -14
  41. package/src/components/XYPad/index.ts +2 -2
  42. package/src/components/_util/change-gesture.ts +44 -0
  43. package/src/components/_util/useCheckSteps.ts +1 -1
  44. package/src/components/_util/visually-hidden-range-input.ts +1 -1
  45. package/src/composables/useInstance.ts +1 -1
  46. package/src/composables/useWheel.ts +5 -2
  47. package/src/index.ts +21 -4
package/dist/index.cjs CHANGED
@@ -1,45 +1,47 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let vue = require("vue");
3
3
  let _tremolo_ui_dom = require("@tremolo-ui/dom");
4
+ let _tremolo_ui_dom_internal = require("@tremolo-ui/dom/internal");
4
5
  let _tremolo_ui_functions = require("@tremolo-ui/functions");
5
6
  //#region src/components/AnimationCanvas/AnimationCanvas.ts
7
+ const animationCanvasProps = {
8
+ /** Draws a frame. */
9
+ draw: {
10
+ type: Function,
11
+ required: true
12
+ },
13
+ /** Sets the canvas up, once, before the first frame. */
14
+ init: Function,
15
+ /** Draw on every animation frame. @default true */
16
+ animate: {
17
+ type: Boolean,
18
+ default: true
19
+ },
20
+ /** Settings for the 2D context. Read once, when the canvas is created. */
21
+ options: Object,
22
+ /** Keep the drawing while the canvas is resized. @default true */
23
+ reduceFlickering: {
24
+ type: Boolean,
25
+ default: true
26
+ },
27
+ /** Follow the size of the parent element; `width` and `height` are ignored. */
28
+ resizable: Boolean,
29
+ /** Width in CSS pixels, while not `resizable`. @default 100 */
30
+ width: {
31
+ type: Number,
32
+ default: 100
33
+ },
34
+ /** Height in CSS pixels, while not `resizable`. @default 100 */
35
+ height: {
36
+ type: Number,
37
+ default: 100
38
+ }
39
+ };
6
40
  /** A `<canvas>` drawn by `draw` on every animation frame. */
7
41
  const AnimationCanvas = /* @__PURE__ */ (0, vue.defineComponent)({
8
42
  name: "AnimationCanvas",
9
43
  inheritAttrs: false,
10
- props: {
11
- /** Draws a frame. */
12
- draw: {
13
- type: Function,
14
- required: true
15
- },
16
- /** Sets the canvas up before the first frame and after a resize. */
17
- init: Function,
18
- /** Draw on every animation frame. @default true */
19
- animate: {
20
- type: Boolean,
21
- default: true
22
- },
23
- /** Settings for the 2D context. Read once, when the canvas is created. */
24
- options: Object,
25
- /** Keep the drawing while the canvas is resized. @default true */
26
- reduceFlickering: {
27
- type: Boolean,
28
- default: true
29
- },
30
- /** Follow the size of the parent element; `width` and `height` are ignored. */
31
- resizable: Boolean,
32
- /** Width in CSS pixels, while not `resizable`. @default 100 */
33
- width: {
34
- type: Number,
35
- default: 100
36
- },
37
- /** Height in CSS pixels, while not `resizable`. @default 100 */
38
- height: {
39
- type: Number,
40
- default: 100
41
- }
42
- },
44
+ props: animationCanvasProps,
43
45
  setup(props, { attrs }) {
44
46
  const canvas = (0, vue.ref)(null);
45
47
  const instance = (0, vue.shallowRef)(null);
@@ -110,7 +112,7 @@ function useInstance(target, options, create, update) {
110
112
  flush: "post"
111
113
  });
112
114
  (0, vue.watch)(() => ({ ...(0, vue.toValue)(options) }), (next, previous) => {
113
- if (instance) update(instance, (0, _tremolo_ui_dom.replaceOptions)(previous, next));
115
+ if (instance) update(instance, (0, _tremolo_ui_dom_internal.replaceOptions)(previous, next));
114
116
  }, { flush: "post" });
115
117
  return { current: () => instance };
116
118
  }
@@ -138,17 +140,18 @@ function useDropZone(target, options = {}) {
138
140
  }
139
141
  //#endregion
140
142
  //#region src/components/DropZone/DropZone.ts
143
+ const dropZoneProps = {
144
+ /** Which files to take, as the `accept` attribute of a file input. */
145
+ accept: String,
146
+ /** Take every file dropped, rather than the first. */
147
+ multiple: Boolean,
148
+ /** Ignore drops. The zone carries `data-disabled`. */
149
+ disabled: Boolean
150
+ };
141
151
  /** A region that takes dropped files. */
142
152
  const DropZone = /* @__PURE__ */ (0, vue.defineComponent)({
143
153
  name: "DropZone",
144
- props: {
145
- /** Which files to take, as the `accept` attribute of a file input. */
146
- accept: String,
147
- /** Take every file dropped, rather than the first. */
148
- multiple: Boolean,
149
- /** Ignore drops. The zone carries `data-disabled`. */
150
- disabled: Boolean
151
- },
154
+ props: dropZoneProps,
152
155
  emits: {
153
156
  /** The dropped files that satisfy `accept`. */
154
157
  drop: (files, _event) => Array.isArray(files),
@@ -189,6 +192,18 @@ function useFileInputContext() {
189
192
  }
190
193
  //#endregion
191
194
  //#region src/components/FileInput/FileInput.ts
195
+ const fileInputProps = {
196
+ /**
197
+ * Which files to take: extensions, MIME types and type groups. It is
198
+ * checked again when the files arrive, since the picker only takes it as
199
+ * a hint.
200
+ */
201
+ accept: String,
202
+ /** Let several files be picked at once. */
203
+ multiple: Boolean,
204
+ /** Make the input unusable. The parts carry `data-disabled`. */
205
+ disabled: Boolean
206
+ };
192
207
  /**
193
208
  * Picks files. The control is the native file input, out of sight but in the
194
209
  * tab order, with `FileInputTrigger` as its label.
@@ -196,18 +211,7 @@ function useFileInputContext() {
196
211
  const FileInput = /* @__PURE__ */ (0, vue.defineComponent)({
197
212
  name: "FileInput",
198
213
  inheritAttrs: false,
199
- props: {
200
- /**
201
- * Which files to take: extensions, MIME types and type groups. It is
202
- * checked again when the files arrive, since the picker only takes it as
203
- * a hint.
204
- */
205
- accept: String,
206
- /** Let several files be picked at once. */
207
- multiple: Boolean,
208
- /** Make the input unusable. The parts carry `data-disabled`. */
209
- disabled: Boolean
210
- },
214
+ props: fileInputProps,
211
215
  emits: {
212
216
  /** The picked files that satisfy `accept`. */
213
217
  change: (files) => Array.isArray(files),
@@ -224,7 +228,7 @@ const FileInput = /* @__PURE__ */ (0, vue.defineComponent)({
224
228
  });
225
229
  function onChange(event) {
226
230
  const input = event.target;
227
- const { accepted, rejected } = (0, _tremolo_ui_dom.partitionByAccept)(Array.from(input.files ?? []), props.accept);
231
+ const { accepted, rejected } = (0, _tremolo_ui_dom_internal.partitionByAccept)(Array.from(input.files ?? []), props.accept);
228
232
  input.value = "";
229
233
  if (accepted.length > 0) emit("change", accepted);
230
234
  if (rejected.length > 0) emit("reject", rejected);
@@ -240,7 +244,7 @@ const FileInput = /* @__PURE__ */ (0, vue.defineComponent)({
240
244
  accept: props.accept,
241
245
  multiple: props.multiple,
242
246
  disabled: props.disabled,
243
- style: _tremolo_ui_dom.visuallyHiddenStyle,
247
+ style: _tremolo_ui_dom_internal.visuallyHiddenStyle,
244
248
  "aria-label": ariaLabel,
245
249
  "aria-labelledby": ariaLabelledby,
246
250
  "aria-describedby": ariaDescribedby,
@@ -292,7 +296,37 @@ function useDragValue(target, options) {
292
296
  * `@tremolo-ui/dom`.
293
297
  */
294
298
  function useWheel(target, onWheel, options = {}) {
295
- useInstance(target, options, (element, opts) => (0, _tremolo_ui_dom.createWheel)(element, (event) => onWheel(event), opts), (instance, next) => instance.update(next));
299
+ useInstance(target, options, (element, opts) => (0, _tremolo_ui_dom.createWheel)(element, {
300
+ ...opts,
301
+ onWheel: (event) => onWheel(event)
302
+ }), (instance, next) => instance.update(next));
303
+ }
304
+ //#endregion
305
+ //#region src/components/_util/change-gesture.ts
306
+ /**
307
+ * The core's change gesture, reporting the control's value: the start gets
308
+ * the value before the change, the end the last one the control emitted.
309
+ * `modelValue` lags behind until the parent renders, and a drag ends in the
310
+ * same event as its last change, so the component reports each value it
311
+ * emits through `changed`.
312
+ */
313
+ function useChangeGesture(options) {
314
+ let last = options.value();
315
+ const gesture = (0, _tremolo_ui_dom.createChangeGesture)({
316
+ onStart: (source) => {
317
+ last = options.value();
318
+ options.onStart(last, source);
319
+ },
320
+ onEnd: (source) => options.onEnd(last, source)
321
+ });
322
+ (0, vue.watchEffect)(() => gesture.update({ endDelay: options.endDelay() }));
323
+ (0, vue.watch)(options.inactive, (inactive) => {
324
+ if (inactive) gesture.end();
325
+ });
326
+ (0, vue.onScopeDispose)(() => gesture.end());
327
+ return Object.assign(gesture, { changed: (value) => {
328
+ last = value;
329
+ } });
296
330
  }
297
331
  //#endregion
298
332
  //#region src/components/_util/useCheckSteps.ts
@@ -306,85 +340,108 @@ function useCheckSteps(options) {
306
340
  try {
307
341
  if (process.env.NODE_ENV === "production") return;
308
342
  (0, vue.watchEffect)(() => {
309
- for (const warning of (0, _tremolo_ui_dom.checkSteps)(options())) console.warn(warning);
343
+ for (const warning of (0, _tremolo_ui_dom_internal.checkSteps)(options())) console.warn(warning);
310
344
  });
311
345
  } catch {}
312
346
  }
313
347
  //#endregion
314
348
  //#region src/components/Knob/Knob.ts
349
+ const knobProps = {
350
+ /** The current value. Bind it with `v-model`. */
351
+ modelValue: {
352
+ type: Number,
353
+ required: true
354
+ },
355
+ /** The value with the knob turned all the way down. */
356
+ min: {
357
+ type: Number,
358
+ required: true
359
+ },
360
+ /** The value with the knob turned all the way up. */
361
+ max: {
362
+ type: Number,
363
+ required: true
364
+ },
365
+ /** Granularity of the value. @default 1 */
366
+ step: {
367
+ type: Number,
368
+ default: 1
369
+ },
370
+ /** How the value is distributed across the travel. @default linearScale */
371
+ scale: {
372
+ type: Object,
373
+ default: () => _tremolo_ui_functions.linearScale
374
+ },
375
+ /**
376
+ * The value a double click restores. `null` turns the double click off.
377
+ * @default startValue
378
+ */
379
+ resetValue: {
380
+ type: Number,
381
+ default: void 0
382
+ },
383
+ /** Where the active arc starts. @default min */
384
+ startValue: Number,
385
+ /** Width and height of the knob. Sets `--knob-size`. */
386
+ size: [Number, String],
387
+ /** The cursor to show while dragging. @default 'grabbing' */
388
+ dragCursor: {
389
+ type: String,
390
+ default: "grabbing"
391
+ },
392
+ /** How much one notch of the wheel moves the value. `null` turns it off. */
393
+ wheel: {
394
+ type: [Array, Object],
395
+ default: () => _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS
396
+ },
397
+ /** How much one arrow key press moves the value. `null` turns it off. */
398
+ keyboard: {
399
+ type: [Array, Object],
400
+ default: () => _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS
401
+ },
402
+ /** How much a drag moves the value, per modifier key. */
403
+ dragSensitivity: {
404
+ type: [Number, Object],
405
+ default: () => _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY
406
+ },
407
+ /** Hide the cursor while dragging and read the movement directly. */
408
+ pointerLock: Boolean,
409
+ /** Make the knob unchangeable and remove it from the tab order. */
410
+ disabled: Boolean,
411
+ /** Make the knob unchangeable while leaving it focusable. */
412
+ readonly: Boolean,
413
+ /**
414
+ * How long after the last wheel notch or arrow key a change counts as over,
415
+ * in milliseconds, for `change-end`.
416
+ * @default 500
417
+ */
418
+ changeEndDelay: {
419
+ type: Number,
420
+ default: 500
421
+ },
422
+ /** How far the knob turns from `min` to `max`, in degrees. @default 270 */
423
+ angleRange: {
424
+ type: Number,
425
+ default: 270
426
+ }
427
+ };
315
428
  /**
316
429
  * Interactive rotary knob, drawn in SVG by `KnobSVGRoot` and the parts inside
317
430
  * it. Bind the value with `v-model`.
318
431
  */
319
432
  const Knob = /* @__PURE__ */ (0, vue.defineComponent)({
320
433
  name: "Knob",
321
- props: {
322
- /** The current value. Bind it with `v-model`. */
323
- modelValue: {
324
- type: Number,
325
- required: true
326
- },
327
- /** The value with the knob turned all the way down. */
328
- min: {
329
- type: Number,
330
- required: true
331
- },
332
- /** The value with the knob turned all the way up. */
333
- max: {
334
- type: Number,
335
- required: true
336
- },
337
- /** Granularity of the value. @default 1 */
338
- step: {
339
- type: Number,
340
- default: 1
341
- },
342
- /** How the value is distributed across the travel. @default linearScale */
343
- scale: {
344
- type: Object,
345
- default: () => _tremolo_ui_functions.linearScale
346
- },
347
- /** The value a double click restores. @default min */
348
- defaultValue: Number,
349
- /** Where the active arc starts. @default min */
350
- startValue: Number,
351
- /** Width and height of the knob. Sets `--knob-size`. */
352
- size: [Number, String],
353
- /** The cursor to show while dragging. @default { cursor: 'grabbing' } */
354
- externalStyles: Object,
355
- /** How much one notch of the wheel moves the value. `null` turns it off. */
356
- wheel: {
357
- type: [Array, Object],
358
- default: () => _tremolo_ui_dom.DEFAULT_WHEEL_OPTIONS
359
- },
360
- /** How much one arrow key press moves the value. `null` turns it off. */
361
- keyboard: {
362
- type: [Array, Object],
363
- default: () => _tremolo_ui_dom.DEFAULT_KEYBOARD_OPTIONS
364
- },
365
- /** How much a drag moves the value, per modifier key. */
366
- dragSensitivity: {
367
- type: [Number, Object],
368
- default: () => _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY
369
- },
370
- /** Hide the cursor while dragging and read the movement directly. */
371
- pointerLock: Boolean,
372
- /** Restore `defaultValue` on a double click. @default true */
373
- enableDoubleClickDefault: {
374
- type: Boolean,
375
- default: true
376
- },
377
- /** Make the knob unchangeable and remove it from the tab order. */
378
- disabled: Boolean,
379
- /** Make the knob unchangeable while leaving it focusable. */
380
- readonly: Boolean,
381
- /** How far the knob turns from `min` to `max`, in degrees. @default 270 */
382
- angleRange: {
383
- type: Number,
384
- default: 270
385
- }
434
+ props: knobProps,
435
+ emits: {
436
+ "update:modelValue": (value) => typeof value === "number",
437
+ /**
438
+ * A change of the value started, with the value before it and what it is
439
+ * made with.
440
+ */
441
+ changeStart: (value, _source) => typeof value === "number",
442
+ /** The change ended, with the value it ended on. */
443
+ changeEnd: (value, _source) => typeof value === "number"
386
444
  },
387
- emits: { "update:modelValue": (value) => typeof value === "number" },
388
445
  setup(props, { slots, emit, expose }) {
389
446
  const root = (0, vue.ref)(null);
390
447
  const dragging = (0, vue.ref)(false);
@@ -435,7 +492,17 @@ const Knob = /* @__PURE__ */ (0, vue.defineComponent)({
435
492
  return angles.value;
436
493
  }
437
494
  });
438
- const change = (next) => emit("update:modelValue", next);
495
+ const gesture = useChangeGesture({
496
+ value: () => props.modelValue,
497
+ inactive: () => inactive.value,
498
+ endDelay: () => props.changeEndDelay,
499
+ onStart: (value, source) => emit("changeStart", value, source),
500
+ onEnd: (value, source) => emit("changeEnd", value, source)
501
+ });
502
+ const change = (next) => {
503
+ gesture.changed(next);
504
+ emit("update:modelValue", next);
505
+ };
439
506
  useDragValue(root, () => ({
440
507
  axis: [range.value, {
441
508
  ...range.value,
@@ -443,11 +510,11 @@ const Knob = /* @__PURE__ */ (0, vue.defineComponent)({
443
510
  }],
444
511
  mapping: (0, _tremolo_ui_dom.relativeMapping)({
445
512
  pixelRange: 100,
446
- sensitivity: (state) => (0, _tremolo_ui_dom.selectModifier)(props.dragSensitivity, state.event).value
513
+ sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(props.dragSensitivity, state.event).value
447
514
  }),
448
515
  getValue: () => [props.modelValue, props.modelValue],
449
516
  threshold: 1,
450
- cursor: inactive.value ? void 0 : props.externalStyles?.cursor ?? "grabbing",
517
+ cursor: inactive.value ? void 0 : props.dragCursor,
451
518
  pointerLock: inactive.value ? false : props.pointerLock,
452
519
  shouldStart: () => !inactive.value,
453
520
  onChange: (v) => {
@@ -455,17 +522,20 @@ const Knob = /* @__PURE__ */ (0, vue.defineComponent)({
455
522
  },
456
523
  onDragStart: () => {
457
524
  dragging.value = true;
525
+ if (!inactive.value) gesture.hold("pointer");
458
526
  },
459
527
  onDragEnd: () => {
460
528
  dragging.value = false;
529
+ gesture.end();
461
530
  }
462
531
  }));
463
532
  useWheel(root, (event) => {
464
533
  if (!props.wheel || inactive.value) return;
465
- const direction = (0, _tremolo_ui_dom.wheelDirection)(event);
534
+ const direction = (0, _tremolo_ui_dom_internal.wheelDirection)(event);
466
535
  if (direction === null) return;
467
536
  event.preventDefault();
468
- change((0, _tremolo_ui_dom.applyDelta)(props.modelValue, direction, props.wheel, range.value, event));
537
+ gesture.pulse("wheel");
538
+ change((0, _tremolo_ui_dom_internal.applyDelta)(props.modelValue, direction, props.wheel, range.value, event));
469
539
  }, { requireFocus: true });
470
540
  expose({
471
541
  /** Move the focus to the knob. */
@@ -487,16 +557,17 @@ const Knob = /* @__PURE__ */ (0, vue.defineComponent)({
487
557
  "data-disabled": props.disabled ? "" : void 0,
488
558
  "data-readonly": props.readonly ? "" : void 0,
489
559
  "data-dragging": dragging.value ? "" : void 0,
490
- style: { "--knob-size": (0, _tremolo_ui_dom.cssLength)(props.size) },
560
+ style: { "--knob-size": (0, _tremolo_ui_dom_internal.cssLength)(props.size) },
491
561
  onKeydown: (event) => {
492
562
  if (!props.keyboard || inactive.value) return;
493
- const direction = (0, _tremolo_ui_dom.arrowKeyDirection)(event.key);
563
+ const direction = (0, _tremolo_ui_dom_internal.arrowKeyDirection)(event.key);
494
564
  if (direction === null) return;
495
565
  event.preventDefault();
496
- change((0, _tremolo_ui_dom.applyDelta)(props.modelValue, direction, props.keyboard, range.value, event));
566
+ gesture.pulse("keyboard");
567
+ change((0, _tremolo_ui_dom_internal.applyDelta)(props.modelValue, direction, props.keyboard, range.value, event));
497
568
  },
498
569
  onDblclick: () => {
499
- if (!inactive.value && props.enableDoubleClickDefault) change(props.defaultValue ?? props.min);
570
+ if (!inactive.value && props.resetValue !== null) gesture.instant("doubleClick", () => change(props.resetValue ?? props.startValue ?? props.min));
500
571
  }
501
572
  }, slots.default?.());
502
573
  }
@@ -602,54 +673,55 @@ const KnobSVGRoot = /* @__PURE__ */ (0, vue.defineComponent)({
602
673
  });
603
674
  //#endregion
604
675
  //#region src/components/Knob/KnobThumb.ts
676
+ const knobThumbProps = {
677
+ /** Fill colour of the circle. @default 'currentColor' */
678
+ color: {
679
+ type: String,
680
+ default: "currentColor"
681
+ },
682
+ /** Colour of the line that points at the value. @default 'currentColor' */
683
+ lineColor: {
684
+ type: String,
685
+ default: "currentColor"
686
+ },
687
+ /** Diameter of the circle, as a percentage of the knob. @default 84 */
688
+ size: {
689
+ type: Number,
690
+ default: 84
691
+ },
692
+ /** Thickness of the line, as a percentage of the knob. @default 6 */
693
+ lineWeight: {
694
+ type: Number,
695
+ default: 6
696
+ },
697
+ /** How far down the line reaches, as a percentage. @default 35 */
698
+ lineLength: {
699
+ type: Number,
700
+ default: 35
701
+ },
702
+ /** Classes for the line that points at the value. */
703
+ classes: Object
704
+ };
605
705
  /** The circle that turns with the value, and the line on it that points at it. */
606
706
  const KnobThumb = /* @__PURE__ */ (0, vue.defineComponent)({
607
707
  name: "KnobThumb",
608
- props: {
609
- /** Fill colour of the circle. @default 'currentColor' */
610
- thumb: {
611
- type: String,
612
- default: "currentColor"
613
- },
614
- /** Colour of the line that points at the value. @default 'currentColor' */
615
- thumbLine: {
616
- type: String,
617
- default: "currentColor"
618
- },
619
- /** Diameter of the circle, as a percentage of the knob. @default 84 */
620
- thumbSize: {
621
- type: Number,
622
- default: 84
623
- },
624
- /** Thickness of the line, as a percentage of the knob. @default 6 */
625
- thumbLineWeight: {
626
- type: Number,
627
- default: 6
628
- },
629
- /** How far down the line reaches, as a percentage. @default 35 */
630
- thumbLineLength: {
631
- type: Number,
632
- default: 35
633
- },
634
- /** Classes for the line that points at the value. */
635
- classes: Object
636
- },
708
+ props: knobThumbProps,
637
709
  setup(props) {
638
710
  checkPlacement("KnobThumb", "KnobSVGRoot");
639
711
  const knob = useKnobContext();
640
712
  return () => (0, vue.h)("svg", null, [(0, vue.h)("circle", {
641
713
  cx: "50%",
642
714
  cy: "50%",
643
- r: `${props.thumbSize / 2}%`,
644
- fill: props.thumb
715
+ r: `${props.size / 2}%`,
716
+ fill: props.color
645
717
  }), (0, vue.h)("line", {
646
- class: props.classes?.thumbLine,
718
+ class: props.classes?.line,
647
719
  x1: "50%",
648
- y1: `${(_tremolo_ui_dom.KNOB_VIEWBOX_SIZE - (0, _tremolo_ui_functions.clamp)(props.thumbSize, 0, 100)) / 2}%`,
720
+ y1: `${(_tremolo_ui_dom.KNOB_VIEWBOX_SIZE - (0, _tremolo_ui_functions.clamp)(props.size, 0, 100)) / 2}%`,
649
721
  x2: "50%",
650
- y2: `${props.thumbLineLength}%`,
651
- stroke: props.thumbLine,
652
- "stroke-width": `${props.thumbLineWeight}%`,
722
+ y2: `${props.lineLength}%`,
723
+ stroke: props.lineColor,
724
+ "stroke-width": `${props.lineWeight}%`,
653
725
  style: {
654
726
  transform: `rotate(${knob.angles.r1 + knob.angles.p * knob.angleRange}deg)`,
655
727
  transformOrigin: "50% 50%"
@@ -664,6 +736,7 @@ const NumberInputKey = Symbol("NumberInput");
664
736
  function useNumberInputContext() {
665
737
  return injectContext(NumberInputKey, "NumberInput");
666
738
  }
739
+ const NumberInputGestureKey = Symbol("NumberInputGesture");
667
740
  /** @internal Whether the drag on `NumberInputStepper` has moved the value. */
668
741
  const StepperKey = Symbol("NumberInputStepper");
669
742
  //#endregion
@@ -685,74 +758,99 @@ const dragSensitivityProp = {
685
758
  };
686
759
  //#endregion
687
760
  //#region src/components/NumberInput/NumberInput.ts
761
+ const numberInputProps = {
762
+ /** The current value. Bind it with `v-model`. */
763
+ modelValue: {
764
+ type: Number,
765
+ required: true
766
+ },
767
+ /** The lowest value. Leave it out for no lower end. */
768
+ min: Number,
769
+ /** The highest value. Leave it out for no upper end. */
770
+ max: Number,
771
+ /** Granularity of the value. @default 1 */
772
+ step: {
773
+ type: Number,
774
+ default: 1
775
+ },
776
+ /** How the value is distributed, for a `normalized` amount. */
777
+ scale: {
778
+ type: Object,
779
+ default: () => _tremolo_ui_functions.linearScale
780
+ },
781
+ /** The text shown for a value. @default String */
782
+ format: {
783
+ type: Function,
784
+ default: String
785
+ },
786
+ /** Read a value back out of the text. Has to undo `format`. */
787
+ parse: {
788
+ type: Function,
789
+ default: _tremolo_ui_dom_internal.parseNumberText
790
+ },
791
+ /** Keep the value within `min` and `max` when committed or stepped. */
792
+ clampValue: {
793
+ type: Boolean,
794
+ default: true
795
+ },
796
+ wheel: wheelProp,
797
+ keyboard: keyboardProp,
798
+ dragSensitivity: dragSensitivityProp,
799
+ /** Pixels of vertical drag on the stepper per `step`. `null` turns it off. */
800
+ drag: {
801
+ type: Number,
802
+ default: 1
803
+ },
804
+ /** Hide the cursor while dragging the stepper. */
805
+ pointerLock: Boolean,
806
+ /** The cursor to show while dragging the stepper. @default 'ns-resize' */
807
+ dragCursor: {
808
+ type: String,
809
+ default: "ns-resize"
810
+ },
811
+ /** Select the text when the field takes focus. @default 'none' */
812
+ selectOnFocus: {
813
+ type: String,
814
+ default: "none"
815
+ },
816
+ /** Show the plain value while the field has focus. */
817
+ unformatOnFocus: Boolean,
818
+ /** Put the caret back at the same digit after an arrow key step. */
819
+ keepCaretOnStep: Boolean,
820
+ /** Commit and leave the field on Enter. @default true */
821
+ blurOnEnter: {
822
+ type: Boolean,
823
+ default: true
824
+ },
825
+ /** Make the input unchangeable and remove it from the tab order. */
826
+ disabled: Boolean,
827
+ /** Make the value unchangeable while leaving the field focusable. */
828
+ readonly: Boolean,
829
+ /**
830
+ * How long after the last wheel notch, arrow key or typed character a change
831
+ * counts as over, in milliseconds, for `change-end`.
832
+ * @default 500
833
+ */
834
+ changeEndDelay: {
835
+ type: Number,
836
+ default: 500
837
+ }
838
+ };
688
839
  /** Input with some useful functions for entering numbers. Bind it with `v-model`. */
689
840
  const NumberInput = /* @__PURE__ */ (0, vue.defineComponent)({
690
841
  name: "NumberInput",
691
- props: {
692
- /** The current value. Bind it with `v-model`. */
693
- modelValue: {
694
- type: Number,
695
- required: true
696
- },
697
- /** The lowest value. Leave it out for no lower end. */
698
- min: Number,
699
- /** The highest value. Leave it out for no upper end. */
700
- max: Number,
701
- /** Granularity of the value. @default 1 */
702
- step: {
703
- type: Number,
704
- default: 1
705
- },
706
- /** How the value is distributed, for a `normalized` amount. */
707
- scale: {
708
- type: Object,
709
- default: () => _tremolo_ui_functions.linearScale
710
- },
711
- /** The text shown for a value. @default String */
712
- format: {
713
- type: Function,
714
- default: String
715
- },
716
- /** Read a value back out of the text. Has to undo `format`. */
717
- parse: {
718
- type: Function,
719
- default: _tremolo_ui_dom.parseNumberText
720
- },
721
- /** Keep the value within `min` and `max` when committed or stepped. */
722
- clampValue: {
723
- type: Boolean,
724
- default: true
725
- },
726
- wheel: wheelProp,
727
- keyboard: keyboardProp,
728
- dragSensitivity: dragSensitivityProp,
729
- /** Pixels of vertical drag on the stepper per `step`. `null` turns it off. */
730
- drag: {
731
- type: Number,
732
- default: 1
733
- },
734
- /** Hide the cursor while dragging the stepper. */
735
- pointerLock: Boolean,
736
- /** Select the text when the field takes focus. @default 'none' */
737
- selectOnFocus: {
738
- type: String,
739
- default: "none"
740
- },
741
- /** Show the plain value while the field has focus. */
742
- unformatOnFocus: Boolean,
743
- /** Put the caret back at the same digit after an arrow key step. */
744
- keepCaretOnStep: Boolean,
745
- /** Commit and leave the field on Enter. @default true */
746
- blurOnEnter: {
747
- type: Boolean,
748
- default: true
749
- },
750
- /** Make the input unchangeable and remove it from the tab order. */
751
- disabled: Boolean,
752
- /** Make the value unchangeable while leaving the field focusable. */
753
- readonly: Boolean
842
+ props: numberInputProps,
843
+ emits: {
844
+ "update:modelValue": (value) => typeof value === "number",
845
+ /**
846
+ * A change of the value started — a press on a stepper, the first wheel
847
+ * notch, arrow key or typed character — with the value before it and what
848
+ * it is made with.
849
+ */
850
+ changeStart: (value, _source) => typeof value === "number",
851
+ /** The change ended, with the value it ended on. */
852
+ changeEnd: (value, _source) => typeof value === "number"
754
853
  },
755
- emits: { "update:modelValue": (value) => typeof value === "number" },
756
854
  setup(props, { slots, emit, expose }) {
757
855
  const root = (0, vue.ref)(null);
758
856
  /**
@@ -762,14 +860,14 @@ const NumberInput = /* @__PURE__ */ (0, vue.defineComponent)({
762
860
  const draft = (0, vue.ref)(null);
763
861
  let input = null;
764
862
  const inactive = (0, vue.computed)(() => props.disabled || props.readonly);
765
- const ranges = (0, vue.computed)(() => (0, _tremolo_ui_dom.numberInputRanges)({
863
+ const ranges = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.numberInputRanges)({
766
864
  min: props.min,
767
865
  max: props.max,
768
866
  step: props.step,
769
867
  scale: props.scale,
770
868
  clampValue: props.clampValue
771
869
  }));
772
- const bounds = (0, vue.computed)(() => (0, _tremolo_ui_dom.numberInputBounds)(props.modelValue, {
870
+ const bounds = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.numberInputBounds)(props.modelValue, {
773
871
  min: props.min,
774
872
  max: props.max,
775
873
  clampValue: props.clampValue
@@ -787,11 +885,40 @@ const NumberInput = /* @__PURE__ */ (0, vue.defineComponent)({
787
885
  wheel: props.wheel,
788
886
  format: props.format
789
887
  }));
888
+ const gesture = useChangeGesture({
889
+ value: () => props.modelValue,
890
+ inactive: () => inactive.value,
891
+ endDelay: () => props.changeEndDelay,
892
+ onStart: (value, source) => emit("changeStart", value, source),
893
+ onEnd: (value, source) => emit("changeEnd", value, source)
894
+ });
790
895
  function changeValue(next) {
791
896
  if (inactive.value) return;
792
897
  draft.value = null;
793
- if (next !== props.modelValue) emit("update:modelValue", next);
898
+ if (next === props.modelValue) return;
899
+ gesture.changed(next);
900
+ emit("update:modelValue", next);
794
901
  }
902
+ let pressed = false;
903
+ (0, vue.provide)(NumberInputGestureKey, {
904
+ press: () => {
905
+ if (inactive.value) return;
906
+ gesture.hold("pointer");
907
+ if (pressed) return;
908
+ pressed = true;
909
+ const release = () => {
910
+ pressed = false;
911
+ gesture.end();
912
+ window.removeEventListener("pointerup", release);
913
+ window.removeEventListener("pointercancel", release);
914
+ };
915
+ window.addEventListener("pointerup", release);
916
+ window.addEventListener("pointercancel", release);
917
+ },
918
+ key: () => {
919
+ if (!inactive.value) gesture.pulse("keyboard");
920
+ }
921
+ });
795
922
  (0, vue.provide)(NumberInputKey, {
796
923
  get value() {
797
924
  return props.modelValue;
@@ -823,6 +950,9 @@ const NumberInput = /* @__PURE__ */ (0, vue.defineComponent)({
823
950
  get pointerLock() {
824
951
  return props.pointerLock;
825
952
  },
953
+ get dragCursor() {
954
+ return props.dragCursor;
955
+ },
826
956
  get selectOnFocus() {
827
957
  return props.selectOnFocus;
828
958
  },
@@ -857,29 +987,40 @@ const NumberInput = /* @__PURE__ */ (0, vue.defineComponent)({
857
987
  if (inactive.value) return;
858
988
  draft.value = next;
859
989
  const parsed = props.parse(next);
860
- if (Number.isFinite(parsed)) emit("update:modelValue", parsed);
990
+ if (!Number.isFinite(parsed)) return;
991
+ if (parsed !== props.modelValue) {
992
+ gesture.pulse("keyboard");
993
+ gesture.changed(parsed);
994
+ }
995
+ emit("update:modelValue", parsed);
861
996
  },
862
997
  commitDraft: () => {
863
998
  if (draft.value === null || inactive.value) return;
864
- const committed = (0, _tremolo_ui_dom.commitNumberInputText)(draft.value, props.parse, {
999
+ const committed = (0, _tremolo_ui_dom_internal.commitNumberInputText)(draft.value, props.parse, {
865
1000
  min: props.min,
866
1001
  max: props.max,
867
1002
  clampValue: props.clampValue
868
1003
  });
869
- if (committed === null) draft.value = null;
870
- else changeValue(committed);
1004
+ if (committed === null) {
1005
+ draft.value = null;
1006
+ return;
1007
+ }
1008
+ if (committed !== props.modelValue) gesture.pulse("keyboard");
1009
+ changeValue(committed);
1010
+ gesture.end();
871
1011
  },
872
1012
  changeValue,
873
- nudge: (direction, option, modifiers) => changeValue((0, _tremolo_ui_dom.nudgeNumberInput)(props.modelValue, direction, option, ranges.value, modifiers)),
1013
+ nudge: (direction, option, modifiers) => changeValue((0, _tremolo_ui_dom_internal.nudgeNumberInput)(props.modelValue, direction, option, ranges.value, modifiers)),
874
1014
  setInput: (element) => {
875
1015
  input = element;
876
1016
  }
877
1017
  });
878
1018
  useWheel(root, (event) => {
879
- const direction = (0, _tremolo_ui_dom.wheelDirection)(event);
1019
+ const direction = (0, _tremolo_ui_dom_internal.wheelDirection)(event);
880
1020
  if (!props.wheel || inactive.value || direction === null) return;
881
1021
  event.preventDefault();
882
- changeValue((0, _tremolo_ui_dom.nudgeNumberInput)(props.modelValue, direction, props.wheel, ranges.value, event));
1022
+ gesture.pulse("wheel");
1023
+ changeValue((0, _tremolo_ui_dom_internal.nudgeNumberInput)(props.modelValue, direction, props.wheel, ranges.value, event));
883
1024
  }, { requireFocus: true });
884
1025
  expose({
885
1026
  focus: () => {
@@ -917,9 +1058,11 @@ function stepperButton(name, direction) {
917
1058
  setup(_, { slots }) {
918
1059
  const field = useNumberInputContext();
919
1060
  const stepper = (0, vue.inject)(StepperKey, null);
1061
+ const gesture = (0, vue.inject)(NumberInputGestureKey, null);
920
1062
  const blocked = (0, vue.computed)(() => field.disabled || (direction > 0 ? field.atMax : field.atMin));
921
1063
  const press = useLongPress({ onPress: () => {
922
1064
  if (field.disabled || field.readonly) return;
1065
+ gesture?.press();
923
1066
  if (stepper?.moved()) return;
924
1067
  field.nudge(direction, ["raw", field.step]);
925
1068
  } });
@@ -960,6 +1103,7 @@ const NumberInputField = /* @__PURE__ */ (0, vue.defineComponent)({
960
1103
  name: "NumberInputField",
961
1104
  setup(_, { expose }) {
962
1105
  const field = useNumberInputContext();
1106
+ const gesture = (0, vue.inject)(NumberInputGestureKey, null);
963
1107
  const input = (0, vue.ref)(null);
964
1108
  const focused = (0, vue.ref)(false);
965
1109
  (0, vue.watchEffect)(() => field.setInput(input.value));
@@ -973,18 +1117,19 @@ const NumberInputField = /* @__PURE__ */ (0, vue.defineComponent)({
973
1117
  const span = field.selectOnFocus === "all" ? {
974
1118
  start: 0,
975
1119
  end: element.value.length
976
- } : (0, _tremolo_ui_dom.numberSpan)(element.value);
1120
+ } : (0, _tremolo_ui_dom_internal.numberSpan)(element.value);
977
1121
  if (span) element.setSelectionRange(span.start, span.end);
978
1122
  }
979
1123
  async function step(direction, event) {
980
1124
  const element = event.currentTarget;
981
1125
  const from = element.value;
982
- const offset = field.keepCaretOnStep && element.selectionStart !== null ? (0, _tremolo_ui_dom.caretDecimalOffset)(from, element.selectionStart) : null;
1126
+ const offset = field.keepCaretOnStep && element.selectionStart !== null ? (0, _tremolo_ui_dom_internal.caretDecimalOffset)(from, element.selectionStart) : null;
1127
+ gesture?.key();
983
1128
  field.nudge(direction, field.keyboard, event);
984
1129
  if (offset === null) return;
985
1130
  await (0, vue.nextTick)();
986
1131
  if (element.value === from) return;
987
- const caret = (0, _tremolo_ui_dom.caretAtDecimalOffset)(element.value, offset);
1132
+ const caret = (0, _tremolo_ui_dom_internal.caretAtDecimalOffset)(element.value, offset);
988
1133
  element.setSelectionRange(caret, caret);
989
1134
  }
990
1135
  expose({
@@ -1045,6 +1190,7 @@ const NumberInputStepper = /* @__PURE__ */ (0, vue.defineComponent)({
1045
1190
  name: "NumberInputStepper",
1046
1191
  setup(_, { slots }) {
1047
1192
  const field = useNumberInputContext();
1193
+ const gesture = (0, vue.inject)(NumberInputGestureKey, null);
1048
1194
  const el = (0, vue.ref)(null);
1049
1195
  let instance = null;
1050
1196
  const enabled = (0, vue.computed)(() => field.drag !== null && !field.disabled && !field.readonly);
@@ -1052,11 +1198,12 @@ const NumberInputStepper = /* @__PURE__ */ (0, vue.defineComponent)({
1052
1198
  range: field.rawRange,
1053
1199
  pixels: field.drag ?? 1,
1054
1200
  sensitivity: field.dragSensitivity,
1055
- pointerLock: field.pointerLock
1201
+ pointerLock: field.pointerLock,
1202
+ cursor: field.dragCursor
1056
1203
  });
1057
1204
  (0, vue.watch)([el, enabled], ([element, on], _, onCleanup) => {
1058
1205
  if (!element || !on) return;
1059
- const current = (0, _tremolo_ui_dom.createStepperDrag)(element, {
1206
+ const current = (0, _tremolo_ui_dom_internal.createStepperDrag)(element, {
1060
1207
  ...options(),
1061
1208
  getValue: () => field.value,
1062
1209
  onChange: (next) => field.changeValue(next)
@@ -1072,70 +1219,76 @@ const NumberInputStepper = /* @__PURE__ */ (0, vue.defineComponent)({
1072
1219
  });
1073
1220
  (0, vue.watch)(options, (next) => instance?.update(next));
1074
1221
  (0, vue.provide)(StepperKey, { moved: () => instance?.moved() ?? false });
1075
- return () => (0, vue.h)("div", { ref: el }, slots.default?.());
1222
+ return () => (0, vue.h)("div", {
1223
+ ref: el,
1224
+ onPointerdown: () => {
1225
+ if (enabled.value) gesture?.press();
1226
+ }
1227
+ }, slots.default?.());
1076
1228
  }
1077
1229
  });
1078
1230
  //#endregion
1079
1231
  //#region src/components/Piano/Piano.ts
1232
+ const pianoProps = {
1233
+ /** The notes drawn, from `first` to `last`. */
1234
+ noteRange: {
1235
+ type: Object,
1236
+ required: true
1237
+ },
1238
+ /** Let a pointer slide from one key to the next. @default true */
1239
+ glissando: {
1240
+ type: Boolean,
1241
+ default: true
1242
+ },
1243
+ /** Highest note that can sound. @default 127 */
1244
+ midiMax: {
1245
+ type: Number,
1246
+ default: 127
1247
+ },
1248
+ /** Play notes from the computer keyboard. See `SHORTCUTS` in `@tremolo-ui/dom`. */
1249
+ keyboardShortcuts: Object,
1250
+ /** Where keyboard shortcuts listen. @default 'root' */
1251
+ keyboardShortcutsScope: {
1252
+ type: String,
1253
+ default: "root"
1254
+ },
1255
+ /** Follow the width of the parent element. */
1256
+ resizable: Boolean,
1257
+ /** Width of a white key in pixels. @default 40 */
1258
+ whiteKeyWidth: {
1259
+ type: Number,
1260
+ default: 40
1261
+ },
1262
+ /** Space between two white keys, in pixels. @default 1 */
1263
+ keyGap: {
1264
+ type: Number,
1265
+ default: 1
1266
+ },
1267
+ /** Width of a black key, as a fraction of a white one. @default 0.65 */
1268
+ blackKeyWidthRatio: {
1269
+ type: Number,
1270
+ default: .65
1271
+ },
1272
+ /** Height of a black key, as a fraction of the keyboard. @default 0.6 */
1273
+ blackKeyHeightRatio: {
1274
+ type: Number,
1275
+ default: .6
1276
+ },
1277
+ /** Classes for the label inside each key. */
1278
+ classes: Object,
1279
+ /**
1280
+ * Extra attributes for one key, by note. The geometry of the key is
1281
+ * applied after the returned style and cannot be overridden.
1282
+ */
1283
+ keyProps: Function
1284
+ };
1080
1285
  /**
1081
1286
  * Customizable piano. The keys are drawn by the component; what goes inside
1082
1287
  * one is the `label` slot, which receives the note and its state.
1083
1288
  */
1084
1289
  const Piano = /* @__PURE__ */ (0, vue.defineComponent)({
1085
1290
  name: "Piano",
1086
- props: {
1087
- /** The notes drawn, from `first` to `last`. */
1088
- noteRange: {
1089
- type: Object,
1090
- required: true
1091
- },
1092
- /** Let a pointer slide from one key to the next. @default true */
1093
- glissando: {
1094
- type: Boolean,
1095
- default: true
1096
- },
1097
- /** Highest note that can sound. @default 127 */
1098
- midiMax: {
1099
- type: Number,
1100
- default: 127
1101
- },
1102
- /** Play notes from the computer keyboard. See `SHORTCUTS` in `@tremolo-ui/dom`. */
1103
- keyboardShortcuts: Object,
1104
- /** Where keyboard shortcuts listen. @default 'root' */
1105
- keyboardShortcutsScope: {
1106
- type: String,
1107
- default: "root"
1108
- },
1109
- /** Follow the width of the parent element. */
1110
- resizable: Boolean,
1111
- /** Width of a white key in pixels. @default 40 */
1112
- whiteKeyWidth: {
1113
- type: Number,
1114
- default: 40
1115
- },
1116
- /** Space between two white keys, in pixels. @default 1 */
1117
- keyGap: {
1118
- type: Number,
1119
- default: 1
1120
- },
1121
- /** Width of a black key, as a fraction of a white one. @default 0.65 */
1122
- blackKeyWidthRatio: {
1123
- type: Number,
1124
- default: .65
1125
- },
1126
- /** Height of a black key, as a fraction of the keyboard. @default 0.6 */
1127
- blackKeyHeightRatio: {
1128
- type: Number,
1129
- default: .6
1130
- },
1131
- /** Classes for the label inside each key. */
1132
- classes: Object,
1133
- /**
1134
- * Extra attributes for one key, by note. The geometry of the key is
1135
- * applied after the returned style and cannot be overridden.
1136
- */
1137
- keyProps: Function
1138
- },
1291
+ props: pianoProps,
1139
1292
  emits: {
1140
1293
  /** A note started sounding. */
1141
1294
  playNote: (note, _velocity) => typeof note === "number",
@@ -1148,7 +1301,7 @@ const Piano = /* @__PURE__ */ (0, vue.defineComponent)({
1148
1301
  const activeNotes = (0, vue.ref)([]);
1149
1302
  /** Set while `resizable` is on, measured from the parent. */
1150
1303
  const resizedKeyWidth = (0, vue.ref)(null);
1151
- const notes = (0, vue.computed)(() => (0, _tremolo_ui_dom.getNoteRangeArray)(props.noteRange));
1304
+ const notes = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.getNoteRangeArray)(props.noteRange));
1152
1305
  const layout = (0, vue.computed)(() => ({
1153
1306
  noteRange: props.noteRange,
1154
1307
  whiteKeyWidth: props.resizable ? resizedKeyWidth.value ?? props.whiteKeyWidth : props.whiteKeyWidth,
@@ -1196,7 +1349,7 @@ const Piano = /* @__PURE__ */ (0, vue.defineComponent)({
1196
1349
  const parent = element.parentElement;
1197
1350
  if (!parent) throw new Error("doesn't have a parent element.");
1198
1351
  const observer = new ResizeObserver(() => {
1199
- resizedKeyWidth.value = (0, _tremolo_ui_dom.fitWhiteKeyWidth)(element.clientWidth, {
1352
+ resizedKeyWidth.value = (0, _tremolo_ui_dom_internal.fitWhiteKeyWidth)(element.clientWidth, {
1200
1353
  noteRange: {
1201
1354
  first,
1202
1355
  last
@@ -1227,7 +1380,7 @@ const Piano = /* @__PURE__ */ (0, vue.defineComponent)({
1227
1380
  "data-resizable": props.resizable ? "" : void 0,
1228
1381
  style: {
1229
1382
  position: "relative",
1230
- width: props.resizable ? "100%" : `${(0, _tremolo_ui_dom.pianoWidth)(layout.value)}px`
1383
+ width: props.resizable ? "100%" : `${(0, _tremolo_ui_dom_internal.pianoWidth)(layout.value)}px`
1231
1384
  }
1232
1385
  }, notes.value.map((note, index) => {
1233
1386
  const state = {
@@ -1257,7 +1410,7 @@ const Piano = /* @__PURE__ */ (0, vue.defineComponent)({
1257
1410
  keyStyle,
1258
1411
  {
1259
1412
  left: `${(0, _tremolo_ui_dom.notePosition)(note, layout.value)}px`,
1260
- width: `${white ? layout.value.whiteKeyWidth : (0, _tremolo_ui_dom.blackKeyWidth)(layout.value)}px`,
1413
+ width: `${white ? layout.value.whiteKeyWidth : (0, _tremolo_ui_dom_internal.blackKeyWidth)(layout.value)}px`,
1261
1414
  height: white ? "100%" : `${props.blackKeyHeightRatio * 100}%`
1262
1415
  }
1263
1416
  ]
@@ -1275,42 +1428,46 @@ function usePointsEditorContext() {
1275
1428
  //#endregion
1276
1429
  //#region src/components/PointsEditor/PointsEditor.ts
1277
1430
  const EMPTY = [];
1431
+ const pointsEditorProps = {
1432
+ /** Make the points unchangeable and remove them from the tab order. */
1433
+ disabled: Boolean,
1434
+ /** Make the points unmovable. */
1435
+ readonly: Boolean,
1436
+ /** The cursor to show while dragging a point. @default 'grabbing' */
1437
+ dragCursor: {
1438
+ type: String,
1439
+ default: "grabbing"
1440
+ },
1441
+ /** How much one wheel notch moves the focused point. */
1442
+ wheel: {
1443
+ type: [Array, Object],
1444
+ default: () => _tremolo_ui_dom_internal.POINTS_EDITOR_DEFAULT_WHEEL
1445
+ },
1446
+ /** How much one arrow key press moves a point. */
1447
+ keyboard: {
1448
+ type: [Array, Object],
1449
+ default: () => _tremolo_ui_dom_internal.POINTS_EDITOR_DEFAULT_KEYBOARD
1450
+ },
1451
+ /** How much a drag moves a point, per modifier key. */
1452
+ dragSensitivity: {
1453
+ type: [Number, Object],
1454
+ default: () => _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY
1455
+ },
1456
+ /**
1457
+ * Let points be selected, and a selection be moved as one. A selection
1458
+ * updates several points in the same tick.
1459
+ */
1460
+ selectable: Boolean,
1461
+ /** Ids of the selected points. Bind it with `v-model:selection`. */
1462
+ selection: Array
1463
+ };
1278
1464
  /**
1279
1465
  * Multiple point controller. Bind the selection with `v-model:selection` to
1280
1466
  * hold it yourself; the editor keeps its own otherwise.
1281
1467
  */
1282
1468
  const PointsEditor = /* @__PURE__ */ (0, vue.defineComponent)({
1283
1469
  name: "PointsEditor",
1284
- props: {
1285
- /** Make the points unchangeable and remove them from the tab order. */
1286
- disabled: Boolean,
1287
- /** Make the points unmovable. */
1288
- readonly: Boolean,
1289
- /** The cursor to show while dragging a point. @default { cursor: 'grabbing' } */
1290
- externalStyles: Object,
1291
- /** How much one wheel notch moves the focused point. */
1292
- wheel: {
1293
- type: [Array, Object],
1294
- default: () => _tremolo_ui_dom.POINTS_EDITOR_DEFAULT_WHEEL
1295
- },
1296
- /** How much one arrow key press moves a point. */
1297
- keyboard: {
1298
- type: [Array, Object],
1299
- default: () => _tremolo_ui_dom.POINTS_EDITOR_DEFAULT_KEYBOARD
1300
- },
1301
- /** How much a drag moves a point, per modifier key. */
1302
- dragSensitivity: {
1303
- type: [Number, Object],
1304
- default: () => _tremolo_ui_dom.DEFAULT_DRAG_SENSITIVITY
1305
- },
1306
- /**
1307
- * Let points be selected, and a selection be moved as one. A selection
1308
- * updates several points in the same tick.
1309
- */
1310
- selectable: Boolean,
1311
- /** Ids of the selected points. Bind it with `v-model:selection`. */
1312
- selection: Array
1313
- },
1470
+ props: pointsEditorProps,
1314
1471
  emits: { "update:selection": (selection) => Array.isArray(selection) },
1315
1472
  setup(props, { slots, emit }) {
1316
1473
  const ownSelection = (0, vue.shallowRef)(props.selection ?? []);
@@ -1347,8 +1504,8 @@ const PointsEditor = /* @__PURE__ */ (0, vue.defineComponent)({
1347
1504
  get dragSensitivity() {
1348
1505
  return props.dragSensitivity;
1349
1506
  },
1350
- get cursor() {
1351
- return props.externalStyles?.cursor ?? "grabbing";
1507
+ get dragCursor() {
1508
+ return props.dragCursor;
1352
1509
  },
1353
1510
  get selectable() {
1354
1511
  return props.selectable;
@@ -1404,7 +1561,7 @@ const PointsEditorContainer = /* @__PURE__ */ (0, vue.defineComponent)({
1404
1561
  (0, vue.watch)([el, selecting], ([element, on], _, onCleanup) => {
1405
1562
  if (!element || !on) return;
1406
1563
  const drag = (0, _tremolo_ui_dom.createDragValue)(element, {
1407
- axis: _tremolo_ui_dom.POINT_AXIS,
1564
+ axis: _tremolo_ui_dom_internal.POINT_AXIS,
1408
1565
  mapping: (0, _tremolo_ui_dom.elementMapping)(() => element),
1409
1566
  shouldStart: (event) => !points.editor.isPointElement(event.target),
1410
1567
  onDragStart: ([x, y], state) => points.editor.beginSelectionBox({
@@ -1423,7 +1580,7 @@ const PointsEditorContainer = /* @__PURE__ */ (0, vue.defineComponent)({
1423
1580
  flush: "post"
1424
1581
  });
1425
1582
  useWheel(el, (event) => {
1426
- const move = (0, _tremolo_ui_dom.wheelMove)(event);
1583
+ const move = (0, _tremolo_ui_dom_internal.wheelMove)(event);
1427
1584
  if (!move) return;
1428
1585
  const axis = move.axis === 0 ? "x" : "y";
1429
1586
  if (points.editor.nudgeFocusedPoint(axis, move.direction, event)) event.preventDefault();
@@ -1451,59 +1608,76 @@ function visuallyHiddenRangeInput(props) {
1451
1608
  return (0, vue.h)("input", {
1452
1609
  ...props,
1453
1610
  type: "range",
1454
- style: _tremolo_ui_dom.visuallyHiddenStyle
1611
+ style: _tremolo_ui_dom_internal.visuallyHiddenStyle
1455
1612
  });
1456
1613
  }
1457
1614
  //#endregion
1458
1615
  //#region src/components/PointsEditor/PointsEditorPoint.ts
1459
1616
  const perAxis = (setting, axis) => typeof setting === "string" ? setting : setting?.[axis];
1617
+ const pointsEditorPointProps = {
1618
+ /** Where the point is, with `y` growing downwards. Bind it with `v-model`. */
1619
+ modelValue: {
1620
+ type: Object,
1621
+ required: true
1622
+ },
1623
+ /** How the selection refers to this point. One is generated when left out. */
1624
+ id: String,
1625
+ /** The lowest position the point can take, per axis. */
1626
+ min: Object,
1627
+ /** The highest position the point can take, per axis. */
1628
+ max: Object,
1629
+ /** Sets `--color`, for the theme to colour the point with. */
1630
+ color: String,
1631
+ /** Overrides the `disabled` of `PointsEditor`. */
1632
+ disabled: {
1633
+ type: Boolean,
1634
+ default: void 0
1635
+ },
1636
+ /** Overrides the `readonly` of `PointsEditor`. */
1637
+ readonly: {
1638
+ type: Boolean,
1639
+ default: void 0
1640
+ },
1641
+ /**
1642
+ * How long after the last wheel notch or arrow key a change counts as over,
1643
+ * in milliseconds, for `change-end`.
1644
+ * @default 500
1645
+ */
1646
+ changeEndDelay: {
1647
+ type: Number,
1648
+ default: 500
1649
+ },
1650
+ /** Overrides the `wheel` of `PointsEditor`; `null` turns it off. */
1651
+ wheel: {
1652
+ type: [Array, Object],
1653
+ default: void 0
1654
+ },
1655
+ /** Overrides the `keyboard` of `PointsEditor`; `null` turns it off. */
1656
+ keyboard: {
1657
+ type: [Array, Object],
1658
+ default: void 0
1659
+ },
1660
+ /** The accessible name of each axis, or one for both. */
1661
+ ariaLabel: [String, Object],
1662
+ /** What the value of each axis means, or one for both. */
1663
+ ariaValuetext: [String, Object]
1664
+ };
1460
1665
  /** One point, bound with `v-model` as `{ x, y }` from 0 to 1. */
1461
1666
  const PointsEditorPoint = /* @__PURE__ */ (0, vue.defineComponent)({
1462
1667
  name: "PointsEditorPoint",
1463
1668
  inheritAttrs: false,
1464
- props: {
1465
- /** Where the point is, with `y` growing downwards. Bind it with `v-model`. */
1466
- modelValue: {
1467
- type: Object,
1468
- required: true
1469
- },
1470
- /** How the selection refers to this point. One is generated when left out. */
1471
- id: String,
1472
- /** The lowest position the point can take, per axis. */
1473
- min: Object,
1474
- /** The highest position the point can take, per axis. */
1475
- max: Object,
1476
- /** Sets `--color`, for the theme to colour the point with. */
1477
- color: String,
1478
- /** Overrides the `disabled` of `PointsEditor`. */
1479
- disabled: {
1480
- type: Boolean,
1481
- default: void 0
1482
- },
1483
- /** Overrides the `readonly` of `PointsEditor`. */
1484
- readonly: {
1485
- type: Boolean,
1486
- default: void 0
1487
- },
1488
- /** Overrides the `wheel` of `PointsEditor`; `null` turns it off. */
1489
- wheel: {
1490
- type: [Array, Object],
1491
- default: void 0
1492
- },
1493
- /** Overrides the `keyboard` of `PointsEditor`; `null` turns it off. */
1494
- keyboard: {
1495
- type: [Array, Object],
1496
- default: void 0
1497
- },
1498
- /** The accessible name of each axis, or one for both. */
1499
- ariaLabel: [String, Object],
1500
- /** What the value of each axis means, or one for both. */
1501
- ariaValuetext: [String, Object]
1502
- },
1669
+ props: pointsEditorPointProps,
1503
1670
  emits: {
1504
1671
  "update:modelValue": (value) => typeof value === "object",
1505
- dragStart: (value) => typeof value === "object",
1506
- dragEnd: (value) => typeof value === "object"
1672
+ /**
1673
+ * A change of this point started — a drag on it, the first wheel notch or
1674
+ * arrow key while it has the focus — with where it is and what the change
1675
+ * is made with. The points that move along with a selection report
1676
+ * through `update:modelValue` only.
1677
+ */
1678
+ changeStart: (value, _source) => typeof value === "object",
1679
+ /** The change ended, with where the point is. */
1680
+ changeEnd: (value, _source) => typeof value === "object"
1507
1681
  },
1508
1682
  setup(props, { slots, attrs, emit }) {
1509
1683
  const points = usePointsEditorContext();
@@ -1515,7 +1689,14 @@ const PointsEditorPoint = /* @__PURE__ */ (0, vue.defineComponent)({
1515
1689
  const inactive = (0, vue.computed)(() => disabled.value || readonly.value);
1516
1690
  const wheel = (0, vue.computed)(() => props.wheel === void 0 ? points.wheel : props.wheel);
1517
1691
  const keyboard = (0, vue.computed)(() => props.keyboard === void 0 ? points.keyboard : props.keyboard);
1518
- const current = (0, vue.computed)(() => (0, _tremolo_ui_dom.clampPoint)(props.modelValue, props.min, props.max));
1692
+ const current = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.clampPoint)(props.modelValue, props.min, props.max));
1693
+ const gesture = useChangeGesture({
1694
+ value: () => current.value,
1695
+ inactive: () => inactive.value,
1696
+ endDelay: () => props.changeEndDelay,
1697
+ onStart: (value, source) => emit("changeStart", value, source),
1698
+ onEnd: (value, source) => emit("changeEnd", value, source)
1699
+ });
1519
1700
  const el = (0, vue.ref)(null);
1520
1701
  const x = (0, vue.ref)(null);
1521
1702
  const dragging = (0, vue.ref)(false);
@@ -1524,9 +1705,13 @@ const PointsEditorPoint = /* @__PURE__ */ (0, vue.defineComponent)({
1524
1705
  min: props.min,
1525
1706
  max: props.max,
1526
1707
  readonly: inactive.value,
1527
- onChange: (next) => emit("update:modelValue", next),
1708
+ onChange: (next) => {
1709
+ gesture.changed(next);
1710
+ emit("update:modelValue", next);
1711
+ },
1528
1712
  element: el.value,
1529
- wheel: wheel.value
1713
+ wheel: wheel.value,
1714
+ beforeWheel: () => gesture.pulse("wheel")
1530
1715
  });
1531
1716
  (0, vue.watch)(id, (key, _, onCleanup) => onCleanup(points.editor.registerPoint(key, read)), { immediate: true });
1532
1717
  /** Where the pointer was when the drag started. */
@@ -1534,9 +1719,9 @@ const PointsEditorPoint = /* @__PURE__ */ (0, vue.defineComponent)({
1534
1719
  (0, vue.watch)(el, (element, _, onCleanup) => {
1535
1720
  if (!element) return;
1536
1721
  const drag = (0, _tremolo_ui_dom.createDragValue)(element, {
1537
- axis: _tremolo_ui_dom.POINT_AXIS,
1538
- mapping: (0, _tremolo_ui_dom.elementMapping)(() => points.container, { sensitivity: (state) => (0, _tremolo_ui_dom.selectModifier)(points.dragSensitivity, state.event).value }),
1539
- cursor: inactive.value ? void 0 : points.cursor,
1722
+ axis: _tremolo_ui_dom_internal.POINT_AXIS,
1723
+ mapping: (0, _tremolo_ui_dom.elementMapping)(() => points.container, { sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(points.dragSensitivity, state.event).value }),
1724
+ cursor: inactive.value ? void 0 : points.dragCursor,
1540
1725
  shouldStart: () => !disabled.value,
1541
1726
  onChange: ([px, py]) => {
1542
1727
  if (origin) points.editor.movePointDrag({
@@ -1552,15 +1737,15 @@ const PointsEditorPoint = /* @__PURE__ */ (0, vue.defineComponent)({
1552
1737
  };
1553
1738
  dragging.value = true;
1554
1739
  x.value?.focus();
1555
- if (!inactive.value) emit("dragStart", current.value);
1740
+ if (!inactive.value) gesture.hold("pointer");
1556
1741
  },
1557
1742
  onDragEnd: () => {
1558
1743
  origin = null;
1559
1744
  dragging.value = false;
1560
- if (!inactive.value) emit("dragEnd", current.value);
1745
+ gesture.end();
1561
1746
  }
1562
1747
  });
1563
- const stop = (0, vue.watch)(() => inactive.value ? void 0 : points.cursor, (cursor) => drag.update({ cursor }));
1748
+ const stop = (0, vue.watch)(() => inactive.value ? void 0 : points.dragCursor, (cursor) => drag.update({ cursor }));
1564
1749
  onCleanup(() => {
1565
1750
  stop();
1566
1751
  drag.destroy();
@@ -1621,10 +1806,11 @@ const PointsEditorPoint = /* @__PURE__ */ (0, vue.defineComponent)({
1621
1806
  if (!disabled.value && event.target === event.currentTarget) x.value?.focus();
1622
1807
  },
1623
1808
  onKeydown: (event) => {
1624
- const move = (0, _tremolo_ui_dom.arrowKeyMove)(event.key);
1809
+ const move = (0, _tremolo_ui_dom_internal.arrowKeyMove)(event.key);
1625
1810
  if (!move) return;
1626
1811
  event.preventDefault();
1627
1812
  if (inactive.value || !keyboard.value) return;
1813
+ gesture.pulse("keyboard");
1628
1814
  points.editor.nudgePoint(id.value, move.axis === 0 ? "x" : "y", move.direction, keyboard.value, event);
1629
1815
  }
1630
1816
  }, {
@@ -1674,62 +1860,85 @@ function useSliderContext() {
1674
1860
  }
1675
1861
  //#endregion
1676
1862
  //#region src/components/Slider/Slider.ts
1863
+ const sliderProps = {
1864
+ /** The current value. Bind it with `v-model`. */
1865
+ modelValue: {
1866
+ type: Number,
1867
+ required: true
1868
+ },
1869
+ /** The value at the start of the travel. */
1870
+ min: {
1871
+ type: Number,
1872
+ required: true
1873
+ },
1874
+ /** The value at the end of the travel. */
1875
+ max: {
1876
+ type: Number,
1877
+ required: true
1878
+ },
1879
+ /** Granularity of the value. @default 1 */
1880
+ step: {
1881
+ type: Number,
1882
+ default: 1
1883
+ },
1884
+ /** How the value is distributed across the travel. @default linearScale */
1885
+ scale: {
1886
+ type: Object,
1887
+ default: () => _tremolo_ui_functions.linearScale
1888
+ },
1889
+ /**
1890
+ * Which way the slider runs. A vertical slider grows upwards.
1891
+ * @default 'horizontal'
1892
+ */
1893
+ orientation: {
1894
+ type: String,
1895
+ default: "horizontal"
1896
+ },
1897
+ /** Grow the value the other way. */
1898
+ reverse: Boolean,
1899
+ /** The cursor to show while dragging. @default 'pointer' */
1900
+ dragCursor: {
1901
+ type: String,
1902
+ default: "pointer"
1903
+ },
1904
+ wheel: wheelProp,
1905
+ keyboard: keyboardProp,
1906
+ dragSensitivity: dragSensitivityProp,
1907
+ /** Make the slider unchangeable and remove it from the tab order. */
1908
+ disabled: Boolean,
1909
+ /** Make the value unchangeable. */
1910
+ readonly: Boolean,
1911
+ /**
1912
+ * How long after the last wheel notch or arrow key a change counts as over,
1913
+ * in milliseconds, for `change-end`.
1914
+ * @default 500
1915
+ */
1916
+ changeEndDelay: {
1917
+ type: Number,
1918
+ default: 500
1919
+ }
1920
+ };
1677
1921
  /** Customizable slider. Bind the value with `v-model`. */
1678
1922
  const Slider = /* @__PURE__ */ (0, vue.defineComponent)({
1679
1923
  name: "Slider",
1680
- props: {
1681
- /** The current value. Bind it with `v-model`. */
1682
- modelValue: {
1683
- type: Number,
1684
- required: true
1685
- },
1686
- /** The value at the start of the travel. */
1687
- min: {
1688
- type: Number,
1689
- required: true
1690
- },
1691
- /** The value at the end of the travel. */
1692
- max: {
1693
- type: Number,
1694
- required: true
1695
- },
1696
- /** Granularity of the value. @default 1 */
1697
- step: {
1698
- type: Number,
1699
- default: 1
1700
- },
1701
- /** How the value is distributed across the travel. @default linearScale */
1702
- scale: {
1703
- type: Object,
1704
- default: () => _tremolo_ui_functions.linearScale
1705
- },
1706
- /** Run the slider vertically, with the value growing upwards. */
1707
- vertical: Boolean,
1708
- /** Grow the value the other way. */
1709
- reverse: Boolean,
1710
- /** The cursor to show while dragging. @default { cursor: 'pointer' } */
1711
- externalStyles: Object,
1712
- wheel: wheelProp,
1713
- keyboard: keyboardProp,
1714
- dragSensitivity: dragSensitivityProp,
1715
- /** Make the slider unchangeable and remove it from the tab order. */
1716
- disabled: Boolean,
1717
- /** Make the value unchangeable. */
1718
- readonly: Boolean
1719
- },
1924
+ props: sliderProps,
1720
1925
  emits: {
1721
1926
  "update:modelValue": (value) => typeof value === "number",
1722
- /** A drag started, with the value where the track was pressed. */
1723
- dragStart: (value) => typeof value === "number",
1724
- /** The drag ended, with the value it ended on. */
1725
- dragEnd: (value) => typeof value === "number"
1927
+ /**
1928
+ * A change of the value started, with the value before it and what it is
1929
+ * made with.
1930
+ */
1931
+ changeStart: (value, _source) => typeof value === "number",
1932
+ /** The change ended, with the value it ended on. */
1933
+ changeEnd: (value, _source) => typeof value === "number"
1726
1934
  },
1727
1935
  setup(props, { slots, emit, expose }) {
1728
1936
  const root = (0, vue.ref)(null);
1729
1937
  let track = null;
1730
1938
  let thumb = null;
1731
1939
  const inactive = (0, vue.computed)(() => props.disabled || props.readonly);
1732
- const displayReversed = (0, vue.computed)(() => props.vertical !== props.reverse);
1940
+ const vertical = (0, vue.computed)(() => props.orientation === "vertical");
1941
+ const displayReversed = (0, vue.computed)(() => vertical.value !== props.reverse);
1733
1942
  const percent = (0, vue.computed)(() => (0, _tremolo_ui_dom.valuePercent)(props.modelValue, {
1734
1943
  min: props.min,
1735
1944
  max: props.max,
@@ -1748,7 +1957,17 @@ const Slider = /* @__PURE__ */ (0, vue.defineComponent)({
1748
1957
  keyboard: props.keyboard,
1749
1958
  wheel: props.wheel
1750
1959
  }));
1751
- const change = (next) => emit("update:modelValue", next);
1960
+ const gesture = useChangeGesture({
1961
+ value: () => props.modelValue,
1962
+ inactive: () => inactive.value,
1963
+ endDelay: () => props.changeEndDelay,
1964
+ onStart: (value, source) => emit("changeStart", value, source),
1965
+ onEnd: (value, source) => emit("changeEnd", value, source)
1966
+ });
1967
+ const change = (next) => {
1968
+ gesture.changed(next);
1969
+ emit("update:modelValue", next);
1970
+ };
1752
1971
  (0, vue.provide)(SliderKey, {
1753
1972
  get value() {
1754
1973
  return props.modelValue;
@@ -1765,8 +1984,8 @@ const Slider = /* @__PURE__ */ (0, vue.defineComponent)({
1765
1984
  get scale() {
1766
1985
  return props.scale;
1767
1986
  },
1768
- get vertical() {
1769
- return props.vertical;
1987
+ get orientation() {
1988
+ return props.orientation;
1770
1989
  },
1771
1990
  get reverse() {
1772
1991
  return props.reverse;
@@ -1788,31 +2007,30 @@ const Slider = /* @__PURE__ */ (0, vue.defineComponent)({
1788
2007
  thumb = input;
1789
2008
  }
1790
2009
  });
1791
- const valueOf = (v) => v[props.vertical ? 1 : 0];
2010
+ const valueOf = (v) => v[vertical.value ? 1 : 0];
1792
2011
  useDragValue(root, () => ({
1793
2012
  axis: axis.value,
1794
- mapping: (0, _tremolo_ui_dom.elementMapping)(() => track, { sensitivity: (state) => (0, _tremolo_ui_dom.selectModifier)(props.dragSensitivity, state.event).value }),
1795
- cursor: inactive.value ? void 0 : props.externalStyles?.cursor ?? "pointer",
2013
+ mapping: (0, _tremolo_ui_dom.elementMapping)(() => track, { sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(props.dragSensitivity, state.event).value }),
2014
+ cursor: inactive.value ? void 0 : props.dragCursor,
1796
2015
  shouldStart: () => !inactive.value,
1797
2016
  updateOnPointerDown: true,
1798
2017
  onChange: (v) => {
1799
2018
  if (!inactive.value) change(valueOf(v));
1800
2019
  },
1801
- onDragStart: (v) => {
2020
+ onDragStart: () => {
1802
2021
  if (inactive.value) return;
2022
+ gesture.hold("pointer");
1803
2023
  thumb?.focus();
1804
- emit("dragStart", valueOf(v));
1805
2024
  },
1806
- onDragEnd: (v) => {
1807
- if (!inactive.value) emit("dragEnd", valueOf(v));
1808
- }
2025
+ onDragEnd: () => gesture.end()
1809
2026
  }));
1810
2027
  useWheel(root, (event) => {
1811
2028
  if (!props.wheel || inactive.value) return;
1812
- const direction = (0, _tremolo_ui_dom.wheelDirection)(event, { horizontal: !props.vertical });
2029
+ const direction = (0, _tremolo_ui_dom_internal.wheelDirection)(event, { horizontal: !vertical.value });
1813
2030
  if (direction === null) return;
1814
2031
  event.preventDefault();
1815
- change((0, _tremolo_ui_dom.applyDelta)(props.modelValue, props.reverse ? -direction : direction, props.wheel, axis.value, event));
2032
+ gesture.pulse("wheel");
2033
+ change((0, _tremolo_ui_dom_internal.applyDelta)(props.modelValue, props.reverse ? -direction : direction, props.wheel, axis.value, event));
1816
2034
  }, { requireFocus: true });
1817
2035
  expose({
1818
2036
  /** Move the focus to the thumb. */
@@ -1826,15 +2044,16 @@ const Slider = /* @__PURE__ */ (0, vue.defineComponent)({
1826
2044
  ref: root,
1827
2045
  role: "group",
1828
2046
  tabindex: -1,
1829
- "data-orientation": props.vertical ? "vertical" : "horizontal",
2047
+ "data-orientation": props.orientation,
1830
2048
  "data-disabled": props.disabled ? "" : void 0,
1831
2049
  "data-readonly": props.readonly ? "" : void 0,
1832
2050
  onKeydown: (event) => {
1833
- const direction = (0, _tremolo_ui_dom.arrowKeyDirection)(event.key);
2051
+ const direction = (0, _tremolo_ui_dom_internal.arrowKeyDirection)(event.key);
1834
2052
  if (direction === null) return;
1835
2053
  event.preventDefault();
1836
2054
  if (!props.keyboard || inactive.value) return;
1837
- change((0, _tremolo_ui_dom.applyDelta)(props.modelValue, props.reverse ? -direction : direction, props.keyboard, axis.value, event));
2055
+ gesture.pulse("keyboard");
2056
+ change((0, _tremolo_ui_dom_internal.applyDelta)(props.modelValue, props.reverse ? -direction : direction, props.keyboard, axis.value, event));
1838
2057
  },
1839
2058
  onFocus: () => {
1840
2059
  if (!props.disabled) thumb?.focus();
@@ -1845,43 +2064,44 @@ const Slider = /* @__PURE__ */ (0, vue.defineComponent)({
1845
2064
  });
1846
2065
  //#endregion
1847
2066
  //#region src/components/Slider/SliderMarksOption.ts
2067
+ const sliderMarksOptionProps = {
2068
+ /** Where the mark sits, on the same scale as the thumb. */
2069
+ value: {
2070
+ type: Number,
2071
+ required: true
2072
+ },
2073
+ /** Draw the mark itself. @default true */
2074
+ mark: {
2075
+ type: Boolean,
2076
+ default: true
2077
+ },
2078
+ /**
2079
+ * Text shown next to the mark. Leave it out to show the value; `null`
2080
+ * leaves the label out.
2081
+ */
2082
+ label: {
2083
+ type: [
2084
+ Number,
2085
+ String,
2086
+ null
2087
+ ],
2088
+ default: void 0
2089
+ },
2090
+ /** Thickness of the mark. Sets `--thickness`. */
2091
+ thickness: [Number, String],
2092
+ /** Length of the mark. Sets `--length`. */
2093
+ length: [Number, String],
2094
+ /** Space between the mark and the label. Sets `--gap`. */
2095
+ gap: [Number, String],
2096
+ /** Classes for the mark and the label inside the option. */
2097
+ classes: Object,
2098
+ /** Styles for the mark and the label inside the option. */
2099
+ styles: Object
2100
+ };
1848
2101
  /** One mark along the slider, and its label. */
1849
2102
  const SliderMarksOption = /* @__PURE__ */ (0, vue.defineComponent)({
1850
2103
  name: "SliderMarksOption",
1851
- props: {
1852
- /** Where the mark sits, on the same scale as the thumb. */
1853
- value: {
1854
- type: Number,
1855
- required: true
1856
- },
1857
- /** Draw the mark itself. @default true */
1858
- mark: {
1859
- type: Boolean,
1860
- default: true
1861
- },
1862
- /**
1863
- * Text shown next to the mark. Leave it out to show the value; `null`
1864
- * leaves the label out.
1865
- */
1866
- label: {
1867
- type: [
1868
- Number,
1869
- String,
1870
- null
1871
- ],
1872
- default: void 0
1873
- },
1874
- /** Thickness of the mark. Sets `--thickness`. */
1875
- thickness: [Number, String],
1876
- /** Length of the mark. Sets `--length`. */
1877
- length: [Number, String],
1878
- /** Space between the mark and the label. Sets `--gap`. */
1879
- gap: [Number, String],
1880
- /** Classes for the mark and the label inside the option. */
1881
- classes: Object,
1882
- /** Styles for the mark and the label inside the option. */
1883
- styles: Object
1884
- },
2104
+ props: sliderMarksOptionProps,
1885
2105
  setup(props) {
1886
2106
  const slider = useSliderContext();
1887
2107
  checkPlacement("SliderMarksOption", "SliderMarks");
@@ -1889,20 +2109,20 @@ const SliderMarksOption = /* @__PURE__ */ (0, vue.defineComponent)({
1889
2109
  min: slider.min,
1890
2110
  max: slider.max,
1891
2111
  scale: slider.scale
1892
- }, slider.vertical !== slider.reverse));
2112
+ }, slider.orientation === "vertical" !== slider.reverse));
1893
2113
  return () => {
1894
- const orientation = slider.vertical ? "vertical" : "horizontal";
2114
+ const orientation = slider.orientation;
1895
2115
  return (0, vue.h)("div", {
1896
2116
  "data-orientation": orientation,
1897
2117
  style: {
1898
- "--thickness": (0, _tremolo_ui_dom.cssLength)(props.thickness),
1899
- "--length": (0, _tremolo_ui_dom.cssLength)(props.length),
1900
- "--gap": (0, _tremolo_ui_dom.cssLength)(props.gap),
2118
+ "--thickness": (0, _tremolo_ui_dom_internal.cssLength)(props.thickness),
2119
+ "--length": (0, _tremolo_ui_dom_internal.cssLength)(props.length),
2120
+ "--gap": (0, _tremolo_ui_dom_internal.cssLength)(props.gap),
1901
2121
  position: "absolute",
1902
- translate: slider.vertical ? "var(--translate, 0 -50%)" : "var(--translate, -50% 0)",
2122
+ translate: slider.orientation === "vertical" ? "var(--translate, 0 -50%)" : "var(--translate, -50% 0)",
1903
2123
  zIndex: 10,
1904
- left: slider.vertical ? void 0 : `${percent.value}%`,
1905
- top: slider.vertical ? `${percent.value}%` : void 0
2124
+ left: slider.orientation === "vertical" ? void 0 : `${percent.value}%`,
2125
+ top: slider.orientation === "vertical" ? `${percent.value}%` : void 0
1906
2126
  }
1907
2127
  }, [props.mark ? (0, vue.h)("div", {
1908
2128
  class: props.classes?.mark,
@@ -1917,35 +2137,36 @@ const SliderMarksOption = /* @__PURE__ */ (0, vue.defineComponent)({
1917
2137
  });
1918
2138
  //#endregion
1919
2139
  //#region src/components/Slider/SliderMarks.ts
2140
+ const sliderMarksProps = {
2141
+ /** Space between the marks and the track. Sets `--gap`. */
2142
+ gap: [Number, String],
2143
+ /**
2144
+ * Build the marks instead of writing `SliderMarksOption` out: a number
2145
+ * puts one every that many, `'step'` one every `step`. The default slot
2146
+ * is ignored while it is set.
2147
+ */
2148
+ options: [
2149
+ String,
2150
+ Number,
2151
+ Object
2152
+ ]
2153
+ };
1920
2154
  /** The marks along the slider: written out as options, or built from `options`. */
1921
2155
  const SliderMarks = /* @__PURE__ */ (0, vue.defineComponent)({
1922
2156
  name: "SliderMarks",
1923
- props: {
1924
- /** Space between the marks and the track. Sets `--gap`. */
1925
- gap: [Number, String],
1926
- /**
1927
- * Build the marks instead of writing `SliderMarksOption` out: a number
1928
- * puts one every that many, `'step'` one every `step`. The default slot
1929
- * is ignored while it is set.
1930
- */
1931
- options: [
1932
- String,
1933
- Number,
1934
- Object
1935
- ]
1936
- },
2157
+ props: sliderMarksProps,
1937
2158
  setup(props, { slots }) {
1938
2159
  const slider = useSliderContext();
1939
2160
  providePlacement("SliderMarks");
1940
2161
  const marks = (0, vue.computed)(() => {
1941
2162
  if (props.options === void 0) return [];
1942
- const list = (0, _tremolo_ui_dom.sliderMarks)(props.options, slider.min, slider.max, slider.step);
1943
- return slider.vertical !== slider.reverse ? list.reverse() : list;
2163
+ const list = (0, _tremolo_ui_dom_internal.sliderMarks)(props.options, slider.min, slider.max, slider.step);
2164
+ return slider.orientation === "vertical" !== slider.reverse ? list.reverse() : list;
1944
2165
  });
1945
2166
  return () => (0, vue.h)("div", {
1946
- "data-orientation": slider.vertical ? "vertical" : "horizontal",
2167
+ "data-orientation": slider.orientation,
1947
2168
  style: {
1948
- "--gap": (0, _tremolo_ui_dom.cssLength)(props.gap),
2169
+ "--gap": (0, _tremolo_ui_dom_internal.cssLength)(props.gap),
1949
2170
  position: "relative"
1950
2171
  }
1951
2172
  }, props.options !== void 0 ? marks.value.map(({ value, mark, label }) => (0, vue.h)(SliderMarksOption, {
@@ -1958,6 +2179,9 @@ const SliderMarks = /* @__PURE__ */ (0, vue.defineComponent)({
1958
2179
  });
1959
2180
  //#endregion
1960
2181
  //#region src/components/Slider/SliderThumb.ts
2182
+ const sliderThumbProps = {
2183
+ /** Sets `--color`, for the theme to colour the thumb with. */
2184
+ color: String };
1961
2185
  /**
1962
2186
  * The thumb, placed at the value. The ARIA (`aria-label` and friends) lands
1963
2187
  * on the range input inside it, which is the control and takes the focus.
@@ -1965,9 +2189,7 @@ const SliderMarks = /* @__PURE__ */ (0, vue.defineComponent)({
1965
2189
  const SliderThumb = /* @__PURE__ */ (0, vue.defineComponent)({
1966
2190
  name: "SliderThumb",
1967
2191
  inheritAttrs: false,
1968
- props: {
1969
- /** Sets `--color`, for the theme to colour the thumb with. */
1970
- color: String },
2192
+ props: sliderThumbProps,
1971
2193
  setup(props, { slots, attrs, expose }) {
1972
2194
  const slider = useSliderContext();
1973
2195
  checkPlacement("SliderThumb", "SliderTrack");
@@ -1995,8 +2217,8 @@ color: String },
1995
2217
  },
1996
2218
  style,
1997
2219
  {
1998
- top: slider.vertical ? `${slider.percent}%` : "50%",
1999
- left: slider.vertical ? "50%" : `${slider.percent}%`
2220
+ top: slider.orientation === "vertical" ? `${slider.percent}%` : "50%",
2221
+ left: slider.orientation === "vertical" ? "50%" : `${slider.percent}%`
2000
2222
  }
2001
2223
  ]
2002
2224
  }, [visuallyHiddenRangeInput({
@@ -2007,7 +2229,7 @@ color: String },
2007
2229
  step: slider.step,
2008
2230
  disabled: slider.disabled,
2009
2231
  "aria-readonly": slider.readonly,
2010
- "aria-orientation": slider.vertical ? "vertical" : "horizontal",
2232
+ "aria-orientation": slider.orientation,
2011
2233
  "aria-label": ariaLabel,
2012
2234
  "aria-labelledby": ariaLabelledby,
2013
2235
  "aria-describedby": ariaDescribedby,
@@ -2026,19 +2248,20 @@ color: String },
2026
2248
  });
2027
2249
  //#endregion
2028
2250
  //#region src/components/Slider/SliderTrack.ts
2251
+ const sliderTrackProps = {
2252
+ /** How long the track is along the axis the slider runs. Sets `--length`. */
2253
+ length: [Number, String],
2254
+ /** How thick the track is across that axis. Sets `--thickness`. */
2255
+ thickness: [Number, String],
2256
+ /** Colour of the part from `min` to the value. Sets `--active-color`. */
2257
+ activeColor: String,
2258
+ /** Colour of the rest of the track. Sets `--inactive-color`. */
2259
+ inactiveColor: String
2260
+ };
2029
2261
  /** The track the thumb runs along, and what the pointer is measured against. */
2030
2262
  const SliderTrack = /* @__PURE__ */ (0, vue.defineComponent)({
2031
2263
  name: "SliderTrack",
2032
- props: {
2033
- /** How long the track is along the axis the slider runs. Sets `--length`. */
2034
- length: [Number, String],
2035
- /** How thick the track is across that axis. Sets `--thickness`. */
2036
- thickness: [Number, String],
2037
- /** Colour of the part from `min` to the value. Sets `--active`. */
2038
- active: String,
2039
- /** Colour of the rest of the track. Sets `--inactive`. */
2040
- inactive: String
2041
- },
2264
+ props: sliderTrackProps,
2042
2265
  setup(props, { slots }) {
2043
2266
  const slider = useSliderContext();
2044
2267
  providePlacement("SliderTrack");
@@ -2048,13 +2271,13 @@ const SliderTrack = /* @__PURE__ */ (0, vue.defineComponent)({
2048
2271
  return () => (0, vue.h)("div", {
2049
2272
  ref: el,
2050
2273
  "data-disabled": slider.disabled ? "" : void 0,
2051
- "data-orientation": slider.vertical ? "vertical" : "horizontal",
2052
- "data-flipped": slider.vertical !== slider.reverse ? "" : void 0,
2274
+ "data-orientation": slider.orientation,
2275
+ "data-flipped": slider.orientation === "vertical" !== slider.reverse ? "" : void 0,
2053
2276
  style: {
2054
- "--active": props.active,
2055
- "--inactive": props.inactive,
2056
- "--length": (0, _tremolo_ui_dom.cssLength)(props.length),
2057
- "--thickness": (0, _tremolo_ui_dom.cssLength)(props.thickness),
2277
+ "--active-color": props.activeColor,
2278
+ "--inactive-color": props.inactiveColor,
2279
+ "--length": (0, _tremolo_ui_dom_internal.cssLength)(props.length),
2280
+ "--thickness": (0, _tremolo_ui_dom_internal.cssLength)(props.thickness),
2058
2281
  "--percent": `${slider.percent}%`,
2059
2282
  position: "relative"
2060
2283
  }
@@ -2070,68 +2293,86 @@ function useXYPadContext() {
2070
2293
  }
2071
2294
  //#endregion
2072
2295
  //#region src/components/XYPad/XYPad.ts
2296
+ const xyPadProps = {
2297
+ /** The current value as `[x, y]`. Bind it with `v-model`. */
2298
+ modelValue: {
2299
+ type: Array,
2300
+ required: true
2301
+ },
2302
+ /** The value at the start of the travel, per axis. */
2303
+ min: {
2304
+ type: [Number, Array],
2305
+ required: true
2306
+ },
2307
+ /** The value at the end of the travel, per axis. */
2308
+ max: {
2309
+ type: [Number, Array],
2310
+ required: true
2311
+ },
2312
+ /** Granularity of the value, per axis. @default 1 */
2313
+ step: {
2314
+ type: [Number, Array],
2315
+ default: 1
2316
+ },
2317
+ /** How the value is distributed, per axis. @default linearScale */
2318
+ scale: {
2319
+ type: [Object, Array],
2320
+ default: () => _tremolo_ui_functions.linearScale
2321
+ },
2322
+ /** Grow the value the other way, per axis. `y` grows downwards unless reversed. */
2323
+ reverse: {
2324
+ type: [Boolean, Array],
2325
+ default: false
2326
+ },
2327
+ /** The cursor to show while dragging. @default 'pointer' */
2328
+ dragCursor: {
2329
+ type: String,
2330
+ default: "pointer"
2331
+ },
2332
+ wheel: wheelProp,
2333
+ keyboard: keyboardProp,
2334
+ dragSensitivity: dragSensitivityProp,
2335
+ /** Make the pad unchangeable and remove it from the tab order. */
2336
+ disabled: Boolean,
2337
+ /** Make the value unchangeable. */
2338
+ readonly: Boolean,
2339
+ /**
2340
+ * How long after the last wheel notch or arrow key a change counts as over,
2341
+ * in milliseconds, for `change-end`.
2342
+ * @default 500
2343
+ */
2344
+ changeEndDelay: {
2345
+ type: Number,
2346
+ default: 500
2347
+ }
2348
+ };
2073
2349
  /**
2074
2350
  * Two-dimensional slider. The per-axis settings are `[x, y]` tuples, and a
2075
2351
  * plain value applies to both axes. Bind the value with `v-model`.
2076
2352
  */
2077
2353
  const XYPad = /* @__PURE__ */ (0, vue.defineComponent)({
2078
2354
  name: "XYPad",
2079
- props: {
2080
- /** The current value as `[x, y]`. Bind it with `v-model`. */
2081
- modelValue: {
2082
- type: Array,
2083
- required: true
2084
- },
2085
- /** The value at the start of the travel, per axis. */
2086
- min: {
2087
- type: [Number, Array],
2088
- required: true
2089
- },
2090
- /** The value at the end of the travel, per axis. */
2091
- max: {
2092
- type: [Number, Array],
2093
- required: true
2094
- },
2095
- /** Granularity of the value, per axis. @default 1 */
2096
- step: {
2097
- type: [Number, Array],
2098
- default: 1
2099
- },
2100
- /** How the value is distributed, per axis. @default linearScale */
2101
- scale: {
2102
- type: [Object, Array],
2103
- default: () => _tremolo_ui_functions.linearScale
2104
- },
2105
- /** Grow the value the other way, per axis. `y` grows downwards unless reversed. */
2106
- reverse: {
2107
- type: [Boolean, Array],
2108
- default: false
2109
- },
2110
- /** The cursor to show while dragging. @default { cursor: 'pointer' } */
2111
- externalStyles: Object,
2112
- wheel: wheelProp,
2113
- keyboard: keyboardProp,
2114
- dragSensitivity: dragSensitivityProp,
2115
- /** Make the pad unchangeable and remove it from the tab order. */
2116
- disabled: Boolean,
2117
- /** Make the value unchangeable. */
2118
- readonly: Boolean
2119
- },
2355
+ props: xyPadProps,
2120
2356
  emits: {
2121
2357
  "update:modelValue": (value) => Array.isArray(value),
2122
- dragStart: (value) => Array.isArray(value),
2123
- dragEnd: (value) => Array.isArray(value)
2358
+ /**
2359
+ * A change of the value started, with the value before it and what it is
2360
+ * made with.
2361
+ */
2362
+ changeStart: (value, _source) => Array.isArray(value),
2363
+ /** The change ended, with the value it ended on. */
2364
+ changeEnd: (value, _source) => Array.isArray(value)
2124
2365
  },
2125
2366
  setup(props, { slots, emit, expose }) {
2126
2367
  const root = (0, vue.ref)(null);
2127
2368
  let area = null;
2128
2369
  let thumb = null;
2129
2370
  const inactive = (0, vue.computed)(() => props.disabled || props.readonly);
2130
- const min = (0, vue.computed)(() => (0, _tremolo_ui_dom.toXY)(props.min));
2131
- const max = (0, vue.computed)(() => (0, _tremolo_ui_dom.toXY)(props.max));
2132
- const step = (0, vue.computed)(() => (0, _tremolo_ui_dom.toXY)(props.step));
2133
- const scale = (0, vue.computed)(() => (0, _tremolo_ui_dom.toXY)(props.scale));
2134
- const reverse = (0, vue.computed)(() => (0, _tremolo_ui_dom.toXY)(props.reverse));
2371
+ const min = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.toXY)(props.min));
2372
+ const max = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.toXY)(props.max));
2373
+ const step = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.toXY)(props.step));
2374
+ const scale = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.toXY)(props.scale));
2375
+ const reverse = (0, vue.computed)(() => (0, _tremolo_ui_dom_internal.toXY)(props.reverse));
2135
2376
  const percent = (0, vue.computed)(() => [0, 1].map((i) => (0, _tremolo_ui_dom.valuePercent)(props.modelValue[i], {
2136
2377
  min: min.value[i],
2137
2378
  max: max.value[i],
@@ -2151,11 +2392,21 @@ const XYPad = /* @__PURE__ */ (0, vue.defineComponent)({
2151
2392
  keyboard: props.keyboard,
2152
2393
  wheel: props.wheel
2153
2394
  }));
2154
- const change = (next) => emit("update:modelValue", next);
2395
+ const gesture = useChangeGesture({
2396
+ value: () => props.modelValue,
2397
+ inactive: () => inactive.value,
2398
+ endDelay: () => props.changeEndDelay,
2399
+ onStart: (value, source) => emit("changeStart", value, source),
2400
+ onEnd: (value, source) => emit("changeEnd", value, source)
2401
+ });
2402
+ const change = (next) => {
2403
+ gesture.changed(next);
2404
+ emit("update:modelValue", next);
2405
+ };
2155
2406
  /** Move one axis by one press of `option`, in screen coordinates. */
2156
2407
  function nudge({ axis: i, direction }, option, modifiers) {
2157
2408
  const value = props.modelValue;
2158
- const next = (0, _tremolo_ui_dom.applyDelta)(value[i], reverse.value[i] ? -direction : direction, option, axis.value[i], modifiers);
2409
+ const next = (0, _tremolo_ui_dom_internal.applyDelta)(value[i], reverse.value[i] ? -direction : direction, option, axis.value[i], modifiers);
2159
2410
  change(i === 0 ? [next, value[1]] : [value[0], next]);
2160
2411
  }
2161
2412
  (0, vue.provide)(XYPadKey, {
@@ -2196,27 +2447,26 @@ const XYPad = /* @__PURE__ */ (0, vue.defineComponent)({
2196
2447
  });
2197
2448
  useDragValue(root, () => ({
2198
2449
  axis: axis.value,
2199
- mapping: (0, _tremolo_ui_dom.elementMapping)(() => area, { sensitivity: (state) => (0, _tremolo_ui_dom.selectModifier)(props.dragSensitivity, state.event).value }),
2450
+ mapping: (0, _tremolo_ui_dom.elementMapping)(() => area, { sensitivity: (state) => (0, _tremolo_ui_dom_internal.selectModifier)(props.dragSensitivity, state.event).value }),
2200
2451
  updateOnPointerDown: true,
2201
- cursor: inactive.value ? void 0 : props.externalStyles?.cursor ?? "pointer",
2452
+ cursor: inactive.value ? void 0 : props.dragCursor,
2202
2453
  shouldStart: () => !inactive.value,
2203
2454
  onChange: (v) => {
2204
2455
  if (!inactive.value) change(v);
2205
2456
  },
2206
- onDragStart: (v) => {
2457
+ onDragStart: () => {
2207
2458
  if (inactive.value) return;
2459
+ gesture.hold("pointer");
2208
2460
  thumb?.focus();
2209
- emit("dragStart", v);
2210
2461
  },
2211
- onDragEnd: (v) => {
2212
- if (!inactive.value) emit("dragEnd", v);
2213
- }
2462
+ onDragEnd: () => gesture.end()
2214
2463
  }));
2215
2464
  useWheel(root, (event) => {
2216
2465
  if (!props.wheel || inactive.value) return;
2217
- const move = (0, _tremolo_ui_dom.wheelMove)(event);
2466
+ const move = (0, _tremolo_ui_dom_internal.wheelMove)(event);
2218
2467
  if (!move) return;
2219
2468
  event.preventDefault();
2469
+ gesture.pulse("wheel");
2220
2470
  nudge(move, props.wheel, event);
2221
2471
  }, { requireFocus: true });
2222
2472
  expose({
@@ -2232,10 +2482,13 @@ const XYPad = /* @__PURE__ */ (0, vue.defineComponent)({
2232
2482
  "data-disabled": props.disabled ? "" : void 0,
2233
2483
  "data-readonly": props.readonly ? "" : void 0,
2234
2484
  onKeydown: (event) => {
2235
- const move = (0, _tremolo_ui_dom.arrowKeyMove)(event.key);
2485
+ const move = (0, _tremolo_ui_dom_internal.arrowKeyMove)(event.key);
2236
2486
  if (!move) return;
2237
2487
  event.preventDefault();
2238
- if (props.keyboard && !inactive.value) nudge(move, props.keyboard, event);
2488
+ if (props.keyboard && !inactive.value) {
2489
+ gesture.pulse("keyboard");
2490
+ nudge(move, props.keyboard, event);
2491
+ }
2239
2492
  },
2240
2493
  onFocus: () => {
2241
2494
  if (!props.disabled) thumb?.focus();
@@ -2263,22 +2516,23 @@ const XYPadArea = /* @__PURE__ */ (0, vue.defineComponent)({
2263
2516
  });
2264
2517
  //#endregion
2265
2518
  //#region src/components/XYPad/XYPadThumb.ts
2519
+ const xyPadThumbProps = {
2520
+ /** Sets `--color`, for the theme to colour the thumb with. */
2521
+ color: String,
2522
+ /** The accessible name of each axis, as `[x, y]`. */
2523
+ ariaLabel: [String, Array],
2524
+ /** The ids of what labels each axis, as `[x, y]` or one for both. */
2525
+ ariaLabelledby: [String, Array],
2526
+ /** The ids of what describes each axis, as `[x, y]` or one for both. */
2527
+ ariaDescribedby: [String, Array],
2528
+ /** What the value of each axis means, as `[x, y]` or one for both. */
2529
+ ariaValuetext: [String, Array]
2530
+ };
2266
2531
  /** The thumb, placed at the value, with one range input per axis inside. */
2267
2532
  const XYPadThumb = /* @__PURE__ */ (0, vue.defineComponent)({
2268
2533
  name: "XYPadThumb",
2269
2534
  inheritAttrs: false,
2270
- props: {
2271
- /** Sets `--color`, for the theme to colour the thumb with. */
2272
- color: String,
2273
- /** The accessible name of each axis, as `[x, y]`. */
2274
- ariaLabel: [String, Array],
2275
- /** The ids of what labels each axis, as `[x, y]` or one for both. */
2276
- ariaLabelledby: [String, Array],
2277
- /** The ids of what describes each axis, as `[x, y]` or one for both. */
2278
- ariaDescribedby: [String, Array],
2279
- /** What the value of each axis means, as `[x, y]` or one for both. */
2280
- ariaValuetext: [String, Array]
2281
- },
2535
+ props: xyPadThumbProps,
2282
2536
  setup(props, { slots, attrs, expose }) {
2283
2537
  const pad = useXYPadContext();
2284
2538
  checkPlacement("XYPadThumb", "XYPadArea");
@@ -2296,7 +2550,7 @@ const XYPadThumb = /* @__PURE__ */ (0, vue.defineComponent)({
2296
2550
  }
2297
2551
  });
2298
2552
  function input(axis) {
2299
- const labels = (0, _tremolo_ui_dom.toXY)(props.ariaLabel);
2553
+ const labels = (0, _tremolo_ui_dom_internal.toXY)(props.ariaLabel);
2300
2554
  return visuallyHiddenRangeInput({
2301
2555
  ref: axis === 0 ? x : y,
2302
2556
  "data-axis": axis === 0 ? "x" : "y",
@@ -2308,9 +2562,9 @@ const XYPadThumb = /* @__PURE__ */ (0, vue.defineComponent)({
2308
2562
  "aria-readonly": pad.readonly,
2309
2563
  "aria-orientation": axis === 0 ? "horizontal" : "vertical",
2310
2564
  "aria-label": labels[axis] ?? (axis === 0 ? "x" : "y"),
2311
- "aria-labelledby": (0, _tremolo_ui_dom.toXY)(props.ariaLabelledby)[axis],
2312
- "aria-describedby": (0, _tremolo_ui_dom.toXY)(props.ariaDescribedby)[axis],
2313
- "aria-valuetext": (0, _tremolo_ui_dom.toXY)(props.ariaValuetext)[axis],
2565
+ "aria-labelledby": (0, _tremolo_ui_dom_internal.toXY)(props.ariaLabelledby)[axis],
2566
+ "aria-describedby": (0, _tremolo_ui_dom_internal.toXY)(props.ariaDescribedby)[axis],
2567
+ "aria-valuetext": (0, _tremolo_ui_dom_internal.toXY)(props.ariaValuetext)[axis],
2314
2568
  onInput: (event) => {
2315
2569
  const target = event.target;
2316
2570
  if (pad.readonly) {