@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
|
@@ -2,19 +2,44 @@ import { SonicValueElement } from "./value-element.js";
|
|
|
2
2
|
import { bindSurface } from "../lib/canvas-surface.js";
|
|
3
3
|
|
|
4
4
|
//#region src/elements/wave-element.ts
|
|
5
|
+
function markerKey(marker) {
|
|
6
|
+
return marker ? [
|
|
7
|
+
marker.start,
|
|
8
|
+
marker.end,
|
|
9
|
+
marker.kind,
|
|
10
|
+
marker.label
|
|
11
|
+
].join("|") : "";
|
|
12
|
+
}
|
|
5
13
|
var SonicWaveElement = class extends SonicValueElement {
|
|
14
|
+
static observedAttributes = [...SonicValueElement.observedAttributes, "fill"];
|
|
15
|
+
get currentMarker() {
|
|
16
|
+
return this.#current ? { ...this.#current } : void 0;
|
|
17
|
+
}
|
|
18
|
+
get fill() {
|
|
19
|
+
return this.hasAttribute("fill");
|
|
20
|
+
}
|
|
21
|
+
set fill(isFilling) {
|
|
22
|
+
this.reflect("fill", isFilling);
|
|
23
|
+
}
|
|
6
24
|
get markers() {
|
|
7
25
|
return this.#markers.map((marker) => ({ ...marker }));
|
|
8
26
|
}
|
|
9
27
|
set markers(list) {
|
|
10
28
|
this.#markers = [...list ?? []].filter((marker) => Number.isFinite(marker.start)).map((marker) => ({ ...marker })).toSorted((first, second) => first.start - second.start);
|
|
11
|
-
if (this.isBound())
|
|
29
|
+
if (!this.isBound()) return;
|
|
30
|
+
this.renderMarkers();
|
|
31
|
+
this.render();
|
|
12
32
|
}
|
|
13
|
-
#
|
|
33
|
+
#current;
|
|
34
|
+
#fillWatch;
|
|
14
35
|
#markers = [];
|
|
15
36
|
#surface;
|
|
37
|
+
attributeChangedCallback(name) {
|
|
38
|
+
super.attributeChangedCallback(name);
|
|
39
|
+
if (name === "fill") this.#watchFill();
|
|
40
|
+
}
|
|
16
41
|
connectedCallback() {
|
|
17
|
-
this.upgradeProperties("markers");
|
|
42
|
+
this.upgradeProperties("fill", "markers");
|
|
18
43
|
super.connectedCallback();
|
|
19
44
|
}
|
|
20
45
|
repaint() {
|
|
@@ -38,16 +63,20 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
38
63
|
});
|
|
39
64
|
this.render();
|
|
40
65
|
this.checkStyles(control, this.sheet);
|
|
66
|
+
this.#watchFill();
|
|
67
|
+
signal.addEventListener("abort", () => {
|
|
68
|
+
this.#fillWatch?.disconnect();
|
|
69
|
+
this.#fillWatch = void 0;
|
|
70
|
+
}, { once: true });
|
|
41
71
|
}
|
|
42
72
|
focusTarget() {
|
|
43
73
|
return this.control;
|
|
44
74
|
}
|
|
45
75
|
heldWrite(next) {
|
|
46
|
-
|
|
76
|
+
super.heldWrite(next);
|
|
47
77
|
this.render();
|
|
48
78
|
}
|
|
49
79
|
holdChanged() {
|
|
50
|
-
if (this.heldFrom() === void 0) this.#heldAt = void 0;
|
|
51
80
|
this.render();
|
|
52
81
|
}
|
|
53
82
|
markerList() {
|
|
@@ -61,19 +90,40 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
61
90
|
return [Math.min(at, to), Math.max(at, to)];
|
|
62
91
|
});
|
|
63
92
|
}
|
|
64
|
-
playback() {
|
|
65
|
-
const from = this.heldFrom();
|
|
66
|
-
return from === void 0 ? this.value : this.#heldAt ?? from;
|
|
67
|
-
}
|
|
68
93
|
proportionsChanged() {
|
|
69
94
|
this.renderMarkers();
|
|
70
95
|
}
|
|
71
96
|
renderEmpty() {
|
|
72
97
|
this.toggleState("empty", this.isEmpty());
|
|
73
98
|
}
|
|
99
|
+
showCurrentMarker(seconds) {
|
|
100
|
+
const current = this.#markers.findLast((marker) => marker.start <= seconds);
|
|
101
|
+
if (markerKey(current) === markerKey(this.#current)) return;
|
|
102
|
+
this.#current = current;
|
|
103
|
+
this.dispatchEvent(new Event("sonic-marker", { bubbles: true }));
|
|
104
|
+
}
|
|
74
105
|
surface() {
|
|
75
106
|
return this.#surface;
|
|
76
107
|
}
|
|
108
|
+
#watchFill() {
|
|
109
|
+
const control = this.control;
|
|
110
|
+
const isFilling = this.fill && this.isBound();
|
|
111
|
+
if (isFilling === (this.#fillWatch !== void 0)) return;
|
|
112
|
+
this.#fillWatch?.disconnect();
|
|
113
|
+
this.#fillWatch = void 0;
|
|
114
|
+
if (!isFilling) {
|
|
115
|
+
control.style.removeProperty(this.sizeProperty);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.#fillWatch = new ResizeObserver((entries) => {
|
|
119
|
+
const box = entries.at(-1)?.borderBoxSize[0];
|
|
120
|
+
if (!box) return;
|
|
121
|
+
requestAnimationFrame(() => {
|
|
122
|
+
if (this.#fillWatch) control.style.setProperty(this.sizeProperty, `${String(box.blockSize)}px`);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
this.#fillWatch.observe(control);
|
|
126
|
+
}
|
|
77
127
|
};
|
|
78
128
|
|
|
79
129
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clamp } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
|
-
import { readRegions } from "../lib/time-regions.js";
|
|
3
|
+
import { dueRegions, readRegions } from "../lib/time-regions.js";
|
|
4
4
|
import { SonicWaveElement } from "./wave-element.js";
|
|
5
5
|
import { frameTimeline } from "../lib/frame-timeline.js";
|
|
6
6
|
import { isKind, writeKind } from "../lib/marker-band.js";
|
|
@@ -10,9 +10,10 @@ import { waveformBuckets } from "../lib/waveform-buckets.js";
|
|
|
10
10
|
|
|
11
11
|
//#region src/elements/waveform.ts
|
|
12
12
|
const colours = {
|
|
13
|
+
ends: "--_sonic-waveform-ends",
|
|
13
14
|
grid: "--_sonic-waveform-grid",
|
|
14
|
-
hatch: "--_sonic-waveform-hatch",
|
|
15
15
|
marker: "--_sonic-waveform-marker",
|
|
16
|
+
placeholder: "--_sonic-waveform-placeholder",
|
|
16
17
|
wave: "--_sonic-waveform-wave",
|
|
17
18
|
waveEdge: "--_sonic-waveform-wave-edge"
|
|
18
19
|
};
|
|
@@ -63,7 +64,7 @@ function paintHatch(context, scene, [from, to]) {
|
|
|
63
64
|
context.lineTo(x + height, 0);
|
|
64
65
|
}
|
|
65
66
|
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
66
|
-
context.strokeStyle = scene.colours.
|
|
67
|
+
context.strokeStyle = scene.colours.ends;
|
|
67
68
|
context.stroke();
|
|
68
69
|
context.restore();
|
|
69
70
|
}
|
|
@@ -83,7 +84,7 @@ function paintPlaceholders(context, scene) {
|
|
|
83
84
|
context.lineTo(closing, waveY(closing));
|
|
84
85
|
}
|
|
85
86
|
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
86
|
-
context.strokeStyle = scene.colours.
|
|
87
|
+
context.strokeStyle = scene.colours.placeholder;
|
|
87
88
|
context.stroke();
|
|
88
89
|
}
|
|
89
90
|
function waveFill(context, scene) {
|
|
@@ -111,8 +112,10 @@ function paintWave(context, scene, peaks) {
|
|
|
111
112
|
var SonicWaveform = class extends SonicWaveElement {
|
|
112
113
|
static observedAttributes = [
|
|
113
114
|
...SonicWaveElement.observedAttributes,
|
|
115
|
+
"pending-delay",
|
|
114
116
|
"playing",
|
|
115
117
|
"playback-rate",
|
|
118
|
+
"reduced-motion",
|
|
116
119
|
"zoom"
|
|
117
120
|
];
|
|
118
121
|
get peaks() {
|
|
@@ -127,9 +130,21 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
127
130
|
return this.#pending.map(([start, end]) => [start, end]);
|
|
128
131
|
}
|
|
129
132
|
set pending(regions) {
|
|
133
|
+
const listedMs = this.#pendingListedMs;
|
|
134
|
+
const nowMs = performance.now();
|
|
130
135
|
this.#pending = regions ? readRegions(regions) : [];
|
|
136
|
+
this.#pendingListedMs = new Map(this.#pending.map((region) => {
|
|
137
|
+
const key = region.join(":");
|
|
138
|
+
return [key, listedMs.get(key) ?? nowMs];
|
|
139
|
+
}));
|
|
131
140
|
this.surface()?.invalidate();
|
|
132
141
|
}
|
|
142
|
+
get pendingDelay() {
|
|
143
|
+
return Math.max(0, this.numberAttribute("pending-delay", 0));
|
|
144
|
+
}
|
|
145
|
+
set pendingDelay(value) {
|
|
146
|
+
this.reflect("pending-delay", value);
|
|
147
|
+
}
|
|
133
148
|
get playbackRate() {
|
|
134
149
|
const playbackRate = this.numberAttribute("playback-rate", 1);
|
|
135
150
|
return playbackRate > 0 ? playbackRate : 1;
|
|
@@ -150,11 +165,18 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
150
165
|
this.#readTime = read;
|
|
151
166
|
this.render();
|
|
152
167
|
}
|
|
168
|
+
get reducedMotion() {
|
|
169
|
+
return this.getAttribute("reduced-motion") === "scroll" ? "scroll" : "page";
|
|
170
|
+
}
|
|
171
|
+
set reducedMotion(motion) {
|
|
172
|
+
this.reflect("reduced-motion", motion);
|
|
173
|
+
}
|
|
153
174
|
get requestPeaks() {
|
|
154
175
|
return this.#requestPeaks;
|
|
155
176
|
}
|
|
156
177
|
set requestPeaks(request) {
|
|
157
178
|
this.#requestPeaks = request;
|
|
179
|
+
this.#asked = void 0;
|
|
158
180
|
this.surface()?.invalidate();
|
|
159
181
|
}
|
|
160
182
|
get zoom() {
|
|
@@ -169,6 +191,8 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
169
191
|
colours = colours;
|
|
170
192
|
lengths = {};
|
|
171
193
|
sheet = "waveform.css";
|
|
194
|
+
sizeProperty = "--_sonic-waveform-size";
|
|
195
|
+
#asked;
|
|
172
196
|
#clock = createTrackingClock();
|
|
173
197
|
#drawn;
|
|
174
198
|
#ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
|
|
@@ -177,16 +201,18 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
177
201
|
#paintedKey = "";
|
|
178
202
|
#peaks;
|
|
179
203
|
#pending = [];
|
|
204
|
+
#pendingListedMs = /* @__PURE__ */ new Map();
|
|
180
205
|
#playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
|
|
181
206
|
#readTime;
|
|
182
207
|
#requestPeaks;
|
|
183
208
|
#rider = createLabelRider(this.control, {
|
|
184
209
|
className: "sonic-waveform-label",
|
|
185
210
|
colourProperty: "--_sonic-waveform-marker",
|
|
211
|
+
fadeProperty: "--_sonic-waveform-label-fade",
|
|
186
212
|
insetProperty: "--_sonic-waveform-label-inset"
|
|
187
213
|
});
|
|
188
214
|
connectedCallback() {
|
|
189
|
-
this.upgradeProperties("peaks", "pending", "playing", "playbackRate", "readTime", "requestPeaks", "zoom");
|
|
215
|
+
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom");
|
|
190
216
|
super.connectedCallback();
|
|
191
217
|
}
|
|
192
218
|
connect(signal) {
|
|
@@ -204,6 +230,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
204
230
|
}
|
|
205
231
|
paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
|
|
206
232
|
const held = this.#held();
|
|
233
|
+
const pending = this.#duePending();
|
|
207
234
|
const timeline = frameTimeline({
|
|
208
235
|
clockSeconds: this.#clock.read(frameMs, {
|
|
209
236
|
isPlaying: this.playing,
|
|
@@ -212,9 +239,10 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
212
239
|
}),
|
|
213
240
|
frameMs,
|
|
214
241
|
...held ? { held } : {},
|
|
215
|
-
isPaged: look.isReducedMotion,
|
|
242
|
+
isPaged: look.isReducedMotion && this.reducedMotion === "page",
|
|
216
243
|
isPlaying: this.playing,
|
|
217
|
-
|
|
244
|
+
isStill: look.isReducedMotion,
|
|
245
|
+
pending,
|
|
218
246
|
range: [this.min, Math.max(this.min, this.max)],
|
|
219
247
|
size,
|
|
220
248
|
zoom: this.zoom
|
|
@@ -226,6 +254,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
226
254
|
};
|
|
227
255
|
placeLine(this.#playhead, timeline.playheadAt);
|
|
228
256
|
placeLine(this.#ghost, timeline.ghostAt);
|
|
257
|
+
this.showCurrentMarker(seconds);
|
|
229
258
|
this.toggleState("pending", view.pending.length > 0);
|
|
230
259
|
this.#rider.place({
|
|
231
260
|
playheadSeconds: seconds,
|
|
@@ -239,7 +268,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
239
268
|
...view,
|
|
240
269
|
colours: look.colours
|
|
241
270
|
}, isRebuilt);
|
|
242
|
-
if (timeline.wanted) this.#
|
|
271
|
+
if (timeline.wanted) this.#ask(timeline.wanted);
|
|
243
272
|
}
|
|
244
273
|
if (timeline.isMoving) this.surface()?.requestFrame();
|
|
245
274
|
}
|
|
@@ -253,6 +282,17 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
253
282
|
}]));
|
|
254
283
|
this.surface()?.rebuild();
|
|
255
284
|
}
|
|
285
|
+
#ask(wanted) {
|
|
286
|
+
const asked = this.#requestPeaks?.(...wanted);
|
|
287
|
+
if (!(asked instanceof Promise)) return;
|
|
288
|
+
this.#asked = asked;
|
|
289
|
+
this.#repaintAfter(asked);
|
|
290
|
+
}
|
|
291
|
+
#duePending() {
|
|
292
|
+
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
293
|
+
if (due.length < this.#pending.length) this.surface()?.requestFrame();
|
|
294
|
+
return due;
|
|
295
|
+
}
|
|
256
296
|
#grab() {
|
|
257
297
|
const drawn = this.#drawn ?? {
|
|
258
298
|
seconds: this.value,
|
|
@@ -291,7 +331,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
291
331
|
#paintScene(context, scene, isRebuilt) {
|
|
292
332
|
const { dpr, height, pixelsPerSecond, startSeconds, width } = scene;
|
|
293
333
|
const x = (seconds) => (seconds - startSeconds) * pixelsPerSecond;
|
|
294
|
-
const { grid } = scene.colours;
|
|
334
|
+
const { ends, grid } = scene.colours;
|
|
295
335
|
const line = Math.max(1, Math.round(dpr));
|
|
296
336
|
const [opening, closing] = [x(this.min), x(Math.max(this.min, this.max))];
|
|
297
337
|
const [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];
|
|
@@ -302,7 +342,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
302
342
|
if (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);
|
|
303
343
|
paintPlaceholders(context, scene);
|
|
304
344
|
if (this.#peaks) paintWave(context, scene, this.#peaks);
|
|
305
|
-
context.fillStyle =
|
|
345
|
+
context.fillStyle = ends;
|
|
306
346
|
for (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);
|
|
307
347
|
this.#paintMarkers(context, scene, isRebuilt);
|
|
308
348
|
}
|
|
@@ -319,6 +359,12 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
319
359
|
for (const probe of probes) probe.remove();
|
|
320
360
|
return new Map(kinds.map((kind, index) => [kind, read[index] ?? ""]));
|
|
321
361
|
}
|
|
362
|
+
async #repaintAfter(asked) {
|
|
363
|
+
await Promise.allSettled([asked]);
|
|
364
|
+
if (this.#asked !== asked) return;
|
|
365
|
+
this.#asked = void 0;
|
|
366
|
+
this.surface()?.invalidate();
|
|
367
|
+
}
|
|
322
368
|
#sourceSeconds() {
|
|
323
369
|
return this.#readTime?.() ?? this.playback();
|
|
324
370
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { clampProportion } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
3
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
import { bindHoverPreview } from "../lib/hover-preview.js";
|
|
5
|
+
import { sortedRegions } from "../lib/time-regions.js";
|
|
4
6
|
import { SonicWaveElement } from "./wave-element.js";
|
|
5
7
|
import { createMarkerBand } from "../lib/marker-band.js";
|
|
6
|
-
import { stripBars, stripRegions } from "../lib/strip-scene.js";
|
|
8
|
+
import { stripBars, stripGroove, stripRegions } from "../lib/strip-scene.js";
|
|
7
9
|
|
|
8
10
|
//#region src/elements/wavestrip.ts
|
|
9
11
|
const colours = {
|
|
@@ -14,6 +16,7 @@ const colours = {
|
|
|
14
16
|
};
|
|
15
17
|
const lengths = {
|
|
16
18
|
gapRatio: "--_sonic-wavestrip-bar-gap-ratio",
|
|
19
|
+
grooveRatio: "--_sonic-wavestrip-groove-ratio",
|
|
17
20
|
pitch: "--_sonic-wavestrip-bar-pitch",
|
|
18
21
|
radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
|
|
19
22
|
};
|
|
@@ -45,6 +48,13 @@ function fillRegion(context, path, [from, to, style]) {
|
|
|
45
48
|
context.restore();
|
|
46
49
|
}
|
|
47
50
|
var SonicWavestrip = class extends SonicWaveElement {
|
|
51
|
+
get buffered() {
|
|
52
|
+
return this.#buffered.map(([start, end]) => [start, end]);
|
|
53
|
+
}
|
|
54
|
+
set buffered(regions) {
|
|
55
|
+
this.#buffered = sortedRegions(regions);
|
|
56
|
+
this.render();
|
|
57
|
+
}
|
|
48
58
|
get cancellable() {
|
|
49
59
|
return this.hasAttribute("cancellable");
|
|
50
60
|
}
|
|
@@ -64,22 +74,43 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
64
74
|
colours = colours;
|
|
65
75
|
lengths = lengths;
|
|
66
76
|
sheet = "wavestrip.css";
|
|
77
|
+
sizeProperty = "--_sonic-wavestrip-size";
|
|
67
78
|
#bars;
|
|
79
|
+
#buffered = [];
|
|
80
|
+
#hover;
|
|
68
81
|
#markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
|
|
69
82
|
#paintedKey = "";
|
|
70
83
|
#peaks;
|
|
71
84
|
connectedCallback() {
|
|
72
|
-
this.upgradeProperties("cancellable", "peaks");
|
|
85
|
+
this.upgradeProperties("buffered", "cancellable", "peaks");
|
|
73
86
|
super.connectedCallback();
|
|
74
87
|
}
|
|
75
88
|
connect(signal) {
|
|
76
89
|
super.connect(signal);
|
|
77
|
-
this.#
|
|
90
|
+
this.#hover = bindHoverPreview(this.control, {
|
|
91
|
+
canShow: () => this.readout,
|
|
92
|
+
dismiss: () => {
|
|
93
|
+
this.render();
|
|
94
|
+
},
|
|
95
|
+
isTaken: () => this.isEditing() || this.isRevealed(),
|
|
96
|
+
show: () => {
|
|
97
|
+
this.renderReadout();
|
|
98
|
+
this.#placeReadout();
|
|
99
|
+
},
|
|
100
|
+
valueAt: (event) => {
|
|
101
|
+
const axis = this.#axis();
|
|
102
|
+
const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
|
|
103
|
+
const mapping = this.mapping();
|
|
104
|
+
return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
|
|
105
|
+
}
|
|
106
|
+
}, signal);
|
|
78
107
|
this.bindGestures(this.control, signal, (event) => this.#grab(event));
|
|
79
108
|
}
|
|
80
109
|
draw() {
|
|
81
110
|
const surface = this.surface();
|
|
111
|
+
if (this.isEditing() || this.isRevealed()) this.#hover?.clear();
|
|
82
112
|
this.#placeReadout();
|
|
113
|
+
this.showCurrentMarker(this.playback());
|
|
83
114
|
if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
|
|
84
115
|
}
|
|
85
116
|
isEmpty() {
|
|
@@ -100,6 +131,9 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
100
131
|
look.colours[kind]
|
|
101
132
|
]);
|
|
102
133
|
}
|
|
134
|
+
previewValue() {
|
|
135
|
+
return this.#hover?.value();
|
|
136
|
+
}
|
|
103
137
|
renderMarkers() {
|
|
104
138
|
this.#markerBand.render(this.markerList(), this.markerProportions());
|
|
105
139
|
}
|
|
@@ -116,28 +150,18 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
116
150
|
};
|
|
117
151
|
}
|
|
118
152
|
#barsFor({ isDirty, look, size }) {
|
|
119
|
-
const peaks = this.#peaks;
|
|
120
|
-
if (!peaks || peaks.length === 0) return void 0;
|
|
121
153
|
if (!isDirty && this.#bars) return this.#bars;
|
|
154
|
+
const peaks = this.#peaks;
|
|
155
|
+
const groove = stripGroove(size, look.lengths.grooveRatio);
|
|
156
|
+
const drawn = groove ? [groove] : [];
|
|
157
|
+
const bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;
|
|
158
|
+
this.#bars = void 0;
|
|
159
|
+
if (bars.length === 0) return void 0;
|
|
122
160
|
const path = new Path2D();
|
|
123
|
-
for (const { height, radius, width, x, y } of
|
|
161
|
+
for (const { height, radius, width, x, y } of bars) path.roundRect(x, y, width, height, radius);
|
|
124
162
|
this.#bars = path;
|
|
125
163
|
return path;
|
|
126
164
|
}
|
|
127
|
-
#bindHover(strip, signal) {
|
|
128
|
-
strip.addEventListener("pointermove", (event) => {
|
|
129
|
-
if (event.pointerType === "touch" || event.buttons !== 0) return;
|
|
130
|
-
if (!this.readout) return;
|
|
131
|
-
const axis = this.#axis();
|
|
132
|
-
const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
|
|
133
|
-
const mapping = this.mapping();
|
|
134
|
-
this.hoverReadout(this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at)));
|
|
135
|
-
this.#placeReadout();
|
|
136
|
-
}, { signal });
|
|
137
|
-
for (const type of ["pointerdown", "pointerleave"]) strip.addEventListener(type, () => {
|
|
138
|
-
this.hoverReadout(void 0);
|
|
139
|
-
}, { signal });
|
|
140
|
-
}
|
|
141
165
|
#grab(event) {
|
|
142
166
|
const axis = this.#axis();
|
|
143
167
|
const at = (event.clientX - axis.startPx) / axis.travelPx;
|
|
@@ -174,7 +198,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
174
198
|
const { proportionOf } = this.mapping();
|
|
175
199
|
const isScrubbing = this.heldFrom() !== void 0 && this.isRevealed();
|
|
176
200
|
return stripRegions({
|
|
177
|
-
buffered: this
|
|
201
|
+
buffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
|
|
178
202
|
played: proportionOf(this.playback()),
|
|
179
203
|
...isScrubbing ? { scrub: proportionOf(this.value) } : {}
|
|
180
204
|
}, width);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { clampProportion, toNumber } from "../lib/math.js";
|
|
2
|
-
import { requireChild, template } from "../lib/render.js";
|
|
3
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
|
+
import { requireChild, template } from "../lib/render.js";
|
|
5
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
5
6
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
6
7
|
import { Readout } from "./readout.js";
|
|
7
8
|
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
8
9
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
9
10
|
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
10
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
11
11
|
import { resetKeys, valueMapping } from "../lib/value-mapping.js";
|
|
12
12
|
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
13
13
|
|
|
@@ -29,6 +29,7 @@ const orientations = {
|
|
|
29
29
|
};
|
|
30
30
|
const renderXy = template(`
|
|
31
31
|
<div class="sonic-xy">
|
|
32
|
+
<div class="sonic-xy-bracket" aria-hidden="true"></div>
|
|
32
33
|
<div class="sonic-xy-field" aria-hidden="true"></div>
|
|
33
34
|
<div class="sonic-xy-puck">
|
|
34
35
|
<div class="sonic-xy-axis" data-sonic-axis="x" role="slider" tabindex="0"></div>
|
package/dist/default/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { SonicMeter } from "./elements/meter.js";
|
|
|
6
6
|
import { SonicNumber } from "./elements/number.js";
|
|
7
7
|
import { SonicSegmented } from "./elements/segmented.js";
|
|
8
8
|
import { SonicSlider } from "./elements/slider.js";
|
|
9
|
+
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
9
10
|
import { SonicSplit } from "./elements/split.js";
|
|
10
11
|
import { SonicSwitch } from "./elements/switch.js";
|
|
11
12
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
@@ -14,4 +15,4 @@ import { SonicXy } from "./elements/xy.js";
|
|
|
14
15
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
15
16
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
16
17
|
|
|
17
|
-
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|
|
18
|
+
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|
|
@@ -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);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { appendOnce } from "./owned-control.js";
|
|
2
|
+
import { attachSlots } from "./render.js";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/mirror-children.ts
|
|
5
|
+
const hostSlots = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function childHolding(host, node) {
|
|
7
|
+
let current = node;
|
|
8
|
+
while (current && current.parentNode !== host) current = current.parentNode;
|
|
9
|
+
return current ?? void 0;
|
|
10
|
+
}
|
|
11
|
+
function isCopiedChange(host, record, { control, isCopied }) {
|
|
12
|
+
if (record.target === host) return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
|
|
13
|
+
const child = childHolding(host, record.target);
|
|
14
|
+
if (child === void 0) return false;
|
|
15
|
+
return isCopied(child) || record.type === "attributes" && record.target === child;
|
|
16
|
+
}
|
|
17
|
+
function mirrorChildren(host, { control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
|
|
18
|
+
const slots = hostSlots.get(host) ?? attachSlots(host);
|
|
19
|
+
let copies = /* @__PURE__ */ new Map();
|
|
20
|
+
hostSlots.set(host, slots);
|
|
21
|
+
const mirror = (touched) => {
|
|
22
|
+
appendOnce(host, control);
|
|
23
|
+
const children = [...host.childNodes].filter((child) => child !== control);
|
|
24
|
+
const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
|
|
25
|
+
const kept = copies;
|
|
26
|
+
const originals = children.filter((child) => isCopied(child));
|
|
27
|
+
copies = new Map(originals.map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
|
|
28
|
+
place([...copies.values()].filter((made) => made !== void 0));
|
|
29
|
+
slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
|
|
30
|
+
slots.kept.assign(...slotted.filter((child) => isCopied(child)));
|
|
31
|
+
};
|
|
32
|
+
const watch = () => {
|
|
33
|
+
observer.disconnect();
|
|
34
|
+
observer.observe(host, { childList: true });
|
|
35
|
+
for (const child of host.childNodes) {
|
|
36
|
+
if (child === control) continue;
|
|
37
|
+
observer.observe(child, {
|
|
38
|
+
attributes: true,
|
|
39
|
+
characterData: true,
|
|
40
|
+
childList: true,
|
|
41
|
+
subtree: true
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const observer = new MutationObserver((records) => {
|
|
46
|
+
const changes = records.filter((record) => isCopiedChange(host, record, {
|
|
47
|
+
control,
|
|
48
|
+
isCopied
|
|
49
|
+
}));
|
|
50
|
+
if (changes.length === 0) return;
|
|
51
|
+
if (changes.some((record) => record.target === host)) {
|
|
52
|
+
mirror();
|
|
53
|
+
watch();
|
|
54
|
+
} else mirror(new Set(changes.map((record) => childHolding(host, record.target))));
|
|
55
|
+
});
|
|
56
|
+
mirror();
|
|
57
|
+
watch();
|
|
58
|
+
signal.addEventListener("abort", () => {
|
|
59
|
+
observer.disconnect();
|
|
60
|
+
}, { once: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { mirrorChildren };
|