@xsynaptic/sonic-ui 0.16.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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## 0.16.0
4
11
 
5
12
  ### 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
- fadeProperty: "--_sonic-waveform-label-fade",
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: look.isReducedMotion && this.reducedMotion === "page",
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
- this.#rider.measure(this.markers.flatMap(({ dimmed, kind, label, start }) => label === void 0 || label === "" ? [] : [{
336
- isDimmed: dimmed === true,
337
- ...kind === void 0 ? {} : { kind },
338
- start: clamp(start, ...bounds),
339
- text: label
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 approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;
15
+ const fadePx = startX - endX;
16
+ const leftPx = next ? lineX(next.start, view) - endX : Infinity;
16
17
  return {
17
18
  index,
18
- opacity: view.fadeShare > 0 ? clamp(approachPx / (view.widthPx * view.fadeShare), 0, 1) : 1,
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 parkedIndex = labels.findLastIndex((label) => label.start <= view.playheadSeconds);
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, [view, arrivingX]);
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 (element.textContent !== label.text) element.textContent = label.text;
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, texts) {
81
- const probes = texts.map((text) => {
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.textContent = text;
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, fadeProperty, insetProperty, parkProperty } = options;
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 fadeShare = .25;
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.map(({ text }) => text));
123
+ const widths = measureWidths(control, className, labels);
103
124
  const styles = getComputedStyle(control);
104
- fadeShare = readPxProperty(styles, fadeProperty, .25);
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
- fadeShare,
137
+ fadeEnd,
138
+ fadeStart,
116
139
  insetPx,
117
140
  parkPx
118
141
  });
@@ -7,6 +7,7 @@ declare global {
7
7
  }
8
8
  }
