@xsynaptic/sonic-ui 0.15.0 → 0.17.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 +17 -0
- package/dist/default/elements/waveform.js +22 -8
- package/dist/default/lib/marker-rider.js +37 -14
- package/dist/dev/elements/wave-element.d.ts +1 -0
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +4 -1
- package/dist/dev/elements/waveform.js +22 -8
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/index.d.ts +2 -2
- package/dist/dev/lib/marker-rider.js +37 -14
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/styles/button.css +65 -57
- package/dist/styles/material/cap.css +26 -2
- package/dist/styles/ring.css +2 -0
- package/dist/styles/segmented.css +11 -2
- package/dist/styles/switch.css +2 -2
- package/dist/styles/toggle.css +2 -2
- package/dist/styles/waveform.css +10 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- - Waveform: `renderLabel(marker, element)` draws a marker's label in place of its text. A marker keeps a consumer's own keys for it to draw from.
|
|
8
|
+
- Waveform: `--sonic-waveform-label-fade-start` (0) and `--sonic-waveform-label-fade-end` (1) replace `--sonic-waveform-label-fade-ratio`. They place the parked label's fade between the next marker crossing the playhead, 0, and its label reaching the park, 1; equal values give no fade. By default the label now holds until the crossing and is gone as the next one lands.
|
|
9
|
+
|
|
10
|
+
## 0.16.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- - Button: a text legend widens the button to fit, never under `--sonic-button-aspect-ratio`. `--sonic-button-font-ratio` (0.34) and `--sonic-button-padding-ratio` (0.25) size it, replacing a font size set on the host, and it takes an icon's engraving and lit glow.
|
|
15
|
+
- Segmented: `--sonic-segmented-padding-ratio` (0.25) sets an option's inline padding.
|
|
16
|
+
- - Button and segmented: `--sonic-button-latched-scale` and `--sonic-segmented-latched-scale` scale a cap that stays down; unset, the press scale. A press still dips.
|
|
17
|
+
- Focus: `--sonic-focus-glow` scales the glow, 0 to 1; `--sonic-focus-outline` and `--sonic-focus-outline-offset` draw an outline on keyboard focus.
|
|
18
|
+
- Disabled: `--sonic-disabled-opacity` fades a disabled control.
|
|
19
|
+
|
|
3
20
|
## 0.15.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -186,6 +186,13 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
186
186
|
set reducedMotion(motion) {
|
|
187
187
|
this.reflect("reduced-motion", motion);
|
|
188
188
|
}
|
|
189
|
+
get renderLabel() {
|
|
190
|
+
return this.#renderLabel;
|
|
191
|
+
}
|
|
192
|
+
set renderLabel(render) {
|
|
193
|
+
this.#renderLabel = render;
|
|
194
|
+
if (this.isBound()) this.renderMarkers();
|
|
195
|
+
}
|
|
189
196
|
get requestPeaks() {
|
|
190
197
|
return this.#requestPeaks;
|
|
191
198
|
}
|
|
@@ -244,11 +251,12 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
244
251
|
#pinch;
|
|
245
252
|
#playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
|
|
246
253
|
#readTime;
|
|
254
|
+
#renderLabel;
|
|
247
255
|
#requestPeaks;
|
|
248
256
|
#rider = createLabelRider(this.control, {
|
|
249
257
|
className: "sonic-waveform-label",
|
|
250
258
|
colourProperty: "--_sonic-waveform-marker",
|
|
251
|
-
|
|
259
|
+
fadeProperties: ["--_sonic-waveform-label-fade-start", "--_sonic-waveform-label-fade-end"],
|
|
252
260
|
insetProperty: "--_sonic-waveform-label-inset",
|
|
253
261
|
parkProperty: "--_sonic-waveform-label-park"
|
|
254
262
|
});
|
|
@@ -259,7 +267,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
259
267
|
if (name === "zoomable") this.#bindWheel();
|
|
260
268
|
}
|
|
261
269
|
connectedCallback() {
|
|
262
|
-
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
270
|
+
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
263
271
|
super.connectedCallback();
|
|
264
272
|
}
|
|
265
273
|
connect(signal) {
|
|
@@ -288,6 +296,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
288
296
|
}
|
|
289
297
|
paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
|
|
290
298
|
const held = this.#held();
|
|
299
|
+
const isPaged = look.isReducedMotion && this.reducedMotion === "page";
|
|
291
300
|
const pending = this.#duePending();
|
|
292
301
|
const timeline = frameTimeline({
|
|
293
302
|
clockSeconds: this.#clock.read(frameMs, {
|
|
@@ -297,7 +306,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
297
306
|
}),
|
|
298
307
|
frameMs,
|
|
299
308
|
...held ? { held } : {},
|
|
300
|
-
isPaged
|
|
309
|
+
isPaged,
|
|
301
310
|
isPlaying: this.playing,
|
|
302
311
|
isStill: look.isReducedMotion,
|
|
303
312
|
pending,
|
|
@@ -315,6 +324,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
315
324
|
this.showCurrentMarker(seconds);
|
|
316
325
|
this.toggleState("pending", view.pending.length > 0);
|
|
317
326
|
this.#rider.place({
|
|
327
|
+
isPaged,
|
|
318
328
|
playheadSeconds: seconds,
|
|
319
329
|
startSeconds: view.startSeconds,
|
|
320
330
|
widthPx: view.width / view.dpr,
|
|
@@ -332,11 +342,15 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
332
342
|
}
|
|
333
343
|
renderMarkers() {
|
|
334
344
|
const bounds = this.mapping().bounds;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
const render = this.#renderLabel;
|
|
346
|
+
this.#rider.measure(this.markers.flatMap((marker) => marker.label === void 0 || marker.label === "" ? [] : [{
|
|
347
|
+
isDimmed: marker.dimmed === true,
|
|
348
|
+
...marker.kind === void 0 ? {} : { kind: marker.kind },
|
|
349
|
+
start: clamp(marker.start, ...bounds),
|
|
350
|
+
text: marker.label,
|
|
351
|
+
...render ? { write: (element) => {
|
|
352
|
+
render(marker, element);
|
|
353
|
+
} } : {}
|
|
340
354
|
}]));
|
|
341
355
|
this.surface()?.rebuild();
|
|
342
356
|
}
|
|
@@ -7,15 +7,16 @@ import { writeKind } from "./marker-band.js";
|
|
|
7
7
|
function lineX(seconds, view) {
|
|
8
8
|
return (seconds - view.startSeconds) / view.windowSeconds * view.widthPx;
|
|
9
9
|
}
|
|
10
|
-
function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
10
|
+
function parkedPlacement(labels, index, [view, arrivingX, [startX, endX]]) {
|
|
11
11
|
const parked = labels[index];
|
|
12
12
|
if (!parked) return void 0;
|
|
13
13
|
const next = labels[index + 1];
|
|
14
14
|
const x = Math.max(view.parkPx, lineX(parked.start, view) + view.insetPx);
|
|
15
|
-
const
|
|
15
|
+
const fadePx = startX - endX;
|
|
16
|
+
const leftPx = next ? lineX(next.start, view) - endX : Infinity;
|
|
16
17
|
return {
|
|
17
18
|
index,
|
|
18
|
-
opacity:
|
|
19
|
+
opacity: fadePx > 0 ? clamp(leftPx / fadePx, 0, 1) : 1,
|
|
19
20
|
shownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),
|
|
20
21
|
x
|
|
21
22
|
};
|
|
@@ -23,12 +24,23 @@ function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
|
23
24
|
function isDrawable(length) {
|
|
24
25
|
return Number.isFinite(length) && length > 0;
|
|
25
26
|
}
|
|
27
|
+
function fadeLines(view) {
|
|
28
|
+
const playheadX = lineX(view.playheadSeconds, view);
|
|
29
|
+
if (view.isPaged) return [playheadX, playheadX];
|
|
30
|
+
const stretchPx = view.parkPx - view.insetPx - playheadX;
|
|
31
|
+
return [playheadX + clamp(view.fadeStart, 0, 1) * stretchPx, playheadX + clamp(view.fadeEnd, 0, 1) * stretchPx];
|
|
32
|
+
}
|
|
26
33
|
function layoutRider(labels, view) {
|
|
27
34
|
if (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};
|
|
28
|
-
const
|
|
35
|
+
const fade = fadeLines(view);
|
|
36
|
+
const parkedIndex = labels.findLastIndex((label) => lineX(label.start, view) <= fade[1]);
|
|
29
37
|
const next = labels[parkedIndex + 1];
|
|
30
38
|
const arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;
|
|
31
|
-
const parked = parkedPlacement(labels, parkedIndex, [
|
|
39
|
+
const parked = parkedPlacement(labels, parkedIndex, [
|
|
40
|
+
view,
|
|
41
|
+
arrivingX,
|
|
42
|
+
fade
|
|
43
|
+
]);
|
|
32
44
|
const arriving = next && arrivingX < view.widthPx ? {
|
|
33
45
|
index: parkedIndex + 1,
|
|
34
46
|
opacity: 1,
|
|
@@ -44,11 +56,16 @@ function labelSlot(element) {
|
|
|
44
56
|
return {
|
|
45
57
|
element,
|
|
46
58
|
kind: void 0,
|
|
59
|
+
label: void 0,
|
|
47
60
|
opacity: NaN,
|
|
48
61
|
shownPx: NaN,
|
|
49
62
|
x: NaN
|
|
50
63
|
};
|
|
51
64
|
}
|
|
65
|
+
function writeContent(element, { text, write }) {
|
|
66
|
+
element.textContent = write ? "" : text;
|
|
67
|
+
write?.(element);
|
|
68
|
+
}
|
|
52
69
|
function writePlacement(slot, { opacity, shownPx, x }, widthPx) {
|
|
53
70
|
const { style } = slot.element;
|
|
54
71
|
if (slot.x !== x) {
|
|
@@ -69,7 +86,10 @@ function writeLabel(slot, [labels, colourProperty], placement) {
|
|
|
69
86
|
const label = placement && placement.shownPx > 0 ? labels[placement.index] : void 0;
|
|
70
87
|
if (element.hidden !== !label) element.hidden = !label;
|
|
71
88
|
if (!label || !placement) return;
|
|
72
|
-
if (
|
|
89
|
+
if (slot.label !== label) {
|
|
90
|
+
slot.label = label;
|
|
91
|
+
writeContent(element, label);
|
|
92
|
+
}
|
|
73
93
|
element.toggleAttribute("data-sonic-dimmed", label.isDimmed);
|
|
74
94
|
if (slot.kind !== label.kind) {
|
|
75
95
|
slot.kind = label.kind;
|
|
@@ -77,11 +97,11 @@ function writeLabel(slot, [labels, colourProperty], placement) {
|
|
|
77
97
|
}
|
|
78
98
|
writePlacement(slot, placement, label.widthPx);
|
|
79
99
|
}
|
|
80
|
-
function measureWidths(control, className,
|
|
81
|
-
const probes =
|
|
100
|
+
function measureWidths(control, className, labels) {
|
|
101
|
+
const probes = labels.map((label) => {
|
|
82
102
|
const probe = document.createElement("div");
|
|
83
103
|
probe.className = className;
|
|
84
|
-
probe
|
|
104
|
+
writeContent(probe, label);
|
|
85
105
|
return probe;
|
|
86
106
|
});
|
|
87
107
|
control.append(...probes);
|
|
@@ -90,18 +110,20 @@ function measureWidths(control, className, texts) {
|
|
|
90
110
|
return widths;
|
|
91
111
|
}
|
|
92
112
|
function createLabelRider(control, options) {
|
|
93
|
-
const { className, colourProperty,
|
|
113
|
+
const { className, colourProperty, fadeProperties, insetProperty, parkProperty } = options;
|
|
94
114
|
const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
|
|
95
115
|
const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
|
|
96
|
-
let
|
|
116
|
+
let fadeStart = 0;
|
|
117
|
+
let fadeEnd = 1;
|
|
97
118
|
let insetPx = 0;
|
|
98
119
|
let parkPx = 0;
|
|
99
120
|
let measured = [];
|
|
100
121
|
return {
|
|
101
122
|
measure: (labels) => {
|
|
102
|
-
const widths = measureWidths(control, className, labels
|
|
123
|
+
const widths = measureWidths(control, className, labels);
|
|
103
124
|
const styles = getComputedStyle(control);
|
|
104
|
-
|
|
125
|
+
fadeStart = readPxProperty(styles, fadeProperties[0], 0);
|
|
126
|
+
fadeEnd = readPxProperty(styles, fadeProperties[1], 1);
|
|
105
127
|
insetPx = readPxProperty(styles, insetProperty, 0);
|
|
106
128
|
parkPx = readPxProperty(styles, parkProperty, 0);
|
|
107
129
|
measured = labels.map((label, index) => ({
|
|
@@ -112,7 +134,8 @@ function createLabelRider(control, options) {
|
|
|
112
134
|
place: (window) => {
|
|
113
135
|
const layout = layoutRider(measured, {
|
|
114
136
|
...window,
|
|
115
|
-
|
|
137
|
+
fadeEnd,
|
|
138
|
+
fadeStart,
|
|
116
139
|
insetPx,
|
|
117
140
|
parkPx
|
|
118
141
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { bindFill } from '#lib/fill-watch.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\tdimmed?: boolean;\n\tend?: number;\n\tkind?: string;\n\tlabel?: string;\n\tstart: number;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n> extends SonicValueElement {\n\tstatic override readonly observedAttributes = [...SonicValueElement.observedAttributes, 'fill'];\n\n\tget currentMarker(): undefined | WaveMarker {\n\t\treturn this.#current ? { ...this.#current } : undefined;\n\t}\n\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\tget markers(): Array<WaveMarker> {\n\t\treturn this.#markers.map((marker) => ({ ...marker }));\n\t}\n\n\tset markers(list: Iterable<WaveMarker> | undefined) {\n\t\tthis.#markers = [...(list ?? [])]\n\t\t\t.filter((marker) => Number.isFinite(marker.start))\n\t\t\t.map((marker) => ({ ...marker }))\n\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\tif (!this.isBound()) return;\n\n\t\tthis.renderMarkers();\n\t\tthis.render();\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 lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\t#syncFill: ((isFilling: boolean) => void) | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#syncFill?.(this.fill);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\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.renderEmpty();\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tlengths: this.lengths,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(control, this.sheet, ['cap', 'glass', 'pane', 'well', 'readout', 'value']);\n\t\tthis.#syncFill = bindFill(control, this.sizeProperty, signal);\n\t\tthis.#syncFill(this.fill);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected override heldWrite(next: number): void {\n\t\tsuper.heldWrite(next);\n\t\tthis.render();\n\t}\n\n\tprotected override holdChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected abstract isEmpty(): boolean;\n\n\tprotected markerList(): ReadonlyArray<WaveMarker> {\n\t\treturn this.#markers;\n\t}\n\n\tprotected markerProportions(): Array<[number, number]> {\n\t\tconst mapping = this.mapping();\n\n\t\treturn this.#markers.map(({ end, start }) => {\n\t\t\tconst at = mapping.proportionOf(start);\n\t\t\tconst to = end === undefined ? at : mapping.proportionOf(end);\n\n\t\t\treturn [Math.min(at, to), Math.max(at, to)];\n\t\t});\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length>,\n\t): void;\n\n\tprotected override proportionsChanged(): void {\n\t\tthis.renderMarkers();\n\t}\n\n\tprotected renderEmpty(): void {\n\t\tthis.toggleState('empty', this.isEmpty());\n\t}\n\n\tprotected abstract renderMarkers(): void;\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { bindFill } from '#lib/fill-watch.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\tend?: number;\n\tkind?: string;\n\tlabel?: string;\n\tstart: number;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n> extends SonicValueElement {\n\tstatic override readonly observedAttributes = [...SonicValueElement.observedAttributes, 'fill'];\n\n\tget currentMarker(): undefined | WaveMarker {\n\t\treturn this.#current ? { ...this.#current } : undefined;\n\t}\n\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\tget markers(): Array<WaveMarker> {\n\t\treturn this.#markers.map((marker) => ({ ...marker }));\n\t}\n\n\tset markers(list: Iterable<WaveMarker> | undefined) {\n\t\tthis.#markers = [...(list ?? [])]\n\t\t\t.filter((marker) => Number.isFinite(marker.start))\n\t\t\t.map((marker) => ({ ...marker }))\n\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\tif (!this.isBound()) return;\n\n\t\tthis.renderMarkers();\n\t\tthis.render();\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 lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\t#syncFill: ((isFilling: boolean) => void) | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#syncFill?.(this.fill);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\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.renderEmpty();\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tlengths: this.lengths,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(control, this.sheet, ['cap', 'glass', 'pane', 'well', 'readout', 'value']);\n\t\tthis.#syncFill = bindFill(control, this.sizeProperty, signal);\n\t\tthis.#syncFill(this.fill);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected override heldWrite(next: number): void {\n\t\tsuper.heldWrite(next);\n\t\tthis.render();\n\t}\n\n\tprotected override holdChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected abstract isEmpty(): boolean;\n\n\tprotected markerList(): ReadonlyArray<WaveMarker> {\n\t\treturn this.#markers;\n\t}\n\n\tprotected markerProportions(): Array<[number, number]> {\n\t\tconst mapping = this.mapping();\n\n\t\treturn this.#markers.map(({ end, start }) => {\n\t\t\tconst at = mapping.proportionOf(start);\n\t\t\tconst to = end === undefined ? at : mapping.proportionOf(end);\n\n\t\t\treturn [Math.min(at, to), Math.max(at, to)];\n\t\t});\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length>,\n\t): void;\n\n\tprotected override proportionsChanged(): void {\n\t\tthis.renderMarkers();\n\t}\n\n\tprotected renderEmpty(): void {\n\t\tthis.toggleState('empty', this.isEmpty());\n\t}\n\n\tprotected abstract renderMarkers(): void;\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WaveformPeaks } from "../lib/waveform-buckets.js";
|
|
2
2
|
import { TimeRegions } from "../lib/time-regions.js";
|
|
3
3
|
import { SurfaceFrame } from "../lib/canvas-surface.js";
|
|
4
|
-
import { SonicWaveElement } from "./wave-element.js";
|
|
4
|
+
import { SonicWaveElement, WaveMarker } from "./wave-element.js";
|
|
5
5
|
//#region src/elements/waveform.d.ts
|
|
6
6
|
declare global {
|
|
7
7
|
interface HTMLElementEventMap {
|
|
@@ -12,6 +12,7 @@ declare global {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
type Colour = keyof typeof colours;
|
|
15
|
+
export type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;
|
|
15
16
|
export type PeaksRequest = (fromSeconds: number, toSeconds: number) => Iterable<Promise<unknown>> | Promise<unknown> | undefined;
|
|
16
17
|
declare const colours: {
|
|
17
18
|
readonly ends: "--_sonic-waveform-ends";
|
|
@@ -38,6 +39,8 @@ export declare class SonicWaveform extends SonicWaveElement<Colour> {
|
|
|
38
39
|
set readTime(read: (() => number | undefined) | undefined);
|
|
39
40
|
get reducedMotion(): 'page' | 'scroll';
|
|
40
41
|
set reducedMotion(motion: 'page' | 'scroll' | undefined);
|
|
42
|
+
get renderLabel(): LabelRender | undefined;
|
|
43
|
+
set renderLabel(render: LabelRender | undefined);
|
|
41
44
|
get requestPeaks(): PeaksRequest | undefined;
|
|
42
45
|
set requestPeaks(request: PeaksRequest | undefined);
|
|
43
46
|
get zoom(): number;
|
|
@@ -186,6 +186,13 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
186
186
|
set reducedMotion(motion) {
|
|
187
187
|
this.reflect("reduced-motion", motion);
|
|
188
188
|
}
|
|
189
|
+
get renderLabel() {
|
|
190
|
+
return this.#renderLabel;
|
|
191
|
+
}
|
|
192
|
+
set renderLabel(render) {
|
|
193
|
+
this.#renderLabel = render;
|
|
194
|
+
if (this.isBound()) this.renderMarkers();
|
|
195
|
+
}
|
|
189
196
|
get requestPeaks() {
|
|
190
197
|
return this.#requestPeaks;
|
|
191
198
|
}
|
|
@@ -244,11 +251,12 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
244
251
|
#pinch;
|
|
245
252
|
#playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
|
|
246
253
|
#readTime;
|
|
254
|
+
#renderLabel;
|
|
247
255
|
#requestPeaks;
|
|
248
256
|
#rider = createLabelRider(this.control, {
|
|
249
257
|
className: "sonic-waveform-label",
|
|
250
258
|
colourProperty: "--_sonic-waveform-marker",
|
|
251
|
-
|
|
259
|
+
fadeProperties: ["--_sonic-waveform-label-fade-start", "--_sonic-waveform-label-fade-end"],
|
|
252
260
|
insetProperty: "--_sonic-waveform-label-inset",
|
|
253
261
|
parkProperty: "--_sonic-waveform-label-park"
|
|
254
262
|
});
|
|
@@ -259,7 +267,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
259
267
|
if (name === "zoomable") this.#bindWheel();
|
|
260
268
|
}
|
|
261
269
|
connectedCallback() {
|
|
262
|
-
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
270
|
+
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
263
271
|
super.connectedCallback();
|
|
264
272
|
}
|
|
265
273
|
connect(signal) {
|
|
@@ -288,6 +296,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
288
296
|
}
|
|
289
297
|
paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
|
|
290
298
|
const held = this.#held();
|
|
299
|
+
const isPaged = look.isReducedMotion && this.reducedMotion === "page";
|
|
291
300
|
const pending = this.#duePending();
|
|
292
301
|
const timeline = frameTimeline({
|
|
293
302
|
clockSeconds: this.#clock.read(frameMs, {
|
|
@@ -297,7 +306,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
297
306
|
}),
|
|
298
307
|
frameMs,
|
|
299
308
|
...held ? { held } : {},
|
|
300
|
-
isPaged
|
|
309
|
+
isPaged,
|
|
301
310
|
isPlaying: this.playing,
|
|
302
311
|
isStill: look.isReducedMotion,
|
|
303
312
|
pending,
|
|
@@ -315,6 +324,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
315
324
|
this.showCurrentMarker(seconds);
|
|
316
325
|
this.toggleState("pending", view.pending.length > 0);
|
|
317
326
|
this.#rider.place({
|
|
327
|
+
isPaged,
|
|
318
328
|
playheadSeconds: seconds,
|
|
319
329
|
startSeconds: view.startSeconds,
|
|
320
330
|
widthPx: view.width / view.dpr,
|
|
@@ -332,11 +342,15 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
332
342
|
}
|
|
333
343
|
renderMarkers() {
|
|
334
344
|
const bounds = this.mapping().bounds;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
const render = this.#renderLabel;
|
|
346
|
+
this.#rider.measure(this.markers.flatMap((marker) => marker.label === void 0 || marker.label === "" ? [] : [{
|
|
347
|
+
isDimmed: marker.dimmed === true,
|
|
348
|
+
...marker.kind === void 0 ? {} : { kind: marker.kind },
|
|
349
|
+
start: clamp(marker.start, ...bounds),
|
|
350
|
+
text: marker.label,
|
|
351
|
+
...render ? { write: (element) => {
|
|
352
|
+
render(marker, element);
|
|
353
|
+
} } : {}
|
|
340
354
|
}]));
|
|
341
355
|
this.surface()?.rebuild();
|
|
342
356
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waveform.js","names":[],"sources":["../../../src/elements/waveform.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { SurfaceFrame, SurfaceLook } from '#lib/canvas-surface.ts';\nimport type { Drawn, View } from '#lib/frame-timeline.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { frameTimeline } from '#lib/frame-timeline.ts';\nimport { isKind, writeKind } from '#lib/marker-band.ts';\nimport { createLabelRider } from '#lib/marker-rider.ts';\nimport { clamp, roundTo } from '#lib/math.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { waveformBuckets } from '#lib/waveform-buckets.ts';\nimport { pinchFactor, wheelFactor, zoomKeyFactor } from '#lib/zoom-gesture.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-zoom': Event;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-waveform': SonicWaveform;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\nexport type PeaksRequest = (\n\tfromSeconds: number,\n\ttoSeconds: number,\n) => Iterable<Promise<unknown>> | Promise<unknown> | undefined;\n\ninterface Pinch {\n\tpointerIds: [number, number];\n\tstartSpanPx: number;\n\tstartZoom: number;\n}\n\ninterface Scene extends View {\n\tcolours: SurfaceLook<Colour>['colours'];\n}\n\nconst colours = {\n\tends: '--_sonic-waveform-ends',\n\tgrid: '--_sonic-waveform-grid',\n\tmarker: '--_sonic-waveform-marker',\n\tplaceholder: '--_sonic-waveform-placeholder',\n\twave: '--_sonic-waveform-wave',\n\twaveEdge: '--_sonic-waveform-wave-edge',\n} as const;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\n\nconst zoomKeys = new Map([\n\t['+', 1],\n\t['-', -1],\n\t['=', 1],\n]);\n\nconst amplitudeMargin = 0.94;\n\nconst hatchPitchPx = 8;\nconst placeholderWavelengthPx = 48;\nconst placeholderAmplitude = 0.2;\n\nconst renderWaveform = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-waveform\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-waveform-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-waveform-scrim\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-ghost\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-playhead\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-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-waveform-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\nfunction placeLine(line: HTMLElement, at: number | undefined): void {\n\tconst isHidden = at === undefined || at < 0 || at > 1;\n\n\tif (line.hidden !== isHidden) line.hidden = isHidden;\n\tif (isHidden) return;\n\n\tconst translate = `${String(at * 100)}cqi`;\n\n\tif (line.style.getPropertyValue('translate') !== translate) {\n\t\tline.style.setProperty('translate', translate);\n\t}\n}\n\nfunction paintHatch(\n\tcontext: CanvasRenderingContext2D,\n\tscene: Scene,\n\t[from, to]: [number, number],\n): void {\n\tif (to <= from) return;\n\n\tconst { dpr, height } = scene;\n\tconst pitch = Math.max(2, Math.round(hatchPitchPx * dpr));\n\tconst offset = scene.startSeconds * scene.pixelsPerSecond;\n\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, height);\n\tcontext.clip();\n\tcontext.beginPath();\n\tfor (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {\n\t\tcontext.moveTo(x, height);\n\t\tcontext.lineTo(x + height, 0);\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.ends;\n\tcontext.stroke();\n\tcontext.restore();\n}\n\nfunction paintPlaceholders(context: CanvasRenderingContext2D, scene: Scene): void {\n\tif (scene.pending.length === 0) return;\n\n\tconst { dpr, height, pixelsPerSecond, startSeconds } = scene;\n\tconst wavelength = placeholderWavelengthPx * dpr;\n\tconst center = height / 2;\n\tconst origin = startSeconds * pixelsPerSecond;\n\tconst waveY = (x: number): number =>\n\t\tcenter -\n\t\tMath.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) *\n\t\t\tcenter *\n\t\t\tplaceholderAmplitude;\n\n\tcontext.beginPath();\n\tfor (const [from, to] of scene.pending) {\n\t\tconst opening = (from - startSeconds) * pixelsPerSecond;\n\t\tconst closing = (to - startSeconds) * pixelsPerSecond;\n\n\t\tcontext.moveTo(opening, waveY(opening));\n\t\tfor (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));\n\t\tcontext.lineTo(closing, waveY(closing));\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.placeholder;\n\tcontext.stroke();\n}\n\nfunction waveFill(context: CanvasRenderingContext2D, scene: Scene): CanvasGradient {\n\tconst { wave, waveEdge } = scene.colours;\n\tconst gradient = context.createLinearGradient(0, 0, 0, scene.height);\n\n\tgradient.addColorStop(0, waveEdge);\n\tgradient.addColorStop(0.5, wave);\n\tgradient.addColorStop(1, waveEdge);\n\n\treturn gradient;\n}\n\nfunction paintWave(context: CanvasRenderingContext2D, scene: Scene, peaks: WaveformPeaks): void {\n\tconst buckets = waveformBuckets(peaks, scene);\n\tconst [first] = buckets;\n\tif (!first) return;\n\n\tconst center = scene.height / 2;\n\tconst amplitudePx = center * amplitudeMargin;\n\n\tcontext.fillStyle = waveFill(context, scene);\n\tcontext.beginPath();\n\tcontext.moveTo(first.x, center);\n\tfor (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);\n\tfor (const bucket of buckets.toReversed())\n\t\tcontext.lineTo(bucket.x, center - bucket.low * amplitudePx);\n\tcontext.closePath();\n\tcontext.fill();\n}\n\nexport class SonicWaveform extends SonicWaveElement<Colour> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicWaveElement.observedAttributes,\n\t\t'pending-delay',\n\t\t'playing',\n\t\t'playback-rate',\n\t\t'reduced-motion',\n\t\t'zoom',\n\t\t'zoom-max',\n\t\t'zoom-min',\n\t\t'zoomable',\n\t];\n\n\tget peaks(): undefined | WaveformPeaks {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: undefined | WaveformPeaks) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.#repaint();\n\t}\n\n\tget pending(): Array<[number, number]> {\n\t\treturn this.#pending.map(([start, end]) => [start, end]);\n\t}\n\n\tset pending(regions: TimeRegions | undefined) {\n\t\tconst pending = regions ? readRegions(regions) : [];\n\t\tif (String(pending) === String(this.#pending)) return;\n\n\t\tconst listedMs = this.#pendingListedMs;\n\t\tconst nowMs = performance.now();\n\n\t\tthis.#pending = pending;\n\t\tthis.#pendingListedMs = new Map(\n\t\t\tthis.#pending.map((region) => {\n\t\t\t\tconst key = region.join(':');\n\n\t\t\t\treturn [key, listedMs.get(key) ?? nowMs];\n\t\t\t}),\n\t\t);\n\t\tthis.#repaint();\n\t}\n\n\tget pendingDelay(): number {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: number | undefined) {\n\t\tthis.reflect('pending-delay', value);\n\t}\n\n\tget playbackRate(): number {\n\t\tconst playbackRate = this.numberAttribute('playback-rate', 1);\n\n\t\treturn playbackRate > 0 ? playbackRate : 1;\n\t}\n\n\tset playbackRate(value: number | undefined) {\n\t\tthis.reflect('playback-rate', value);\n\t}\n\n\tget playing(): boolean {\n\t\treturn this.hasAttribute('playing');\n\t}\n\n\tset playing(isPlaying: boolean) {\n\t\tthis.reflect('playing', isPlaying);\n\t}\n\n\tget readTime(): (() => number | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => number | undefined) | undefined) {\n\t\tthis.#readTime = read;\n\t\tthis.render();\n\t}\n\n\tget reducedMotion(): 'page' | 'scroll' {\n\t\treturn this.getAttribute('reduced-motion') === 'scroll' ? 'scroll' : 'page';\n\t}\n\n\tset reducedMotion(motion: 'page' | 'scroll' | undefined) {\n\t\tthis.reflect('reduced-motion', motion);\n\t}\n\n\tget requestPeaks(): PeaksRequest | undefined {\n\t\treturn this.#requestPeaks;\n\t}\n\n\tset requestPeaks(request: PeaksRequest | undefined) {\n\t\tthis.#requestPeaks = request;\n\t\tthis.#askedKey = undefined;\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tget zoom(): number {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: number | undefined) {\n\t\tthis.reflect('zoom', value);\n\t}\n\n\tget zoomable(): boolean {\n\t\treturn this.hasAttribute('zoomable');\n\t}\n\n\tset zoomable(isZoomable: boolean) {\n\t\tthis.reflect('zoomable', isZoomable);\n\t}\n\n\tget zoomMax(): number {\n\t\tconst zoomMax = this.numberAttribute('zoom-max', defaultZoomMax);\n\n\t\treturn Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);\n\t}\n\n\tset zoomMax(value: number | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): number {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: number | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected readonly control = renderWaveform();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-waveform-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\treadonly #asked = new WeakSet<Promise<unknown>>();\n\n\t#askedKey: string | undefined;\n\n\treadonly #clock = createTrackingClock();\n\n\t#connection: AbortSignal | undefined;\n\n\t#drawn: Drawn | undefined;\n\n\treadonly #ghost = requireChild(this.control, '.sonic-waveform-ghost', HTMLDivElement);\n\n\t#grabbed: Drawn | undefined;\n\n\t#isAsking = false;\n\n\t#isPinchSpent = false;\n\n\t#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\n\n\t#pinch: Pinch | undefined;\n\n\treadonly #playhead = requireChild(this.control, '.sonic-waveform-playhead', HTMLDivElement);\n\n\t#readTime: (() => number | undefined) | undefined;\n\n\t#requestPeaks: PeaksRequest | undefined;\n\n\treadonly #rider = createLabelRider(this.control, {\n\t\tclassName: 'sonic-waveform-label',\n\t\tcolourProperty: '--_sonic-waveform-marker',\n\t\tfadeProperty: '--_sonic-waveform-label-fade',\n\t\tinsetProperty: '--_sonic-waveform-label-inset',\n\t\tparkProperty: '--_sonic-waveform-label-park',\n\t});\n\n\treadonly #touches = new Map<number, number>();\n\n\t#wheel: AbortController | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#bindWheel();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'peaks',\n\t\t\t'pending',\n\t\t\t'pendingDelay',\n\t\t\t'playing',\n\t\t\t'playbackRate',\n\t\t\t'readTime',\n\t\t\t'reducedMotion',\n\t\t\t'requestPeaks',\n\t\t\t'zoom',\n\t\t\t'zoomable',\n\t\t\t'zoomMax',\n\t\t\t'zoomMin',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.#connection = signal;\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#connection = undefined;\n\t\t\t\tthis.#bindWheel();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bindWheel();\n\t\tthis.#bindPinch(signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\tthis.#bindZoomKeys(signal);\n\t\tif (!('fonts' in document)) return;\n\n\t\tdocument.fonts.addEventListener(\n\t\t\t'loadingdone',\n\t\t\t() => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tsurface?.requestFrame();\n\t\t// No frame follows the position out of view, or before the first one paints\n\t\tif (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.samples.length === 0;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour>,\n\t): void {\n\t\tconst held = this.#held();\n\t\tconst pending = this.#duePending();\n\t\tconst timeline = frameTimeline({\n\t\t\tclockSeconds: this.#clock.read(frameMs, {\n\t\t\t\tisPlaying: this.playing,\n\t\t\t\tplaybackRate: this.playbackRate,\n\t\t\t\tseconds: this.#sourceSeconds(),\n\t\t\t}),\n\t\t\tframeMs,\n\t\t\t...(held ? { held } : {}),\n\t\t\tisPaged: look.isReducedMotion && this.reducedMotion === 'page',\n\t\t\tisPlaying: this.playing,\n\t\t\tisStill: look.isReducedMotion,\n\t\t\tpending,\n\t\t\trange: [this.min, Math.max(this.min, this.max)],\n\t\t\tsize,\n\t\t\tzoom: this.zoom,\n\t\t});\n\t\tconst { seconds, view } = timeline;\n\n\t\tthis.#drawn = { seconds, startSeconds: view.startSeconds };\n\t\tplaceLine(this.#playhead, timeline.playheadAt);\n\t\tplaceLine(this.#ghost, timeline.ghostAt);\n\t\tthis.showCurrentMarker(seconds);\n\t\tthis.toggleState('pending', view.pending.length > 0);\n\t\tthis.#rider.place({\n\t\t\tplayheadSeconds: seconds,\n\t\t\tstartSeconds: view.startSeconds,\n\t\t\twidthPx: view.width / view.dpr,\n\t\t\twindowSeconds: view.width / view.pixelsPerSecond,\n\t\t});\n\t\tif (isDirty || timeline.paintKey !== this.#paintedKey) {\n\t\t\tthis.#paintedKey = timeline.paintKey;\n\t\t\tthis.#paintScene(context, { ...view, colours: look.colours }, isRebuilt);\n\t\t\tif (timeline.wanted) this.#ask(timeline.wanted);\n\t\t}\n\t\tif (timeline.isMoving) this.surface()?.requestFrame();\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tconst bounds = this.mapping().bounds;\n\n\t\tthis.#rider.measure(\n\t\t\tthis.markers.flatMap(({ dimmed, kind, label, start }) =>\n\t\t\t\tlabel === undefined || label === ''\n\t\t\t\t\t? []\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tisDimmed: dimmed === true,\n\t\t\t\t\t\t\t\t...(kind === undefined ? {} : { kind }),\n\t\t\t\t\t\t\t\tstart: clamp(start, ...bounds),\n\t\t\t\t\t\t\t\ttext: label,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t),\n\t\t);\n\t\tthis.surface()?.rebuild();\n\t}\n\n\t#ask(wanted: [number, number]): void {\n\t\tconst key = wanted.join(':');\n\t\tif (key === this.#askedKey) return;\n\n\t\tlet asked: ReturnType<PeaksRequest>;\n\n\t\tthis.#askedKey = key;\n\t\tthis.#isAsking = true;\n\t\ttry {\n\t\t\tasked = this.#requestPeaks?.(...wanted);\n\t\t} finally {\n\t\t\tthis.#isAsking = false;\n\t\t}\n\n\t\tconst promises = asked instanceof Promise ? [asked] : (asked ?? []);\n\n\t\tfor (const promise of promises) {\n\t\t\tif (this.#asked.has(promise)) continue;\n\n\t\t\tthis.#asked.add(promise);\n\t\t\tvoid this.#repaintAfter(promise);\n\t\t}\n\t}\n\n\t#bindPinch(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.#touches.clear();\n\t\tthis.#pinch = undefined;\n\t\tthis.#isPinchSpent = false;\n\t\tcontrol.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerType !== 'touch' || !this.zoomable || this.isDisabled()) return;\n\n\t\t\t\tthis.#touches.set(event.pointerId, event.clientX);\n\t\t\t\tif (this.#pinch || this.#isPinchSpent) return;\n\n\t\t\t\tconst pointerIds = this.#touchPair();\n\t\t\t\tif (!pointerIds) return;\n\n\t\t\t\tthis.abandonHold();\n\t\t\t\tcapturePointer(control, event.pointerId);\n\t\t\t\tthis.#pinch = { pointerIds, startSpanPx: this.#spanPx(pointerIds), startZoom: this.zoom };\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (!this.#touches.has(event.pointerId)) return;\n\n\t\t\t\tthis.#touches.set(event.pointerId, event.clientX);\n\n\t\t\t\tconst pinch = this.#pinch;\n\t\t\t\tif (!pinch?.pointerIds.includes(event.pointerId)) return;\n\n\t\t\t\tthis.#zoomTo(\n\t\t\t\t\tpinch.startZoom * pinchFactor(pinch.startSpanPx, this.#spanPx(pinch.pointerIds)),\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tfor (const type of ['pointerup', 'pointercancel'] as const) {\n\t\t\tcontrol.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tif (!this.#touches.delete(event.pointerId)) return;\n\n\t\t\t\t\tif (this.#pinch?.pointerIds.includes(event.pointerId)) {\n\t\t\t\t\t\tthis.#pinch = undefined;\n\t\t\t\t\t\tthis.#isPinchSpent = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (this.#touches.size === 0) this.#isPinchSpent = false;\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t}\n\n\t#bindWheel(): void {\n\t\tthis.#wheel?.abort();\n\t\tthis.#wheel = undefined;\n\t\tif (!this.zoomable || !this.#connection) return;\n\n\t\tthis.#wheel = new AbortController();\n\t\t// Chromium cannot cancel a wheel heard only on the `display: contents` host\n\t\tthis.control.addEventListener(\n\t\t\t'wheel',\n\t\t\t(event) => {\n\t\t\t\tif ((!event.ctrlKey && !event.metaKey) || this.isDisabled()) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#zoomTo(this.zoom * wheelFactor(event.deltaY, event.deltaMode));\n\t\t\t},\n\t\t\t{ passive: false, signal: this.#wheel.signal },\n\t\t);\n\n\t\tlet startZoom = this.zoom;\n\n\t\t// Safari sends a trackpad pinch as gesture events, never as a ctrl wheel; on iOS they shadow a touch pinch\n\t\tfor (const type of ['gesturestart', 'gesturechange']) {\n\t\t\tthis.control.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tif (this.isDisabled() || this.#touches.size > 0) return;\n\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tif (type === 'gesturestart') startZoom = this.zoom;\n\t\t\t\t\telse if ('scale' in event && typeof event.scale === 'number') {\n\t\t\t\t\t\tthis.#zoomTo(startZoom * event.scale);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal: this.#wheel.signal },\n\t\t\t);\n\t\t}\n\t}\n\n\t#bindZoomKeys(signal: AbortSignal): void {\n\t\tthis.control.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tconst direction = this.#zoomKey(event);\n\t\t\t\tif (direction === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#zoomTo(this.zoom * zoomKeyFactor ** direction);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#duePending(): Array<[number, number]> {\n\t\tconst due = dueRegions(this.#pending, this.#pendingListedMs, [\n\t\t\tperformance.now(),\n\t\t\tthis.pendingDelay,\n\t\t]);\n\n\t\tif (due.length < this.#pending.length) this.surface()?.requestFrame();\n\n\t\treturn due;\n\t}\n\n\t#grab(): undefined | ValueAxis {\n\t\tif (this.#pinch || this.#isPinchSpent) return undefined;\n\n\t\tconst drawn = this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\n\t\tthis.#grabbed = drawn;\n\n\t\tconst mapping = this.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn {\n\t\t\tfromProportion: mapping.proportionOf(drawn.seconds),\n\t\t\tisKeptOnCancel: true,\n\t\t\tposition: (event) => -event.clientX,\n\t\t\ttravelPx: Math.max(1, (high - low) * this.zoom),\n\t\t};\n\t}\n\n\t#held(): undefined | { grabbed: Drawn; seconds: number } {\n\t\tconst grabbed = this.#grabbed;\n\t\tconst from = this.heldFrom();\n\t\tif (!grabbed || from === undefined) return undefined;\n\n\t\treturn { grabbed, seconds: this.value === from ? grabbed.seconds : this.value };\n\t}\n\n\t#paintMarkers(context: CanvasRenderingContext2D, scene: Scene, isRebuilt: boolean): void {\n\t\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\n\t\tconst line = Math.max(1, Math.round(scene.dpr));\n\n\t\tfor (const { dimmed, kind, start } of this.markers) {\n\t\t\tconst x = (start - scene.startSeconds) * scene.pixelsPerSecond;\n\n\t\t\tcontext.fillStyle =\n\t\t\t\t(isKind(kind) ? this.#kindColours.get(kind) : undefined) ?? scene.colours.marker;\n\t\t\tcontext.globalAlpha = dimmed === true ? 0.5 : 1;\n\t\t\tcontext.fillRect(x - line / 2, 0, line, scene.height);\n\t\t}\n\t\tcontext.globalAlpha = 1;\n\t}\n\n\t#paintScene(context: CanvasRenderingContext2D, scene: Scene, isRebuilt: boolean): void {\n\t\tconst { dpr, height, pixelsPerSecond, startSeconds, width } = scene;\n\t\tconst x = (seconds: number): number => (seconds - startSeconds) * pixelsPerSecond;\n\t\tconst { ends, grid } = scene.colours;\n\t\tconst line = Math.max(1, Math.round(dpr));\n\t\tconst [opening, closing] = [x(this.min), x(Math.max(this.min, this.max))];\n\t\tconst [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];\n\n\t\tcontext.clearRect(0, 0, width, height);\n\t\tpaintHatch(context, scene, [0, from]);\n\t\tpaintHatch(context, scene, [to, width]);\n\t\tcontext.fillStyle = grid;\n\t\tif (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);\n\t\tpaintPlaceholders(context, scene);\n\t\tif (this.#peaks) paintWave(context, scene, this.#peaks);\n\t\t// Scrolling lines sit at fractional x; rounding them judders\n\t\tcontext.fillStyle = ends;\n\t\tfor (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);\n\t\tthis.#paintMarkers(context, scene, isRebuilt);\n\t}\n\n\t#readKindColours(): Map<string, string> {\n\t\tconst kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];\n\t\tconst probes = kinds.map((kind) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.className = 'sonic-waveform-label';\n\t\t\twriteKind(probe, kind, '--_sonic-waveform-marker');\n\n\t\t\treturn probe;\n\t\t});\n\n\t\tthis.control.append(...probes);\n\n\t\tconst read = probes.map((probe) => getComputedStyle(probe).color);\n\n\t\tfor (const probe of probes) probe.remove();\n\n\t\treturn new Map(kinds.map((kind, index) => [kind, read[index] ?? '']));\n\t}\n\n\t// A write from inside the consumer's call repaints without asking for the same window again\n\t#repaint(): void {\n\t\tif (!this.#isAsking) this.#askedKey = undefined;\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tasync #repaintAfter(asked: Promise<unknown>): Promise<void> {\n\t\tawait Promise.allSettled([asked]);\n\t\tthis.#repaint();\n\t}\n\n\t#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.playback();\n\t}\n\n\t#spanPx([first, second]: [number, number]): number {\n\t\treturn (this.#touches.get(second) ?? 0) - (this.#touches.get(first) ?? 0);\n\t}\n\n\t#touchPair(): [number, number] | undefined {\n\t\tconst [first, second, third] = this.#touches.keys();\n\t\tif (first === undefined || second === undefined || third !== undefined) return undefined;\n\n\t\treturn [first, second];\n\t}\n\n\t#zoomKey(event: KeyboardEvent): number | undefined {\n\t\tif (!this.zoomable || this.isDisabled() || event.target !== this.control) return undefined;\n\t\tif (event.ctrlKey || event.metaKey || event.altKey) return undefined;\n\n\t\treturn zoomKeys.get(event.key);\n\t}\n\n\t#zoomTo(target: number): void {\n\t\tconst next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);\n\t\tif (next === this.zoom) return;\n\n\t\tthis.zoom = next;\n\t\tthis.dispatchEvent(new Event('sonic-zoom', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"waveform.js","names":[],"sources":["../../../src/elements/waveform.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame, SurfaceLook } from '#lib/canvas-surface.ts';\nimport type { Drawn, View } from '#lib/frame-timeline.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { frameTimeline } from '#lib/frame-timeline.ts';\nimport { isKind, writeKind } from '#lib/marker-band.ts';\nimport { createLabelRider } from '#lib/marker-rider.ts';\nimport { clamp, roundTo } from '#lib/math.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { waveformBuckets } from '#lib/waveform-buckets.ts';\nimport { pinchFactor, wheelFactor, zoomKeyFactor } from '#lib/zoom-gesture.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-zoom': Event;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-waveform': SonicWaveform;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nexport type PeaksRequest = (\n\tfromSeconds: number,\n\ttoSeconds: number,\n) => Iterable<Promise<unknown>> | Promise<unknown> | undefined;\n\ninterface Pinch {\n\tpointerIds: [number, number];\n\tstartSpanPx: number;\n\tstartZoom: number;\n}\n\ninterface Scene extends View {\n\tcolours: SurfaceLook<Colour>['colours'];\n}\n\nconst colours = {\n\tends: '--_sonic-waveform-ends',\n\tgrid: '--_sonic-waveform-grid',\n\tmarker: '--_sonic-waveform-marker',\n\tplaceholder: '--_sonic-waveform-placeholder',\n\twave: '--_sonic-waveform-wave',\n\twaveEdge: '--_sonic-waveform-wave-edge',\n} as const;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\n\nconst zoomKeys = new Map([\n\t['+', 1],\n\t['-', -1],\n\t['=', 1],\n]);\n\nconst amplitudeMargin = 0.94;\n\nconst hatchPitchPx = 8;\nconst placeholderWavelengthPx = 48;\nconst placeholderAmplitude = 0.2;\n\nconst renderWaveform = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-waveform\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-waveform-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-waveform-scrim\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-ghost\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-playhead\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-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-waveform-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\nfunction placeLine(line: HTMLElement, at: number | undefined): void {\n\tconst isHidden = at === undefined || at < 0 || at > 1;\n\n\tif (line.hidden !== isHidden) line.hidden = isHidden;\n\tif (isHidden) return;\n\n\tconst translate = `${String(at * 100)}cqi`;\n\n\tif (line.style.getPropertyValue('translate') !== translate) {\n\t\tline.style.setProperty('translate', translate);\n\t}\n}\n\nfunction paintHatch(\n\tcontext: CanvasRenderingContext2D,\n\tscene: Scene,\n\t[from, to]: [number, number],\n): void {\n\tif (to <= from) return;\n\n\tconst { dpr, height } = scene;\n\tconst pitch = Math.max(2, Math.round(hatchPitchPx * dpr));\n\tconst offset = scene.startSeconds * scene.pixelsPerSecond;\n\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, height);\n\tcontext.clip();\n\tcontext.beginPath();\n\tfor (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {\n\t\tcontext.moveTo(x, height);\n\t\tcontext.lineTo(x + height, 0);\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.ends;\n\tcontext.stroke();\n\tcontext.restore();\n}\n\nfunction paintPlaceholders(context: CanvasRenderingContext2D, scene: Scene): void {\n\tif (scene.pending.length === 0) return;\n\n\tconst { dpr, height, pixelsPerSecond, startSeconds } = scene;\n\tconst wavelength = placeholderWavelengthPx * dpr;\n\tconst center = height / 2;\n\tconst origin = startSeconds * pixelsPerSecond;\n\tconst waveY = (x: number): number =>\n\t\tcenter -\n\t\tMath.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) *\n\t\t\tcenter *\n\t\t\tplaceholderAmplitude;\n\n\tcontext.beginPath();\n\tfor (const [from, to] of scene.pending) {\n\t\tconst opening = (from - startSeconds) * pixelsPerSecond;\n\t\tconst closing = (to - startSeconds) * pixelsPerSecond;\n\n\t\tcontext.moveTo(opening, waveY(opening));\n\t\tfor (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));\n\t\tcontext.lineTo(closing, waveY(closing));\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.placeholder;\n\tcontext.stroke();\n}\n\nfunction waveFill(context: CanvasRenderingContext2D, scene: Scene): CanvasGradient {\n\tconst { wave, waveEdge } = scene.colours;\n\tconst gradient = context.createLinearGradient(0, 0, 0, scene.height);\n\n\tgradient.addColorStop(0, waveEdge);\n\tgradient.addColorStop(0.5, wave);\n\tgradient.addColorStop(1, waveEdge);\n\n\treturn gradient;\n}\n\nfunction paintWave(context: CanvasRenderingContext2D, scene: Scene, peaks: WaveformPeaks): void {\n\tconst buckets = waveformBuckets(peaks, scene);\n\tconst [first] = buckets;\n\tif (!first) return;\n\n\tconst center = scene.height / 2;\n\tconst amplitudePx = center * amplitudeMargin;\n\n\tcontext.fillStyle = waveFill(context, scene);\n\tcontext.beginPath();\n\tcontext.moveTo(first.x, center);\n\tfor (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);\n\tfor (const bucket of buckets.toReversed())\n\t\tcontext.lineTo(bucket.x, center - bucket.low * amplitudePx);\n\tcontext.closePath();\n\tcontext.fill();\n}\n\nexport class SonicWaveform extends SonicWaveElement<Colour> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicWaveElement.observedAttributes,\n\t\t'pending-delay',\n\t\t'playing',\n\t\t'playback-rate',\n\t\t'reduced-motion',\n\t\t'zoom',\n\t\t'zoom-max',\n\t\t'zoom-min',\n\t\t'zoomable',\n\t];\n\n\tget peaks(): undefined | WaveformPeaks {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: undefined | WaveformPeaks) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.#repaint();\n\t}\n\n\tget pending(): Array<[number, number]> {\n\t\treturn this.#pending.map(([start, end]) => [start, end]);\n\t}\n\n\tset pending(regions: TimeRegions | undefined) {\n\t\tconst pending = regions ? readRegions(regions) : [];\n\t\tif (String(pending) === String(this.#pending)) return;\n\n\t\tconst listedMs = this.#pendingListedMs;\n\t\tconst nowMs = performance.now();\n\n\t\tthis.#pending = pending;\n\t\tthis.#pendingListedMs = new Map(\n\t\t\tthis.#pending.map((region) => {\n\t\t\t\tconst key = region.join(':');\n\n\t\t\t\treturn [key, listedMs.get(key) ?? nowMs];\n\t\t\t}),\n\t\t);\n\t\tthis.#repaint();\n\t}\n\n\tget pendingDelay(): number {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: number | undefined) {\n\t\tthis.reflect('pending-delay', value);\n\t}\n\n\tget playbackRate(): number {\n\t\tconst playbackRate = this.numberAttribute('playback-rate', 1);\n\n\t\treturn playbackRate > 0 ? playbackRate : 1;\n\t}\n\n\tset playbackRate(value: number | undefined) {\n\t\tthis.reflect('playback-rate', value);\n\t}\n\n\tget playing(): boolean {\n\t\treturn this.hasAttribute('playing');\n\t}\n\n\tset playing(isPlaying: boolean) {\n\t\tthis.reflect('playing', isPlaying);\n\t}\n\n\tget readTime(): (() => number | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => number | undefined) | undefined) {\n\t\tthis.#readTime = read;\n\t\tthis.render();\n\t}\n\n\tget reducedMotion(): 'page' | 'scroll' {\n\t\treturn this.getAttribute('reduced-motion') === 'scroll' ? 'scroll' : 'page';\n\t}\n\n\tset reducedMotion(motion: 'page' | 'scroll' | undefined) {\n\t\tthis.reflect('reduced-motion', motion);\n\t}\n\n\tget renderLabel(): LabelRender | undefined {\n\t\treturn this.#renderLabel;\n\t}\n\n\tset renderLabel(render: LabelRender | undefined) {\n\t\tthis.#renderLabel = render;\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tget requestPeaks(): PeaksRequest | undefined {\n\t\treturn this.#requestPeaks;\n\t}\n\n\tset requestPeaks(request: PeaksRequest | undefined) {\n\t\tthis.#requestPeaks = request;\n\t\tthis.#askedKey = undefined;\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tget zoom(): number {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: number | undefined) {\n\t\tthis.reflect('zoom', value);\n\t}\n\n\tget zoomable(): boolean {\n\t\treturn this.hasAttribute('zoomable');\n\t}\n\n\tset zoomable(isZoomable: boolean) {\n\t\tthis.reflect('zoomable', isZoomable);\n\t}\n\n\tget zoomMax(): number {\n\t\tconst zoomMax = this.numberAttribute('zoom-max', defaultZoomMax);\n\n\t\treturn Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);\n\t}\n\n\tset zoomMax(value: number | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): number {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: number | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected readonly control = renderWaveform();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-waveform-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\treadonly #asked = new WeakSet<Promise<unknown>>();\n\n\t#askedKey: string | undefined;\n\n\treadonly #clock = createTrackingClock();\n\n\t#connection: AbortSignal | undefined;\n\n\t#drawn: Drawn | undefined;\n\n\treadonly #ghost = requireChild(this.control, '.sonic-waveform-ghost', HTMLDivElement);\n\n\t#grabbed: Drawn | undefined;\n\n\t#isAsking = false;\n\n\t#isPinchSpent = false;\n\n\t#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\n\n\t#pinch: Pinch | undefined;\n\n\treadonly #playhead = requireChild(this.control, '.sonic-waveform-playhead', HTMLDivElement);\n\n\t#readTime: (() => number | undefined) | undefined;\n\n\t#renderLabel: LabelRender | undefined;\n\n\t#requestPeaks: PeaksRequest | undefined;\n\n\treadonly #rider = createLabelRider(this.control, {\n\t\tclassName: 'sonic-waveform-label',\n\t\tcolourProperty: '--_sonic-waveform-marker',\n\t\tfadeProperties: ['--_sonic-waveform-label-fade-start', '--_sonic-waveform-label-fade-end'],\n\t\tinsetProperty: '--_sonic-waveform-label-inset',\n\t\tparkProperty: '--_sonic-waveform-label-park',\n\t});\n\n\treadonly #touches = new Map<number, number>();\n\n\t#wheel: AbortController | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#bindWheel();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'peaks',\n\t\t\t'pending',\n\t\t\t'pendingDelay',\n\t\t\t'playing',\n\t\t\t'playbackRate',\n\t\t\t'readTime',\n\t\t\t'reducedMotion',\n\t\t\t'renderLabel',\n\t\t\t'requestPeaks',\n\t\t\t'zoom',\n\t\t\t'zoomable',\n\t\t\t'zoomMax',\n\t\t\t'zoomMin',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.#connection = signal;\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#connection = undefined;\n\t\t\t\tthis.#bindWheel();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bindWheel();\n\t\tthis.#bindPinch(signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\tthis.#bindZoomKeys(signal);\n\t\tif (!('fonts' in document)) return;\n\n\t\tdocument.fonts.addEventListener(\n\t\t\t'loadingdone',\n\t\t\t() => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tsurface?.requestFrame();\n\t\t// No frame follows the position out of view, or before the first one paints\n\t\tif (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.samples.length === 0;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour>,\n\t): void {\n\t\tconst held = this.#held();\n\t\tconst isPaged = look.isReducedMotion && this.reducedMotion === 'page';\n\t\tconst pending = this.#duePending();\n\t\tconst timeline = frameTimeline({\n\t\t\tclockSeconds: this.#clock.read(frameMs, {\n\t\t\t\tisPlaying: this.playing,\n\t\t\t\tplaybackRate: this.playbackRate,\n\t\t\t\tseconds: this.#sourceSeconds(),\n\t\t\t}),\n\t\t\tframeMs,\n\t\t\t...(held ? { held } : {}),\n\t\t\tisPaged,\n\t\t\tisPlaying: this.playing,\n\t\t\tisStill: look.isReducedMotion,\n\t\t\tpending,\n\t\t\trange: [this.min, Math.max(this.min, this.max)],\n\t\t\tsize,\n\t\t\tzoom: this.zoom,\n\t\t});\n\t\tconst { seconds, view } = timeline;\n\n\t\tthis.#drawn = { seconds, startSeconds: view.startSeconds };\n\t\tplaceLine(this.#playhead, timeline.playheadAt);\n\t\tplaceLine(this.#ghost, timeline.ghostAt);\n\t\tthis.showCurrentMarker(seconds);\n\t\tthis.toggleState('pending', view.pending.length > 0);\n\t\tthis.#rider.place({\n\t\t\tisPaged,\n\t\t\tplayheadSeconds: seconds,\n\t\t\tstartSeconds: view.startSeconds,\n\t\t\twidthPx: view.width / view.dpr,\n\t\t\twindowSeconds: view.width / view.pixelsPerSecond,\n\t\t});\n\t\tif (isDirty || timeline.paintKey !== this.#paintedKey) {\n\t\t\tthis.#paintedKey = timeline.paintKey;\n\t\t\tthis.#paintScene(context, { ...view, colours: look.colours }, isRebuilt);\n\t\t\tif (timeline.wanted) this.#ask(timeline.wanted);\n\t\t}\n\t\tif (timeline.isMoving) this.surface()?.requestFrame();\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tconst bounds = this.mapping().bounds;\n\n\t\tconst render = this.#renderLabel;\n\n\t\tthis.#rider.measure(\n\t\t\tthis.markers.flatMap((marker) =>\n\t\t\t\tmarker.label === undefined || marker.label === ''\n\t\t\t\t\t? []\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tisDimmed: marker.dimmed === true,\n\t\t\t\t\t\t\t\t...(marker.kind === undefined ? {} : { kind: marker.kind }),\n\t\t\t\t\t\t\t\tstart: clamp(marker.start, ...bounds),\n\t\t\t\t\t\t\t\ttext: marker.label,\n\t\t\t\t\t\t\t\t...(render\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\twrite: (element: HTMLElement) => {\n\t\t\t\t\t\t\t\t\t\t\t\trender(marker, element);\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t),\n\t\t);\n\t\tthis.surface()?.rebuild();\n\t}\n\n\t#ask(wanted: [number, number]): void {\n\t\tconst key = wanted.join(':');\n\t\tif (key === this.#askedKey) return;\n\n\t\tlet asked: ReturnType<PeaksRequest>;\n\n\t\tthis.#askedKey = key;\n\t\tthis.#isAsking = true;\n\t\ttry {\n\t\t\tasked = this.#requestPeaks?.(...wanted);\n\t\t} finally {\n\t\t\tthis.#isAsking = false;\n\t\t}\n\n\t\tconst promises = asked instanceof Promise ? [asked] : (asked ?? []);\n\n\t\tfor (const promise of promises) {\n\t\t\tif (this.#asked.has(promise)) continue;\n\n\t\t\tthis.#asked.add(promise);\n\t\t\tvoid this.#repaintAfter(promise);\n\t\t}\n\t}\n\n\t#bindPinch(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.#touches.clear();\n\t\tthis.#pinch = undefined;\n\t\tthis.#isPinchSpent = false;\n\t\tcontrol.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerType !== 'touch' || !this.zoomable || this.isDisabled()) return;\n\n\t\t\t\tthis.#touches.set(event.pointerId, event.clientX);\n\t\t\t\tif (this.#pinch || this.#isPinchSpent) return;\n\n\t\t\t\tconst pointerIds = this.#touchPair();\n\t\t\t\tif (!pointerIds) return;\n\n\t\t\t\tthis.abandonHold();\n\t\t\t\tcapturePointer(control, event.pointerId);\n\t\t\t\tthis.#pinch = { pointerIds, startSpanPx: this.#spanPx(pointerIds), startZoom: this.zoom };\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (!this.#touches.has(event.pointerId)) return;\n\n\t\t\t\tthis.#touches.set(event.pointerId, event.clientX);\n\n\t\t\t\tconst pinch = this.#pinch;\n\t\t\t\tif (!pinch?.pointerIds.includes(event.pointerId)) return;\n\n\t\t\t\tthis.#zoomTo(\n\t\t\t\t\tpinch.startZoom * pinchFactor(pinch.startSpanPx, this.#spanPx(pinch.pointerIds)),\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tfor (const type of ['pointerup', 'pointercancel'] as const) {\n\t\t\tcontrol.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tif (!this.#touches.delete(event.pointerId)) return;\n\n\t\t\t\t\tif (this.#pinch?.pointerIds.includes(event.pointerId)) {\n\t\t\t\t\t\tthis.#pinch = undefined;\n\t\t\t\t\t\tthis.#isPinchSpent = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (this.#touches.size === 0) this.#isPinchSpent = false;\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t}\n\n\t#bindWheel(): void {\n\t\tthis.#wheel?.abort();\n\t\tthis.#wheel = undefined;\n\t\tif (!this.zoomable || !this.#connection) return;\n\n\t\tthis.#wheel = new AbortController();\n\t\t// Chromium cannot cancel a wheel heard only on the `display: contents` host\n\t\tthis.control.addEventListener(\n\t\t\t'wheel',\n\t\t\t(event) => {\n\t\t\t\tif ((!event.ctrlKey && !event.metaKey) || this.isDisabled()) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#zoomTo(this.zoom * wheelFactor(event.deltaY, event.deltaMode));\n\t\t\t},\n\t\t\t{ passive: false, signal: this.#wheel.signal },\n\t\t);\n\n\t\tlet startZoom = this.zoom;\n\n\t\t// Safari sends a trackpad pinch as gesture events, never as a ctrl wheel\n\t\tfor (const type of ['gesturestart', 'gesturechange']) {\n\t\t\tthis.control.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tif (this.isDisabled() || this.#touches.size > 0) return;\n\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tif (type === 'gesturestart') startZoom = this.zoom;\n\t\t\t\t\telse if ('scale' in event && typeof event.scale === 'number') {\n\t\t\t\t\t\tthis.#zoomTo(startZoom * event.scale);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal: this.#wheel.signal },\n\t\t\t);\n\t\t}\n\t}\n\n\t#bindZoomKeys(signal: AbortSignal): void {\n\t\tthis.control.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tconst direction = this.#zoomKey(event);\n\t\t\t\tif (direction === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#zoomTo(this.zoom * zoomKeyFactor ** direction);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#duePending(): Array<[number, number]> {\n\t\tconst due = dueRegions(this.#pending, this.#pendingListedMs, [\n\t\t\tperformance.now(),\n\t\t\tthis.pendingDelay,\n\t\t]);\n\n\t\tif (due.length < this.#pending.length) this.surface()?.requestFrame();\n\n\t\treturn due;\n\t}\n\n\t#grab(): undefined | ValueAxis {\n\t\tif (this.#pinch || this.#isPinchSpent) return undefined;\n\n\t\tconst drawn = this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\n\t\tthis.#grabbed = drawn;\n\n\t\tconst mapping = this.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn {\n\t\t\tfromProportion: mapping.proportionOf(drawn.seconds),\n\t\t\tisKeptOnCancel: true,\n\t\t\tposition: (event) => -event.clientX,\n\t\t\ttravelPx: Math.max(1, (high - low) * this.zoom),\n\t\t};\n\t}\n\n\t#held(): undefined | { grabbed: Drawn; seconds: number } {\n\t\tconst grabbed = this.#grabbed;\n\t\tconst from = this.heldFrom();\n\t\tif (!grabbed || from === undefined) return undefined;\n\n\t\treturn { grabbed, seconds: this.value === from ? grabbed.seconds : this.value };\n\t}\n\n\t#paintMarkers(context: CanvasRenderingContext2D, scene: Scene, isRebuilt: boolean): void {\n\t\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\n\t\tconst line = Math.max(1, Math.round(scene.dpr));\n\n\t\tfor (const { dimmed, kind, start } of this.markers) {\n\t\t\tconst x = (start - scene.startSeconds) * scene.pixelsPerSecond;\n\n\t\t\tcontext.fillStyle =\n\t\t\t\t(isKind(kind) ? this.#kindColours.get(kind) : undefined) ?? scene.colours.marker;\n\t\t\tcontext.globalAlpha = dimmed === true ? 0.5 : 1;\n\t\t\tcontext.fillRect(x - line / 2, 0, line, scene.height);\n\t\t}\n\t\tcontext.globalAlpha = 1;\n\t}\n\n\t#paintScene(context: CanvasRenderingContext2D, scene: Scene, isRebuilt: boolean): void {\n\t\tconst { dpr, height, pixelsPerSecond, startSeconds, width } = scene;\n\t\tconst x = (seconds: number): number => (seconds - startSeconds) * pixelsPerSecond;\n\t\tconst { ends, grid } = scene.colours;\n\t\tconst line = Math.max(1, Math.round(dpr));\n\t\tconst [opening, closing] = [x(this.min), x(Math.max(this.min, this.max))];\n\t\tconst [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];\n\n\t\tcontext.clearRect(0, 0, width, height);\n\t\tpaintHatch(context, scene, [0, from]);\n\t\tpaintHatch(context, scene, [to, width]);\n\t\tcontext.fillStyle = grid;\n\t\tif (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);\n\t\tpaintPlaceholders(context, scene);\n\t\tif (this.#peaks) paintWave(context, scene, this.#peaks);\n\t\t// Scrolling lines sit at fractional x; rounding them judders\n\t\tcontext.fillStyle = ends;\n\t\tfor (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);\n\t\tthis.#paintMarkers(context, scene, isRebuilt);\n\t}\n\n\t#readKindColours(): Map<string, string> {\n\t\tconst kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];\n\t\tconst probes = kinds.map((kind) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.className = 'sonic-waveform-label';\n\t\t\twriteKind(probe, kind, '--_sonic-waveform-marker');\n\n\t\t\treturn probe;\n\t\t});\n\n\t\tthis.control.append(...probes);\n\n\t\tconst read = probes.map((probe) => getComputedStyle(probe).color);\n\n\t\tfor (const probe of probes) probe.remove();\n\n\t\treturn new Map(kinds.map((kind, index) => [kind, read[index] ?? '']));\n\t}\n\n\t// A write from inside the consumer's call repaints without asking for the same window again\n\t#repaint(): void {\n\t\tif (!this.#isAsking) this.#askedKey = undefined;\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tasync #repaintAfter(asked: Promise<unknown>): Promise<void> {\n\t\tawait Promise.allSettled([asked]);\n\t\tthis.#repaint();\n\t}\n\n\t#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.playback();\n\t}\n\n\t#spanPx([first, second]: [number, number]): number {\n\t\treturn (this.#touches.get(second) ?? 0) - (this.#touches.get(first) ?? 0);\n\t}\n\n\t#touchPair(): [number, number] | undefined {\n\t\tconst [first, second, third] = this.#touches.keys();\n\t\tif (first === undefined || second === undefined || third !== undefined) return undefined;\n\n\t\treturn [first, second];\n\t}\n\n\t#zoomKey(event: KeyboardEvent): number | undefined {\n\t\tif (!this.zoomable || this.isDisabled() || event.target !== this.control) return undefined;\n\t\tif (event.ctrlKey || event.metaKey || event.altKey) return undefined;\n\n\t\treturn zoomKeys.get(event.key);\n\t}\n\n\t#zoomTo(target: number): void {\n\t\tconst next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);\n\t\tif (next === this.zoom) return;\n\n\t\tthis.zoom = next;\n\t\tthis.dispatchEvent(new Event('sonic-zoom', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
|
package/dist/dev/index.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ import { SonicSplit } from "./elements/split.js";
|
|
|
12
12
|
import { SonicSwitch } from "./elements/switch.js";
|
|
13
13
|
import { SonicToggle } from "./elements/toggle.js";
|
|
14
14
|
import { WaveMarker } from "./elements/wave-element.js";
|
|
15
|
-
import { PeaksRequest, SonicWaveform } from "./elements/waveform.js";
|
|
15
|
+
import { LabelRender, PeaksRequest, SonicWaveform } from "./elements/waveform.js";
|
|
16
16
|
import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
17
17
|
import { SonicXy } from "./elements/xy.js";
|
|
18
18
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
19
19
|
import { envelopeCurve } from "./lib/envelope-curve.js";
|
|
20
20
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
21
|
-
export { type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type TimeRegions, type WaveMarker, type WaveformPeaks, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|
|
21
|
+
export { type LabelRender, type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type TimeRegions, type WaveMarker, type WaveformPeaks, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
|
|
@@ -7,15 +7,16 @@ import { writeKind } from "./marker-band.js";
|
|
|
7
7
|
function lineX(seconds, view) {
|
|
8
8
|
return (seconds - view.startSeconds) / view.windowSeconds * view.widthPx;
|
|
9
9
|
}
|
|
10
|
-
function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
10
|
+
function parkedPlacement(labels, index, [view, arrivingX, [startX, endX]]) {
|
|
11
11
|
const parked = labels[index];
|
|
12
12
|
if (!parked) return void 0;
|
|
13
13
|
const next = labels[index + 1];
|
|
14
14
|
const x = Math.max(view.parkPx, lineX(parked.start, view) + view.insetPx);
|
|
15
|
-
const
|
|
15
|
+
const fadePx = startX - endX;
|
|
16
|
+
const leftPx = next ? lineX(next.start, view) - endX : Infinity;
|
|
16
17
|
return {
|
|
17
18
|
index,
|
|
18
|
-
opacity:
|
|
19
|
+
opacity: fadePx > 0 ? clamp(leftPx / fadePx, 0, 1) : 1,
|
|
19
20
|
shownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),
|
|
20
21
|
x
|
|
21
22
|
};
|
|
@@ -23,12 +24,23 @@ function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
|
23
24
|
function isDrawable(length) {
|
|
24
25
|
return Number.isFinite(length) && length > 0;
|
|
25
26
|
}
|
|
27
|
+
function fadeLines(view) {
|
|
28
|
+
const playheadX = lineX(view.playheadSeconds, view);
|
|
29
|
+
if (view.isPaged) return [playheadX, playheadX];
|
|
30
|
+
const stretchPx = view.parkPx - view.insetPx - playheadX;
|
|
31
|
+
return [playheadX + clamp(view.fadeStart, 0, 1) * stretchPx, playheadX + clamp(view.fadeEnd, 0, 1) * stretchPx];
|
|
32
|
+
}
|
|
26
33
|
function layoutRider(labels, view) {
|
|
27
34
|
if (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};
|
|
28
|
-
const
|
|
35
|
+
const fade = fadeLines(view);
|
|
36
|
+
const parkedIndex = labels.findLastIndex((label) => lineX(label.start, view) <= fade[1]);
|
|
29
37
|
const next = labels[parkedIndex + 1];
|
|
30
38
|
const arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;
|
|
31
|
-
const parked = parkedPlacement(labels, parkedIndex, [
|
|
39
|
+
const parked = parkedPlacement(labels, parkedIndex, [
|
|
40
|
+
view,
|
|
41
|
+
arrivingX,
|
|
42
|
+
fade
|
|
43
|
+
]);
|
|
32
44
|
const arriving = next && arrivingX < view.widthPx ? {
|
|
33
45
|
index: parkedIndex + 1,
|
|
34
46
|
opacity: 1,
|
|
@@ -44,11 +56,16 @@ function labelSlot(element) {
|
|
|
44
56
|
return {
|
|
45
57
|
element,
|
|
46
58
|
kind: void 0,
|
|
59
|
+
label: void 0,
|
|
47
60
|
opacity: NaN,
|
|
48
61
|
shownPx: NaN,
|
|
49
62
|
x: NaN
|
|
50
63
|
};
|
|
51
64
|
}
|
|
65
|
+
function writeContent(element, { text, write }) {
|
|
66
|
+
element.textContent = write ? "" : text;
|
|
67
|
+
write?.(element);
|
|
68
|
+
}
|
|
52
69
|
function writePlacement(slot, { opacity, shownPx, x }, widthPx) {
|
|
53
70
|
const { style } = slot.element;
|
|
54
71
|
if (slot.x !== x) {
|
|
@@ -69,7 +86,10 @@ function writeLabel(slot, [labels, colourProperty], placement) {
|
|
|
69
86
|
const label = placement && placement.shownPx > 0 ? labels[placement.index] : void 0;
|
|
70
87
|
if (element.hidden !== !label) element.hidden = !label;
|
|
71
88
|
if (!label || !placement) return;
|
|
72
|
-
if (
|
|
89
|
+
if (slot.label !== label) {
|
|
90
|
+
slot.label = label;
|
|
91
|
+
writeContent(element, label);
|
|
92
|
+
}
|
|
73
93
|
element.toggleAttribute("data-sonic-dimmed", label.isDimmed);
|
|
74
94
|
if (slot.kind !== label.kind) {
|
|
75
95
|
slot.kind = label.kind;
|
|
@@ -77,11 +97,11 @@ function writeLabel(slot, [labels, colourProperty], placement) {
|
|
|
77
97
|
}
|
|
78
98
|
writePlacement(slot, placement, label.widthPx);
|
|
79
99
|
}
|
|
80
|
-
function measureWidths(control, className,
|
|
81
|
-
const probes =
|
|
100
|
+
function measureWidths(control, className, labels) {
|
|
101
|
+
const probes = labels.map((label) => {
|
|
82
102
|
const probe = document.createElement("div");
|
|
83
103
|
probe.className = className;
|
|
84
|
-
probe
|
|
104
|
+
writeContent(probe, label);
|
|
85
105
|
return probe;
|
|
86
106
|
});
|
|
87
107
|
control.append(...probes);
|
|
@@ -90,18 +110,20 @@ function measureWidths(control, className, texts) {
|
|
|
90
110
|
return widths;
|
|
91
111
|
}
|
|
92
112
|
function createLabelRider(control, options) {
|
|
93
|
-
const { className, colourProperty,
|
|
113
|
+
const { className, colourProperty, fadeProperties, insetProperty, parkProperty } = options;
|
|
94
114
|
const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
|
|
95
115
|
const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
|
|
96
|
-
let
|
|
116
|
+
let fadeStart = 0;
|
|
117
|
+
let fadeEnd = 1;
|
|
97
118
|
let insetPx = 0;
|
|
98
119
|
let parkPx = 0;
|
|
99
120
|
let measured = [];
|
|
100
121
|
return {
|
|
101
122
|
measure: (labels) => {
|
|
102
|
-
const widths = measureWidths(control, className, labels
|
|
123
|
+
const widths = measureWidths(control, className, labels);
|
|
103
124
|
const styles = getComputedStyle(control);
|
|
104
|
-
|
|
125
|
+
fadeStart = readPxProperty(styles, fadeProperties[0], 0);
|
|
126
|
+
fadeEnd = readPxProperty(styles, fadeProperties[1], 1);
|
|
105
127
|
insetPx = readPxProperty(styles, insetProperty, 0);
|
|
106
128
|
parkPx = readPxProperty(styles, parkProperty, 0);
|
|
107
129
|
measured = labels.map((label, index) => ({
|
|
@@ -112,7 +134,8 @@ function createLabelRider(control, options) {
|
|
|
112
134
|
place: (window) => {
|
|
113
135
|
const layout = layoutRider(measured, {
|
|
114
136
|
...window,
|
|
115
|
-
|
|
137
|
+
fadeEnd,
|
|
138
|
+
fadeStart,
|
|
116
139
|
insetPx,
|
|
117
140
|
parkPx
|
|
118
141
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marker-rider.js","names":[],"sources":["../../../src/lib/marker-rider.ts"],"sourcesContent":["import { writeKind } from '#lib/marker-band.ts';\nimport { clamp } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\n\ninterface RiderWindow {\n\tplayheadSeconds: number;\n\tstartSeconds: number;\n\twidthPx: number;\n\twindowSeconds: number;\n}\n\nexport interface RiderView extends RiderWindow {\n\tfadeShare: number;\n\tinsetPx: number;\n\tparkPx: number;\n}\n\nexport interface RiderLabel {\n\tisDimmed: boolean;\n\tkind?: string;\n\tstart: number;\n\ttext: string;\n\twidthPx: number;\n}\n\ninterface LabelPlacement {\n\tindex: number;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\ninterface RiderLayout {\n\tarriving?: LabelPlacement;\n\tparked?: LabelPlacement;\n}\n\ninterface LabelSlot {\n\telement: HTMLElement;\n\tkind: string | undefined;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\nfunction lineX(seconds: number, view: RiderView): number {\n\treturn ((seconds - view.startSeconds) / view.windowSeconds) * view.widthPx;\n}\n\nfunction parkedPlacement(\n\tlabels: ReadonlyArray<RiderLabel>,\n\tindex: number,\n\t[view, arrivingX]: [RiderView, number],\n): LabelPlacement | undefined {\n\tconst parked = labels[index];\n\tif (!parked) return undefined;\n\n\tconst next = labels[index + 1];\n\tconst x = Math.max(view.parkPx, lineX(parked.start, view) + view.insetPx);\n\tconst approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;\n\n\treturn {\n\t\tindex,\n\t\topacity: view.fadeShare > 0 ? clamp(approachPx / (view.widthPx * view.fadeShare), 0, 1) : 1,\n\t\tshownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),\n\t\tx,\n\t};\n}\n\nfunction isDrawable(length: number): boolean {\n\treturn Number.isFinite(length) && length > 0;\n}\n\nexport function layoutRider(labels: ReadonlyArray<RiderLabel>, view: RiderView): RiderLayout {\n\tif (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};\n\n\tconst parkedIndex = labels.findLastIndex((label) => label.start <= view.playheadSeconds);\n\tconst next = labels[parkedIndex + 1];\n\tconst arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;\n\tconst parked = parkedPlacement(labels, parkedIndex, [view, arrivingX]);\n\tconst arriving: LabelPlacement | undefined =\n\t\tnext && arrivingX < view.widthPx\n\t\t\t? {\n\t\t\t\t\tindex: parkedIndex + 1,\n\t\t\t\t\topacity: 1,\n\t\t\t\t\tshownPx: Math.min(next.widthPx, view.widthPx - arrivingX),\n\t\t\t\t\tx: arrivingX,\n\t\t\t\t}\n\t\t\t: undefined;\n\n\treturn { ...(arriving ? { arriving } : {}), ...(parked ? { parked } : {}) };\n}\n\nfunction labelSlot(element: HTMLElement): LabelSlot {\n\treturn { element, kind: undefined, opacity: NaN, shownPx: NaN, x: NaN };\n}\n\nfunction writePlacement(\n\tslot: LabelSlot,\n\t{ opacity, shownPx, x }: LabelPlacement,\n\twidthPx: number,\n): void {\n\tconst { style } = slot.element;\n\n\tif (slot.x !== x) {\n\t\tslot.x = x;\n\t\tstyle.setProperty('translate', `${x.toFixed(2)}px`);\n\t}\n\tif (slot.shownPx !== shownPx) {\n\t\tslot.shownPx = shownPx;\n\t\tstyle.setProperty(\n\t\t\t'clip-path',\n\t\t\tshownPx < widthPx ? `inset(0 calc(100% - ${shownPx.toFixed(2)}px) 0 0)` : '',\n\t\t);\n\t}\n\tif (slot.opacity !== opacity) {\n\t\tslot.opacity = opacity;\n\t\tstyle.setProperty('opacity', opacity < 1 ? opacity.toFixed(3) : '');\n\t}\n}\n\nfunction writeLabel(\n\tslot: LabelSlot,\n\t[labels, colourProperty]: [ReadonlyArray<RiderLabel>, string],\n\tplacement: LabelPlacement | undefined,\n): void {\n\tconst { element } = slot;\n\tconst label = placement && placement.shownPx > 0 ? labels[placement.index] : undefined;\n\n\tif (element.hidden !== !label) element.hidden = !label;\n\tif (!label || !placement) return;\n\n\tif (element.textContent !== label.text) element.textContent = label.text;\n\telement.toggleAttribute('data-sonic-dimmed', label.isDimmed);\n\tif (slot.kind !== label.kind) {\n\t\tslot.kind = label.kind;\n\t\twriteKind(element, label.kind, colourProperty);\n\t}\n\twritePlacement(slot, placement, label.widthPx);\n}\n\nfunction measureWidths(\n\tcontrol: HTMLElement,\n\tclassName: string,\n\ttexts: Array<string>,\n): Array<number> {\n\tconst probes = texts.map((text) => {\n\t\tconst probe = document.createElement('div');\n\n\t\tprobe.className = className;\n\t\tprobe.textContent = text;\n\n\t\treturn probe;\n\t});\n\n\tcontrol.append(...probes);\n\n\tconst widths = probes.map((probe) => probe.getBoundingClientRect().width);\n\n\tfor (const probe of probes) probe.remove();\n\n\treturn widths;\n}\n\nexport function createLabelRider(\n\tcontrol: HTMLElement,\n\toptions: {\n\t\tclassName: string;\n\t\tcolourProperty: `--_sonic-${string}`;\n\t\tfadeProperty: `--_sonic-${string}`;\n\t\tinsetProperty: `--_sonic-${string}`;\n\t\tparkProperty: `--_sonic-${string}`;\n\t},\n): {\n\tmeasure: (labels: ReadonlyArray<Omit<RiderLabel, 'widthPx'>>) => void;\n\tplace: (window: RiderWindow) => void;\n} {\n\tconst { className, colourProperty, fadeProperty, insetProperty, parkProperty } = options;\n\tconst parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));\n\tconst arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));\n\tlet fadeShare = 0.25;\n\tlet insetPx = 0;\n\tlet parkPx = 0;\n\tlet measured: Array<RiderLabel> = [];\n\n\treturn {\n\t\tmeasure: (labels) => {\n\t\t\tconst widths = measureWidths(\n\t\t\t\tcontrol,\n\t\t\t\tclassName,\n\t\t\t\tlabels.map(({ text }) => text),\n\t\t\t);\n\n\t\t\tconst styles = getComputedStyle(control);\n\n\t\t\tfadeShare = readPxProperty(styles, fadeProperty, 0.25);\n\t\t\tinsetPx = readPxProperty(styles, insetProperty, 0);\n\t\t\tparkPx = readPxProperty(styles, parkProperty, 0);\n\t\t\tmeasured = labels\n\t\t\t\t.map((label, index) => ({ ...label, widthPx: widths[index] ?? 0 }))\n\t\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\t},\n\t\tplace: (window) => {\n\t\t\tconst layout = layoutRider(measured, { ...window, fadeShare, insetPx, parkPx });\n\n\t\t\twriteLabel(parked, [measured, colourProperty], layout.parked);\n\t\t\twriteLabel(arriving, [measured, colourProperty], layout.arriving);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"marker-rider.js","names":[],"sources":["../../../src/lib/marker-rider.ts"],"sourcesContent":["import { writeKind } from '#lib/marker-band.ts';\nimport { clamp } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\n\ninterface RiderWindow {\n\tisPaged: boolean;\n\tplayheadSeconds: number;\n\tstartSeconds: number;\n\twidthPx: number;\n\twindowSeconds: number;\n}\n\nexport interface RiderView extends RiderWindow {\n\tfadeEnd: number;\n\tfadeStart: number;\n\tinsetPx: number;\n\tparkPx: number;\n}\n\nexport interface RiderLabel {\n\tisDimmed: boolean;\n\tkind?: string;\n\tstart: number;\n\ttext: string;\n\twidthPx: number;\n\twrite?: (element: HTMLElement) => void;\n}\n\ninterface LabelPlacement {\n\tindex: number;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\ninterface RiderLayout {\n\tarriving?: LabelPlacement;\n\tparked?: LabelPlacement;\n}\n\ninterface LabelSlot {\n\telement: HTMLElement;\n\tkind: string | undefined;\n\tlabel: RiderLabel | undefined;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\nfunction lineX(seconds: number, view: RiderView): number {\n\treturn ((seconds - view.startSeconds) / view.windowSeconds) * view.widthPx;\n}\n\nfunction parkedPlacement(\n\tlabels: ReadonlyArray<RiderLabel>,\n\tindex: number,\n\t[view, arrivingX, [startX, endX]]: [RiderView, number, [number, number]],\n): LabelPlacement | undefined {\n\tconst parked = labels[index];\n\tif (!parked) return undefined;\n\n\tconst next = labels[index + 1];\n\tconst x = Math.max(view.parkPx, lineX(parked.start, view) + view.insetPx);\n\tconst fadePx = startX - endX;\n\tconst leftPx = next ? lineX(next.start, view) - endX : Infinity;\n\n\treturn {\n\t\tindex,\n\t\topacity: fadePx > 0 ? clamp(leftPx / fadePx, 0, 1) : 1,\n\t\tshownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),\n\t\tx,\n\t};\n}\n\nfunction isDrawable(length: number): boolean {\n\treturn Number.isFinite(length) && length > 0;\n}\n\n// Where the next line is as the fade starts and ends; 0 at the playhead, 1 with its label on the park\nfunction fadeLines(view: RiderView): [number, number] {\n\tconst playheadX = lineX(view.playheadSeconds, view);\n\t// Nothing rides in on a still page, so the label changes as the playhead crosses\n\tif (view.isPaged) return [playheadX, playheadX];\n\n\tconst stretchPx = view.parkPx - view.insetPx - playheadX;\n\n\treturn [\n\t\tplayheadX + clamp(view.fadeStart, 0, 1) * stretchPx,\n\t\tplayheadX + clamp(view.fadeEnd, 0, 1) * stretchPx,\n\t];\n}\n\nexport function layoutRider(labels: ReadonlyArray<RiderLabel>, view: RiderView): RiderLayout {\n\tif (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};\n\n\tconst fade = fadeLines(view);\n\tconst parkedIndex = labels.findLastIndex((label) => lineX(label.start, view) <= fade[1]);\n\tconst next = labels[parkedIndex + 1];\n\tconst arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;\n\tconst parked = parkedPlacement(labels, parkedIndex, [view, arrivingX, fade]);\n\tconst arriving: LabelPlacement | undefined =\n\t\tnext && arrivingX < view.widthPx\n\t\t\t? {\n\t\t\t\t\tindex: parkedIndex + 1,\n\t\t\t\t\topacity: 1,\n\t\t\t\t\tshownPx: Math.min(next.widthPx, view.widthPx - arrivingX),\n\t\t\t\t\tx: arrivingX,\n\t\t\t\t}\n\t\t\t: undefined;\n\n\treturn { ...(arriving ? { arriving } : {}), ...(parked ? { parked } : {}) };\n}\n\nfunction labelSlot(element: HTMLElement): LabelSlot {\n\treturn { element, kind: undefined, label: undefined, opacity: NaN, shownPx: NaN, x: NaN };\n}\n\nfunction writeContent(element: HTMLElement, { text, write }: Omit<RiderLabel, 'widthPx'>): void {\n\telement.textContent = write ? '' : text;\n\twrite?.(element);\n}\n\nfunction writePlacement(\n\tslot: LabelSlot,\n\t{ opacity, shownPx, x }: LabelPlacement,\n\twidthPx: number,\n): void {\n\tconst { style } = slot.element;\n\n\tif (slot.x !== x) {\n\t\tslot.x = x;\n\t\tstyle.setProperty('translate', `${x.toFixed(2)}px`);\n\t}\n\tif (slot.shownPx !== shownPx) {\n\t\tslot.shownPx = shownPx;\n\t\tstyle.setProperty(\n\t\t\t'clip-path',\n\t\t\tshownPx < widthPx ? `inset(0 calc(100% - ${shownPx.toFixed(2)}px) 0 0)` : '',\n\t\t);\n\t}\n\tif (slot.opacity !== opacity) {\n\t\tslot.opacity = opacity;\n\t\tstyle.setProperty('opacity', opacity < 1 ? opacity.toFixed(3) : '');\n\t}\n}\n\nfunction writeLabel(\n\tslot: LabelSlot,\n\t[labels, colourProperty]: [ReadonlyArray<RiderLabel>, string],\n\tplacement: LabelPlacement | undefined,\n): void {\n\tconst { element } = slot;\n\tconst label = placement && placement.shownPx > 0 ? labels[placement.index] : undefined;\n\n\tif (element.hidden !== !label) element.hidden = !label;\n\tif (!label || !placement) return;\n\n\tif (slot.label !== label) {\n\t\tslot.label = label;\n\t\twriteContent(element, label);\n\t}\n\telement.toggleAttribute('data-sonic-dimmed', label.isDimmed);\n\tif (slot.kind !== label.kind) {\n\t\tslot.kind = label.kind;\n\t\twriteKind(element, label.kind, colourProperty);\n\t}\n\twritePlacement(slot, placement, label.widthPx);\n}\n\nfunction measureWidths(\n\tcontrol: HTMLElement,\n\tclassName: string,\n\tlabels: ReadonlyArray<Omit<RiderLabel, 'widthPx'>>,\n): Array<number> {\n\tconst probes = labels.map((label) => {\n\t\tconst probe = document.createElement('div');\n\n\t\tprobe.className = className;\n\t\twriteContent(probe, label);\n\n\t\treturn probe;\n\t});\n\n\tcontrol.append(...probes);\n\n\tconst widths = probes.map((probe) => probe.getBoundingClientRect().width);\n\n\tfor (const probe of probes) probe.remove();\n\n\treturn widths;\n}\n\nexport function createLabelRider(\n\tcontrol: HTMLElement,\n\toptions: {\n\t\tclassName: string;\n\t\tcolourProperty: `--_sonic-${string}`;\n\t\tfadeProperties: [start: `--_sonic-${string}`, end: `--_sonic-${string}`];\n\t\tinsetProperty: `--_sonic-${string}`;\n\t\tparkProperty: `--_sonic-${string}`;\n\t},\n): {\n\tmeasure: (labels: ReadonlyArray<Omit<RiderLabel, 'widthPx'>>) => void;\n\tplace: (window: RiderWindow) => void;\n} {\n\tconst { className, colourProperty, fadeProperties, insetProperty, parkProperty } = options;\n\tconst parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));\n\tconst arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));\n\tlet fadeStart = 0;\n\tlet fadeEnd = 1;\n\tlet insetPx = 0;\n\tlet parkPx = 0;\n\tlet measured: Array<RiderLabel> = [];\n\n\treturn {\n\t\tmeasure: (labels) => {\n\t\t\tconst widths = measureWidths(control, className, labels);\n\n\t\t\tconst styles = getComputedStyle(control);\n\n\t\t\tfadeStart = readPxProperty(styles, fadeProperties[0], 0);\n\t\t\tfadeEnd = readPxProperty(styles, fadeProperties[1], 1);\n\t\t\tinsetPx = readPxProperty(styles, insetProperty, 0);\n\t\t\tparkPx = readPxProperty(styles, parkProperty, 0);\n\t\t\tmeasured = labels\n\t\t\t\t.map((label, index) => ({ ...label, widthPx: widths[index] ?? 0 }))\n\t\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\t},\n\t\tplace: (window) => {\n\t\t\tconst layout = layoutRider(measured, { ...window, fadeEnd, fadeStart, insetPx, parkPx });\n\n\t\t\twriteLabel(parked, [measured, colourProperty], layout.parked);\n\t\t\twriteLabel(arriving, [measured, colourProperty], layout.arriving);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
package/dist/styles/button.css
CHANGED
|
@@ -31,6 +31,13 @@
|
|
|
31
31
|
overflow: hidden;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
sonic-button:not(:defined, [hidden]):has(> :not(svg, .sonic-led)) {
|
|
35
|
+
inline-size: max-content;
|
|
36
|
+
min-inline-size: calc(var(--_sonic-button-size) * var(--_sonic-button-aspect-ratio));
|
|
37
|
+
padding-inline: calc(var(--_sonic-button-size) * var(--sonic-button-padding-ratio, 0.25));
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
.sonic-button {
|
|
35
42
|
--_sonic-button-gap-ratio: var(--sonic-button-gap-ratio, 0);
|
|
36
43
|
--_sonic-button-radius-ratio: var(--sonic-button-radius-ratio, 0.2);
|
|
@@ -39,8 +46,14 @@
|
|
|
39
46
|
--_sonic-button-icon-ratio: var(--sonic-button-icon-ratio, 0.5);
|
|
40
47
|
--_sonic-button-led-ratio: var(--sonic-button-led-ratio, 0.2);
|
|
41
48
|
--_sonic-button-led-icon-ratio: var(--sonic-button-led-icon-ratio, 0.35);
|
|
49
|
+
--_sonic-button-font-ratio: var(--sonic-button-font-ratio, 0.34);
|
|
50
|
+
--_sonic-button-padding-ratio: var(--sonic-button-padding-ratio, 0.25);
|
|
42
51
|
--_sonic-button-depth-ratio: var(--sonic-button-depth-ratio, calc(0.03 * var(--_sonic-relief)));
|
|
43
52
|
--_sonic-button-press-scale: var(--sonic-button-press-scale, 0.985);
|
|
53
|
+
--_sonic-button-latched-scale: var(
|
|
54
|
+
--sonic-button-latched-scale,
|
|
55
|
+
var(--_sonic-button-press-scale)
|
|
56
|
+
);
|
|
44
57
|
--_sonic-unit: calc(var(--_sonic-button-size) / 32);
|
|
45
58
|
--_sonic-button-corner: calc(var(--_sonic-button-size) * var(--_sonic-button-radius-ratio));
|
|
46
59
|
--_sonic-button-gap: calc(var(--_sonic-button-size) * var(--_sonic-button-gap-ratio));
|
|
@@ -61,6 +74,12 @@
|
|
|
61
74
|
transition: color 120ms ease-out;
|
|
62
75
|
}
|
|
63
76
|
|
|
77
|
+
/* An `auto` width stretches in a column flex or grid parent */
|
|
78
|
+
.sonic-button:where(:has(> .sonic-button-cap > :not(svg, .sonic-led))) {
|
|
79
|
+
inline-size: max-content;
|
|
80
|
+
min-inline-size: calc(var(--_sonic-button-size) * var(--_sonic-button-aspect-ratio));
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
:where([momentary]) > .sonic-button {
|
|
65
84
|
touch-action: none;
|
|
66
85
|
}
|
|
@@ -86,25 +105,17 @@
|
|
|
86
105
|
transition-delay: var(--sonic-button-busy-delay, 0s);
|
|
87
106
|
}
|
|
88
107
|
|
|
89
|
-
/* fallow-ignore-next-line code-duplication -- one pressed state across the cap's parts */
|
|
90
108
|
.sonic-button:is(
|
|
91
109
|
[aria-pressed='true'],
|
|
92
110
|
[aria-expanded='true'],
|
|
93
111
|
:where([momentary]:state(pressed)) > *
|
|
94
|
-
)
|
|
95
|
-
.sonic-button:is(:active, [data-sonic-pressed]):where(:not(:disabled, [aria-disabled='true'])) {
|
|
112
|
+
) {
|
|
96
113
|
--_sonic-keycap-shift: calc(var(--_sonic-keycap-depth) / 2);
|
|
97
|
-
--_sonic-keycap-scale: var(--_sonic-button-
|
|
114
|
+
--_sonic-keycap-scale: var(--_sonic-button-latched-scale);
|
|
98
115
|
--_sonic-keycap-drop: var(--_sonic-press-drop);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* fallow-ignore-next-line code-duplication -- one pressed state across the cap's parts */
|
|
102
|
-
.sonic-button:is(
|
|
103
|
-
[aria-pressed='true'],
|
|
104
|
-
[aria-expanded='true'],
|
|
105
|
-
:where([momentary]:state(pressed)) > *
|
|
106
|
-
) {
|
|
107
116
|
--_sonic-keycap-glow: var(--_sonic-keycap-lit-glow);
|
|
117
|
+
--_sonic-blank-edge-lit: currentcolor;
|
|
118
|
+
--_sonic-button-legend-glow: var(--_sonic-legend-glow);
|
|
108
119
|
|
|
109
120
|
color: var(--_sonic-lit);
|
|
110
121
|
background: var(
|
|
@@ -123,6 +134,28 @@
|
|
|
123
134
|
)
|
|
124
135
|
)
|
|
125
136
|
);
|
|
137
|
+
|
|
138
|
+
& .sonic-button-cap > [data-sonic-when='released'] {
|
|
139
|
+
display: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (forced-colors: active) {
|
|
143
|
+
--_sonic-skirt: var(--_sonic-lit);
|
|
144
|
+
--_sonic-face: var(--_sonic-lit);
|
|
145
|
+
--_sonic-ink: HighlightText;
|
|
146
|
+
|
|
147
|
+
&:is(:disabled, [aria-disabled='true']) {
|
|
148
|
+
--_sonic-ink: ButtonFace;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.sonic-button:is(:active, [data-sonic-pressed], :where([momentary]:state(pressed)) > *):where(
|
|
154
|
+
:not(:disabled, [aria-disabled='true'])
|
|
155
|
+
) {
|
|
156
|
+
--_sonic-keycap-shift: calc(var(--_sonic-keycap-depth) / 2);
|
|
157
|
+
--_sonic-keycap-scale: var(--_sonic-button-press-scale);
|
|
158
|
+
--_sonic-keycap-drop: var(--_sonic-press-drop);
|
|
126
159
|
}
|
|
127
160
|
|
|
128
161
|
.sonic-button:is(:disabled, [aria-disabled='true']) {
|
|
@@ -178,13 +211,9 @@
|
|
|
178
211
|
);
|
|
179
212
|
--_sonic-blank-edge: var(
|
|
180
213
|
--_sonic-blank-edge-off,
|
|
181
|
-
color-mix(in oklab, currentcolor 35%, transparent)
|
|
214
|
+
var(--_sonic-blank-edge-lit, color-mix(in oklab, currentcolor 35%, transparent))
|
|
182
215
|
);
|
|
183
216
|
|
|
184
|
-
&:is([aria-pressed='true'], [aria-expanded='true'], :where([momentary]:state(pressed)) > *) {
|
|
185
|
-
--_sonic-blank-edge: var(--_sonic-blank-edge-off, currentcolor);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
217
|
@media (forced-colors: active) {
|
|
189
218
|
--_sonic-blank-edge: transparent;
|
|
190
219
|
}
|
|
@@ -209,10 +238,20 @@
|
|
|
209
238
|
);
|
|
210
239
|
}
|
|
211
240
|
|
|
241
|
+
& > :not(.sonic-led) {
|
|
242
|
+
filter: var(--_sonic-engraving) var(--_sonic-button-legend-glow,);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
& > :not(svg, .sonic-led) {
|
|
246
|
+
padding-inline: calc(var(--_sonic-button-size) * var(--_sonic-button-padding-ratio));
|
|
247
|
+
font-size: calc(var(--_sonic-button-size) * var(--_sonic-button-font-ratio));
|
|
248
|
+
line-height: 1;
|
|
249
|
+
white-space: nowrap;
|
|
250
|
+
}
|
|
251
|
+
|
|
212
252
|
& > svg {
|
|
213
253
|
inline-size: calc(var(--_sonic-button-size) * var(--_sonic-button-icon-ratio));
|
|
214
254
|
block-size: calc(var(--_sonic-button-size) * var(--_sonic-button-icon-ratio));
|
|
215
|
-
filter: var(--_sonic-engraving);
|
|
216
255
|
mask-image: linear-gradient(
|
|
217
256
|
var(--_sonic-light-fall),
|
|
218
257
|
#000 15%,
|
|
@@ -222,30 +261,18 @@
|
|
|
222
261
|
}
|
|
223
262
|
}
|
|
224
263
|
|
|
264
|
+
.sonic-button-cap:has(> :not(svg, .sonic-led)) {
|
|
265
|
+
position: relative;
|
|
266
|
+
inset: auto;
|
|
267
|
+
block-size: calc(var(--_sonic-button-size) - 2 * var(--_sonic-button-gap));
|
|
268
|
+
margin: var(--_sonic-button-gap);
|
|
269
|
+
}
|
|
270
|
+
|
|
225
271
|
.sonic-button-cap:has(> .sonic-led) > svg {
|
|
226
272
|
inline-size: calc(var(--_sonic-button-size) * var(--_sonic-button-led-icon-ratio));
|
|
227
273
|
block-size: calc(var(--_sonic-button-size) * var(--_sonic-button-led-icon-ratio));
|
|
228
274
|
}
|
|
229
275
|
|
|
230
|
-
/* fallow-ignore-next-line code-duplication -- one pressed state across the cap's parts */
|
|
231
|
-
.sonic-button:is(
|
|
232
|
-
[aria-pressed='true'],
|
|
233
|
-
[aria-expanded='true'],
|
|
234
|
-
:where([momentary]:state(pressed)) > *
|
|
235
|
-
)
|
|
236
|
-
.sonic-button-cap
|
|
237
|
-
> svg {
|
|
238
|
-
filter: var(--_sonic-engraving) var(--_sonic-legend-glow);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/* fallow-ignore-next-line code-duplication -- one pressed state across the cap's parts */
|
|
242
|
-
.sonic-button:is(
|
|
243
|
-
[aria-pressed='true'],
|
|
244
|
-
[aria-expanded='true'],
|
|
245
|
-
:where([momentary]:state(pressed)) > *
|
|
246
|
-
)
|
|
247
|
-
.sonic-button-cap
|
|
248
|
-
> [data-sonic-when='released'],
|
|
249
276
|
.sonic-button:not(
|
|
250
277
|
[aria-pressed='true'],
|
|
251
278
|
[aria-expanded='true'],
|
|
@@ -274,28 +301,9 @@
|
|
|
274
301
|
}
|
|
275
302
|
|
|
276
303
|
@media (forced-colors: active) {
|
|
277
|
-
.sonic-button-cap >
|
|
304
|
+
.sonic-button-cap > :not(.sonic-led) {
|
|
278
305
|
filter: none;
|
|
279
306
|
mask: none;
|
|
280
307
|
}
|
|
281
|
-
|
|
282
|
-
/* fallow-ignore-next-line code-duplication -- one pressed state across the cap's parts */
|
|
283
|
-
.sonic-button:is(
|
|
284
|
-
[aria-pressed='true'],
|
|
285
|
-
[aria-expanded='true'],
|
|
286
|
-
:where([momentary]:state(pressed)) > *
|
|
287
|
-
) {
|
|
288
|
-
--_sonic-skirt: var(--_sonic-lit);
|
|
289
|
-
--_sonic-face: var(--_sonic-lit);
|
|
290
|
-
--_sonic-ink: HighlightText;
|
|
291
|
-
|
|
292
|
-
&:is(:disabled, [aria-disabled='true']) {
|
|
293
|
-
--_sonic-ink: ButtonFace;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
& .sonic-button-cap > svg {
|
|
297
|
-
filter: none;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
308
|
}
|
|
301
309
|
}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
--_sonic-cap: var(--sonic-cap, transparent);
|
|
15
15
|
--_sonic-cap-hover: var(--sonic-cap-hover, transparent);
|
|
16
16
|
--_sonic-focus: var(--sonic-focus, oklch(90% 0.23 128deg));
|
|
17
|
+
--_sonic-focus-strength: var(--sonic-focus-glow, 1);
|
|
18
|
+
--_sonic-disabled-opacity: var(--sonic-disabled-opacity, 1);
|
|
17
19
|
--_sonic-ink: var(--sonic-ink, currentcolor);
|
|
18
20
|
--_sonic-etch: color-mix(in oklab, var(--_sonic-ink) 30%, transparent);
|
|
19
21
|
--_sonic-ink-disabled: var(--sonic-ink-disabled, var(--_sonic-etch));
|
|
@@ -53,7 +55,7 @@
|
|
|
53
55
|
--_sonic-drop-shade: rgb(0 0 0 / calc(0.65 * var(--_sonic-relief)));
|
|
54
56
|
--_sonic-focus-shadow: 0 0 transparent;
|
|
55
57
|
--_sonic-focus-glow: 0 0 max(4px, 3 * var(--_sonic-unit)) max(1px, 0.5 * var(--_sonic-unit))
|
|
56
|
-
color-mix(in oklab, var(--_sonic-focus) 60
|
|
58
|
+
color-mix(in oklab, var(--_sonic-focus) calc(60% * var(--_sonic-focus-strength)), transparent);
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
:is(.sonic-number, .sonic-waveform, .sonic-wavestrip, .sonic-xy) {
|
|
@@ -77,6 +79,26 @@
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
@media not (forced-colors: active) {
|
|
83
|
+
:is(
|
|
84
|
+
:disabled
|
|
85
|
+
> :is(
|
|
86
|
+
.sonic-dial,
|
|
87
|
+
.sonic-number,
|
|
88
|
+
.sonic-slider,
|
|
89
|
+
.sonic-waveform,
|
|
90
|
+
.sonic-wavestrip,
|
|
91
|
+
.sonic-xy
|
|
92
|
+
),
|
|
93
|
+
.sonic-switch:not(:has(> .sonic-switch-position:not(:disabled))),
|
|
94
|
+
.sonic-button:is(:disabled, [aria-disabled='true']),
|
|
95
|
+
.sonic-toggle:not(:has(> .sonic-toggle-position:not(:disabled))),
|
|
96
|
+
.sonic-segmented-option:disabled
|
|
97
|
+
) {
|
|
98
|
+
opacity: var(--_sonic-disabled-opacity);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
@media (hover: hover) {
|
|
81
103
|
:is(.sonic-dial, .sonic-button, .sonic-segmented-option, .sonic-slider):not(
|
|
82
104
|
:disabled,
|
|
@@ -97,9 +119,11 @@
|
|
|
97
119
|
.sonic-xy-axis
|
|
98
120
|
):focus-visible {
|
|
99
121
|
outline: 2px solid transparent;
|
|
100
|
-
outline-offset: -2px;
|
|
122
|
+
outline-offset: var(--sonic-focus-outline-offset, -2px);
|
|
101
123
|
|
|
102
124
|
&:not([data-sonic-pointer-focus]) {
|
|
125
|
+
outline: var(--sonic-focus-outline, 2px solid transparent);
|
|
126
|
+
|
|
103
127
|
--_sonic-cap: var(--_sonic-cap-hover);
|
|
104
128
|
--_sonic-focus-shadow: var(--_sonic-focus-glow);
|
|
105
129
|
}
|
package/dist/styles/ring.css
CHANGED
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
calc(0.03 * var(--_sonic-relief))
|
|
34
34
|
);
|
|
35
35
|
--_sonic-segmented-press-scale: var(--sonic-segmented-press-scale, 0.985);
|
|
36
|
+
--_sonic-segmented-latched-scale: var(
|
|
37
|
+
--sonic-segmented-latched-scale,
|
|
38
|
+
var(--_sonic-segmented-press-scale)
|
|
39
|
+
);
|
|
36
40
|
--_sonic-segmented-font-ratio: var(--sonic-segmented-font-ratio, 0.34);
|
|
41
|
+
--_sonic-segmented-padding-ratio: var(--sonic-segmented-padding-ratio, 0.25);
|
|
37
42
|
--_sonic-segmented-icon-ratio: var(--sonic-segmented-icon-ratio, 0.5);
|
|
38
43
|
--_sonic-unit: calc(var(--_sonic-segmented-size) / 32);
|
|
39
44
|
--_sonic-segmented-corner: calc(
|
|
@@ -83,10 +88,14 @@
|
|
|
83
88
|
.sonic-segmented-option:where([data-sonic-pressed]):not(:disabled),
|
|
84
89
|
.sonic-segmented-option[aria-checked='true'] {
|
|
85
90
|
--_sonic-keycap-shift: calc(var(--_sonic-keycap-depth) / 2);
|
|
86
|
-
--_sonic-keycap-scale: var(--_sonic-segmented-
|
|
91
|
+
--_sonic-keycap-scale: var(--_sonic-segmented-latched-scale);
|
|
87
92
|
--_sonic-keycap-drop: var(--_sonic-press-drop);
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
.sonic-segmented-option:where([data-sonic-pressed]):not(:disabled) {
|
|
96
|
+
--_sonic-keycap-scale: var(--_sonic-segmented-press-scale);
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
.sonic-segmented-option[aria-checked='true'] {
|
|
91
100
|
--_sonic-keycap-glow: var(--_sonic-keycap-lit-glow);
|
|
92
101
|
|
|
@@ -143,7 +152,7 @@
|
|
|
143
152
|
);
|
|
144
153
|
|
|
145
154
|
position: relative;
|
|
146
|
-
padding-inline: calc(var(--_sonic-segmented-size) *
|
|
155
|
+
padding-inline: calc(var(--_sonic-segmented-size) * var(--_sonic-segmented-padding-ratio));
|
|
147
156
|
|
|
148
157
|
& > * {
|
|
149
158
|
filter: var(--_sonic-engraving);
|
package/dist/styles/switch.css
CHANGED
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
&:focus-visible {
|
|
101
|
-
outline: 2px solid transparent;
|
|
102
|
-
outline-offset: -2px;
|
|
101
|
+
outline: var(--sonic-focus-outline, 2px solid transparent);
|
|
102
|
+
outline-offset: var(--sonic-focus-outline-offset, -2px);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
&:has([data-sonic-momentary]) {
|
package/dist/styles/toggle.css
CHANGED
|
@@ -252,8 +252,8 @@
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
.sonic-toggle-position:focus-visible {
|
|
255
|
-
outline: 2px solid transparent;
|
|
256
|
-
outline-offset: -2px;
|
|
255
|
+
outline: var(--sonic-focus-outline, 2px solid transparent);
|
|
256
|
+
outline-offset: var(--sonic-focus-outline-offset, -2px);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
.sonic-toggle-position:disabled {
|
package/dist/styles/waveform.css
CHANGED
|
@@ -54,10 +54,16 @@
|
|
|
54
54
|
initial-value: rgb(96 96 104);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
@property --_sonic-waveform-label-fade {
|
|
57
|
+
@property --_sonic-waveform-label-fade-start {
|
|
58
58
|
syntax: '<number>';
|
|
59
59
|
inherits: true;
|
|
60
|
-
initial-value: 0
|
|
60
|
+
initial-value: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@property --_sonic-waveform-label-fade-end {
|
|
64
|
+
syntax: '<number>';
|
|
65
|
+
inherits: true;
|
|
66
|
+
initial-value: 1;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
@property --_sonic-waveform-marker {
|
|
@@ -105,7 +111,8 @@
|
|
|
105
111
|
--_sonic-waveform-placeholder: var(--sonic-waveform-placeholder, var(--_sonic-etch));
|
|
106
112
|
--_sonic-waveform-marker: var(--sonic-marker, var(--_sonic-ink));
|
|
107
113
|
--_sonic-waveform-label: var(--sonic-waveform-label, var(--_sonic-waveform-marker));
|
|
108
|
-
--_sonic-waveform-label-fade: var(--sonic-waveform-label-fade-
|
|
114
|
+
--_sonic-waveform-label-fade-start: var(--sonic-waveform-label-fade-start, 0);
|
|
115
|
+
--_sonic-waveform-label-fade-end: var(--sonic-waveform-label-fade-end, 1);
|
|
109
116
|
--_sonic-waveform-label-inset: calc(
|
|
110
117
|
var(--_sonic-waveform-size) * var(--sonic-waveform-label-inset-ratio, 0.04)
|
|
111
118
|
);
|