@xsynaptic/sonic-ui 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 (52) hide show
  1. package/README.md +0 -2
  2. package/dist/default/elements/dial.js +12 -3
  3. package/dist/default/elements/envelope.js +2 -2
  4. package/dist/default/elements/modulation.js +45 -0
  5. package/dist/default/elements/readout-claim.js +8 -3
  6. package/dist/default/elements/slider.js +15 -17
  7. package/dist/default/elements/value-element.js +89 -330
  8. package/dist/default/elements/value-gestures.js +301 -0
  9. package/dist/default/elements/waveform.js +5 -2
  10. package/dist/default/elements/wavestrip.js +5 -15
  11. package/dist/default/elements/xy.js +9 -20
  12. package/dist/default/lib/asked-value.js +21 -0
  13. package/dist/default/lib/canvas-surface.js +3 -0
  14. package/dist/default/lib/marker-rider.js +6 -3
  15. package/dist/default/lib/read-ms-property.js +10 -0
  16. package/dist/dev/elements/dial.js +12 -3
  17. package/dist/dev/elements/dial.js.map +1 -1
  18. package/dist/dev/elements/envelope.js +2 -2
  19. package/dist/dev/elements/envelope.js.map +1 -1
  20. package/dist/dev/elements/modulation.js +46 -0
  21. package/dist/dev/elements/modulation.js.map +1 -0
  22. package/dist/dev/elements/readout-claim.js +8 -3
  23. package/dist/dev/elements/readout-claim.js.map +1 -1
  24. package/dist/dev/elements/slider.d.ts +0 -1
  25. package/dist/dev/elements/slider.js +15 -17
  26. package/dist/dev/elements/slider.js.map +1 -1
  27. package/dist/dev/elements/value-element.d.ts +8 -13
  28. package/dist/dev/elements/value-element.js +89 -330
  29. package/dist/dev/elements/value-element.js.map +1 -1
  30. package/dist/dev/elements/value-gestures.d.ts +10 -0
  31. package/dist/dev/elements/value-gestures.js +302 -0
  32. package/dist/dev/elements/value-gestures.js.map +1 -0
  33. package/dist/dev/elements/waveform.js +5 -2
  34. package/dist/dev/elements/waveform.js.map +1 -1
  35. package/dist/dev/elements/wavestrip.d.ts +0 -1
  36. package/dist/dev/elements/wavestrip.js +5 -15
  37. package/dist/dev/elements/wavestrip.js.map +1 -1
  38. package/dist/dev/elements/xy.js +9 -20
  39. package/dist/dev/elements/xy.js.map +1 -1
  40. package/dist/dev/lib/asked-value.js +22 -0
  41. package/dist/dev/lib/asked-value.js.map +1 -0
  42. package/dist/dev/lib/canvas-surface.d.ts +1 -0
  43. package/dist/dev/lib/canvas-surface.js +3 -0
  44. package/dist/dev/lib/canvas-surface.js.map +1 -1
  45. package/dist/dev/lib/marker-rider.js +6 -3
  46. package/dist/dev/lib/marker-rider.js.map +1 -1
  47. package/dist/dev/lib/read-ms-property.js +11 -0
  48. package/dist/dev/lib/read-ms-property.js.map +1 -0
  49. package/dist/styles/material.css +18 -0
  50. package/dist/styles/waveform.css +10 -0
  51. package/dist/styles/wavestrip.css +4 -1
  52. 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 };
@@ -209,7 +209,8 @@ var SonicWaveform = class extends SonicWaveElement {
209
209
  className: "sonic-waveform-label",
210
210
  colourProperty: "--_sonic-waveform-marker",
211
211
  fadeProperty: "--_sonic-waveform-label-fade",
212
- insetProperty: "--_sonic-waveform-label-inset"
212
+ insetProperty: "--_sonic-waveform-label-inset",
213
+ parkProperty: "--_sonic-waveform-label-park"
213
214
  });
214
215
  connectedCallback() {
215
216
  this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom");
@@ -223,7 +224,9 @@ var SonicWaveform = class extends SonicWaveElement {
223
224
  }, { signal });
224
225
  }
225
226
  draw() {
226
- this.surface()?.requestFrame();
227
+ const surface = this.surface();
228
+ surface?.requestFrame();
229
+ if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
227
230
  }
228
231
  isEmpty() {
229
232
  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,11 @@ 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, {
89
+ this.bindHover(control, signal, {
91
90
  canShow: () => this.readout,
92
- dismiss: () => {
93
- this.render();
94
- },
95
- isTaken: () => this.isEditing() || this.isRevealed(),
96
- show: () => {
97
- this.renderReadout();
91
+ place: () => {
98
92
  this.#placeReadout();
99
93
  },
100
94
  valueAt: (event) => {
@@ -103,12 +97,11 @@ var SonicWavestrip = class extends SonicWaveElement {
103
97
  const mapping = this.mapping();
104
98
  return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
105
99
  }
106
- }, signal);
107
- this.bindGestures(this.control, signal, (event) => this.#grab(event));
100
+ });
101
+ this.bindGestures(control, signal, (event) => this.#grab(event));
108
102
  }
109
103
  draw() {
110
104
  const surface = this.surface();
111
- if (this.isEditing() || this.isRevealed()) this.#hover?.clear();
112
105
  this.#placeReadout();
113
106
  this.showCurrentMarker(this.playback());
114
107
  if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
@@ -131,9 +124,6 @@ var SonicWavestrip = class extends SonicWaveElement {
131
124
  look.colours[kind]
132
125
  ]);
133
126
  }
134
- previewValue() {
135
- return this.#hover?.value();
136
- }
137
127
  renderMarkers() {
138
128
  this.#markerBand.render(this.markerList(), this.markerProportions());
139
129
  }
@@ -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
  }
@@ -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 };
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.readModulationValue();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.writeModulationValue(this.#dial, 'dial', value);\n\t}\n\n\treadonly #dial = renderDial();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.writeProportions(this.#dial, 'dial', this.endless ? 0 : this.modulation);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;AAUA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,oBAAoB;CACjC;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,qBAAqB,KAAK,OAAO,QAAQ,KAAK;CACpD;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,UAAU;EACjC,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,iBAAiB,KAAK,OAAO,QAAQ,KAAK,UAAU,IAAI,KAAK,UAAU;CAC7E;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
1
+ {"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,UAAU;EACjC,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
@@ -2,8 +2,8 @@ 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
4
  import { bindDrag } from "../lib/pointer-drag.js";
5
- import { ReadoutClaim } from "./readout-claim.js";
6
5
  import { Readout } from "./readout.js";
6
+ import { ReadoutClaim, revealDelay } from "./readout-claim.js";
7
7
  import { dragThresholdPx } from "../lib/drag-step.js";
8
8
  import { SonicValueElement, linkValue } from "./value-element.js";
9
9
  import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.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();