9
9
  export interface WaveMarker {
10
+ [key: string]: unknown;
10
11
  dimmed?: boolean;
11
12
  end?: number;
12
13
  kind?: string;
@@ -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":"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"}
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
- fadeProperty: "--_sonic-waveform-label-fade",
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: look.isReducedMotion && this.reducedMotion === "page",
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
- this.#rider.measure(this.markers.flatMap(({ dimmed, kind, label, start }) => label === void 0 || label === "" ? [] : [{
336
- isDimmed: dimmed === true,
337
- ...kind === void 0 ? {} : { kind },
338
- start: clamp(start, ...bounds),
339
- text: label
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\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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"}
@@ -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 approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;
15
+ const fadePx = startX - endX;
16
+ const leftPx = next ? lineX(next.start, view) - endX : Infinity;
16
17
  return {
17
18
  index,
18
- opacity: view.fadeShare > 0 ? clamp(approachPx / (view.widthPx * view.fadeShare), 0, 1) : 1,
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 parkedIndex = labels.findLastIndex((label) => label.start <= view.playheadSeconds);
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, [view, arrivingX]);
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 (element.textContent !== label.text) element.textContent = label.text;
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, texts) {
81
- const probes = texts.map((text) => {
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.textContent = text;
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, fadeProperty, insetProperty, parkProperty } = options;
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 fadeShare = .25;
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.map(({ text }) => text));
123
+ const widths = measureWidths(control, className, labels);
103
124
  const styles = getComputedStyle(control);
104
- fadeShare = readPxProperty(styles, fadeProperty, .25);
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
- fadeShare,
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":";;;;;;AAkDA,SAAS,MAAM,SAAiB,MAAyB;CACxD,QAAS,UAAU,KAAK,gBAAgB,KAAK,gBAAiB,KAAK;AACpE;AAEA,SAAS,gBACR,QACA,OACA,CAAC,MAAM,WAAW,CAAC,QAAQ,QACE;CAC7B,MAAM,SAAS,OAAO;CACtB,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,IAAI,KAAK,IAAI,KAAK,QAAQ,MAAM,OAAO,OAAO,IAAI,IAAI,KAAK,OAAO;CACxE,MAAM,SAAS,SAAS;CACxB,MAAM,SAAS,OAAO,MAAM,KAAK,OAAO,IAAI,IAAI,OAAO;CAEvD,OAAO;EACN;EACA,SAAS,SAAS,IAAI,MAAM,SAAS,QAAQ,GAAG,CAAC,IAAI;EACrD,SAAS,KAAK,IAAI,OAAO,SAAS,YAAY,GAAG,KAAK,UAAU,CAAC;EACjE;CACD;AACD;AAEA,SAAS,WAAW,QAAyB;CAC5C,OAAO,OAAO,SAAS,MAAM,KAAK,SAAS;AAC5C;AAGA,SAAS,UAAU,MAAmC;CACrD,MAAM,YAAY,MAAM,KAAK,iBAAiB,IAAI;CAElD,IAAI,KAAK,SAAS,OAAO,CAAC,WAAW,SAAS;CAE9C,MAAM,YAAY,KAAK,SAAS,KAAK,UAAU;CAE/C,OAAO,CACN,YAAY,MAAM,KAAK,WAAW,GAAG,CAAC,IAAI,WAC1C,YAAY,MAAM,KAAK,SAAS,GAAG,CAAC,IAAI,SACzC;AACD;AAEA,SAAgB,YAAY,QAAmC,MAA8B;CAC5F,IAAI,CAAC,WAAW,KAAK,aAAa,KAAK,CAAC,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC;CAE1E,MAAM,OAAO,UAAU,IAAI;CAC3B,MAAM,cAAc,OAAO,eAAe,UAAU,MAAM,MAAM,OAAO,IAAI,KAAK,KAAK,EAAE;CACvF,MAAM,OAAO,OAAO,cAAc;CAClC,MAAM,YAAY,OAAO,MAAM,KAAK,OAAO,IAAI,IAAI,KAAK,UAAU;CAClE,MAAM,SAAS,gBAAgB,QAAQ,aAAa;EAAC;EAAM;EAAW;CAAI,CAAC;CAC3E,MAAM,WACL,QAAQ,YAAY,KAAK,UACtB;EACA,OAAO,cAAc;EACrB,SAAS;EACT,SAAS,KAAK,IAAI,KAAK,SAAS,KAAK,UAAU,SAAS;EACxD,GAAG;CACJ,IACC;CAEJ,OAAO;EAAE,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;EAAI,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAAG;AAC3E;AAEA,SAAS,UAAU,SAAiC;CACnD,OAAO;EAAE;EAAS,MAAM;EAAW,OAAO;EAAW,SAAS;EAAK,SAAS;EAAK,GAAG;CAAI;AACzF;AAEA,SAAS,aAAa,SAAsB,EAAE,MAAM,SAA4C;CAC/F,QAAQ,cAAc,QAAQ,KAAK;CACnC,QAAQ,OAAO;AAChB;AAEA,SAAS,eACR,MACA,EAAE,SAAS,SAAS,KACpB,SACO;CACP,MAAM,EAAE,UAAU,KAAK;CAEvB,IAAI,KAAK,MAAM,GAAG;EACjB,KAAK,IAAI;EACT,MAAM,YAAY,aAAa,GAAG,EAAE,QAAQ,CAAC,EAAE,GAAG;CACnD;CACA,IAAI,KAAK,YAAY,SAAS;EAC7B,KAAK,UAAU;EACf,MAAM,YACL,aACA,UAAU,UAAU,uBAAuB,QAAQ,QAAQ,CAAC,EAAE,YAAY,EAC3E;CACD;CACA,IAAI,KAAK,YAAY,SAAS;EAC7B,KAAK,UAAU;EACf,MAAM,YAAY,WAAW,UAAU,IAAI,QAAQ,QAAQ,CAAC,IAAI,EAAE;CACnE;AACD;AAEA,SAAS,WACR,MACA,CAAC,QAAQ,iBACT,WACO;CACP,MAAM,EAAE,YAAY;CACpB,MAAM,QAAQ,aAAa,UAAU,UAAU,IAAI,OAAO,UAAU,SAAS;CAE7E,IAAI,QAAQ,WAAW,CAAC,OAAO,QAAQ,SAAS,CAAC;CACjD,IAAI,CAAC,SAAS,CAAC,WAAW;CAE1B,IAAI,KAAK,UAAU,OAAO;EACzB,KAAK,QAAQ;EACb,aAAa,SAAS,KAAK;CAC5B;CACA,QAAQ,gBAAgB,qBAAqB,MAAM,QAAQ;CAC3D,IAAI,KAAK,SAAS,MAAM,MAAM;EAC7B,KAAK,OAAO,MAAM;EAClB,UAAU,SAAS,MAAM,MAAM,cAAc;CAC9C;CACA,eAAe,MAAM,WAAW,MAAM,OAAO;AAC9C;AAEA,SAAS,cACR,SACA,WACA,QACgB;CAChB,MAAM,SAAS,OAAO,KAAK,UAAU;EACpC,MAAM,QAAQ,SAAS,cAAc,KAAK;EAE1C,MAAM,YAAY;EAClB,aAAa,OAAO,KAAK;EAEzB,OAAO;CACR,CAAC;CAED,QAAQ,OAAO,GAAG,MAAM;CAExB,MAAM,SAAS,OAAO,KAAK,UAAU,MAAM,sBAAsB,CAAC,CAAC,KAAK;CAExE,KAAK,MAAM,SAAS,QAAQ,MAAM,OAAO;CAEzC,OAAO;AACR;AAEA,SAAgB,iBACf,SACA,SAUC;CACD,MAAM,EAAE,WAAW,gBAAgB,gBAAgB,eAAe,iBAAiB;CACnF,MAAM,SAAS,UAAU,aAAa,SAAS,IAAI,aAAa,WAAW,CAAC;CAC5E,MAAM,WAAW,UAAU,aAAa,SAAS,IAAI,UAAU,MAAM,aAAa,WAAW,CAAC;CAC9F,IAAI,YAAY;CAChB,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI,WAA8B,CAAC;CAEnC,OAAO;EACN,UAAU,WAAW;GACpB,MAAM,SAAS,cAAc,SAAS,WAAW,MAAM;GAEvD,MAAM,SAAS,iBAAiB,OAAO;GAEvC,YAAY,eAAe,QAAQ,eAAe,IAAI,CAAC;GACvD,UAAU,eAAe,QAAQ,eAAe,IAAI,CAAC;GACrD,UAAU,eAAe,QAAQ,eAAe,CAAC;GACjD,SAAS,eAAe,QAAQ,cAAc,CAAC;GAC/C,WAAW,OACT,KAAK,OAAO,WAAW;IAAE,GAAG;IAAO,SAAS,OAAO,UAAU;GAAE,EAAE,CAAC,CAClE,UAAU,OAAO,WAAW,MAAM,QAAQ,OAAO,KAAK;EACzD;EACA,QAAQ,WAAW;GAClB,MAAM,SAAS,YAAY,UAAU;IAAE,GAAG;IAAQ;IAAS;IAAW;IAAS;GAAO,CAAC;GAEvF,WAAW,QAAQ,CAAC,UAAU,cAAc,GAAG,OAAO,MAAM;GAC5D,WAAW,UAAU,CAAC,UAAU,cAAc,GAAG,OAAO,QAAQ;EACjE;CACD;AACD"}
@@ -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.25;
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-ratio, 0.25);
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
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xsynaptic/sonic-ui",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Native custom elements for audio interfaces, skinned with CSS custom properties",
5
5
  "keywords": [
6
6
  "custom-elements",