@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
@@ -0,0 +1,301 @@
1
+ import { clamp } from "../lib/math.js";
2
+ import { requireChild } from "../lib/render.js";
3
+ import { bindDrag } from "../lib/pointer-drag.js";
4
+ import { ReadoutClaim, revealDelay } from "./readout-claim.js";
5
+ import { ValueEntry } from "./value-entry.js";
6
+ import { dragThresholdPx, startDrag, stepDrag } from "../lib/drag-step.js";
7
+ import { focusByPointer } from "../lib/focus-by-pointer.js";
8
+ import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
9
+ import { readPxProperty } from "../lib/read-px-property.js";
10
+ import { resetKeys } from "../lib/value-mapping.js";
11
+
12
+ //#region src/elements/value-gestures.ts
13
+ const detentZonePx = 8;
14
+ var ValueGestures = class {
15
+ #claim = new ReadoutClaim(() => {
16
+ this.#host.holdChanged();
17
+ });
18
+ #control;
19
+ #entry;
20
+ #host;
21
+ #isRevealed = false;
22
+ #keyScrub;
23
+ #pointerDrag;
24
+ constructor(host, control) {
25
+ this.#host = host;
26
+ this.#control = control;
27
+ }
28
+ bind(signal, grab) {
29
+ const control = this.#control;
30
+ const host = this.#host;
31
+ const entry = this.#bindEntry(signal);
32
+ this.#pointerDrag = bindDrag(control, {
33
+ cancel: (drag) => {
34
+ if (drag.isKeptOnCancel !== true) host.input(drag.fromValue);
35
+ },
36
+ grab: (event) => {
37
+ if (isMenuPress(event) || host.isDisabled() || entry.isOpen) return;
38
+ host.toggleState("springing", false);
39
+ if (event.pointerType === "touch") event.preventDefault();
40
+ focusByPointer(control);
41
+ if (isResetPress(event)) {
42
+ this.#reset();
43
+ return;
44
+ }
45
+ const fromValue = host.value();
46
+ const axis = grab(event);
47
+ if (!axis) return;
48
+ const drag = {
49
+ ...axis,
50
+ fromValue,
51
+ isOutside: false,
52
+ isRevealed: false,
53
+ pointerType: event.pointerType,
54
+ revealTimer: void 0,
55
+ state: this.#startDrag(axis, event)
56
+ };
57
+ drag.revealTimer = setTimeout(() => {
58
+ this.#reveal(drag);
59
+ }, revealDelay(control));
60
+ return drag;
61
+ },
62
+ lift: (drag, event) => {
63
+ const tap = drag.state.isEngaged ? void 0 : host.tapTarget();
64
+ if (tap !== void 0) {
65
+ entry.forgetPress();
66
+ this.#commit(tap);
67
+ return;
68
+ }
69
+ if (!entry.press(event) || host.springTarget() !== void 0) return;
70
+ const gesture = host.doublePress();
71
+ if (gesture === "reset") this.#reset();
72
+ else if (gesture === "entry") entry.open();
73
+ },
74
+ move: (drag, event) => {
75
+ this.#dragTo(drag, event);
76
+ },
77
+ release: (drag) => {
78
+ this.#releaseDrag(drag);
79
+ },
80
+ toggle: (isDragging) => {
81
+ host.toggleState("dragging", isDragging);
82
+ }
83
+ }, signal);
84
+ control.addEventListener("pointercancel", () => {
85
+ entry.forgetPress();
86
+ }, { signal });
87
+ this.#bindKeys(entry, signal);
88
+ signal.addEventListener("abort", () => {
89
+ this.#endKeyScrub();
90
+ this.#concealKeyReveal();
91
+ }, { once: true });
92
+ }
93
+ end() {
94
+ this.#entry?.close(false);
95
+ this.#pointerDrag?.end();
96
+ this.#endKeyScrub();
97
+ }
98
+ heldFrom() {
99
+ return this.#dragging()?.fromValue ?? this.#keyScrub?.fromValue;
100
+ }
101
+ isEditing() {
102
+ return this.#entry?.isOpen === true;
103
+ }
104
+ isHeld() {
105
+ return this.#dragging() !== void 0 || this.#keyScrub !== void 0;
106
+ }
107
+ isRevealed() {
108
+ return this.#claim.isRevealed;
109
+ }
110
+ pointerType() {
111
+ return this.#dragging()?.pointerType;
112
+ }
113
+ #bindEntry(signal) {
114
+ const control = this.#control;
115
+ const host = this.#host;
116
+ const input = requireChild(control, "input", HTMLInputElement);
117
+ input.id = host.entryId;
118
+ const entry = new ValueEntry(input, {
119
+ commit: (text) => {
120
+ this.#commit(host.parse(text));
121
+ },
122
+ text: host.entryText,
123
+ toggle: (isOpen) => {
124
+ if (isOpen) host.forwardNaming(input, true);
125
+ host.toggleState("editing", isOpen);
126
+ host.render();
127
+ }
128
+ });
129
+ this.#entry = entry;
130
+ entry.bind(control, signal);
131
+ return entry;
132
+ }
133
+ #bindKeys(entry, signal) {
134
+ const control = this.#control;
135
+ const host = this.#host;
136
+ control.addEventListener("keydown", (event) => {
137
+ if (host.isDisabled() || event.defaultPrevented || event.target !== control) return;
138
+ host.toggleState("springing", false);
139
+ if (event.key === "Enter") {
140
+ if (host.springTarget() !== void 0) return;
141
+ event.preventDefault();
142
+ entry.open();
143
+ return;
144
+ }
145
+ const next = this.#keyTarget(event.key);
146
+ if (next === void 0) return;
147
+ event.preventDefault();
148
+ this.#keyTo(event, next);
149
+ this.#claim.reveal("keys");
150
+ host.holdChanged();
151
+ }, { signal });
152
+ control.addEventListener("keyup", (event) => {
153
+ const isMeta = event.key === "Meta";
154
+ if (isMeta || event.key === this.#keyScrub?.key) this.#endKeyScrub();
155
+ if (isMeta && this.#dragging()) return;
156
+ if (isMeta || host.mapping().keyTarget(event.key, host.value()) !== void 0) this.#springBack();
157
+ }, { signal });
158
+ control.addEventListener("blur", () => {
159
+ this.#endKeyScrub();
160
+ this.#concealKeyReveal();
161
+ this.#springBack();
162
+ }, { signal });
163
+ }
164
+ #commit(next, direction = 0) {
165
+ const host = this.#host;
166
+ const landed = Number.isFinite(next) ? host.land(next, direction) : next;
167
+ if (host.input(landed)) host.dispatch("change");
168
+ }
169
+ #concealKeyReveal() {
170
+ if (this.#claim.conceal("keys")) this.#host.holdChanged();
171
+ }
172
+ #dragging() {
173
+ return this.#pointerDrag?.current();
174
+ }
175
+ #dragTo(drag, event) {
176
+ const host = this.#host;
177
+ const isOutside = drag.outside?.(event) === true;
178
+ const wasEngaged = drag.state.isEngaged;
179
+ const mapping = host.mapping();
180
+ const { state, value } = stepDrag(mapping, drag.state, {
181
+ isFine: event.shiftKey,
182
+ isOutside,
183
+ position: drag.position(event)
184
+ });
185
+ drag.state = this.#withinLimit(mapping, state);
186
+ if (value === void 0) return;
187
+ if (!wasEngaged) this.#reveal(drag);
188
+ const isCrossing = isOutside !== drag.isOutside;
189
+ if (isCrossing) {
190
+ drag.isOutside = isOutside;
191
+ host.toggleState("cancelling", isOutside);
192
+ }
193
+ host.input(isOutside ? drag.fromValue : value);
194
+ if (isCrossing) this.#renderDragReveal(drag);
195
+ }
196
+ #endKeyScrub() {
197
+ const host = this.#host;
198
+ const scrub = this.#keyScrub;
199
+ if (!scrub) return;
200
+ this.#keyScrub = void 0;
201
+ host.holdChanged();
202
+ if (host.value() !== scrub.fromValue) host.dispatch("change");
203
+ }
204
+ #keyTarget(key) {
205
+ const host = this.#host;
206
+ if (resetKeys.has(key)) return host.default();
207
+ return host.mapping().keyTarget(key, host.value(), host.keyStep());
208
+ }
209
+ #keyTo(event, next) {
210
+ const host = this.#host;
211
+ if (event.repeat && host.scrubsKeyRepeat()) {
212
+ this.#scrubKey(event.key, next);
213
+ return;
214
+ }
215
+ this.#endKeyScrub();
216
+ this.#commit(next, next > host.value() ? 1 : -1);
217
+ }
218
+ #releaseDrag(drag) {
219
+ const host = this.#host;
220
+ clearTimeout(drag.revealTimer);
221
+ this.#claim.conceal("drag");
222
+ host.toggleState("cancelling", false);
223
+ host.holdChanged();
224
+ this.#showRevealed(false);
225
+ if (host.value() !== drag.fromValue) host.dispatch("change");
226
+ this.#springBack();
227
+ }
228
+ #renderDragReveal(drag) {
229
+ const isRevealed = drag.isRevealed && !drag.isOutside;
230
+ if (isRevealed) this.#claim.reveal("drag");
231
+ else this.#claim.conceal("drag");
232
+ this.#host.holdChanged();
233
+ this.#showRevealed(isRevealed);
234
+ }
235
+ #reset() {
236
+ this.#commit(this.#host.default() ?? NaN);
237
+ }
238
+ #reveal(drag) {
239
+ if (drag.isRevealed || this.#dragging() !== drag) return;
240
+ drag.isRevealed = true;
241
+ this.#renderDragReveal(drag);
242
+ }
243
+ #scrubKey(key, next) {
244
+ const host = this.#host;
245
+ if (this.#keyScrub?.key !== key) {
246
+ this.#endKeyScrub();
247
+ this.#keyScrub = {
248
+ fromValue: host.value(),
249
+ key
250
+ };
251
+ }
252
+ host.input(next);
253
+ }
254
+ #showRevealed(isRevealed) {
255
+ if (isRevealed === this.#isRevealed) return;
256
+ this.#isRevealed = isRevealed;
257
+ this.#host.toggleState("revealed", isRevealed);
258
+ this.#host.dispatch("sonic-reveal");
259
+ }
260
+ #springBack() {
261
+ const host = this.#host;
262
+ const target = host.springTarget();
263
+ if (target === void 0) return;
264
+ if (host.mapping().snap(target) !== host.value()) host.toggleState("springing", true);
265
+ this.#commit(target);
266
+ }
267
+ #startDrag(axis, event) {
268
+ const host = this.#host;
269
+ const mapping = host.mapping();
270
+ const detent = host.detent();
271
+ const value = host.value();
272
+ const start = {
273
+ from: value,
274
+ position: axis.position(event),
275
+ proportion: axis.fromProportion ?? mapping.proportionOf(value),
276
+ thresholdPx: dragThresholdPx(event.pointerType),
277
+ travelPx: axis.travelPx
278
+ };
279
+ if (detent === void 0) return startDrag(mapping, start);
280
+ const zonePx = readPxProperty(getComputedStyle(this.#control), "--_sonic-detent-zone", detentZonePx);
281
+ return startDrag(mapping, {
282
+ ...start,
283
+ detent: {
284
+ value: detent,
285
+ zone: zonePx / axis.travelPx
286
+ }
287
+ });
288
+ }
289
+ #withinLimit(mapping, state) {
290
+ const limit = this.#host.limit();
291
+ if (!limit || mapping.isWrapping) return state;
292
+ const [low, high] = limit;
293
+ return {
294
+ ...state,
295
+ rawProportion: clamp(state.rawProportion, mapping.proportionOf(low), mapping.proportionOf(high))
296
+ };
297
+ }
298
+ };
299
+
300
+ //#endregion
301
+ export { ValueGestures };
@@ -2,19 +2,44 @@ import { SonicValueElement } from "./value-element.js";
2
2
  import { bindSurface } from "../lib/canvas-surface.js";
