@xsynaptic/sonic-ui 0.9.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 (39) hide show
  1. package/dist/default/elements/display-element.js +14 -2
  2. package/dist/default/elements/slider.js +1 -1
  3. package/dist/default/elements/spectrum.js +1 -0
  4. package/dist/default/elements/value-element.js +22 -3
  5. package/dist/default/elements/wave-element.js +5 -26
  6. package/dist/default/elements/waveform.js +1 -0
  7. package/dist/default/elements/wavestrip.js +0 -1
  8. package/dist/default/lib/fill-watch.js +29 -0
  9. package/dist/default/lib/hover-preview.js +8 -3
  10. package/dist/dev/elements/display-element.d.ts +4 -1
  11. package/dist/dev/elements/display-element.js +14 -2
  12. package/dist/dev/elements/display-element.js.map +1 -1
  13. package/dist/dev/elements/slider.js +1 -1
  14. package/dist/dev/elements/slider.js.map +1 -1
  15. package/dist/dev/elements/spectrum.d.ts +1 -0
  16. package/dist/dev/elements/spectrum.js +1 -0
  17. package/dist/dev/elements/spectrum.js.map +1 -1
  18. package/dist/dev/elements/value-element.d.ts +4 -1
  19. package/dist/dev/elements/value-element.js +22 -3
  20. package/dist/dev/elements/value-element.js.map +1 -1
  21. package/dist/dev/elements/wave-element.js +5 -26
  22. package/dist/dev/elements/wave-element.js.map +1 -1
  23. package/dist/dev/elements/waveform.js +1 -0
  24. package/dist/dev/elements/waveform.js.map +1 -1
  25. package/dist/dev/elements/wavestrip.js +0 -1
  26. package/dist/dev/elements/wavestrip.js.map +1 -1
  27. package/dist/dev/lib/fill-watch.js +30 -0
  28. package/dist/dev/lib/fill-watch.js.map +1 -0
  29. package/dist/dev/lib/hover-preview.js +8 -3
  30. package/dist/dev/lib/hover-preview.js.map +1 -1
  31. package/dist/styles/envelope.css +1 -1
  32. package/dist/styles/led.css +1 -1
  33. package/dist/styles/material.css +24 -8
  34. package/dist/styles/spectrum.css +5 -0
  35. package/dist/styles/switch.css +42 -33
  36. package/dist/styles/waveform.css +21 -2
  37. package/dist/styles/wavestrip.css +9 -6
  38. package/dist/styles/xy.css +4 -4
  39. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
1
  import { SonicElement } from "./sonic-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/display-element.ts
