@xsynaptic/sonic-ui 0.25.1 → 0.27.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 +21 -0
- package/README.md +1 -1
- package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
- package/dist/default/elements/button.js +12 -6
- package/dist/default/elements/dial.js +9 -10
- package/dist/default/elements/envelope.js +26 -20
- package/dist/default/elements/field-gesture.js +49 -8
- package/dist/default/elements/meter.js +6 -5
- package/dist/default/elements/number.js +5 -11
- package/dist/default/elements/position-group.js +4 -4
- package/dist/default/elements/radio-group.js +7 -7
- package/dist/default/elements/region-layer.js +3 -1
- package/dist/default/elements/region.js +5 -1
- package/dist/default/elements/segmented.js +7 -4
- package/dist/default/elements/slider.js +12 -10
- package/dist/default/elements/sonic-element.js +10 -13
- package/dist/default/elements/spectrum.js +3 -3
- package/dist/default/elements/switch.js +11 -6
- package/dist/default/elements/toggle.js +16 -10
- package/dist/default/elements/value-element.js +26 -4
- package/dist/default/elements/wave-element.js +11 -9
- package/dist/default/elements/waveform.js +1 -4
- package/dist/default/elements/wavestrip.js +1 -1
- package/dist/default/elements/xy.js +34 -44
- package/dist/default/lib/canvas-surface.js +35 -2
- package/dist/default/lib/key-press.js +2 -2
- package/dist/default/lib/render.js +2 -2
- package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
- package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
- package/dist/dev/elements/analyser-element.js.map +1 -0
- package/dist/dev/elements/button.d.ts +4 -0
- package/dist/dev/elements/button.js +12 -11
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +4 -1
- package/dist/dev/elements/dial.js +9 -14
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +5 -0
- package/dist/dev/elements/envelope.js +26 -24
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +49 -8
- package/dist/dev/elements/field-gesture.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +2 -0
- package/dist/dev/elements/meter.js +6 -6
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +2 -1
- package/dist/dev/elements/number.js +5 -12
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/position-group.js +4 -4
- package/dist/dev/elements/position-group.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +1 -1
- package/dist/dev/elements/radio-group.js +7 -7
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/region-layer.js +3 -1
- package/dist/dev/elements/region-layer.js.map +1 -1
- package/dist/dev/elements/region.d.ts +1 -0
- package/dist/dev/elements/region.js +5 -1
- package/dist/dev/elements/region.js.map +1 -1
- package/dist/dev/elements/segmented.d.ts +4 -2
- package/dist/dev/elements/segmented.js +7 -9
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +4 -1
- package/dist/dev/elements/slider.js +12 -14
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +6 -7
- package/dist/dev/elements/sonic-element.js +16 -91
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +2 -2
- package/dist/dev/elements/spectrum.js +3 -3
- package/dist/dev/elements/spectrum.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +4 -1
- package/dist/dev/elements/switch.js +11 -10
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.d.ts +4 -1
- package/dist/dev/elements/toggle.js +16 -15
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +4 -3
- package/dist/dev/elements/value-element.js +26 -4
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +2 -1
- package/dist/dev/elements/wave-element.js +11 -13
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +0 -1
- package/dist/dev/elements/waveform.js +1 -4
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.js +1 -1
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +4 -0
- package/dist/dev/elements/xy.js +34 -48
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -1
- package/dist/dev/lib/canvas-surface.js +35 -2
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/check-styles.d.ts +8 -0
- package/dist/dev/lib/check-styles.js +87 -0
- package/dist/dev/lib/check-styles.js.map +1 -0
- package/dist/dev/lib/key-press.js +2 -2
- package/dist/dev/lib/key-press.js.map +1 -1
- package/dist/dev/lib/render.js +2 -2
- package/dist/dev/lib/render.js.map +1 -1
- package/dist/styles/button.css +11 -9
- package/dist/styles/dial.css +6 -6
- package/dist/styles/envelope.css +6 -0
- package/dist/styles/led.css +8 -8
- package/dist/styles/material/cap.css +13 -2
- package/dist/styles/material/pane.css +7 -1
- package/dist/styles/material/readout.css +10 -2
- package/dist/styles/material/reticle.css +2 -2
- package/dist/styles/meter.css +11 -11
- package/dist/styles/number.css +1 -1
- package/dist/styles/region.css +6 -0
- package/dist/styles/segmented.css +3 -3
- package/dist/styles/slider.css +12 -12
- package/dist/styles/switch.css +6 -0
- package/dist/styles/toggle.css +3 -3
- package/dist/styles/waveform.css +7 -1
- package/dist/styles/wavestrip.css +6 -0
- package/dist/styles/xy.css +15 -3
- package/package.json +1 -1
- package/dist/dev/elements/display-element.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SonicValueElement } from "./value-element.js";
|
|
2
|
-
import {
|
|
2
|
+
import { bindControlSurface } from "../lib/canvas-surface.js";
|
|
3
3
|
import { formatClock, parseClock } from "../lib/clock.js";
|
|
4
4
|
import { spokenDuration } from "../lib/spoken-duration.js";
|
|
5
5
|
|
|
@@ -32,6 +32,10 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
32
32
|
this.renderMarkers();
|
|
33
33
|
this.render();
|
|
34
34
|
}
|
|
35
|
+
boxProbe = {
|
|
36
|
+
property: "margin-bottom",
|
|
37
|
+
selector: "[popover]"
|
|
38
|
+
};
|
|
35
39
|
notation = {
|
|
36
40
|
format: formatClock,
|
|
37
41
|
parse: parseClock,
|
|
@@ -65,15 +69,13 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
65
69
|
}
|
|
66
70
|
connect(signal) {
|
|
67
71
|
const control = this.control;
|
|
68
|
-
this.keepControl(
|
|
72
|
+
this.keepControl(signal);
|
|
69
73
|
this.renderEmpty();
|
|
70
|
-
this.#surface =
|
|
74
|
+
this.#surface = bindControlSurface({
|
|
71
75
|
canvas: this.canvas,
|
|
72
76
|
colours: this.colours,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
sizeProperty: this.sizeProperty
|
|
76
|
-
},
|
|
77
|
+
control,
|
|
78
|
+
fill: this.fill,
|
|
77
79
|
lengths: this.lengths,
|
|
78
80
|
numbers: this.numbers,
|
|
79
81
|
paint: (context, frame) => {
|
|
@@ -82,14 +84,10 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
82
84
|
resize: () => {
|
|
83
85
|
this.renderMarkers();
|
|
84
86
|
},
|
|
85
|
-
signal
|
|
87
|
+
signal,
|
|
88
|
+
sizeProperty: this.sizeProperty
|
|
86
89
|
});
|
|
87
90
|
this.render();
|
|
88
|
-
this.checkStyles(control, this.sheet, {
|
|
89
|
-
property: "margin-bottom",
|
|
90
|
-
selector: "[popover]"
|
|
91
|
-
});
|
|
92
|
-
this.#surface.setFill(this.fill);
|
|
93
91
|
}
|
|
94
92
|
markerList() {
|
|
95
93
|
return this.#markers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport {
|
|
1
|
+
{"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { BoxProbe } from '#lib/check-styles.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindControlSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.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\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\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\tNumeric 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 override readonly boxProbe: BoxProbe = {\n\t\tproperty: 'margin-bottom',\n\t\tselector: '[popover]',\n\t};\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract override 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\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\tabstract clientXOf(value: Seconds): number;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** Copies; none outside the canvas */\n\tmarkersFromPoint(clientX: number, clientY: number): Array<WaveMarker> {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\t\tconst isInside =\n\t\t\tclientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom;\n\t\tif (!isInside) return [];\n\n\t\treturn this.markersInReach({ x: clientX - box.left, y: clientY - box.top }, box.width).flatMap(\n\t\t\t(index) => {\n\t\t\t\tconst marker = this.#markers[index];\n\n\t\t\t\treturn marker ? [{ ...marker }] : [];\n\t\t\t},\n\t\t);\n\t}\n\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\t/** The time at a viewport x, clamped to the bounds and not snapped to the step */\n\tabstract valueFromPoint(clientX: number, clientY: number): Seconds;\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(signal);\n\t\tthis.renderEmpty();\n\t\tthis.#surface = bindControlSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tcontrol,\n\t\t\tfill: this.fill,\n\t\t\tlengths: this.lengths,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t\tsizeProperty: this.sizeProperty,\n\t\t});\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 markersInReach(point: Point, widthPx: number): Array<number>;\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length, Numeric>,\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 override scrubChanged(): void {\n\t\tthis.render();\n\t}\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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"}
|
|
@@ -82,7 +82,6 @@ export declare class SonicWaveform extends SonicWaveElement<Colour, never, Numer
|
|
|
82
82
|
valueFromPoint(clientX: number, _clientY: number): Seconds;
|
|
83
83
|
protected connect(signal: AbortSignal): void;
|
|
84
84
|
protected draw(): void;
|
|
85
|
-
protected focusTarget(): HTMLElement;
|
|
86
85
|
protected isEmpty(): boolean;
|
|
87
86
|
protected markersInReach({ x }: Point, widthPx: number): Array<number>;
|
|
88
87
|
protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>): void;
|
|
@@ -231,7 +231,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
231
231
|
}
|
|
232
232
|
};
|
|
233
233
|
this.#zoomGesture = bindZoom(this.control, zoomTarget, signal);
|
|
234
|
-
this.bindGestures(
|
|
234
|
+
this.bindGestures(signal, () => this.#grab());
|
|
235
235
|
bindZoomKeys(this.control, zoomTarget, signal);
|
|
236
236
|
if (!("fonts" in document)) return;
|
|
237
237
|
document.fonts.addEventListener("loadingdone", () => {
|
|
@@ -243,9 +243,6 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
243
243
|
surface?.requestFrame();
|
|
244
244
|
if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
|
|
245
245
|
}
|
|
246
|
-
focusTarget() {
|
|
247
|
-
return this.control;
|
|
248
|
-
}
|
|
249
246
|
isEmpty() {
|
|
250
247
|
return !this.#peaks || this.#peaks.samples.length === 0;
|
|
251
248
|
}
|
|
@@ -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 { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Drawn } from '#lib/frame-timeline.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { PeaksRequest } from '#lib/peaks-request.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Milliseconds, PixelsPerSecond, Seconds } from '#lib/units.ts';\nimport type { BandChannels, WaveformBands } from '#lib/waveform-bands.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\nimport type { WaveformTint } from '#lib/waveform-scene.ts';\nimport type { ZoomGesture } from '#lib/zoom-gesture.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 { createPeaksRequest } from '#lib/peaks-request.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { bandTable } from '#lib/waveform-bands.ts';\nimport {\n\tbandChannels,\n\tpaintWaveform,\n\twaveformColours,\n\twaveformNumbers,\n} from '#lib/waveform-scene.ts';\nimport { bindZoom, bindZoomKeys } from '#lib/zoom-gesture.ts';\n\nexport type { PeaksRequest } from '#lib/peaks-request.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 waveformColours;\n\ntype Numeric = keyof typeof waveformNumbers;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\nconst lineReachPx = 4;\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\nexport class SonicWaveform extends SonicWaveElement<Colour, never, Numeric> {\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 bands(): undefined | WaveformBands {\n\t\treturn this.#bands;\n\t}\n\n\tset bands(bands: undefined | WaveformBands) {\n\t\tthis.#bands = bands;\n\t\tthis.#bandTable = bandTable(bands);\n\t\tthis.#peaksRequest.changed();\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.#peaksRequest.changed();\n\t}\n\n\t/** Spans being fetched; drawn as placeholders until cleared */\n\tget pending(): Array<[Seconds, Seconds]> {\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.#peaksRequest.changed();\n\t}\n\n\t/** How long a span stays listed in `pending` before its placeholder draws */\n\tget pendingDelay(): Milliseconds {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: Milliseconds | 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\t/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */\n\tget readTime(): (() => Seconds | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => Seconds | 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.#peaksRequest.reset();\n\t}\n\n\tget zoom(): PixelsPerSecond {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: PixelsPerSecond | 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(): PixelsPerSecond {\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: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): PixelsPerSecond {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: PixelsPerSecond | 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 = waveformColours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly numbers = waveformNumbers;\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\t#bandColours: Array<BandChannels | undefined> = [];\n\n\t#bands: undefined | WaveformBands;\n\n\t#bandTable: ReturnType<typeof bandTable>;\n\n\treadonly #clock = createTrackingClock();\n\n\t#drawn: (Drawn & { zoom: PixelsPerSecond }) | undefined;\n\n\treadonly #ghost = requireChild(this.control, '.sonic-waveform-ghost', HTMLDivElement);\n\n\t#grabbed: Drawn | undefined;\n\n\t#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\treadonly #peaksRequest = createPeaksRequest(() => {\n\t\tthis.surface()?.invalidate();\n\t});\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\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\t#strip: CanvasRenderingContext2D | undefined;\n\n\t#zoomGesture: undefined | ZoomGesture;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#zoomGesture?.sync();\n\t}\n\n\t/** Viewport x of a time as last drawn; not clamped to the window */\n\tclientXOf(value: Seconds): number {\n\t\tconst { left, width } = this.canvas.getBoundingClientRect();\n\n\t\treturn left + this.#drawnX(value, width);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'bands',\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\t/** The time at a viewport x as last drawn */\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { left, width } = this.canvas.getBoundingClientRect();\n\t\tconst { startSeconds, zoom } = this.#drawnWindow(width);\n\n\t\treturn clamp(startSeconds + (clientX - left) / zoom, ...this.mapping().bounds);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\n\t\tconst zoomTarget = {\n\t\t\tclaim: () => {\n\t\t\t\tthis.abandonHold();\n\t\t\t},\n\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\tisZoomable: () => this.zoomable,\n\t\t\tzoom: () => this.zoom,\n\t\t\tzoomTo: (target: number) => {\n\t\t\t\tthis.#zoomTo(target);\n\t\t\t},\n\t\t};\n\n\t\tthis.#zoomGesture = bindZoom(this.control, zoomTarget, signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\t// After the value keys, which stand down once a key is taken\n\t\tbindZoomKeys(this.control, zoomTarget, 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 override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.samples.length === 0;\n\t}\n\n\t// The canvas paints a region as its start line alone\n\tprotected markersInReach({ x }: Point, widthPx: number): Array<number> {\n\t\treturn this.markerList()\n\t\t\t.map(({ start }, index) => ({\n\t\t\t\tdistance: Math.abs(this.#drawnX(start, widthPx) - x),\n\t\t\t\tindex,\n\t\t\t}))\n\t\t\t.filter(({ distance }) => distance <= lineReachPx)\n\t\t\t.toSorted((first, second) => first.distance - second.distance)\n\t\t\t.map(({ index }) => index);\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>,\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 range: [number, number] = [this.min, Math.max(this.min, this.max)];\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,\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, zoom: this.zoom };\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\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\t\t\tpaintWaveform(\n\t\t\t\tcontext,\n\t\t\t\t{ ...view, colours: look.colours, numbers: look.numbers },\n\t\t\t\t{\n\t\t\t\t\tbands: this.#tint(isRebuilt),\n\t\t\t\t\tkindColours: this.#kindColours,\n\t\t\t\t\tmarkers: this.markerList().map((marker) => ({\n\t\t\t\t\t\t...marker,\n\t\t\t\t\t\tstart: clamp(marker.start, ...range),\n\t\t\t\t\t})),\n\t\t\t\t\tpeaks: this.#peaks,\n\t\t\t\t\trange,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.#peaksRequest.ask(this.#requestPeaks, 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\tprotected override scrubChanged(): void {\n\t\tconst isScrubbing = this.scrubState().from !== undefined;\n\n\t\tthis.#grabbed = isScrubbing ? (this.#grabbed ?? this.#lastDrawn()) : undefined;\n\t\tsuper.scrubChanged();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t// Before the first frame, the window a centerd playhead would draw\n\t#drawnWindow(widthPx: number): { startSeconds: Seconds; zoom: PixelsPerSecond } {\n\t\tconst zoom = this.#drawn?.zoom ?? this.zoom;\n\n\t\treturn { startSeconds: this.#drawn?.startSeconds ?? this.value - widthPx / zoom / 2, zoom };\n\t}\n\n\t#drawnX(value: Seconds, widthPx: number): number {\n\t\tconst { startSeconds, zoom } = this.#drawnWindow(widthPx);\n\n\t\treturn (clamp(value, ...this.mapping().bounds) - startSeconds) * zoom;\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.#zoomGesture?.isPinching() === true) return undefined;\n\n\t\tconst drawn = this.#lastDrawn();\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.scrubState();\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#lastDrawn(): Drawn {\n\t\treturn this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\t}\n\n\t#readBandColours(\n\t\tstrip: CanvasRenderingContext2D,\n\t\tbandCount: number,\n\t): Array<BandChannels | undefined> {\n\t\tconst probes = Array.from({ length: bandCount }, (_probe, band) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.style.setProperty(\n\t\t\t\t'color',\n\t\t\t\t`var(--sonic-waveform-band-${String(band + 1)}, transparent)`,\n\t\t\t);\n\n\t\t\treturn probe;\n\t\t});\n\n\t\treturn this.#readProbes(probes).map((colour) => bandChannels(strip, colour));\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\tconst read = this.#readProbes(probes);\n\n\t\treturn new Map(kinds.map((kind, index) => [kind, read[index] ?? '']));\n\t}\n\n\t#readProbes(probes: Array<HTMLElement>): Array<string> {\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 read;\n\t}\n\n\t#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.scrubState().played;\n\t}\n\n\t#tint(isRebuilt: boolean): undefined | WaveformTint {\n\t\tconst table = this.#bandTable;\n\t\tif (!table) return undefined;\n\n\t\tconst strip =\n\t\t\tthis.#strip ??\n\t\t\tdocument.createElement('canvas').getContext('2d', { willReadFrequently: true }) ??\n\t\t\tundefined;\n\t\tif (!strip) return undefined;\n\n\t\tthis.#strip = strip;\n\n\t\tif (isRebuilt || this.#bandColours.length !== table.bandCount) {\n\t\t\tstrip.canvas.height = 1;\n\t\t\tthis.#bandColours = this.#readBandColours(strip, table.bandCount);\n\t\t}\n\n\t\treturn { colours: this.#bandColours, strip, table };\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 } from '#lib/canvas-surface.ts';\nimport type { Drawn } from '#lib/frame-timeline.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { PeaksRequest } from '#lib/peaks-request.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Milliseconds, PixelsPerSecond, Seconds } from '#lib/units.ts';\nimport type { BandChannels, WaveformBands } from '#lib/waveform-bands.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\nimport type { WaveformTint } from '#lib/waveform-scene.ts';\nimport type { ZoomGesture } from '#lib/zoom-gesture.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 { createPeaksRequest } from '#lib/peaks-request.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { bandTable } from '#lib/waveform-bands.ts';\nimport {\n\tbandChannels,\n\tpaintWaveform,\n\twaveformColours,\n\twaveformNumbers,\n} from '#lib/waveform-scene.ts';\nimport { bindZoom, bindZoomKeys } from '#lib/zoom-gesture.ts';\n\nexport type { PeaksRequest } from '#lib/peaks-request.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 waveformColours;\n\ntype Numeric = keyof typeof waveformNumbers;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\nconst lineReachPx = 4;\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\nexport class SonicWaveform extends SonicWaveElement<Colour, never, Numeric> {\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 bands(): undefined | WaveformBands {\n\t\treturn this.#bands;\n\t}\n\n\tset bands(bands: undefined | WaveformBands) {\n\t\tthis.#bands = bands;\n\t\tthis.#bandTable = bandTable(bands);\n\t\tthis.#peaksRequest.changed();\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.#peaksRequest.changed();\n\t}\n\n\t/** Spans being fetched; drawn as placeholders until cleared */\n\tget pending(): Array<[Seconds, Seconds]> {\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.#peaksRequest.changed();\n\t}\n\n\t/** How long a span stays listed in `pending` before its placeholder draws */\n\tget pendingDelay(): Milliseconds {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: Milliseconds | 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\t/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */\n\tget readTime(): (() => Seconds | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => Seconds | 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.#peaksRequest.reset();\n\t}\n\n\tget zoom(): PixelsPerSecond {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: PixelsPerSecond | 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(): PixelsPerSecond {\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: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): PixelsPerSecond {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected override 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 = waveformColours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly numbers = waveformNumbers;\n\n\tprotected override readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\t#bandColours: Array<BandChannels | undefined> = [];\n\n\t#bands: undefined | WaveformBands;\n\n\t#bandTable: ReturnType<typeof bandTable>;\n\n\treadonly #clock = createTrackingClock();\n\n\t#drawn: (Drawn & { zoom: PixelsPerSecond }) | undefined;\n\n\treadonly #ghost = requireChild(this.control, '.sonic-waveform-ghost', HTMLDivElement);\n\n\t#grabbed: Drawn | undefined;\n\n\t#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\treadonly #peaksRequest = createPeaksRequest(() => {\n\t\tthis.surface()?.invalidate();\n\t});\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\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\t#strip: CanvasRenderingContext2D | undefined;\n\n\t#zoomGesture: undefined | ZoomGesture;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#zoomGesture?.sync();\n\t}\n\n\t/** Viewport x of a time as last drawn; not clamped to the window */\n\tclientXOf(value: Seconds): number {\n\t\tconst { left, width } = this.canvas.getBoundingClientRect();\n\n\t\treturn left + this.#drawnX(value, width);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'bands',\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\t/** The time at a viewport x as last drawn */\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { left, width } = this.canvas.getBoundingClientRect();\n\t\tconst { startSeconds, zoom } = this.#drawnWindow(width);\n\n\t\treturn clamp(startSeconds + (clientX - left) / zoom, ...this.mapping().bounds);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\n\t\tconst zoomTarget = {\n\t\t\tclaim: () => {\n\t\t\t\tthis.abandonHold();\n\t\t\t},\n\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\tisZoomable: () => this.zoomable,\n\t\t\tzoom: () => this.zoom,\n\t\t\tzoomTo: (target: number) => {\n\t\t\t\tthis.#zoomTo(target);\n\t\t\t},\n\t\t};\n\n\t\tthis.#zoomGesture = bindZoom(this.control, zoomTarget, signal);\n\t\tthis.bindGestures(signal, () => this.#grab());\n\t\t// After the value keys, which stand down once a key is taken\n\t\tbindZoomKeys(this.control, zoomTarget, 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\t// The canvas paints a region as its start line alone\n\tprotected markersInReach({ x }: Point, widthPx: number): Array<number> {\n\t\treturn this.markerList()\n\t\t\t.map(({ start }, index) => ({\n\t\t\t\tdistance: Math.abs(this.#drawnX(start, widthPx) - x),\n\t\t\t\tindex,\n\t\t\t}))\n\t\t\t.filter(({ distance }) => distance <= lineReachPx)\n\t\t\t.toSorted((first, second) => first.distance - second.distance)\n\t\t\t.map(({ index }) => index);\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>,\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 range: [number, number] = [this.min, Math.max(this.min, this.max)];\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,\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, zoom: this.zoom };\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\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\t\t\tpaintWaveform(\n\t\t\t\tcontext,\n\t\t\t\t{ ...view, colours: look.colours, numbers: look.numbers },\n\t\t\t\t{\n\t\t\t\t\tbands: this.#tint(isRebuilt),\n\t\t\t\t\tkindColours: this.#kindColours,\n\t\t\t\t\tmarkers: this.markerList().map((marker) => ({\n\t\t\t\t\t\t...marker,\n\t\t\t\t\t\tstart: clamp(marker.start, ...range),\n\t\t\t\t\t})),\n\t\t\t\t\tpeaks: this.#peaks,\n\t\t\t\t\trange,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.#peaksRequest.ask(this.#requestPeaks, 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\tprotected override scrubChanged(): void {\n\t\tconst isScrubbing = this.scrubState().from !== undefined;\n\n\t\tthis.#grabbed = isScrubbing ? (this.#grabbed ?? this.#lastDrawn()) : undefined;\n\t\tsuper.scrubChanged();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t// Before the first frame, the window a centerd playhead would draw\n\t#drawnWindow(widthPx: number): { startSeconds: Seconds; zoom: PixelsPerSecond } {\n\t\tconst zoom = this.#drawn?.zoom ?? this.zoom;\n\n\t\treturn { startSeconds: this.#drawn?.startSeconds ?? this.value - widthPx / zoom / 2, zoom };\n\t}\n\n\t#drawnX(value: Seconds, widthPx: number): number {\n\t\tconst { startSeconds, zoom } = this.#drawnWindow(widthPx);\n\n\t\treturn (clamp(value, ...this.mapping().bounds) - startSeconds) * zoom;\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.#zoomGesture?.isPinching() === true) return undefined;\n\n\t\tconst drawn = this.#lastDrawn();\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.scrubState();\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#lastDrawn(): Drawn {\n\t\treturn this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\t}\n\n\t#readBandColours(\n\t\tstrip: CanvasRenderingContext2D,\n\t\tbandCount: number,\n\t): Array<BandChannels | undefined> {\n\t\tconst probes = Array.from({ length: bandCount }, (_probe, band) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.style.setProperty(\n\t\t\t\t'color',\n\t\t\t\t`var(--sonic-waveform-band-${String(band + 1)}, transparent)`,\n\t\t\t);\n\n\t\t\treturn probe;\n\t\t});\n\n\t\treturn this.#readProbes(probes).map((colour) => bandChannels(strip, colour));\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\tconst read = this.#readProbes(probes);\n\n\t\treturn new Map(kinds.map((kind, index) => [kind, read[index] ?? '']));\n\t}\n\n\t#readProbes(probes: Array<HTMLElement>): Array<string> {\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 read;\n\t}\n\n\t#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.scrubState().played;\n\t}\n\n\t#tint(isRebuilt: boolean): undefined | WaveformTint {\n\t\tconst table = this.#bandTable;\n\t\tif (!table) return undefined;\n\n\t\tconst strip =\n\t\t\tthis.#strip ??\n\t\t\tdocument.createElement('canvas').getContext('2d', { willReadFrequently: true }) ??\n\t\t\tundefined;\n\t\tif (!strip) return undefined;\n\n\t\tthis.#strip = strip;\n\n\t\tif (isRebuilt || this.#bandColours.length !== table.bandCount) {\n\t\t\tstrip.canvas.height = 1;\n\t\t\tthis.#bandColours = this.#readBandColours(strip, table.bandCount);\n\t\t}\n\n\t\treturn { colours: this.#bandColours, strip, table };\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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"}
|
|
@@ -157,7 +157,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
157
157
|
},
|
|
158
158
|
valueAt: (event) => this.#markerStartAt(event, this.#axis()) ?? this.mapping().snap(this.valueFromPoint(event.clientX, event.clientY))
|
|
159
159
|
});
|
|
160
|
-
this.bindGestures(
|
|
160
|
+
this.bindGestures(signal, (event) => this.#grab(event));
|
|
161
161
|
}
|
|
162
162
|
draw() {
|
|
163
163
|
const surface = this.surface();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { RegionLayer } from '#elements/region-layer.ts';\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { createMarkerBand } from '#lib/marker-band.ts';\nimport { clamp, clampProportion } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { stripBars, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nexport type MarkerRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\tgrooveSize: '--_sonic-wavestrip-groove-size',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" role=\"slider\" tabindex=\"0\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-regions\"></div>\n\t\t\t<div class=\"sonic-wavestrip-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-wavestrip-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 fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\t/** Loaded spans, in seconds */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\t/** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */\n\tget formatSpokenRegion(): ((start: Seconds, end: Seconds) => string) | undefined {\n\t\treturn this.#formatSpokenRegion;\n\t}\n\n\tset formatSpokenRegion(format: ((start: Seconds, end: Seconds) => string) | undefined) {\n\t\tthis.#formatSpokenRegion = format;\n\t\tthis.#regions?.render();\n\t}\n\n\t/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\t/** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */\n\tget preview(): Seconds | undefined {\n\t\treturn this.#preview;\n\t}\n\n\tset preview(seconds: null | Seconds | undefined) {\n\t\tthis.#preview = Number.isFinite(seconds) ? (seconds ?? undefined) : undefined;\n\t\tthis.render();\n\t}\n\n\t/** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */\n\tget renderMarker(): MarkerRender | undefined {\n\t\treturn this.#renderMarker;\n\t}\n\n\tset renderMarker(render: MarkerRender | undefined) {\n\t\tthis.#renderMarker = render;\n\t\tthis.#drawMarker =\n\t\t\trender &&\n\t\t\t((marker, element) => {\n\t\t\t\trender({ ...marker }, element);\n\t\t\t});\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tprotected readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly numbers = {};\n\n\tprotected readonly sheet = 'wavestrip.css';\n\n\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\t#drawMarker: MarkerRender | undefined;\n\n\t#formatSpokenRegion: ((start: Seconds, end: Seconds) => string) | undefined;\n\n\treadonly #markerBand = createMarkerBand<WaveMarker>(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\t#preview: Seconds | undefined;\n\n\t#regions: RegionLayer | undefined;\n\n\t#renderMarker: MarkerRender | undefined;\n\n\t/** Viewport x of a time, clamped to the strip */\n\tclientXOf(value: Seconds): number {\n\t\tconst { startPx, travelPx } = this.#axis();\n\n\t\treturn startPx + this.mapping().proportionOf(value) * travelPx;\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'cancellable',\n\t\t\t'formatSpokenRegion',\n\t\t\t'peaks',\n\t\t\t'preview',\n\t\t\t'renderMarker',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { startPx, travelPx } = this.#axis();\n\t\tconst mapping = this.mapping();\n\n\t\treturn clamp(\n\t\t\tmapping.valueAt(clampProportion((clientX - startPx) / travelPx)),\n\t\t\t...mapping.bounds,\n\t\t);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst { control } = this;\n\n\t\tthis.#regions = new RegionLayer(\n\t\t\t{\n\t\t\t\taxis: () => this.#axis(),\n\t\t\t\tcontrol,\n\t\t\t\telement: this,\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tlayer: requireChild(control, '.sonic-wavestrip-regions', HTMLDivElement),\n\t\t\t\tmapping: () => this.mapping(),\n\t\t\t\toutside: () => this.#outside(),\n\t\t\t\tseek: (event) => {\n\t\t\t\t\tif (this.input(this.valueFromPoint(event.clientX, event.clientY))) {\n\t\t\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tspeak: ({ end, start }) =>\n\t\t\t\t\tthis.#formatSpokenRegion?.(start, end) ??\n\t\t\t\t\tthis.spokenText(start) ??\n\t\t\t\t\tthis.notation.format(start),\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsuper.connect(signal);\n\t\tthis.bindHover(control, signal, {\n\t\t\tchanged: () => {\n\t\t\t\tif (this.#preview !== undefined) this.render();\n\t\t\t},\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\tvalueAt: (event) =>\n\t\t\t\tthis.#markerStartAt(event, this.#axis()) ??\n\t\t\t\tthis.mapping().snap(this.valueFromPoint(event.clientX, event.clientY)),\n\t\t});\n\t\tthis.bindGestures(control, signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tthis.#regions?.follow();\n\t\tthis.showCurrentMarker(this.scrubState().played);\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.canvas;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\t/** The first point marker is the one a press there snaps to */\n\tprotected markersInReach(point: Point, widthPx: number): Array<number> {\n\t\tconst regions = this.markerProportions().flatMap(([from, to], index) => {\n\t\t\tconst isRegion = this.markerList()[index]?.end !== undefined;\n\n\t\t\treturn isRegion && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];\n\t\t});\n\n\t\treturn [...this.#markerBand.inReach(point, widthPx), ...regions];\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override usesChild(child: Node): boolean {\n\t\treturn child instanceof Element && child.localName === 'sonic-region';\n\t}\n\n\t#axis(): ValueAxis & { startPx: number; topPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttopPx: box.top,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst peaks = this.#peaks;\n\t\tconst groove = stripGroove(size, look.lengths);\n\t\tconst drawn = groove ? [groove] : [];\n\t\tconst bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;\n\n\t\tthis.#bars = undefined;\n\t\tif (bars.length === 0) return undefined;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of bars) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#grab(event: PointerEvent): undefined | ValueAxis {\n\t\tconst axis = this.#axis();\n\t\tconst marker = this.#markerStartAt(event, axis);\n\t\tconst regions = this.#regions;\n\n\t\tif (regions?.isHeld() === true) {\n\t\t\tregions.abandon();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (marker === undefined && regions?.take(event) === true) return undefined;\n\n\t\tconst at = (event.clientX - axis.startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = {\n\t\t\tfromProportion: clampProportion(at),\n\t\t\tposition: axis.position,\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\n\t\tthis.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#markerStartAt(\n\t\t{ clientX, clientY }: PointerEvent,\n\t\t{ startPx, topPx, travelPx }: { startPx: number; topPx: number; travelPx: number },\n\t): number | undefined {\n\t\tconst [index] = this.#markerBand.inReach(\n\t\t\t{ x: clientX - startPx, y: clientY - topPx },\n\t\t\ttravelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : this.markerList()[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\tconst box = this.control.getBoundingClientRect();\n\t\tconst zone = readPxProperty(\n\t\t\tgetComputedStyle(this.control),\n\t\t\t'--_sonic-cancel-zone',\n\t\t\tcancelZonePx,\n\t\t);\n\n\t\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst mapping = this.mapping();\n\t\tconst { proportionOf } = mapping;\n\t\tconst state = this.scrubState();\n\t\tconst isOwn = state.from !== undefined || this.hoverValue !== undefined;\n\t\tconst preview = isOwn || this.#preview === undefined ? undefined : proportionOf(this.#preview);\n\t\tconst { played, scrub = preview } = scrubRegions(state, this.value, mapping);\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed,\n\t\t\t\t...(scrub === undefined ? {} : { scrub }),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { RegionLayer } from '#elements/region-layer.ts';\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { createMarkerBand } from '#lib/marker-band.ts';\nimport { clamp, clampProportion } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { stripBars, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nexport type MarkerRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\tgrooveSize: '--_sonic-wavestrip-groove-size',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" role=\"slider\" tabindex=\"0\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-regions\"></div>\n\t\t\t<div class=\"sonic-wavestrip-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-wavestrip-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 fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\t/** Loaded spans, in seconds */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\t/** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */\n\tget formatSpokenRegion(): ((start: Seconds, end: Seconds) => string) | undefined {\n\t\treturn this.#formatSpokenRegion;\n\t}\n\n\tset formatSpokenRegion(format: ((start: Seconds, end: Seconds) => string) | undefined) {\n\t\tthis.#formatSpokenRegion = format;\n\t\tthis.#regions?.render();\n\t}\n\n\t/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\t/** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */\n\tget preview(): Seconds | undefined {\n\t\treturn this.#preview;\n\t}\n\n\tset preview(seconds: null | Seconds | undefined) {\n\t\tthis.#preview = Number.isFinite(seconds) ? (seconds ?? undefined) : undefined;\n\t\tthis.render();\n\t}\n\n\t/** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */\n\tget renderMarker(): MarkerRender | undefined {\n\t\treturn this.#renderMarker;\n\t}\n\n\tset renderMarker(render: MarkerRender | undefined) {\n\t\tthis.#renderMarker = render;\n\t\tthis.#drawMarker =\n\t\t\trender &&\n\t\t\t((marker, element) => {\n\t\t\t\trender({ ...marker }, element);\n\t\t\t});\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tprotected override readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly numbers = {};\n\n\tprotected override readonly sheet = 'wavestrip.css';\n\n\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\t#drawMarker: MarkerRender | undefined;\n\n\t#formatSpokenRegion: ((start: Seconds, end: Seconds) => string) | undefined;\n\n\treadonly #markerBand = createMarkerBand<WaveMarker>(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\t#preview: Seconds | undefined;\n\n\t#regions: RegionLayer | undefined;\n\n\t#renderMarker: MarkerRender | undefined;\n\n\t/** Viewport x of a time, clamped to the strip */\n\tclientXOf(value: Seconds): number {\n\t\tconst { startPx, travelPx } = this.#axis();\n\n\t\treturn startPx + this.mapping().proportionOf(value) * travelPx;\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'cancellable',\n\t\t\t'formatSpokenRegion',\n\t\t\t'peaks',\n\t\t\t'preview',\n\t\t\t'renderMarker',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { startPx, travelPx } = this.#axis();\n\t\tconst mapping = this.mapping();\n\n\t\treturn clamp(\n\t\t\tmapping.valueAt(clampProportion((clientX - startPx) / travelPx)),\n\t\t\t...mapping.bounds,\n\t\t);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst { control } = this;\n\n\t\tthis.#regions = new RegionLayer(\n\t\t\t{\n\t\t\t\taxis: () => this.#axis(),\n\t\t\t\tcontrol,\n\t\t\t\telement: this,\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tlayer: requireChild(control, '.sonic-wavestrip-regions', HTMLDivElement),\n\t\t\t\tmapping: () => this.mapping(),\n\t\t\t\toutside: () => this.#outside(),\n\t\t\t\tseek: (event) => {\n\t\t\t\t\tif (this.input(this.valueFromPoint(event.clientX, event.clientY))) {\n\t\t\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tspeak: ({ end, start }) =>\n\t\t\t\t\tthis.#formatSpokenRegion?.(start, end) ??\n\t\t\t\t\tthis.spokenText(start) ??\n\t\t\t\t\tthis.notation.format(start),\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsuper.connect(signal);\n\t\tthis.bindHover(control, signal, {\n\t\t\tchanged: () => {\n\t\t\t\tif (this.#preview !== undefined) this.render();\n\t\t\t},\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\tvalueAt: (event) =>\n\t\t\t\tthis.#markerStartAt(event, this.#axis()) ??\n\t\t\t\tthis.mapping().snap(this.valueFromPoint(event.clientX, event.clientY)),\n\t\t});\n\t\tthis.bindGestures(signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tthis.#regions?.follow();\n\t\tthis.showCurrentMarker(this.scrubState().played);\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.canvas;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\t/** The first point marker is the one a press there snaps to */\n\tprotected markersInReach(point: Point, widthPx: number): Array<number> {\n\t\tconst regions = this.markerProportions().flatMap(([from, to], index) => {\n\t\t\tconst isRegion = this.markerList()[index]?.end !== undefined;\n\n\t\t\treturn isRegion && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];\n\t\t});\n\n\t\treturn [...this.#markerBand.inReach(point, widthPx), ...regions];\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override usesChild(child: Node): boolean {\n\t\treturn child instanceof Element && child.localName === 'sonic-region';\n\t}\n\n\t#axis(): ValueAxis & { startPx: number; topPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttopPx: box.top,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst peaks = this.#peaks;\n\t\tconst groove = stripGroove(size, look.lengths);\n\t\tconst drawn = groove ? [groove] : [];\n\t\tconst bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;\n\n\t\tthis.#bars = undefined;\n\t\tif (bars.length === 0) return undefined;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of bars) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#grab(event: PointerEvent): undefined | ValueAxis {\n\t\tconst axis = this.#axis();\n\t\tconst marker = this.#markerStartAt(event, axis);\n\t\tconst regions = this.#regions;\n\n\t\tif (regions?.isHeld() === true) {\n\t\t\tregions.abandon();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (marker === undefined && regions?.take(event) === true) return undefined;\n\n\t\tconst at = (event.clientX - axis.startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = {\n\t\t\tfromProportion: clampProportion(at),\n\t\t\tposition: axis.position,\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\n\t\tthis.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#markerStartAt(\n\t\t{ clientX, clientY }: PointerEvent,\n\t\t{ startPx, topPx, travelPx }: { startPx: number; topPx: number; travelPx: number },\n\t): number | undefined {\n\t\tconst [index] = this.#markerBand.inReach(\n\t\t\t{ x: clientX - startPx, y: clientY - topPx },\n\t\t\ttravelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : this.markerList()[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\tconst box = this.control.getBoundingClientRect();\n\t\tconst zone = readPxProperty(\n\t\t\tgetComputedStyle(this.control),\n\t\t\t'--_sonic-cancel-zone',\n\t\t\tcancelZonePx,\n\t\t);\n\n\t\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst mapping = this.mapping();\n\t\tconst { proportionOf } = mapping;\n\t\tconst state = this.scrubState();\n\t\tconst isOwn = state.from !== undefined || this.hoverValue !== undefined;\n\t\tconst preview = isOwn || this.#preview === undefined ? undefined : proportionOf(this.#preview);\n\t\tconst { played, scrub = preview } = scrubRegions(state, this.value, mapping);\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed,\n\t\t\t\t...(scrub === undefined ? {} : { scrub }),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BoxProbe } from "../lib/check-styles.js";
|
|
1
2
|
import { SonicFormElement } from "./form-element.js";
|
|
2
3
|
import { ValueSpec } from "../lib/value-mapping.js";
|
|
3
4
|
import { FieldAxis } from "../lib/field.js";
|
|
@@ -55,6 +56,9 @@ export declare class SonicXy extends SonicFormElement {
|
|
|
55
56
|
set yStep(value: number | undefined);
|
|
56
57
|
get yTaper(): ValueSpec['taper'];
|
|
57
58
|
set yTaper(curve: undefined | ValueSpec['taper']);
|
|
59
|
+
protected readonly boxProbe: BoxProbe;
|
|
60
|
+
protected readonly control: HTMLDivElement;
|
|
61
|
+
protected readonly sheet = "xy.css";
|
|
58
62
|
attributeChangedCallback(name: string): void;
|
|
59
63
|
connectedCallback(): void;
|
|
60
64
|
formResetCallback(): void;
|
package/dist/dev/elements/xy.js
CHANGED
|
@@ -3,10 +3,7 @@ import { writeAttribute } from "../lib/write-attribute.js";
|
|
|
3
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
4
|
import { requireChild, template } from "../lib/render.js";
|
|
5
5
|
import { Readout } from "./readout.js";
|
|
6
|
-
import { ReadoutClaim } from "./readout-claim.js";
|
|
7
|
-
import { createDoublePress } from "../lib/double-press.js";
|
|
8
6
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
9
|
-
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
10
7
|
import { resetKeys } from "../lib/value-mapping.js";
|
|
11
8
|
import { createValueModel } from "../lib/value-model.js";
|
|
12
9
|
import { pointerPosition } from "../lib/field.js";
|
|
@@ -203,26 +200,26 @@ var SonicXy = class extends SonicFormElement {
|
|
|
203
200
|
set yTaper(curve) {
|
|
204
201
|
this.reflect("y-taper", curve);
|
|
205
202
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
boxProbe = {
|
|
204
|
+
property: "margin-bottom",
|
|
205
|
+
selector: "[popover]"
|
|
206
|
+
};
|
|
207
|
+
control = renderXy();
|
|
208
|
+
sheet = "xy.css";
|
|
209
209
|
#currentAxis = "x";
|
|
210
|
-
#
|
|
211
|
-
#xy = renderXy();
|
|
212
|
-
#field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
|
|
210
|
+
#field = requireChild(this.control, ".sonic-xy-field", HTMLDivElement);
|
|
213
211
|
#formatValue;
|
|
214
212
|
#gesture;
|
|
215
|
-
#isRevealed = false;
|
|
216
213
|
#models = {
|
|
217
214
|
x: createValueModel(() => this.#spec("x")),
|
|
218
215
|
y: createValueModel(() => this.#spec("y"))
|
|
219
216
|
};
|
|
220
217
|
#parts = {
|
|
221
|
-
x: requireChild(this
|
|
222
|
-
y: requireChild(this
|
|
218
|
+
x: requireChild(this.control, "[data-sonic-axis=\"x\"]", HTMLDivElement),
|
|
219
|
+
y: requireChild(this.control, "[data-sonic-axis=\"y\"]", HTMLDivElement)
|
|
223
220
|
};
|
|
224
|
-
#puck = requireChild(this
|
|
225
|
-
#readout = new Readout(requireChild(this
|
|
221
|
+
#puck = requireChild(this.control, ".sonic-xy-puck", HTMLDivElement);
|
|
222
|
+
#readout = new Readout(requireChild(this.control, ".sonic-xy-readout", HTMLDivElement));
|
|
226
223
|
attributeChangedCallback(name) {
|
|
227
224
|
if ((name === "x" || name === "y") && this.#gesture?.current()) return;
|
|
228
225
|
if (name === "disabled" && this.isDisabled()) this.#gesture?.end();
|
|
@@ -237,13 +234,9 @@ var SonicXy = class extends SonicFormElement {
|
|
|
237
234
|
this.#respec(...axes);
|
|
238
235
|
}
|
|
239
236
|
connect(signal) {
|
|
240
|
-
const xy = this
|
|
241
|
-
this.keepControl(
|
|
237
|
+
const xy = this.control;
|
|
238
|
+
this.keepControl(signal);
|
|
242
239
|
this.render();
|
|
243
|
-
this.checkStyles(xy, "xy.css", {
|
|
244
|
-
property: "margin-bottom",
|
|
245
|
-
selector: "[popover]"
|
|
246
|
-
});
|
|
247
240
|
this.#bindPointer(xy, signal);
|
|
248
241
|
this.#bindKeys(xy, signal);
|
|
249
242
|
}
|
|
@@ -252,7 +245,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
252
245
|
}
|
|
253
246
|
render() {
|
|
254
247
|
if (!this.isBound()) return;
|
|
255
|
-
const { style } = this
|
|
248
|
+
const { style } = this.control;
|
|
256
249
|
for (const axis of axes) {
|
|
257
250
|
const model = this.#models[axis];
|
|
258
251
|
const mapping = model.mapping();
|
|
@@ -279,49 +272,47 @@ var SonicXy = class extends SonicFormElement {
|
|
|
279
272
|
const move = keyMoves.get(event.key);
|
|
280
273
|
if (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;
|
|
281
274
|
event.preventDefault();
|
|
282
|
-
this.#
|
|
283
|
-
this.#renderReadout();
|
|
275
|
+
this.#gesture?.revealKeys();
|
|
284
276
|
if (move) this.#keyTo(...move);
|
|
285
277
|
else this.#reset();
|
|
286
278
|
}, { signal });
|
|
287
279
|
xy.addEventListener("focusout", (event) => {
|
|
288
280
|
if (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;
|
|
289
|
-
|
|
281
|
+
this.#gesture?.concealKeys();
|
|
290
282
|
}, { signal });
|
|
291
283
|
signal.addEventListener("abort", () => {
|
|
292
|
-
|
|
284
|
+
this.#gesture?.concealKeys();
|
|
293
285
|
}, { once: true });
|
|
294
286
|
}
|
|
295
287
|
#bindPointer(xy, signal) {
|
|
296
288
|
xy.addEventListener("mousedown", (event) => {
|
|
297
289
|
event.preventDefault();
|
|
298
290
|
}, { signal });
|
|
299
|
-
xy.addEventListener("pointercancel", () => {
|
|
300
|
-
this.#doublePress.forget();
|
|
301
|
-
}, { signal });
|
|
302
291
|
this.#gesture = bindFieldGesture(xy, {
|
|
303
|
-
|
|
292
|
+
element: this,
|
|
304
293
|
grab: (event) => {
|
|
305
|
-
if (this.isDisabled()
|
|
294
|
+
if (this.isDisabled()) return;
|
|
306
295
|
focusByPointer(this.focusTarget());
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return this.#grab(event);
|
|
310
|
-
}
|
|
311
|
-
this.#reset();
|
|
296
|
+
this.toggleState("dragging", true);
|
|
297
|
+
return this.#grab(event);
|
|
312
298
|
},
|
|
313
299
|
input: (_hold, next) => {
|
|
314
|
-
|
|
315
|
-
},
|
|
316
|
-
lift: (_hold, event) => {
|
|
317
|
-
if (!this.#doublePress.press(event) || this.doublePress !== "reset") return;
|
|
318
|
-
this.#reset();
|
|
300
|
+
this.#input(next);
|
|
319
301
|
},
|
|
320
302
|
release: (_hold, moved) => {
|
|
321
303
|
if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
322
304
|
},
|
|
323
|
-
|
|
324
|
-
this
|
|
305
|
+
renderReadout: () => {
|
|
306
|
+
this.#renderReadout();
|
|
307
|
+
},
|
|
308
|
+
reset: () => {
|
|
309
|
+
if (this.isDisabled()) return;
|
|
310
|
+
focusByPointer(this.focusTarget());
|
|
311
|
+
this.#reset();
|
|
312
|
+
},
|
|
313
|
+
resetsOnDoublePress: () => this.doublePress === "reset",
|
|
314
|
+
toggle: (state, isOn) => {
|
|
315
|
+
this.toggleState(state, isOn);
|
|
325
316
|
}
|
|
326
317
|
}, signal);
|
|
327
318
|
}
|
|
@@ -412,16 +403,11 @@ var SonicXy = class extends SonicFormElement {
|
|
|
412
403
|
this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
|
|
413
404
|
}
|
|
414
405
|
#renderReadout() {
|
|
415
|
-
const { isRevealed } = this.#claim;
|
|
416
406
|
this.#readout.show({
|
|
417
407
|
anchor: this.#puck,
|
|
418
|
-
isOpen: isRevealed && this.readout,
|
|
408
|
+
isOpen: this.#gesture?.isRevealed() === true && this.readout,
|
|
419
409
|
text: this.#valueText("x")
|
|
420
410
|
});
|
|
421
|
-
if (isRevealed === this.#isRevealed) return;
|
|
422
|
-
this.#isRevealed = isRevealed;
|
|
423
|
-
this.toggleState("revealed", isRevealed);
|
|
424
|
-
this.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
|
|
425
411
|
}
|
|
426
412
|
#reset() {
|
|
427
413
|
this.#commit({
|