@xsynaptic/sonic-ui 0.18.0 → 0.19.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 +15 -0
- package/README.md +12 -2
- package/dist/default/elements/button.js +0 -5
- package/dist/default/elements/dial.js +0 -7
- package/dist/default/elements/display-element.js +0 -5
- package/dist/default/elements/envelope.js +0 -6
- package/dist/default/elements/meter.js +0 -1
- package/dist/default/elements/number.js +0 -7
- package/dist/default/elements/segmented.js +0 -5
- package/dist/default/elements/slider.js +0 -7
- package/dist/default/elements/sonic-element.js +3 -1
- package/dist/default/elements/switch.js +0 -1
- package/dist/default/elements/toggle.js +0 -5
- package/dist/default/elements/value-element.js +8 -4
- package/dist/default/elements/wave-element.js +7 -8
- package/dist/default/elements/xy.js +0 -7
- package/dist/default/index.js +2 -1
- package/dist/default/lib/clock.js +23 -0
- package/dist/default/lib/spoken-duration.js +30 -0
- package/dist/default/lib/waveform-buckets.js +7 -1
- package/dist/dev/elements/button.js +12 -5
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +14 -7
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.js +2 -2
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +17 -6
- 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 +13 -7
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/segmented.js +12 -5
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.js +14 -7
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +13 -2
- package/dist/dev/elements/sonic-element.js +63 -11
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.js +7 -1
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +12 -5
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +6 -0
- package/dist/dev/elements/value-element.js +8 -4
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +2 -1
- package/dist/dev/elements/wave-element.js +21 -8
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +1 -1
- package/dist/dev/elements/xy.js +14 -7
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/clock.d.ts +5 -0
- package/dist/dev/lib/clock.js +24 -0
- package/dist/dev/lib/clock.js.map +1 -0
- package/dist/dev/lib/mirror-children.js.map +1 -1
- package/dist/dev/lib/owned-control.js.map +1 -1
- package/dist/dev/lib/spoken-duration.js +31 -0
- package/dist/dev/lib/spoken-duration.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +2 -2
- package/dist/dev/lib/waveform-buckets.js +7 -1
- package/dist/dev/lib/waveform-buckets.js.map +1 -1
- package/dist/skins/amber.css +1 -0
- package/dist/skins/ivory.css +1 -0
- package/dist/skins/lime.css +1 -0
- package/dist/skins/slate.css +1 -0
- package/dist/styles/button.css +29 -5
- package/dist/styles/dial.css +10 -9
- package/dist/styles/envelope.css +10 -4
- package/dist/styles/led.css +5 -1
- package/dist/styles/material/arc.css +3 -2
- package/dist/styles/material/bracket.css +2 -2
- package/dist/styles/material/cap.css +33 -12
- package/dist/styles/material/core.css +4 -0
- package/dist/styles/material/glass.css +1 -1
- package/dist/styles/material/groove.css +4 -3
- package/dist/styles/material/keycap.css +3 -2
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +5 -4
- package/dist/styles/material/readout.css +6 -6
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/meter.css +2 -7
- package/dist/styles/number.css +5 -1
- package/dist/styles/panel.css +4 -3
- package/dist/styles/ring.css +1 -1
- package/dist/styles/screen.css +5 -1
- package/dist/styles/segmented.css +17 -4
- package/dist/styles/slider.css +9 -3
- package/dist/styles/spectrum.css +16 -3
- package/dist/styles/switch.css +35 -10
- package/dist/styles/toggle.css +14 -7
- package/dist/styles/waveform.css +15 -4
- package/dist/styles/wavestrip.css +11 -3
- package/dist/styles/xy.css +9 -5
- package/package.json +1 -6
- package/dist/default/dat.js +0 -52
- package/dist/dev/dat.d.ts +0 -20
- package/dist/dev/dat.js +0 -53
- package/dist/dev/dat.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- - Shared tokens, each unset or at today's value: `--sonic-press-duration` (60ms) and `--sonic-fade-duration` (120ms) time a cap's press and every fade; `--sonic-shade` (black) and `--sonic-shine` (white) are the poles the light mixes a surface towards.
|
|
8
|
+
- `--sonic-legend-font-size` sets the text of a button, a segmented option, a switch and a toggle. `--sonic-radius` sets the outer corner of a button, a segmented control, a number box, a screen, a slider's cap, a wave strip, a waveform, a spectrum, an envelope and an XY pad. A control's own `-font-ratio` or `-radius-ratio` still wins, and under the shared radius a button's face follows its corner.
|
|
9
|
+
- Waveform and wave strip: the inset ratio now defaults to 0, so the wave fills its glass; set `--sonic-waveform-inset-ratio: 0.06` or `--sonic-wavestrip-inset-ratio: 0.1` for the inset each had. The slate, amber, ivory and lime skins keep the wave strip's 0.1, which holds its markers clear of the well's shadow.
|
|
10
|
+
- Spectrum and wave strip: the canvas is rounded to the glass, so a large radius no longer leaves corners poking out.
|
|
11
|
+
- A cast shadow or highlight now computes to `color(srgb …)` where it was `rgba()`; nothing drawn changes.
|
|
12
|
+
- In development a control warns about a box property on its host, a padding zeroed by an unlayered rule, and a missing `material/scale.css` or `material/bracket.css`. The README has the recipe for layering a reset.
|
|
13
|
+
- - A waveform or a wave strip now shows, reads and speaks a clock when no formatter is set: `1:23` in the readout and the typed entry, and a duration in the nearest `lang` for assistive technology. Setting `formatValue`, `parseValue` or `formatSpokenValue` replaces that part.
|
|
14
|
+
- `formatClock` and `parseClock` are exported, so your own time text can match. A negative time is written with `−` (U+2212), not the ASCII hyphen; `parseClock` reads either.
|
|
15
|
+
- `fullScale` is optional on `peaks` and follows the array when unset.
|
|
16
|
+
- `./dat` is removed. Pass `peaks` as `{ pairsPerSecond, samples }`, where the first is a header's `sampleRate / samplesPerPixel`.
|
|
17
|
+
|
|
3
18
|
## 0.18.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -34,9 +34,17 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
- Every sheet sits in the `sonic` layer, so declare it below the layer your own rules live in.
|
|
37
|
+
- A rule outside any layer beats every layered one, so an unlayered reset (`* { margin: 0; padding: 0 }`, a zeroed `border`, a bare `button`) strips padding, borders, type sizes and colours from the controls. Import a reset you do not own into a layer declared before `sonic`:
|
|
38
|
+
|
|
39
|
+
```css
|
|
40
|
+
@layer base, sonic, components;
|
|
41
|
+
|
|
42
|
+
@import 'reset.css' layer(base);
|
|
43
|
+
```
|
|
44
|
+
|
|
37
45
|
- A skin sheet does nothing until its class, `sonic-skin-<name>`, is on an ancestor of the controls. The skins are `slate`, `amber`, `ivory`, `lime` and `flat`; a wrapper with another skin's class reskins what is inside it. With no skin a control is bare: no cap and no well, drawn in the text colour around it.
|
|
38
46
|
- `./define` registers every control. `./define/<control>` registers one, and `.` exports the classes and registers nothing.
|
|
39
|
-
- Under a bundler that sets the `development` condition, as Vite's dev server does, the controls warn in the console about a missing sheet, a child they do not use and a binding that found nothing. Read the console before debugging.
|
|
47
|
+
- Under a bundler that sets the `development` condition, as Vite's dev server does, the controls warn in the console about a missing sheet, a padding lost to an unlayered rule, a box property set on a host, a child they do not use and a binding that found nothing. Read the console before debugging.
|
|
40
48
|
|
|
41
49
|
## What every control shares
|
|
42
50
|
|
|
@@ -110,7 +118,9 @@ A display takes its signal as data and never opens an `AudioContext`.
|
|
|
110
118
|
|
|
111
119
|
## Wave controls
|
|
112
120
|
|
|
113
|
-
`<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.
|
|
121
|
+
`<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. `peaks` is `{ pairsPerSecond, samples }`, and the full scale follows the array unless `fullScale` is set.
|
|
122
|
+
|
|
123
|
+
A wave control shows and reads a clock (`1:23`, typed or drawn) and speaks a duration in the language around it, the nearest `lang`. Setting `formatValue`, `parseValue` or `formatSpokenValue` replaces that part; `formatClock` and `parseClock` are the same pair, exported so your own time text matches.
|
|
114
124
|
|
|
115
125
|
Their events bubble and carry no detail, so read the property when one fires: `revealed` on `sonic-reveal`, `currentMarker` on `sonic-marker`, and the strip's `hoverValue` on `sonic-hover`, which `clientXOf(value)` turns into a viewport x.
|
|
116
126
|
|
|
@@ -116,11 +116,6 @@ var SonicButton = class extends SonicFormElement {
|
|
|
116
116
|
}
|
|
117
117
|
}, signal);
|
|
118
118
|
this.render();
|
|
119
|
-
this.checkStyles(button, "button.css", [
|
|
120
|
-
"cap",
|
|
121
|
-
"well",
|
|
122
|
-
"keycap"
|
|
123
|
-
]);
|
|
124
119
|
button.addEventListener("click", (event) => {
|
|
125
120
|
if (this.#isSoftDisabled()) {
|
|
126
121
|
event.preventDefault();
|
|
@@ -66,13 +66,6 @@ var SonicDial = class extends SonicValueElement {
|
|
|
66
66
|
marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
|
|
67
67
|
}, signal);
|
|
68
68
|
this.render();
|
|
69
|
-
this.checkStyles(dial, "dial.css", [
|
|
70
|
-
"cap",
|
|
71
|
-
"readout",
|
|
72
|
-
"value",
|
|
73
|
-
["glass", ".sonic-dial-readout"],
|
|
74
|
-
["arc", ".sonic-dial-ring"]
|
|
75
|
-
]);
|
|
76
69
|
this.bindGestures(dial, signal, () => ({
|
|
77
70
|
position: (event) => -event.clientY,
|
|
78
71
|
travelPx: this.travelPx(dial, "--_sonic-dial-travel")
|
|
@@ -161,12 +161,6 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
161
161
|
this.#signal = signal;
|
|
162
162
|
this.keepControl(envelope, signal);
|
|
163
163
|
this.#bind();
|
|
164
|
-
this.checkStyles(envelope, "envelope.css", [
|
|
165
|
-
"glass",
|
|
166
|
-
"pane",
|
|
167
|
-
"well",
|
|
168
|
-
"readout"
|
|
169
|
-
]);
|
|
170
164
|
this.#bindPointer(envelope, signal);
|
|
171
165
|
signal.addEventListener("abort", () => {
|
|
172
166
|
this.#signal = void 0;
|
|
@@ -142,7 +142,6 @@ var SonicMeter = class extends SonicElement {
|
|
|
142
142
|
this.#renderSegments();
|
|
143
143
|
this.#render(now);
|
|
144
144
|
this.#schedule(now);
|
|
145
|
-
this.checkStyles(this.#meter, "meter.css", ["lens", "groove"]);
|
|
146
145
|
signal.addEventListener("abort", () => {
|
|
147
146
|
if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
|
|
148
147
|
this.#frame = void 0;
|
|
@@ -32,13 +32,6 @@ 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", [
|
|
36
|
-
"cap",
|
|
37
|
-
"glass",
|
|
38
|
-
"pane",
|
|
39
|
-
"well",
|
|
40
|
-
"value"
|
|
41
|
-
]);
|
|
42
35
|
this.bindGestures(number, signal, () => ({
|
|
43
36
|
position: (event) => -event.clientY,
|
|
44
37
|
travelPx: this.travelPx(number, "--_sonic-number-travel")
|
|
@@ -12,11 +12,6 @@ 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", [
|
|
16
|
-
"well",
|
|
17
|
-
"keycap",
|
|
18
|
-
["cap", ".sonic-segmented-option"]
|
|
19
|
-
]);
|
|
20
15
|
}
|
|
21
16
|
renderOption() {
|
|
22
17
|
return renderOption();
|
|
@@ -118,13 +118,6 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
this.render();
|
|
121
|
-
this.checkStyles(slider, "slider.css", [
|
|
122
|
-
"cap",
|
|
123
|
-
"readout",
|
|
124
|
-
"value",
|
|
125
|
-
"groove",
|
|
126
|
-
["glass", ".sonic-slider-readout"]
|
|
127
|
-
]);
|
|
128
121
|
this.bindGestures(slider, signal, (event) => {
|
|
129
122
|
const axis = this.#axis(slider, cap);
|
|
130
123
|
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, material
|
|
61
|
+
checkStyles(control, sheet, { box, material }) {}
|
|
62
62
|
focusTarget() {}
|
|
63
63
|
forwardNaming(target, isNamed) {
|
|
64
64
|
const labels = isNamed ? this.namingLabels() : [];
|
|
@@ -124,6 +124,8 @@ var SonicElement = class extends HTMLElement {
|
|
|
124
124
|
this[name] = value;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
+
#checkBox(control, checked, box) {}
|
|
128
|
+
#checkHost(checked) {}
|
|
127
129
|
#focused() {
|
|
128
130
|
const root = this.getRootNode();
|
|
129
131
|
const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
|
|
@@ -35,7 +35,6 @@ var SonicSwitch = class extends SonicPositionGroupElement {
|
|
|
35
35
|
connect(signal) {
|
|
36
36
|
const group = this.group;
|
|
37
37
|
super.connect(signal);
|
|
38
|
-
this.checkStyles(group, "switch.css", ["cap"]);
|
|
39
38
|
this.bare.addEventListener("click", (event) => {
|
|
40
39
|
const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
|
|
41
40
|
this.#isClickSwallowed = false;
|
|
@@ -32,11 +32,6 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
32
32
|
}
|
|
33
33
|
connect(signal) {
|
|
34
34
|
super.connect(signal);
|
|
35
|
-
this.checkStyles(this.group, "toggle.css", [
|
|
36
|
-
"cap",
|
|
37
|
-
"well",
|
|
38
|
-
"keycap"
|
|
39
|
-
]);
|
|
40
35
|
this.bare.addEventListener("click", (event) => {
|
|
41
36
|
if (event instanceof PointerEvent && event.pointerType !== "") return;
|
|
42
37
|
this.setChecked(!this.checked);
|
|
@@ -216,6 +216,10 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
216
216
|
get valueText() {
|
|
217
217
|
return this.#textFor(this.#model.value);
|
|
218
218
|
}
|
|
219
|
+
notation = {
|
|
220
|
+
format: String,
|
|
221
|
+
parse: Number.parseFloat
|
|
222
|
+
};
|
|
219
223
|
#formatEntry;
|
|
220
224
|
#formatSpokenValue;
|
|
221
225
|
#formatValue;
|
|
@@ -266,7 +270,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
266
270
|
isDisabled: () => this.isDisabled(),
|
|
267
271
|
keyStep: () => this.keyStep,
|
|
268
272
|
model: this.#model,
|
|
269
|
-
parse: (text) => (this.#parseValue ??
|
|
273
|
+
parse: (text) => (this.#parseValue ?? this.notation.parse)(text),
|
|
270
274
|
render: () => {
|
|
271
275
|
this.render();
|
|
272
276
|
},
|
|
@@ -368,7 +372,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
368
372
|
#entryText() {
|
|
369
373
|
const value = this.#model.value;
|
|
370
374
|
if (this.#formatEntry) return this.#formatEntry(value);
|
|
371
|
-
if (!this.#parseValue) return
|
|
375
|
+
if (!this.#parseValue) return this.notation.format(value);
|
|
372
376
|
return this.valueText;
|
|
373
377
|
}
|
|
374
378
|
#isEditing() {
|
|
@@ -394,7 +398,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
394
398
|
writeAttribute(control, "aria-valuemax", String(high));
|
|
395
399
|
writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
|
|
396
400
|
if (orientation) writeAttribute(control, "aria-orientation", orientation);
|
|
397
|
-
writeAttribute(control, "aria-valuetext", (this.#formatSpokenValue ?? this.#formatValue)?.(this.#spokenNow()));
|
|
401
|
+
writeAttribute(control, "aria-valuetext", (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(this.#spokenNow()));
|
|
398
402
|
this.forwardNaming(control, true);
|
|
399
403
|
this.#renderDisabled(control);
|
|
400
404
|
this.renderReadout();
|
|
@@ -448,7 +452,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
448
452
|
return step !== void 0 && step > 0 ? trimFloat(Math.round(this.#model.value / step) * step) : this.#model.value;
|
|
449
453
|
}
|
|
450
454
|
#textFor(value) {
|
|
451
|
-
return this.#formatValue
|
|
455
|
+
return (this.#formatValue ?? this.notation.format)(value);
|
|
452
456
|
}
|
|
453
457
|
#writeHeld(next) {
|
|
454
458
|
this.#heldAt = next;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SonicValueElement } from "./value-element.js";
|
|
2
2
|
import { bindSurface } from "../lib/canvas-surface.js";
|
|
3
|
+
import { formatClock, parseClock } from "../lib/clock.js";
|
|
4
|
+
import { spokenDuration } from "../lib/spoken-duration.js";
|
|
3
5
|
|
|
4
6
|
//#region src/elements/wave-element.ts
|
|
5
7
|
function markerKey(marker) {
|
|
@@ -30,6 +32,11 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
30
32
|
this.renderMarkers();
|
|
31
33
|
this.render();
|
|
32
34
|
}
|
|
35
|
+
notation = {
|
|
36
|
+
format: formatClock,
|
|
37
|
+
parse: parseClock,
|
|
38
|
+
speak: (seconds) => spokenDuration(seconds, this.closest("[lang]")?.lang)
|
|
39
|
+
};
|
|
33
40
|
#current;
|
|
34
41
|
#markers = [];
|
|
35
42
|
#surface;
|
|
@@ -65,14 +72,6 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
65
72
|
signal
|
|
66
73
|
});
|
|
67
74
|
this.render();
|
|
68
|
-
this.checkStyles(control, this.sheet, [
|
|
69
|
-
"cap",
|
|
70
|
-
"glass",
|
|
71
|
-
"pane",
|
|
72
|
-
"well",
|
|
73
|
-
"readout",
|
|
74
|
-
"value"
|
|
75
|
-
]);
|
|
76
75
|
this.#surface.setFill(this.fill);
|
|
77
76
|
}
|
|
78
77
|
focusTarget() {
|
|
@@ -224,13 +224,6 @@ var SonicXy = class extends SonicFormElement {
|
|
|
224
224
|
const xy = this.#xy;
|
|
225
225
|
this.keepControl(xy, signal);
|
|
226
226
|
this.render();
|
|
227
|
-
this.checkStyles(xy, "xy.css", [
|
|
228
|
-
"cap",
|
|
229
|
-
"glass",
|
|
230
|
-
"pane",
|
|
231
|
-
"well",
|
|
232
|
-
"readout"
|
|
233
|
-
]);
|
|
234
227
|
this.#bindPointer(xy, signal);
|
|
235
228
|
this.#bindKeys(xy, signal);
|
|
236
229
|
}
|
package/dist/default/index.js
CHANGED
|
@@ -10,10 +10,11 @@ import { SonicSpectrum } from "./elements/spectrum.js";
|
|
|
10
10
|
import { SonicSplit } from "./elements/split.js";
|
|
11
11
|
import { SonicSwitch } from "./elements/switch.js";
|
|
12
12
|
import { SonicToggle } from "./elements/toggle.js";
|
|
13
|
+
import { formatClock, parseClock } from "./lib/clock.js";
|
|
13
14
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
14
15
|
import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
15
16
|
import { SonicXy } from "./elements/xy.js";
|
|
16
17
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
17
18
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
18
19
|
|
|
19
|
-
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|
|
20
|
+
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/lib/clock.ts
|
|
2
|
+
const sign = /^[-−]/;
|
|
3
|
+
function formatClock(seconds) {
|
|
4
|
+
const total = Math.floor(Math.abs(seconds));
|
|
5
|
+
const hours = Math.floor(total / 3600);
|
|
6
|
+
const minutes = Math.floor(total / 60) % 60;
|
|
7
|
+
const rest = String(total % 60).padStart(2, "0");
|
|
8
|
+
const prefix = seconds < 0 ? "−" : "";
|
|
9
|
+
if (hours === 0) return `${prefix}${String(minutes)}:${rest}`;
|
|
10
|
+
return `${prefix}${String(hours)}:${String(minutes).padStart(2, "0")}:${rest}`;
|
|
11
|
+
}
|
|
12
|
+
function parseClock(text) {
|
|
13
|
+
const clock = text.trim();
|
|
14
|
+
let seconds = 0;
|
|
15
|
+
for (const part of clock.replace(sign, "").split(":")) {
|
|
16
|
+
if (part.trim() === "") return NaN;
|
|
17
|
+
seconds = seconds * 60 + Number(part);
|
|
18
|
+
}
|
|
19
|
+
return sign.test(clock) ? -seconds : seconds;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { formatClock, parseClock };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/lib/spoken-duration.ts
|
|
2
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
3
|
+
function formatterFor(language, isZero) {
|
|
4
|
+
const key = `${language}|${String(isZero)}`;
|
|
5
|
+
const cached = formatters.get(key);
|
|
6
|
+
if (cached) return cached;
|
|
7
|
+
const options = {
|
|
8
|
+
secondsDisplay: isZero ? "always" : "auto",
|
|
9
|
+
style: "long"
|
|
10
|
+
};
|
|
11
|
+
let formatter;
|
|
12
|
+
try {
|
|
13
|
+
formatter = new Intl.DurationFormat(language || void 0, options);
|
|
14
|
+
} catch {
|
|
15
|
+
formatter = new Intl.DurationFormat(void 0, options);
|
|
16
|
+
}
|
|
17
|
+
formatters.set(key, formatter);
|
|
18
|
+
return formatter;
|
|
19
|
+
}
|
|
20
|
+
function spokenDuration(seconds, language = "") {
|
|
21
|
+
const total = Math.trunc(seconds);
|
|
22
|
+
return formatterFor(language, total === 0).format({
|
|
23
|
+
hours: Math.trunc(total / 3600),
|
|
24
|
+
minutes: Math.trunc(total / 60) % 60,
|
|
25
|
+
seconds: total % 60
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { spokenDuration };
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
//#region src/lib/waveform-buckets.ts
|
|
2
|
+
function fullScaleOf({ fullScale, samples }) {
|
|
3
|
+
if (fullScale !== void 0) return fullScale;
|
|
4
|
+
if (samples instanceof Int8Array) return 128;
|
|
5
|
+
return samples instanceof Int16Array ? 32768 : 1;
|
|
6
|
+
}
|
|
2
7
|
function foldPairs(peaks, fromPair, toPair) {
|
|
3
8
|
const { samples } = peaks;
|
|
4
9
|
const channels = Math.max(1, peaks.channels ?? 1);
|
|
5
10
|
const stride = channels * 2;
|
|
6
11
|
const pairsTotal = Math.floor(samples.length / stride);
|
|
12
|
+
const fullScale = fullScaleOf(peaks);
|
|
7
13
|
let low = 0;
|
|
8
14
|
let high = 0;
|
|
9
15
|
for (let pair = Math.max(0, fromPair); pair < Math.min(pairsTotal, toPair); pair += 1) for (let channel = 0; channel < channels; channel += 1) {
|
|
@@ -11,7 +17,7 @@ function foldPairs(peaks, fromPair, toPair) {
|
|
|
11
17
|
low = Math.min(low, samples[at] ?? 0);
|
|
12
18
|
high = Math.max(high, samples[at + 1] ?? 0);
|
|
13
19
|
}
|
|
14
|
-
return [low /
|
|
20
|
+
return [low / fullScale, high / fullScale];
|
|
15
21
|
}
|
|
16
22
|
function waveformBuckets(peaks, view) {
|
|
17
23
|
const { pairsPerSecond } = peaks;
|
|
@@ -116,11 +116,18 @@ var SonicButton = class extends SonicFormElement {
|
|
|
116
116
|
}
|
|
117
117
|
}, signal);
|
|
118
118
|
this.render();
|
|
119
|
-
this.checkStyles(button, "button.css",
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
this.checkStyles(button, "button.css", {
|
|
120
|
+
box: {
|
|
121
|
+
property: "padding-inline-start",
|
|
122
|
+
ratio: "--_sonic-button-padding-ratio",
|
|
123
|
+
selector: ".sonic-button-cap > :not(svg, .sonic-led)"
|
|
124
|
+
},
|
|
125
|
+
material: [
|
|
126
|
+
"cap",
|
|
127
|
+
"well",
|
|
128
|
+
"keycap"
|
|
129
|
+
]
|
|
130
|
+
});
|
|
124
131
|
button.addEventListener("click", (event) => {
|
|
125
132
|
if (this.#isSoftDisabled()) {
|
|
126
133
|
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\t/** Shows the legend child whose `data-sonic-when` has this name */\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\t/** Submitted with the form while a latching button is pressed */\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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"}
|
|
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 const __DEV__: boolean;\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\t/** Shows the legend child whose `data-sonic-when` has this name */\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\t/** Submitted with the form while a latching button is pressed */\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\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tbox: {\n\t\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t\t},\n\t\t\t\tmaterial: ['cap', 'well', 'keycap'],\n\t\t\t});\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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"}
|
|
@@ -66,13 +66,20 @@ var SonicDial = class extends SonicValueElement {
|
|
|
66
66
|
marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
|
|
67
67
|
}, signal);
|
|
68
68
|
this.render();
|
|
69
|
-
this.checkStyles(dial, "dial.css",
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
[
|
|
75
|
-
|
|
69
|
+
this.checkStyles(dial, "dial.css", {
|
|
70
|
+
box: {
|
|
71
|
+
property: "margin-bottom",
|
|
72
|
+
selector: "[popover]"
|
|
73
|
+
},
|
|
74
|
+
material: [
|
|
75
|
+
"cap",
|
|
76
|
+
"readout",
|
|
77
|
+
"value",
|
|
78
|
+
["glass", ".sonic-dial-readout"],
|
|
79
|
+
["arc", ".sonic-dial-ring"],
|
|
80
|
+
["scale", ".sonic-dial-scale"]
|
|
81
|
+
]
|
|
82
|
+
});
|
|
76
83
|
this.bindGestures(dial, signal, () => ({
|
|
77
84
|
position: (event) => -event.clientY,
|
|
78
85
|
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/** How far a modulation source can push the value, in value units; signed */\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/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\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":";;;;;;
|
|
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 const __DEV__: boolean;\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/** How far a modulation source can push the value, in value units; signed */\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/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\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\tif (__DEV__)\n\t\t\tthis.checkStyles(dial, 'dial.css', {\n\t\t\t\tbox: { property: 'margin-bottom', selector: '[popover]' },\n\t\t\t\tmaterial: [\n\t\t\t\t\t'cap',\n\t\t\t\t\t'readout',\n\t\t\t\t\t'value',\n\t\t\t\t\t['glass', '.sonic-dial-readout'],\n\t\t\t\t\t['arc', '.sonic-dial-ring'],\n\t\t\t\t\t['scale', '.sonic-dial-scale'],\n\t\t\t\t],\n\t\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":";;;;;;AAaA,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;;CAIA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAIA,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;EAEX,KAAK,YAAY,MAAM,YAAY;GAClC,KAAK;IAAE,UAAU;IAAiB,UAAU;GAAY;GACxD,UAAU;IACT;IACA;IACA;IACA,CAAC,SAAS,qBAAqB;IAC/B,CAAC,OAAO,kBAAkB;IAC1B,CAAC,SAAS,mBAAmB;GAC9B;EACD,CAAC;EACF,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"}
|
|
@@ -69,11 +69,11 @@ var SonicDisplayElement = class extends SonicElement {
|
|
|
69
69
|
},
|
|
70
70
|
signal
|
|
71
71
|
});
|
|
72
|
-
this.checkStyles(control, this.sheet, [
|
|
72
|
+
this.checkStyles(control, this.sheet, { material: [
|
|
73
73
|
"glass",
|
|
74
74
|
"pane",
|
|
75
75
|
"well"
|
|
76
|
-
]);
|
|
76
|
+
] });
|
|
77
77
|
this.#surface.setFill(this.fill);
|
|
78
78
|
}
|
|
79
79
|
surface() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'fill',\n\t\t'max',\n\t\t'min',\n\t];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'fill', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */\n\tpush(decibels: Float32Array): void {\n\t\tthis.receive(decibels);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tfill: { control, sizeProperty: this.sizeProperty },\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\
|
|
1
|
+
{"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\n\ndeclare const __DEV__: boolean;\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'fill',\n\t\t'max',\n\t\t'min',\n\t];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'fill', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */\n\tpush(decibels: Float32Array): void {\n\t\tthis.receive(decibels);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tfill: { control, sizeProperty: this.sizeProperty },\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tif (__DEV__) this.checkStyles(control, this.sheet, { material: ['glass', 'pane', 'well'] });\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;AAQA,IAAsB,sBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAC7C,GAAG,aAAa;EAChB;EACA;EACA;CACD;CAEA,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,OAAgB;EACnB,OAAO,KAAK,aAAa,MAAM;CAChC;CAEA,IAAI,KAAK,WAAoB;EAC5B,KAAK,QAAQ,QAAQ,SAAS;CAC/B;CAGA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAcA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,IAAI;EACrD,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,QAAQ,OAAO,KAAK;EACvD,MAAM,kBAAkB;CACzB;;CAGA,KAAK,UAA8B;EAClC,KAAK,QAAQ,QAAQ;EACrB,KAAK,UAAU,aAAa;CAC7B;CAGA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,MAAM;IAAE;IAAS,cAAc,KAAK;GAAa;GACjD,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACY,KAAK,YAAY,SAAS,KAAK,OAAO,EAAE,UAAU;GAAC;GAAS;GAAQ;EAAM,EAAE,CAAC;EAC1F,KAAK,SAAS,QAAQ,KAAK,IAAI;CAChC;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
|
|
@@ -161,12 +161,23 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
161
161
|
this.#signal = signal;
|
|
162
162
|
this.keepControl(envelope, signal);
|
|
163
163
|
this.#bind();
|
|
164
|
-
this.checkStyles(envelope, "envelope.css",
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
this.checkStyles(envelope, "envelope.css", {
|
|
165
|
+
box: {
|
|
166
|
+
property: "margin-bottom",
|
|
167
|
+
selector: "[popover]"
|
|
168
|
+
},
|
|
169
|
+
material: [
|
|
170
|
+
"glass",
|
|
171
|
+
"pane",
|
|
172
|
+
"well",
|
|
173
|
+
"readout",
|
|
174
|
+
[
|
|
175
|
+
"bracket",
|
|
176
|
+
".sonic-envelope-bracket",
|
|
177
|
+
"::before"
|
|
178
|
+
]
|
|
179
|
+
]
|
|
180
|
+
});
|
|
170
181
|
this.#bindPointer(envelope, signal);
|
|
171
182
|
signal.addEventListener("abort", () => {
|
|
172
183
|
this.#signal = void 0;
|