3
3
 
4
4
  //#region src/elements/wave-element.ts
5
+ function markerKey(marker) {
6
+ return marker ? [
7
+ marker.start,
8
+ marker.end,
9
+ marker.kind,
10
+ marker.label
11
+ ].join("|") : "";
12
+ }
5
13
  var SonicWaveElement = class extends SonicValueElement {
14
+ static observedAttributes = [...SonicValueElement.observedAttributes, "fill"];
15
+ get currentMarker() {
16
+ return this.#current ? { ...this.#current } : void 0;
17
+ }
18
+ get fill() {
19
+ return this.hasAttribute("fill");
20
+ }
21
+ set fill(isFilling) {
22
+ this.reflect("fill", isFilling);
23
+ }
6
24
  get markers() {
7
25
  return this.#markers.map((marker) => ({ ...marker }));
8
26
  }
9
27
  set markers(list) {
10
28
  this.#markers = [...list ?? []].filter((marker) => Number.isFinite(marker.start)).map((marker) => ({ ...marker })).toSorted((first, second) => first.start - second.start);
11
- if (this.isBound()) this.renderMarkers();
29
+ if (!this.isBound()) return;
30
+ this.renderMarkers();
31
+ this.render();
12
32
  }
13
- #heldAt;
33
+ #current;
34
+ #fillWatch;
14
35
  #markers = [];
15
36
  #surface;
37
+ attributeChangedCallback(name) {
38
+ super.attributeChangedCallback(name);
39
+ if (name === "fill") this.#watchFill();
40
+ }
16
41
  connectedCallback() {
17
- this.upgradeProperties("markers");
42
+ this.upgradeProperties("fill", "markers");
18
43
  super.connectedCallback();
19
44
  }
20
45
  repaint() {
@@ -38,16 +63,20 @@ var SonicWaveElement = class extends SonicValueElement {
38
63
  });
39
64
  this.render();
40
65
  this.checkStyles(control, this.sheet);
66
+ this.#watchFill();
67
+ signal.addEventListener("abort", () => {
68
+ this.#fillWatch?.disconnect();
69
+ this.#fillWatch = void 0;
70
+ }, { once: true });
41
71
  }
