@xsynaptic/sonic-ui 0.10.3 → 0.12.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 +27 -0
- package/README.md +12 -0
- package/dist/default/define/toggle.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +2 -0
- package/dist/default/elements/position-group.js +163 -0
- package/dist/default/elements/radio-group.js +34 -12
- package/dist/default/elements/sonic-element.js +11 -2
- package/dist/default/elements/switch.js +34 -168
- package/dist/default/elements/toggle.js +189 -0
- package/dist/default/elements/value-element.js +9 -0
- package/dist/default/elements/waveform.js +38 -19
- package/dist/default/elements/wavestrip.js +6 -1
- package/dist/default/index.js +2 -1
- package/dist/default/lib/key-press.js +27 -0
- package/dist/default/lib/strip-scene.js +4 -3
- package/dist/default/lib/toggle-travel.js +19 -0
- package/dist/dev/define/toggle.d.ts +2 -0
- package/dist/dev/define/toggle.js +8 -0
- package/dist/dev/define/toggle.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.js +2 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/position-group.d.ts +38 -0
- package/dist/dev/elements/position-group.js +164 -0
- package/dist/dev/elements/position-group.js.map +1 -0
- package/dist/dev/elements/radio-group.d.ts +4 -0
- package/dist/dev/elements/radio-group.js +34 -12
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +1 -0
- package/dist/dev/elements/sonic-element.js +11 -2
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +4 -19
- package/dist/dev/elements/switch.js +34 -168
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.d.ts +21 -0
- package/dist/dev/elements/toggle.js +197 -0
- package/dist/dev/elements/toggle.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +3 -0
- package/dist/dev/elements/value-element.js +9 -0
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +1 -1
- package/dist/dev/elements/waveform.js +38 -19
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +3 -0
- package/dist/dev/elements/wavestrip.js +6 -1
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/key-press.js +28 -0
- package/dist/dev/lib/key-press.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +4 -3
- package/dist/dev/lib/strip-scene.js.map +1 -1
- package/dist/dev/lib/toggle-travel.js +20 -0
- package/dist/dev/lib/toggle-travel.js.map +1 -0
- package/dist/styles/button.css +2 -2
- package/dist/styles/controls.css +1 -0
- package/dist/styles/led.css +16 -13
- package/dist/styles/material.css +36 -9
- package/dist/styles/meter.css +7 -6
- package/dist/styles/segmented.css +8 -4
- package/dist/styles/spectrum.css +2 -2
- package/dist/styles/switch.css +6 -2
- package/dist/styles/toggle.css +481 -0
- package/dist/styles/waveform.css +5 -2
- package/dist/styles/wavestrip.css +7 -0
- package/package.json +2 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { clamp } from "../lib/math.js";
|
|
2
|
-
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
|
-
import { bindHold } from "../lib/hold.js";
|
|
4
1
|
import { requireChild, template } from "../lib/render.js";
|
|
5
2
|
import { bindDrag } from "../lib/pointer-drag.js";
|
|
6
|
-
import {
|
|
3
|
+
import { isUnder, optionValue } from "./radio-group.js";
|
|
4
|
+
import { SonicPositionGroupElement } from "./position-group.js";
|
|
7
5
|
|
|
8
6
|
//#region src/elements/switch.ts
|
|
9
7
|
const renderSwitch = template(`
|
|
@@ -18,166 +16,60 @@ const renderPosition = template(`
|
|
|
18
16
|
</button>
|
|
19
17
|
`, HTMLButtonElement);
|
|
20
18
|
const renderBare = template(`<button class="sonic-switch-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const next = index + side;
|
|
26
|
-
return next < 0 || next >= count ? index - side : next;
|
|
27
|
-
}
|
|
28
|
-
var SonicSwitch = class extends SonicRadioGroupElement {
|
|
29
|
-
static observedAttributes = [
|
|
30
|
-
...SonicRadioGroupElement.observedAttributes,
|
|
31
|
-
"checked",
|
|
32
|
-
"orientation"
|
|
33
|
-
];
|
|
34
|
-
get checked() {
|
|
35
|
-
return this.#isDirty ? this.#checked : this.defaultChecked;
|
|
36
|
-
}
|
|
37
|
-
set checked(isChecked) {
|
|
38
|
-
this.#isDirty = true;
|
|
39
|
-
this.#checked = isChecked === true;
|
|
40
|
-
this.render();
|
|
41
|
-
}
|
|
42
|
-
get defaultChecked() {
|
|
43
|
-
return this.hasAttribute("checked");
|
|
44
|
-
}
|
|
45
|
-
set defaultChecked(isChecked) {
|
|
46
|
-
this.reflect("checked", isChecked);
|
|
47
|
-
}
|
|
48
|
-
get orientation() {
|
|
49
|
-
return this.getAttribute("orientation") === "horizontal" ? "horizontal" : "vertical";
|
|
50
|
-
}
|
|
51
|
-
set orientation(direction) {
|
|
52
|
-
this.reflect("orientation", direction);
|
|
53
|
-
}
|
|
54
|
-
get value() {
|
|
55
|
-
const value = super.value;
|
|
56
|
-
return value === "" && this.#isBare() ? "on" : value;
|
|
57
|
-
}
|
|
58
|
-
set value(next) {
|
|
59
|
-
super.value = next;
|
|
60
|
-
}
|
|
19
|
+
const pressCenterZone = .08;
|
|
20
|
+
var SonicSwitch = class extends SonicPositionGroupElement {
|
|
21
|
+
bare = renderBare();
|
|
22
|
+
defaultOrientation = "vertical";
|
|
61
23
|
group = renderSwitch();
|
|
62
|
-
#bare = renderBare();
|
|
63
24
|
#bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
|
|
64
|
-
#checked = false;
|
|
65
|
-
#holding;
|
|
66
25
|
#isClickSwallowed = false;
|
|
67
|
-
#isDirty = false;
|
|
68
|
-
attributeChangedCallback(name) {
|
|
69
|
-
if (name === "checked") this.#isDirty = false;
|
|
70
|
-
if (this.isDisabled()) this.#holding?.release();
|
|
71
|
-
super.attributeChangedCallback(name);
|
|
72
|
-
}
|
|
73
|
-
formResetCallback() {
|
|
74
|
-
this.#isDirty = false;
|
|
75
|
-
super.formResetCallback();
|
|
76
|
-
}
|
|
77
|
-
activate() {
|
|
78
|
-
if (this.#isBare()) this.#bare.click();
|
|
79
|
-
else super.activate();
|
|
80
|
-
}
|
|
81
|
-
chooseByClick(option) {
|
|
82
|
-
super.chooseByClick(option);
|
|
83
|
-
if (this.#isMomentary(option)) this.#springBack();
|
|
84
|
-
}
|
|
85
|
-
chooseByKey(option, event) {
|
|
86
|
-
if (!this.#isMomentary(option)) {
|
|
87
|
-
super.chooseByKey(option, event);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (event.repeat) return;
|
|
91
|
-
this.#hold(option, event.key);
|
|
92
|
-
option.focus();
|
|
93
|
-
}
|
|
94
26
|
claimPress(option, event) {
|
|
95
27
|
if (this.#isOnBat(event)) return true;
|
|
96
|
-
|
|
28
|
+
const target = this.pressTarget(option);
|
|
29
|
+
if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
|
|
97
30
|
this.group.setPointerCapture(event.pointerId);
|
|
98
|
-
this
|
|
31
|
+
this.hold(target, event.pointerId);
|
|
99
32
|
return true;
|
|
100
33
|
}
|
|
101
34
|
connect(signal) {
|
|
102
35
|
const group = this.group;
|
|
103
|
-
this.upgradeProperties("defaultChecked", "checked", "orientation");
|
|
104
36
|
super.connect(signal);
|
|
105
37
|
this.checkStyles(group, "switch.css");
|
|
106
|
-
this
|
|
38
|
+
this.bare.addEventListener("click", (event) => {
|
|
107
39
|
const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
|
|
108
40
|
this.#isClickSwallowed = false;
|
|
109
|
-
if (!isSwallowed) this
|
|
41
|
+
if (!isSwallowed) this.setChecked(!this.checked);
|
|
110
42
|
}, { signal });
|
|
111
|
-
this
|
|
43
|
+
this.bare.addEventListener("pointerdown", () => {
|
|
112
44
|
this.#isClickSwallowed = false;
|
|
113
45
|
}, { signal });
|
|
114
46
|
this.#bindBat(group, signal);
|
|
115
|
-
this.#holding = bindHold(group, {
|
|
116
|
-
canHold: () => !this.isDisabled(),
|
|
117
|
-
leave: "focusout",
|
|
118
|
-
onHold: () => {
|
|
119
|
-
this.toggleState("held", true);
|
|
120
|
-
},
|
|
121
|
-
onRelease: () => {
|
|
122
|
-
this.toggleState("held", false);
|
|
123
|
-
this.#springBack();
|
|
124
|
-
}
|
|
125
|
-
}, signal);
|
|
126
|
-
}
|
|
127
|
-
focusTarget() {
|
|
128
|
-
return this.#isBare() ? this.#bare : super.focusTarget();
|
|
129
|
-
}
|
|
130
|
-
isWrapping() {
|
|
131
|
-
return false;
|
|
132
47
|
}
|
|
133
48
|
releaseTarget(option) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (options.length === 2 && optionValue(option) === this.value) return options.find((other) => other !== option);
|
|
137
|
-
return option;
|
|
49
|
+
const target = this.pressTarget(option);
|
|
50
|
+
return this.isMomentary(target) || this.isOptionDisabled(target) ? void 0 : target;
|
|
138
51
|
}
|
|
139
52
|
render() {
|
|
140
53
|
if (!this.isBound()) return;
|
|
141
|
-
|
|
54
|
+
super.render();
|
|
142
55
|
const options = this.options();
|
|
143
56
|
if (options.length > 0) {
|
|
144
|
-
this.#
|
|
145
|
-
writeAttribute(group, "role", "radiogroup");
|
|
146
|
-
writeAttribute(group, "data-sonic-position-count", String(options.length));
|
|
147
|
-
super.render();
|
|
148
|
-
this.#renderThrow(this.#checkedIndex(options));
|
|
57
|
+
this.#renderThrow(this.checkedIndex(options));
|
|
149
58
|
return;
|
|
150
59
|
}
|
|
151
|
-
const bare = this.#bare;
|
|
152
|
-
const isChecked = this.checked;
|
|
153
|
-
writeAttribute(group, "role", void 0);
|
|
154
|
-
delete group.dataset.sonicPositionCount;
|
|
155
|
-
if (bare.parentElement !== group) group.append(bare);
|
|
156
|
-
writeAttribute(bare, "disabled", this.isDisabled() ? "" : void 0);
|
|
157
|
-
writeAttribute(bare, "aria-checked", String(isChecked));
|
|
158
|
-
this.forwardNaming(group, false);
|
|
159
|
-
this.forwardNaming(bare, true);
|
|
160
60
|
const onSide = this.orientation === "horizontal" ? 1 : -1;
|
|
161
|
-
group.style.setProperty("--_sonic-switch-at", String(
|
|
162
|
-
this.writeFormValue(isChecked ? this.value : null, String(isChecked));
|
|
61
|
+
this.group.style.setProperty("--_sonic-switch-at", String(this.checked ? onSide : -onSide));
|
|
163
62
|
}
|
|
164
63
|
renderOption() {
|
|
165
64
|
return renderPosition();
|
|
166
65
|
}
|
|
167
|
-
restoreState(state) {
|
|
168
|
-
if (this.#isBare()) this.checked = state === "true";
|
|
169
|
-
else super.restoreState(state);
|
|
170
|
-
}
|
|
171
|
-
#along(event) {
|
|
172
|
-
return this.orientation === "horizontal" ? event.clientX : event.clientY;
|
|
173
|
-
}
|
|
174
66
|
#bindBat(group, signal) {
|
|
175
67
|
bindDrag(group, {
|
|
176
68
|
grab: (event) => {
|
|
177
|
-
if (!(event.target === this
|
|
69
|
+
if (!(event.target === this.bare || this.optionOf(event.target) !== void 0) || this.isDisabled() || !this.#isOnBat(event)) return;
|
|
178
70
|
return {
|
|
179
71
|
hasMoved: false,
|
|
180
|
-
last: this
|
|
72
|
+
last: this.along(event),
|
|
181
73
|
width: this.#bat.getBoundingClientRect().width
|
|
182
74
|
};
|
|
183
75
|
},
|
|
@@ -187,83 +79,57 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
187
79
|
},
|
|
188
80
|
move: (press, event) => {
|
|
189
81
|
if (this.isDisabled()) return;
|
|
190
|
-
const delta = this
|
|
82
|
+
const delta = this.along(event) - press.last;
|
|
191
83
|
if (Math.abs(delta) < press.width / 4) return;
|
|
192
84
|
press.hasMoved = true;
|
|
193
|
-
press.last = this
|
|
85
|
+
press.last = this.along(event);
|
|
194
86
|
this.#throwBy(Math.sign(delta), event.pointerId);
|
|
195
87
|
}
|
|
196
88
|
}, signal);
|
|
197
89
|
}
|
|
198
|
-
#checkedIndex(options) {
|
|
199
|
-
return options.findIndex((option) => optionValue(option) === this.value);
|
|
200
|
-
}
|
|
201
90
|
#hasSprungFrom(option) {
|
|
202
|
-
if (!option || !this
|
|
203
|
-
this
|
|
91
|
+
if (!option || !this.isMomentary(option)) return false;
|
|
92
|
+
this.releaseHold();
|
|
204
93
|
return this.value !== optionValue(option);
|
|
205
94
|
}
|
|
206
|
-
#hold(option, by) {
|
|
207
|
-
if (this.#holding?.hold(by) === true) this.select(option);
|
|
208
|
-
}
|
|
209
|
-
#isBare() {
|
|
210
|
-
return this.options().length === 0;
|
|
211
|
-
}
|
|
212
|
-
#isMomentary(option) {
|
|
213
|
-
const options = this.options();
|
|
214
|
-
return (option === options[0] || option === options.at(-1)) && options.length > 1 && option.querySelector("[data-sonic-value]")?.hasAttribute("data-sonic-momentary") === true;
|
|
215
|
-
}
|
|
216
95
|
#isOnBat(event) {
|
|
217
96
|
return isUnder(this.#bat, event.clientX, event.clientY);
|
|
218
97
|
}
|
|
219
98
|
#pressBat(event) {
|
|
220
99
|
const options = this.options();
|
|
221
100
|
if (options.length === 0) {
|
|
222
|
-
this
|
|
101
|
+
this.setChecked(!this.checked);
|
|
223
102
|
return;
|
|
224
103
|
}
|
|
225
104
|
const box = this.#bat.getBoundingClientRect();
|
|
226
105
|
const [start, size] = this.orientation === "horizontal" ? [box.left, box.width] : [box.top, box.height];
|
|
227
|
-
const offset = (this
|
|
228
|
-
const
|
|
106
|
+
const offset = (this.along(event) - start) / size - .5;
|
|
107
|
+
const index = this.checkedIndex(options);
|
|
108
|
+
const side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);
|
|
109
|
+
const toward = this.stepFrom(index, side);
|
|
110
|
+
const option = toward && toward !== options[index] ? toward : this.stepFrom(index, -side);
|
|
229
111
|
if (!option) return;
|
|
230
112
|
this.chooseByClick(option);
|
|
231
113
|
this.#refocus(options);
|
|
232
114
|
}
|
|
233
115
|
#refocus(options) {
|
|
234
|
-
if (this.group.matches(":focus-within")) options[this
|
|
116
|
+
if (this.group.matches(":focus-within")) options[this.checkedIndex(options)]?.focus();
|
|
235
117
|
}
|
|
236
118
|
#renderThrow(index) {
|
|
237
119
|
const count = this.options().length;
|
|
238
120
|
const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
|
|
239
121
|
this.group.style.setProperty("--_sonic-switch-at", String(at));
|
|
240
122
|
}
|
|
241
|
-
#setChecked(isChecked) {
|
|
242
|
-
if (isChecked === this.checked) return;
|
|
243
|
-
this.checked = isChecked;
|
|
244
|
-
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
245
|
-
}
|
|
246
|
-
#springBack() {
|
|
247
|
-
const options = this.options();
|
|
248
|
-
const index = this.#checkedIndex(options);
|
|
249
|
-
const held = options[index];
|
|
250
|
-
if (!held || !this.#isMomentary(held)) return;
|
|
251
|
-
const rest = options[index === 0 ? 1 : index - 1];
|
|
252
|
-
if (!rest) return;
|
|
253
|
-
const isFocused = this.group.contains(this.ownerDocument.activeElement);
|
|
254
|
-
this.select(rest);
|
|
255
|
-
if (isFocused) rest.focus();
|
|
256
|
-
}
|
|
257
123
|
#throwBy(direction, pointerId) {
|
|
258
124
|
const options = this.options();
|
|
259
125
|
if (options.length === 0) {
|
|
260
|
-
this
|
|
126
|
+
this.setChecked(direction === (this.orientation === "horizontal" ? 1 : -1));
|
|
261
127
|
return;
|
|
262
128
|
}
|
|
263
|
-
const index = this
|
|
264
|
-
const option =
|
|
129
|
+
const index = this.checkedIndex(options);
|
|
130
|
+
const option = this.stepFrom(index, direction);
|
|
265
131
|
if (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;
|
|
266
|
-
if (this
|
|
132
|
+
if (this.isMomentary(option)) this.hold(option, pointerId);
|
|
267
133
|
else this.select(option);
|
|
268
134
|
this.#refocus(options);
|
|
269
135
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
3
|
+
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
4
|
+
import { isUnder, optionValue } from "./radio-group.js";
|
|
5
|
+
import { SonicPositionGroupElement } from "./position-group.js";
|
|
6
|
+
import { positionAt, travelFrom } from "../lib/toggle-travel.js";
|
|
7
|
+
|
|
8
|
+
//#region src/elements/toggle.ts
|
|
9
|
+
const renderToggle = template(`
|
|
10
|
+
<div class="sonic-toggle">
|
|
11
|
+
<span aria-hidden="true" class="sonic-toggle-well"></span>
|
|
12
|
+
<span aria-hidden="true" class="sonic-toggle-cap"></span>
|
|
13
|
+
</div>
|
|
14
|
+
`, HTMLDivElement);
|
|
15
|
+
const renderPosition = template(`
|
|
16
|
+
<button class="sonic-toggle-position" role="radio" type="button">
|
|
17
|
+
<span class="sonic-toggle-legend"></span>
|
|
18
|
+
</button>
|
|
19
|
+
`, HTMLButtonElement);
|
|
20
|
+
const renderBare = template(`<button class="sonic-toggle-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
21
|
+
var SonicToggle = class extends SonicPositionGroupElement {
|
|
22
|
+
bare = renderBare();
|
|
23
|
+
defaultOrientation = "horizontal";
|
|
24
|
+
group = renderToggle();
|
|
25
|
+
#cap = requireChild(this.group, ".sonic-toggle-cap", HTMLSpanElement);
|
|
26
|
+
#draggedTo;
|
|
27
|
+
#hasWarnedOfLegends = false;
|
|
28
|
+
#well = requireChild(this.group, ".sonic-toggle-well", HTMLSpanElement);
|
|
29
|
+
claimPress() {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
connect(signal) {
|
|
33
|
+
super.connect(signal);
|
|
34
|
+
this.checkStyles(this.group, "toggle.css");
|
|
35
|
+
this.bare.addEventListener("click", (event) => {
|
|
36
|
+
if (event instanceof PointerEvent && event.pointerType !== "") return;
|
|
37
|
+
this.setChecked(!this.checked);
|
|
38
|
+
}, { signal });
|
|
39
|
+
this.#bindCap(signal);
|
|
40
|
+
}
|
|
41
|
+
isMirrored(child) {
|
|
42
|
+
return super.isMirrored(child) || child === this.#fixedLegend();
|
|
43
|
+
}
|
|
44
|
+
placeCopies(copies) {
|
|
45
|
+
const positions = copies.filter((copy) => super.isMirrored(copy));
|
|
46
|
+
placeChildren(this.#cap, copies.filter((copy) => !positions.includes(copy)));
|
|
47
|
+
super.placeCopies(positions);
|
|
48
|
+
}
|
|
49
|
+
render() {
|
|
50
|
+
if (!this.isBound()) return;
|
|
51
|
+
this.group.style.setProperty("--_sonic-toggle-count", String(this.#stopCount()));
|
|
52
|
+
this.#placeCap(this.#draggedTo ?? this.#restingAt());
|
|
53
|
+
super.render();
|
|
54
|
+
for (const [index, option] of this.options().entries()) option.style.setProperty("--_sonic-toggle-index", String(index));
|
|
55
|
+
}
|
|
56
|
+
renderOption() {
|
|
57
|
+
return renderPosition();
|
|
58
|
+
}
|
|
59
|
+
#bindCap(signal) {
|
|
60
|
+
bindDrag(this.group, {
|
|
61
|
+
grab: (event) => this.#grab(event),
|
|
62
|
+
lift: (drag, event) => {
|
|
63
|
+
if (drag.hasMoved || drag.isHolding || this.isDisabled()) return;
|
|
64
|
+
if (!isUnder(this.group, event.clientX, event.clientY)) return;
|
|
65
|
+
this.#press(drag.position);
|
|
66
|
+
},
|
|
67
|
+
move: (drag, event) => {
|
|
68
|
+
if (drag.isHolding || this.isDisabled()) return;
|
|
69
|
+
const pointer = this.along(event);
|
|
70
|
+
if (!drag.hasMoved && Math.abs(pointer - drag.start) < drag.thresholdPx) return;
|
|
71
|
+
drag.hasMoved = true;
|
|
72
|
+
this.toggleState("dragging", true);
|
|
73
|
+
this.#slideTo(positionAt(drag.travel, pointer), event.pointerId);
|
|
74
|
+
},
|
|
75
|
+
release: (drag) => {
|
|
76
|
+
delete (drag.position ?? this.bare).dataset.sonicPressed;
|
|
77
|
+
this.#draggedTo = void 0;
|
|
78
|
+
this.toggleState("dragging", false);
|
|
79
|
+
this.render();
|
|
80
|
+
}
|
|
81
|
+
}, signal);
|
|
82
|
+
}
|
|
83
|
+
#checkLegends() {}
|
|
84
|
+
#enabledEnds() {
|
|
85
|
+
if (this.isBare()) return [0, 1];
|
|
86
|
+
const options = this.options();
|
|
87
|
+
const isEnabled = (option) => !this.isOptionDisabled(option);
|
|
88
|
+
return [options.findIndex(isEnabled), options.findLastIndex(isEnabled)];
|
|
89
|
+
}
|
|
90
|
+
#fixedLegend() {
|
|
91
|
+
return [...this.children].find((child) => child !== this.group && !super.isMirrored(child));
|
|
92
|
+
}
|
|
93
|
+
#grab(event) {
|
|
94
|
+
const position = this.optionOf(event.target);
|
|
95
|
+
if (this.isDisabled() || !position && event.target !== this.bare) return void 0;
|
|
96
|
+
const isOnCap = isUnder(this.#cap, event.clientX, event.clientY);
|
|
97
|
+
const travel = this.#travel(event, isOnCap);
|
|
98
|
+
if (!travel || this.#isBlocked(position, isOnCap)) return void 0;
|
|
99
|
+
(position ?? this.bare).toggleAttribute("data-sonic-pressed", true);
|
|
100
|
+
return {
|
|
101
|
+
hasMoved: false,
|
|
102
|
+
isHolding: this.#holdFrom(position, event.pointerId),
|
|
103
|
+
position,
|
|
104
|
+
start: this.along(event),
|
|
105
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
106
|
+
travel
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
#holdFrom(position, pointerId) {
|
|
110
|
+
const target = position && this.pressTarget(position);
|
|
111
|
+
if (!target || !this.isMomentary(target) || this.isOptionDisabled(target)) return false;
|
|
112
|
+
this.hold(target, pointerId);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
#isBlocked(position, isOnCap) {
|
|
116
|
+
return !isOnCap && position !== void 0 && this.isOptionDisabled(position);
|
|
117
|
+
}
|
|
118
|
+
#onAt() {
|
|
119
|
+
return this.orientation === "vertical" ? 0 : 1;
|
|
120
|
+
}
|
|
121
|
+
#placeCap(at) {
|
|
122
|
+
this.group.style.setProperty("--_sonic-toggle-at", String(at));
|
|
123
|
+
}
|
|
124
|
+
#press(position) {
|
|
125
|
+
if (!position) {
|
|
126
|
+
this.setChecked(!this.checked);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const target = this.pressTarget(position);
|
|
130
|
+
if (this.isOptionDisabled(target)) return;
|
|
131
|
+
this.select(target);
|
|
132
|
+
this.#refocus(target);
|
|
133
|
+
}
|
|
134
|
+
#refocus(position) {
|
|
135
|
+
if (this.group.matches(":focus-within")) position.focus();
|
|
136
|
+
}
|
|
137
|
+
#restingAt() {
|
|
138
|
+
if (this.isBare()) return this.checked ? this.#onAt() : 1 - this.#onAt();
|
|
139
|
+
return Math.max(0, this.checkedIndex(this.options()));
|
|
140
|
+
}
|
|
141
|
+
#slideTo(at, pointerId) {
|
|
142
|
+
const crossed = this.options()[Math.round(at)];
|
|
143
|
+
this.#draggedTo = at;
|
|
144
|
+
this.#placeCap(at);
|
|
145
|
+
if (this.isBare()) {
|
|
146
|
+
this.setChecked(Math.round(at) === this.#onAt());
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (!crossed || optionValue(crossed) === this.value) return;
|
|
150
|
+
this.releaseHold();
|
|
151
|
+
if (this.isOptionDisabled(crossed) || optionValue(crossed) === this.value) return;
|
|
152
|
+
if (this.isMomentary(crossed)) this.hold(crossed, pointerId);
|
|
153
|
+
else this.select(crossed);
|
|
154
|
+
this.#refocus(crossed);
|
|
155
|
+
}
|
|
156
|
+
#stopCount() {
|
|
157
|
+
return this.options().length || 2;
|
|
158
|
+
}
|
|
159
|
+
#travel(event, isOnCap) {
|
|
160
|
+
const [first, last] = this.#enabledEnds();
|
|
161
|
+
if (first < 0) return void 0;
|
|
162
|
+
const box = this.#well.getBoundingClientRect();
|
|
163
|
+
const [start, length, thickness] = this.orientation === "vertical" ? [
|
|
164
|
+
box.top,
|
|
165
|
+
box.height,
|
|
166
|
+
box.width
|
|
167
|
+
] : [
|
|
168
|
+
box.left,
|
|
169
|
+
box.width,
|
|
170
|
+
box.height
|
|
171
|
+
];
|
|
172
|
+
return travelFrom({
|
|
173
|
+
at: this.#restingAt(),
|
|
174
|
+
count: this.#stopCount(),
|
|
175
|
+
first,
|
|
176
|
+
isOnCap,
|
|
177
|
+
last,
|
|
178
|
+
pointer: this.along(event),
|
|
179
|
+
well: {
|
|
180
|
+
length,
|
|
181
|
+
start,
|
|
182
|
+
thickness
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
export { SonicToggle };
|
|
@@ -72,6 +72,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
get cancelling() {
|
|
76
|
+
return this.hasState("cancelling");
|
|
77
|
+
}
|
|
75
78
|
get default() {
|
|
76
79
|
return this.optionalNumberAttribute("default");
|
|
77
80
|
}
|
|
@@ -97,6 +100,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
97
100
|
set doublePress(gesture) {
|
|
98
101
|
this.reflect("double-press", gesture);
|
|
99
102
|
}
|
|
103
|
+
get dragging() {
|
|
104
|
+
return this.hasState("dragging");
|
|
105
|
+
}
|
|
100
106
|
get formatEntry() {
|
|
101
107
|
return this.#formatEntry;
|
|
102
108
|
}
|
|
@@ -177,6 +183,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
177
183
|
set readout(isShown) {
|
|
178
184
|
this.reflect("readout", isShown);
|
|
179
185
|
}
|
|
186
|
+
get revealed() {
|
|
187
|
+
return this.hasState("revealed");
|
|
188
|
+
}
|
|
180
189
|
get spokenStep() {
|
|
181
190
|
return this.optionalNumberAttribute("spoken-step");
|
|
182
191
|
}
|
|
@@ -73,9 +73,9 @@ function paintPlaceholders(context, scene) {
|
|
|
73
73
|
if (scene.pending.length === 0) return;
|
|
74
74
|
const { dpr, height, pixelsPerSecond, startSeconds } = scene;
|
|
75
75
|
const wavelength = placeholderWavelengthPx * dpr;
|
|
76
|
-
const
|
|
76
|
+
const center = height / 2;
|
|
77
77
|
const origin = startSeconds * pixelsPerSecond;
|
|
78
|
-
const waveY = (x) =>
|
|
78
|
+
const waveY = (x) => center - Math.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) * center * placeholderAmplitude;
|
|
79
79
|
context.beginPath();
|
|
80
80
|
for (const [from, to] of scene.pending) {
|
|
81
81
|
const opening = (from - startSeconds) * pixelsPerSecond;
|
|
@@ -100,13 +100,13 @@ function paintWave(context, scene, peaks) {
|
|
|
100
100
|
const buckets = waveformBuckets(peaks, scene);
|
|
101
101
|
const [first] = buckets;
|
|
102
102
|
if (!first) return;
|
|
103
|
-
const
|
|
104
|
-
const amplitudePx =
|
|
103
|
+
const center = scene.height / 2;
|
|
104
|
+
const amplitudePx = center * amplitudeMargin;
|
|
105
105
|
context.fillStyle = waveFill(context, scene);
|
|
106
106
|
context.beginPath();
|
|
107
|
-
context.moveTo(first.x,
|
|
108
|
-
for (const bucket of buckets) context.lineTo(bucket.x,
|
|
109
|
-
for (const bucket of buckets.toReversed()) context.lineTo(bucket.x,
|
|
107
|
+
context.moveTo(first.x, center);
|
|
108
|
+
for (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);
|
|
109
|
+
for (const bucket of buckets.toReversed()) context.lineTo(bucket.x, center - bucket.low * amplitudePx);
|
|
110
110
|
context.closePath();
|
|
111
111
|
context.fill();
|
|
112
112
|
}
|
|
@@ -125,20 +125,22 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
125
125
|
set peaks(peaks) {
|
|
126
126
|
this.#peaks = peaks;
|
|
127
127
|
this.renderEmpty();
|
|
128
|
-
this
|
|
128
|
+
this.#repaint();
|
|
129
129
|
}
|
|
130
130
|
get pending() {
|
|
131
131
|
return this.#pending.map(([start, end]) => [start, end]);
|
|
132
132
|
}
|
|
133
133
|
set pending(regions) {
|
|
134
|
+
const pending = regions ? readRegions(regions) : [];
|
|
135
|
+
if (String(pending) === String(this.#pending)) return;
|
|
134
136
|
const listedMs = this.#pendingListedMs;
|
|
135
137
|
const nowMs = performance.now();
|
|
136
|
-
this.#pending =
|
|
138
|
+
this.#pending = pending;
|
|
137
139
|
this.#pendingListedMs = new Map(this.#pending.map((region) => {
|
|
138
140
|
const key = region.join(":");
|
|
139
141
|
return [key, listedMs.get(key) ?? nowMs];
|
|
140
142
|
}));
|
|
141
|
-
this
|
|
143
|
+
this.#repaint();
|
|
142
144
|
}
|
|
143
145
|
get pendingDelay() {
|
|
144
146
|
return Math.max(0, this.numberAttribute("pending-delay", 0));
|
|
@@ -177,7 +179,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
177
179
|
}
|
|
178
180
|
set requestPeaks(request) {
|
|
179
181
|
this.#requestPeaks = request;
|
|
180
|
-
this.#
|
|
182
|
+
this.#askedKey = void 0;
|
|
181
183
|
this.surface()?.invalidate();
|
|
182
184
|
}
|
|
183
185
|
get zoom() {
|
|
@@ -193,11 +195,13 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
193
195
|
lengths = {};
|
|
194
196
|
sheet = "waveform.css";
|
|
195
197
|
sizeProperty = "--_sonic-waveform-size";
|
|
196
|
-
#asked;
|
|
198
|
+
#asked = /* @__PURE__ */ new WeakSet();
|
|
199
|
+
#askedKey;
|
|
197
200
|
#clock = createTrackingClock();
|
|
198
201
|
#drawn;
|
|
199
202
|
#ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
|
|
200
203
|
#grabbed;
|
|
204
|
+
#isAsking = false;
|
|
201
205
|
#kindColours = /* @__PURE__ */ new Map();
|
|
202
206
|
#paintedKey = "";
|
|
203
207
|
#peaks;
|
|
@@ -220,6 +224,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
220
224
|
connect(signal) {
|
|
221
225
|
super.connect(signal);
|
|
222
226
|
this.bindGestures(this.control, signal, () => this.#grab());
|
|
227
|
+
if (!("fonts" in document)) return;
|
|
223
228
|
document.fonts.addEventListener("loadingdone", () => {
|
|
224
229
|
this.renderMarkers();
|
|
225
230
|
}, { signal });
|
|
@@ -287,10 +292,22 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
287
292
|
this.surface()?.rebuild();
|
|
288
293
|
}
|
|
289
294
|
#ask(wanted) {
|
|
290
|
-
const
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
this.#
|
|
295
|
+
const key = wanted.join(":");
|
|
296
|
+
if (key === this.#askedKey) return;
|
|
297
|
+
let asked;
|
|
298
|
+
this.#askedKey = key;
|
|
299
|
+
this.#isAsking = true;
|
|
300
|
+
try {
|
|
301
|
+
asked = this.#requestPeaks?.(...wanted);
|
|
302
|
+
} finally {
|
|
303
|
+
this.#isAsking = false;
|
|
304
|
+
}
|
|
305
|
+
const promises = asked instanceof Promise ? [asked] : asked ?? [];
|
|
306
|
+
for (const promise of promises) {
|
|
307
|
+
if (this.#asked.has(promise)) continue;
|
|
308
|
+
this.#asked.add(promise);
|
|
309
|
+
this.#repaintAfter(promise);
|
|
310
|
+
}
|
|
294
311
|
}
|
|
295
312
|
#duePending() {
|
|
296
313
|
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
@@ -363,11 +380,13 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
363
380
|
for (const probe of probes) probe.remove();
|
|
364
381
|
return new Map(kinds.map((kind, index) => [kind, read[index] ?? ""]));
|
|
365
382
|
}
|
|
383
|
+
#repaint() {
|
|
384
|
+
if (!this.#isAsking) this.#askedKey = void 0;
|
|
385
|
+
this.surface()?.invalidate();
|
|
386
|
+
}
|
|
366
387
|
async #repaintAfter(asked) {
|
|
367
388
|
await Promise.allSettled([asked]);
|
|
368
|
-
|
|
369
|
-
this.#asked = void 0;
|
|
370
|
-
this.surface()?.invalidate();
|
|
389
|
+
this.#repaint();
|
|
371
390
|
}
|
|
372
391
|
#sourceSeconds() {
|
|
373
392
|
return this.#readTime?.() ?? this.playback();
|
|
@@ -16,6 +16,7 @@ const colours = {
|
|
|
16
16
|
const lengths = {
|
|
17
17
|
gapRatio: "--_sonic-wavestrip-bar-gap-ratio",
|
|
18
18
|
grooveRatio: "--_sonic-wavestrip-groove-ratio",
|
|
19
|
+
grooveSize: "--_sonic-wavestrip-groove-size",
|
|
19
20
|
pitch: "--_sonic-wavestrip-bar-pitch",
|
|
20
21
|
radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
|
|
21
22
|
};
|
|
@@ -79,6 +80,10 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
79
80
|
#markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
|
|
80
81
|
#paintedKey = "";
|
|
81
82
|
#peaks;
|
|
83
|
+
clientXOf(value) {
|
|
84
|
+
const { startPx, travelPx } = this.#axis();
|
|
85
|
+
return startPx + this.mapping().proportionOf(value) * travelPx;
|
|
86
|
+
}
|
|
82
87
|
connectedCallback() {
|
|
83
88
|
this.upgradeProperties("buffered", "cancellable", "peaks");
|
|
84
89
|
super.connectedCallback();
|
|
@@ -141,7 +146,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
141
146
|
#barsFor({ isDirty, look, size }) {
|
|
142
147
|
if (!isDirty && this.#bars) return this.#bars;
|
|
143
148
|
const peaks = this.#peaks;
|
|
144
|
-
const groove = stripGroove(size, look.lengths
|
|
149
|
+
const groove = stripGroove(size, look.lengths);
|
|
145
150
|
const drawn = groove ? [groove] : [];
|
|
146
151
|
const bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;
|
|
147
152
|
this.#bars = void 0;
|
package/dist/default/index.js
CHANGED
|
@@ -9,10 +9,11 @@ import { SonicSlider } from "./elements/slider.js";
|
|
|
9
9
|
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
10
10
|
import { SonicSplit } from "./elements/split.js";
|
|
11
11
|
import { SonicSwitch } from "./elements/switch.js";
|
|
12
|
+
import { SonicToggle } from "./elements/toggle.js";
|
|
12
13
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
13
14
|
import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
14
15
|
import { SonicXy } from "./elements/xy.js";
|
|
15
16
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
16
17
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
17
18
|
|
|
18
|
-
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|
|
19
|
+
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|