@xsynaptic/sonic-ui 0.8.0 → 0.10.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 (74) hide show
  1. package/README.md +0 -2
  2. package/dist/default/elements/dial.js +12 -3
  3. package/dist/default/elements/display-element.js +14 -2
  4. package/dist/default/elements/envelope.js +2 -2
  5. package/dist/default/elements/modulation.js +45 -0
  6. package/dist/default/elements/readout-claim.js +8 -3
  7. package/dist/default/elements/slider.js +16 -18
  8. package/dist/default/elements/spectrum.js +1 -0
  9. package/dist/default/elements/value-element.js +108 -330
  10. package/dist/default/elements/value-gestures.js +301 -0
  11. package/dist/default/elements/wave-element.js +5 -26
  12. package/dist/default/elements/waveform.js +6 -2
  13. package/dist/default/elements/wavestrip.js +5 -16
  14. package/dist/default/elements/xy.js +9 -20
  15. package/dist/default/lib/asked-value.js +21 -0
  16. package/dist/default/lib/canvas-surface.js +3 -0
  17. package/dist/default/lib/fill-watch.js +29 -0
  18. package/dist/default/lib/hover-preview.js +8 -3
  19. package/dist/default/lib/marker-rider.js +6 -3
  20. package/dist/default/lib/read-ms-property.js +10 -0
  21. package/dist/dev/elements/dial.js +12 -3
  22. package/dist/dev/elements/dial.js.map +1 -1
  23. package/dist/dev/elements/display-element.d.ts +4 -1
  24. package/dist/dev/elements/display-element.js +14 -2
  25. package/dist/dev/elements/display-element.js.map +1 -1
  26. package/dist/dev/elements/envelope.js +2 -2
  27. package/dist/dev/elements/envelope.js.map +1 -1
  28. package/dist/dev/elements/modulation.js +46 -0
  29. package/dist/dev/elements/modulation.js.map +1 -0
  30. package/dist/dev/elements/readout-claim.js +8 -3
  31. package/dist/dev/elements/readout-claim.js.map +1 -1
  32. package/dist/dev/elements/slider.d.ts +0 -1
  33. package/dist/dev/elements/slider.js +16 -18
  34. package/dist/dev/elements/slider.js.map +1 -1
  35. package/dist/dev/elements/spectrum.d.ts +1 -0
  36. package/dist/dev/elements/spectrum.js +1 -0
  37. package/dist/dev/elements/spectrum.js.map +1 -1
  38. package/dist/dev/elements/value-element.d.ts +11 -13
  39. package/dist/dev/elements/value-element.js +108 -330
  40. package/dist/dev/elements/value-element.js.map +1 -1
  41. package/dist/dev/elements/value-gestures.d.ts +10 -0
  42. package/dist/dev/elements/value-gestures.js +302 -0
  43. package/dist/dev/elements/value-gestures.js.map +1 -0
  44. package/dist/dev/elements/wave-element.js +5 -26
  45. package/dist/dev/elements/wave-element.js.map +1 -1
  46. package/dist/dev/elements/waveform.js +6 -2
  47. package/dist/dev/elements/waveform.js.map +1 -1
  48. package/dist/dev/elements/wavestrip.d.ts +0 -1
  49. package/dist/dev/elements/wavestrip.js +5 -16
  50. package/dist/dev/elements/wavestrip.js.map +1 -1
  51. package/dist/dev/elements/xy.js +9 -20
  52. package/dist/dev/elements/xy.js.map +1 -1
  53. package/dist/dev/lib/asked-value.js +22 -0
  54. package/dist/dev/lib/asked-value.js.map +1 -0
  55. package/dist/dev/lib/canvas-surface.d.ts +1 -0
  56. package/dist/dev/lib/canvas-surface.js +3 -0
  57. package/dist/dev/lib/canvas-surface.js.map +1 -1
  58. package/dist/dev/lib/fill-watch.js +30 -0
  59. package/dist/dev/lib/fill-watch.js.map +1 -0
  60. package/dist/dev/lib/hover-preview.js +8 -3
  61. package/dist/dev/lib/hover-preview.js.map +1 -1
  62. package/dist/dev/lib/marker-rider.js +6 -3
  63. package/dist/dev/lib/marker-rider.js.map +1 -1
  64. package/dist/dev/lib/read-ms-property.js +11 -0
  65. package/dist/dev/lib/read-ms-property.js.map +1 -0
  66. package/dist/styles/envelope.css +1 -1
  67. package/dist/styles/led.css +1 -1
  68. package/dist/styles/material.css +42 -8
  69. package/dist/styles/spectrum.css +5 -0
  70. package/dist/styles/switch.css +42 -33
  71. package/dist/styles/waveform.css +31 -2
  72. package/dist/styles/wavestrip.css +13 -7
  73. package/dist/styles/xy.css +4 -4
  74. package/package.json +1 -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 };