42
72
  focusTarget() {
43
73
  return this.control;
44
74
  }
45
75
  heldWrite(next) {
46
- this.#heldAt = next;
76
+ super.heldWrite(next);
47
77
  this.render();
48
78
  }
49
79
  holdChanged() {
50
- if (this.heldFrom() === void 0) this.#heldAt = void 0;
51
80
  this.render();
52
81
  }
53
82
  markerList() {
@@ -61,19 +90,40 @@ var SonicWaveElement = class extends SonicValueElement {
61
90
  return [Math.min(at, to), Math.max(at, to)];
62
91
  });
63
92
  }
64
- playback() {
65
- const from = this.heldFrom();
66
- return from === void 0 ? this.value : this.#heldAt ?? from;
67
- }
68
93
  proportionsChanged() {
69
94
  this.renderMarkers();
70
95
  }
71
96
  renderEmpty() {
72
97
  this.toggleState("empty", this.isEmpty());
73
98
  }
99
+ showCurrentMarker(seconds) {
100
+ const current = this.#markers.findLast((marker) => marker.start <= seconds);
101
+ if (markerKey(current) === markerKey(this.#current)) return;
102
+ this.#current = current;
103
+ this.dispatchEvent(new Event("sonic-marker", { bubbles: true }));
104
+ }
74
105
  surface() {
75
106
  return this.#surface;
76
107
  }
108
+ #watchFill() {
109
+ const control = this.control;
110
+ const isFilling = this.fill && this.isBound();
111
+ if (isFilling === (this.#fillWatch !== void 0)) return;
112
+ this.#fillWatch?.disconnect();
113
+ this.#fillWatch = void 0;
114
+ if (!isFilling) {
115
+ control.style.removeProperty(this.sizeProperty);
116
+ return;
117
+ }
118
+ this.#fillWatch = new ResizeObserver((entries) => {
119
+ const box = entries.at(-1)?.borderBoxSize[0];
120
+ if (!box) return;
121
+ requestAnimationFrame(() => {
122
+ if (this.#fillWatch) control.style.setProperty(this.sizeProperty, `${String(box.blockSize)}px`);
123
+ });
124
+ });
125
+ this.#fillWatch.observe(control);
126
+ }
77
127
  };
78
128
 
79
129
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { clamp } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
- import { readRegions } from "../lib/time-regions.js";
3
+ import { dueRegions, readRegions } from "../lib/time-regions.js";
4
4
  import { SonicWaveElement } from "./wave-element.js";
5
5
  import { frameTimeline } from "../lib/frame-timeline.js";
6
6
  import { isKind, writeKind } from "../lib/marker-band.js";
@@ -10,9 +10,10 @@ import { waveformBuckets } from "../lib/waveform-buckets.js";
10
10
 
11
11
  //#region src/elements/waveform.ts
12
12
  const colours = {
13
+ ends: "--_sonic-waveform-ends",
13
14
  grid: "--_sonic-waveform-grid",
14
- hatch: "--_sonic-waveform-hatch",
15
15
  marker: "--_sonic-waveform-marker",
16
+ placeholder: "--_sonic-waveform-placeholder",
16
17
  wave: "--_sonic-waveform-wave",
17
18
  waveEdge: "--_sonic-waveform-wave-edge"
18
19
  };
@@ -63,7 +64,7 @@ function paintHatch(context, scene, [from, to]) {
63
64
  context.lineTo(x + height, 0);
64
65
  }
65
66
  context.lineWidth = Math.max(1, Math.round(dpr));
66
- context.strokeStyle = scene.colours.hatch;
67
+ context.strokeStyle = scene.colours.ends;
67
68
  context.stroke();
68
69
  context.restore();
69
70
  }
