@xsynaptic/sonic-ui 0.6.1 → 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 (105) hide show
  1. package/README.md +2 -0
  2. package/dist/default/define/spectrum.js +7 -0
  3. package/dist/default/define.js +1 -0
  4. package/dist/default/elements/button.js +30 -2
  5. package/dist/default/elements/dial.js +12 -3
  6. package/dist/default/elements/display-element.js +77 -0
  7. package/dist/default/elements/envelope.js +3 -3
  8. package/dist/default/elements/modulation.js +45 -0
  9. package/dist/default/elements/readout-claim.js +8 -3
  10. package/dist/default/elements/slider.js +68 -4
  11. package/dist/default/elements/spectrum.js +230 -0
  12. package/dist/default/elements/value-element.js +114 -317
  13. package/dist/default/elements/value-gestures.js +301 -0
  14. package/dist/default/elements/wave-element.js +59 -9
  15. package/dist/default/elements/waveform.js +61 -12
  16. package/dist/default/elements/wavestrip.js +25 -49
  17. package/dist/default/elements/xy.js +10 -21
  18. package/dist/default/index.js +2 -1
  19. package/dist/default/lib/asked-value.js +21 -0
  20. package/dist/default/lib/canvas-surface.js +10 -4
  21. package/dist/default/lib/frame-timeline.js +2 -2
  22. package/dist/default/lib/hover-preview.js +46 -0
  23. package/dist/default/lib/marker-rider.js +12 -6
  24. package/dist/default/lib/pointer-drag.js +7 -2
  25. package/dist/default/lib/read-ms-property.js +10 -0
  26. package/dist/default/lib/spectrum-columns.js +74 -0
  27. package/dist/default/lib/spectrum-levels.js +55 -0
  28. package/dist/default/lib/strip-scene.js +12 -1
  29. package/dist/default/lib/time-regions.js +5 -1
  30. package/dist/dev/define/spectrum.d.ts +2 -0
  31. package/dist/dev/define/spectrum.js +8 -0
  32. package/dist/dev/define/spectrum.js.map +1 -0
  33. package/dist/dev/define.d.ts +3 -1
  34. package/dist/dev/define.js +1 -0
  35. package/dist/dev/elements/button.d.ts +2 -0
  36. package/dist/dev/elements/button.js +30 -2
  37. package/dist/dev/elements/button.js.map +1 -1
  38. package/dist/dev/elements/dial.js +12 -3
  39. package/dist/dev/elements/dial.js.map +1 -1
  40. package/dist/dev/elements/display-element.d.ts +29 -0
  41. package/dist/dev/elements/display-element.js +78 -0
  42. package/dist/dev/elements/display-element.js.map +1 -0
  43. package/dist/dev/elements/envelope.js +3 -3
  44. package/dist/dev/elements/envelope.js.map +1 -1
  45. package/dist/dev/elements/modulation.js +46 -0
  46. package/dist/dev/elements/modulation.js.map +1 -0
  47. package/dist/dev/elements/readout-claim.js +8 -3
  48. package/dist/dev/elements/readout-claim.js.map +1 -1
  49. package/dist/dev/elements/slider.d.ts +5 -0
  50. package/dist/dev/elements/slider.js +68 -4
  51. package/dist/dev/elements/slider.js.map +1 -1
  52. package/dist/dev/elements/spectrum.d.ts +59 -0
  53. package/dist/dev/elements/spectrum.js +231 -0
  54. package/dist/dev/elements/spectrum.js.map +1 -0
  55. package/dist/dev/elements/value-element.d.ts +13 -14
  56. package/dist/dev/elements/value-element.js +114 -317
  57. package/dist/dev/elements/value-element.js.map +1 -1
  58. package/dist/dev/elements/value-gestures.d.ts +10 -0
  59. package/dist/dev/elements/value-gestures.js +302 -0
  60. package/dist/dev/elements/value-gestures.js.map +1 -0
  61. package/dist/dev/elements/wave-element.d.ts +8 -2
  62. package/dist/dev/elements/wave-element.js +59 -9
  63. package/dist/dev/elements/wave-element.js.map +1 -1
  64. package/dist/dev/elements/waveform.d.ts +12 -4
  65. package/dist/dev/elements/waveform.js +61 -12
  66. package/dist/dev/elements/waveform.js.map +1 -1
  67. package/dist/dev/elements/wavestrip.d.ts +3 -1
  68. package/dist/dev/elements/wavestrip.js +25 -49
  69. package/dist/dev/elements/wavestrip.js.map +1 -1
  70. package/dist/dev/elements/xy.js +10 -21
  71. package/dist/dev/elements/xy.js.map +1 -1
  72. package/dist/dev/index.d.ts +4 -1
  73. package/dist/dev/index.js +2 -1
  74. package/dist/dev/lib/asked-value.js +22 -0
  75. package/dist/dev/lib/asked-value.js.map +1 -0
  76. package/dist/dev/lib/canvas-surface.d.ts +5 -3
  77. package/dist/dev/lib/canvas-surface.js +10 -4
  78. package/dist/dev/lib/canvas-surface.js.map +1 -1
  79. package/dist/dev/lib/frame-timeline.js +2 -2
  80. package/dist/dev/lib/frame-timeline.js.map +1 -1
  81. package/dist/dev/lib/hover-preview.js +47 -0
  82. package/dist/dev/lib/hover-preview.js.map +1 -0
  83. package/dist/dev/lib/marker-rider.js +12 -6
  84. package/dist/dev/lib/marker-rider.js.map +1 -1
  85. package/dist/dev/lib/pointer-drag.js +7 -2
  86. package/dist/dev/lib/pointer-drag.js.map +1 -1
  87. package/dist/dev/lib/read-ms-property.js +11 -0
  88. package/dist/dev/lib/read-ms-property.js.map +1 -0
  89. package/dist/dev/lib/spectrum-columns.js +75 -0
  90. package/dist/dev/lib/spectrum-columns.js.map +1 -0
  91. package/dist/dev/lib/spectrum-levels.js +56 -0
  92. package/dist/dev/lib/spectrum-levels.js.map +1 -0
  93. package/dist/dev/lib/strip-scene.js +12 -1
  94. package/dist/dev/lib/strip-scene.js.map +1 -1
  95. package/dist/dev/lib/time-regions.js +5 -1
  96. package/dist/dev/lib/time-regions.js.map +1 -1
  97. package/dist/styles/button.css +7 -1
  98. package/dist/styles/controls.css +1 -0
  99. package/dist/styles/material.css +36 -3
  100. package/dist/styles/meter.css +22 -18
  101. package/dist/styles/slider.css +42 -5
  102. package/dist/styles/spectrum.css +105 -0
  103. package/dist/styles/waveform.css +48 -16
  104. package/dist/styles/wavestrip.css +26 -5
  105. package/package.json +2 -1