@@ -1,5 +1,6 @@
1
1
  import { SonicValueElement } from "./value-element.js";
2
2
  import { bindSurface } from "../lib/canvas-surface.js";
3
+ import { bindFill } from "../lib/fill-watch.js";
3
4
 
4
5
  //#region src/elements/wave-element.ts
5
6
  function markerKey(marker) {
@@ -31,12 +32,12 @@ var SonicWaveElement = class extends SonicValueElement {
31
32
  this.render();
32
33
  }
33
34
  #current;
34
- #fillWatch;
35
35
  #markers = [];
36
36
  #surface;
37
+ #syncFill;
37
38
  attributeChangedCallback(name) {
38
39
  super.attributeChangedCallback(name);
39
- if (name === "fill") this.#watchFill();
40
+ if (name === "fill") this.#syncFill?.(this.fill);
40
41
  }
41
42
  connectedCallback() {
42
43
  this.upgradeProperties("fill", "markers");
@@ -63,11 +64,8 @@ var SonicWaveElement = class extends SonicValueElement {
63
64
  });
64
65
  this.render();
65
66
  this.checkStyles(control, this.sheet);
66
- this.#watchFill();
67
- signal.addEventListener("abort", () => {
68
- this.#fillWatch?.disconnect();
69
- this.#fillWatch = void 0;
70
- }, { once: true });
67
+ this.#syncFill = bindFill(control, this.sizeProperty, signal);
68
+ this.#syncFill(this.fill);
71
69
  }
72
70
  focusTarget() {
73
71
  return this.control;
@@ -105,25 +103,6 @@ var SonicWaveElement = class extends SonicValueElement {
105
103
  surface() {
106
104
  return this.#surface;
107
105
  }
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
- }
127
106
  };
128
107
 
129
108
  //#endregion