5
6
  var SonicDisplayElement = class extends SonicElement {
6
7
  static observedAttributes = [
7
8
  ...SonicElement.observedAttributes,
9
+ "fill",
8
10
  "max",
9
11
  "min"
10
12
  ];
@@ -15,6 +17,12 @@ var SonicDisplayElement = class extends SonicElement {
15
17
  this.#analyser = node ?? void 0;
16
18
  this.#surface?.requestFrame();
17
19
  }
20
+ get fill() {
21
+ return this.hasAttribute("fill");
22
+ }
23
+ set fill(isFilling) {
24
+ this.reflect("fill", isFilling);
25
+ }
18
26
  get max() {
19
27
  return this.numberAttribute("max", 0);
20
28
  }
@@ -30,11 +38,13 @@ var SonicDisplayElement = class extends SonicElement {
30
38
  #analyser;
31
39
  #buffer = /* @__PURE__ */ new Float32Array(0);
32
40
  #surface;
33
- attributeChangedCallback() {
41
+ #syncFill;
42
+ attributeChangedCallback(name) {
43
+ if (name === "fill") this.#syncFill?.(this.fill);
34
44
  this.#surface?.invalidate();
35
45
  }
36
46
  connectedCallback() {
37
- this.upgradeProperties("analyser", "max", "min");
47
+ this.upgradeProperties("analyser", "fill", "max", "min");
38
48
  super.connectedCallback();
39
49
  }
40
50
  push(frame) {
@@ -57,6 +67,8 @@ var SonicDisplayElement = class extends SonicElement {
57
67
  signal
58
68
  });
59
69
  this.checkStyles(control, this.sheet);
70
+ this.#syncFill = bindFill(control, this.sizeProperty, signal);
71
+ this.#syncFill(this.fill);
60
72
  }
61
73
  surface() {
62
74
  return this.#surface;
@@ -102,7 +102,7 @@ var SonicSlider = class extends SonicValueElement {
102
102
  if (event.propertyName === "--_sonic-slider-value") this.toggleState("springing", false);
103
103
  }, { signal });
104
104
  this.bindHover(slider, signal, {
105
- canShow: () => this.scrub && this.readout && this.orientation === "horizontal",
105
+ canShow: () => this.scrub && this.orientation === "horizontal",
106
106
  place: () => {
107
107
  this.#placeReadout(slider);
108
108
  },
@@ -119,6 +119,7 @@ var SonicSpectrum = class extends SonicDisplayElement {
119
119
  colours = colours;
120
120
  numbers = numbers;
121
121
  sheet = "spectrum.css";
122
+ sizeProperty = "--_sonic-spectrum-size";
122
123
  #columns;
123
124
  #isSettled = true;
124
125
  #lastFrameMs;
@@ -97,6 +97,12 @@ var SonicValueElement = class extends SonicFormElement {
97
97
  set doublePress(gesture) {
98
98
  this.reflect("double-press", gesture);
99
99
  }
100
+ get formatEntry() {
101
+ return this.#formatEntry;
102
+ }
103
+ set formatEntry(format) {
104
+ this.#formatEntry = format;
105
+ }
100
106
  get formatSpokenValue() {
101
107
  return this.#formatSpokenValue;
102
108
  }
@@ -111,6 +117,9 @@ var SonicValueElement = class extends SonicFormElement {
111
117
  this.#formatValue = format;
112
118
  this.render();
113
119
  }
120
+ get hoverValue() {
121
+ return this.#hover?.value();
122
+ }
114
123
  get keyStep() {
115
124
  return this.optionalNumberAttribute("key-step");
116
125
  }
@@ -208,6 +217,7 @@ var SonicValueElement = class extends SonicFormElement {
208
217
  asked: void 0,
209
218
  value: 0
210
219
  };
220
+ #formatEntry;
211
221
  #formatSpokenValue;
212
222
  #formatValue;
213
223
  #gestures;
@@ -237,7 +247,7 @@ var SonicValueElement = class extends SonicFormElement {
237
247
  this.render();
238
248
  }
239
249
  connectedCallback() {
240
- this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "spokenStep", "step", "taper", "positions", "detent", "formatValue", "formatSpokenValue", "keyStep", "parseValue", "value");
250
+ this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "spokenStep", "step", "taper", "positions", "detent", "formatEntry", "formatValue", "formatSpokenValue", "keyStep", "parseValue", "value");
241
251
  super.connectedCallback();
242
252
  }
243
253
  bindGestures(control, signal, grab) {
@@ -254,7 +264,7 @@ var SonicValueElement = class extends SonicFormElement {
254
264
  },
255
265
  doublePress: () => this.doublePress,
256
266
  entryId: `sonic-entry-${this.#instance}`,
257
- entryText: () => this.#parseValue ? this.valueText : String(this.#cell.value),
267
+ entryText: () => this.#entryText(),
258
268
  forwardNaming: (target, isNamed) => {
259
269
  this.forwardNaming(target, isNamed);
260
270
  },
@@ -281,14 +291,18 @@ var SonicValueElement = class extends SonicFormElement {
281
291
  }, control);
282
292
  this.#gestures.bind(signal, grab);
283
293
  }
284
- bindHover(control, signal, { canShow, place, valueAt }) {
294
+ bindHover(control, signal, { canShow = () => true, place, valueAt }) {
285
295
  this.#hover = bindHoverPreview(control, {
286
296
  canShow,
297
+ changed: () => {
298
+ this.dispatchEvent(new Event("sonic-hover", { bubbles: true }));
299
+ },
287
300
  dismiss: () => {
288
301
  this.render();
289
302
  },
290
303
  isTaken: () => this.#isEditing() || this.isRevealed(),
291
304
  show: () => {
305
+ if (!this.readout) return;
292
306
  this.renderReadout();
293
307
  place();
294
308
  },
@@ -376,6 +390,11 @@ var SonicValueElement = class extends SonicFormElement {
376
390
  travelPx(control, property) {
377
391
  return Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));
378
392
  }
393
+ #entryText() {
394
+ const value = this.#cell.value;
395
+ if (this.#formatEntry) return this.#formatEntry(value);
396
+ return this.#parseValue ? this.valueText : String(value);
397
+ }
379
398
  #isEditing() {
380
399
  return this.#gestures?.isEditing() === true;
381
400
  }
@@ -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>
@@ -87,7 +87,6 @@ var SonicWavestrip = class extends SonicWaveElement {
87
87
  const { control } = this;
88
88
  super.connect(signal);
89
89
  this.bindHover(control, signal, {
90
- canShow: () => this.readout,
91
90
  place: () => {
92
91
  this.#placeReadout();
93
92
  },
@@ -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
  };
@@ -6,6 +6,8 @@ export declare abstract class SonicDisplayElement<Colour extends string, Numeric
6
6
  static readonly observedAttributes: string[];
7
7
  get analyser(): AnalyserNode | undefined;
8
8
  set analyser(node: AnalyserNode | null | undefined);
9
+ get fill(): boolean;
10
+ set fill(isFilling: boolean);
9
11
  get max(): number;
10
12
  set max(decibels: number | undefined);
11
13
  get min(): number;
@@ -15,7 +17,8 @@ export declare abstract class SonicDisplayElement<Colour extends string, Numeric
15
17
  protected abstract readonly control: HTMLElement;
16
18
  protected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;
17
19
  protected abstract readonly sheet: string;
18
- attributeChangedCallback(): void;
20
+ protected abstract readonly sizeProperty: `--_sonic-${string}`;
21
+ attributeChangedCallback(name: string): void;
19
22
  connectedCallback(): void;
20
23
  push(frame: Float32Array): void;
21
24
  repaint(): void;
@@ -1,10 +1,12 @@
1
1
  import { SonicElement } from "./sonic-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/display-element.ts
5
6
  var SonicDisplayElement = class extends SonicElement {
6
7
  static observedAttributes = [
7
8
  ...SonicElement.observedAttributes,
9
+ "fill",
8
10
  "max",
9
11
  "min"
10
12
  ];
@@ -15,6 +17,12 @@ var SonicDisplayElement = class extends SonicElement {
15
17
  this.#analyser = node ?? void 0;
16
18
  this.#surface?.requestFrame();
17
19
  }
20
+ get fill() {
21
+ return this.hasAttribute("fill");
22
+ }
23
+ set fill(isFilling) {
24
+ this.reflect("fill", isFilling);
25
+ }
18
26
  get max() {
19
27
  return this.numberAttribute("max", 0);
20
28
  }
@@ -30,11 +38,13 @@ var SonicDisplayElement = class extends SonicElement {
30
38
  #analyser;
31
39
  #buffer = /* @__PURE__ */ new Float32Array(0);
32
40
  #surface;
33
- attributeChangedCallback() {
41
+ #syncFill;
42
+ attributeChangedCallback(name) {
43
+ if (name === "fill") this.#syncFill?.(this.fill);
34
44
  this.#surface?.invalidate();
35
45
  }
36
46
  connectedCallback() {
37
- this.upgradeProperties("analyser", "max", "min");
47
+ this.upgradeProperties("analyser", "fill", "max", "min");
38
48
  super.connectedCallback();
39
49
  }
40
50
  push(frame) {
@@ -57,6 +67,8 @@ var SonicDisplayElement = class extends SonicElement {
57
67
  signal
58
68
  });
59
69
  this.checkStyles(control, this.sheet);
70
+ this.#syncFill = bindFill(control, this.sizeProperty, signal);
71
+ this.#syncFill(this.fill);
60
72
  }
61
73
  surface() {
62
74
  return this.#surface;
@@ -1 +1 @@
1
- {"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [...SonicElement.observedAttributes, 'max', 'min'];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(): void {\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\tpush(frame: Float32Array): void {\n\t\tthis.receive(frame);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.checkStyles(control, this.sheet);\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;AAKA,IAAsB,sBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAAC,GAAG,aAAa;EAAoB;EAAO;CAAK;CAE/F,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,MAAc;EACjB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAc;EACjB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAYA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,2BAAiC;EAChC,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,OAAO,KAAK;EAC/C,MAAM,kBAAkB;CACzB;CAEA,KAAK,OAA2B;EAC/B,KAAK,QAAQ,KAAK;EAClB,KAAK,UAAU,aAAa;CAC7B;CAGA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACD,KAAK,YAAY,SAAS,KAAK,KAAK;CACrC;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
1
+ {"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { bindFill } from '#lib/fill-watch.ts';\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'fill',\n\t\t'max',\n\t\t'min',\n\t];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\t#syncFill: ((isFilling: boolean) => void) | undefined;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#syncFill?.(this.fill);\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'fill', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\tpush(frame: Float32Array): void {\n\t\tthis.receive(frame);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.checkStyles(control, this.sheet);\n\t\tthis.#syncFill = bindFill(control, this.sizeProperty, signal);\n\t\tthis.#syncFill(this.fill);\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;;AAMA,IAAsB,sBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAC7C,GAAG,aAAa;EAChB;EACA;EACA;CACD;CAEA,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,OAAgB;EACnB,OAAO,KAAK,aAAa,MAAM;CAChC;CAEA,IAAI,KAAK,WAAoB;EAC5B,KAAK,QAAQ,QAAQ,SAAS;CAC/B;CAGA,IAAI,MAAc;EACjB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAc;EACjB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAcA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA;CAEA,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,QAAQ,KAAK,YAAY,KAAK,IAAI;EAC/C,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,QAAQ,OAAO,KAAK;EACvD,MAAM,kBAAkB;CACzB;CAEA,KAAK,OAA2B;EAC/B,KAAK,QAAQ,KAAK;EAClB,KAAK,UAAU,aAAa;CAC7B;CAGA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACD,KAAK,YAAY,SAAS,KAAK,KAAK;EACpC,KAAK,YAAY,SAAS,SAAS,KAAK,cAAc,MAAM;EAC5D,KAAK,UAAU,KAAK,IAAI;CACzB;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
@@ -102,7 +102,7 @@ var SonicSlider = class extends SonicValueElement {
102
102
  if (event.propertyName === "--_sonic-slider-value") this.toggleState("springing", false);
103
103
  }, { signal });
104
104
  this.bindHover(slider, signal, {
105
- canShow: () => this.scrub && this.readout && this.orientation === "horizontal",
105
+ canShow: () => this.scrub && this.orientation === "horizontal",
106
106
  place: () => {
107
107
  this.#placeReadout(slider);
108
108
  },
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-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-slider-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 SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\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\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\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.readout && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override heldWrite(next: number): void {\n\t\tsuper.heldWrite(next);\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override holdChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst isHeld = this.scrub && this.heldFrom() !== undefined;\n\t\tconst played = proportionOf(this.playback());\n\t\tconst scrubbed = proportionOf(this.value);\n\n\t\tif (isHeld) style.setProperty('--_sonic-slider-played', String(played));\n\t\telse style.removeProperty('--_sonic-slider-played');\n\n\t\tif (played === scrubbed || !isHeld || !this.isRevealed()) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrubbed));\n\t\tconst to = grooveStop(Math.max(played, scrubbed));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-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-slider-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 SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\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\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\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override heldWrite(next: number): void {\n\t\tsuper.heldWrite(next);\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override holdChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst isHeld = this.scrub && this.heldFrom() !== undefined;\n\t\tconst played = proportionOf(this.playback());\n\t\tconst scrubbed = proportionOf(this.value);\n\n\t\tif (isHeld) style.setProperty('--_sonic-slider-played', String(played));\n\t\telse style.removeProperty('--_sonic-slider-played');\n\n\t\tif (played === scrubbed || !isHeld || !this.isRevealed()) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrubbed));\n\t\tconst to = grooveStop(Math.max(played, scrubbed));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -49,6 +49,7 @@ export declare class SonicSpectrum extends SonicDisplayElement<Colour, Numeric>
49
49
  readonly hotFrom: "--_sonic-spectrum-hot-from";
50
50
  };
51
51
  protected readonly sheet = "spectrum.css";
52
+ protected readonly sizeProperty = "--_sonic-spectrum-size";
52
53
  connectedCallback(): void;
53
54
  resetPeak(): void;
54
55
  protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>): void;
@@ -119,6 +119,7 @@ var SonicSpectrum = class extends SonicDisplayElement {
119
119
  colours = colours;
120
120
  numbers = numbers;
121
121
  sheet = "spectrum.css";
122
+ sizeProperty = "--_sonic-spectrum-size";
122
123
  #columns;
123
124
  #isSettled = true;
124
125
  #lastFrameMs;
@@ -1 +1 @@
1
- {"version":3,"file":"spectrum.js","names":[],"sources":["../../../src/elements/spectrum.ts"],"sourcesContent":["import type { SurfaceFrame, SurfaceLook, SurfaceSize } from '#lib/canvas-surface.ts';\nimport type { FrequencyAxis } from '#lib/spectrum-columns.ts';\n\nimport { SonicDisplayElement } from '#elements/display-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport {\n\tcolumnEdges,\n\tcolumnLevels,\n\tgridColumns,\n\tgridRows,\n\tlevelRows,\n\tloudestBin,\n\tzoneRows,\n} from '#lib/spectrum-columns.ts';\nimport { areSettled, createLevels, receiveLevels, stepLevels } from '#lib/spectrum-levels.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-spectrum': SonicSpectrum;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Numeric = keyof typeof numbers;\n\ntype Look = SurfaceLook<Colour, never, Numeric>;\n\ninterface SpectrumPeak {\n\tdecibels: number;\n\tfrequency: number;\n}\n\ninterface Scene extends SurfaceSize {\n\tlook: Look;\n\trange: { max: number; min: number };\n\trowOf: (level: number) => number;\n}\n\ninterface Columns {\n\tbars: Float64Array;\n\tedges: Float64Array;\n\tholds: Float64Array;\n\tkey: string;\n}\n\nconst colours = {\n\tclip: '--_sonic-spectrum-clip',\n\tgrid: '--_sonic-spectrum-grid',\n\thot: '--_sonic-spectrum-hot',\n\tlit: '--_sonic-spectrum-lit',\n} as const;\n\nconst numbers = {\n\tclipFrom: '--_sonic-spectrum-clip-from',\n\thotFrom: '--_sonic-spectrum-hot-from',\n} as const;\n\nconst defaultFrequencies: [number, number] = [20, 20_000];\nconst defaultSampleRate = 48_000;\n\nconst renderSpectrum = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-spectrum\">\n\t\t\t<canvas class=\"sonic-spectrum-canvas\"></canvas>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\n// One path, since a crossing filled twice would show brighter than its lines\nfunction paintGrid(\n\tcontext: CanvasRenderingContext2D,\n\t{ dpr, height, look, range, rowOf, width }: Scene,\n\t[frequencyMin, frequencyMax]: [number, number],\n): void {\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst columns = gridColumns({ frequencyMax, frequencyMin }, width);\n\tconst rows = gridRows(range, rowOf);\n\n\tcontext.beginPath();\n\tfor (const column of columns) context.rect(column, 0, line, height);\n\tfor (const row of rows) context.rect(0, row, width, line);\n\tcontext.fillStyle = look.colours.grid;\n\tcontext.fill();\n}\n\nfunction zoneFill(\n\tcontext: CanvasRenderingContext2D,\n\t{ height, look, rowOf }: Scene,\n): CanvasGradient {\n\tconst gradient = context.createLinearGradient(0, 0, 0, height);\n\tconst { clip, hot } = zoneRows(look.numbers, rowOf);\n\n\tgradient.addColorStop(clip / height, look.colours.clip);\n\tgradient.addColorStop(clip / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.lit);\n\n\treturn gradient;\n}\n\n// Whole device pixels, so nothing is antialiased\nfunction paintBars(context: CanvasRenderingContext2D, scene: Scene, bars: Float64Array): void {\n\tconst { height, rowOf } = scene;\n\tlet previous = height;\n\n\tcontext.beginPath();\n\tcontext.moveTo(0, height);\n\tfor (const [column, level] of bars.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tbars[column] = row;\n\t\tif (row === previous) continue;\n\n\t\tcontext.lineTo(column, previous);\n\t\tcontext.lineTo(column, row);\n\t\tprevious = row;\n\t}\n\tcontext.lineTo(bars.length, previous);\n\tcontext.lineTo(bars.length, height);\n\tcontext.closePath();\n\tcontext.fillStyle = zoneFill(context, scene);\n\tcontext.fill();\n}\n\nfunction paintHolds(\n\tcontext: CanvasRenderingContext2D,\n\t{ look, rowOf }: Scene,\n\t{ bars, holds }: { bars: Float64Array; holds: Float64Array },\n): void {\n\tcontext.beginPath();\n\tfor (const [column, level] of holds.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tif (row < (bars[column] ?? 0)) context.rect(column, row, 1, 1);\n\t}\n\tcontext.fillStyle = look.colours.lit;\n\tcontext.fill();\n}\n\nexport class SonicSpectrum extends SonicDisplayElement<Colour, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicDisplayElement.observedAttributes,\n\t\t'frequency-max',\n\t\t'frequency-min',\n\t\t'grid',\n\t\t'peak-hold',\n\t\t'sample-rate',\n\t];\n\n\tget frequencyMax(): number {\n\t\treturn this.numberAttribute('frequency-max', defaultFrequencies[1]);\n\t}\n\n\tset frequencyMax(hertz: number | undefined) {\n\t\tthis.reflect('frequency-max', hertz);\n\t}\n\n\tget frequencyMin(): number {\n\t\treturn this.numberAttribute('frequency-min', defaultFrequencies[0]);\n\t}\n\n\tset frequencyMin(hertz: number | undefined) {\n\t\tthis.reflect('frequency-min', hertz);\n\t}\n\n\tget grid(): 'none' | 'on' {\n\t\treturn this.getAttribute('grid') === 'none' ? 'none' : 'on';\n\t}\n\n\tset grid(grid: 'none' | 'on' | undefined) {\n\t\tthis.reflect('grid', grid);\n\t}\n\n\tget peak(): SpectrumPeak | undefined {\n\t\treturn this.#peak && { ...this.#peak };\n\t}\n\n\tget peakHold(): boolean {\n\t\treturn this.hasAttribute('peak-hold');\n\t}\n\n\tset peakHold(isHeld: boolean) {\n\t\tthis.reflect('peak-hold', isHeld);\n\t}\n\n\tget sampleRate(): number {\n\t\tconst sampleRate = this.numberAttribute('sample-rate', defaultSampleRate);\n\n\t\treturn sampleRate > 0 ? sampleRate : defaultSampleRate;\n\t}\n\n\tset sampleRate(hertz: number | undefined) {\n\t\tthis.reflect('sample-rate', hertz);\n\t}\n\n\tprotected readonly control = renderSpectrum();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-spectrum-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly numbers = numbers;\n\n\tprotected readonly sheet = 'spectrum.css';\n\n\t#columns: Columns | undefined;\n\n\t#isSettled = true;\n\n\t#lastFrameMs: number | undefined;\n\n\t#levels = createLevels(0);\n\n\t#peak: SpectrumPeak | undefined;\n\n\t#receivedRate: number | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('frequencyMax', 'frequencyMin', 'grid', 'peakHold', 'sampleRate');\n\t\tsuper.connectedCallback();\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = undefined;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tif (this.isDisabled()) {\n\t\t\tif (isDirty) this.#draw(context, look, size);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wasSettled = this.#isSettled;\n\n\t\tthis.#step(frameMs, look.isReducedMotion);\n\t\tif (isDirty || !wasSettled || !this.#isSettled) this.#draw(context, look, size);\n\t\tif (!this.#isSettled) this.surface()?.requestFrame();\n\t}\n\n\tprotected pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void {\n\t\tanalyser.getFloatFrequencyData(buffer);\n\t}\n\n\tprotected receive(frame: Float32Array): void {\n\t\tif (frame.length !== this.#levels.bars.length) this.#levels = createLevels(frame.length);\n\t\tthis.#receivedRate = this.analyser?.context.sampleRate ?? this.sampleRate;\n\t\treceiveLevels(this.#levels, frame, this.min);\n\n\t\tconst loudest = loudestBin(frame, this.#axis());\n\n\t\tif (loudest && loudest.decibels > (this.#peak?.decibels ?? -Infinity)) this.#peak = loudest;\n\t}\n\n\t#axis(): FrequencyAxis {\n\t\tconst [frequencyMin, frequencyMax] = this.#frequencies();\n\n\t\treturn {\n\t\t\tbinCount: this.#levels.bars.length,\n\t\t\tfrequencyMax,\n\t\t\tfrequencyMin,\n\t\t\t// The bars on show keep the rate they arrived at once the analyser is unset\n\t\t\tsampleRate: this.#receivedRate ?? this.sampleRate,\n\t\t};\n\t}\n\n\t#columnsFor(width: number): Columns {\n\t\tconst axis = this.#axis();\n\t\tconst key = [width, axis.binCount, axis.sampleRate, axis.frequencyMin, axis.frequencyMax].join(\n\t\t\t' ',\n\t\t);\n\n\t\tif (this.#columns?.key !== key) {\n\t\t\tthis.#columns = {\n\t\t\t\tbars: new Float64Array(width),\n\t\t\t\tedges: columnEdges(axis, width),\n\t\t\t\tholds: new Float64Array(width),\n\t\t\t\tkey,\n\t\t\t};\n\t\t}\n\n\t\treturn this.#columns;\n\t}\n\n\t#draw(context: CanvasRenderingContext2D, look: Look, size: SurfaceSize): void {\n\t\tconst scene = {\n\t\t\t...size,\n\t\t\tlook,\n\t\t\trange: { max: this.max, min: this.min },\n\t\t\trowOf: levelRows({ max: this.max, min: this.min }, size.height),\n\t\t};\n\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (this.grid === 'on') paintGrid(context, scene, this.#frequencies());\n\t\tif (this.isDisabled()) return;\n\n\t\tconst { bars, edges, holds } = this.#columnsFor(size.width);\n\t\tconst spread = { edges, floor: scene.range.min };\n\n\t\tcolumnLevels(bars, this.#levels.bars, spread);\n\t\tpaintBars(context, scene, bars);\n\t\tif (!this.peakHold) return;\n\n\t\tcolumnLevels(holds, this.#levels.holds, spread);\n\t\tpaintHolds(context, scene, { bars, holds });\n\t}\n\n\t#frequencies(): [number, number] {\n\t\tconst { frequencyMax, frequencyMin } = this;\n\n\t\treturn frequencyMin > 0 && frequencyMax > frequencyMin\n\t\t\t? [frequencyMin, frequencyMax]\n\t\t\t: defaultFrequencies;\n\t}\n\n\t#step(frameMs: number, isReducedMotion: boolean): void {\n\t\tconst floor = this.min;\n\n\t\tstepLevels(this.#levels, {\n\t\t\telapsedMs: Math.max(0, frameMs - (this.#lastFrameMs ?? frameMs)),\n\t\t\tfloor,\n\t\t\tisHeld: this.peakHold,\n\t\t\tisHoldStepped: isReducedMotion,\n\t\t\tnowMs: frameMs,\n\t\t});\n\t\tthis.#isSettled = areSettled(this.#levels, floor);\n\t\tthis.#lastFrameMs = this.#isSettled ? undefined : frameMs;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"spectrum.js","names":[],"sources":["../../../src/elements/spectrum.ts"],"sourcesContent":["import type { SurfaceFrame, SurfaceLook, SurfaceSize } from '#lib/canvas-surface.ts';\nimport type { FrequencyAxis } from '#lib/spectrum-columns.ts';\n\nimport { SonicDisplayElement } from '#elements/display-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport {\n\tcolumnEdges,\n\tcolumnLevels,\n\tgridColumns,\n\tgridRows,\n\tlevelRows,\n\tloudestBin,\n\tzoneRows,\n} from '#lib/spectrum-columns.ts';\nimport { areSettled, createLevels, receiveLevels, stepLevels } from '#lib/spectrum-levels.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-spectrum': SonicSpectrum;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Numeric = keyof typeof numbers;\n\ntype Look = SurfaceLook<Colour, never, Numeric>;\n\ninterface SpectrumPeak {\n\tdecibels: number;\n\tfrequency: number;\n}\n\ninterface Scene extends SurfaceSize {\n\tlook: Look;\n\trange: { max: number; min: number };\n\trowOf: (level: number) => number;\n}\n\ninterface Columns {\n\tbars: Float64Array;\n\tedges: Float64Array;\n\tholds: Float64Array;\n\tkey: string;\n}\n\nconst colours = {\n\tclip: '--_sonic-spectrum-clip',\n\tgrid: '--_sonic-spectrum-grid',\n\thot: '--_sonic-spectrum-hot',\n\tlit: '--_sonic-spectrum-lit',\n} as const;\n\nconst numbers = {\n\tclipFrom: '--_sonic-spectrum-clip-from',\n\thotFrom: '--_sonic-spectrum-hot-from',\n} as const;\n\nconst defaultFrequencies: [number, number] = [20, 20_000];\nconst defaultSampleRate = 48_000;\n\nconst renderSpectrum = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-spectrum\">\n\t\t\t<canvas class=\"sonic-spectrum-canvas\"></canvas>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\n// One path, since a crossing filled twice would show brighter than its lines\nfunction paintGrid(\n\tcontext: CanvasRenderingContext2D,\n\t{ dpr, height, look, range, rowOf, width }: Scene,\n\t[frequencyMin, frequencyMax]: [number, number],\n): void {\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst columns = gridColumns({ frequencyMax, frequencyMin }, width);\n\tconst rows = gridRows(range, rowOf);\n\n\tcontext.beginPath();\n\tfor (const column of columns) context.rect(column, 0, line, height);\n\tfor (const row of rows) context.rect(0, row, width, line);\n\tcontext.fillStyle = look.colours.grid;\n\tcontext.fill();\n}\n\nfunction zoneFill(\n\tcontext: CanvasRenderingContext2D,\n\t{ height, look, rowOf }: Scene,\n): CanvasGradient {\n\tconst gradient = context.createLinearGradient(0, 0, 0, height);\n\tconst { clip, hot } = zoneRows(look.numbers, rowOf);\n\n\tgradient.addColorStop(clip / height, look.colours.clip);\n\tgradient.addColorStop(clip / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.lit);\n\n\treturn gradient;\n}\n\n// Whole device pixels, so nothing is antialiased\nfunction paintBars(context: CanvasRenderingContext2D, scene: Scene, bars: Float64Array): void {\n\tconst { height, rowOf } = scene;\n\tlet previous = height;\n\n\tcontext.beginPath();\n\tcontext.moveTo(0, height);\n\tfor (const [column, level] of bars.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tbars[column] = row;\n\t\tif (row === previous) continue;\n\n\t\tcontext.lineTo(column, previous);\n\t\tcontext.lineTo(column, row);\n\t\tprevious = row;\n\t}\n\tcontext.lineTo(bars.length, previous);\n\tcontext.lineTo(bars.length, height);\n\tcontext.closePath();\n\tcontext.fillStyle = zoneFill(context, scene);\n\tcontext.fill();\n}\n\nfunction paintHolds(\n\tcontext: CanvasRenderingContext2D,\n\t{ look, rowOf }: Scene,\n\t{ bars, holds }: { bars: Float64Array; holds: Float64Array },\n): void {\n\tcontext.beginPath();\n\tfor (const [column, level] of holds.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tif (row < (bars[column] ?? 0)) context.rect(column, row, 1, 1);\n\t}\n\tcontext.fillStyle = look.colours.lit;\n\tcontext.fill();\n}\n\nexport class SonicSpectrum extends SonicDisplayElement<Colour, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicDisplayElement.observedAttributes,\n\t\t'frequency-max',\n\t\t'frequency-min',\n\t\t'grid',\n\t\t'peak-hold',\n\t\t'sample-rate',\n\t];\n\n\tget frequencyMax(): number {\n\t\treturn this.numberAttribute('frequency-max', defaultFrequencies[1]);\n\t}\n\n\tset frequencyMax(hertz: number | undefined) {\n\t\tthis.reflect('frequency-max', hertz);\n\t}\n\n\tget frequencyMin(): number {\n\t\treturn this.numberAttribute('frequency-min', defaultFrequencies[0]);\n\t}\n\n\tset frequencyMin(hertz: number | undefined) {\n\t\tthis.reflect('frequency-min', hertz);\n\t}\n\n\tget grid(): 'none' | 'on' {\n\t\treturn this.getAttribute('grid') === 'none' ? 'none' : 'on';\n\t}\n\n\tset grid(grid: 'none' | 'on' | undefined) {\n\t\tthis.reflect('grid', grid);\n\t}\n\n\tget peak(): SpectrumPeak | undefined {\n\t\treturn this.#peak && { ...this.#peak };\n\t}\n\n\tget peakHold(): boolean {\n\t\treturn this.hasAttribute('peak-hold');\n\t}\n\n\tset peakHold(isHeld: boolean) {\n\t\tthis.reflect('peak-hold', isHeld);\n\t}\n\n\tget sampleRate(): number {\n\t\tconst sampleRate = this.numberAttribute('sample-rate', defaultSampleRate);\n\n\t\treturn sampleRate > 0 ? sampleRate : defaultSampleRate;\n\t}\n\n\tset sampleRate(hertz: number | undefined) {\n\t\tthis.reflect('sample-rate', hertz);\n\t}\n\n\tprotected readonly control = renderSpectrum();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-spectrum-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly numbers = numbers;\n\n\tprotected readonly sheet = 'spectrum.css';\n\n\tprotected readonly sizeProperty = '--_sonic-spectrum-size';\n\n\t#columns: Columns | undefined;\n\n\t#isSettled = true;\n\n\t#lastFrameMs: number | undefined;\n\n\t#levels = createLevels(0);\n\n\t#peak: SpectrumPeak | undefined;\n\n\t#receivedRate: number | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('frequencyMax', 'frequencyMin', 'grid', 'peakHold', 'sampleRate');\n\t\tsuper.connectedCallback();\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = undefined;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tif (this.isDisabled()) {\n\t\t\tif (isDirty) this.#draw(context, look, size);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wasSettled = this.#isSettled;\n\n\t\tthis.#step(frameMs, look.isReducedMotion);\n\t\tif (isDirty || !wasSettled || !this.#isSettled) this.#draw(context, look, size);\n\t\tif (!this.#isSettled) this.surface()?.requestFrame();\n\t}\n\n\tprotected pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void {\n\t\tanalyser.getFloatFrequencyData(buffer);\n\t}\n\n\tprotected receive(frame: Float32Array): void {\n\t\tif (frame.length !== this.#levels.bars.length) this.#levels = createLevels(frame.length);\n\t\tthis.#receivedRate = this.analyser?.context.sampleRate ?? this.sampleRate;\n\t\treceiveLevels(this.#levels, frame, this.min);\n\n\t\tconst loudest = loudestBin(frame, this.#axis());\n\n\t\tif (loudest && loudest.decibels > (this.#peak?.decibels ?? -Infinity)) this.#peak = loudest;\n\t}\n\n\t#axis(): FrequencyAxis {\n\t\tconst [frequencyMin, frequencyMax] = this.#frequencies();\n\n\t\treturn {\n\t\t\tbinCount: this.#levels.bars.length,\n\t\t\tfrequencyMax,\n\t\t\tfrequencyMin,\n\t\t\t// The bars on show keep the rate they arrived at once the analyser is unset\n\t\t\tsampleRate: this.#receivedRate ?? this.sampleRate,\n\t\t};\n\t}\n\n\t#columnsFor(width: number): Columns {\n\t\tconst axis = this.#axis();\n\t\tconst key = [width, axis.binCount, axis.sampleRate, axis.frequencyMin, axis.frequencyMax].join(\n\t\t\t' ',\n\t\t);\n\n\t\tif (this.#columns?.key !== key) {\n\t\t\tthis.#columns = {\n\t\t\t\tbars: new Float64Array(width),\n\t\t\t\tedges: columnEdges(axis, width),\n\t\t\t\tholds: new Float64Array(width),\n\t\t\t\tkey,\n\t\t\t};\n\t\t}\n\n\t\treturn this.#columns;\n\t}\n\n\t#draw(context: CanvasRenderingContext2D, look: Look, size: SurfaceSize): void {\n\t\tconst scene = {\n\t\t\t...size,\n\t\t\tlook,\n\t\t\trange: { max: this.max, min: this.min },\n\t\t\trowOf: levelRows({ max: this.max, min: this.min }, size.height),\n\t\t};\n\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (this.grid === 'on') paintGrid(context, scene, this.#frequencies());\n\t\tif (this.isDisabled()) return;\n\n\t\tconst { bars, edges, holds } = this.#columnsFor(size.width);\n\t\tconst spread = { edges, floor: scene.range.min };\n\n\t\tcolumnLevels(bars, this.#levels.bars, spread);\n\t\tpaintBars(context, scene, bars);\n\t\tif (!this.peakHold) return;\n\n\t\tcolumnLevels(holds, this.#levels.holds, spread);\n\t\tpaintHolds(context, scene, { bars, holds });\n\t}\n\n\t#frequencies(): [number, number] {\n\t\tconst { frequencyMax, frequencyMin } = this;\n\n\t\treturn frequencyMin > 0 && frequencyMax > frequencyMin\n\t\t\t? [frequencyMin, frequencyMax]\n\t\t\t: defaultFrequencies;\n\t}\n\n\t#step(frameMs: number, isReducedMotion: boolean): void {\n\t\tconst floor = this.min;\n\n\t\tstepLevels(this.#levels, {\n\t\t\telapsedMs: Math.max(0, frameMs - (this.#lastFrameMs ?? frameMs)),\n\t\t\tfloor,\n\t\t\tisHeld: this.peakHold,\n\t\t\tisHoldStepped: isReducedMotion,\n\t\t\tnowMs: frameMs,\n\t\t});\n\t\tthis.#isSettled = areSettled(this.#levels, floor);\n\t\tthis.#lastFrameMs = this.#isSettled ? undefined : frameMs;\n\t}\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { ValueMapping } from "../lib/value-mapping.js";
3
3
  import { ValueAxis } from "./value-gestures.js";
4
4
  //#region src/elements/value-element.d.ts
5
5
  interface HoverBinding {
6
- canShow: () => boolean;
6
+ canShow?: () => boolean;
7
7
  place: () => void;
8
8
  valueAt: (event: PointerEvent) => number | undefined;
9
9
  }
@@ -18,10 +18,13 @@ export declare abstract class SonicValueElement extends SonicFormElement {
18
18
  set dimmed(isDimmed: boolean);
19
19
  get doublePress(): 'entry' | 'none' | 'reset';
20
20
  set doublePress(gesture: 'entry' | 'none' | 'reset' | undefined);
21
+ get formatEntry(): ((value: number) => string) | undefined;
22
+ set formatEntry(format: ((value: number) => string) | undefined);
21
23
  get formatSpokenValue(): ((value: number) => string) | undefined;
22
24
  set formatSpokenValue(format: ((value: number) => string) | undefined);
23
25
  get formatValue(): ((value: number) => string) | undefined;
24
26
  set formatValue(format: ((value: number) => string) | undefined);
27
+ get hoverValue(): number | undefined;
25
28
  get keyStep(): number | undefined;
26
29
  set keyStep(value: number | undefined);
27
30
  get max(): number;