@@ -83,7 +84,7 @@ function paintPlaceholders(context, scene) {
83
84
  context.lineTo(closing, waveY(closing));
84
85
  }
85
86
  context.lineWidth = Math.max(1, Math.round(dpr));
86
- context.strokeStyle = scene.colours.hatch;
87
+ context.strokeStyle = scene.colours.placeholder;
87
88
  context.stroke();
88
89
  }
89
90
  function waveFill(context, scene) {
@@ -111,8 +112,10 @@ function paintWave(context, scene, peaks) {
111
112
  var SonicWaveform = class extends SonicWaveElement {
112
113
  static observedAttributes = [
113
114
  ...SonicWaveElement.observedAttributes,
115
+ "pending-delay",
114
116
  "playing",
115
117
  "playback-rate",
118
+ "reduced-motion",
116
119
  "zoom"
117
120
  ];
118
121
  get peaks() {
@@ -127,9 +130,21 @@ var SonicWaveform = class extends SonicWaveElement {
127
130
  return this.#pending.map(([start, end]) => [start, end]);
128
131
  }
129
132
  set pending(regions) {
133
+ const listedMs = this.#pendingListedMs;
134
+ const nowMs = performance.now();
130
135
  this.#pending = regions ? readRegions(regions) : [];
136
+ this.#pendingListedMs = new Map(this.#pending.map((region) => {
137
+ const key = region.join(":");
138
+ return [key, listedMs.get(key) ?? nowMs];
139
+ }));
131
140
  this.surface()?.invalidate();
132
141
  }
142
+ get pendingDelay() {
143
+ return Math.max(0, this.numberAttribute("pending-delay", 0));
144
+ }
145
+ set pendingDelay(value) {
146
+ this.reflect("pending-delay", value);
147
+ }
133
148
  get playbackRate() {
134
149
  const playbackRate = this.numberAttribute("playback-rate", 1);
135
150
  return playbackRate > 0 ? playbackRate : 1;
@@ -150,11 +165,18 @@ var SonicWaveform = class extends SonicWaveElement {
150
165
  this.#readTime = read;
151
166
  this.render();
152
167
  }
168
+ get reducedMotion() {
169
+ return this.getAttribute("reduced-motion") === "scroll" ? "scroll" : "page";
170
+ }
171
+ set reducedMotion(motion) {
172
+ this.reflect("reduced-motion", motion);
173
+ }
153
174
  get requestPeaks() {
154
175
  return this.#requestPeaks;
155
176
  }
156
177
  set requestPeaks(request) {
157
178
  this.#requestPeaks = request;
179
+ this.#asked = void 0;
158
180
  this.surface()?.invalidate();
159
181
  }
160
182
  get zoom() {
@@ -169,6 +191,8 @@ var SonicWaveform = class extends SonicWaveElement {
169
191
  colours = colours;
170
192
  lengths = {};
171
193
  sheet = "waveform.css";
194
+ sizeProperty = "--_sonic-waveform-size";
195
+ #asked;
172
196
  #clock = createTrackingClock();
173
197
  #drawn;
174
198
  #ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
@@ -177,16 +201,19 @@ var SonicWaveform = class extends SonicWaveElement {
177
201
  #paintedKey = "";
178
202
  #peaks;
179
203
  #pending = [];
204
+ #pendingListedMs = /* @__PURE__ */ new Map();
180
205
  #playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
181
206
  #readTime;
182
207
  #requestPeaks;
183
208
  #rider = createLabelRider(this.control, {
184
209
  className: "sonic-waveform-label",
185
210
  colourProperty: "--_sonic-waveform-marker",
186
- insetProperty: "--_sonic-waveform-label-inset"
211
+ fadeProperty: "--_sonic-waveform-label-fade",
212
+ insetProperty: "--_sonic-waveform-label-inset",
213
+ parkProperty: "--_sonic-waveform-label-park"
187
214
  });
188
215
  connectedCallback() {
189
- this.upgradeProperties("peaks", "pending", "playing", "playbackRate", "readTime", "requestPeaks", "zoom");
216
+ this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom");
190
217
  super.connectedCallback();
191
218
  }
192
219
  connect(signal) {
@@ -197,13 +224,16 @@ var SonicWaveform = class extends SonicWaveElement {
197
224
  }, { signal });
198
225
  }
199
226
  draw() {
200
- this.surface()?.requestFrame();
227
+ const surface = this.surface();
228
+ surface?.requestFrame();
229
+ if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
201
230
  }
202
231
  isEmpty() {
203
232
  return !this.#peaks || this.#peaks.samples.length === 0;
204
233
  }
205
234
  paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
206
235
  const held = this.#held();
236
+ const pending = this.#duePending();
207
237
  const timeline = frameTimeline({
208
238
  clockSeconds: this.#clock.read(frameMs, {
209
239
  isPlaying: this.playing,
@@ -212,9 +242,10 @@ var SonicWaveform = class extends SonicWaveElement {
212
242
  }),
213
243
  frameMs,
214
244
  ...held ? { held } : {},
215
- isPaged: look.isReducedMotion,
245
+ isPaged: look.isReducedMotion && this.reducedMotion === "page",
216
246
  isPlaying: this.playing,
217
- pending: this.#pending,
247
+ isStill: look.isReducedMotion,
248
+ pending,
218
249
  range: [this.min, Math.max(this.min, this.max)],
219
250
  size,
220
251
  zoom: this.zoom
@@ -226,6 +257,7 @@ var SonicWaveform = class extends SonicWaveElement {
226
257
  };
227
258
  placeLine(this.#playhead, timeline.playheadAt);
228
259
  placeLine(this.#ghost, timeline.ghostAt);
260
+ this.showCurrentMarker(seconds);
229
261
  this.toggleState("pending", view.pending.length > 0);
230
262
  this.#rider.place({
231
263
  playheadSeconds: seconds,
@@ -239,7 +271,7 @@ var SonicWaveform = class extends SonicWaveElement {
239
271
  ...view,
240
272
  colours: look.colours
241
273
  }, isRebuilt);
242
- if (timeline.wanted) this.#requestPeaks?.(...timeline.wanted);
274
+ if (timeline.wanted) this.#ask(timeline.wanted);
243
275
  }
244
276
  if (timeline.isMoving) this.surface()?.requestFrame();
245
277
  }
@@ -253,6 +285,17 @@ var SonicWaveform = class extends SonicWaveElement {
253
285
  }]));
