@xsynaptic/sonic-ui 0.6.1 → 0.8.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/README.md +4 -0
- package/dist/default/define/spectrum.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +30 -2
- package/dist/default/elements/display-element.js +77 -0
- package/dist/default/elements/envelope.js +1 -1
- package/dist/default/elements/slider.js +67 -1
- package/dist/default/elements/spectrum.js +230 -0
- package/dist/default/elements/value-element.js +49 -11
- package/dist/default/elements/wave-element.js +59 -9
- package/dist/default/elements/waveform.js +56 -10
- package/dist/default/elements/wavestrip.js +31 -45
- package/dist/default/elements/xy.js +1 -1
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +7 -4
- package/dist/default/lib/frame-timeline.js +2 -2
- package/dist/default/lib/hover-preview.js +46 -0
- package/dist/default/lib/marker-rider.js +7 -4
- package/dist/default/lib/pointer-drag.js +7 -2
- package/dist/default/lib/spectrum-columns.js +74 -0
- package/dist/default/lib/spectrum-levels.js +55 -0
- package/dist/default/lib/strip-scene.js +12 -1
- package/dist/default/lib/time-regions.js +5 -1
- package/dist/dev/define/spectrum.d.ts +2 -0
- package/dist/dev/define/spectrum.js +8 -0
- package/dist/dev/define/spectrum.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +30 -2
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +29 -0
- package/dist/dev/elements/display-element.js +78 -0
- package/dist/dev/elements/display-element.js.map +1 -0
- package/dist/dev/elements/envelope.js +1 -1
- package/dist/dev/elements/slider.d.ts +6 -0
- package/dist/dev/elements/slider.js +67 -1
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +59 -0
- package/dist/dev/elements/spectrum.js +231 -0
- package/dist/dev/elements/spectrum.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +5 -1
- package/dist/dev/elements/value-element.js +49 -11
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +8 -2
- package/dist/dev/elements/wave-element.js +59 -9
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +12 -4
- package/dist/dev/elements/waveform.js +56 -10
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +3 -0
- package/dist/dev/elements/wavestrip.js +31 -45
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +1 -1
- package/dist/dev/index.d.ts +4 -1
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/canvas-surface.d.ts +4 -3
- package/dist/dev/lib/canvas-surface.js +7 -4
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -2
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/hover-preview.js +47 -0
- package/dist/dev/lib/hover-preview.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +7 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +7 -2
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/spectrum-columns.js +75 -0
- package/dist/dev/lib/spectrum-columns.js.map +1 -0
- package/dist/dev/lib/spectrum-levels.js +56 -0
- package/dist/dev/lib/spectrum-levels.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +12 -1
- package/dist/dev/lib/strip-scene.js.map +1 -1
- package/dist/dev/lib/time-regions.js +5 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/styles/button.css +7 -1
- package/dist/styles/controls.css +1 -0
- package/dist/styles/material.css +18 -3
- package/dist/styles/meter.css +22 -18
- package/dist/styles/slider.css +42 -5
- package/dist/styles/spectrum.css +105 -0
- package/dist/styles/waveform.css +38 -16
- package/dist/styles/wavestrip.css +22 -4
- package/package.json +2 -1
|
@@ -63,8 +63,10 @@ var CanvasSurface = class {
|
|
|
63
63
|
this.#schedule();
|
|
64
64
|
}
|
|
65
65
|
#armTransitions() {
|
|
66
|
-
const {
|
|
67
|
-
style
|
|
66
|
+
const { canvas, colours, numbers = {} } = this.#options;
|
|
67
|
+
const { style } = canvas;
|
|
68
|
+
const watched = [...Object.values(colours), ...Object.values(numbers)];
|
|
69
|
+
style.setProperty("transition-property", watched.join(", "));
|
|
68
70
|
style.setProperty("transition-duration", "0.001ms");
|
|
69
71
|
style.setProperty("transition-timing-function", "step-start");
|
|
70
72
|
style.setProperty("transition-behavior", "allow-discrete");
|
|
@@ -148,12 +150,13 @@ var CanvasSurface = class {
|
|
|
148
150
|
}, { once: true });
|
|
149
151
|
}
|
|
150
152
|
#read() {
|
|
151
|
-
const { canvas, colours, lengths = {} } = this.#options;
|
|
153
|
+
const { canvas, colours, lengths = {}, numbers = {} } = this.#options;
|
|
152
154
|
const styles = getComputedStyle(canvas);
|
|
153
155
|
return {
|
|
154
156
|
colours: readEach(colours, (property) => styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color)),
|
|
155
157
|
isReducedMotion: this.#reducedMotion.matches,
|
|
156
|
-
lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0))
|
|
158
|
+
lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0)),
|
|
159
|
+
numbers: readEach(numbers, (property) => readPxProperty(styles, property, 0))
|
|
157
160
|
};
|
|
158
161
|
}
|
|
159
162
|
#rescale() {
|
|
@@ -20,8 +20,8 @@ function pendingIn({ pending, range }, window) {
|
|
|
20
20
|
return shown ? [shown] : [];
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
function placeholderPhase({ frameMs,
|
|
24
|
-
return !
|
|
23
|
+
function placeholderPhase({ frameMs, isStill }, pending) {
|
|
24
|
+
return !isStill && pending.length > 0 ? frameMs / placeholderPeriodMs : void 0;
|
|
25
25
|
}
|
|
26
26
|
function frameTimeline(input) {
|
|
27
27
|
const { clockSeconds, held, range, size } = input;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/lib/hover-preview.ts
|
|
2
|
+
function bindHoverPreview(target, options, signal) {
|
|
3
|
+
let hover;
|
|
4
|
+
let isDismissed = false;
|
|
5
|
+
let escapeWatch;
|
|
6
|
+
const watchEscape = (isHovered) => {
|
|
7
|
+
if (!isHovered) {
|
|
8
|
+
escapeWatch?.abort();
|
|
9
|
+
escapeWatch = void 0;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (escapeWatch) return;
|
|
13
|
+
escapeWatch = new AbortController();
|
|
14
|
+
target.ownerDocument.addEventListener("keydown", (event) => {
|
|
15
|
+
if (hover === void 0 || event.key !== "Escape") return;
|
|
16
|
+
hover = void 0;
|
|
17
|
+
isDismissed = true;
|
|
18
|
+
options.dismiss();
|
|
19
|
+
}, { signal: escapeWatch.signal });
|
|
20
|
+
};
|
|
21
|
+
const hoverAt = (value) => {
|
|
22
|
+
if (value === void 0) isDismissed = false;
|
|
23
|
+
if (!isDismissed && !options.isTaken()) hover = value;
|
|
24
|
+
options.show();
|
|
25
|
+
watchEscape(value !== void 0);
|
|
26
|
+
};
|
|
27
|
+
target.addEventListener("pointermove", (event) => {
|
|
28
|
+
if (event.pointerType === "touch" || event.buttons !== 0 || !options.canShow()) return;
|
|
29
|
+
hoverAt(options.valueAt(event));
|
|
30
|
+
}, { signal });
|
|
31
|
+
for (const type of ["pointerdown", "pointerleave"]) target.addEventListener(type, () => {
|
|
32
|
+
hoverAt(void 0);
|
|
33
|
+
}, { signal });
|
|
34
|
+
signal.addEventListener("abort", () => {
|
|
35
|
+
watchEscape(false);
|
|
36
|
+
}, { once: true });
|
|
37
|
+
return {
|
|
38
|
+
clear: () => {
|
|
39
|
+
hover = void 0;
|
|
40
|
+
},
|
|
41
|
+
value: () => hover
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { bindHoverPreview };
|
|
@@ -4,7 +4,6 @@ import { readPxProperty } from "./read-px-property.js";
|
|
|
4
4
|
import { writeKind } from "./marker-band.js";
|
|
5
5
|
|
|
6
6
|
//#region src/lib/marker-rider.ts
|
|
7
|
-
const fadeShare = .25;
|
|
8
7
|
function lineX(seconds, view) {
|
|
9
8
|
return (seconds - view.startSeconds) / view.windowSeconds * view.widthPx;
|
|
10
9
|
}
|
|
@@ -16,7 +15,7 @@ function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
|
16
15
|
const approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;
|
|
17
16
|
return {
|
|
18
17
|
index,
|
|
19
|
-
opacity: clamp(approachPx / (view.widthPx * fadeShare), 0, 1),
|
|
18
|
+
opacity: view.fadeShare > 0 ? clamp(approachPx / (view.widthPx * view.fadeShare), 0, 1) : 1,
|
|
20
19
|
shownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),
|
|
21
20
|
x
|
|
22
21
|
};
|
|
@@ -91,15 +90,18 @@ function measureWidths(control, className, texts) {
|
|
|
91
90
|
return widths;
|
|
92
91
|
}
|
|
93
92
|
function createLabelRider(control, options) {
|
|
94
|
-
const { className, colourProperty, insetProperty } = options;
|
|
93
|
+
const { className, colourProperty, fadeProperty, insetProperty } = options;
|
|
95
94
|
const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
|
|
96
95
|
const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
|
|
96
|
+
let fadeShare = .25;
|
|
97
97
|
let insetPx = 0;
|
|
98
98
|
let measured = [];
|
|
99
99
|
return {
|
|
100
100
|
measure: (labels) => {
|
|
101
101
|
const widths = measureWidths(control, className, labels.map(({ text }) => text));
|
|
102
|
-
|
|
102
|
+
const styles = getComputedStyle(control);
|
|
103
|
+
fadeShare = readPxProperty(styles, fadeProperty, .25);
|
|
104
|
+
insetPx = readPxProperty(styles, insetProperty, 0);
|
|
103
105
|
measured = labels.map((label, index) => ({
|
|
104
106
|
...label,
|
|
105
107
|
widthPx: widths[index] ?? 0
|
|
@@ -108,6 +110,7 @@ function createLabelRider(control, options) {
|
|
|
108
110
|
place: (window) => {
|
|
109
111
|
const layout = layoutRider(measured, {
|
|
110
112
|
...window,
|
|
113
|
+
fadeShare,
|
|
111
114
|
insetPx
|
|
112
115
|
});
|
|
113
116
|
writeLabel(parked, [measured, colourProperty], layout.parked);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
//#region src/lib/pointer-drag.ts
|
|
2
|
+
function capturePointer(target, pointerId) {
|
|
3
|
+
try {
|
|
4
|
+
target.setPointerCapture(pointerId);
|
|
5
|
+
} catch {}
|
|
6
|
+
}
|
|
2
7
|
function bindDrag(target, handlers, signal) {
|
|
3
8
|
let held;
|
|
4
9
|
const end = () => {
|
|
@@ -22,8 +27,8 @@ function bindDrag(target, handlers, signal) {
|
|
|
22
27
|
drag,
|
|
23
28
|
pointerId: event.pointerId
|
|
24
29
|
};
|
|
25
|
-
target.setPointerCapture(event.pointerId);
|
|
26
30
|
handlers.toggle?.(true);
|
|
31
|
+
capturePointer(target, event.pointerId);
|
|
27
32
|
}, { signal });
|
|
28
33
|
whileHeld("pointermove", handlers.move);
|
|
29
34
|
whileHeld("pointercancel", (drag) => {
|
|
@@ -43,4 +48,4 @@ function bindDrag(target, handlers, signal) {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
//#endregion
|
|
46
|
-
export { bindDrag };
|
|
51
|
+
export { bindDrag, capturePointer };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { linearTaper, logTaper } from "./taper.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/spectrum-columns.ts
|
|
4
|
+
const gridDecibels = 12;
|
|
5
|
+
function binHertz({ binCount, sampleRate }) {
|
|
6
|
+
return sampleRate / 2 / binCount;
|
|
7
|
+
}
|
|
8
|
+
function columnEdges(axis, width) {
|
|
9
|
+
const edges = new Float64Array(width + 1);
|
|
10
|
+
const taper = logTaper(axis.frequencyMin, axis.frequencyMax);
|
|
11
|
+
if (!taper) return edges;
|
|
12
|
+
const hertz = binHertz(axis);
|
|
13
|
+
for (let edge = 0; edge <= width; edge += 1) edges[edge] = taper.valueAt(edge / width) / hertz;
|
|
14
|
+
return edges;
|
|
15
|
+
}
|
|
16
|
+
function columnLevels(columns, levels, { edges, floor }) {
|
|
17
|
+
const levelAt = (bin) => bin < levels.length ? levels[bin] ?? floor : floor;
|
|
18
|
+
for (let column = 0; column < columns.length; column += 1) {
|
|
19
|
+
const from = edges[column] ?? 0;
|
|
20
|
+
const to = edges[column + 1] ?? from;
|
|
21
|
+
const first = Math.ceil(from);
|
|
22
|
+
const last = Math.ceil(to) - 1;
|
|
23
|
+
if (first > last) {
|
|
24
|
+
const below = levelAt(first - 1);
|
|
25
|
+
columns[column] = below + (levelAt(first) - below) * ((from + to) / 2 - (first - 1));
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let loudest = floor;
|
|
29
|
+
for (let bin = first; bin <= last && bin < levels.length; bin += 1) loudest = Math.max(loudest, levelAt(bin));
|
|
30
|
+
columns[column] = loudest;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function levelRows({ max, min }, height) {
|
|
34
|
+
const taper = linearTaper(min, max);
|
|
35
|
+
return (level) => Math.round((1 - taper.proportionOf(level)) * height);
|
|
36
|
+
}
|
|
37
|
+
function gridColumns({ frequencyMax, frequencyMin }, width) {
|
|
38
|
+
const taper = logTaper(frequencyMin, frequencyMax);
|
|
39
|
+
const columns = [];
|
|
40
|
+
if (!taper) return columns;
|
|
41
|
+
for (let exponent = Math.floor(Math.log10(frequencyMin)) + 1; 10 ** exponent < frequencyMax; exponent += 1) columns.push(Math.round(taper.proportionOf(10 ** exponent) * width));
|
|
42
|
+
return columns;
|
|
43
|
+
}
|
|
44
|
+
function gridRows({ max, min }, rowOf) {
|
|
45
|
+
const rows = [];
|
|
46
|
+
for (let level = (Math.floor(min / gridDecibels) + 1) * gridDecibels; level < max; level += gridDecibels) rows.push(rowOf(level));
|
|
47
|
+
return rows;
|
|
48
|
+
}
|
|
49
|
+
function zoneRows({ clipFrom, hotFrom }, rowOf) {
|
|
50
|
+
const clip = rowOf(clipFrom);
|
|
51
|
+
return {
|
|
52
|
+
clip,
|
|
53
|
+
hot: Math.max(clip, rowOf(hotFrom))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function loudestBin(frame, axis) {
|
|
57
|
+
const hertz = binHertz(axis);
|
|
58
|
+
const last = Math.min(Math.floor(axis.frequencyMax / hertz), frame.length - 1);
|
|
59
|
+
let loudest = -1;
|
|
60
|
+
let decibels = -Infinity;
|
|
61
|
+
for (let bin = Math.ceil(axis.frequencyMin / hertz); bin <= last; bin += 1) {
|
|
62
|
+
const level = frame[bin] ?? NaN;
|
|
63
|
+
if (!(Number.isFinite(level) && level > decibels)) continue;
|
|
64
|
+
loudest = bin;
|
|
65
|
+
decibels = level;
|
|
66
|
+
}
|
|
67
|
+
return loudest === -1 ? void 0 : {
|
|
68
|
+
decibels,
|
|
69
|
+
frequency: loudest * hertz
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { columnEdges, columnLevels, gridColumns, gridRows, levelRows, loudestBin, zoneRows };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ballisticsRates, fall } from "./ballistics.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/spectrum-levels.ts
|
|
4
|
+
const holdMs = 1500;
|
|
5
|
+
function createLevels(binCount) {
|
|
6
|
+
return {
|
|
7
|
+
bars: new Float64Array(binCount).fill(-Infinity),
|
|
8
|
+
heldAt: new Float64Array(binCount).fill(-Infinity),
|
|
9
|
+
holds: new Float64Array(binCount).fill(-Infinity),
|
|
10
|
+
incoming: new Float64Array(binCount).fill(-Infinity)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function receiveLevels({ incoming }, frame, floor) {
|
|
14
|
+
for (let bin = 0; bin < incoming.length; bin += 1) {
|
|
15
|
+
const level = frame[bin] ?? NaN;
|
|
16
|
+
incoming[bin] = Math.max(incoming[bin] ?? floor, Number.isFinite(level) ? level : floor);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function fallenHold(hold, bar, { elapsedMs, isHoldStepped, pastHoldMs }) {
|
|
20
|
+
if (pastHoldMs < 0) return hold;
|
|
21
|
+
if (isHoldStepped) return bar;
|
|
22
|
+
return fall(hold, {
|
|
23
|
+
fallDecibelsPerSecond: ballisticsRates.peak.fallDecibelsPerSecond,
|
|
24
|
+
floor: bar
|
|
25
|
+
}, Math.min(elapsedMs, pastHoldMs));
|
|
26
|
+
}
|
|
27
|
+
function stepLevels(levels, step) {
|
|
28
|
+
const { bars, heldAt, holds, incoming } = levels;
|
|
29
|
+
const rate = {
|
|
30
|
+
fallDecibelsPerSecond: ballisticsRates.peak.fallDecibelsPerSecond,
|
|
31
|
+
floor: step.floor
|
|
32
|
+
};
|
|
33
|
+
for (let bin = 0; bin < bars.length; bin += 1) {
|
|
34
|
+
const bar = Math.max(fall(bars[bin] ?? step.floor, rate, step.elapsedMs), incoming[bin] ?? step.floor);
|
|
35
|
+
const hold = holds[bin] ?? bar;
|
|
36
|
+
bars[bin] = bar;
|
|
37
|
+
incoming[bin] = -Infinity;
|
|
38
|
+
if (!step.isHeld || bar >= hold) {
|
|
39
|
+
holds[bin] = bar;
|
|
40
|
+
heldAt[bin] = step.nowMs;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const pastHoldMs = step.nowMs - (heldAt[bin] ?? step.nowMs) - holdMs;
|
|
44
|
+
holds[bin] = fallenHold(hold, bar, {
|
|
45
|
+
...step,
|
|
46
|
+
pastHoldMs
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function areSettled({ holds }, floor) {
|
|
51
|
+
return holds.every((hold) => hold <= floor);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { areSettled, createLevels, receiveLevels, stepLevels };
|
|
@@ -18,6 +18,17 @@ function stripBars(peaks, size, grid) {
|
|
|
18
18
|
};
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
function stripGroove(size, ratio) {
|
|
22
|
+
if (ratio <= 0) return void 0;
|
|
23
|
+
const height = Math.max(1, Math.round(size.height * ratio));
|
|
24
|
+
return {
|
|
25
|
+
height,
|
|
26
|
+
radius: height / 2,
|
|
27
|
+
width: size.width,
|
|
28
|
+
x: 0,
|
|
29
|
+
y: Math.round((size.height - height) / 2)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
21
32
|
function stripRegions(state, widthPx) {
|
|
22
33
|
const px = (proportion) => Math.round((Number.isFinite(proportion) ? proportion : 0) * widthPx);
|
|
23
34
|
const buffered = state.buffered.map(([start, end]) => [px(start), px(end)]).filter(([start, end]) => end > start);
|
|
@@ -45,4 +56,4 @@ function stripRegions(state, widthPx) {
|
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
//#endregion
|
|
48
|
-
export { stripBars, stripRegions };
|
|
59
|
+
export { stripBars, stripGroove, stripRegions };
|
|
@@ -3,9 +3,13 @@ function readRegions(regions) {
|
|
|
3
3
|
if ("start" in regions) return Array.from({ length: regions.length }, (_entry, index) => [regions.start(index), regions.end(index)]);
|
|
4
4
|
return [...regions].map(([start, end]) => [start, end]);
|
|
5
5
|
}
|
|
6
|
+
function dueRegions(regions, listedMs, [nowMs, delayMs]) {
|
|
7
|
+
if (delayMs <= 0) return [...regions];
|
|
8
|
+
return regions.filter((region) => nowMs - (listedMs.get(region.join(":")) ?? nowMs) >= delayMs);
|
|
9
|
+
}
|
|
6
10
|
function sortedRegions(regions) {
|
|
7
11
|
return regions ? readRegions(regions).toSorted((first, second) => first[0] - second[0]) : [];
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
//#endregion
|
|
11
|
-
export { readRegions, sortedRegions };
|
|
15
|
+
export { dueRegions, readRegions, sortedRegions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spectrum.js","names":[],"sources":["../../../src/define/spectrum.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicSpectrum } from '#elements/spectrum.ts';\n\ndefineOnce('sonic-spectrum', SonicSpectrum);\n\nexport type { SonicSpectrum } from '#elements/spectrum.ts';\n"],"mappings":";;;;AAGA,WAAW,kBAAkB,aAAa"}
|
package/dist/dev/define.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { SonicSegmented } from "./elements/segmented.js";
|
|
|
12
12
|
import "./define/segmented.js";
|
|
13
13
|
import { SonicSlider } from "./elements/slider.js";
|
|
14
14
|
import "./define/slider.js";
|
|
15
|
+
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
16
|
+
import "./define/spectrum.js";
|
|
15
17
|
import { SonicSplit } from "./elements/split.js";
|
|
16
18
|
import "./define/split.js";
|
|
17
19
|
import { SonicSwitch } from "./elements/switch.js";
|
|
@@ -22,4 +24,4 @@ import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
|
22
24
|
import "./define/wavestrip.js";
|
|
23
25
|
import { SonicXy } from "./elements/xy.js";
|
|
24
26
|
import "./define/xy.js";
|
|
25
|
-
export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy };
|
|
27
|
+
export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy };
|
package/dist/dev/define.js
CHANGED
|
@@ -16,6 +16,8 @@ export declare class SonicButton extends SonicFormElement {
|
|
|
16
16
|
set defaultPressed(isPressed: boolean);
|
|
17
17
|
get latching(): boolean;
|
|
18
18
|
set latching(isLatching: boolean);
|
|
19
|
+
get legend(): string | undefined;
|
|
20
|
+
set legend(name: string | undefined);
|
|
19
21
|
get momentary(): boolean;
|
|
20
22
|
set momentary(isMomentary: boolean);
|
|
21
23
|
get pressed(): boolean;
|
|
@@ -4,16 +4,25 @@ import { copyNode } from "../lib/copy-node.js";
|
|
|
4
4
|
import { bindHold } from "../lib/hold.js";
|
|
5
5
|
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
6
6
|
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
7
|
+
import { capturePointer } from "../lib/pointer-drag.js";
|
|
7
8
|
|
|
8
9
|
//#region src/elements/button.ts
|
|
9
10
|
const renderButton = template(`
|
|
10
11
|
<button class="sonic-button" type="button"><span class="sonic-button-cap"></span></button>
|
|
11
12
|
`, HTMLButtonElement);
|
|
12
13
|
const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
14
|
+
const popupAttributes = {
|
|
15
|
+
controls: "aria-controls",
|
|
16
|
+
expanded: "aria-expanded",
|
|
17
|
+
popup: "aria-haspopup"
|
|
18
|
+
};
|
|
19
|
+
const pressWhens = /* @__PURE__ */ new Set(["pressed", "released"]);
|
|
13
20
|
var SonicButton = class extends SonicFormElement {
|
|
14
21
|
static observedAttributes = [
|
|
15
22
|
...SonicFormElement.observedAttributes,
|
|
23
|
+
...Object.keys(popupAttributes),
|
|
16
24
|
"busy",
|
|
25
|
+
"legend",
|
|
17
26
|
"momentary",
|
|
18
27
|
"pressed",
|
|
19
28
|
"soft-disabled",
|
|
@@ -44,6 +53,12 @@ var SonicButton = class extends SonicFormElement {
|
|
|
44
53
|
set latching(isLatching) {
|
|
45
54
|
this.reflect("latching", isLatching);
|
|
46
55
|
}
|
|
56
|
+
get legend() {
|
|
57
|
+
return this.getAttribute("legend") ?? void 0;
|
|
58
|
+
}
|
|
59
|
+
set legend(name) {
|
|
60
|
+
this.reflect("legend", name);
|
|
61
|
+
}
|
|
47
62
|
get momentary() {
|
|
48
63
|
return this.hasAttribute("momentary");
|
|
49
64
|
}
|
|
@@ -88,12 +103,13 @@ var SonicButton = class extends SonicFormElement {
|
|
|
88
103
|
connect(signal) {
|
|
89
104
|
const button = this.#button;
|
|
90
105
|
const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
|
|
91
|
-
this.upgradeProperties("armed", "busy", "defaultPressed", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
106
|
+
this.upgradeProperties("armed", "busy", "defaultPressed", "legend", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
92
107
|
mirrorChildren(this, {
|
|
93
108
|
control: button,
|
|
94
109
|
copy: copyNode,
|
|
95
110
|
place: (copies) => {
|
|
96
111
|
placeChildren(cap, copies);
|
|
112
|
+
this.#showLegend();
|
|
97
113
|
}
|
|
98
114
|
}, signal);
|
|
99
115
|
this.render();
|
|
@@ -122,6 +138,8 @@ var SonicButton = class extends SonicFormElement {
|
|
|
122
138
|
writeAttribute(button, "aria-pressed", this.latching ? String(this.pressed) : void 0);
|
|
123
139
|
this.toggleState("pressed", this.pressed);
|
|
124
140
|
this.forwardNaming(button, true);
|
|
141
|
+
this.#forwardPopup(button);
|
|
142
|
+
this.#showLegend();
|
|
125
143
|
const submitted = this.latching && this.pressed ? this.value : null;
|
|
126
144
|
this.writeFormValue(submitted, String(this.pressed));
|
|
127
145
|
}
|
|
@@ -142,7 +160,7 @@ var SonicButton = class extends SonicFormElement {
|
|
|
142
160
|
this.#holding = holding;
|
|
143
161
|
button.addEventListener("pointerdown", (event) => {
|
|
144
162
|
if (event.button !== 0 || !this.#isMomentary()) return;
|
|
145
|
-
button
|
|
163
|
+
capturePointer(button, event.pointerId);
|
|
146
164
|
holding.hold(event.pointerId);
|
|
147
165
|
}, { signal });
|
|
148
166
|
button.addEventListener("keydown", (event) => {
|
|
@@ -150,6 +168,9 @@ var SonicButton = class extends SonicFormElement {
|
|
|
150
168
|
holding.hold(event.key);
|
|
151
169
|
}, { signal });
|
|
152
170
|
}
|
|
171
|
+
#forwardPopup(button) {
|
|
172
|
+
for (const [name, aria] of Object.entries(popupAttributes)) writeAttribute(button, aria, this.getAttribute(name) ?? void 0);
|
|
173
|
+
}
|
|
153
174
|
#isMomentary() {
|
|
154
175
|
return this.momentary && !this.latching;
|
|
155
176
|
}
|
|
@@ -160,6 +181,13 @@ var SonicButton = class extends SonicFormElement {
|
|
|
160
181
|
this.pressed = isPressed;
|
|
161
182
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
162
183
|
}
|
|
184
|
+
#showLegend() {
|
|
185
|
+
const legend = this.legend;
|
|
186
|
+
for (const part of this.#button.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
|
|
187
|
+
const when = part.dataset.sonicWhen ?? "";
|
|
188
|
+
part.toggleAttribute("data-sonic-hidden", !pressWhens.has(when) && when !== legend);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
163
191
|
};
|
|
164
192
|
|
|
165
193
|
//#endregion
|
|
@@ -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 { mirrorChildren } from '#lib/mirror-children.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\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'busy',\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 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'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},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css');\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}\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\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\tbutton.setPointerCapture(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#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"],"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 { 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');\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}\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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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SonicElement } from "./sonic-element.js";
|
|
2
|
+
import { Surface, SurfaceFrame } from "../lib/canvas-surface.js";
|
|
3
|
+
//#region src/elements/display-element.d.ts
|
|
4
|
+
export declare abstract class SonicDisplayElement<Colour extends string, Numeric extends string = never> extends SonicElement {
|
|
5
|
+
#private;
|
|
6
|
+
static readonly observedAttributes: string[];
|
|
7
|
+
get analyser(): AnalyserNode | undefined;
|
|
8
|
+
set analyser(node: AnalyserNode | null | undefined);
|
|
9
|
+
get max(): number;
|
|
10
|
+
set max(decibels: number | undefined);
|
|
11
|
+
get min(): number;
|
|
12
|
+
set min(decibels: number | undefined);
|
|
13
|
+
protected abstract readonly canvas: HTMLCanvasElement;
|
|
14
|
+
protected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;
|
|
15
|
+
protected abstract readonly control: HTMLElement;
|
|
16
|
+
protected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;
|
|
17
|
+
protected abstract readonly sheet: string;
|
|
18
|
+
attributeChangedCallback(): void;
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
push(frame: Float32Array): void;
|
|
21
|
+
repaint(): void;
|
|
22
|
+
protected connect(signal: AbortSignal): void;
|
|
23
|
+
protected abstract paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void;
|
|
24
|
+
protected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;
|
|
25
|
+
protected abstract receive(frame: Float32Array): void;
|
|
26
|
+
protected surface(): Surface | undefined;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=display-element.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SonicElement } from "./sonic-element.js";
|
|
2
|
+
import { bindSurface } from "../lib/canvas-surface.js";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/display-element.ts
|
|
5
|
+
var SonicDisplayElement = class extends SonicElement {
|
|
6
|
+
static observedAttributes = [
|
|
7
|
+
...SonicElement.observedAttributes,
|
|
8
|
+
"max",
|
|
9
|
+
"min"
|
|
10
|
+
];
|
|
11
|
+
get analyser() {
|
|
12
|
+
return this.#analyser;
|
|
13
|
+
}
|
|
14
|
+
set analyser(node) {
|
|
15
|
+
this.#analyser = node ?? void 0;
|
|
16
|
+
this.#surface?.requestFrame();
|
|
17
|
+
}
|
|
18
|
+
get max() {
|
|
19
|
+
return this.numberAttribute("max", 0);
|
|
20
|
+
}
|
|
21
|
+
set max(decibels) {
|
|
22
|
+
this.reflect("max", decibels);
|
|
23
|
+
}
|
|
24
|
+
get min() {
|
|
25
|
+
return this.numberAttribute("min", -90);
|
|
26
|
+
}
|
|
27
|
+
set min(decibels) {
|
|
28
|
+
this.reflect("min", decibels);
|
|
29
|
+
}
|
|
30
|
+
#analyser;
|
|
31
|
+
#buffer = /* @__PURE__ */ new Float32Array(0);
|
|
32
|
+
#surface;
|
|
33
|
+
attributeChangedCallback() {
|
|
34
|
+
this.#surface?.invalidate();
|
|
35
|
+
}
|
|
36
|
+
connectedCallback() {
|
|
37
|
+
this.upgradeProperties("analyser", "max", "min");
|
|
38
|
+
super.connectedCallback();
|
|
39
|
+
}
|
|
40
|
+
push(frame) {
|
|
41
|
+
this.receive(frame);
|
|
42
|
+
this.#surface?.requestFrame();
|
|
43
|
+
}
|
|
44
|
+
repaint() {
|
|
45
|
+
this.#surface?.rebuild();
|
|
46
|
+
}
|
|
47
|
+
connect(signal) {
|
|
48
|
+
const control = this.control;
|
|
49
|
+
this.keepControl(control, signal);
|
|
50
|
+
this.#surface = bindSurface({
|
|
51
|
+
canvas: this.canvas,
|
|
52
|
+
colours: this.colours,
|
|
53
|
+
numbers: this.numbers,
|
|
54
|
+
paint: (context, frame) => {
|
|
55
|
+
this.#paint(context, frame);
|
|
56
|
+
},
|
|
57
|
+
signal
|
|
58
|
+
});
|
|
59
|
+
this.checkStyles(control, this.sheet);
|
|
60
|
+
}
|
|
61
|
+
surface() {
|
|
62
|
+
return this.#surface;
|
|
63
|
+
}
|
|
64
|
+
#paint(context, frame) {
|
|
65
|
+
const analyser = this.isDisabled() ? void 0 : this.#analyser;
|
|
66
|
+
if (analyser) {
|
|
67
|
+
if (this.#buffer.length !== analyser.frequencyBinCount) this.#buffer = new Float32Array(analyser.frequencyBinCount);
|
|
68
|
+
this.pull(analyser, this.#buffer);
|
|
69
|
+
this.receive(this.#buffer);
|
|
70
|
+
}
|
|
71
|
+
this.paint(context, frame);
|
|
72
|
+
if (analyser) this.#surface?.requestFrame();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { SonicDisplayElement };
|
|
78
|
+
//# sourceMappingURL=display-element.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [...SonicElement.observedAttributes, 'max', 'min'];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(): void {\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\tpush(frame: Float32Array): void {\n\t\tthis.receive(frame);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.checkStyles(control, this.sheet);\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;AAKA,IAAsB,sBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAAC,GAAG,aAAa;EAAoB;EAAO;CAAK;CAE/F,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,MAAc;EACjB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAc;EACjB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAYA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,2BAAiC;EAChC,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,OAAO,KAAK;EAC/C,MAAM,kBAAkB;CACzB;CAEA,KAAK,OAA2B;EAC/B,KAAK,QAAQ,KAAK;EAClB,KAAK,UAAU,aAAa;CAC7B;CAGA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACD,KAAK,YAAY,SAAS,KAAK,KAAK;CACrC;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
|