@xsynaptic/sonic-ui 0.24.0 → 0.25.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/CHANGELOG.md +34 -2
- package/README.md +5 -3
- package/dist/default/define/region.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/envelope.js +11 -11
- package/dist/default/elements/form-element.js +10 -0
- package/dist/default/elements/readout.js +3 -2
- package/dist/default/elements/region-layer.js +251 -0
- package/dist/default/elements/region.js +157 -0
- package/dist/default/elements/sonic-element.js +26 -4
- package/dist/default/elements/split.js +2 -2
- package/dist/default/elements/toggle.js +2 -2
- package/dist/default/elements/value-element.js +9 -5
- package/dist/default/elements/value-gestures.js +7 -5
- package/dist/default/elements/wave-element.js +12 -3
- package/dist/default/elements/waveform.js +38 -2
- package/dist/default/elements/wavestrip.js +90 -17
- package/dist/default/elements/xy.js +3 -11
- package/dist/default/index.js +2 -1
- package/dist/default/lib/clock.js +1 -0
- package/dist/default/lib/drag-step.js +1 -1
- package/dist/default/lib/marker-band.js +40 -29
- package/dist/default/lib/region-drag.js +48 -0
- package/dist/default/lib/spoken-duration.js +3 -0
- package/dist/dev/define/region.d.ts +2 -0
- package/dist/dev/define/region.js +8 -0
- package/dist/dev/define/region.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/envelope.js +11 -11
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/form-element.d.ts +1 -0
- package/dist/dev/elements/form-element.js +10 -0
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/readout.js +3 -2
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/region-layer.js +252 -0
- package/dist/dev/elements/region-layer.js.map +1 -0
- package/dist/dev/elements/region.d.ts +29 -0
- package/dist/dev/elements/region.js +165 -0
- package/dist/dev/elements/region.js.map +1 -0
- package/dist/dev/elements/sonic-element.d.ts +1 -0
- package/dist/dev/elements/sonic-element.js +27 -5
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js +2 -2
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/toggle.js +2 -2
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +3 -1
- package/dist/dev/elements/value-element.js +9 -5
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +7 -5
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +7 -1
- package/dist/dev/elements/wave-element.js +12 -3
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +7 -0
- package/dist/dev/elements/waveform.js +38 -2
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +17 -1
- package/dist/dev/elements/wavestrip.js +90 -17
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +3 -11
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/clock.js +1 -0
- package/dist/dev/lib/clock.js.map +1 -1
- package/dist/dev/lib/drag-step.js +1 -1
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/marker-band.d.ts +7 -0
- package/dist/dev/lib/marker-band.js +40 -29
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/region-drag.js +49 -0
- package/dist/dev/lib/region-drag.js.map +1 -0
- package/dist/dev/lib/spoken-duration.js +3 -0
- package/dist/dev/lib/spoken-duration.js.map +1 -1
- package/dist/styles/button.css +42 -15
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +48 -33
- package/dist/styles/envelope.css +7 -6
- package/dist/styles/led.css +28 -13
- package/dist/styles/material/arc.css +2 -2
- package/dist/styles/material/cap.css +25 -15
- package/dist/styles/material/core.css +53 -1
- package/dist/styles/material/glass.css +12 -2
- package/dist/styles/material/groove.css +3 -3
- package/dist/styles/material/keycap.css +5 -5
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +21 -11
- package/dist/styles/material/readout.css +23 -10
- package/dist/styles/material/{bracket.css → reticle.css} +15 -8
- package/dist/styles/material/scale.css +4 -4
- package/dist/styles/material/value.css +12 -3
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/material.css +1 -1
- package/dist/styles/meter.css +24 -11
- package/dist/styles/number.css +2 -1
- package/dist/styles/panel.css +5 -6
- package/dist/styles/region.css +88 -0
- package/dist/styles/ring.css +5 -4
- package/dist/styles/screen.css +7 -5
- package/dist/styles/segmented.css +8 -7
- package/dist/styles/slider.css +40 -32
- package/dist/styles/spectrum.css +9 -1
- package/dist/styles/switch.css +52 -53
- package/dist/styles/toggle.css +33 -38
- package/dist/styles/waveform.css +5 -4
- package/dist/styles/wavestrip.css +15 -12
- package/dist/styles/xy.css +19 -19
- package/package.json +3 -2
|
@@ -8,13 +8,16 @@ const namingAttributes = [
|
|
|
8
8
|
"aria-label",
|
|
9
9
|
"aria-labelledby"
|
|
10
10
|
];
|
|
11
|
+
const canMatchStates = CSS.supports("selector(:state(a))");
|
|
12
|
+
const canReferElements = "ariaLabelledByElements" in Element.prototype;
|
|
11
13
|
function writeLabelledBy(target, labels) {
|
|
12
14
|
const current = target.ariaLabelledByElements;
|
|
13
15
|
if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
|
|
14
16
|
target.ariaLabelledByElements = labels;
|
|
15
17
|
}
|
|
16
|
-
var SonicElement = class extends HTMLElement {
|
|
18
|
+
var SonicElement = class SonicElement extends HTMLElement {
|
|
17
19
|
static observedAttributes = [...namingAttributes, "disabled"];
|
|
20
|
+
static #labelCount = 0;
|
|
18
21
|
get disabled() {
|
|
19
22
|
return this.hasAttribute("disabled");
|
|
20
23
|
}
|
|
@@ -23,7 +26,15 @@ var SonicElement = class extends HTMLElement {
|
|
|
23
26
|
}
|
|
24
27
|
internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
|
|
25
28
|
#connection;
|
|
29
|
+
#customStates = canMatchStates ? this.internals?.states : void 0;
|
|
26
30
|
#states = /* @__PURE__ */ new Set();
|
|
31
|
+
static #labelId(label) {
|
|
32
|
+
if (label.id === "") {
|
|
33
|
+
SonicElement.#labelCount += 1;
|
|
34
|
+
label.id = `sonic-label-${String(SonicElement.#labelCount)}`;
|
|
35
|
+
}
|
|
36
|
+
return label.id;
|
|
37
|
+
}
|
|
27
38
|
blur() {
|
|
28
39
|
this.#focused()?.blur();
|
|
29
40
|
}
|
|
@@ -67,7 +78,7 @@ var SonicElement = class extends HTMLElement {
|
|
|
67
78
|
const value = this.getAttribute(name);
|
|
68
79
|
writeAttribute(target, name, isNamed && value !== null ? value : void 0);
|
|
69
80
|
}
|
|
70
|
-
if (labels.length > 0)
|
|
81
|
+
if (labels.length > 0) this.#writeLabels(target, labels);
|
|
71
82
|
}
|
|
72
83
|
hasState(state) {
|
|
73
84
|
return this.#states.has(state);
|
|
@@ -110,11 +121,11 @@ var SonicElement = class extends HTMLElement {
|
|
|
110
121
|
toggleState(state, isOn) {
|
|
111
122
|
if (isOn) {
|
|
112
123
|
this.#states.add(state);
|
|
113
|
-
this
|
|
124
|
+
this.#customStates?.add(state);
|
|
114
125
|
return;
|
|
115
126
|
}
|
|
116
127
|
this.#states.delete(state);
|
|
117
|
-
this
|
|
128
|
+
this.#customStates?.delete(state);
|
|
118
129
|
}
|
|
119
130
|
upgradeProperties(...names) {
|
|
120
131
|
for (const name of names) {
|
|
@@ -124,6 +135,9 @@ var SonicElement = class extends HTMLElement {
|
|
|
124
135
|
this[name] = value;
|
|
125
136
|
}
|
|
126
137
|
}
|
|
138
|
+
usesChild(_child) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
127
141
|
#checkBox(control, checked, box) {}
|
|
128
142
|
#checkHost(checked) {}
|
|
129
143
|
#focused() {
|
|
@@ -131,6 +145,14 @@ var SonicElement = class extends HTMLElement {
|
|
|
131
145
|
const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
|
|
132
146
|
return active instanceof HTMLElement && this.contains(active) ? active : void 0;
|
|
133
147
|
}
|
|
148
|
+
#writeLabels(target, labels) {
|
|
149
|
+
if (canReferElements) {
|
|
150
|
+
writeLabelledBy(target, labels);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const ids = labels.map((label) => SonicElement.#labelId(label));
|
|
154
|
+
writeAttribute(target, "aria-labelledby", ids.join(" "));
|
|
155
|
+
}
|
|
134
156
|
};
|
|
135
157
|
|
|
136
158
|
//#endregion
|
|
@@ -8,7 +8,7 @@ const modeNames = /* @__PURE__ */ new Set([
|
|
|
8
8
|
"equal",
|
|
9
9
|
"proportional"
|
|
10
10
|
]);
|
|
11
|
-
const
|
|
11
|
+
const gridTolerance = 1e-9;
|
|
12
12
|
const lockAttributes = ["data-sonic-locked", "disabled"];
|
|
13
13
|
function isMode(name) {
|
|
14
14
|
return name !== null && modeNames.has(name);
|
|
@@ -19,7 +19,7 @@ function isLocked(member) {
|
|
|
19
19
|
function gridLimits(mapping, [low, high]) {
|
|
20
20
|
const bottom = mapping.snap(low);
|
|
21
21
|
const top = mapping.snap(high);
|
|
22
|
-
return [bottom < low -
|
|
22
|
+
return [bottom < low - gridTolerance ? mapping.keyTarget("ArrowRight", bottom) ?? bottom : bottom, top > high + gridTolerance ? mapping.keyTarget("ArrowLeft", top) ?? top : top];
|
|
23
23
|
}
|
|
24
24
|
function modelOf(member, value, isFree) {
|
|
25
25
|
const mapping = member.link.model.mapping();
|
|
@@ -19,7 +19,7 @@ const renderPosition = template(`
|
|
|
19
19
|
</button>
|
|
20
20
|
`, HTMLButtonElement);
|
|
21
21
|
const renderBare = template(`<button class="sonic-toggle-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
22
|
-
const
|
|
22
|
+
const pressTolerance = 2;
|
|
23
23
|
var SonicToggle = class extends SonicPositionGroupElement {
|
|
24
24
|
bare = renderBare();
|
|
25
25
|
defaultOrientation = "horizontal";
|
|
@@ -72,7 +72,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
72
72
|
if (!drag.hasMoved && moved < drag.thresholdPx) return;
|
|
73
73
|
drag.hasMoved = true;
|
|
74
74
|
this.toggleState("dragging", true);
|
|
75
|
-
if (moved >=
|
|
75
|
+
if (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;
|
|
76
76
|
if (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId)) drag.isPress = false;
|
|
77
77
|
},
|
|
78
78
|
release: (drag) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toNumber, trimFloat } from "../lib/math.js";
|
|
2
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
|
-
import { Readout } from "./readout.js";
|
|
4
|
+
import { Readout, isUnanchored } from "./readout.js";
|
|
5
5
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
6
6
|
import { parseNumberList } from "../lib/number-list.js";
|
|
7
7
|
import { ValueGestures } from "./value-gestures.js";
|
|
@@ -268,7 +268,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
268
268
|
forwardNaming: (target, isNamed) => {
|
|
269
269
|
this.forwardNaming(target, isNamed);
|
|
270
270
|
},
|
|
271
|
-
hasEntry: () => this.entry !== "none",
|
|
271
|
+
hasEntry: () => !isUnanchored && this.entry !== "none",
|
|
272
272
|
input: (next) => this.input(next),
|
|
273
273
|
isDisabled: () => this.isDisabled(),
|
|
274
274
|
keyStep: () => this.keyStep,
|
|
@@ -286,16 +286,17 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
286
286
|
toggleState: (state, isOn) => {
|
|
287
287
|
this.toggleState(state, isOn);
|
|
288
288
|
}
|
|
289
|
-
}, control);
|
|
289
|
+
}, control, this.focusTarget());
|
|
290
290
|
this.#gestures.bind(signal, grab);
|
|
291
291
|
control.addEventListener("pointerup", (event) => {
|
|
292
292
|
this.#hover?.lift(event);
|
|
293
293
|
}, { signal });
|
|
294
294
|
}
|
|
295
|
-
bindHover(control, signal, { canShow = () => true, place, valueAt }) {
|
|
295
|
+
bindHover(control, signal, { canShow = () => true, changed, place, valueAt }) {
|
|
296
296
|
this.#hover = bindHoverPreview(control, {
|
|
297
297
|
canShow,
|
|
298
298
|
changed: () => {
|
|
299
|
+
changed?.();
|
|
299
300
|
this.dispatchEvent(new Event("sonic-hover", { bubbles: true }));
|
|
300
301
|
},
|
|
301
302
|
dismiss: () => {
|
|
@@ -367,6 +368,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
367
368
|
played: from === void 0 ? this.#model.value : this.#heldAt ?? from
|
|
368
369
|
};
|
|
369
370
|
}
|
|
371
|
+
spokenText(value) {
|
|
372
|
+
return (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(value);
|
|
373
|
+
}
|
|
370
374
|
springTarget() {}
|
|
371
375
|
tapTarget() {}
|
|
372
376
|
travelPx(control, property) {
|
|
@@ -401,7 +405,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
401
405
|
writeAttribute(control, "aria-valuemax", String(high));
|
|
402
406
|
writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
|
|
403
407
|
if (orientation) writeAttribute(control, "aria-orientation", orientation);
|
|
404
|
-
writeAttribute(control, "aria-valuetext",
|
|
408
|
+
writeAttribute(control, "aria-valuetext", this.spokenText(this.#spokenNow()));
|
|
405
409
|
this.forwardNaming(control, true);
|
|
406
410
|
this.#renderDisabled(control);
|
|
407
411
|
this.renderReadout();
|
|
@@ -18,14 +18,16 @@ var ValueGestures = class {
|
|
|
18
18
|
});
|
|
19
19
|
#control;
|
|
20
20
|
#entry;
|
|
21
|
+
#focus;
|
|
21
22
|
#host;
|
|
22
23
|
#isRevealed = false;
|
|
23
24
|
#keyScrub;
|
|
24
25
|
#pointerDrag;
|
|
25
26
|
#pointerType;
|
|
26
|
-
constructor(host, control) {
|
|
27
|
+
constructor(host, control, focus) {
|
|
27
28
|
this.#host = host;
|
|
28
29
|
this.#control = control;
|
|
30
|
+
this.#focus = focus;
|
|
29
31
|
}
|
|
30
32
|
abandon() {
|
|
31
33
|
const drag = this.#dragging();
|
|
@@ -46,7 +48,7 @@ var ValueGestures = class {
|
|
|
46
48
|
if (isMenuPress(event) || host.isDisabled() || entry.isOpen) return;
|
|
47
49
|
host.toggleState("springing", false);
|
|
48
50
|
if (event.pointerType === "touch") event.preventDefault();
|
|
49
|
-
focusByPointer(
|
|
51
|
+
focusByPointer(this.#focus);
|
|
50
52
|
this.#pointerType = event.pointerType;
|
|
51
53
|
if (isResetPress(event)) {
|
|
52
54
|
this.#reset();
|
|
@@ -135,14 +137,14 @@ var ValueGestures = class {
|
|
|
135
137
|
}
|
|
136
138
|
});
|
|
137
139
|
this.#entry = entry;
|
|
138
|
-
entry.bind(
|
|
140
|
+
entry.bind(this.#focus, signal);
|
|
139
141
|
return entry;
|
|
140
142
|
}
|
|
141
143
|
#bindKeys(entry, signal) {
|
|
142
144
|
const control = this.#control;
|
|
143
145
|
const host = this.#host;
|
|
144
146
|
control.addEventListener("keydown", (event) => {
|
|
145
|
-
if (host.isDisabled() || event.defaultPrevented || event.target !==
|
|
147
|
+
if (host.isDisabled() || event.defaultPrevented || event.target !== this.#focus) return;
|
|
146
148
|
host.toggleState("springing", false);
|
|
147
149
|
if (event.key === "Enter") {
|
|
148
150
|
if (host.springTarget() !== void 0 || !host.hasEntry()) return;
|
|
@@ -164,7 +166,7 @@ var ValueGestures = class {
|
|
|
164
166
|
if (isMeta && this.#dragging()) return;
|
|
165
167
|
if (isMeta || host.model.mapping().keyTarget(event.key, host.model.value) !== void 0) this.#springBack();
|
|
166
168
|
}, { signal });
|
|
167
|
-
|
|
169
|
+
this.#focus.addEventListener("blur", () => {
|
|
168
170
|
this.#endKeyScrub();
|
|
169
171
|
this.#concealKeyReveal();
|
|
170
172
|
this.#springBack();
|
|
@@ -48,6 +48,18 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
48
48
|
this.upgradeProperties("fill", "markers");
|
|
49
49
|
super.connectedCallback();
|
|
50
50
|
}
|
|
51
|
+
/** Copies; none outside the canvas */
|
|
52
|
+
markersFromPoint(clientX, clientY) {
|
|
53
|
+
const box = this.canvas.getBoundingClientRect();
|
|
54
|
+
if (!(clientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom)) return [];
|
|
55
|
+
return this.markersInReach({
|
|
56
|
+
x: clientX - box.left,
|
|
57
|
+
y: clientY - box.top
|
|
58
|
+
}, box.width).flatMap((index) => {
|
|
59
|
+
const marker = this.#markers[index];
|
|
60
|
+
return marker ? [{ ...marker }] : [];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
51
63
|
repaint() {
|
|
52
64
|
this.#surface?.rebuild();
|
|
53
65
|
}
|
|
@@ -75,9 +87,6 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
75
87
|
this.render();
|
|
76
88
|
this.#surface.setFill(this.fill);
|
|
77
89
|
}
|
|
78
|
-
focusTarget() {
|
|
79
|
-
return this.control;
|
|
80
|
-
}
|
|
81
90
|
markerList() {
|
|
82
91
|
return this.#markers;
|
|
83
92
|
}
|
|
@@ -15,6 +15,7 @@ import { bindZoom, bindZoomKeys } from "../lib/zoom-gesture.js";
|
|
|
15
15
|
const defaultZoom = 70;
|
|
16
16
|
const defaultZoomMin = 20;
|
|
17
17
|
const defaultZoomMax = 280;
|
|
18
|
+
const lineReachPx = 4;
|
|
18
19
|
const renderWaveform = template(`
|
|
19
20
|
<div class="sonic-waveform" role="slider" tabindex="0">
|
|
20
21
|
<canvas class="sonic-waveform-canvas" aria-hidden="true"></canvas>
|
|
@@ -201,10 +202,21 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
201
202
|
super.attributeChangedCallback(name);
|
|
202
203
|
if (name === "zoomable") this.#zoomGesture?.sync();
|
|
203
204
|
}
|
|
205
|
+
/** Viewport x of a time as last drawn; not clamped to the window */
|
|
206
|
+
clientXOf(value) {
|
|
207
|
+
const { left, width } = this.canvas.getBoundingClientRect();
|
|
208
|
+
return left + this.#drawnX(value, width);
|
|
209
|
+
}
|
|
204
210
|
connectedCallback() {
|
|
205
211
|
this.upgradeProperties("bands", "peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
206
212
|
super.connectedCallback();
|
|
207
213
|
}
|
|
214
|
+
/** The time at a viewport x as last drawn */
|
|
215
|
+
valueFromPoint(clientX, _clientY) {
|
|
216
|
+
const { left, width } = this.canvas.getBoundingClientRect();
|
|
217
|
+
const { startSeconds, zoom } = this.#drawnWindow(width);
|
|
218
|
+
return clamp(startSeconds + (clientX - left) / zoom, ...this.mapping().bounds);
|
|
219
|
+
}
|
|
208
220
|
connect(signal) {
|
|
209
221
|
super.connect(signal);
|
|
210
222
|
const zoomTarget = {
|
|
@@ -231,9 +243,18 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
231
243
|
surface?.requestFrame();
|
|
232
244
|
if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
|
|
233
245
|
}
|
|
246
|
+
focusTarget() {
|
|
247
|
+
return this.control;
|
|
248
|
+
}
|
|
234
249
|
isEmpty() {
|
|
235
250
|
return !this.#peaks || this.#peaks.samples.length === 0;
|
|
236
251
|
}
|
|
252
|
+
markersInReach({ x }, widthPx) {
|
|
253
|
+
return this.markerList().map(({ start }, index) => ({
|
|
254
|
+
distance: Math.abs(this.#drawnX(start, widthPx) - x),
|
|
255
|
+
index
|
|
256
|
+
})).filter(({ distance }) => distance <= lineReachPx).toSorted((first, second) => first.distance - second.distance).map(({ index }) => index);
|
|
257
|
+
}
|
|
237
258
|
paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
|
|
238
259
|
const held = this.#held();
|
|
239
260
|
const isPaged = look.isReducedMotion && this.reducedMotion === "page";
|
|
@@ -258,7 +279,8 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
258
279
|
const { seconds, view } = timeline;
|
|
259
280
|
this.#drawn = {
|
|
260
281
|
seconds,
|
|
261
|
-
startSeconds: view.startSeconds
|
|
282
|
+
startSeconds: view.startSeconds,
|
|
283
|
+
zoom: this.zoom
|
|
262
284
|
};
|
|
263
285
|
placeLine(this.#playhead, timeline.playheadAt);
|
|
264
286
|
placeLine(this.#ghost, timeline.ghostAt);
|
|
@@ -281,7 +303,10 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
281
303
|
}, {
|
|
282
304
|
bands: this.#tint(isRebuilt),
|
|
283
305
|
kindColours: this.#kindColours,
|
|
284
|
-
markers: this.markerList()
|
|
306
|
+
markers: this.markerList().map((marker) => ({
|
|
307
|
+
...marker,
|
|
308
|
+
start: clamp(marker.start, ...range)
|
|
309
|
+
})),
|
|
285
310
|
peaks: this.#peaks,
|
|
286
311
|
range
|
|
287
312
|
});
|
|
@@ -311,6 +336,17 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
311
336
|
scrubsKeyRepeat() {
|
|
312
337
|
return true;
|
|
313
338
|
}
|
|
339
|
+
#drawnWindow(widthPx) {
|
|
340
|
+
const zoom = this.#drawn?.zoom ?? this.zoom;
|
|
341
|
+
return {
|
|
342
|
+
startSeconds: this.#drawn?.startSeconds ?? this.value - widthPx / zoom / 2,
|
|
343
|
+
zoom
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
#drawnX(value, widthPx) {
|
|
347
|
+
const { startSeconds, zoom } = this.#drawnWindow(widthPx);
|
|
348
|
+
return (clamp(value, ...this.mapping().bounds) - startSeconds) * zoom;
|
|
349
|
+
}
|
|
314
350
|
#duePending() {
|
|
315
351
|
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
316
352
|
if (due.length < this.#pending.length) this.surface()?.requestFrame();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { clampProportion } from "../lib/math.js";
|
|
1
|
+
import { clamp, 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
4
|
import { scrubRegions } from "../lib/scrub.js";
|
|
5
5
|
import { sortedRegions } from "../lib/time-regions.js";
|
|
6
6
|
import { SonicWaveElement } from "./wave-element.js";
|
|
7
7
|
import { createMarkerBand } from "../lib/marker-band.js";
|
|
8
|
+
import { RegionLayer } from "./region-layer.js";
|
|
8
9
|
import { stripBars, stripGroove, stripRegions } from "../lib/strip-scene.js";
|
|
9
10
|
|
|
10
11
|
//#region src/elements/wavestrip.ts
|
|
@@ -23,9 +24,10 @@ const lengths = {
|
|
|
23
24
|
};
|
|
24
25
|
const cancelZonePx = 48;
|
|
25
26
|
const renderWavestrip = template(`
|
|
26
|
-
<div class="sonic-wavestrip"
|
|
27
|
-
<canvas class="sonic-wavestrip-canvas"
|
|
27
|
+
<div class="sonic-wavestrip">
|
|
28
|
+
<canvas class="sonic-wavestrip-canvas" role="slider" tabindex="0"></canvas>
|
|
28
29
|
<div class="sonic-wavestrip-markers" aria-hidden="true"></div>
|
|
30
|
+
<div class="sonic-wavestrip-regions"></div>
|
|
29
31
|
<div class="sonic-wavestrip-readout" popover="manual">
|
|
30
32
|
<span></span>
|
|
31
33
|
<input
|
|
@@ -63,6 +65,14 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
63
65
|
set cancellable(isCancellable) {
|
|
64
66
|
this.reflect("cancellable", isCancellable);
|
|
65
67
|
}
|
|
68
|
+
/** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */
|
|
69
|
+
get formatSpokenRegion() {
|
|
70
|
+
return this.#formatSpokenRegion;
|
|
71
|
+
}
|
|
72
|
+
set formatSpokenRegion(format) {
|
|
73
|
+
this.#formatSpokenRegion = format;
|
|
74
|
+
this.#regions?.render();
|
|
75
|
+
}
|
|
66
76
|
/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */
|
|
67
77
|
get peaks() {
|
|
68
78
|
return this.#peaks;
|
|
@@ -72,6 +82,25 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
72
82
|
this.renderEmpty();
|
|
73
83
|
this.surface()?.invalidate();
|
|
74
84
|
}
|
|
85
|
+
/** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */
|
|
86
|
+
get preview() {
|
|
87
|
+
return this.#preview;
|
|
88
|
+
}
|
|
89
|
+
set preview(seconds) {
|
|
90
|
+
this.#preview = Number.isFinite(seconds) ? seconds ?? void 0 : void 0;
|
|
91
|
+
this.render();
|
|
92
|
+
}
|
|
93
|
+
/** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */
|
|
94
|
+
get renderMarker() {
|
|
95
|
+
return this.#renderMarker;
|
|
96
|
+
}
|
|
97
|
+
set renderMarker(render) {
|
|
98
|
+
this.#renderMarker = render;
|
|
99
|
+
this.#drawMarker = render && ((marker, element) => {
|
|
100
|
+
render({ ...marker }, element);
|
|
101
|
+
});
|
|
102
|
+
if (this.isBound()) this.renderMarkers();
|
|
103
|
+
}
|
|
75
104
|
control = renderWavestrip();
|
|
76
105
|
canvas = requireChild(this.control, ".sonic-wavestrip-canvas", HTMLCanvasElement);
|
|
77
106
|
colours = colours;
|
|
@@ -81,43 +110,75 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
81
110
|
sizeProperty = "--_sonic-wavestrip-size";
|
|
82
111
|
#bars;
|
|
83
112
|
#buffered = [];
|
|
113
|
+
#drawMarker;
|
|
114
|
+
#formatSpokenRegion;
|
|
84
115
|
#markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
|
|
85
116
|
#paintedKey = "";
|
|
86
117
|
#peaks;
|
|
118
|
+
#preview;
|
|
119
|
+
#regions;
|
|
120
|
+
#renderMarker;
|
|
87
121
|
/** Viewport x of a time, clamped to the strip */
|
|
88
122
|
clientXOf(value) {
|
|
89
123
|
const { startPx, travelPx } = this.#axis();
|
|
90
124
|
return startPx + this.mapping().proportionOf(value) * travelPx;
|
|
91
125
|
}
|
|
92
126
|
connectedCallback() {
|
|
93
|
-
this.upgradeProperties("buffered", "cancellable", "peaks");
|
|
127
|
+
this.upgradeProperties("buffered", "cancellable", "formatSpokenRegion", "peaks", "preview", "renderMarker");
|
|
94
128
|
super.connectedCallback();
|
|
95
129
|
}
|
|
130
|
+
valueFromPoint(clientX, _clientY) {
|
|
131
|
+
const { startPx, travelPx } = this.#axis();
|
|
132
|
+
const mapping = this.mapping();
|
|
133
|
+
return clamp(mapping.valueAt(clampProportion((clientX - startPx) / travelPx)), ...mapping.bounds);
|
|
134
|
+
}
|
|
96
135
|
connect(signal) {
|
|
97
136
|
const { control } = this;
|
|
137
|
+
this.#regions = new RegionLayer({
|
|
138
|
+
axis: () => this.#axis(),
|
|
139
|
+
control,
|
|
140
|
+
element: this,
|
|
141
|
+
isDisabled: () => this.isDisabled(),
|
|
142
|
+
layer: requireChild(control, ".sonic-wavestrip-regions", HTMLDivElement),
|
|
143
|
+
mapping: () => this.mapping(),
|
|
144
|
+
outside: () => this.#outside(),
|
|
145
|
+
seek: (event) => {
|
|
146
|
+
if (this.input(this.valueFromPoint(event.clientX, event.clientY))) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
147
|
+
},
|
|
148
|
+
speak: ({ end, start }) => this.#formatSpokenRegion?.(start, end) ?? this.spokenText(start) ?? this.notation.format(start)
|
|
149
|
+
}, signal);
|
|
98
150
|
super.connect(signal);
|
|
99
151
|
this.bindHover(control, signal, {
|
|
152
|
+
changed: () => {
|
|
153
|
+
if (this.#preview !== void 0) this.render();
|
|
154
|
+
},
|
|
100
155
|
place: () => {
|
|
101
156
|
this.#placeReadout();
|
|
102
157
|
},
|
|
103
|
-
valueAt: (event) =>
|
|
104
|
-
const axis = this.#axis();
|
|
105
|
-
const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
|
|
106
|
-
const mapping = this.mapping();
|
|
107
|
-
return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
|
|
108
|
-
}
|
|
158
|
+
valueAt: (event) => this.#markerStartAt(event, this.#axis()) ?? this.mapping().snap(this.valueFromPoint(event.clientX, event.clientY))
|
|
109
159
|
});
|
|
110
160
|
this.bindGestures(control, signal, (event) => this.#grab(event));
|
|
111
161
|
}
|
|
112
162
|
draw() {
|
|
113
163
|
const surface = this.surface();
|
|
114
164
|
this.#placeReadout();
|
|
165
|
+
this.#regions?.follow();
|
|
115
166
|
this.showCurrentMarker(this.scrubState().played);
|
|
116
167
|
if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
|
|
117
168
|
}
|
|
169
|
+
focusTarget() {
|
|
170
|
+
return this.canvas;
|
|
171
|
+
}
|
|
118
172
|
isEmpty() {
|
|
119
173
|
return !this.#peaks || this.#peaks.length === 0;
|
|
120
174
|
}
|
|
175
|
+
/** The first point marker is the one a press there snaps to */
|
|
176
|
+
markersInReach(point, widthPx) {
|
|
177
|
+
const regions = this.markerProportions().flatMap(([from, to], index) => {
|
|
178
|
+
return this.markerList()[index]?.end !== void 0 && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];
|
|
179
|
+
});
|
|
180
|
+
return [...this.#markerBand.inReach(point, widthPx), ...regions];
|
|
181
|
+
}
|
|
121
182
|
paint(context, frame) {
|
|
122
183
|
const { look, size } = frame;
|
|
123
184
|
const strip = this.#stripRegions(size.width);
|
|
@@ -134,11 +195,14 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
134
195
|
]);
|
|
135
196
|
}
|
|
136
197
|
renderMarkers() {
|
|
137
|
-
this.#markerBand.render(this.markerList(), this.markerProportions());
|
|
198
|
+
this.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);
|
|
138
199
|
}
|
|
139
200
|
scrubsKeyRepeat() {
|
|
140
201
|
return true;
|
|
141
202
|
}
|
|
203
|
+
usesChild(child) {
|
|
204
|
+
return child instanceof Element && child.localName === "sonic-region";
|
|
205
|
+
}
|
|
142
206
|
#axis() {
|
|
143
207
|
const box = this.canvas.getBoundingClientRect();
|
|
144
208
|
return {
|
|
@@ -163,6 +227,13 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
163
227
|
}
|
|
164
228
|
#grab(event) {
|
|
165
229
|
const axis = this.#axis();
|
|
230
|
+
const marker = this.#markerStartAt(event, axis);
|
|
231
|
+
const regions = this.#regions;
|
|
232
|
+
if (regions?.isHeld() === true) {
|
|
233
|
+
regions.abandon();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (marker === void 0 && regions?.take(event) === true) return void 0;
|
|
166
237
|
const at = (event.clientX - axis.startPx) / axis.travelPx;
|
|
167
238
|
const outside = this.#outside();
|
|
168
239
|
const grabbed = {
|
|
@@ -170,16 +241,16 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
170
241
|
position: axis.position,
|
|
171
242
|
travelPx: axis.travelPx
|
|
172
243
|
};
|
|
173
|
-
this.input(
|
|
244
|
+
this.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));
|
|
174
245
|
return outside ? {
|
|
175
246
|
...grabbed,
|
|
176
247
|
outside
|
|
177
248
|
} : grabbed;
|
|
178
249
|
}
|
|
179
|
-
#
|
|
180
|
-
const index = this.#markerBand.
|
|
181
|
-
x:
|
|
182
|
-
y:
|
|
250
|
+
#markerStartAt({ clientX, clientY }, { startPx, topPx, travelPx }) {
|
|
251
|
+
const [index] = this.#markerBand.inReach({
|
|
252
|
+
x: clientX - startPx,
|
|
253
|
+
y: clientY - topPx
|
|
183
254
|
}, travelPx);
|
|
184
255
|
return index === void 0 ? void 0 : this.markerList()[index]?.start;
|
|
185
256
|
}
|
|
@@ -196,7 +267,9 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
196
267
|
#stripRegions(width) {
|
|
197
268
|
const mapping = this.mapping();
|
|
198
269
|
const { proportionOf } = mapping;
|
|
199
|
-
const
|
|
270
|
+
const state = this.scrubState();
|
|
271
|
+
const preview = state.from !== void 0 || this.hoverValue !== void 0 || this.#preview === void 0 ? void 0 : proportionOf(this.#preview);
|
|
272
|
+
const { played, scrub = preview } = scrubRegions(state, this.value, mapping);
|
|
200
273
|
return stripRegions({
|
|
201
274
|
buffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
|
|
202
275
|
played,
|
|
@@ -30,7 +30,7 @@ const orientations = {
|
|
|
30
30
|
};
|
|
31
31
|
const renderXy = template(`
|
|
32
32
|
<div class="sonic-xy">
|
|
33
|
-
<div class="sonic-xy-
|
|
33
|
+
<div class="sonic-xy-reticle" aria-hidden="true"></div>
|
|
34
34
|
<div class="sonic-xy-field" aria-hidden="true"></div>
|
|
35
35
|
<div class="sonic-xy-puck">
|
|
36
36
|
<div class="sonic-xy-axis" data-sonic-axis="x" role="slider" tabindex="0"></div>
|
|
@@ -404,16 +404,8 @@ var SonicXy = class extends SonicFormElement {
|
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
#renderForm() {
|
|
407
|
-
const {
|
|
408
|
-
|
|
409
|
-
if (name === "") {
|
|
410
|
-
this.writeFormValue(null, state);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
const fields = new FormData();
|
|
414
|
-
fields.set(`${name}.x`, String(x));
|
|
415
|
-
fields.set(`${name}.y`, String(y));
|
|
416
|
-
this.writeFormValue(fields, state);
|
|
407
|
+
const { x, y } = this;
|
|
408
|
+
this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
|
|
417
409
|
}
|
|
418
410
|
#renderReadout() {
|
|
419
411
|
const { isRevealed } = this.#claim;
|
package/dist/default/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { envelopeCurve } from "./lib/envelope-curve.js";
|
|
|
4
4
|
import { SonicEnvelope } from "./elements/envelope.js";
|
|
5
5
|
import { SonicMeter } from "./elements/meter.js";
|
|
6
6
|
import { SonicNumber } from "./elements/number.js";
|
|
7
|
+
import { SonicRegion } from "./elements/region.js";
|
|
7
8
|
import { SonicSegmented } from "./elements/segmented.js";
|
|
8
9
|
import { SonicSlider } from "./elements/slider.js";
|
|
9
10
|
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
@@ -17,4 +18,4 @@ import { SonicXy } from "./elements/xy.js";
|
|
|
17
18
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
18
19
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
19
20
|
|
|
20
|
-
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
21
|
+
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|