@xsynaptic/sonic-ui 0.17.0 → 0.18.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/CHANGELOG.md +13 -0
- package/README.md +112 -19
- package/dist/default/dat.js +1 -0
- package/dist/default/elements/button.js +2 -0
- package/dist/default/elements/dial.js +2 -0
- package/dist/default/elements/display-element.js +9 -7
- package/dist/default/elements/envelope.js +31 -72
- package/dist/default/elements/field-gesture.js +81 -0
- package/dist/default/elements/meter.js +4 -0
- package/dist/default/elements/position-group.js +42 -26
- package/dist/default/elements/radio-group.js +59 -56
- package/dist/default/elements/scale.js +3 -3
- package/dist/default/elements/slider.js +11 -13
- package/dist/default/elements/split.js +14 -13
- package/dist/default/elements/switch.js +31 -46
- package/dist/default/elements/toggle.js +20 -44
- package/dist/default/elements/value-element.js +65 -91
- package/dist/default/elements/value-gestures.js +22 -22
- package/dist/default/elements/wave-element.js +9 -12
- package/dist/default/elements/waveform.js +42 -241
- package/dist/default/elements/wavestrip.js +10 -5
- package/dist/default/elements/xy.js +67 -112
- package/dist/default/lib/canvas-surface.js +25 -0
- package/dist/default/lib/crossfade.js +1 -0
- package/dist/default/lib/envelope-curve.js +1 -0
- package/dist/default/lib/peaks-request.js +44 -0
- package/dist/default/lib/positions.js +58 -0
- package/dist/default/lib/scrub.js +12 -0
- package/dist/default/lib/value-model.js +62 -0
- package/dist/default/lib/waveform-scene.js +108 -0
- package/dist/default/lib/zoom-gesture.js +97 -1
- package/dist/dev/dat.d.ts +1 -0
- package/dist/dev/dat.js +1 -0
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +2 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +2 -0
- package/dist/dev/elements/dial.js +2 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +5 -3
- package/dist/dev/elements/display-element.js +9 -7
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +31 -72
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +82 -0
- package/dist/dev/elements/field-gesture.js.map +1 -0
- package/dist/dev/elements/meter.d.ts +10 -5
- package/dist/dev/elements/meter.js +4 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/position-group.d.ts +12 -6
- package/dist/dev/elements/position-group.js +42 -26
- package/dist/dev/elements/position-group.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +11 -7
- package/dist/dev/elements/radio-group.js +59 -56
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/scale.js +3 -3
- package/dist/dev/elements/scale.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +4 -2
- package/dist/dev/elements/slider.js +11 -13
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/split.d.ts +1 -0
- package/dist/dev/elements/split.js +14 -13
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +2 -2
- package/dist/dev/elements/switch.js +31 -46
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +20 -44
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +5 -5
- package/dist/dev/elements/value-element.js +65 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +22 -22
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +6 -4
- package/dist/dev/elements/wave-element.js +9 -12
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +19 -21
- package/dist/dev/elements/waveform.js +42 -241
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +5 -1
- package/dist/dev/elements/wavestrip.js +10 -5
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +67 -112
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +2 -1
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +25 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +1 -0
- package/dist/dev/lib/crossfade.js +1 -0
- package/dist/dev/lib/crossfade.js.map +1 -1
- package/dist/dev/lib/envelope-curve.d.ts +1 -0
- package/dist/dev/lib/envelope-curve.js +1 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -1
- package/dist/dev/lib/peaks-request.d.ts +5 -0
- package/dist/dev/lib/peaks-request.js +45 -0
- package/dist/dev/lib/peaks-request.js.map +1 -0
- package/dist/dev/lib/positions.d.ts +7 -0
- package/dist/dev/lib/positions.js +59 -0
- package/dist/dev/lib/positions.js.map +1 -0
- package/dist/dev/lib/scrub.d.ts +8 -0
- package/dist/dev/lib/scrub.js +13 -0
- package/dist/dev/lib/scrub.js.map +1 -0
- package/dist/dev/lib/time-regions.d.ts +1 -0
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/dev/lib/units.d.ts +8 -0
- package/dist/dev/lib/value-model.js +63 -0
- package/dist/dev/lib/value-model.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +1 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -1
- package/dist/dev/lib/waveform-scene.d.ts +11 -0
- package/dist/dev/lib/waveform-scene.js +109 -0
- package/dist/dev/lib/waveform-scene.js.map +1 -0
- package/dist/dev/lib/zoom-gesture.js +97 -1
- package/dist/dev/lib/zoom-gesture.js.map +1 -1
- package/dist/styles/button.css +16 -6
- package/dist/styles/waveform.css +2 -1
- package/package.json +1 -1
- package/dist/default/lib/asked-value.js +0 -21
- package/dist/default/lib/fill-watch.js +0 -29
- package/dist/dev/lib/asked-value.js +0 -22
- package/dist/dev/lib/asked-value.js.map +0 -1
- package/dist/dev/lib/fill-watch.js +0 -30
- package/dist/dev/lib/fill-watch.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { clamp } from "./math.js";
|
|
2
|
+
import { capturePointer } from "./pointer-drag.js";
|
|
2
3
|
|
|
3
4
|
//#region src/lib/zoom-gesture.ts
|
|
4
5
|
const wheelRate = .01;
|
|
@@ -6,6 +7,11 @@ const linePx = 16;
|
|
|
6
7
|
const pixelMode = 0;
|
|
7
8
|
const wheelCapPx = 25;
|
|
8
9
|
const minSpanPx = 16;
|
|
10
|
+
const zoomKeys = /* @__PURE__ */ new Map([
|
|
11
|
+
["+", 1],
|
|
12
|
+
["-", -1],
|
|
13
|
+
["=", 1]
|
|
14
|
+
]);
|
|
9
15
|
const zoomKeyFactor = Math.exp(wheelCapPx * wheelRate);
|
|
10
16
|
function wheelFactor(deltaY, deltaMode) {
|
|
11
17
|
const pixels = deltaMode === pixelMode ? deltaY : deltaY * linePx;
|
|
@@ -14,6 +20,96 @@ function wheelFactor(deltaY, deltaMode) {
|
|
|
14
20
|
function pinchFactor(startSpanPx, spanPx) {
|
|
15
21
|
return Math.max(minSpanPx, Math.abs(spanPx)) / Math.max(minSpanPx, Math.abs(startSpanPx));
|
|
16
22
|
}
|
|
23
|
+
function bindPinch(control, options, signal) {
|
|
24
|
+
const touches = /* @__PURE__ */ new Map();
|
|
25
|
+
let pinch;
|
|
26
|
+
let isPinchSpent = false;
|
|
27
|
+
const spanPx = ([first, second]) => (touches.get(second) ?? 0) - (touches.get(first) ?? 0);
|
|
28
|
+
const touchPair = () => {
|
|
29
|
+
const [first, second, third] = touches.keys();
|
|
30
|
+
if (first === void 0 || second === void 0 || third !== void 0) return void 0;
|
|
31
|
+
return [first, second];
|
|
32
|
+
};
|
|
33
|
+
control.addEventListener("pointerdown", (event) => {
|
|
34
|
+
if (event.pointerType !== "touch" || !options.isZoomable() || options.isDisabled()) return;
|
|
35
|
+
touches.set(event.pointerId, event.clientX);
|
|
36
|
+
if (pinch || isPinchSpent) return;
|
|
37
|
+
const pointerIds = touchPair();
|
|
38
|
+
if (!pointerIds) return;
|
|
39
|
+
options.claim();
|
|
40
|
+
capturePointer(control, event.pointerId);
|
|
41
|
+
pinch = {
|
|
42
|
+
pointerIds,
|
|
43
|
+
startSpanPx: spanPx(pointerIds),
|
|
44
|
+
startZoom: options.zoom()
|
|
45
|
+
};
|
|
46
|
+
}, { signal });
|
|
47
|
+
control.addEventListener("pointermove", (event) => {
|
|
48
|
+
if (!touches.has(event.pointerId)) return;
|
|
49
|
+
touches.set(event.pointerId, event.clientX);
|
|
50
|
+
if (!pinch?.pointerIds.includes(event.pointerId)) return;
|
|
51
|
+
options.zoomTo(pinch.startZoom * pinchFactor(pinch.startSpanPx, spanPx(pinch.pointerIds)));
|
|
52
|
+
}, { signal });
|
|
53
|
+
for (const type of ["pointerup", "pointercancel"]) control.addEventListener(type, (event) => {
|
|
54
|
+
if (!touches.delete(event.pointerId)) return;
|
|
55
|
+
if (pinch?.pointerIds.includes(event.pointerId)) {
|
|
56
|
+
pinch = void 0;
|
|
57
|
+
isPinchSpent = true;
|
|
58
|
+
}
|
|
59
|
+
if (touches.size === 0) isPinchSpent = false;
|
|
60
|
+
}, { signal });
|
|
61
|
+
return {
|
|
62
|
+
isPinching: () => pinch !== void 0 || isPinchSpent,
|
|
63
|
+
isTouched: () => touches.size > 0
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function bindWheel(control, options, signal) {
|
|
67
|
+
control.addEventListener("wheel", (event) => {
|
|
68
|
+
if (!event.ctrlKey && !event.metaKey || options.isDisabled()) return;
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
options.zoomTo(options.zoom() * wheelFactor(event.deltaY, event.deltaMode));
|
|
71
|
+
}, {
|
|
72
|
+
passive: false,
|
|
73
|
+
signal
|
|
74
|
+
});
|
|
75
|
+
let startZoom = options.zoom();
|
|
76
|
+
for (const type of ["gesturestart", "gesturechange"]) control.addEventListener(type, (event) => {
|
|
77
|
+
if (options.isDisabled() || options.isTouched()) return;
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
if (type === "gesturestart") startZoom = options.zoom();
|
|
80
|
+
else if ("scale" in event && typeof event.scale === "number") options.zoomTo(startZoom * event.scale);
|
|
81
|
+
}, { signal });
|
|
82
|
+
}
|
|
83
|
+
function bindZoom(control, options, signal) {
|
|
84
|
+
const { isPinching, isTouched } = bindPinch(control, options, signal);
|
|
85
|
+
let wheel;
|
|
86
|
+
const sync = () => {
|
|
87
|
+
wheel?.abort();
|
|
88
|
+
wheel = void 0;
|
|
89
|
+
if (signal.aborted || !options.isZoomable()) return;
|
|
90
|
+
wheel = new AbortController();
|
|
91
|
+
bindWheel(control, {
|
|
92
|
+
...options,
|
|
93
|
+
isTouched
|
|
94
|
+
}, wheel.signal);
|
|
95
|
+
};
|
|
96
|
+
signal.addEventListener("abort", sync, { once: true });
|
|
97
|
+
sync();
|
|
98
|
+
return {
|
|
99
|
+
isPinching,
|
|
100
|
+
sync
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function bindZoomKeys(control, target, signal) {
|
|
104
|
+
control.addEventListener("keydown", (event) => {
|
|
105
|
+
if (!target.isZoomable() || target.isDisabled() || event.target !== control) return;
|
|
106
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
107
|
+
const direction = zoomKeys.get(event.key);
|
|
108
|
+
if (direction === void 0) return;
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
target.zoomTo(target.zoom() * zoomKeyFactor ** direction);
|
|
111
|
+
}, { signal });
|
|
112
|
+
}
|
|
17
113
|
|
|
18
114
|
//#endregion
|
|
19
|
-
export { pinchFactor, wheelFactor, zoomKeyFactor };
|
|
115
|
+
export { bindZoom, bindZoomKeys, pinchFactor, wheelFactor, zoomKeyFactor };
|
package/dist/dev/dat.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface DatHeader {
|
|
|
11
11
|
version: 1 | 2;
|
|
12
12
|
}
|
|
13
13
|
export declare function readDatHeader(buffer: ArrayBuffer): DatHeader;
|
|
14
|
+
/** An inclusive byte range for a `Range` header; `undefined` when the pairs asked for are empty */
|
|
14
15
|
export declare function datByteRange(header: DatHeader, fromPair: number, toPair: number): [number, number] | undefined;
|
|
15
16
|
export declare function readDatSamples(header: DatHeader, body: ArrayBuffer, byteOffset?: number): Int8Array | Int16Array;
|
|
16
17
|
export declare function readDatPeaks(header: DatHeader, body: ArrayBuffer, byteOffset?: number): WaveformPeaks;
|
package/dist/dev/dat.js
CHANGED
|
@@ -28,6 +28,7 @@ function readDatHeader(buffer) {
|
|
|
28
28
|
version
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
/** An inclusive byte range for a `Range` header; `undefined` when the pairs asked for are empty */
|
|
31
32
|
function datByteRange(header, fromPair, toPair) {
|
|
32
33
|
const from = clamp(Math.floor(fromPair), 0, header.pairs);
|
|
33
34
|
const to = clamp(Math.ceil(toPair), from, header.pairs);
|
package/dist/dev/dat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dat.js","names":[],"sources":["../../src/dat.ts"],"sourcesContent":["import type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { clamp } from '#lib/math.ts';\n\nexport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nexport interface DatHeader {\n\tbits: 8 | 16;\n\tchannels: number;\n\tdataOffset: number;\n\tpairs: number;\n\tpairsPerSecond: number;\n\tsampleRate: number;\n\tsamplesPerPixel: number;\n\tversion: 1 | 2;\n}\n\nfunction readVersion(view: DataView): 1 | 2 {\n\tconst version = view.getInt32(0, true);\n\tif (version === 1 || version === 2) return version;\n\n\tthrow new Error(`A .dat file of version ${String(version)} is not one this reads`);\n}\n\nexport function readDatHeader(buffer: ArrayBuffer): DatHeader {\n\tif (buffer.byteLength < 20) throw new Error('A .dat header takes at least 20 bytes');\n\n\tconst view = new DataView(buffer);\n\tconst version = readVersion(view);\n\tconst dataOffset = version === 1 ? 20 : 24;\n\tif (buffer.byteLength < dataOffset) {\n\t\tthrow new Error(`A version ${String(version)} .dat header takes ${String(dataOffset)} bytes`);\n\t}\n\n\tconst channels = version === 1 ? 1 : view.getInt32(20, true);\n\tif (channels < 1) throw new Error('A .dat header names no channels');\n\n\tconst sampleRate = view.getInt32(8, true);\n\tconst samplesPerPixel = view.getInt32(12, true);\n\tif (samplesPerPixel < 1) throw new Error('A .dat header names no samples per pixel');\n\n\treturn {\n\t\tbits: (view.getUint32(4, true) & 1) === 1 ? 8 : 16,\n\t\tchannels,\n\t\tdataOffset,\n\t\tpairs: view.getUint32(16, true),\n\t\tpairsPerSecond: sampleRate / samplesPerPixel,\n\t\tsampleRate,\n\t\tsamplesPerPixel,\n\t\tversion,\n\t};\n}\n\nexport function datByteRange(\n\theader: DatHeader,\n\tfromPair: number,\n\ttoPair: number,\n): [number, number] | undefined {\n\tconst from = clamp(Math.floor(fromPair), 0, header.pairs);\n\tconst to = clamp(Math.ceil(toPair), from, header.pairs);\n\t// A server ignores an inverted `Range` and sends the whole file\n\tif (!(to > from)) return undefined;\n\n\tconst pairBytes = (header.channels * 2 * header.bits) / 8;\n\n\treturn [header.dataOffset + from * pairBytes, header.dataOffset + to * pairBytes - 1];\n}\n\n// Typed arrays read the platform's byte order; every browser platform is little-endian, as the format is\n// A range response can end mid-sample; the odd byte is dropped\nexport function readDatSamples(\n\theader: DatHeader,\n\tbody: ArrayBuffer,\n\tbyteOffset = 0,\n): Int8Array | Int16Array {\n\treturn header.bits === 8\n\t\t? new Int8Array(body, byteOffset)\n\t\t: new Int16Array(body, byteOffset, Math.floor((body.byteLength - byteOffset) / 2));\n}\n\nexport function readDatPeaks(header: DatHeader, body: ArrayBuffer, byteOffset = 0): WaveformPeaks {\n\treturn {\n\t\tchannels: header.channels,\n\t\tfullScale: header.bits === 8 ? 128 : 32_768,\n\t\tpairsPerSecond: header.pairsPerSecond,\n\t\tsamples: readDatSamples(header, body, byteOffset),\n\t};\n}\n"],"mappings":";;;AAiBA,SAAS,YAAY,MAAuB;CAC3C,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI;CACrC,IAAI,YAAY,KAAK,YAAY,GAAG,OAAO;CAE3C,MAAM,IAAI,MAAM,0BAA0B,OAAO,OAAO,EAAE,uBAAuB;AAClF;AAEA,SAAgB,cAAc,QAAgC;CAC7D,IAAI,OAAO,aAAa,IAAI,MAAM,IAAI,MAAM,uCAAuC;CAEnF,MAAM,OAAO,IAAI,SAAS,MAAM;CAChC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,aAAa,YAAY,IAAI,KAAK;CACxC,IAAI,OAAO,aAAa,YACvB,MAAM,IAAI,MAAM,aAAa,OAAO,OAAO,EAAE,qBAAqB,OAAO,UAAU,EAAE,OAAO;CAG7F,MAAM,WAAW,YAAY,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI;CAC3D,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,iCAAiC;CAEnE,MAAM,aAAa,KAAK,SAAS,GAAG,IAAI;CACxC,MAAM,kBAAkB,KAAK,SAAS,IAAI,IAAI;CAC9C,IAAI,kBAAkB,GAAG,MAAM,IAAI,MAAM,0CAA0C;CAEnF,OAAO;EACN,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI,OAAO,IAAI,IAAI;EAChD;EACA;EACA,OAAO,KAAK,UAAU,IAAI,IAAI;EAC9B,gBAAgB,aAAa;EAC7B;EACA;EACA;CACD;AACD
|
|
1
|
+
{"version":3,"file":"dat.js","names":[],"sources":["../../src/dat.ts"],"sourcesContent":["import type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { clamp } from '#lib/math.ts';\n\nexport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nexport interface DatHeader {\n\tbits: 8 | 16;\n\tchannels: number;\n\tdataOffset: number;\n\tpairs: number;\n\tpairsPerSecond: number;\n\tsampleRate: number;\n\tsamplesPerPixel: number;\n\tversion: 1 | 2;\n}\n\nfunction readVersion(view: DataView): 1 | 2 {\n\tconst version = view.getInt32(0, true);\n\tif (version === 1 || version === 2) return version;\n\n\tthrow new Error(`A .dat file of version ${String(version)} is not one this reads`);\n}\n\nexport function readDatHeader(buffer: ArrayBuffer): DatHeader {\n\tif (buffer.byteLength < 20) throw new Error('A .dat header takes at least 20 bytes');\n\n\tconst view = new DataView(buffer);\n\tconst version = readVersion(view);\n\tconst dataOffset = version === 1 ? 20 : 24;\n\tif (buffer.byteLength < dataOffset) {\n\t\tthrow new Error(`A version ${String(version)} .dat header takes ${String(dataOffset)} bytes`);\n\t}\n\n\tconst channels = version === 1 ? 1 : view.getInt32(20, true);\n\tif (channels < 1) throw new Error('A .dat header names no channels');\n\n\tconst sampleRate = view.getInt32(8, true);\n\tconst samplesPerPixel = view.getInt32(12, true);\n\tif (samplesPerPixel < 1) throw new Error('A .dat header names no samples per pixel');\n\n\treturn {\n\t\tbits: (view.getUint32(4, true) & 1) === 1 ? 8 : 16,\n\t\tchannels,\n\t\tdataOffset,\n\t\tpairs: view.getUint32(16, true),\n\t\tpairsPerSecond: sampleRate / samplesPerPixel,\n\t\tsampleRate,\n\t\tsamplesPerPixel,\n\t\tversion,\n\t};\n}\n\n/** An inclusive byte range for a `Range` header; `undefined` when the pairs asked for are empty */\nexport function datByteRange(\n\theader: DatHeader,\n\tfromPair: number,\n\ttoPair: number,\n): [number, number] | undefined {\n\tconst from = clamp(Math.floor(fromPair), 0, header.pairs);\n\tconst to = clamp(Math.ceil(toPair), from, header.pairs);\n\t// A server ignores an inverted `Range` and sends the whole file\n\tif (!(to > from)) return undefined;\n\n\tconst pairBytes = (header.channels * 2 * header.bits) / 8;\n\n\treturn [header.dataOffset + from * pairBytes, header.dataOffset + to * pairBytes - 1];\n}\n\n// Typed arrays read the platform's byte order; every browser platform is little-endian, as the format is\n// A range response can end mid-sample; the odd byte is dropped\nexport function readDatSamples(\n\theader: DatHeader,\n\tbody: ArrayBuffer,\n\tbyteOffset = 0,\n): Int8Array | Int16Array {\n\treturn header.bits === 8\n\t\t? new Int8Array(body, byteOffset)\n\t\t: new Int16Array(body, byteOffset, Math.floor((body.byteLength - byteOffset) / 2));\n}\n\nexport function readDatPeaks(header: DatHeader, body: ArrayBuffer, byteOffset = 0): WaveformPeaks {\n\treturn {\n\t\tchannels: header.channels,\n\t\tfullScale: header.bits === 8 ? 128 : 32_768,\n\t\tpairsPerSecond: header.pairsPerSecond,\n\t\tsamples: readDatSamples(header, body, byteOffset),\n\t};\n}\n"],"mappings":";;;AAiBA,SAAS,YAAY,MAAuB;CAC3C,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI;CACrC,IAAI,YAAY,KAAK,YAAY,GAAG,OAAO;CAE3C,MAAM,IAAI,MAAM,0BAA0B,OAAO,OAAO,EAAE,uBAAuB;AAClF;AAEA,SAAgB,cAAc,QAAgC;CAC7D,IAAI,OAAO,aAAa,IAAI,MAAM,IAAI,MAAM,uCAAuC;CAEnF,MAAM,OAAO,IAAI,SAAS,MAAM;CAChC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,aAAa,YAAY,IAAI,KAAK;CACxC,IAAI,OAAO,aAAa,YACvB,MAAM,IAAI,MAAM,aAAa,OAAO,OAAO,EAAE,qBAAqB,OAAO,UAAU,EAAE,OAAO;CAG7F,MAAM,WAAW,YAAY,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI;CAC3D,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,iCAAiC;CAEnE,MAAM,aAAa,KAAK,SAAS,GAAG,IAAI;CACxC,MAAM,kBAAkB,KAAK,SAAS,IAAI,IAAI;CAC9C,IAAI,kBAAkB,GAAG,MAAM,IAAI,MAAM,0CAA0C;CAEnF,OAAO;EACN,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI,OAAO,IAAI,IAAI;EAChD;EACA;EACA,OAAO,KAAK,UAAU,IAAI,IAAI;EAC9B,gBAAgB,aAAa;EAC7B;EACA;EACA;CACD;AACD;;AAGA,SAAgB,aACf,QACA,UACA,QAC+B;CAC/B,MAAM,OAAO,MAAM,KAAK,MAAM,QAAQ,GAAG,GAAG,OAAO,KAAK;CACxD,MAAM,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,MAAM,OAAO,KAAK;CAEtD,IAAI,EAAE,KAAK,OAAO,OAAO;CAEzB,MAAM,YAAa,OAAO,WAAW,IAAI,OAAO,OAAQ;CAExD,OAAO,CAAC,OAAO,aAAa,OAAO,WAAW,OAAO,aAAa,KAAK,YAAY,CAAC;AACrF;AAIA,SAAgB,eACf,QACA,MACA,aAAa,GACY;CACzB,OAAO,OAAO,SAAS,IACpB,IAAI,UAAU,MAAM,UAAU,IAC9B,IAAI,WAAW,MAAM,YAAY,KAAK,OAAO,KAAK,aAAa,cAAc,CAAC,CAAC;AACnF;AAEA,SAAgB,aAAa,QAAmB,MAAmB,aAAa,GAAkB;CACjG,OAAO;EACN,UAAU,OAAO;EACjB,WAAW,OAAO,SAAS,IAAI,MAAM;EACrC,gBAAgB,OAAO;EACvB,SAAS,eAAe,QAAQ,MAAM,UAAU;CACjD;AACD"}
|
|
@@ -16,6 +16,7 @@ export declare class SonicButton extends SonicFormElement {
|
|
|
16
16
|
set defaultPressed(isPressed: boolean);
|
|
17
17
|
get latching(): boolean;
|
|
18
18
|
set latching(isLatching: boolean);
|
|
19
|
+
/** Shows the legend child whose `data-sonic-when` has this name */
|
|
19
20
|
get legend(): string | undefined;
|
|
20
21
|
set legend(name: string | undefined);
|
|
21
22
|
get momentary(): boolean;
|
|
@@ -24,6 +25,7 @@ export declare class SonicButton extends SonicFormElement {
|
|
|
24
25
|
set pressed(isPressed: boolean | undefined);
|
|
25
26
|
get softDisabled(): boolean;
|
|
26
27
|
set softDisabled(isSoftDisabled: boolean);
|
|
28
|
+
/** Submitted with the form while a latching button is pressed */
|
|
27
29
|
get value(): string;
|
|
28
30
|
set value(value: string | undefined);
|
|
29
31
|
attributeChangedCallback(name: string): void;
|
|
@@ -54,6 +54,7 @@ var SonicButton = class extends SonicFormElement {
|
|
|
54
54
|
set latching(isLatching) {
|
|
55
55
|
this.reflect("latching", isLatching);
|
|
56
56
|
}
|
|
57
|
+
/** Shows the legend child whose `data-sonic-when` has this name */
|
|
57
58
|
get legend() {
|
|
58
59
|
return this.getAttribute("legend") ?? void 0;
|
|
59
60
|
}
|
|
@@ -80,6 +81,7 @@ var SonicButton = class extends SonicFormElement {
|
|
|
80
81
|
set softDisabled(isSoftDisabled) {
|
|
81
82
|
this.reflect("soft-disabled", isSoftDisabled);
|
|
82
83
|
}
|
|
84
|
+
/** Submitted with the form while a latching button is pressed */
|
|
83
85
|
get value() {
|
|
84
86
|
return this.getAttribute("value") ?? "on";
|
|
85
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css', ['cap', 'well', 'keycap']);\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\t/** Shows the legend child whose `data-sonic-when` has this name */\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\t/** Submitted with the form while a latching button is pressed */\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css', ['cap', 'well', 'keycap']);\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -10,8 +10,10 @@ export declare class SonicDial extends SonicValueElement {
|
|
|
10
10
|
static readonly observedAttributes: string[];
|
|
11
11
|
get endless(): boolean;
|
|
12
12
|
set endless(isEndless: boolean);
|
|
13
|
+
/** How far a modulation source can push the value, in value units; signed */
|
|
13
14
|
get modulation(): number;
|
|
14
15
|
set modulation(amount: number | undefined);
|
|
16
|
+
/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */
|
|
15
17
|
get modulationValue(): number | undefined;
|
|
16
18
|
set modulationValue(value: null | number | undefined);
|
|
17
19
|
connectedCallback(): void;
|
|
@@ -37,12 +37,14 @@ var SonicDial = class extends SonicValueElement {
|
|
|
37
37
|
set endless(isEndless) {
|
|
38
38
|
this.reflect("endless", isEndless);
|
|
39
39
|
}
|
|
40
|
+
/** How far a modulation source can push the value, in value units; signed */
|
|
40
41
|
get modulation() {
|
|
41
42
|
return this.numberAttribute("modulation", 0);
|
|
42
43
|
}
|
|
43
44
|
set modulation(amount) {
|
|
44
45
|
this.reflect("modulation", amount);
|
|
45
46
|
}
|
|
47
|
+
/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */
|
|
46
48
|
get modulationValue() {
|
|
47
49
|
return this.#modulation.value();
|
|
48
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t['glass', '.sonic-dial-readout'],\n\t\t\t['arc', '.sonic-dial-ring'],\n\t\t]);\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC
|
|
1
|
+
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t['glass', '.sonic-dial-readout'],\n\t\t\t['arc', '.sonic-dial-ring'],\n\t\t]);\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;;CAIA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAIA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,YAAY;GAClC;GACA;GACA;GACA,CAAC,SAAS,qBAAqB;GAC/B,CAAC,OAAO,kBAAkB;EAC3B,CAAC;EACD,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SonicElement } from "./sonic-element.js";
|
|
2
|
+
import { Decibels } from "../lib/units.js";
|
|
2
3
|
import { Surface, SurfaceFrame } from "../lib/canvas-surface.js";
|
|
3
4
|
//#region src/elements/display-element.d.ts
|
|
4
5
|
export declare abstract class SonicDisplayElement<Colour extends string, Numeric extends string = never> extends SonicElement {
|
|
@@ -8,9 +9,9 @@ export declare abstract class SonicDisplayElement<Colour extends string, Numeric
|
|
|
8
9
|
set analyser(node: AnalyserNode | null | undefined);
|
|
9
10
|
get fill(): boolean;
|
|
10
11
|
set fill(isFilling: boolean);
|
|
11
|
-
get max():
|
|
12
|
+
get max(): Decibels;
|
|
12
13
|
set max(decibels: number | undefined);
|
|
13
|
-
get min():
|
|
14
|
+
get min(): Decibels;
|
|
14
15
|
set min(decibels: number | undefined);
|
|
15
16
|
protected abstract readonly canvas: HTMLCanvasElement;
|
|
16
17
|
protected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;
|
|
@@ -20,7 +21,8 @@ export declare abstract class SonicDisplayElement<Colour extends string, Numeric
|
|
|
20
21
|
protected abstract readonly sizeProperty: `--_sonic-${string}`;
|
|
21
22
|
attributeChangedCallback(name: string): void;
|
|
22
23
|
connectedCallback(): void;
|
|
23
|
-
|
|
24
|
+
/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */
|
|
25
|
+
push(decibels: Float32Array): void;
|
|
24
26
|
repaint(): void;
|
|
25
27
|
protected connect(signal: AbortSignal): void;
|
|
26
28
|
protected abstract paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void;
|
|
@@ -1,6 +1,5 @@
|
|
|
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";
|
|
4
3
|
|
|
5
4
|
//#region src/elements/display-element.ts
|
|
6
5
|
var SonicDisplayElement = class extends SonicElement {
|
|
@@ -38,17 +37,17 @@ var SonicDisplayElement = class extends SonicElement {
|
|
|
38
37
|
#analyser;
|
|
39
38
|
#buffer = /* @__PURE__ */ new Float32Array(0);
|
|
40
39
|
#surface;
|
|
41
|
-
#syncFill;
|
|
42
40
|
attributeChangedCallback(name) {
|
|
43
|
-
if (name === "fill") this.#
|
|
41
|
+
if (name === "fill") this.#surface?.setFill(this.fill);
|
|
44
42
|
this.#surface?.invalidate();
|
|
45
43
|
}
|
|
46
44
|
connectedCallback() {
|
|
47
45
|
this.upgradeProperties("analyser", "fill", "max", "min");
|
|
48
46
|
super.connectedCallback();
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */
|
|
49
|
+
push(decibels) {
|
|
50
|
+
this.receive(decibels);
|
|
52
51
|
this.#surface?.requestFrame();
|
|
53
52
|
}
|
|
54
53
|
repaint() {
|
|
@@ -60,6 +59,10 @@ var SonicDisplayElement = class extends SonicElement {
|
|
|
60
59
|
this.#surface = bindSurface({
|
|
61
60
|
canvas: this.canvas,
|
|
62
61
|
colours: this.colours,
|
|
62
|
+
fill: {
|
|
63
|
+
control,
|
|
64
|
+
sizeProperty: this.sizeProperty
|
|
65
|
+
},
|
|
63
66
|
numbers: this.numbers,
|
|
64
67
|
paint: (context, frame) => {
|
|
65
68
|
this.#paint(context, frame);
|
|
@@ -71,8 +74,7 @@ var SonicDisplayElement = class extends SonicElement {
|
|
|
71
74
|
"pane",
|
|
72
75
|
"well"
|
|
73
76
|
]);
|
|
74
|
-
this.#
|
|
75
|
-
this.#syncFill(this.fill);
|
|
77
|
+
this.#surface.setFill(this.fill);
|
|
76
78
|
}
|
|
77
79
|
surface() {
|
|
78
80
|
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';\
|
|
1
|
+
{"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Decibels } from '#lib/units.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 = [\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(): Decibels {\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(): Decibels {\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\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#surface?.setFill(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\t/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */\n\tpush(decibels: Float32Array): void {\n\t\tthis.receive(decibels);\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\tfill: { control, sizeProperty: this.sizeProperty },\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, ['glass', 'pane', 'well']);\n\t\tthis.#surface.setFill(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,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAcA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,IAAI;EACrD,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,QAAQ,OAAO,KAAK;EACvD,MAAM,kBAAkB;CACzB;;CAGA,KAAK,UAA8B;EAClC,KAAK,QAAQ,QAAQ;EACrB,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,MAAM;IAAE;IAAS,cAAc,KAAK;GAAa;GACjD,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACD,KAAK,YAAY,SAAS,KAAK,OAAO;GAAC;GAAS;GAAQ;EAAM,CAAC;EAC/D,KAAK,SAAS,QAAQ,KAAK,IAAI;CAChC;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,13 +1,11 @@
|
|
|
1
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
2
2
|
import { SonicElement } from "./sonic-element.js";
|
|
3
3
|
import { requireChild, template } from "../lib/render.js";
|
|
4
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
5
4
|
import { Readout } from "./readout.js";
|
|
6
|
-
import { ReadoutClaim
|
|
7
|
-
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
5
|
+
import { ReadoutClaim } from "./readout-claim.js";
|
|
8
6
|
import { SonicValueElement, linkValue } from "./value-element.js";
|
|
7
|
+
import { bindFieldGesture } from "./field-gesture.js";
|
|
9
8
|
import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
|
|
10
|
-
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
11
9
|
|
|
12
10
|
//#region src/elements/envelope.ts
|
|
13
11
|
const curveStages = [
|
|
@@ -22,12 +20,6 @@ const bindable = [
|
|
|
22
20
|
"release-curve"
|
|
23
21
|
];
|
|
24
22
|
const bindableNames = new Set(bindable);
|
|
25
|
-
function flip(point, isFlipped) {
|
|
26
|
-
return isFlipped ? {
|
|
27
|
-
...point,
|
|
28
|
-
y: -point.y
|
|
29
|
-
} : point;
|
|
30
|
-
}
|
|
31
23
|
function isParent(node) {
|
|
32
24
|
return "querySelector" in node;
|
|
33
25
|
}
|
|
@@ -104,7 +96,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
104
96
|
this.reflect("hold", id);
|
|
105
97
|
}
|
|
106
98
|
get pointerType() {
|
|
107
|
-
return this.#pointerType;
|
|
99
|
+
return this.#gesture?.pointerType();
|
|
108
100
|
}
|
|
109
101
|
get readout() {
|
|
110
102
|
return this.hasAttribute("readout");
|
|
@@ -139,13 +131,12 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
139
131
|
#curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
|
|
140
132
|
#fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
|
|
141
133
|
#floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
|
|
134
|
+
#gesture;
|
|
142
135
|
#graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
|
|
143
136
|
#handles = stageParts(this.#envelope, "sonic-envelope-handle", timeStages);
|
|
144
137
|
#held;
|
|
145
138
|
#isDriving = false;
|
|
146
139
|
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
147
|
-
#pointerDrag;
|
|
148
|
-
#pointerType;
|
|
149
140
|
#readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
|
|
150
141
|
#shape = adsrShape({});
|
|
151
142
|
#signal;
|
|
@@ -154,7 +145,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
154
145
|
attributeChangedCallback(name) {
|
|
155
146
|
if (!this.#signal) return;
|
|
156
147
|
if (name === "disabled") {
|
|
157
|
-
this.#
|
|
148
|
+
this.#gesture?.end();
|
|
158
149
|
this.#draw();
|
|
159
150
|
return;
|
|
160
151
|
}
|
|
@@ -183,13 +174,13 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
183
174
|
}, { once: true });
|
|
184
175
|
}
|
|
185
176
|
#bind() {
|
|
186
|
-
this.#
|
|
177
|
+
this.#gesture?.end();
|
|
187
178
|
this.#unbind();
|
|
188
179
|
for (const name of bindable) {
|
|
189
180
|
const element = this.#resolve(name);
|
|
190
181
|
if (!element) continue;
|
|
191
182
|
const link = linkValue(element);
|
|
192
|
-
const unwatch = link.watch(() => {
|
|
183
|
+
const unwatch = link.model.watch(() => {
|
|
193
184
|
if (!this.#isDriving) this.#draw();
|
|
194
185
|
});
|
|
195
186
|
this.#bindings.set(name, {
|
|
@@ -201,21 +192,23 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
201
192
|
this.#draw();
|
|
202
193
|
}
|
|
203
194
|
#bindPointer(envelope, signal) {
|
|
204
|
-
this.#
|
|
205
|
-
|
|
206
|
-
for (const { binding, from } of drag.drives) binding.link.input(from);
|
|
207
|
-
},
|
|
195
|
+
this.#gesture = bindFieldGesture(envelope, {
|
|
196
|
+
claim: this.#claim,
|
|
208
197
|
grab: (event) => this.#grab(event),
|
|
209
|
-
|
|
210
|
-
this.#
|
|
198
|
+
input: ({ drives }, next) => {
|
|
199
|
+
this.#isDriving = true;
|
|
200
|
+
for (const { axis, binding } of drives) {
|
|
201
|
+
const value = next[axis];
|
|
202
|
+
if (value !== void 0) binding.link.input(value);
|
|
203
|
+
}
|
|
204
|
+
this.#isDriving = false;
|
|
205
|
+
this.#draw();
|
|
211
206
|
},
|
|
212
|
-
release: (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (binding.link.value() === from) continue;
|
|
207
|
+
release: ({ drives }, moved) => {
|
|
208
|
+
for (const { axis, binding } of drives) {
|
|
209
|
+
if (!moved.includes(axis)) continue;
|
|
216
210
|
binding.element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
217
211
|
}
|
|
218
|
-
this.#pointerType = void 0;
|
|
219
212
|
},
|
|
220
213
|
toggle: (isDragging) => {
|
|
221
214
|
this.toggleState("dragging", isDragging);
|
|
@@ -278,57 +271,23 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
278
271
|
#grab(event) {
|
|
279
272
|
const pressed = this.#pressed(event);
|
|
280
273
|
if (!pressed || pressed.drives.length === 0) return void 0;
|
|
281
|
-
|
|
282
|
-
this.#pointerType = event.pointerType;
|
|
283
|
-
this.#claim.press(true, revealDelay(this.#envelope));
|
|
284
|
-
this.#held = part;
|
|
274
|
+
this.#held = pressed.part;
|
|
285
275
|
this.#drawBracket();
|
|
286
276
|
const box = this.#graph.getBoundingClientRect();
|
|
287
277
|
const travel = {
|
|
288
278
|
x: Math.max(1, box.width * this.#shape.share),
|
|
289
279
|
y: Math.max(1, box.height)
|
|
290
280
|
};
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const from = link.value();
|
|
296
|
-
return {
|
|
297
|
-
from,
|
|
298
|
-
mapping,
|
|
299
|
-
proportion: mapping.proportionOf(from),
|
|
300
|
-
travelPx: travel[axis]
|
|
301
|
-
};
|
|
281
|
+
const axes = {};
|
|
282
|
+
for (const { axis, binding } of pressed.drives) axes[axis] = {
|
|
283
|
+
model: binding.link.model,
|
|
284
|
+
travelPx: travel[axis]
|
|
302
285
|
};
|
|
303
286
|
return {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
from: drive.binding.link.value()
|
|
307
|
-
})),
|
|
308
|
-
isFlipped,
|
|
309
|
-
part,
|
|
310
|
-
state: startFieldDrag({
|
|
311
|
-
position: flip(pointerPosition(event), isFlipped),
|
|
312
|
-
thresholdPx: dragThresholdPx(event.pointerType),
|
|
313
|
-
x: start("x"),
|
|
314
|
-
y: start("y")
|
|
315
|
-
})
|
|
287
|
+
...pressed,
|
|
288
|
+
axes
|
|
316
289
|
};
|
|
317
290
|
}
|
|
318
|
-
#moveDrag(drag, event) {
|
|
319
|
-
const mappings = {};
|
|
320
|
-
for (const { axis, binding } of drag.drives) mappings[axis] = binding.link.mapping();
|
|
321
|
-
const step = stepFieldDrag(mappings, drag.state, flip(pointerMove(event), drag.isFlipped));
|
|
322
|
-
drag.state = step.state;
|
|
323
|
-
if (step.state.isEngaged) this.#claim.reveal("drag");
|
|
324
|
-
this.#isDriving = true;
|
|
325
|
-
for (const { axis, binding } of drag.drives) {
|
|
326
|
-
const next = step[axis];
|
|
327
|
-
if (next !== void 0) binding.link.input(next);
|
|
328
|
-
}
|
|
329
|
-
this.#isDriving = false;
|
|
330
|
-
this.#draw();
|
|
331
|
-
}
|
|
332
291
|
#pressed(event) {
|
|
333
292
|
const part = event.target instanceof Element ? event.target.closest(".sonic-envelope-handle, .sonic-envelope-curve") : void 0;
|
|
334
293
|
const stage = part?.dataset.sonicStage;
|
|
@@ -344,10 +303,10 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
344
303
|
const proportions = {};
|
|
345
304
|
const curves = {};
|
|
346
305
|
for (const stage of adsrStages) {
|
|
347
|
-
const
|
|
348
|
-
proportions[stage] =
|
|
306
|
+
const model = this.#bindings.get(stage)?.link.model;
|
|
307
|
+
proportions[stage] = model?.mapping().proportionOf(model.value);
|
|
349
308
|
}
|
|
350
|
-
for (const stage of curveStages) curves[stage] = this.#bindings.get(`${stage}-curve`)?.link.value
|
|
309
|
+
for (const stage of curveStages) curves[stage] = this.#bindings.get(`${stage}-curve`)?.link.model.value;
|
|
351
310
|
return adsrShape(proportions, curves);
|
|
352
311
|
}
|
|
353
312
|
#rebindOnce(key, arrives) {
|
|
@@ -359,7 +318,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
359
318
|
});
|
|
360
319
|
}
|
|
361
320
|
#renderReadout() {
|
|
362
|
-
const drag = this.#
|
|
321
|
+
const drag = this.#gesture?.current();
|
|
363
322
|
if (!drag) {
|
|
364
323
|
this.#readout.close();
|
|
365
324
|
return;
|