254
286
  this.surface()?.rebuild();
255
287
  }
288
+ #ask(wanted) {
289
+ const asked = this.#requestPeaks?.(...wanted);
290
+ if (!(asked instanceof Promise)) return;
291
+ this.#asked = asked;
292
+ this.#repaintAfter(asked);
293
+ }
294
+ #duePending() {
295
+ const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
296
+ if (due.length < this.#pending.length) this.surface()?.requestFrame();
297
+ return due;
298
+ }
256
299
  #grab() {
257
300
  const drawn = this.#drawn ?? {
258
301
  seconds: this.value,
@@ -291,7 +334,7 @@ var SonicWaveform = class extends SonicWaveElement {
291
334
  #paintScene(context, scene, isRebuilt) {
292
335
  const { dpr, height, pixelsPerSecond, startSeconds, width } = scene;
293
336
  const x = (seconds) => (seconds - startSeconds) * pixelsPerSecond;
294
- const { grid } = scene.colours;
337
+ const { ends, grid } = scene.colours;
295
338
  const line = Math.max(1, Math.round(dpr));
296
339
  const [opening, closing] = [x(this.min), x(Math.max(this.min, this.max))];
297
340
  const [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];
@@ -302,7 +345,7 @@ var SonicWaveform = class extends SonicWaveElement {
302
345
  if (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);
303
346
  paintPlaceholders(context, scene);
304
347
  if (this.#peaks) paintWave(context, scene, this.#peaks);
305
- context.fillStyle = grid;
348
+ context.fillStyle = ends;
306
349
  for (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);
307
350
  this.#paintMarkers(context, scene, isRebuilt);
308
351
  }
@@ -319,6 +362,12 @@ var SonicWaveform = class extends SonicWaveElement {
319
362
  for (const probe of probes) probe.remove();
320
363
  return new Map(kinds.map((kind, index) => [kind, read[index] ?? ""]));
321
364
  }
365
+ async #repaintAfter(asked) {
366
+ await Promise.allSettled([asked]);
367
+ if (this.#asked !== asked) return;
368
+ this.#asked = void 0;
369
+ this.surface()?.invalidate();
370
+ }
322
371
  #sourceSeconds() {
323
372
  return this.#readTime?.() ?? this.playback();
324
373
  }