@xsynaptic/sonic-ui 0.5.0 → 0.6.1
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 +3 -1
- package/dist/default/dat.js +5 -4
- package/dist/default/define/button.js +1 -1
- package/dist/default/define.js +2 -2
- package/dist/default/elements/button.js +6 -5
- package/dist/default/elements/dial.js +5 -1
- package/dist/default/elements/envelope.js +46 -18
- package/dist/default/elements/meter.js +12 -8
- package/dist/default/elements/number.js +1 -1
- package/dist/default/elements/radio-group.js +46 -47
- package/dist/default/elements/readout-claim.js +0 -29
- package/dist/default/elements/readout.js +1 -1
- package/dist/default/elements/scale.js +43 -0
- package/dist/default/elements/slider.js +16 -3
- package/dist/default/elements/sonic-element.js +17 -55
- package/dist/default/elements/switch.js +33 -39
- package/dist/default/elements/value-element.js +142 -187
- package/dist/default/elements/wave-element.js +5 -2
- package/dist/default/elements/waveform.js +35 -12
- package/dist/default/elements/wavestrip.js +59 -18
- package/dist/default/elements/xy.js +34 -19
- package/dist/default/index.js +2 -2
- package/dist/default/lib/adsr-shape.js +5 -0
- package/dist/default/lib/ballistics.js +2 -2
- package/dist/default/lib/canvas-surface.js +13 -2
- package/dist/default/lib/distribute.js +1 -1
- package/dist/default/lib/drag-step.js +4 -4
- package/dist/default/lib/frame-timeline.js +2 -1
- package/dist/default/lib/marker-band.js +54 -10
- package/dist/default/lib/marker-rider.js +10 -4
- package/dist/default/lib/math.js +6 -1
- 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 +8 -5
- package/dist/default/lib/resample-peaks.js +11 -1
- package/dist/default/lib/time-regions.js +4 -1
- package/dist/default/lib/write-attribute.js +1 -0
- package/dist/dev/dat.d.ts +2 -2
- package/dist/dev/dat.js +5 -4
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/define/button.js +1 -1
- package/dist/dev/define.d.ts +4 -4
- package/dist/dev/define.js +2 -2
- package/dist/dev/elements/button.js +6 -5
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +1 -1
- package/dist/dev/elements/dial.js +5 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.js +46 -18
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +1 -1
- package/dist/dev/elements/meter.js +12 -8
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +1 -1
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.js +46 -47
- 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/readout.js +1 -1
- package/dist/dev/elements/readout.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 +4 -1
- package/dist/dev/elements/slider.js +16 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +2 -8
- package/dist/dev/elements/sonic-element.js +18 -55
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/switch.js +33 -39
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +6 -8
- package/dist/dev/elements/value-element.js +142 -187
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +1 -0
- package/dist/dev/elements/wave-element.js +5 -2
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +1 -1
- package/dist/dev/elements/waveform.js +35 -12
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +4 -0
- package/dist/dev/elements/wavestrip.js +59 -18
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +3 -3
- package/dist/dev/elements/xy.js +34 -19
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +2 -2
- package/dist/dev/index.js +2 -2
- package/dist/dev/lib/adsr-shape.js +5 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -1
- package/dist/dev/lib/ballistics.js +2 -2
- package/dist/dev/lib/ballistics.js.map +1 -1
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +13 -2
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/distribute.js +1 -1
- package/dist/dev/lib/distribute.js.map +1 -1
- package/dist/dev/lib/drag-step.js +4 -4
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -1
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/marker-band.js +54 -10
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/marker-rider.js +10 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/math.js +6 -1
- package/dist/dev/lib/math.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 +8 -5
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +11 -1
- package/dist/dev/lib/resample-peaks.js.map +1 -1
- package/dist/dev/lib/time-regions.js +4 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/dev/lib/write-attribute.js +1 -0
- package/dist/dev/lib/write-attribute.js.map +1 -1
- package/dist/skins/amber.css +8 -0
- package/dist/skins/flat.css +26 -0
- package/dist/skins/ivory.css +1 -0
- package/dist/skins/lime.css +2 -0
- package/dist/skins/slate.css +2 -0
- package/dist/styles/button.css +59 -10
- package/dist/styles/dial.css +24 -16
- package/dist/styles/envelope.css +58 -13
- package/dist/styles/led.css +20 -4
- package/dist/styles/material.css +143 -55
- package/dist/styles/meter.css +8 -4
- package/dist/styles/panel.css +7 -4
- package/dist/styles/segmented.css +27 -5
- package/dist/styles/slider.css +2 -1
- package/dist/styles/switch.css +65 -23
- package/dist/styles/waveform.css +28 -5
- package/dist/styles/wavestrip.css +23 -6
- package/dist/styles/xy.css +48 -27
- package/package.json +2 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
-
Native custom elements for audio interfaces. Each control renders in the light DOM,
|
|
3
|
+
Native custom elements for audio interfaces. Each control renders in the light DOM, skinned with `--sonic-*` tokens (CSS custom properties). No framework, no runtime dependencies.
|
|
4
4
|
|
|
5
5
|
Early and unstable: any 0.x release may break the API.
|
|
6
6
|
|
|
@@ -23,4 +23,6 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
23
23
|
<sonic-dial aria-label="Cutoff" value="40"></sonic-dial>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Under a hydrating framework, register the definitions once hydration is done (in React, from an effect); a control owns one child of its host and uses only the children it documents.
|
|
27
|
+
|
|
26
28
|
Targets current Chrome and Edge, Safari 26 and Firefox 147. MIT licensed.
|
package/dist/default/dat.js
CHANGED
|
@@ -16,6 +16,7 @@ function readDatHeader(buffer) {
|
|
|
16
16
|
if (channels < 1) throw new Error("A .dat header names no channels");
|
|
17
17
|
const sampleRate = view.getInt32(8, true);
|
|
18
18
|
const samplesPerPixel = view.getInt32(12, true);
|
|
19
|
+
if (samplesPerPixel < 1) throw new Error("A .dat header names no samples per pixel");
|
|
19
20
|
return {
|
|
20
21
|
bits: (view.getUint32(4, true) & 1) === 1 ? 8 : 16,
|
|
21
22
|
channels,
|
|
@@ -34,15 +35,15 @@ function datByteRange(header, fromPair, toPair) {
|
|
|
34
35
|
const pairBytes = header.channels * 2 * header.bits / 8;
|
|
35
36
|
return [header.dataOffset + from * pairBytes, header.dataOffset + to * pairBytes - 1];
|
|
36
37
|
}
|
|
37
|
-
function readDatSamples(header, body) {
|
|
38
|
-
return header.bits === 8 ? new Int8Array(body) : new Int16Array(body,
|
|
38
|
+
function readDatSamples(header, body, byteOffset = 0) {
|
|
39
|
+
return header.bits === 8 ? new Int8Array(body, byteOffset) : new Int16Array(body, byteOffset, Math.floor((body.byteLength - byteOffset) / 2));
|
|
39
40
|
}
|
|
40
|
-
function readDatPeaks(header, body) {
|
|
41
|
+
function readDatPeaks(header, body, byteOffset = 0) {
|
|
41
42
|
return {
|
|
42
43
|
channels: header.channels,
|
|
43
44
|
fullScale: header.bits === 8 ? 128 : 32768,
|
|
44
45
|
pairsPerSecond: header.pairsPerSecond,
|
|
45
|
-
samples: readDatSamples(header, body)
|
|
46
|
+
samples: readDatSamples(header, body, byteOffset)
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
package/dist/default/define.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import "./define/button.js";
|
|
1
2
|
import "./define/dial.js";
|
|
2
3
|
import "./define/envelope.js";
|
|
3
|
-
import "./define/button.js";
|
|
4
|
-
import "./define/switch.js";
|
|
5
4
|
import "./define/meter.js";
|
|
6
5
|
import "./define/number.js";
|
|
7
6
|
import "./define/segmented.js";
|
|
8
7
|
import "./define/slider.js";
|
|
9
8
|
import "./define/split.js";
|
|
9
|
+
import "./define/switch.js";
|
|
10
10
|
import "./define/waveform.js";
|
|
11
11
|
import "./define/wavestrip.js";
|
|
12
12
|
import "./define/xy.js";
|
|
@@ -1,8 +1,9 @@
|
|
|
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";
|
|
6
7
|
|
|
7
8
|
//#region src/elements/button.ts
|
|
8
9
|
const renderButton = template(`
|
|
@@ -88,7 +89,7 @@ var SonicButton = class extends SonicFormElement {
|
|
|
88
89
|
const button = this.#button;
|
|
89
90
|
const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
|
|
90
91
|
this.upgradeProperties("armed", "busy", "defaultPressed", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
91
|
-
|
|
92
|
+
mirrorChildren(this, {
|
|
92
93
|
control: button,
|
|
93
94
|
copy: copyNode,
|
|
94
95
|
place: (copies) => {
|
|
@@ -113,12 +114,12 @@ var SonicButton = class extends SonicFormElement {
|
|
|
113
114
|
return this.#button;
|
|
114
115
|
}
|
|
115
116
|
render() {
|
|
117
|
+
if (!this.isBound()) return;
|
|
116
118
|
const button = this.#button;
|
|
117
|
-
button
|
|
119
|
+
writeAttribute(button, "disabled", this.isDisabled() ? "" : void 0);
|
|
118
120
|
writeAttribute(button, "aria-disabled", this.#isSoftDisabled() ? "true" : void 0);
|
|
119
121
|
writeAttribute(button, "aria-busy", this.busy ? "true" : void 0);
|
|
120
|
-
button
|
|
121
|
-
if (this.latching) button.setAttribute("aria-pressed", String(this.pressed));
|
|
122
|
+
writeAttribute(button, "aria-pressed", this.latching ? String(this.pressed) : void 0);
|
|
122
123
|
this.toggleState("pressed", this.pressed);
|
|
123
124
|
this.forwardNaming(button, true);
|
|
124
125
|
const submitted = this.latching && this.pressed ? this.value : null;
|
|
@@ -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,13 +1,13 @@
|
|
|
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
4
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
5
5
|
import { Readout } from "./readout.js";
|
|
6
6
|
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
7
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
7
8
|
import { SonicValueElement, linkValue } from "./value-element.js";
|
|
8
9
|
import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
|
|
9
10
|
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
10
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
11
11
|
|
|
12
12
|
//#region src/elements/envelope.ts
|
|
13
13
|
const curveStages = [
|
|
@@ -31,10 +31,23 @@ function flip(point, isFlipped) {
|
|
|
31
31
|
function isParent(node) {
|
|
32
32
|
return "querySelector" in node;
|
|
33
33
|
}
|
|
34
|
+
const emptyPath = {
|
|
35
|
+
fill: "",
|
|
36
|
+
floor: {
|
|
37
|
+
from: 0,
|
|
38
|
+
to: 0
|
|
39
|
+
},
|
|
40
|
+
stroke: ""
|
|
41
|
+
};
|
|
42
|
+
function stageParts(envelope, hook, stages) {
|
|
43
|
+
return new Map(stages.map((stage) => [stage, requireChild(envelope, `.${hook}[data-sonic-stage="${stage}"]`, HTMLDivElement)]));
|
|
44
|
+
}
|
|
34
45
|
const renderEnvelope = template(`
|
|
35
46
|
<div class="sonic-envelope" aria-hidden="true">
|
|
47
|
+
<div class="sonic-envelope-bracket"></div>
|
|
36
48
|
<svg class="sonic-envelope-graph" preserveAspectRatio="none" viewBox="0 0 1 1">
|
|
37
49
|
<path class="sonic-envelope-fill" />
|
|
50
|
+
<rect class="sonic-envelope-fill sonic-envelope-floor" y="1" />
|
|
38
51
|
<path class="sonic-envelope-line" />
|
|
39
52
|
</svg>
|
|
40
53
|
<div class="sonic-envelope-handle" data-sonic-stage="delay" hidden></div>
|
|
@@ -115,12 +128,18 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
115
128
|
this.reflect("sustain", id);
|
|
116
129
|
}
|
|
117
130
|
#bindings = /* @__PURE__ */ new Map();
|
|
131
|
+
#envelope = renderEnvelope();
|
|
132
|
+
#bracket = requireChild(this.#envelope, ".sonic-envelope-bracket", HTMLDivElement);
|
|
118
133
|
#claim = new ReadoutClaim(() => {
|
|
119
134
|
this.#renderReadout();
|
|
120
135
|
});
|
|
121
|
-
#
|
|
136
|
+
#curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
|
|
122
137
|
#fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
|
|
138
|
+
#floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
|
|
123
139
|
#graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
|
|
140
|
+
#handles = stageParts(this.#envelope, "sonic-envelope-handle", timeStages);
|
|
141
|
+
#held;
|
|
142
|
+
#isDriving = false;
|
|
124
143
|
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
125
144
|
#pointerDrag;
|
|
126
145
|
#readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
|
|
@@ -145,7 +164,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
145
164
|
connect(signal) {
|
|
146
165
|
const envelope = this.#envelope;
|
|
147
166
|
this.#signal = signal;
|
|
148
|
-
this.
|
|
167
|
+
this.keepControl(envelope, signal);
|
|
149
168
|
this.#bind();
|
|
150
169
|
this.checkStyles(envelope, "envelope.css");
|
|
151
170
|
this.#bindPointer(envelope, signal);
|
|
@@ -162,7 +181,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
162
181
|
if (!element) continue;
|
|
163
182
|
const link = linkValue(element);
|
|
164
183
|
const unwatch = link.watch(() => {
|
|
165
|
-
this.#draw();
|
|
184
|
+
if (!this.#isDriving) this.#draw();
|
|
166
185
|
});
|
|
167
186
|
this.#bindings.set(name, {
|
|
168
187
|
element,
|
|
@@ -202,32 +221,37 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
202
221
|
}
|
|
203
222
|
#draw() {
|
|
204
223
|
const shape = this.#readShape();
|
|
205
|
-
const path = this.#bindings.size === 0 ?
|
|
206
|
-
fill: "",
|
|
207
|
-
stroke: ""
|
|
208
|
-
} : adsrPath(shape);
|
|
224
|
+
const path = this.#bindings.size === 0 ? emptyPath : adsrPath(shape);
|
|
209
225
|
this.#shape = shape;
|
|
210
|
-
this.#line
|
|
211
|
-
this.#fill
|
|
212
|
-
|
|
226
|
+
writeAttribute(this.#line, "d", path.stroke);
|
|
227
|
+
writeAttribute(this.#fill, "d", path.fill);
|
|
228
|
+
writeAttribute(this.#floor, "x", String(path.floor.from));
|
|
229
|
+
writeAttribute(this.#floor, "width", String(path.floor.to - path.floor.from));
|
|
230
|
+
for (const [stage, part] of this.#handles) this.#drawPoint(part, {
|
|
213
231
|
at: shape.handles.find((point) => point.stage === stage),
|
|
214
232
|
drives: this.#drives(stage)
|
|
215
233
|
});
|
|
216
|
-
for (const stage of
|
|
234
|
+
for (const [stage, part] of this.#curveHandles) this.#drawPoint(part, {
|
|
217
235
|
at: shape.curveHandles.find((point) => point.stage === stage),
|
|
218
236
|
drives: this.#curveDrives(stage)
|
|
219
237
|
});
|
|
238
|
+
this.#drawBracket();
|
|
220
239
|
this.#renderReadout();
|
|
221
240
|
}
|
|
222
|
-
#
|
|
223
|
-
const part =
|
|
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
|
+
}
|
|
247
|
+
#drawPoint(part, point) {
|
|
224
248
|
const { at, drives } = point;
|
|
225
|
-
part
|
|
249
|
+
writeAttribute(part, "hidden", at ? void 0 : "");
|
|
226
250
|
if (!at) return;
|
|
227
251
|
const isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());
|
|
228
252
|
part.style.setProperty("--_sonic-envelope-x", String(at.x));
|
|
229
253
|
part.style.setProperty("--_sonic-envelope-y", String(at.y));
|
|
230
|
-
part
|
|
254
|
+
writeAttribute(part, "data-sonic-axes", drives.map(({ axis }) => axis).join(""));
|
|
231
255
|
writeAttribute(part, "data-sonic-disabled", isStill ? "" : void 0);
|
|
232
256
|
}
|
|
233
257
|
#drives(stage) {
|
|
@@ -246,6 +270,8 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
246
270
|
if (!pressed || pressed.drives.length === 0) return void 0;
|
|
247
271
|
const { drives, isFlipped, part } = pressed;
|
|
248
272
|
this.#claim.press(true);
|
|
273
|
+
this.#held = part;
|
|
274
|
+
this.#drawBracket();
|
|
249
275
|
const box = this.#graph.getBoundingClientRect();
|
|
250
276
|
const travel = {
|
|
251
277
|
x: Math.max(1, box.width * this.#shape.share),
|
|
@@ -284,11 +310,13 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
284
310
|
const step = stepFieldDrag(mappings, drag.state, flip(pointerMove(event), drag.isFlipped));
|
|
285
311
|
drag.state = step.state;
|
|
286
312
|
if (step.state.isEngaged) this.#claim.reveal("drag");
|
|
313
|
+
this.#isDriving = true;
|
|
287
314
|
for (const { axis, binding } of drag.drives) {
|
|
288
315
|
const next = step[axis];
|
|
289
316
|
if (next !== void 0) binding.link.input(next);
|
|
290
317
|
}
|
|
291
|
-
this.#
|
|
318
|
+
this.#isDriving = false;
|
|
319
|
+
this.#draw();
|
|
292
320
|
}
|
|
293
321
|
#pressed(event) {
|
|
294
322
|
const part = event.target instanceof Element ? event.target.closest(".sonic-envelope-handle, .sonic-envelope-curve") : void 0;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toNumber } from "../lib/math.js";
|
|
2
2
|
import { SonicElement } from "./sonic-element.js";
|
|
3
|
+
import { template } from "../lib/render.js";
|
|
3
4
|
import { parseNumberList } from "../lib/number-list.js";
|
|
4
5
|
import { linearTaper } from "../lib/taper.js";
|
|
5
6
|
import { ballisticsRates, fall, rise, stepNeedle } from "../lib/ballistics.js";
|
|
@@ -51,7 +52,8 @@ var SonicMeter = class extends SonicElement {
|
|
|
51
52
|
get level() {
|
|
52
53
|
return this.#level;
|
|
53
54
|
}
|
|
54
|
-
set level(
|
|
55
|
+
set level(level) {
|
|
56
|
+
const amplitude = toNumber(level);
|
|
55
57
|
if (!Number.isFinite(amplitude)) return;
|
|
56
58
|
const decibels = toDecibels(amplitude);
|
|
57
59
|
this.#level = amplitude;
|
|
@@ -121,7 +123,8 @@ var SonicMeter = class extends SonicElement {
|
|
|
121
123
|
const now = performance.now();
|
|
122
124
|
if (name === "ballistics") this.#velocity = 0;
|
|
123
125
|
else if (name === "value") this.#readValue(now);
|
|
124
|
-
this.#
|
|
126
|
+
this.#segments = this.segments;
|
|
127
|
+
if (name === "segments") this.#renderSegments();
|
|
125
128
|
this.#render(now);
|
|
126
129
|
this.#schedule(now);
|
|
127
130
|
}
|
|
@@ -131,7 +134,7 @@ var SonicMeter = class extends SonicElement {
|
|
|
131
134
|
connect(signal) {
|
|
132
135
|
const now = performance.now();
|
|
133
136
|
this.upgradeProperties("ballistics", "max", "min", "orientation", "origin", "segments", "value", "level");
|
|
134
|
-
this.
|
|
137
|
+
this.keepControl(this.#meter, signal);
|
|
135
138
|
this.#renderSegments();
|
|
136
139
|
this.#render(now);
|
|
137
140
|
this.#schedule(now);
|
|
@@ -165,6 +168,7 @@ var SonicMeter = class extends SonicElement {
|
|
|
165
168
|
if (value !== void 0) this.#peak = Math.max(this.#peak, value);
|
|
166
169
|
}
|
|
167
170
|
#render(now) {
|
|
171
|
+
if (!this.isBound()) return;
|
|
168
172
|
const meter = this.#meter;
|
|
169
173
|
const origin = this.origin === void 0 ? 0 : this.#proportionOf(this.origin);
|
|
170
174
|
meter.style.setProperty("--_sonic-meter-min", String(this.min));
|
|
@@ -185,9 +189,9 @@ var SonicMeter = class extends SonicElement {
|
|
|
185
189
|
meter.style.setProperty("--_sonic-meter-clipped", now - this.#clipAt < holdMs ? "1" : "0");
|
|
186
190
|
}
|
|
187
191
|
#renderSegments() {
|
|
192
|
+
if (!this.isBound()) return;
|
|
188
193
|
const meter = this.#meter;
|
|
189
|
-
const segments = this
|
|
190
|
-
this.#segments = segments;
|
|
194
|
+
const segments = this.#segments;
|
|
191
195
|
this.toggleState("ladder", segments !== void 0);
|
|
192
196
|
if (!segments) {
|
|
193
197
|
for (const name of [
|
|
@@ -226,9 +230,9 @@ var SonicMeter = class extends SonicElement {
|
|
|
226
230
|
this.#stepNeedle(elapsedMs);
|
|
227
231
|
return;
|
|
228
232
|
}
|
|
229
|
-
const { fallDecibelsPerSecond,
|
|
233
|
+
const { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];
|
|
230
234
|
const floor = Math.max(this.#target, this.min);
|
|
231
|
-
this.#bar = this.#target > this.#bar ? this.#risen(
|
|
235
|
+
this.#bar = this.#target > this.#bar ? this.#risen(riseTimeConstantMs, elapsedMs) : fall(this.#bar, {
|
|
232
236
|
fallDecibelsPerSecond,
|
|
233
237
|
floor
|
|
234
238
|
}, elapsedMs);
|
|
@@ -30,7 +30,7 @@ var SonicNumber = class extends SonicValueElement {
|
|
|
30
30
|
}
|
|
31
31
|
connect(signal) {
|
|
32
32
|
const number = this.#number;
|
|
33
|
-
this.
|
|
33
|
+
this.keepControl(number, signal);
|
|
34
34
|
this.render();
|
|
35
35
|
this.checkStyles(number, "number.css");
|
|
36
36
|
this.bindGestures(number, signal, () => ({
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
2
2
|
import { SonicFormElement } from "./form-element.js";
|
|
3
3
|
import { copyNode } from "../lib/copy-node.js";
|
|
4
|
+
import { placeChildren } from "../lib/render.js";
|
|
5
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
6
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
4
7
|
|
|
5
8
|
//#region src/elements/radio-group.ts
|
|
6
9
|
const keySteps = /* @__PURE__ */ new Map([
|
|
@@ -9,10 +12,12 @@ const keySteps = /* @__PURE__ */ new Map([
|
|
|
9
12
|
["ArrowRight", 1],
|
|
10
13
|
["ArrowUp", -1]
|
|
11
14
|
]);
|
|
12
|
-
function
|
|
13
|
-
const box = option.getBoundingClientRect();
|
|
15
|
+
function isInside(box, x, y) {
|
|
14
16
|
return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
|
|
15
17
|
}
|
|
18
|
+
function isUnder(option, x, y) {
|
|
19
|
+
return isInside(option.getBoundingClientRect(), x, y);
|
|
20
|
+
}
|
|
16
21
|
function optionValue(option) {
|
|
17
22
|
return option.querySelector("[data-sonic-value]")?.dataset.sonicValue;
|
|
18
23
|
}
|
|
@@ -26,7 +31,6 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
26
31
|
this.#value = next;
|
|
27
32
|
this.render();
|
|
28
33
|
}
|
|
29
|
-
#press;
|
|
30
34
|
#value = "";
|
|
31
35
|
attributeChangedCallback(name) {
|
|
32
36
|
if (name === "value") this.#value = this.getAttribute("value") ?? "";
|
|
@@ -45,7 +49,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
45
49
|
connect(signal) {
|
|
46
50
|
const group = this.group;
|
|
47
51
|
this.upgradeProperties("value");
|
|
48
|
-
|
|
52
|
+
mirrorChildren(this, {
|
|
49
53
|
control: group,
|
|
50
54
|
copy: copyNode,
|
|
51
55
|
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
|
|
@@ -86,13 +90,15 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
86
90
|
return option;
|
|
87
91
|
}
|
|
88
92
|
render() {
|
|
93
|
+
if (!this.isBound()) return;
|
|
89
94
|
const options = this.options();
|
|
90
95
|
const checked = options.find((option) => optionValue(option) === this.#value);
|
|
91
96
|
const focusable = checked ?? options[0];
|
|
97
|
+
const isDisabled = this.isDisabled();
|
|
92
98
|
for (const option of options) {
|
|
93
|
-
option
|
|
94
|
-
option
|
|
95
|
-
option
|
|
99
|
+
writeAttribute(option, "disabled", isDisabled ? "" : void 0);
|
|
100
|
+
writeAttribute(option, "aria-checked", String(option === checked));
|
|
101
|
+
writeAttribute(option, "tabindex", option === focusable ? "0" : "-1");
|
|
96
102
|
}
|
|
97
103
|
this.forwardNaming(this.group, true);
|
|
98
104
|
this.writeFormValue(checked ? this.#value : null, this.#value);
|
|
@@ -107,41 +113,32 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
107
113
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
108
114
|
}
|
|
109
115
|
#bindPress(group, signal) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (!option) return;
|
|
137
|
-
this.select(option);
|
|
138
|
-
if (group.matches(":focus-within")) option.focus();
|
|
139
|
-
}, { signal });
|
|
140
|
-
group.addEventListener("pointercancel", release, { signal });
|
|
141
|
-
group.addEventListener("lostpointercapture", release, { signal });
|
|
142
|
-
signal.addEventListener("abort", () => {
|
|
143
|
-
this.#hold(void 0);
|
|
144
|
-
}, { once: true });
|
|
116
|
+
bindDrag(group, {
|
|
117
|
+
grab: (event) => {
|
|
118
|
+
const option = this.optionOf(event.target);
|
|
119
|
+
if (!option || this.isDisabled() || this.claimPress(option, event)) return;
|
|
120
|
+
const boxes = this.#optionBoxes();
|
|
121
|
+
this.#markPressed(option);
|
|
122
|
+
return {
|
|
123
|
+
boxes,
|
|
124
|
+
option
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
lift: (press) => {
|
|
128
|
+
if (!press.option || this.isDisabled()) return;
|
|
129
|
+
const option = this.releaseTarget(press.option);
|
|
130
|
+
if (!option) return;
|
|
131
|
+
this.select(option);
|
|
132
|
+
if (group.matches(":focus-within")) option.focus();
|
|
133
|
+
},
|
|
134
|
+
move: (press, event) => {
|
|
135
|
+
press.option = press.boxes.find(([, box]) => isInside(box, event.clientX, event.clientY))?.[0];
|
|
136
|
+
this.#markPressed(press.option);
|
|
137
|
+
},
|
|
138
|
+
release: () => {
|
|
139
|
+
this.#markPressed(void 0);
|
|
140
|
+
}
|
|
141
|
+
}, signal);
|
|
145
142
|
}
|
|
146
143
|
#copyOptions(copies) {
|
|
147
144
|
const group = this.group;
|
|
@@ -160,10 +157,6 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
160
157
|
this.render();
|
|
161
158
|
if (focused) this.#refocus(focused, focusedValue);
|
|
162
159
|
}
|
|
163
|
-
#hold(press) {
|
|
164
|
-
this.#press = press;
|
|
165
|
-
for (const option of this.options()) option.toggleAttribute("data-sonic-pressed", option === press?.option);
|
|
166
|
-
}
|
|
167
160
|
#keyTarget(option, key) {
|
|
168
161
|
const options = this.options();
|
|
169
162
|
if (key === "Home") return options[0];
|
|
@@ -174,6 +167,12 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
174
167
|
if (this.isWrapping()) return options[(index + options.length) % options.length];
|
|
175
168
|
return options[Math.min(Math.max(index, 0), options.length - 1)];
|
|
176
169
|
}
|
|
170
|
+
#markPressed(pressed) {
|
|
171
|
+
for (const option of this.options()) option.toggleAttribute("data-sonic-pressed", option === pressed);
|
|
172
|
+
}
|
|
173
|
+
#optionBoxes() {
|
|
174
|
+
return this.options().map((option) => [option, option.getBoundingClientRect()]);
|
|
175
|
+
}
|
|
177
176
|
#refocus(focused, value) {
|
|
178
177
|
const options = this.options();
|
|
179
178
|
const next = options.find((option) => optionValue(option) === value) ?? options.find((option) => option.tabIndex === 0);
|
|
@@ -6,10 +6,7 @@ var ReadoutClaim = class {
|
|
|
6
6
|
return this.#isDragging || this.#keyTimer !== void 0;
|
|
7
7
|
}
|
|
8
8
|
#holdTimer;
|
|
9
|
-
#hover;
|
|
10
|
-
#isDismissed = false;
|
|
11
9
|
#isDragging = false;
|
|
12
|
-
#isEditing = false;
|
|
13
10
|
#keyTimer;
|
|
14
11
|
#onLapse;
|
|
15
12
|
constructor(onLapse) {
|
|
@@ -27,21 +24,6 @@ var ReadoutClaim = class {
|
|
|
27
24
|
this.#keyTimer = void 0;
|
|
28
25
|
return wasTimed;
|
|
29
26
|
}
|
|
30
|
-
dismiss() {
|
|
31
|
-
const wasHovered = this.#hover !== void 0;
|
|
32
|
-
this.#hover = void 0;
|
|
33
|
-
this.#isDismissed = wasHovered || this.#isDismissed;
|
|
34
|
-
return wasHovered;
|
|
35
|
-
}
|
|
36
|
-
edit(isOpen) {
|
|
37
|
-
this.#isEditing = isOpen;
|
|
38
|
-
if (isOpen) this.#hover = void 0;
|
|
39
|
-
}
|
|
40
|
-
hover(value) {
|
|
41
|
-
if (value === void 0) this.#isDismissed = false;
|
|
42
|
-
if (this.#isDismissed || this.#isEditing || this.isRevealed) return;
|
|
43
|
-
this.#hover = value;
|
|
44
|
-
}
|
|
45
27
|
press(isDown) {
|
|
46
28
|
this.conceal("drag");
|
|
47
29
|
if (!isDown) {
|
|
@@ -54,7 +36,6 @@ var ReadoutClaim = class {
|
|
|
54
36
|
}, 250);
|
|
55
37
|
}
|
|
56
38
|
reveal(by) {
|
|
57
|
-
this.#hover = void 0;
|
|
58
39
|
if (by === "drag") {
|
|
59
40
|
this.#isDragging = true;
|
|
60
41
|
return;
|
|
@@ -65,16 +46,6 @@ var ReadoutClaim = class {
|
|
|
65
46
|
this.#onLapse();
|
|
66
47
|
}, keyRevealMs);
|
|
67
48
|
}
|
|
68
|
-
shown(current, isReadout) {
|
|
69
|
-
if (this.#isEditing) return {
|
|
70
|
-
isOpen: true,
|
|
71
|
-
value: void 0
|
|
72
|
-
};
|
|
73
|
-
return {
|
|
74
|
-
isOpen: (this.isRevealed || this.#hover !== void 0) && isReadout,
|
|
75
|
-
value: this.#hover ?? current
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
49
|
};
|
|
79
50
|
|
|
80
51
|
//#endregion
|
|
@@ -23,7 +23,7 @@ var Readout = class {
|
|
|
23
23
|
this.#anchor = anchor;
|
|
24
24
|
}
|
|
25
25
|
this.#text.hidden = text === void 0;
|
|
26
|
-
if (text !== void 0) this.#text.textContent = text;
|
|
26
|
+
if (isOpen && text !== void 0 && this.#text.textContent !== text) this.#text.textContent = text;
|
|
27
27
|
this.#toggle(isOpen);
|
|
28
28
|
}
|
|
29
29
|
#toggle(isOpen) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
2
|
+
import { placeChildren } from "../lib/render.js";
|
|
3
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
4
|
+
import { linkValue } from "./value-element.js";
|
|
5
|
+
|
|
6
|
+
//#region src/elements/scale.ts
|
|
7
|
+
function scaleValue(mark) {
|
|
8
|
+
const text = mark.dataset.sonicValue?.trim();
|
|
9
|
+
return text ? Number(text) : NaN;
|
|
10
|
+
}
|
|
11
|
+
function scaleMark(original) {
|
|
12
|
+
const mark = copyNode(original);
|
|
13
|
+
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) return void 0;
|
|
14
|
+
if (!Number.isFinite(scaleValue(mark))) return void 0;
|
|
15
|
+
const isTick = mark.childElementCount === 0 && mark.textContent.trim() === "";
|
|
16
|
+
mark.classList.add(isTick ? "sonic-scale-tick" : "sonic-scale-label");
|
|
17
|
+
return mark;
|
|
18
|
+
}
|
|
19
|
+
function bindScale(element, { control, marks }, signal) {
|
|
20
|
+
const { mapping, watch } = linkValue(element);
|
|
21
|
+
const render = () => {
|
|
22
|
+
const { proportionOf } = mapping();
|
|
23
|
+
for (const mark of marks.children) {
|
|
24
|
+
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) continue;
|
|
25
|
+
const at = String(proportionOf(scaleValue(mark)));
|
|
26
|
+
if (mark.style.getPropertyValue("--_sonic-scale-at") !== at) mark.style.setProperty("--_sonic-scale-at", at);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
mirrorChildren(element, {
|
|
30
|
+
control,
|
|
31
|
+
copy: scaleMark,
|
|
32
|
+
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
|
|
33
|
+
isPassed: (child) => child instanceof Element && child.matches(".sonic-led"),
|
|
34
|
+
place: (copies) => {
|
|
35
|
+
placeChildren(marks, copies);
|
|
36
|
+
render();
|
|
37
|
+
}
|
|
38
|
+
}, signal);
|
|
39
|
+
signal.addEventListener("abort", watch(render), { once: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { bindScale };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
2
|
import { SonicValueElement } from "./value-element.js";
|
|
3
|
+
import { bindScale } from "./scale.js";
|
|
4
|
+
import { sortedRegions } from "../lib/time-regions.js";
|
|
3
5
|
|
|
4
6
|
//#region src/elements/slider.ts
|
|
5
7
|
function grooveStop(proportion) {
|
|
@@ -34,6 +36,13 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
34
36
|
"orientation",
|
|
35
37
|
"spring"
|
|
36
38
|
];
|
|
39
|
+
get buffered() {
|
|
40
|
+
return this.#buffered.map(([start, end]) => [start, end]);
|
|
41
|
+
}
|
|
42
|
+
set buffered(regions) {
|
|
43
|
+
this.#buffered = sortedRegions(regions);
|
|
44
|
+
this.render();
|
|
45
|
+
}
|
|
37
46
|
get groovePress() {
|
|
38
47
|
return this.getAttribute("groove-press") === "none" ? "none" : "jump";
|
|
39
48
|
}
|
|
@@ -64,15 +73,19 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
64
73
|
set spring(isSpring) {
|
|
65
74
|
this.reflect("spring", isSpring);
|
|
66
75
|
}
|
|
76
|
+
#buffered = [];
|
|
67
77
|
#slider = renderSlider();
|
|
68
78
|
connectedCallback() {
|
|
69
|
-
this.upgradeProperties("groovePress", "modulationValue", "modulation", "orientation", "spring");
|
|
79
|
+
this.upgradeProperties("buffered", "groovePress", "modulationValue", "modulation", "orientation", "spring");
|
|
70
80
|
super.connectedCallback();
|
|
71
81
|
}
|
|
72
82
|
connect(signal) {
|
|
73
83
|
const slider = this.#slider;
|
|
74
84
|
const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
|
|
75
|
-
|
|
85
|
+
bindScale(this, {
|
|
86
|
+
control: slider,
|
|
87
|
+
marks: requireChild(slider, ".sonic-slider-scale", HTMLDivElement)
|
|
88
|
+
}, signal);
|
|
76
89
|
slider.addEventListener("transitionend", (event) => {
|
|
77
90
|
if (event.propertyName === "--_sonic-slider-value") this.toggleState("springing", false);
|
|
78
91
|
}, { signal });
|
|
@@ -113,7 +126,7 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
113
126
|
};
|
|
114
127
|
}
|
|
115
128
|
#renderBuffered(slider) {
|
|
116
|
-
const buffered = this
|
|
129
|
+
const buffered = this.#buffered;
|
|
117
130
|
if (buffered.length === 0) {
|
|
118
131
|
slider.style.removeProperty("--_sonic-slider-buffered-regions");
|
|
119
132
|
return;
|