@xsynaptic/sonic-ui 0.24.1 → 0.26.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/CHANGELOG.md +42 -2
- package/README.md +6 -4
- package/dist/default/define/region.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +3 -0
- package/dist/default/elements/dial.js +3 -0
- package/dist/default/elements/envelope.js +14 -11
- package/dist/default/elements/form-element.js +10 -0
- package/dist/default/elements/meter.js +3 -0
- package/dist/default/elements/number.js +3 -0
- package/dist/default/elements/radio-group.js +4 -1
- package/dist/default/elements/readout.js +1 -1
- package/dist/default/elements/region-layer.js +253 -0
- package/dist/default/elements/region.js +161 -0
- package/dist/default/elements/slider.js +3 -0
- package/dist/default/elements/sonic-element.js +7 -9
- package/dist/default/elements/split.js +2 -2
- package/dist/default/elements/toggle.js +4 -4
- package/dist/default/elements/value-element.js +21 -3
- package/dist/default/elements/value-gestures.js +7 -5
- package/dist/default/elements/wave-element.js +15 -3
- package/dist/default/elements/waveform.js +38 -2
- package/dist/default/elements/wavestrip.js +90 -17
- package/dist/default/elements/xy.js +6 -11
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +22 -1
- package/dist/default/lib/clock.js +1 -0
- package/dist/default/lib/drag-step.js +1 -1
- package/dist/default/lib/key-press.js +2 -2
- package/dist/default/lib/marker-band.js +40 -29
- package/dist/default/lib/region-drag.js +48 -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/button.d.ts +1 -0
- package/dist/dev/elements/button.js +3 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +1 -0
- package/dist/dev/elements/dial.js +3 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +1 -0
- package/dist/dev/elements/envelope.js +14 -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/meter.d.ts +1 -0
- package/dist/dev/elements/meter.js +3 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +1 -0
- package/dist/dev/elements/number.js +3 -0
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +1 -0
- package/dist/dev/elements/radio-group.js +4 -1
- package/dist/dev/elements/radio-group.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/region-layer.js +254 -0
- package/dist/dev/elements/region-layer.js.map +1 -0
- package/dist/dev/elements/region.d.ts +30 -0
- package/dist/dev/elements/region.js +169 -0
- package/dist/dev/elements/region.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +1 -0
- package/dist/dev/elements/slider.js +3 -0
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +2 -0
- package/dist/dev/elements/sonic-element.js +8 -10
- 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 +4 -4
- 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 +21 -3
- 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 +8 -1
- package/dist/dev/elements/wave-element.js +15 -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.d.ts +1 -0
- package/dist/dev/elements/xy.js +6 -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/canvas-surface.js +22 -1
- package/dist/dev/lib/canvas-surface.js.map +1 -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/key-press.js +2 -2
- package/dist/dev/lib/key-press.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/styles/button.css +23 -13
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +41 -24
- package/dist/styles/envelope.css +12 -5
- package/dist/styles/led.css +8 -8
- package/dist/styles/material/arc.css +2 -2
- package/dist/styles/material/cap.css +34 -13
- package/dist/styles/material/glass.css +1 -1
- package/dist/styles/material/groove.css +3 -3
- package/dist/styles/material/keycap.css +2 -2
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +20 -6
- package/dist/styles/material/readout.css +14 -5
- package/dist/styles/material/{bracket.css → reticle.css} +15 -8
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/material.css +1 -1
- package/dist/styles/meter.css +29 -16
- package/dist/styles/number.css +3 -2
- package/dist/styles/panel.css +3 -3
- package/dist/styles/region.css +94 -0
- package/dist/styles/ring.css +2 -1
- package/dist/styles/screen.css +7 -5
- package/dist/styles/segmented.css +5 -4
- package/dist/styles/slider.css +24 -15
- package/dist/styles/spectrum.css +9 -1
- package/dist/styles/switch.css +9 -2
- package/dist/styles/toggle.css +5 -4
- package/dist/styles/waveform.css +10 -3
- package/dist/styles/wavestrip.css +17 -8
- package/dist/styles/xy.css +24 -11
- package/package.json +3 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
2
|
+
|
|
3
|
+
//#region src/elements/region.ts
|
|
4
|
+
const owners = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
let link;
|
|
6
|
+
function linkRegion(element) {
|
|
7
|
+
return link(element);
|
|
8
|
+
}
|
|
9
|
+
function ownRegions(host, changed, signal) {
|
|
10
|
+
owners.set(host, changed);
|
|
11
|
+
signal.addEventListener("abort", () => {
|
|
12
|
+
if (owners.get(host) === changed) owners.delete(host);
|
|
13
|
+
}, { once: true });
|
|
14
|
+
}
|
|
15
|
+
var SonicRegion = class extends SonicFormElement {
|
|
16
|
+
static observedAttributes = [
|
|
17
|
+
...SonicFormElement.observedAttributes,
|
|
18
|
+
"end",
|
|
19
|
+
"kind",
|
|
20
|
+
"name",
|
|
21
|
+
"start",
|
|
22
|
+
"tabindex"
|
|
23
|
+
];
|
|
24
|
+
static {
|
|
25
|
+
link = (element) => ({
|
|
26
|
+
change: () => {
|
|
27
|
+
element.#report("change");
|
|
28
|
+
},
|
|
29
|
+
draw: (part) => {
|
|
30
|
+
element.#draw(part);
|
|
31
|
+
},
|
|
32
|
+
hold: (by) => {
|
|
33
|
+
element.#held = by;
|
|
34
|
+
element.toggleState("dragging", by === "pointer");
|
|
35
|
+
},
|
|
36
|
+
input: (span) => element.#input(span),
|
|
37
|
+
isDisabled: () => element.isDisabled(),
|
|
38
|
+
span: () => {
|
|
39
|
+
const { end, start } = element.#span;
|
|
40
|
+
return {
|
|
41
|
+
end: Math.max(start, end),
|
|
42
|
+
start: Math.min(start, end)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
get dragging() {
|
|
48
|
+
return this.hasState("dragging");
|
|
49
|
+
}
|
|
50
|
+
get end() {
|
|
51
|
+
return this.#span.end;
|
|
52
|
+
}
|
|
53
|
+
set end(next) {
|
|
54
|
+
if (!this.#held) this.reflect("end", next);
|
|
55
|
+
}
|
|
56
|
+
/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */
|
|
57
|
+
get kind() {
|
|
58
|
+
return this.getAttribute("kind") ?? void 0;
|
|
59
|
+
}
|
|
60
|
+
set kind(kind) {
|
|
61
|
+
this.reflect("kind", kind);
|
|
62
|
+
}
|
|
63
|
+
get start() {
|
|
64
|
+
return this.#span.start;
|
|
65
|
+
}
|
|
66
|
+
set start(next) {
|
|
67
|
+
if (!this.#held) this.reflect("start", next);
|
|
68
|
+
}
|
|
69
|
+
#formState = "";
|
|
70
|
+
#held;
|
|
71
|
+
#part;
|
|
72
|
+
#span = {
|
|
73
|
+
end: 0,
|
|
74
|
+
start: 0
|
|
75
|
+
};
|
|
76
|
+
attributeChangedCallback(name) {
|
|
77
|
+
const isSpan = name === "end" || name === "start";
|
|
78
|
+
if (isSpan && this.#held) return;
|
|
79
|
+
if (isSpan) this.#readSpan(name);
|
|
80
|
+
this.render();
|
|
81
|
+
}
|
|
82
|
+
connectedCallback() {
|
|
83
|
+
this.upgradeProperties("end", "kind", "start");
|
|
84
|
+
super.connectedCallback();
|
|
85
|
+
}
|
|
86
|
+
formResetCallback() {
|
|
87
|
+
if (this.#held) return;
|
|
88
|
+
this.#readSpan("start");
|
|
89
|
+
this.#readSpan("end");
|
|
90
|
+
this.render();
|
|
91
|
+
}
|
|
92
|
+
connect() {
|
|
93
|
+
this.render();
|
|
94
|
+
if (this.parentElement?.localName !== "sonic-wavestrip");
|
|
95
|
+
}
|
|
96
|
+
focusTarget() {
|
|
97
|
+
return this.#part;
|
|
98
|
+
}
|
|
99
|
+
render() {
|
|
100
|
+
if (!this.isBound() || !this.parentElement) return;
|
|
101
|
+
owners.get(this.parentElement)?.(this);
|
|
102
|
+
}
|
|
103
|
+
restoreState(state) {
|
|
104
|
+
const [start = NaN, end = NaN] = state.split(",").map(Number);
|
|
105
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return;
|
|
106
|
+
this.#span = {
|
|
107
|
+
end,
|
|
108
|
+
start
|
|
109
|
+
};
|
|
110
|
+
this.render();
|
|
111
|
+
}
|
|
112
|
+
stateTarget() {
|
|
113
|
+
return this.#part;
|
|
114
|
+
}
|
|
115
|
+
#draw(part) {
|
|
116
|
+
this.#part = part;
|
|
117
|
+
this.forwardNaming(part, true);
|
|
118
|
+
this.#writeForm();
|
|
119
|
+
}
|
|
120
|
+
#input(span) {
|
|
121
|
+
const hasMoved = span.start !== this.#span.start || span.end !== this.#span.end;
|
|
122
|
+
if (hasMoved) {
|
|
123
|
+
this.#span = span;
|
|
124
|
+
this.render();
|
|
125
|
+
this.#report("input");
|
|
126
|
+
}
|
|
127
|
+
return hasMoved;
|
|
128
|
+
}
|
|
129
|
+
#readSpan(name) {
|
|
130
|
+
const { end, start } = this.#span;
|
|
131
|
+
if (name === "end") {
|
|
132
|
+
this.#span = {
|
|
133
|
+
end: this.numberAttribute("end", start),
|
|
134
|
+
start
|
|
135
|
+
};
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const next = this.numberAttribute("start", 0);
|
|
139
|
+
this.#span = {
|
|
140
|
+
end: this.hasAttribute("end") ? end : next,
|
|
141
|
+
start: next
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
#report(type) {
|
|
145
|
+
this.dispatchEvent(new Event(type, {
|
|
146
|
+
bubbles: true,
|
|
147
|
+
composed: type === "input"
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
#writeForm() {
|
|
151
|
+
const { end, start } = this.#span;
|
|
152
|
+
const state = `${String(start)},${String(end)}`;
|
|
153
|
+
const formState = `${this.name}=${state}`;
|
|
154
|
+
if (formState === this.#formState) return;
|
|
155
|
+
this.#formState = formState;
|
|
156
|
+
this.writeFormFields([["start", start], ["end", end]], state);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
export { SonicRegion, linkRegion, ownRegions };
|
|
@@ -152,6 +152,9 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
152
152
|
springTarget() {
|
|
153
153
|
return this.spring ? this.originValue() : void 0;
|
|
154
154
|
}
|
|
155
|
+
stateTarget() {
|
|
156
|
+
return this.#slider;
|
|
157
|
+
}
|
|
155
158
|
#axis(slider, cap) {
|
|
156
159
|
const box = slider.getBoundingClientRect();
|
|
157
160
|
const capBox = cap.getBoundingClientRect();
|
|
@@ -8,7 +8,6 @@ const namingAttributes = [
|
|
|
8
8
|
"aria-label",
|
|
9
9
|
"aria-labelledby"
|
|
10
10
|
];
|
|
11
|
-
const canMatchStates = CSS.supports("selector(:state(a))");
|
|
12
11
|
const canReferElements = "ariaLabelledByElements" in Element.prototype;
|
|
13
12
|
function writeLabelledBy(target, labels) {
|
|
14
13
|
const current = target.ariaLabelledByElements;
|
|
@@ -26,7 +25,6 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
26
25
|
}
|
|
27
26
|
internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
|
|
28
27
|
#connection;
|
|
29
|
-
#customStates = canMatchStates ? this.internals?.states : void 0;
|
|
30
28
|
#states = /* @__PURE__ */ new Set();
|
|
31
29
|
static #labelId(label) {
|
|
32
30
|
if (label.id === "") {
|
|
@@ -118,14 +116,11 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
118
116
|
}
|
|
119
117
|
this.setAttribute(name, value === true ? "" : value.toString());
|
|
120
118
|
}
|
|
119
|
+
stateTarget() {}
|
|
121
120
|
toggleState(state, isOn) {
|
|
122
|
-
if (isOn)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
this.#states.delete(state);
|
|
128
|
-
this.#customStates?.delete(state);
|
|
121
|
+
if (isOn) this.#states.add(state);
|
|
122
|
+
else this.#states.delete(state);
|
|
123
|
+
this.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);
|
|
129
124
|
}
|
|
130
125
|
upgradeProperties(...names) {
|
|
131
126
|
for (const name of names) {
|
|
@@ -135,6 +130,9 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
135
130
|
this[name] = value;
|
|
136
131
|
}
|
|
137
132
|
}
|
|
133
|
+
usesChild(_child) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
138
136
|
#checkBox(control, checked, box) {}
|
|
139
137
|
#checkHost(checked) {}
|
|
140
138
|
#focused() {
|
|
@@ -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,11 +72,11 @@ 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) => {
|
|
79
|
-
delete drag.marked.dataset.
|
|
79
|
+
delete drag.marked.dataset.sonicActive;
|
|
80
80
|
this.#draggedTo = void 0;
|
|
81
81
|
this.toggleState("dragging", false);
|
|
82
82
|
this.render();
|
|
@@ -94,7 +94,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
94
94
|
const travel = this.#travel(event, isOnCap);
|
|
95
95
|
if (!travel || !isOnCap && this.isOptionDisabled(position)) return void 0;
|
|
96
96
|
const marked = this.options()[position] ?? this.bare;
|
|
97
|
-
marked.toggleAttribute("data-sonic-
|
|
97
|
+
marked.toggleAttribute("data-sonic-active", true);
|
|
98
98
|
return {
|
|
99
99
|
hasMoved: false,
|
|
100
100
|
isHolding: this.#holdFrom(position, event.pointerId),
|
|
@@ -233,6 +233,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
233
233
|
#model = createValueModel(() => this.#spec());
|
|
234
234
|
#parseValue;
|
|
235
235
|
#readout;
|
|
236
|
+
#said;
|
|
236
237
|
attributeChangedCallback(name) {
|
|
237
238
|
if (name === "value" && this.#isHeld()) {
|
|
238
239
|
this.#writeHeld(this.numberAttribute("value", this.min));
|
|
@@ -286,16 +287,17 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
286
287
|
toggleState: (state, isOn) => {
|
|
287
288
|
this.toggleState(state, isOn);
|
|
288
289
|
}
|
|
289
|
-
}, control);
|
|
290
|
+
}, control, this.focusTarget());
|
|
290
291
|
this.#gestures.bind(signal, grab);
|
|
291
292
|
control.addEventListener("pointerup", (event) => {
|
|
292
293
|
this.#hover?.lift(event);
|
|
293
294
|
}, { signal });
|
|
294
295
|
}
|
|
295
|
-
bindHover(control, signal, { canShow = () => true, place, valueAt }) {
|
|
296
|
+
bindHover(control, signal, { canShow = () => true, changed, place, valueAt }) {
|
|
296
297
|
this.#hover = bindHoverPreview(control, {
|
|
297
298
|
canShow,
|
|
298
299
|
changed: () => {
|
|
300
|
+
changed?.();
|
|
299
301
|
this.dispatchEvent(new Event("sonic-hover", { bubbles: true }));
|
|
300
302
|
},
|
|
301
303
|
dismiss: () => {
|
|
@@ -367,6 +369,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
367
369
|
played: from === void 0 ? this.#model.value : this.#heldAt ?? from
|
|
368
370
|
};
|
|
369
371
|
}
|
|
372
|
+
spokenText(value) {
|
|
373
|
+
return (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(value);
|
|
374
|
+
}
|
|
370
375
|
springTarget() {}
|
|
371
376
|
tapTarget() {}
|
|
372
377
|
travelPx(control, property) {
|
|
@@ -401,7 +406,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
401
406
|
writeAttribute(control, "aria-valuemax", String(high));
|
|
402
407
|
writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
|
|
403
408
|
if (orientation) writeAttribute(control, "aria-orientation", orientation);
|
|
404
|
-
writeAttribute(control, "aria-valuetext",
|
|
409
|
+
writeAttribute(control, "aria-valuetext", this.#spokenValueText());
|
|
405
410
|
this.forwardNaming(control, true);
|
|
406
411
|
this.#renderDisabled(control);
|
|
407
412
|
this.renderReadout();
|
|
@@ -454,6 +459,19 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
454
459
|
const step = this.spokenStep;
|
|
455
460
|
return step !== void 0 && step > 0 ? trimFloat(Math.round(this.#model.value / step) * step) : this.#model.value;
|
|
456
461
|
}
|
|
462
|
+
#spokenValueText() {
|
|
463
|
+
const format = this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak;
|
|
464
|
+
const value = this.#spokenNow();
|
|
465
|
+
const said = this.#said;
|
|
466
|
+
if (said && said.format === format && said.value === value) return said.text;
|
|
467
|
+
const text = format?.(value);
|
|
468
|
+
this.#said = {
|
|
469
|
+
format,
|
|
470
|
+
text,
|
|
471
|
+
value
|
|
472
|
+
};
|
|
473
|
+
return text;
|
|
474
|
+
}
|
|
457
475
|
#textFor(value) {
|
|
458
476
|
return (this.#formatValue ?? this.notation.format)(value);
|
|
459
477
|
}
|
|
@@ -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
|
}
|
|
@@ -104,6 +113,9 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
104
113
|
this.#current = current;
|
|
105
114
|
this.dispatchEvent(new Event("sonic-marker", { bubbles: true }));
|
|
106
115
|
}
|
|
116
|
+
stateTarget() {
|
|
117
|
+
return this.control;
|
|
118
|
+
}
|
|
107
119
|
surface() {
|
|
108
120
|
return this.#surface;
|
|
109
121
|
}
|
|
@@ -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();
|