package/README.md CHANGED
@@ -23,6 +23,8 @@ import '@xsynaptic/sonic-ui/define';
23
23
  <sonic-dial aria-label="Cutoff" value="40"></sonic-dial>
24
24
  ```
25
25
 
26
+ A display takes its signal as data: `spectrum.analyser = node`, or `spectrum.push(frame)`.
27
+
26
28
  Under a hydrating framework, register the definitions once hydration is done (in React, from an effect); a control owns one child of its host and uses only the children it documents.
27
29
 
28
30
  Targets current Chrome and Edge, Safari 26 and Firefox 147. MIT licensed.
@@ -0,0 +1,7 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicSpectrum } from "../elements/spectrum.js";
3
+
4
+ //#region src/define/spectrum.ts
5
+ defineOnce("sonic-spectrum", SonicSpectrum);
6
+
7
+ //#endregion
@@ -5,6 +5,7 @@ import "./define/meter.js";
5
5
  import "./define/number.js";
6
6
  import "./define/segmented.js";
7
7
  import "./define/slider.js";
8
+ import "./define/spectrum.js";
8
9
  import "./define/split.js";
9
10
  import "./define/switch.js";
10
11
  import "./define/waveform.js";
@@ -4,16 +4,25 @@ import { copyNode } from "../lib/copy-node.js";
4
4
  import { bindHold } from "../lib/hold.js";
5
5
  import { placeChildren, requireChild, template } from "../lib/render.js";
6
6
  import { mirrorChildren } from "../lib/mirror-children.js";
7
+ import { capturePointer } from "../lib/pointer-drag.js";
7
8
 
8
9
  //#region src/elements/button.ts
9
10
  const renderButton = template(`