@@ -25,6 +25,7 @@ const placeholderAmplitude = .2;
25
25
  const renderWaveform = template(`
26
26
  <div class="sonic-waveform" role="slider" tabindex="0">
27
27
  <canvas class="sonic-waveform-canvas" aria-hidden="true"></canvas>
28
+ <div class="sonic-waveform-scrim" aria-hidden="true"></div>
28
29
  <div class="sonic-waveform-ghost" aria-hidden="true" hidden></div>
29
30
  <div class="sonic-waveform-playhead" aria-hidden="true"></div>
30
31
  <div class="sonic-waveform-label" aria-hidden="true" hidden></div>
@@ -209,7 +210,8 @@ var SonicWaveform = class extends SonicWaveElement {
209
210
  className: "sonic-waveform-label",
210
211
  colourProperty: "--_sonic-waveform-marker",
211
212
  fadeProperty: "--_sonic-waveform-label-fade",
212
- insetProperty: "--_sonic-waveform-label-inset"
213
+ insetProperty: "--_sonic-waveform-label-inset",
214
+ parkProperty: "--_sonic-waveform-label-park"
213
215
  });
214
216
  connectedCallback() {
215
217
  this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom");
@@ -223,7 +225,9 @@ var SonicWaveform = class extends SonicWaveElement {
223
225
  }, { signal });
224
226
  }
225
227
  draw() {
226
- this.surface()?.requestFrame();
228
+ const surface = this.surface();
229
+ surface?.requestFrame();
230
+ if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
227
231
  }
228
232
  isEmpty() {
229
233
  return !this.#peaks || this.#peaks.samples.length === 0;
@@ -1,7 +1,6 @@
1
1
  import { clampProportion } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
3
  import { readPxProperty } from "../lib/read-px-property.js";
4
- import { bindHoverPreview } from "../lib/hover-preview.js";
5
4
  import { sortedRegions } from "../lib/time-regions.js";
6
5
  import { SonicWaveElement } from "./wave-element.js";
7
6
  import { createMarkerBand } from "../lib/marker-band.js";
@@ -77,7 +76,6 @@ var SonicWavestrip = class extends SonicWaveElement {
77
76
  sizeProperty = "--_sonic-wavestrip-size";
78
77
  #bars;
79
78
  #buffered = [];
80
- #hover;
81
79
  #markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
82
80
  #paintedKey = "";
83
81
  #peaks;
@@ -86,15 +84,10 @@ var SonicWavestrip = class extends SonicWaveElement {
86
84
  super.connectedCallback();
87
85
  }
88
86
  connect(signal) {
87
+ const { control } = this;
89
88
  super.connect(signal);
90
- this.#hover = bindHoverPreview(this.control, {
91
- canShow: () => this.readout,
92
- dismiss: () => {
93
- this.render();
94
- },
95
- isTaken: () => this.isEditing() || this.isRevealed(),
96
- show: () => {
97
- this.renderReadout();
89
+ this.bindHover(control, signal, {
90
+ place: () => {
98
91
  this.#placeReadout();
99
92
  },
100
93
  valueAt: (event) => {
@@ -103,12 +96,11 @@ var SonicWavestrip = class extends SonicWaveElement {
103
96
  const mapping = this.mapping();
104
97
  return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
105
98
  }
106
- }, signal);
107
- this.bindGestures(this.control, signal, (event) => this.#grab(event));
99
+ });
100
+ this.bindGestures(control, signal, (event) => this.#grab(event));
108
101
  }
109
102
  draw() {
110
103
  const surface = this.surface();
111
- if (this.isEditing() || this.isRevealed()) this.#hover?.clear();
112
104
  this.#placeReadout();
113
105
  this.showCurrentMarker(this.playback());
114
106
  if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
@@ -131,9 +123,6 @@ var SonicWavestrip = class extends SonicWaveElement {
131
123
  look.colours[kind]
132
124
  ]);
133
125
  }
134
- previewValue() {
135
- return this.#hover?.value();
136
- }
137
126
  renderMarkers() {
138
127
  this.#markerBand.render(this.markerList(), this.markerProportions());
139
128
  }
@@ -3,12 +3,13 @@ import { writeAttribute } from "../lib/write-attribute.js";
3
3
  import { SonicFormElement } from "./form-element.js";
4
4
  import { requireChild, template } from "../lib/render.js";
5
5
  import { bindDrag } from "../lib/pointer-drag.js";
6
- import { ReadoutClaim } from "./readout-claim.js";
7
6
  import { Readout } from "./readout.js";
7
+ import { ReadoutClaim, revealDelay } from "./readout-claim.js";
8
8
  import { dragThresholdPx } from "../lib/drag-step.js";
9
9
  import { focusByPointer } from "../lib/focus-by-pointer.js";
10
10
  import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
11
11
  import { resetKeys, valueMapping } from "../lib/value-mapping.js";
12
+ import { moveTo, resnap, setAsked } from "../lib/asked-value.js";
12
13
  import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
13
14
 
14
15
  //#region src/elements/xy.ts
@@ -337,7 +338,7 @@ var SonicXy = class extends SonicFormElement {
337
338
  x: mappings.x.valueAt(pressed.x),
338
339
  y: mappings.y.valueAt(pressed.y)
339
340
  });
340
- this.#claim.press(true);
341
+ this.#claim.press(true, revealDelay(this.#xy));
341
342
  const start = (axis) => ({
342
343
  from: this.#values[axis].value,
343
344
  mapping: mappings[axis],
@@ -359,7 +360,7 @@ var SonicXy = class extends SonicFormElement {
359
360
  }
360
361
  #input(next) {
361
362
  let hasMoved = false;
362
- for (const axis of axes) if (this.#set(axis, next[axis] ?? NaN)) hasMoved = true;
363
+ for (const axis of axes) if (moveTo(this.#values[axis], this.#mappings()[axis], next[axis] ?? NaN)) hasMoved = true;
363
364
  if (!hasMoved) return false;
364
365
  this.render();
365
366
  this.dispatchEvent(new Event("input", {
@@ -369,8 +370,9 @@ var SonicXy = class extends SonicFormElement {
369
370
  return true;
370
371
  }
371
372
  #keyTo(axis, key) {
372
- const next = this.#mappings()[axis].keyTarget(key, this.#values[axis].value);
373
- const hasMoved = this.#set(axis, next ?? NaN);
373
+ const mapping = this.#mappings()[axis];
374
+ const next = mapping.keyTarget(key, this.#values[axis].value);
375
+ const hasMoved = moveTo(this.#values[axis], mapping, next ?? NaN);
374
376
  this.#currentAxis = axis;
375
377
  this.render();
376
378
  this.#parts[axis].focus();
@@ -404,9 +406,8 @@ var SonicXy = class extends SonicFormElement {
404
406
  #refresh() {
405
407
  const mappings = this.#mappings();
406
408
  for (const axis of axes) {
407
- const axisValue = this.#values[axis];
408
409
  const mapping = mappings[axis];
409
- axisValue.value = mapping.snap(axisValue.asked ?? this.numberAttribute(axis, mapping.bounds[0]));
410
+ resnap(this.#values[axis], mapping, this.numberAttribute(axis, mapping.bounds[0]));
410
411
  }
411
412
  this.render();
412
413
  }
@@ -454,15 +455,6 @@ var SonicXy = class extends SonicFormElement {
454
455
  y: this.yDefault
455
456
  });
456
457
  }
457
- #set(axis, next) {
458
- if (!Number.isFinite(next)) return false;
459
- const axisValue = this.#values[axis];
460
- const snapped = this.#mappings()[axis].snap(next);
461
- axisValue.asked = snapped;
462
- if (snapped === axisValue.value) return false;
463
- axisValue.value = snapped;
464
- return true;
465
- }
466
458
  #taper(axis) {
467
459
  return this.getAttribute(`${axis}-taper`) === "log" ? "log" : "linear";
468
460
  }
@@ -477,10 +469,7 @@ var SonicXy = class extends SonicFormElement {
477
469
  this.#refresh();
478
470
  return;
479
471
  }
480
- const asked = toNumber(next);
481
- const hasMoved = this.#set(axis, asked);
482
- if (Number.isFinite(asked)) axisValue.asked = asked;
483
- if (hasMoved) this.render();
472
+ if (setAsked(axisValue, this.#mappings()[axis], toNumber(next))) this.render();
484
473
  }
485
474
  };
486
475
 
@@ -0,0 +1,21 @@
1
+ //#region src/lib/asked-value.ts
2
+ function moveTo(cell, mapping, next) {
3
+ if (!Number.isFinite(next)) return false;
4
+ const snapped = mapping.snap(next);
5
+ cell.asked = snapped;
6
+ if (snapped === cell.value) return false;
7
+ cell.value = snapped;
8
+ return true;
9
+ }
10
+ function setAsked(cell, mapping, next) {
11
+ if (!Number.isFinite(next)) return false;
12
+ const hasMoved = moveTo(cell, mapping, next);
13
+ cell.asked = next;
14
+ return hasMoved;
15
+ }
16
+ function resnap(cell, mapping, fallback) {
17
+ cell.value = mapping.snap(cell.asked ?? fallback);
18
+ }
19
+
20
+ //#endregion
21
+ export { moveTo, resnap, setAsked };
@@ -14,6 +14,9 @@ function readEach(properties, read) {
14
14
  return Object.fromEntries(Object.entries(properties).flatMap(([name, property]) => property === void 0 ? [] : [[name, read(property)]]));
15
15
  }
16
16
  var CanvasSurface = class {
17
+ get isVisible() {
18
+ return this.#isVisible;
19
+ }
17
20
  get size() {
18
21
  return this.#size;
19
22
  }
@@ -0,0 +1,29 @@
1
+ //#region src/lib/fill-watch.ts
2
+ function bindFill(control, sizeProperty, signal) {
3
+ let watch;
4
+ const stop = () => {
5
+ watch?.disconnect();
6
+ watch = void 0;
7
+ };
8
+ signal.addEventListener("abort", stop, { once: true });
9
+ return (isFilling) => {
10
+ if (signal.aborted || isFilling === (watch !== void 0)) return;
11
+ stop();
12
+ if (!isFilling) {
13
+ control.style.removeProperty(sizeProperty);
14
+ return;
15
+ }
16
+ const started = new ResizeObserver((entries) => {
17
+ const box = entries.at(-1)?.borderBoxSize[0];
18
+ if (!box) return;
19
+ requestAnimationFrame(() => {
20
+ if (watch === started) control.style.setProperty(sizeProperty, `${String(box.blockSize)}px`);
21
+ });
22
+ });
23
+ watch = started;
24
+ started.observe(control);
25
+ };
26
+ }
27
+
28
+ //#endregion
29
+ export { bindFill };
@@ -3,6 +3,11 @@ function bindHoverPreview(target, options, signal) {
3
3
  let hover;
4
4
  let isDismissed = false;
5
5
  let escapeWatch;
6
+ const hold = (value) => {
7
+ if (value === hover) return;
8
+ hover = value;
9
+ options.changed();
10
+ };
6
11
  const watchEscape = (isHovered) => {
7
12
  if (!isHovered) {
8
13
  escapeWatch?.abort();
@@ -13,14 +18,14 @@ function bindHoverPreview(target, options, signal) {
13
18
  escapeWatch = new AbortController();
14
19
  target.ownerDocument.addEventListener("keydown", (event) => {
15
20
  if (hover === void 0 || event.key !== "Escape") return;
16
- hover = void 0;
17
21
  isDismissed = true;
22
+ hold(void 0);
18
23
  options.dismiss();
19
24
  }, { signal: escapeWatch.signal });
20
25
  };
21
26
  const hoverAt = (value) => {
22
27
  if (value === void 0) isDismissed = false;
23
- if (!isDismissed && !options.isTaken()) hover = value;
28
+ if (!isDismissed && !options.isTaken()) hold(value);
24
29
  options.show();
25
30
  watchEscape(value !== void 0);
26
31
  };
@@ -36,7 +41,7 @@ function bindHoverPreview(target, options, signal) {
36
41
  }, { once: true });
37
42
  return {
38
43
  clear: () => {
39
- hover = void 0;
44
+ hold(void 0);
40
45
  },
41
46
  value: () => hover
42
47
  };
@@ -11,7 +11,7 @@ function parkedPlacement(labels, index, [view, arrivingX]) {
11
11
  const parked = labels[index];
12
12
  if (!parked) return void 0;
13
13
  const next = labels[index + 1];
14
- const x = Math.max(view.insetPx, lineX(parked.start, view) + view.insetPx);
14
+ const x = Math.max(view.parkPx, lineX(parked.start, view) + view.insetPx);
15
15
  const approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;
16
16
  return {
17
17
  index,
@@ -90,11 +90,12 @@ function measureWidths(control, className, texts) {
90
90
  return widths;
91
91
  }
92
92
  function createLabelRider(control, options) {
93
- const { className, colourProperty, fadeProperty, insetProperty } = options;
93
+ const { className, colourProperty, fadeProperty, insetProperty, parkProperty } = options;
94
94
  const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
95
95
  const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
96
96
  let fadeShare = .25;
97
97
  let insetPx = 0;
98
+ let parkPx = 0;
98
99
  let measured = [];
99
100
  return {
100
101
  measure: (labels) => {
@@ -102,6 +103,7 @@ function createLabelRider(control, options) {
102
103
  const styles = getComputedStyle(control);
103
104
  fadeShare = readPxProperty(styles, fadeProperty, .25);
104
105
  insetPx = readPxProperty(styles, insetProperty, 0);
106
+ parkPx = readPxProperty(styles, parkProperty, 0);
105
107
  measured = labels.map((label, index) => ({
106
108
  ...label,
107
109
  widthPx: widths[index] ?? 0
@@ -111,7 +113,8 @@ function createLabelRider(control, options) {
111
113
  const layout = layoutRider(measured, {
112
114
  ...window,
113
115
  fadeShare,
114
- insetPx
116
+ insetPx,
117
+ parkPx
115
118
  });
116
119
  writeLabel(parked, [measured, colourProperty], layout.parked);
117
120
  writeLabel(arriving, [measured, colourProperty], layout.arriving);
@@ -0,0 +1,10 @@
1
+ //#region src/lib/read-ms-property.ts
2
+ function readMsProperty(styles, property, fallback) {
3
+ const text = styles.getPropertyValue(property).trim();
4
+ const parsed = Number.parseFloat(text);
5
+ if (!Number.isFinite(parsed)) return fallback;
6
+ return text.endsWith("ms") ? parsed : parsed * 1e3;
7
+ }
8
+
9
+ //#endregion
10
+ export { readMsProperty };