@xsynaptic/sonic-ui 0.6.0 → 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 +33 -4
- package/dist/default/elements/dial.js +5 -1
- package/dist/default/elements/display-element.js +77 -0
- package/dist/default/elements/envelope.js +15 -3
- package/dist/default/elements/meter.js +1 -1
- package/dist/default/elements/radio-group.js +3 -2
- package/dist/default/elements/readout-claim.js +0 -29
- package/dist/default/elements/scale.js +43 -0
- package/dist/default/elements/slider.js +82 -3
- package/dist/default/elements/sonic-element.js +2 -67
- package/dist/default/elements/spectrum.js +230 -0
- package/dist/default/elements/switch.js +1 -1
- package/dist/default/elements/value-element.js +81 -91
- package/dist/default/elements/wave-element.js +59 -9
- package/dist/default/elements/waveform.js +56 -10
- package/dist/default/elements/wavestrip.js +45 -21
- package/dist/default/elements/xy.js +3 -2
- 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/mirror-children.js +64 -0
- package/dist/default/lib/owned-control.js +10 -0
- 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 +8 -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 +33 -4
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +5 -1
- package/dist/dev/elements/dial.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 +15 -3
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/radio-group.js +3 -2
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout-claim.js +0 -29
- package/dist/dev/elements/readout-claim.js.map +1 -1
- package/dist/dev/elements/scale.js +44 -0
- package/dist/dev/elements/scale.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +9 -0
- package/dist/dev/elements/slider.js +82 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +0 -8
- package/dist/dev/elements/sonic-element.js +3 -90
- package/dist/dev/elements/sonic-element.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/switch.js +1 -1
- package/dist/dev/elements/value-element.d.ts +8 -6
- package/dist/dev/elements/value-element.js +81 -91
- 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 +7 -0
- package/dist/dev/elements/wavestrip.js +45 -21
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +3 -2
- package/dist/dev/elements/xy.js.map +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/mirror-children.js +80 -0
- package/dist/dev/lib/mirror-children.js.map +1 -0
- package/dist/dev/lib/owned-control.js +23 -0
- package/dist/dev/lib/owned-control.js.map +1 -0
- 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 +8 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/skins/flat.css +2 -1
- package/dist/styles/button.css +28 -1
- package/dist/styles/controls.css +1 -0
- package/dist/styles/envelope.css +30 -30
- package/dist/styles/led.css +18 -2
- package/dist/styles/material.css +90 -29
- package/dist/styles/meter.css +22 -18
- package/dist/styles/panel.css +2 -1
- package/dist/styles/slider.css +42 -5
- package/dist/styles/spectrum.css +105 -0
- package/dist/styles/switch.css +10 -7
- package/dist/styles/waveform.css +38 -16
- package/dist/styles/wavestrip.css +22 -4
- package/dist/styles/xy.css +14 -30
- package/package.json +2 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/lib/owned-control.ts
|
|
2
|
+
function appendOnce(host, control) {
|
|
3
|
+
const [hook] = control.classList;
|
|
4
|
+
const stale = [...host.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
|
|
5
|
+
for (const child of stale) child.remove();
|
|
6
|
+
if (control.parentNode !== host) host.append(control);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
export { appendOnce };
|
|
@@ -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,6 +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
|
+
}
|
|
10
|
+
function sortedRegions(regions) {
|
|
11
|
+
return regions ? readRegions(regions).toSorted((first, second) => first[0] - second[0]) : [];
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
//#endregion
|
|
8
|
-
export { readRegions };
|
|
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;
|
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
2
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
2
|
import { SonicFormElement } from "./form-element.js";
|
|
4
3
|
import { copyNode } from "../lib/copy-node.js";
|
|
5
4
|
import { bindHold } from "../lib/hold.js";
|
|
5
|
+
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
6
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
7
|
+
import { capturePointer } from "../lib/pointer-drag.js";
|
|
6
8
|
|
|
7
9
|
//#region src/elements/button.ts
|
|
8
10
|
const renderButton = template(`
|
|
9
11
|
<button class="sonic-button" type="button"><span class="sonic-button-cap"></span></button>
|
|
10
12
|
`, HTMLButtonElement);
|
|
11
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"]);
|
|
12
20
|
var SonicButton = class extends SonicFormElement {
|
|
13
21
|
static observedAttributes = [
|
|
14
22
|
...SonicFormElement.observedAttributes,
|
|
23
|
+
...Object.keys(popupAttributes),
|
|
15
24
|
"busy",
|
|
25
|
+
"legend",
|
|
16
26
|
"momentary",
|
|
17
27
|
"pressed",
|
|
18
28
|
"soft-disabled",
|
|
@@ -43,6 +53,12 @@ var SonicButton = class extends SonicFormElement {
|
|
|
43
53
|
set latching(isLatching) {
|
|
44
54
|
this.reflect("latching", isLatching);
|
|
45
55
|
}
|
|
56
|
+
get legend() {
|
|
57
|
+
return this.getAttribute("legend") ?? void 0;
|
|
58
|
+
}
|
|
59
|
+
set legend(name) {
|
|
60
|
+
this.reflect("legend", name);
|
|
61
|
+
}
|
|
46
62
|
get momentary() {
|
|
47
63
|
return this.hasAttribute("momentary");
|
|
48
64
|
}
|
|
@@ -87,12 +103,13 @@ var SonicButton = class extends SonicFormElement {
|
|
|
87
103
|
connect(signal) {
|
|
88
104
|
const button = this.#button;
|
|
89
105
|
const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
|
|
90
|
-
this.upgradeProperties("armed", "busy", "defaultPressed", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
91
|
-
|
|
106
|
+
this.upgradeProperties("armed", "busy", "defaultPressed", "legend", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
107
|
+
mirrorChildren(this, {
|
|
92
108
|
control: button,
|
|
93
109
|
copy: copyNode,
|
|
94
110
|
place: (copies) => {
|
|
95
111
|
placeChildren(cap, copies);
|
|
112
|
+
this.#showLegend();
|
|
96
113
|
}
|
|
97
114
|
}, signal);
|
|
98
115
|
this.render();
|
|
@@ -121,6 +138,8 @@ var SonicButton = class extends SonicFormElement {
|
|
|
121
138
|
writeAttribute(button, "aria-pressed", this.latching ? String(this.pressed) : void 0);
|
|
122
139
|
this.toggleState("pressed", this.pressed);
|
|
123
140
|
this.forwardNaming(button, true);
|
|
141
|
+
this.#forwardPopup(button);
|
|
142
|
+
this.#showLegend();
|
|
124
143
|
const submitted = this.latching && this.pressed ? this.value : null;
|
|
125
144
|
this.writeFormValue(submitted, String(this.pressed));
|
|
126
145
|
}
|
|
@@ -141,7 +160,7 @@ var SonicButton = class extends SonicFormElement {
|
|
|
141
160
|
this.#holding = holding;
|
|
142
161
|
button.addEventListener("pointerdown", (event) => {
|
|
143
162
|
if (event.button !== 0 || !this.#isMomentary()) return;
|
|
144
|
-
button
|
|
163
|
+
capturePointer(button, event.pointerId);
|
|
145
164
|
holding.hold(event.pointerId);
|
|
146
165
|
}, { signal });
|
|
147
166
|
button.addEventListener("keydown", (event) => {
|
|
@@ -149,6 +168,9 @@ var SonicButton = class extends SonicFormElement {
|
|
|
149
168
|
holding.hold(event.key);
|
|
150
169
|
}, { signal });
|
|
151
170
|
}
|
|
171
|
+
#forwardPopup(button) {
|
|
172
|
+
for (const [name, aria] of Object.entries(popupAttributes)) writeAttribute(button, aria, this.getAttribute(name) ?? void 0);
|
|
173
|
+
}
|
|
152
174
|
#isMomentary() {
|
|
153
175
|
return this.momentary && !this.latching;
|
|
154
176
|
}
|
|
@@ -159,6 +181,13 @@ var SonicButton = class extends SonicFormElement {
|
|
|
159
181
|
this.pressed = isPressed;
|
|
160
182
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
161
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
|
+
}
|
|
162
191
|
};
|
|
163
192
|
|
|
164
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 { 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\tthis.mirrorChildren(\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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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
2
|
import { SonicValueElement } from "./value-element.js";
|
|
3
|
+
import { bindScale } from "./scale.js";
|
|
3
4
|
|
|
4
5
|
//#region src/elements/dial.ts
|
|
5
6
|
const renderDial = template(`
|
|
@@ -54,7 +55,10 @@ var SonicDial = class extends SonicValueElement {
|
|
|
54
55
|
}
|
|
55
56
|
connect(signal) {
|
|
56
57
|
const dial = this.#dial;
|
|
57
|
-
|
|
58
|
+
bindScale(this, {
|
|
59
|
+
control: dial,
|
|
60
|
+
marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
|
|
61
|
+
}, signal);
|
|
58
62
|
this.render();
|
|
59
63
|
this.checkStyles(dial, "dial.css");
|
|
60
64
|
this.bindGestures(dial, signal, () => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.readModulationValue();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.writeModulationValue(this.#dial, 'dial', value);\n\t}\n\n\treadonly #dial = renderDial();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tthis
|
|
1
|
+
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.readModulationValue();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.writeModulationValue(this.#dial, 'dial', value);\n\t}\n\n\treadonly #dial = renderDial();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.writeProportions(this.#dial, 'dial', this.endless ? 0 : this.modulation);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;AAUA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,oBAAoB;CACjC;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,qBAAqB,KAAK,OAAO,QAAQ,KAAK;CACpD;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,UAAU;EACjC,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,iBAAiB,KAAK,OAAO,QAAQ,KAAK,UAAU,IAAI,KAAK,UAAU;CAC7E;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
|
|
@@ -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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { requireChild, template } from "../lib/render.js";
|
|
2
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
2
|
import { SonicElement } from "./sonic-element.js";
|
|
3
|
+
import { requireChild, template } from "../lib/render.js";
|
|
4
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
4
5
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
5
6
|
import { Readout } from "./readout.js";
|
|
6
7
|
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
7
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
8
8
|
import { SonicValueElement, linkValue } from "./value-element.js";
|
|
9
9
|
import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
|
|
10
10
|
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
@@ -44,6 +44,7 @@ function stageParts(envelope, hook, stages) {
|
|
|
44
44
|
}
|
|
45
45
|
const renderEnvelope = template(`
|
|
46
46
|
<div class="sonic-envelope" aria-hidden="true">
|
|
47
|
+
<div class="sonic-envelope-bracket"></div>
|
|
47
48
|
<svg class="sonic-envelope-graph" preserveAspectRatio="none" viewBox="0 0 1 1">
|
|
48
49
|
<path class="sonic-envelope-fill" />
|
|
49
50
|
<rect class="sonic-envelope-fill sonic-envelope-floor" y="1" />
|
|
@@ -127,15 +128,17 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
127
128
|
this.reflect("sustain", id);
|
|
128
129
|
}
|
|
129
130
|
#bindings = /* @__PURE__ */ new Map();
|
|
131
|
+
#envelope = renderEnvelope();
|
|
132
|
+
#bracket = requireChild(this.#envelope, ".sonic-envelope-bracket", HTMLDivElement);
|
|
130
133
|
#claim = new ReadoutClaim(() => {
|
|
131
134
|
this.#renderReadout();
|
|
132
135
|
});
|
|
133
|
-
#envelope = renderEnvelope();
|
|
134
136
|
#curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
|
|
135
137
|
#fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
|
|
136
138
|
#floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
|
|
137
139
|
#graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
|
|
138
140
|
#handles = stageParts(this.#envelope, "sonic-envelope-handle", timeStages);
|
|
141
|
+
#held;
|
|
139
142
|
#isDriving = false;
|
|
140
143
|
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
141
144
|
#pointerDrag;
|
|
@@ -232,8 +235,15 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
232
235
|
at: shape.curveHandles.find((point) => point.stage === stage),
|
|
233
236
|
drives: this.#curveDrives(stage)
|
|
234
237
|
});
|
|
238
|
+
this.#drawBracket();
|
|
235
239
|
this.#renderReadout();
|
|
236
240
|
}
|
|
241
|
+
#drawBracket() {
|
|
242
|
+
const part = this.#held;
|
|
243
|
+
if (!part) return;
|
|
244
|
+
for (const name of ["--_sonic-envelope-x", "--_sonic-envelope-y"]) this.#bracket.style.setProperty(name, part.style.getPropertyValue(name));
|
|
245
|
+
writeAttribute(this.#bracket, "data-sonic-part", part.classList.contains("sonic-envelope-curve") ? "curve" : void 0);
|
|
246
|
+
}
|
|
237
247
|
#drawPoint(part, point) {
|
|
238
248
|
const { at, drives } = point;
|
|
239
249
|
writeAttribute(part, "hidden", at ? void 0 : "");
|
|
@@ -260,6 +270,8 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
260
270
|
if (!pressed || pressed.drives.length === 0) return void 0;
|
|
261
271
|
const { drives, isFlipped, part } = pressed;
|
|
262
272
|
this.#claim.press(true);
|
|
273
|
+
this.#held = part;
|
|
274
|
+
this.#drawBracket();
|
|
263
275
|
const box = this.#graph.getBoundingClientRect();
|
|
264
276
|
const travel = {
|
|
265
277
|
x: Math.max(1, box.width * this.#shape.share),
|