10
11
  <button class="sonic-button" type="button"><span class="sonic-button-cap"></span></button>
11
12
  `, HTMLButtonElement);
12
13
  const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
14
+ const popupAttributes = {
15
+ controls: "aria-controls",
16
+ expanded: "aria-expanded",
17
+ popup: "aria-haspopup"
18
+ };
19
+ const pressWhens = /* @__PURE__ */ new Set(["pressed", "released"]);
13
20
  var SonicButton = class extends SonicFormElement {
14
21
  static observedAttributes = [
15
22
  ...SonicFormElement.observedAttributes,
23
+ ...Object.keys(popupAttributes),
16
24
  "busy",
25
+ "legend",
17
26
  "momentary",
18
27
  "pressed",
19
28
  "soft-disabled",
@@ -44,6 +53,12 @@ var SonicButton = class extends SonicFormElement {
44
53
  set latching(isLatching) {
45
54
  this.reflect("latching", isLatching);
46
55
  }
56
+ get legend() {
57
+ return this.getAttribute("legend") ?? void 0;
58
+ }
59
+ set legend(name) {
60
+ this.reflect("legend", name);
61
+ }
47
62
  get momentary() {
48
63
  return this.hasAttribute("momentary");
49
64
  }
@@ -88,12 +103,13 @@ var SonicButton = class extends SonicFormElement {
88
103
  connect(signal) {
89
104
  const button = this.#button;
90
105
  const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
91
- this.upgradeProperties("armed", "busy", "defaultPressed", "momentary", "softDisabled", "latching", "pressed", "value");
106
+ this.upgradeProperties("armed", "busy", "defaultPressed", "legend", "momentary", "softDisabled", "latching", "pressed", "value");
92
107
  mirrorChildren(this, {
93
108
  control: button,
94
109
  copy: copyNode,
95
110
  place: (copies) => {
96
111
  placeChildren(cap, copies);
112
+ this.#showLegend();
97
113
  }
98
114
  }, signal);
99
115
  this.render();
@@ -122,6 +138,8 @@ var SonicButton = class extends SonicFormElement {
122
138
  writeAttribute(button, "aria-pressed", this.latching ? String(this.pressed) : void 0);
123
139
  this.toggleState("pressed", this.pressed);
124
140
  this.forwardNaming(button, true);
141
+ this.#forwardPopup(button);
142
+ this.#showLegend();
125
143
  const submitted = this.latching && this.pressed ? this.value : null;
126
144
  this.writeFormValue(submitted, String(this.pressed));
127
145
  }
@@ -142,7 +160,7 @@ var SonicButton = class extends SonicFormElement {
142
160
  this.#holding = holding;
143
161
  button.addEventListener("pointerdown", (event) => {
144
162
  if (event.button !== 0 || !this.#isMomentary()) return;
145
- button.setPointerCapture(event.pointerId);
163
+ capturePointer(button, event.pointerId);
146
164
  holding.hold(event.pointerId);
147
165
  }, { signal });
148
166
  button.addEventListener("keydown", (event) => {
@@ -150,6 +168,9 @@ var SonicButton = class extends SonicFormElement {
150
168
  holding.hold(event.key);
151
169
  }, { signal });
152
170
  }
171
+ #forwardPopup(button) {
172
+ for (const [name, aria] of Object.entries(popupAttributes)) writeAttribute(button, aria, this.getAttribute(name) ?? void 0);
173
+ }
153
174
  #isMomentary() {
154
175
  return this.momentary && !this.latching;
155
176
  }
@@ -160,6 +181,13 @@ var SonicButton = class extends SonicFormElement {
160
181
  this.pressed = isPressed;
161
182
  this.dispatchEvent(new Event("change", { bubbles: true }));
162
183
  }
184
+ #showLegend() {
185
+ const legend = this.legend;
186
+ for (const part of this.#button.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
187
+ const when = part.dataset.sonicWhen ?? "";
188
+ part.toggleAttribute("data-sonic-hidden", !pressWhens.has(when) && when !== legend);
189
+ }
190
+ }
163
191
  };
164
192
 
165
193
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { requireChild, template } from "../lib/render.js";
2
+ import { createModulation } from "./modulation.js";
2
3
  import { SonicValueElement } from "./value-element.js";
3
4
  import { bindScale } from "./scale.js";
4
5
 
@@ -43,12 +44,15 @@ var SonicDial = class extends SonicValueElement {
43
44
  this.reflect("modulation", amount);
44
45
  }
45
46
  get modulationValue() {
46
- return this.readModulationValue();
47
+ return this.#modulation.value();
47
48
  }
48
49
  set modulationValue(value) {
49
- this.writeModulationValue(this.#dial, "dial", value);
50
+ this.#modulation.write(value, this.isBound() ? this.mapping() : void 0);
50
51
  }
51
52
  #dial = renderDial();
53
+ #modulation = createModulation(this.#dial, "dial", (isModulated) => {
54
+ this.toggleState("modulated", isModulated);
55
+ });
52
56
  connectedCallback() {
53
57
  this.upgradeProperties("endless", "modulationValue", "modulation");
54
58
  super.connectedCallback();
@@ -67,7 +71,12 @@ var SonicDial = class extends SonicValueElement {
67
71
  }));
68
72
  }
69
73
  draw() {
70
- this.writeProportions(this.#dial, "dial", this.endless ? 0 : this.modulation);
74
+ this.#modulation.draw({
75
+ mapping: this.mapping(),
76
+ modulation: this.endless ? 0 : this.modulation,
77
+ origin: this.originValue(),
78
+ value: this.value
79
+ });
71
80
  }
72
81
  focusTarget() {
73
82
  return this.#dial;
@@ -0,0 +1,77 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ import { bindSurface } from "../lib/canvas-surface.js";
3
+
4
+ //#region src/elements/display-element.ts
5
+ var SonicDisplayElement = class extends SonicElement {
6
+ static observedAttributes = [
7
+ ...SonicElement.observedAttributes,
8
+ "max",
9
+ "min"
10
+ ];
11
+ get analyser() {
12
+ return this.#analyser;
13
+ }
14
+ set analyser(node) {
15
+ this.#analyser = node ?? void 0;
16
+ this.#surface?.requestFrame();
17
+ }
18
+ get max() {
19
+ return this.numberAttribute("max", 0);
20
+ }
21
+ set max(decibels) {
22
+ this.reflect("max", decibels);
23
+ }
24
+ get min() {
25
+ return this.numberAttribute("min", -90);
26
+ }
27
+ set min(decibels) {
28
+ this.reflect("min", decibels);
29
+ }
30
+ #analyser;
31
+ #buffer = /* @__PURE__ */ new Float32Array(0);
32
+ #surface;
33
+ attributeChangedCallback() {
34
+ this.#surface?.invalidate();
35
+ }
36
+ connectedCallback() {
37
+ this.upgradeProperties("analyser", "max", "min");
38
+ super.connectedCallback();
39
+ }
40
+ push(frame) {
41
+ this.receive(frame);
42
+ this.#surface?.requestFrame();
43
+ }
44
+ repaint() {
45
+ this.#surface?.rebuild();
46
+ }
47
+ connect(signal) {
48
+ const control = this.control;
49
+ this.keepControl(control, signal);
50
+ this.#surface = bindSurface({
51
+ canvas: this.canvas,
52
+ colours: this.colours,
53
+ numbers: this.numbers,
54
+ paint: (context, frame) => {
55
+ this.#paint(context, frame);
56
+ },
57
+ signal
58
+ });
59
+ this.checkStyles(control, this.sheet);
60
+ }
61
+ surface() {
62
+ return this.#surface;
63
+ }
64
+ #paint(context, frame) {
65
+ const analyser = this.isDisabled() ? void 0 : this.#analyser;
66
+ if (analyser) {
67
+ if (this.#buffer.length !== analyser.frequencyBinCount) this.#buffer = new Float32Array(analyser.frequencyBinCount);
68
+ this.pull(analyser, this.#buffer);
69
+ this.receive(this.#buffer);
70
+ }
71
+ this.paint(context, frame);
72
+ if (analyser) this.#surface?.requestFrame();
73
+ }
74
+ };
75
+
76
+ //#endregion
77
+ export { SonicDisplayElement };
@@ -1,10 +1,10 @@
1
1
  import { writeAttribute } from "../lib/write-attribute.js";
2
2
  import { SonicElement } from "./sonic-element.js";
3
3
  import { requireChild, template } from "../lib/render.js";
4
- import { ReadoutClaim } from "./readout-claim.js";
4
+ import { bindDrag } from "../lib/pointer-drag.js";
5
5
  import { Readout } from "./readout.js";
6
+ import { ReadoutClaim, revealDelay } from "./readout-claim.js";
6
7
  import { dragThresholdPx } from "../lib/drag-step.js";
7
- import { bindDrag } from "../lib/pointer-drag.js";
8
8
  import { SonicValueElement, linkValue } from "./value-element.js";
9
9
  import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
10
10
  import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
@@ -269,7 +269,7 @@ var SonicEnvelope = class extends SonicElement {
269
269
  const pressed = this.#pressed(event);
270
270
  if (!pressed || pressed.drives.length === 0) return void 0;
271
271
  const { drives, isFlipped, part } = pressed;
272
- this.#claim.press(true);
272
+ this.#claim.press(true, revealDelay(this.#envelope));
273
273
  this.#held = part;
274
274
  this.#drawBracket();
275
275
  const box = this.#graph.getBoundingClientRect();
@@ -0,0 +1,45 @@
1
+ import { toNumber } from "../lib/math.js";
2
+
3
+ //#region src/elements/modulation.ts
4
+ function createModulation(control, prefix, toggle) {
5
+ const { style } = control;
6
+ let isModulated = false;
7
+ let modulationValue;
8
+ const writeProportion = (mapping) => {
9
+ const shown = mapping.isWrapping ? void 0 : modulationValue;
10
+ const isShown = shown !== void 0;
11
+ if (isShown !== isModulated) {
12
+ isModulated = isShown;
13
+ toggle(isShown);
14
+ }
15
+ if (shown === void 0) {
16
+ style.removeProperty(`--_sonic-${prefix}-modulation-value`);
17
+ return;
18
+ }
19
+ style.setProperty(`--_sonic-${prefix}-modulation-value`, String(mapping.proportionOf(shown)));
20
+ };
21
+ return {
22
+ draw: ({ mapping, modulation, origin, value }) => {
23
+ const at = mapping.proportionOf(value);
24
+ const reach = mapping.proportionOf(value + modulation);
25
+ const { positionCount } = mapping;
26
+ style.setProperty(`--_sonic-${prefix}-value`, String(at));
27
+ style.setProperty(`--_sonic-${prefix}-origin`, String(mapping.proportionOf(origin)));
28
+ style.setProperty(`--_sonic-${prefix}-modulation-from`, String(Math.min(at, reach)));
29
+ style.setProperty(`--_sonic-${prefix}-modulation-to`, String(Math.max(at, reach)));
30
+ if (positionCount === void 0) style.removeProperty(`--_sonic-${prefix}-position-count`);
31
+ else style.setProperty(`--_sonic-${prefix}-position-count`, String(positionCount));
32
+ writeProportion(mapping);
33
+ },
34
+ value: () => modulationValue,
35
+ write: (next, mapping) => {
36
+ const value = next === void 0 || next === null ? void 0 : toNumber(next);
37
+ if (value !== void 0 && !Number.isFinite(value)) return;
38
+ modulationValue = value;
39
+ if (mapping) writeProportion(mapping);
40
+ }
41
+ };
42
+ }
43
+
44
+ //#endregion
45
+ export { createModulation };
@@ -1,6 +1,11 @@
1
+ import { readMsProperty } from "../lib/read-ms-property.js";
2
+
1
3
  //#region src/elements/readout-claim.ts
2
4
  const keyRevealMs = 1e3;
3
5
  const revealMs = 250;
6
+ function revealDelay(control) {
7
+ return Math.max(0, readMsProperty(getComputedStyle(control), "--_sonic-reveal-delay", revealMs));
8
+ }
4
9
  var ReadoutClaim = class {
5
10
  get isRevealed() {
6
11
  return this.#isDragging || this.#keyTimer !== void 0;
@@ -24,7 +29,7 @@ var ReadoutClaim = class {
24
29
  this.#keyTimer = void 0;
25
30
  return wasTimed;
26
31
  }
27
- press(isDown) {
32
+ press(isDown, delayMs = revealMs) {
28
33
  this.conceal("drag");
29
34
  if (!isDown) {
30
35
  this.#onLapse();
@@ -33,7 +38,7 @@ var ReadoutClaim = class {
33
38
  this.#holdTimer = setTimeout(() => {
34
39
  this.reveal("drag");
35
40
  this.#onLapse();
36
- }, 250);
41
+ }, delayMs);
37
42
  }
38
43
  reveal(by) {
39
44
  if (by === "drag") {
@@ -49,4 +54,4 @@ var ReadoutClaim = class {
49
54
  };
50
55
 
51
56
  //#endregion
52
- export { ReadoutClaim };
57
+ export { ReadoutClaim, revealDelay };
@@ -1,4 +1,6 @@
1
+ import { clampProportion } from "../lib/math.js";
1
2
  import { requireChild, template } from "../lib/render.js";
3
+ import { createModulation } from "./modulation.js";
2
4
  import { SonicValueElement } from "./value-element.js";
3
5
  import { bindScale } from "./scale.js";
4
6
  import { sortedRegions } from "../lib/time-regions.js";
@@ -34,6 +36,7 @@ var SonicSlider = class extends SonicValueElement {
34
36
  ...SonicValueElement.observedAttributes,
35
37
  "modulation",
36
38
  "orientation",
39
+ "scrub",
37
40
  "spring"
38
41
  ];
39
42
  get buffered() {
@@ -56,10 +59,10 @@ var SonicSlider = class extends SonicValueElement {
56
59
  this.reflect("modulation", amount);
57
60
  }
58
61
  get modulationValue() {
59
- return this.readModulationValue();
62
+ return this.#modulation.value();
60
63
  }
61
64
  set modulationValue(value) {
62
- this.writeModulationValue(this.#slider, "slider", value);
65
+ this.#modulation.write(value, this.isBound() ? this.mapping() : void 0);
63
66
  }
64
67
  get orientation() {
65
68
  return this.getAttribute("orientation") === "vertical" ? "vertical" : "horizontal";
@@ -67,6 +70,12 @@ var SonicSlider = class extends SonicValueElement {
67
70
  set orientation(direction) {
68
71
  this.reflect("orientation", direction);
69
72
  }
73
+ get scrub() {
74
+ return this.hasAttribute("scrub");
75
+ }
76
+ set scrub(isScrubbing) {
77
+ this.reflect("scrub", isScrubbing);
78
+ }
70
79
  get spring() {
71
80
  return this.hasAttribute("spring");
72
81
  }
@@ -75,8 +84,11 @@ var SonicSlider = class extends SonicValueElement {
75
84
  }
76
85
  #buffered = [];
77
86
  #slider = renderSlider();
87
+ #modulation = createModulation(this.#slider, "slider", (isModulated) => {
88
+ this.toggleState("modulated", isModulated);
89
+ });
78
90
  connectedCallback() {
79
- this.upgradeProperties("buffered", "groovePress", "modulationValue", "modulation", "orientation", "spring");
91
+ this.upgradeProperties("buffered", "groovePress", "modulationValue", "modulation", "orientation", "scrub", "spring");
80
92
  super.connectedCallback();
81
93
  }
82
94
  connect(signal) {
@@ -89,6 +101,18 @@ var SonicSlider = class extends SonicValueElement {
89
101
  slider.addEventListener("transitionend", (event) => {
90
102
  if (event.propertyName === "--_sonic-slider-value") this.toggleState("springing", false);
91
103
  }, { signal });
104
+ this.bindHover(slider, signal, {
105
+ canShow: () => this.scrub && this.readout && this.orientation === "horizontal",
106
+ place: () => {
107
+ this.#placeReadout(slider);
108
+ },
109
+ valueAt: (event) => {
110
+ const axis = this.#axis(slider, cap);
111
+ const at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);
112
+ const mapping = this.mapping();
113
+ return mapping.snap(mapping.valueAt(at));
114
+ }
115
+ });
92
116
  this.render();
93
117
  this.checkStyles(slider, "slider.css");
94
118
  this.bindGestures(slider, signal, (event) => {
@@ -103,12 +127,29 @@ var SonicSlider = class extends SonicValueElement {
103
127
  return this.orientation;
104
128
  }
105
129
  draw() {
106
- this.writeProportions(this.#slider, "slider", this.modulation);
130
+ this.#modulation.draw({
131
+ mapping: this.mapping(),
132
+ modulation: this.modulation,
133
+ origin: this.originValue(),
134
+ value: this.value
135
+ });
107
136
  this.#renderBuffered(this.#slider);
137
+ this.#renderScrub(this.#slider);
138
+ this.#placeReadout(this.#slider);
108
139
  }
109
140
  focusTarget() {
110
141
  return this.#slider;
111
142
  }
143
+ heldWrite(next) {
144
+ super.heldWrite(next);
145
+ if (this.scrub) this.render();
146
+ }
147
+ holdChanged() {
148
+ if (this.scrub) this.render();
149
+ }
150
+ scrubsKeyRepeat() {
151
+ return this.scrub;
152
+ }
112
153
  springTarget() {
113
154
  return this.spring ? this.originValue() : void 0;
114
155
  }
@@ -125,6 +166,13 @@ var SonicSlider = class extends SonicValueElement {
125
166
  travelPx: Math.max(1, box.width - capBox.width)
126
167
  };
127
168
  }
169
+ #placeReadout(slider) {
170
+ if (!this.scrub) {
171
+ slider.style.removeProperty("--_sonic-slider-readout-value");
172
+ return;
173
+ }
174
+ slider.style.setProperty("--_sonic-slider-readout-value", String(this.mapping().proportionOf(this.readoutValue())));
175
+ }
128
176
  #renderBuffered(slider) {
129
177
  const buffered = this.#buffered;
130
178
  if (buffered.length === 0) {
@@ -143,6 +191,22 @@ var SonicSlider = class extends SonicValueElement {
143
191
  });
144
192
  slider.style.setProperty("--_sonic-slider-buffered-regions", `linear-gradient(var(--_sonic-slider-toward), ${stops.join(", ")})`);
145
193
  }
194
+ #renderScrub(slider) {
195
+ const { style } = slider;
196
+ const { proportionOf } = this.mapping();
197
+ const isHeld = this.scrub && this.heldFrom() !== void 0;
198
+ const played = proportionOf(this.playback());
199
+ const scrubbed = proportionOf(this.value);
200
+ if (isHeld) style.setProperty("--_sonic-slider-played", String(played));
201
+ else style.removeProperty("--_sonic-slider-played");
202
+ if (played === scrubbed || !isHeld || !this.isRevealed()) {
203
+ style.removeProperty("--_sonic-slider-scrub-region");
204
+ return;
205
+ }
206
+ const from = grooveStop(Math.min(played, scrubbed));
207
+ const to = grooveStop(Math.max(played, scrubbed));
208
+ style.setProperty("--_sonic-slider-scrub-region", `linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`);
209
+ }
146
210
  };
147
211
 
148
212
  //#endregion