@xsynaptic/sonic-ui 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/default/define/spectrum.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +33 -4
- package/dist/default/elements/dial.js +5 -1
- package/dist/default/elements/display-element.js +77 -0
- package/dist/default/elements/envelope.js +15 -3
- package/dist/default/elements/meter.js +1 -1
- package/dist/default/elements/radio-group.js +3 -2
- package/dist/default/elements/readout-claim.js +0 -29
- package/dist/default/elements/scale.js +43 -0
- package/dist/default/elements/slider.js +82 -3
- package/dist/default/elements/sonic-element.js +2 -67
- package/dist/default/elements/spectrum.js +230 -0
- package/dist/default/elements/switch.js +1 -1
- package/dist/default/elements/value-element.js +81 -91
- package/dist/default/elements/wave-element.js +59 -9
- package/dist/default/elements/waveform.js +56 -10
- package/dist/default/elements/wavestrip.js +45 -21
- package/dist/default/elements/xy.js +3 -2
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +7 -4
- package/dist/default/lib/frame-timeline.js +2 -2
- package/dist/default/lib/hover-preview.js +46 -0
- package/dist/default/lib/marker-rider.js +7 -4
- package/dist/default/lib/mirror-children.js +64 -0
- package/dist/default/lib/owned-control.js +10 -0
- package/dist/default/lib/pointer-drag.js +7 -2
- package/dist/default/lib/spectrum-columns.js +74 -0
- package/dist/default/lib/spectrum-levels.js +55 -0
- package/dist/default/lib/strip-scene.js +12 -1
- package/dist/default/lib/time-regions.js +8 -1
- package/dist/dev/define/spectrum.d.ts +2 -0
- package/dist/dev/define/spectrum.js +8 -0
- package/dist/dev/define/spectrum.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +33 -4
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +5 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +29 -0
- package/dist/dev/elements/display-element.js +78 -0
- package/dist/dev/elements/display-element.js.map +1 -0
- package/dist/dev/elements/envelope.js +15 -3
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/radio-group.js +3 -2
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout-claim.js +0 -29
- package/dist/dev/elements/readout-claim.js.map +1 -1
- package/dist/dev/elements/scale.js +44 -0
- package/dist/dev/elements/scale.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +9 -0
- package/dist/dev/elements/slider.js +82 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +0 -8
- package/dist/dev/elements/sonic-element.js +3 -90
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +59 -0
- package/dist/dev/elements/spectrum.js +231 -0
- package/dist/dev/elements/spectrum.js.map +1 -0
- package/dist/dev/elements/switch.js +1 -1
- package/dist/dev/elements/value-element.d.ts +8 -6
- package/dist/dev/elements/value-element.js +81 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +8 -2
- package/dist/dev/elements/wave-element.js +59 -9
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +12 -4
- package/dist/dev/elements/waveform.js +56 -10
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +7 -0
- package/dist/dev/elements/wavestrip.js +45 -21
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +3 -2
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +4 -1
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/canvas-surface.d.ts +4 -3
- package/dist/dev/lib/canvas-surface.js +7 -4
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -2
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/hover-preview.js +47 -0
- package/dist/dev/lib/hover-preview.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +7 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/mirror-children.js +80 -0
- package/dist/dev/lib/mirror-children.js.map +1 -0
- package/dist/dev/lib/owned-control.js +23 -0
- package/dist/dev/lib/owned-control.js.map +1 -0
- package/dist/dev/lib/pointer-drag.js +7 -2
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/spectrum-columns.js +75 -0
- package/dist/dev/lib/spectrum-columns.js.map +1 -0
- package/dist/dev/lib/spectrum-levels.js +56 -0
- package/dist/dev/lib/spectrum-levels.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +12 -1
- package/dist/dev/lib/strip-scene.js.map +1 -1
- package/dist/dev/lib/time-regions.js +8 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/skins/flat.css +2 -1
- package/dist/styles/button.css +28 -1
- package/dist/styles/controls.css +1 -0
- package/dist/styles/envelope.css +30 -30
- package/dist/styles/led.css +18 -2
- package/dist/styles/material.css +90 -29
- package/dist/styles/meter.css +22 -18
- package/dist/styles/panel.css +2 -1
- package/dist/styles/slider.css +42 -5
- package/dist/styles/spectrum.css +105 -0
- package/dist/styles/switch.css +10 -7
- package/dist/styles/waveform.css +38 -16
- package/dist/styles/wavestrip.css +22 -4
- package/dist/styles/xy.css +14 -30
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
23
23
|
<sonic-dial aria-label="Cutoff" value="40"></sonic-dial>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
A `-size` token takes any CSS length and scales its control as one piece; a `-length` token also takes a percentage of the parent.
|
|
27
|
+
|
|
28
|
+
A display takes its signal as data: `spectrum.analyser = node`, or `spectrum.push(frame)`.
|
|
29
|
+
|
|
26
30
|
Under a hydrating framework, register the definitions once hydration is done (in React, from an effect); a control owns one child of its host and uses only the children it documents.
|
|
27
31
|
|
|
28
32
|
Targets current Chrome and Edge, Safari 26 and Firefox 147. MIT licensed.
|
package/dist/default/define.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
2
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
2
|
import { SonicFormElement } from "./form-element.js";
|
|
4
3
|
import { copyNode } from "../lib/copy-node.js";
|
|
5
4
|
import { bindHold } from "../lib/hold.js";
|
|
5
|
+
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
6
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
7
|
+
import { capturePointer } from "../lib/pointer-drag.js";
|
|
6
8
|
|
|
7
9
|
//#region src/elements/button.ts
|
|
8
10
|
const renderButton = template(`
|
|
9
11
|
<button class="sonic-button" type="button"><span class="sonic-button-cap"></span></button>
|
|
10
12
|
`, HTMLButtonElement);
|
|
11
13
|
const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
14
|
+
const popupAttributes = {
|
|
15
|
+
controls: "aria-controls",
|
|
16
|
+
expanded: "aria-expanded",
|
|
17
|
+
popup: "aria-haspopup"
|
|
18
|
+
};
|
|
19
|
+
const pressWhens = /* @__PURE__ */ new Set(["pressed", "released"]);
|
|
12
20
|
var SonicButton = class extends SonicFormElement {
|
|
13
21
|
static observedAttributes = [
|
|
14
22
|
...SonicFormElement.observedAttributes,
|
|
23
|
+
...Object.keys(popupAttributes),
|
|
15
24
|
"busy",
|
|
25
|
+
"legend",
|
|
16
26
|
"momentary",
|
|
17
27
|
"pressed",
|
|
18
28
|
"soft-disabled",
|
|
@@ -43,6 +53,12 @@ var SonicButton = class extends SonicFormElement {
|
|
|
43
53
|
set latching(isLatching) {
|
|
44
54
|
this.reflect("latching", isLatching);
|
|
45
55
|
}
|
|
56
|
+
get legend() {
|
|
57
|
+
return this.getAttribute("legend") ?? void 0;
|
|
58
|
+
}
|
|
59
|
+
set legend(name) {
|
|
60
|
+
this.reflect("legend", name);
|
|
61
|
+
}
|
|
46
62
|
get momentary() {
|
|
47
63
|
return this.hasAttribute("momentary");
|
|
48
64
|
}
|
|
@@ -87,12 +103,13 @@ var SonicButton = class extends SonicFormElement {
|
|
|
87
103
|
connect(signal) {
|
|
88
104
|
const button = this.#button;
|
|
89
105
|
const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
|
|
90
|
-
this.upgradeProperties("armed", "busy", "defaultPressed", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
91
|
-
|
|
106
|
+
this.upgradeProperties("armed", "busy", "defaultPressed", "legend", "momentary", "softDisabled", "latching", "pressed", "value");
|
|
107
|
+
mirrorChildren(this, {
|
|
92
108
|
control: button,
|
|
93
109
|
copy: copyNode,
|
|
94
110
|
place: (copies) => {
|
|
95
111
|
placeChildren(cap, copies);
|
|
112
|
+
this.#showLegend();
|
|
96
113
|
}
|
|
97
114
|
}, signal);
|
|
98
115
|
this.render();
|
|
@@ -121,6 +138,8 @@ var SonicButton = class extends SonicFormElement {
|
|
|
121
138
|
writeAttribute(button, "aria-pressed", this.latching ? String(this.pressed) : void 0);
|
|
122
139
|
this.toggleState("pressed", this.pressed);
|
|
123
140
|
this.forwardNaming(button, true);
|
|
141
|
+
this.#forwardPopup(button);
|
|
142
|
+
this.#showLegend();
|
|
124
143
|
const submitted = this.latching && this.pressed ? this.value : null;
|
|
125
144
|
this.writeFormValue(submitted, String(this.pressed));
|
|
126
145
|
}
|
|
@@ -141,7 +160,7 @@ var SonicButton = class extends SonicFormElement {
|
|
|
141
160
|
this.#holding = holding;
|
|
142
161
|
button.addEventListener("pointerdown", (event) => {
|
|
143
162
|
if (event.button !== 0 || !this.#isMomentary()) return;
|
|
144
|
-
button
|
|
163
|
+
capturePointer(button, event.pointerId);
|
|
145
164
|
holding.hold(event.pointerId);
|
|
146
165
|
}, { signal });
|
|
147
166
|
button.addEventListener("keydown", (event) => {
|
|
@@ -149,6 +168,9 @@ var SonicButton = class extends SonicFormElement {
|
|
|
149
168
|
holding.hold(event.key);
|
|
150
169
|
}, { signal });
|
|
151
170
|
}
|
|
171
|
+
#forwardPopup(button) {
|
|
172
|
+
for (const [name, aria] of Object.entries(popupAttributes)) writeAttribute(button, aria, this.getAttribute(name) ?? void 0);
|
|
173
|
+
}
|
|
152
174
|
#isMomentary() {
|
|
153
175
|
return this.momentary && !this.latching;
|
|
154
176
|
}
|
|
@@ -159,6 +181,13 @@ var SonicButton = class extends SonicFormElement {
|
|
|
159
181
|
this.pressed = isPressed;
|
|
160
182
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
161
183
|
}
|
|
184
|
+
#showLegend() {
|
|
185
|
+
const legend = this.legend;
|
|
186
|
+
for (const part of this.#button.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
|
|
187
|
+
const when = part.dataset.sonicWhen ?? "";
|
|
188
|
+
part.toggleAttribute("data-sonic-hidden", !pressWhens.has(when) && when !== legend);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
162
191
|
};
|
|
163
192
|
|
|
164
193
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
2
|
import { SonicValueElement } from "./value-element.js";
|
|
3
|
+
import { bindScale } from "./scale.js";
|
|
3
4
|
|
|
4
5
|
//#region src/elements/dial.ts
|
|
5
6
|
const renderDial = template(`
|
|
@@ -54,7 +55,10 @@ var SonicDial = class extends SonicValueElement {
|
|
|
54
55
|
}
|
|
55
56
|
connect(signal) {
|
|
56
57
|
const dial = this.#dial;
|
|
57
|
-
|
|
58
|
+
bindScale(this, {
|
|
59
|
+
control: dial,
|
|
60
|
+
marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
|
|
61
|
+
}, signal);
|
|
58
62
|
this.render();
|
|
59
63
|
this.checkStyles(dial, "dial.css");
|
|
60
64
|
this.bindGestures(dial, signal, () => ({
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { SonicElement } from "./sonic-element.js";
|
|
2
|
+
import { bindSurface } from "../lib/canvas-surface.js";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/display-element.ts
|
|
5
|
+
var SonicDisplayElement = class extends SonicElement {
|
|
6
|
+
static observedAttributes = [
|
|
7
|
+
...SonicElement.observedAttributes,
|
|
8
|
+
"max",
|
|
9
|
+
"min"
|
|
10
|
+
];
|
|
11
|
+
get analyser() {
|
|
12
|
+
return this.#analyser;
|
|
13
|
+
}
|
|
14
|
+
set analyser(node) {
|
|
15
|
+
this.#analyser = node ?? void 0;
|
|
16
|
+
this.#surface?.requestFrame();
|
|
17
|
+
}
|
|
18
|
+
get max() {
|
|
19
|
+
return this.numberAttribute("max", 0);
|
|
20
|
+
}
|
|
21
|
+
set max(decibels) {
|
|
22
|
+
this.reflect("max", decibels);
|
|
23
|
+
}
|
|
24
|
+
get min() {
|
|
25
|
+
return this.numberAttribute("min", -90);
|
|
26
|
+
}
|
|
27
|
+
set min(decibels) {
|
|
28
|
+
this.reflect("min", decibels);
|
|
29
|
+
}
|
|
30
|
+
#analyser;
|
|
31
|
+
#buffer = /* @__PURE__ */ new Float32Array(0);
|
|
32
|
+
#surface;
|
|
33
|
+
attributeChangedCallback() {
|
|
34
|
+
this.#surface?.invalidate();
|
|
35
|
+
}
|
|
36
|
+
connectedCallback() {
|
|
37
|
+
this.upgradeProperties("analyser", "max", "min");
|
|
38
|
+
super.connectedCallback();
|
|
39
|
+
}
|
|
40
|
+
push(frame) {
|
|
41
|
+
this.receive(frame);
|
|
42
|
+
this.#surface?.requestFrame();
|
|
43
|
+
}
|
|
44
|
+
repaint() {
|
|
45
|
+
this.#surface?.rebuild();
|
|
46
|
+
}
|
|
47
|
+
connect(signal) {
|
|
48
|
+
const control = this.control;
|
|
49
|
+
this.keepControl(control, signal);
|
|
50
|
+
this.#surface = bindSurface({
|
|
51
|
+
canvas: this.canvas,
|
|
52
|
+
colours: this.colours,
|
|
53
|
+
numbers: this.numbers,
|
|
54
|
+
paint: (context, frame) => {
|
|
55
|
+
this.#paint(context, frame);
|
|
56
|
+
},
|
|
57
|
+
signal
|
|
58
|
+
});
|
|
59
|
+
this.checkStyles(control, this.sheet);
|
|
60
|
+
}
|
|
61
|
+
surface() {
|
|
62
|
+
return this.#surface;
|
|
63
|
+
}
|
|
64
|
+
#paint(context, frame) {
|
|
65
|
+
const analyser = this.isDisabled() ? void 0 : this.#analyser;
|
|
66
|
+
if (analyser) {
|
|
67
|
+
if (this.#buffer.length !== analyser.frequencyBinCount) this.#buffer = new Float32Array(analyser.frequencyBinCount);
|
|
68
|
+
this.pull(analyser, this.#buffer);
|
|
69
|
+
this.receive(this.#buffer);
|
|
70
|
+
}
|
|
71
|
+
this.paint(context, frame);
|
|
72
|
+
if (analyser) this.#surface?.requestFrame();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { SonicDisplayElement };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { requireChild, template } from "../lib/render.js";
|
|
2
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
2
|
import { SonicElement } from "./sonic-element.js";
|
|
3
|
+
import { requireChild, template } from "../lib/render.js";
|
|
4
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
4
5
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
5
6
|
import { Readout } from "./readout.js";
|
|
6
7
|
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
7
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
8
8
|
import { SonicValueElement, linkValue } from "./value-element.js";
|
|
9
9
|
import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
|
|
10
10
|
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
@@ -44,6 +44,7 @@ function stageParts(envelope, hook, stages) {
|
|
|
44
44
|
}
|
|
45
45
|
const renderEnvelope = template(`
|
|
46
46
|
<div class="sonic-envelope" aria-hidden="true">
|
|
47
|
+
<div class="sonic-envelope-bracket"></div>
|
|
47
48
|
<svg class="sonic-envelope-graph" preserveAspectRatio="none" viewBox="0 0 1 1">
|
|
48
49
|
<path class="sonic-envelope-fill" />
|
|
49
50
|
<rect class="sonic-envelope-fill sonic-envelope-floor" y="1" />
|
|
@@ -127,15 +128,17 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
127
128
|
this.reflect("sustain", id);
|
|
128
129
|
}
|
|
129
130
|
#bindings = /* @__PURE__ */ new Map();
|
|
131
|
+
#envelope = renderEnvelope();
|
|
132
|
+
#bracket = requireChild(this.#envelope, ".sonic-envelope-bracket", HTMLDivElement);
|
|
130
133
|
#claim = new ReadoutClaim(() => {
|
|
131
134
|
this.#renderReadout();
|
|
132
135
|
});
|
|
133
|
-
#envelope = renderEnvelope();
|
|
134
136
|
#curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
|
|
135
137
|
#fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
|
|
136
138
|
#floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
|
|
137
139
|
#graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
|
|
138
140
|
#handles = stageParts(this.#envelope, "sonic-envelope-handle", timeStages);
|
|
141
|
+
#held;
|
|
139
142
|
#isDriving = false;
|
|
140
143
|
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
141
144
|
#pointerDrag;
|
|
@@ -232,8 +235,15 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
232
235
|
at: shape.curveHandles.find((point) => point.stage === stage),
|
|
233
236
|
drives: this.#curveDrives(stage)
|
|
234
237
|
});
|
|
238
|
+
this.#drawBracket();
|
|
235
239
|
this.#renderReadout();
|
|
236
240
|
}
|
|
241
|
+
#drawBracket() {
|
|
242
|
+
const part = this.#held;
|
|
243
|
+
if (!part) return;
|
|
244
|
+
for (const name of ["--_sonic-envelope-x", "--_sonic-envelope-y"]) this.#bracket.style.setProperty(name, part.style.getPropertyValue(name));
|
|
245
|
+
writeAttribute(this.#bracket, "data-sonic-part", part.classList.contains("sonic-envelope-curve") ? "curve" : void 0);
|
|
246
|
+
}
|
|
237
247
|
#drawPoint(part, point) {
|
|
238
248
|
const { at, drives } = point;
|
|
239
249
|
writeAttribute(part, "hidden", at ? void 0 : "");
|
|
@@ -260,6 +270,8 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
260
270
|
if (!pressed || pressed.drives.length === 0) return void 0;
|
|
261
271
|
const { drives, isFlipped, part } = pressed;
|
|
262
272
|
this.#claim.press(true);
|
|
273
|
+
this.#held = part;
|
|
274
|
+
this.#drawBracket();
|
|
263
275
|
const box = this.#graph.getBoundingClientRect();
|
|
264
276
|
const travel = {
|
|
265
277
|
x: Math.max(1, box.width * this.#shape.share),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toNumber } from "../lib/math.js";
|
|
2
|
-
import { template } from "../lib/render.js";
|
|
3
2
|
import { SonicElement } from "./sonic-element.js";
|
|
3
|
+
import { template } from "../lib/render.js";
|
|
4
4
|
import { parseNumberList } from "../lib/number-list.js";
|
|
5
5
|
import { linearTaper } from "../lib/taper.js";
|
|
6
6
|
import { ballisticsRates, fall, rise, stepNeedle } from "../lib/ballistics.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { placeChildren } from "../lib/render.js";
|
|
2
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
2
|
import { SonicFormElement } from "./form-element.js";
|
|
4
3
|
import { copyNode } from "../lib/copy-node.js";
|
|
4
|
+
import { placeChildren } from "../lib/render.js";
|
|
5
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
5
6
|
import { bindDrag } from "../lib/pointer-drag.js";
|
|
6
7
|
|
|
7
8
|
//#region src/elements/radio-group.ts
|
|
@@ -48,7 +49,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
48
49
|
connect(signal) {
|
|
49
50
|
const group = this.group;
|
|
50
51
|
this.upgradeProperties("value");
|
|
51
|
-
|
|
52
|
+
mirrorChildren(this, {
|
|
52
53
|
control: group,
|
|
53
54
|
copy: copyNode,
|
|
54
55
|
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
|
|
@@ -6,10 +6,7 @@ var ReadoutClaim = class {
|
|
|
6
6
|
return this.#isDragging || this.#keyTimer !== void 0;
|
|
7
7
|
}
|
|
8
8
|
#holdTimer;
|
|
9
|
-
#hover;
|
|
10
|
-
#isDismissed = false;
|
|
11
9
|
#isDragging = false;
|
|
12
|
-
#isEditing = false;
|
|
13
10
|
#keyTimer;
|
|
14
11
|
#onLapse;
|
|
15
12
|
constructor(onLapse) {
|
|
@@ -27,21 +24,6 @@ var ReadoutClaim = class {
|
|
|
27
24
|
this.#keyTimer = void 0;
|
|
28
25
|
return wasTimed;
|
|
29
26
|
}
|
|
30
|
-
dismiss() {
|
|
31
|
-
const wasHovered = this.#hover !== void 0;
|
|
32
|
-
this.#hover = void 0;
|
|
33
|
-
this.#isDismissed = wasHovered || this.#isDismissed;
|
|
34
|
-
return wasHovered;
|
|
35
|
-
}
|
|
36
|
-
edit(isOpen) {
|
|
37
|
-
this.#isEditing = isOpen;
|
|
38
|
-
if (isOpen) this.#hover = void 0;
|
|
39
|
-
}
|
|
40
|
-
hover(value) {
|
|
41
|
-
if (value === void 0) this.#isDismissed = false;
|
|
42
|
-
if (this.#isDismissed || this.#isEditing || this.isRevealed) return;
|
|
43
|
-
this.#hover = value;
|
|
44
|
-
}
|
|
45
27
|
press(isDown) {
|
|
46
28
|
this.conceal("drag");
|
|
47
29
|
if (!isDown) {
|
|
@@ -54,7 +36,6 @@ var ReadoutClaim = class {
|
|
|
54
36
|
}, 250);
|
|
55
37
|
}
|
|
56
38
|
reveal(by) {
|
|
57
|
-
this.#hover = void 0;
|
|
58
39
|
if (by === "drag") {
|
|
59
40
|
this.#isDragging = true;
|
|
60
41
|
return;
|
|
@@ -65,16 +46,6 @@ var ReadoutClaim = class {
|
|
|
65
46
|
this.#onLapse();
|
|
66
47
|
}, keyRevealMs);
|
|
67
48
|
}
|
|
68
|
-
shown(current, isReadout) {
|
|
69
|
-
if (this.#isEditing) return {
|
|
70
|
-
isOpen: true,
|
|
71
|
-
value: void 0
|
|
72
|
-
};
|
|
73
|
-
return {
|
|
74
|
-
isOpen: (this.isRevealed || this.#hover !== void 0) && isReadout,
|
|
75
|
-
value: this.#hover ?? current
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
49
|
};
|
|
79
50
|
|
|
80
51
|
//#endregion
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
2
|
+
import { placeChildren } from "../lib/render.js";
|
|
3
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
4
|
+
import { linkValue } from "./value-element.js";
|
|
5
|
+
|
|
6
|
+
//#region src/elements/scale.ts
|
|
7
|
+
function scaleValue(mark) {
|
|
8
|
+
const text = mark.dataset.sonicValue?.trim();
|
|
9
|
+
return text ? Number(text) : NaN;
|
|
10
|
+
}
|
|
11
|
+
function scaleMark(original) {
|
|
12
|
+
const mark = copyNode(original);
|
|
13
|
+
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) return void 0;
|
|
14
|
+
if (!Number.isFinite(scaleValue(mark))) return void 0;
|
|
15
|
+
const isTick = mark.childElementCount === 0 && mark.textContent.trim() === "";
|
|
16
|
+
mark.classList.add(isTick ? "sonic-scale-tick" : "sonic-scale-label");
|
|
17
|
+
return mark;
|
|
18
|
+
}
|
|
19
|
+
function bindScale(element, { control, marks }, signal) {
|
|
20
|
+
const { mapping, watch } = linkValue(element);
|
|
21
|
+
const render = () => {
|
|
22
|
+
const { proportionOf } = mapping();
|
|
23
|
+
for (const mark of marks.children) {
|
|
24
|
+
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) continue;
|
|
25
|
+
const at = String(proportionOf(scaleValue(mark)));
|
|
26
|
+
if (mark.style.getPropertyValue("--_sonic-scale-at") !== at) mark.style.setProperty("--_sonic-scale-at", at);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
mirrorChildren(element, {
|
|
30
|
+
control,
|
|
31
|
+
copy: scaleMark,
|
|
32
|
+
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
|
|
33
|
+
isPassed: (child) => child instanceof Element && child.matches(".sonic-led"),
|
|
34
|
+
place: (copies) => {
|
|
35
|
+
placeChildren(marks, copies);
|
|
36
|
+
render();
|
|
37
|
+
}
|
|
38
|
+
}, signal);
|
|
39
|
+
signal.addEventListener("abort", watch(render), { once: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { bindScale };
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { clampProportion } from "../lib/math.js";
|
|
1
2
|
import { requireChild, template } from "../lib/render.js";
|
|
2
3
|
import { SonicValueElement } from "./value-element.js";
|
|
4
|
+
import { bindScale } from "./scale.js";
|
|
5
|
+
import { bindHoverPreview } from "../lib/hover-preview.js";
|
|
6
|
+
import { sortedRegions } from "../lib/time-regions.js";
|
|
3
7
|
|
|
4
8
|
//#region src/elements/slider.ts
|
|
5
9
|
function grooveStop(proportion) {
|
|
@@ -32,8 +36,16 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
32
36
|
...SonicValueElement.observedAttributes,
|
|
33
37
|
"modulation",
|
|
34
38
|
"orientation",
|
|
39
|
+
"scrub",
|
|
35
40
|
"spring"
|
|
36
41
|
];
|
|
42
|
+
get buffered() {
|
|
43
|
+
return this.#buffered.map(([start, end]) => [start, end]);
|
|
44
|
+
}
|
|
45
|
+
set buffered(regions) {
|
|
46
|
+
this.#buffered = sortedRegions(regions);
|
|
47
|
+
this.render();
|
|
48
|
+
}
|
|
37
49
|
get groovePress() {
|
|
38
50
|
return this.getAttribute("groove-press") === "none" ? "none" : "jump";
|
|
39
51
|
}
|
|
@@ -58,24 +70,52 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
58
70
|
set orientation(direction) {
|
|
59
71
|
this.reflect("orientation", direction);
|
|
60
72
|
}
|
|
73
|
+
get scrub() {
|
|
74
|
+
return this.hasAttribute("scrub");
|
|
75
|
+
}
|
|
76
|
+
set scrub(isScrubbing) {
|
|
77
|
+
this.reflect("scrub", isScrubbing);
|
|
78
|
+
}
|
|
61
79
|
get spring() {
|
|
62
80
|
return this.hasAttribute("spring");
|
|
63
81
|
}
|
|
64
82
|
set spring(isSpring) {
|
|
65
83
|
this.reflect("spring", isSpring);
|
|
66
84
|
}
|
|
85
|
+
#buffered = [];
|
|
86
|
+
#hover;
|
|
67
87
|
#slider = renderSlider();
|
|
68
88
|
connectedCallback() {
|
|
69
|
-
this.upgradeProperties("groovePress", "modulationValue", "modulation", "orientation", "spring");
|
|
89
|
+
this.upgradeProperties("buffered", "groovePress", "modulationValue", "modulation", "orientation", "scrub", "spring");
|
|
70
90
|
super.connectedCallback();
|
|
71
91
|
}
|
|
72
92
|
connect(signal) {
|
|
73
93
|
const slider = this.#slider;
|
|
74
94
|
const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
|
|
75
|
-
|
|
95
|
+
bindScale(this, {
|
|
96
|
+
control: slider,
|
|
97
|
+
marks: requireChild(slider, ".sonic-slider-scale", HTMLDivElement)
|
|
98
|
+
}, signal);
|
|
76
99
|
slider.addEventListener("transitionend", (event) => {
|
|
77
100
|
if (event.propertyName === "--_sonic-slider-value") this.toggleState("springing", false);
|
|
78
101
|
}, { signal });
|
|
102
|
+
this.#hover = bindHoverPreview(slider, {
|
|
103
|
+
canShow: () => this.scrub && this.readout && this.orientation === "horizontal",
|
|
104
|
+
dismiss: () => {
|
|
105
|
+
this.render();
|
|
106
|
+
},
|
|
107
|
+
isTaken: () => this.isEditing() || this.isRevealed(),
|
|
108
|
+
show: () => {
|
|
109
|
+
this.renderReadout();
|
|
110
|
+
this.#placeReadout(slider);
|
|
111
|
+
},
|
|
112
|
+
valueAt: (event) => {
|
|
113
|
+
const axis = this.#axis(slider, cap);
|
|
114
|
+
const at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);
|
|
115
|
+
const mapping = this.mapping();
|
|
116
|
+
return mapping.snap(mapping.valueAt(at));
|
|
117
|
+
}
|
|
118
|
+
}, signal);
|
|
79
119
|
this.render();
|
|
80
120
|
this.checkStyles(slider, "slider.css");
|
|
81
121
|
this.bindGestures(slider, signal, (event) => {
|
|
@@ -90,12 +130,28 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
90
130
|
return this.orientation;
|
|
91
131
|
}
|
|
92
132
|
draw() {
|
|
133
|
+
if (this.isEditing() || this.isRevealed()) this.#hover?.clear();
|
|
93
134
|
this.writeProportions(this.#slider, "slider", this.modulation);
|
|
94
135
|
this.#renderBuffered(this.#slider);
|
|
136
|
+
this.#renderScrub(this.#slider);
|
|
137
|
+
this.#placeReadout(this.#slider);
|
|
95
138
|
}
|
|
96
139
|
focusTarget() {
|
|
97
140
|
return this.#slider;
|
|
98
141
|
}
|
|
142
|
+
heldWrite(next) {
|
|
143
|
+
super.heldWrite(next);
|
|
144
|
+
if (this.scrub) this.render();
|
|
145
|
+
}
|
|
146
|
+
holdChanged() {
|
|
147
|
+
if (this.scrub) this.render();
|
|
148
|
+
}
|
|
149
|
+
previewValue() {
|
|
150
|
+
return this.#hover?.value();
|
|
151
|
+
}
|
|
152
|
+
scrubsKeyRepeat() {
|
|
153
|
+
return this.scrub;
|
|
154
|
+
}
|
|
99
155
|
springTarget() {
|
|
100
156
|
return this.spring ? this.originValue() : void 0;
|
|
101
157
|
}
|
|
@@ -112,8 +168,15 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
112
168
|
travelPx: Math.max(1, box.width - capBox.width)
|
|
113
169
|
};
|
|
114
170
|
}
|
|
171
|
+
#placeReadout(slider) {
|
|
172
|
+
if (!this.scrub) {
|
|
173
|
+
slider.style.removeProperty("--_sonic-slider-readout-value");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
slider.style.setProperty("--_sonic-slider-readout-value", String(this.mapping().proportionOf(this.readoutValue())));
|
|
177
|
+
}
|
|
115
178
|
#renderBuffered(slider) {
|
|
116
|
-
const buffered = this
|
|
179
|
+
const buffered = this.#buffered;
|
|
117
180
|
if (buffered.length === 0) {
|
|
118
181
|
slider.style.removeProperty("--_sonic-slider-buffered-regions");
|
|
119
182
|
return;
|
|
@@ -130,6 +193,22 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
130
193
|
});
|
|
131
194
|
slider.style.setProperty("--_sonic-slider-buffered-regions", `linear-gradient(var(--_sonic-slider-toward), ${stops.join(", ")})`);
|
|
132
195
|
}
|
|
196
|
+
#renderScrub(slider) {
|
|
197
|
+
const { style } = slider;
|
|
198
|
+
const { proportionOf } = this.mapping();
|
|
199
|
+
const isHeld = this.scrub && this.heldFrom() !== void 0;
|
|
200
|
+
const played = proportionOf(this.playback());
|
|
201
|
+
const scrubbed = proportionOf(this.value);
|
|
202
|
+
if (isHeld) style.setProperty("--_sonic-slider-played", String(played));
|
|
203
|
+
else style.removeProperty("--_sonic-slider-played");
|
|
204
|
+
if (played === scrubbed || !isHeld || !this.isRevealed()) {
|
|
205
|
+
style.removeProperty("--_sonic-slider-scrub-region");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const from = grooveStop(Math.min(played, scrubbed));
|
|
209
|
+
const to = grooveStop(Math.max(played, scrubbed));
|
|
210
|
+
style.setProperty("--_sonic-slider-scrub-region", `linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`);
|
|
211
|
+
}
|
|
133
212
|
};
|
|
134
213
|
|
|
135
214
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toNumber } from "../lib/math.js";
|
|
2
|
-
import {
|
|
2
|
+
import { appendOnce } from "../lib/owned-control.js";
|
|
3
3
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
4
|
|
|
5
5
|
//#region src/elements/sonic-element.ts
|
|
@@ -23,7 +23,6 @@ var SonicElement = class extends HTMLElement {
|
|
|
23
23
|
}
|
|
24
24
|
internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
|
|
25
25
|
#connection;
|
|
26
|
-
#slots;
|
|
27
26
|
blur() {
|
|
28
27
|
this.#focused()?.blur();
|
|
29
28
|
}
|
|
@@ -77,8 +76,7 @@ var SonicElement = class extends HTMLElement {
|
|
|
77
76
|
}
|
|
78
77
|
keepControl(control, signal) {
|
|
79
78
|
const keep = () => {
|
|
80
|
-
|
|
81
|
-
this.#checkChildren([...this.childNodes].filter((child) => child !== control));
|
|
79
|
+
appendOnce(this, control);
|
|
82
80
|
};
|
|
83
81
|
const observer = new MutationObserver(keep);
|
|
84
82
|
keep();
|
|
@@ -87,50 +85,6 @@ var SonicElement = class extends HTMLElement {
|
|
|
87
85
|
observer.disconnect();
|
|
88
86
|
}, { once: true });
|
|
89
87
|
}
|
|
90
|
-
mirrorChildren({ control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
|
|
91
|
-
const slots = this.#slots ?? attachSlots(this);
|
|
92
|
-
let copies = /* @__PURE__ */ new Map();
|
|
93
|
-
this.#slots = slots;
|
|
94
|
-
const mirror = (touched) => {
|
|
95
|
-
this.#appendOnce(control);
|
|
96
|
-
const children = [...this.childNodes].filter((child) => child !== control);
|
|
97
|
-
const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
|
|
98
|
-
const kept = copies;
|
|
99
|
-
const originals = children.filter((child) => isCopied(child));
|
|
100
|
-
this.#checkMirrored(originals);
|
|
101
|
-
this.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));
|
|
102
|
-
copies = new Map(originals.map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
|
|
103
|
-
place([...copies.values()].filter((made) => made !== void 0));
|
|
104
|
-
slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
|
|
105
|
-
slots.kept.assign(...slotted.filter((child) => isCopied(child)));
|
|
106
|
-
};
|
|
107
|
-
const watch = () => {
|
|
108
|
-
observer.disconnect();
|
|
109
|
-
observer.observe(this, { childList: true });
|
|
110
|
-
for (const child of this.childNodes) {
|
|
111
|
-
if (child === control) continue;
|
|
112
|
-
observer.observe(child, {
|
|
113
|
-
attributes: true,
|
|
114
|
-
characterData: true,
|
|
115
|
-
childList: true,
|
|
116
|
-
subtree: true
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
const observer = new MutationObserver((records) => {
|
|
121
|
-
const changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));
|
|
122
|
-
if (changes.length === 0) return;
|
|
123
|
-
if (changes.some((record) => record.target === this)) {
|
|
124
|
-
mirror();
|
|
125
|
-
watch();
|
|
126
|
-
} else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));
|
|
127
|
-
});
|
|
128
|
-
mirror();
|
|
129
|
-
watch();
|
|
130
|
-
signal.addEventListener("abort", () => {
|
|
131
|
-
observer.disconnect();
|
|
132
|
-
}, { once: true });
|
|
133
|
-
}
|
|
134
88
|
namingLabels() {
|
|
135
89
|
return [];
|
|
136
90
|
}
|
|
@@ -161,30 +115,11 @@ var SonicElement = class extends HTMLElement {
|
|
|
161
115
|
this[name] = value;
|
|
162
116
|
}
|
|
163
117
|
}
|
|
164
|
-
#appendOnce(control) {
|
|
165
|
-
const [hook] = control.classList;
|
|
166
|
-
const stale = [...this.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
|
|
167
|
-
for (const child of stale) child.remove();
|
|
168
|
-
if (control.parentNode !== this) this.append(control);
|
|
169
|
-
}
|
|
170
|
-
#checkChildren(undocumented) {}
|
|
171
|
-
#checkMirrored(originals) {}
|
|
172
|
-
#childHolding(node) {
|
|
173
|
-
let current = node;
|
|
174
|
-
while (current && current.parentNode !== this) current = current.parentNode;
|
|
175
|
-
return current ?? void 0;
|
|
176
|
-
}
|
|
177
118
|
#focused() {
|
|
178
119
|
const root = this.getRootNode();
|
|
179
120
|
const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
|
|
180
121
|
return active instanceof HTMLElement && this.contains(active) ? active : void 0;
|
|
181
122
|
}
|
|
182
|
-
#isCopiedChange(record, control, isCopied) {
|
|
183
|
-
if (record.target === this) return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
|
|
184
|
-
const child = this.#childHolding(record.target);
|
|
185
|
-
if (child === void 0) return false;
|
|
186
|
-
return isCopied(child) || record.type === "attributes" && record.target === child;
|
|
187
|
-
}
|
|
188
123
|
};
|
|
189
124
|
|
|
190
125
|
//#endregion
|