@xsynaptic/sonic-ui 0.12.0 → 0.14.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 +16 -0
- package/README.md +35 -0
- package/dist/default/elements/button.js +5 -1
- package/dist/default/elements/dial.js +7 -1
- package/dist/default/elements/display-element.js +5 -1
- package/dist/default/elements/envelope.js +6 -1
- package/dist/default/elements/meter.js +1 -1
- package/dist/default/elements/number.js +7 -1
- package/dist/default/elements/segmented.js +5 -1
- package/dist/default/elements/slider.js +8 -2
- package/dist/default/elements/sonic-element.js +1 -1
- package/dist/default/elements/switch.js +1 -1
- package/dist/default/elements/toggle.js +5 -1
- package/dist/default/elements/value-element.js +3 -0
- package/dist/default/elements/value-gestures.js +7 -0
- package/dist/default/elements/wave-element.js +8 -1
- package/dist/default/elements/waveform.js +129 -3
- package/dist/default/elements/xy.js +7 -1
- package/dist/default/lib/zoom-gesture.js +19 -0
- package/dist/dev/elements/button.js +5 -1
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +7 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.js +5 -1
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +6 -1
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +7 -1
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/segmented.js +5 -1
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.js +8 -2
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +16 -1
- package/dist/dev/elements/sonic-element.js +27 -5
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/switch.js +1 -1
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +5 -1
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +1 -0
- package/dist/dev/elements/value-element.js +3 -0
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +7 -0
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.js +8 -1
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +10 -0
- package/dist/dev/elements/waveform.js +129 -3
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/xy.js +7 -1
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/lib/zoom-gesture.js +20 -0
- package/dist/dev/lib/zoom-gesture.js.map +1 -0
- package/dist/styles/button.css +3 -1
- package/dist/styles/dial.css +3 -1
- package/dist/styles/envelope.css +3 -1
- package/dist/styles/led.css +3 -1
- package/dist/styles/material/arc.css +21 -0
- package/dist/styles/material/bracket.css +31 -0
- package/dist/styles/material/cap.css +179 -0
- package/dist/styles/material/core.css +103 -0
- package/dist/styles/material/glass.css +62 -0
- package/dist/styles/material/groove.css +10 -0
- package/dist/styles/material/keycap.css +95 -0
- package/dist/styles/material/lens.css +11 -0
- package/dist/styles/material/pane.css +97 -0
- package/dist/styles/material/readout.css +87 -0
- package/dist/styles/material/scale.css +30 -0
- package/dist/styles/material/value.css +43 -0
- package/dist/styles/material/well.css +24 -0
- package/dist/styles/material.css +15 -772
- package/dist/styles/meter.css +3 -1
- package/dist/styles/number.css +3 -1
- package/dist/styles/panel.css +3 -1
- package/dist/styles/ring.css +3 -1
- package/dist/styles/screen.css +3 -1
- package/dist/styles/segmented.css +3 -1
- package/dist/styles/slider.css +34 -19
- package/dist/styles/spectrum.css +3 -1
- package/dist/styles/switch.css +3 -1
- package/dist/styles/toggle.css +3 -1
- package/dist/styles/waveform.css +3 -1
- package/dist/styles/wavestrip.css +3 -1
- package/dist/styles/xy.css +3 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Slider: three tokens for a capless progress strip. `--sonic-slider-groove-radius-ratio` rounds the groove's ends as a ratio of its thickness (0.5 by default; at 0 a capless groove is exactly the control's length and its fills stop at their proportions of it). `--sonic-slider-groove-anchor` places the groove across the breadth, 0 at the start, 0.5 by default, 1 at the end, so a thickening groove grows one way. `--sonic-slider-indicator` colours the indicator line apart from the ink.
|
|
8
|
+
|
|
9
|
+
## 0.13.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- The material is split into sheets under `material/*.css`, so you can load only what your controls need; the README lists them. `material.css` and `controls.css` work as before, and sheets now load in any order.
|
|
14
|
+
|
|
15
|
+
New public surface: the material sheets, and the sublayers `sonic.material` and `sonic.control`. A rule written directly in `@layer sonic` now always wins over the control sheets.
|
|
16
|
+
|
|
17
|
+
- Waveform: `zoomable` opts in to zoom by ctrl or cmd wheel, two-finger pinch and the `+` and `-` keys, between `zoom-min` and `zoom-max` (20 and 280 by default). `sonic-zoom` fires for a gesture and never for a write to `zoom`. A second finger ends a scrub without a seek. Without `zoomable` nothing changes.
|
|
18
|
+
|
|
3
19
|
## 0.12.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -27,6 +27,39 @@ A display takes its signal as data: `spectrum.analyser = node`, or `spectrum.pus
|
|
|
27
27
|
|
|
28
28
|
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.
|
|
29
29
|
|
|
30
|
+
## Loading less
|
|
31
|
+
|
|
32
|
+
`controls.css` loads everything. To load less, import each control's sheet and the material sheets it needs, in any order:
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
@import '@xsynaptic/sonic-ui/material/core.css';
|
|
36
|
+
@import '@xsynaptic/sonic-ui/material/lens.css';
|
|
37
|
+
@import '@xsynaptic/sonic-ui/material/groove.css';
|
|
38
|
+
@import '@xsynaptic/sonic-ui/meter.css';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Sheet | Needs from `material/` |
|
|
42
|
+
| --------------- | ----------------------------------------------- |
|
|
43
|
+
| `button.css` | core, cap, well, keycap |
|
|
44
|
+
| `dial.css` | core, cap, glass, readout, value, arc, scale |
|
|
45
|
+
| `envelope.css` | core, glass, pane, well, readout, bracket |
|
|
46
|
+
| `led.css` | core, lens |
|
|
47
|
+
| `meter.css` | core, lens, groove |
|
|
48
|
+
| `number.css` | core, cap, glass, pane, well, value |
|
|
49
|
+
| `panel.css` | core |
|
|
50
|
+
| `ring.css` | core, arc |
|
|
51
|
+
| `screen.css` | core, glass, pane, well |
|
|
52
|
+
| `segmented.css` | core, cap, well, keycap |
|
|
53
|
+
| `slider.css` | core, cap, glass, readout, value, groove, scale |
|
|
54
|
+
| `spectrum.css` | core, glass, pane, well |
|
|
55
|
+
| `switch.css` | core, cap |
|
|
56
|
+
| `toggle.css` | core, cap, well, keycap |
|
|
57
|
+
| `waveform.css` | core, cap, glass, pane, well, readout, value |
|
|
58
|
+
| `wavestrip.css` | core, cap, glass, pane, well, readout, value |
|
|
59
|
+
| `xy.css` | core, cap, glass, pane, well, readout, bracket |
|
|
60
|
+
|
|
61
|
+
An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`. The development build warns about a missing sheet.
|
|
62
|
+
|
|
30
63
|
## Wave controls
|
|
31
64
|
|
|
32
65
|
`<sonic-wavestrip>` is the overview of a track, scrubbed to seek; `<sonic-waveform>` is the close-up that scrolls under a fixed playhead. Both are value controls in seconds that take their data as properties (`peaks`, `markers`); the types list the rest.
|
|
@@ -37,6 +70,8 @@ The strip draws one part per marker inside `.sonic-wavestrip-markers`, in `start
|
|
|
37
70
|
|
|
38
71
|
The waveform calls `requestPeaks(fromSeconds, toSeconds)` as its window moves, often every frame, so return at once for a span already loaded or on its way. Return a promise, or several, and the waveform repaints as each settles; an `async` function is the wrong shape, since it hands back a new settled promise each call. `pending` is yours to keep: list the spans being fetched, and clear each as it lands.
|
|
39
72
|
|
|
73
|
+
With `zoomable`, a ctrl or cmd wheel, a pinch and the `+` and `-` keys set the waveform's `zoom` and fire `sonic-zoom`; a write to `zoom` fires nothing.
|
|
74
|
+
|
|
40
75
|
With `fill` a control takes its container's height and its ratios follow. `--sonic-wavestrip-marker-size`, `--sonic-wavestrip-groove-size` and `--sonic-waveform-label-font-size` hold an absolute length instead.
|
|
41
76
|
|
|
42
77
|
Targets current Chrome and Edge, Safari 26 and Firefox 147. MIT licensed.
|
|
@@ -114,7 +114,11 @@ var SonicButton = class extends SonicFormElement {
|
|
|
114
114
|
}
|
|
115
115
|
}, signal);
|
|
116
116
|
this.render();
|
|
117
|
-
this.checkStyles(button, "button.css"
|
|
117
|
+
this.checkStyles(button, "button.css", [
|
|
118
|
+
"cap",
|
|
119
|
+
"well",
|
|
120
|
+
"keycap"
|
|
121
|
+
]);
|
|
118
122
|
button.addEventListener("click", (event) => {
|
|
119
123
|
if (this.#isSoftDisabled()) {
|
|
120
124
|
event.preventDefault();
|
|
@@ -64,7 +64,13 @@ var SonicDial = class extends SonicValueElement {
|
|
|
64
64
|
marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
|
|
65
65
|
}, signal);
|
|
66
66
|
this.render();
|
|
67
|
-
this.checkStyles(dial, "dial.css"
|
|
67
|
+
this.checkStyles(dial, "dial.css", [
|
|
68
|
+
"cap",
|
|
69
|
+
"readout",
|
|
70
|
+
"value",
|
|
71
|
+
["glass", ".sonic-dial-readout"],
|
|
72
|
+
["arc", ".sonic-dial-ring"]
|
|
73
|
+
]);
|
|
68
74
|
this.bindGestures(dial, signal, () => ({
|
|
69
75
|
position: (event) => -event.clientY,
|
|
70
76
|
travelPx: this.travelPx(dial, "--_sonic-dial-travel")
|
|
@@ -66,7 +66,11 @@ var SonicDisplayElement = class extends SonicElement {
|
|
|
66
66
|
},
|
|
67
67
|
signal
|
|
68
68
|
});
|
|
69
|
-
this.checkStyles(control, this.sheet
|
|
69
|
+
this.checkStyles(control, this.sheet, [
|
|
70
|
+
"glass",
|
|
71
|
+
"pane",
|
|
72
|
+
"well"
|
|
73
|
+
]);
|
|
70
74
|
this.#syncFill = bindFill(control, this.sizeProperty, signal);
|
|
71
75
|
this.#syncFill(this.fill);
|
|
72
76
|
}
|
|
@@ -170,7 +170,12 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
170
170
|
this.#signal = signal;
|
|
171
171
|
this.keepControl(envelope, signal);
|
|
172
172
|
this.#bind();
|
|
173
|
-
this.checkStyles(envelope, "envelope.css"
|
|
173
|
+
this.checkStyles(envelope, "envelope.css", [
|
|
174
|
+
"glass",
|
|
175
|
+
"pane",
|
|
176
|
+
"well",
|
|
177
|
+
"readout"
|
|
178
|
+
]);
|
|
174
179
|
this.#bindPointer(envelope, signal);
|
|
175
180
|
signal.addEventListener("abort", () => {
|
|
176
181
|
this.#signal = void 0;
|
|
@@ -138,7 +138,7 @@ var SonicMeter = class extends SonicElement {
|
|
|
138
138
|
this.#renderSegments();
|
|
139
139
|
this.#render(now);
|
|
140
140
|
this.#schedule(now);
|
|
141
|
-
this.checkStyles(this.#meter, "meter.css");
|
|
141
|
+
this.checkStyles(this.#meter, "meter.css", ["lens", "groove"]);
|
|
142
142
|
signal.addEventListener("abort", () => {
|
|
143
143
|
if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
|
|
144
144
|
this.#frame = void 0;
|
|
@@ -32,7 +32,13 @@ var SonicNumber = class extends SonicValueElement {
|
|
|
32
32
|
const number = this.#number;
|
|
33
33
|
this.keepControl(number, signal);
|
|
34
34
|
this.render();
|
|
35
|
-
this.checkStyles(number, "number.css"
|
|
35
|
+
this.checkStyles(number, "number.css", [
|
|
36
|
+
"cap",
|
|
37
|
+
"glass",
|
|
38
|
+
"pane",
|
|
39
|
+
"well",
|
|
40
|
+
"value"
|
|
41
|
+
]);
|
|
36
42
|
this.bindGestures(number, signal, () => ({
|
|
37
43
|
position: (event) => -event.clientY,
|
|
38
44
|
travelPx: this.travelPx(number, "--_sonic-number-travel")
|
|
@@ -12,7 +12,11 @@ var SonicSegmented = class extends SonicRadioGroupElement {
|
|
|
12
12
|
group = renderSegmented();
|
|
13
13
|
connect(signal) {
|
|
14
14
|
super.connect(signal);
|
|
15
|
-
this.checkStyles(this.group, "segmented.css"
|
|
15
|
+
this.checkStyles(this.group, "segmented.css", [
|
|
16
|
+
"well",
|
|
17
|
+
"keycap",
|
|
18
|
+
["cap", ".sonic-segmented-option"]
|
|
19
|
+
]);
|
|
16
20
|
}
|
|
17
21
|
renderOption() {
|
|
18
22
|
return renderOption();
|
|
@@ -9,7 +9,7 @@ import { sortedRegions } from "../lib/time-regions.js";
|
|
|
9
9
|
function grooveStop(proportion) {
|
|
10
10
|
if (proportion <= 0) return "0%";
|
|
11
11
|
if (proportion >= 1) return "100%";
|
|
12
|
-
return `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove)
|
|
12
|
+
return `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;
|
|
13
13
|
}
|
|
14
14
|
const renderSlider = template(`
|
|
15
15
|
<div class="sonic-slider" role="slider" tabindex="0">
|
|
@@ -114,7 +114,13 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
this.render();
|
|
117
|
-
this.checkStyles(slider, "slider.css"
|
|
117
|
+
this.checkStyles(slider, "slider.css", [
|
|
118
|
+
"cap",
|
|
119
|
+
"readout",
|
|
120
|
+
"value",
|
|
121
|
+
"groove",
|
|
122
|
+
["glass", ".sonic-slider-readout"]
|
|
123
|
+
]);
|
|
118
124
|
this.bindGestures(slider, signal, (event) => {
|
|
119
125
|
const axis = this.#axis(slider, cap);
|
|
120
126
|
if (event.target instanceof Node && cap.contains(event.target)) return axis;
|
|
@@ -58,7 +58,7 @@ var SonicElement = class extends HTMLElement {
|
|
|
58
58
|
}
|
|
59
59
|
if (!this.isDisabled() && !this.#focused()) target.focus(options);
|
|
60
60
|
}
|
|
61
|
-
checkStyles(control, sheet) {}
|
|
61
|
+
checkStyles(control, sheet, material = []) {}
|
|
62
62
|
focusTarget() {}
|
|
63
63
|
forwardNaming(target, isNamed) {
|
|
64
64
|
const labels = isNamed ? this.namingLabels() : [];
|
|
@@ -34,7 +34,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
|
|
|
34
34
|
connect(signal) {
|
|
35
35
|
const group = this.group;
|
|
36
36
|
super.connect(signal);
|
|
37
|
-
this.checkStyles(group, "switch.css");
|
|
37
|
+
this.checkStyles(group, "switch.css", ["cap"]);
|
|
38
38
|
this.bare.addEventListener("click", (event) => {
|
|
39
39
|
const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
|
|
40
40
|
this.#isClickSwallowed = false;
|
|
@@ -31,7 +31,11 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
31
31
|
}
|
|
32
32
|
connect(signal) {
|
|
33
33
|
super.connect(signal);
|
|
34
|
-
this.checkStyles(this.group, "toggle.css"
|
|
34
|
+
this.checkStyles(this.group, "toggle.css", [
|
|
35
|
+
"cap",
|
|
36
|
+
"well",
|
|
37
|
+
"keycap"
|
|
38
|
+
]);
|
|
35
39
|
this.bare.addEventListener("click", (event) => {
|
|
36
40
|
if (event instanceof PointerEvent && event.pointerType !== "") return;
|
|
37
41
|
this.setChecked(!this.checked);
|
|
@@ -259,6 +259,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
259
259
|
this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "spokenStep", "step", "taper", "positions", "detent", "formatEntry", "formatValue", "formatSpokenValue", "keyStep", "parseValue", "value");
|
|
260
260
|
super.connectedCallback();
|
|
261
261
|
}
|
|
262
|
+
abandonHold() {
|
|
263
|
+
this.#gestures?.abandon();
|
|
264
|
+
}
|
|
262
265
|
bindGestures(control, signal, grab) {
|
|
263
266
|
const bubble = control.querySelector("[popover]");
|
|
264
267
|
if (bubble) this.#readout = {
|
|
@@ -27,6 +27,13 @@ var ValueGestures = class {
|
|
|
27
27
|
this.#host = host;
|
|
28
28
|
this.#control = control;
|
|
29
29
|
}
|
|
30
|
+
abandon() {
|
|
31
|
+
const drag = this.#dragging();
|
|
32
|
+
if (!drag) return;
|
|
33
|
+
this.#host.input(drag.fromValue);
|
|
34
|
+
this.#entry?.forgetPress();
|
|
35
|
+
this.#pointerDrag?.end();
|
|
36
|
+
}
|
|
30
37
|
bind(signal, grab) {
|
|
31
38
|
const control = this.#control;
|
|
32
39
|
const host = this.#host;
|
|
@@ -63,7 +63,14 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
63
63
|
signal
|
|
64
64
|
});
|
|
65
65
|
this.render();
|
|
66
|
-
this.checkStyles(control, this.sheet
|
|
66
|
+
this.checkStyles(control, this.sheet, [
|
|
67
|
+
"cap",
|
|
68
|
+
"glass",
|
|
69
|
+
"pane",
|
|
70
|
+
"well",
|
|
71
|
+
"readout",
|
|
72
|
+
"value"
|
|
73
|
+
]);
|
|
67
74
|
this.#syncFill = bindFill(control, this.sizeProperty, signal);
|
|
68
75
|
this.#syncFill(this.fill);
|
|
69
76
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { clamp } from "../lib/math.js";
|
|
1
|
+
import { clamp, roundTo } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
|
+
import { capturePointer } from "../lib/pointer-drag.js";
|
|
3
4
|
import { dueRegions, readRegions } from "../lib/time-regions.js";
|
|
4
5
|
import { SonicWaveElement } from "./wave-element.js";
|
|
5
6
|
import { frameTimeline } from "../lib/frame-timeline.js";
|
|
@@ -7,6 +8,7 @@ import { isKind, writeKind } from "../lib/marker-band.js";
|
|
|
7
8
|
import { createLabelRider } from "../lib/marker-rider.js";
|
|
8
9
|
import { createTrackingClock } from "../lib/tracking-clock.js";
|
|
9
10
|
import { waveformBuckets } from "../lib/waveform-buckets.js";
|
|
11
|
+
import { pinchFactor, wheelFactor, zoomKeyFactor } from "../lib/zoom-gesture.js";
|
|
10
12
|
|
|
11
13
|
//#region src/elements/waveform.ts
|
|
12
14
|
const colours = {
|
|
@@ -18,6 +20,13 @@ const colours = {
|
|
|
18
20
|
waveEdge: "--_sonic-waveform-wave-edge"
|
|
19
21
|
};
|
|
20
22
|
const defaultZoom = 70;
|
|
23
|
+
const defaultZoomMin = 20;
|
|
24
|
+
const defaultZoomMax = 280;
|
|
25
|
+
const zoomKeys = /* @__PURE__ */ new Map([
|
|
26
|
+
["+", 1],
|
|
27
|
+
["-", -1],
|
|
28
|
+
["=", 1]
|
|
29
|
+
]);
|
|
21
30
|
const amplitudeMargin = .94;
|
|
22
31
|
const hatchPitchPx = 8;
|
|
23
32
|
const placeholderWavelengthPx = 48;
|
|
@@ -117,7 +126,10 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
117
126
|
"playing",
|
|
118
127
|
"playback-rate",
|
|
119
128
|
"reduced-motion",
|
|
120
|
-
"zoom"
|
|
129
|
+
"zoom",
|
|
130
|
+
"zoom-max",
|
|
131
|
+
"zoom-min",
|
|
132
|
+
"zoomable"
|
|
121
133
|
];
|
|
122
134
|
get peaks() {
|
|
123
135
|
return this.#peaks;
|
|
@@ -189,6 +201,26 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
189
201
|
set zoom(value) {
|
|
190
202
|
this.reflect("zoom", value);
|
|
191
203
|
}
|
|
204
|
+
get zoomable() {
|
|
205
|
+
return this.hasAttribute("zoomable");
|
|
206
|
+
}
|
|
207
|
+
set zoomable(isZoomable) {
|
|
208
|
+
this.reflect("zoomable", isZoomable);
|
|
209
|
+
}
|
|
210
|
+
get zoomMax() {
|
|
211
|
+
const zoomMax = this.numberAttribute("zoom-max", defaultZoomMax);
|
|
212
|
+
return Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);
|
|
213
|
+
}
|
|
214
|
+
set zoomMax(value) {
|
|
215
|
+
this.reflect("zoom-max", value);
|
|
216
|
+
}
|
|
217
|
+
get zoomMin() {
|
|
218
|
+
const zoomMin = this.numberAttribute("zoom-min", defaultZoomMin);
|
|
219
|
+
return zoomMin > 0 ? zoomMin : defaultZoomMin;
|
|
220
|
+
}
|
|
221
|
+
set zoomMin(value) {
|
|
222
|
+
this.reflect("zoom-min", value);
|
|
223
|
+
}
|
|
192
224
|
control = renderWaveform();
|
|
193
225
|
canvas = requireChild(this.control, ".sonic-waveform-canvas", HTMLCanvasElement);
|
|
194
226
|
colours = colours;
|
|
@@ -198,15 +230,18 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
198
230
|
#asked = /* @__PURE__ */ new WeakSet();
|
|
199
231
|
#askedKey;
|
|
200
232
|
#clock = createTrackingClock();
|
|
233
|
+
#connection;
|
|
201
234
|
#drawn;
|
|
202
235
|
#ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
|
|
203
236
|
#grabbed;
|
|
204
237
|
#isAsking = false;
|
|
238
|
+
#isPinchSpent = false;
|
|
205
239
|
#kindColours = /* @__PURE__ */ new Map();
|
|
206
240
|
#paintedKey = "";
|
|
207
241
|
#peaks;
|
|
208
242
|
#pending = [];
|
|
209
243
|
#pendingListedMs = /* @__PURE__ */ new Map();
|
|
244
|
+
#pinch;
|
|
210
245
|
#playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
|
|
211
246
|
#readTime;
|
|
212
247
|
#requestPeaks;
|
|
@@ -217,13 +252,27 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
217
252
|
insetProperty: "--_sonic-waveform-label-inset",
|
|
218
253
|
parkProperty: "--_sonic-waveform-label-park"
|
|
219
254
|
});
|
|
255
|
+
#touches = /* @__PURE__ */ new Map();
|
|
256
|
+
#wheel;
|
|
257
|
+
attributeChangedCallback(name) {
|
|
258
|
+
super.attributeChangedCallback(name);
|
|
259
|
+
if (name === "zoomable") this.#bindWheel();
|
|
260
|
+
}
|
|
220
261
|
connectedCallback() {
|
|
221
|
-
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom");
|
|
262
|
+
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
222
263
|
super.connectedCallback();
|
|
223
264
|
}
|
|
224
265
|
connect(signal) {
|
|
225
266
|
super.connect(signal);
|
|
267
|
+
this.#connection = signal;
|
|
268
|
+
signal.addEventListener("abort", () => {
|
|
269
|
+
this.#connection = void 0;
|
|
270
|
+
this.#bindWheel();
|
|
271
|
+
}, { once: true });
|
|
272
|
+
this.#bindWheel();
|
|
273
|
+
this.#bindPinch(signal);
|
|
226
274
|
this.bindGestures(this.control, signal, () => this.#grab());
|
|
275
|
+
this.#bindZoomKeys(signal);
|
|
227
276
|
if (!("fonts" in document)) return;
|
|
228
277
|
document.fonts.addEventListener("loadingdone", () => {
|
|
229
278
|
this.renderMarkers();
|
|
@@ -309,12 +358,70 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
309
358
|
this.#repaintAfter(promise);
|
|
310
359
|
}
|
|
311
360
|
}
|
|
361
|
+
#bindPinch(signal) {
|
|
362
|
+
const control = this.control;
|
|
363
|
+
this.#touches.clear();
|
|
364
|
+
this.#pinch = void 0;
|
|
365
|
+
this.#isPinchSpent = false;
|
|
366
|
+
control.addEventListener("pointerdown", (event) => {
|
|
367
|
+
if (event.pointerType !== "touch" || !this.zoomable || this.isDisabled()) return;
|
|
368
|
+
this.#touches.set(event.pointerId, event.clientX);
|
|
369
|
+
if (this.#pinch || this.#isPinchSpent) return;
|
|
370
|
+
const pointerIds = this.#touchPair();
|
|
371
|
+
if (!pointerIds) return;
|
|
372
|
+
this.abandonHold();
|
|
373
|
+
capturePointer(control, event.pointerId);
|
|
374
|
+
this.#pinch = {
|
|
375
|
+
pointerIds,
|
|
376
|
+
startSpanPx: this.#spanPx(pointerIds),
|
|
377
|
+
startZoom: this.zoom
|
|
378
|
+
};
|
|
379
|
+
}, { signal });
|
|
380
|
+
control.addEventListener("pointermove", (event) => {
|
|
381
|
+
if (!this.#touches.has(event.pointerId)) return;
|
|
382
|
+
this.#touches.set(event.pointerId, event.clientX);
|
|
383
|
+
const pinch = this.#pinch;
|
|
384
|
+
if (!pinch?.pointerIds.includes(event.pointerId)) return;
|
|
385
|
+
this.#zoomTo(pinch.startZoom * pinchFactor(pinch.startSpanPx, this.#spanPx(pinch.pointerIds)));
|
|
386
|
+
}, { signal });
|
|
387
|
+
for (const type of ["pointerup", "pointercancel"]) control.addEventListener(type, (event) => {
|
|
388
|
+
if (!this.#touches.delete(event.pointerId)) return;
|
|
389
|
+
if (this.#pinch?.pointerIds.includes(event.pointerId)) {
|
|
390
|
+
this.#pinch = void 0;
|
|
391
|
+
this.#isPinchSpent = true;
|
|
392
|
+
}
|
|
393
|
+
if (this.#touches.size === 0) this.#isPinchSpent = false;
|
|
394
|
+
}, { signal });
|
|
395
|
+
}
|
|
396
|
+
#bindWheel() {
|
|
397
|
+
this.#wheel?.abort();
|
|
398
|
+
this.#wheel = void 0;
|
|
399
|
+
if (!this.zoomable || !this.#connection) return;
|
|
400
|
+
this.#wheel = new AbortController();
|
|
401
|
+
this.control.addEventListener("wheel", (event) => {
|
|
402
|
+
if (!event.ctrlKey && !event.metaKey || this.isDisabled()) return;
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
this.#zoomTo(this.zoom * wheelFactor(event.deltaY, event.deltaMode));
|
|
405
|
+
}, {
|
|
406
|
+
passive: false,
|
|
407
|
+
signal: this.#wheel.signal
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
#bindZoomKeys(signal) {
|
|
411
|
+
this.control.addEventListener("keydown", (event) => {
|
|
412
|
+
const direction = this.#zoomKey(event);
|
|
413
|
+
if (direction === void 0) return;
|
|
414
|
+
event.preventDefault();
|
|
415
|
+
this.#zoomTo(this.zoom * zoomKeyFactor ** direction);
|
|
416
|
+
}, { signal });
|
|
417
|
+
}
|
|
312
418
|
#duePending() {
|
|
313
419
|
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
314
420
|
if (due.length < this.#pending.length) this.surface()?.requestFrame();
|
|
315
421
|
return due;
|
|
316
422
|
}
|
|
317
423
|
#grab() {
|
|
424
|
+
if (this.#pinch || this.#isPinchSpent) return void 0;
|
|
318
425
|
const drawn = this.#drawn ?? {
|
|
319
426
|
seconds: this.value,
|
|
320
427
|
startSeconds: this.value
|
|
@@ -391,6 +498,25 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
391
498
|
#sourceSeconds() {
|
|
392
499
|
return this.#readTime?.() ?? this.playback();
|
|
393
500
|
}
|
|
501
|
+
#spanPx([first, second]) {
|
|
502
|
+
return (this.#touches.get(second) ?? 0) - (this.#touches.get(first) ?? 0);
|
|
503
|
+
}
|
|
504
|
+
#touchPair() {
|
|
505
|
+
const [first, second, third] = this.#touches.keys();
|
|
506
|
+
if (first === void 0 || second === void 0 || third !== void 0) return void 0;
|
|
507
|
+
return [first, second];
|
|
508
|
+
}
|
|
509
|
+
#zoomKey(event) {
|
|
510
|
+
if (!this.zoomable || this.isDisabled() || event.target !== this.control) return void 0;
|
|
511
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return void 0;
|
|
512
|
+
return zoomKeys.get(event.key);
|
|
513
|
+
}
|
|
514
|
+
#zoomTo(target) {
|
|
515
|
+
const next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);
|
|
516
|
+
if (next === this.zoom) return;
|
|
517
|
+
this.zoom = next;
|
|
518
|
+
this.dispatchEvent(new Event("sonic-zoom", { bubbles: true }));
|
|
519
|
+
}
|
|
394
520
|
};
|
|
395
521
|
|
|
396
522
|
//#endregion
|
|
@@ -238,7 +238,13 @@ var SonicXy = class extends SonicFormElement {
|
|
|
238
238
|
const xy = this.#xy;
|
|
239
239
|
this.keepControl(xy, signal);
|
|
240
240
|
this.render();
|
|
241
|
-
this.checkStyles(xy, "xy.css"
|
|
241
|
+
this.checkStyles(xy, "xy.css", [
|
|
242
|
+
"cap",
|
|
243
|
+
"glass",
|
|
244
|
+
"pane",
|
|
245
|
+
"well",
|
|
246
|
+
"readout"
|
|
247
|
+
]);
|
|
242
248
|
this.#bindPointer(xy, signal);
|
|
243
249
|
this.#bindKeys(xy, signal);
|
|
244
250
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/zoom-gesture.ts
|
|
4
|
+
const wheelRate = .01;
|
|
5
|
+
const linePx = 16;
|
|
6
|
+
const pixelMode = 0;
|
|
7
|
+
const wheelCapPx = 25;
|
|
8
|
+
const minSpanPx = 16;
|
|
9
|
+
const zoomKeyFactor = Math.exp(wheelCapPx * wheelRate);
|
|
10
|
+
function wheelFactor(deltaY, deltaMode) {
|
|
11
|
+
const pixels = deltaMode === pixelMode ? deltaY : deltaY * linePx;
|
|
12
|
+
return Math.exp(-clamp(pixels, -25, wheelCapPx) * wheelRate);
|
|
13
|
+
}
|
|
14
|
+
function pinchFactor(startSpanPx, spanPx) {
|
|
15
|
+
return Math.max(minSpanPx, Math.abs(spanPx)) / Math.max(minSpanPx, Math.abs(startSpanPx));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { pinchFactor, wheelFactor, zoomKeyFactor };
|
|
@@ -114,7 +114,11 @@ var SonicButton = class extends SonicFormElement {
|
|
|
114
114
|
}
|
|
115
115
|
}, signal);
|
|
116
116
|
this.render();
|
|
117
|
-
this.checkStyles(button, "button.css"
|
|
117
|
+
this.checkStyles(button, "button.css", [
|
|
118
|
+
"cap",
|
|
119
|
+
"well",
|
|
120
|
+
"keycap"
|
|
121
|
+
]);
|
|
118
122
|
button.addEventListener("click", (event) => {
|
|
119
123
|
if (this.#isSoftDisabled()) {
|
|
120
124
|
event.preventDefault();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css');\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css', ['cap', 'well', 'keycap']);\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -64,7 +64,13 @@ var SonicDial = class extends SonicValueElement {
|
|
|
64
64
|
marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
|
|
65
65
|
}, signal);
|
|
66
66
|
this.render();
|
|
67
|
-
this.checkStyles(dial, "dial.css"
|
|
67
|
+
this.checkStyles(dial, "dial.css", [
|
|
68
|
+
"cap",
|
|
69
|
+
"readout",
|
|
70
|
+
"value",
|
|
71
|
+
["glass", ".sonic-dial-readout"],
|
|
72
|
+
["arc", ".sonic-dial-ring"]
|
|
73
|
+
]);
|
|
68
74
|
this.bindGestures(dial, signal, () => ({
|
|
69
75
|
position: (event) => -event.clientY,
|
|
70
76
|
travelPx: this.travelPx(dial, "--_sonic-dial-travel")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,
|
|
1
|
+
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t['glass', '.sonic-dial-readout'],\n\t\t\t['arc', '.sonic-dial-ring'],\n\t\t]);\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,YAAY;GAClC;GACA;GACA;GACA,CAAC,SAAS,qBAAqB;GAC/B,CAAC,OAAO,kBAAkB;EAC3B,CAAC;EACD,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
|
|
@@ -66,7 +66,11 @@ var SonicDisplayElement = class extends SonicElement {
|
|
|
66
66
|
},
|
|
67
67
|
signal
|
|
68
68
|
});
|
|
69
|
-
this.checkStyles(control, this.sheet
|
|
69
|
+
this.checkStyles(control, this.sheet, [
|
|
70
|
+
"glass",
|
|
71
|
+
"pane",
|
|
72
|
+
"well"
|
|
73
|
+
]);
|
|
70
74
|
this.#syncFill = bindFill(control, this.sizeProperty, signal);
|
|
71
75
|
this.#syncFill(this.fill);
|
|
72
76
|
}
|