@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.
- package/dist/default/elements/display-element.js +14 -2
- package/dist/default/elements/slider.js +1 -1
- package/dist/default/elements/spectrum.js +1 -0
- package/dist/default/elements/value-element.js +22 -3
- package/dist/default/elements/wave-element.js +5 -26
- package/dist/default/elements/waveform.js +1 -0
- package/dist/default/elements/wavestrip.js +0 -1
- package/dist/default/lib/fill-watch.js +29 -0
- package/dist/default/lib/hover-preview.js +8 -3
- package/dist/dev/elements/display-element.d.ts +4 -1
- package/dist/dev/elements/display-element.js +14 -2
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/slider.js +1 -1
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +1 -0
- package/dist/dev/elements/spectrum.js +1 -0
- package/dist/dev/elements/spectrum.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +4 -1
- package/dist/dev/elements/value-element.js +22 -3
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.js +5 -26
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.js +1 -0
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.js +0 -1
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/lib/fill-watch.js +30 -0
- package/dist/dev/lib/fill-watch.js.map +1 -0
- package/dist/dev/lib/hover-preview.js +8 -3
- package/dist/dev/lib/hover-preview.js.map +1 -1
- package/dist/styles/envelope.css +1 -1
- package/dist/styles/led.css +1 -1
- package/dist/styles/material.css +24 -8
- package/dist/styles/spectrum.css +5 -0
- package/dist/styles/switch.css +42 -33
- package/dist/styles/waveform.css +21 -2
- package/dist/styles/wavestrip.css +9 -6
- package/dist/styles/xy.css +4 -4
- 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
|
-
|
|
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.
|
|
105
|
+
canShow: () => this.scrub && this.orientation === "horizontal",
|
|
106
106
|
place: () => {
|
|
107
107
|
this.#placeReadout(slider);
|
|
108
108
|
},
|
|
@@ -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.#
|
|
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.#
|
|
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.#
|
|
67
|
-
|
|
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>
|
|
@@ -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())
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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;
|
|
@@ -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
|
|
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;
|