@xsynaptic/sonic-ui 0.19.0 → 0.21.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 +37 -0
- package/README.md +27 -112
- package/dist/default/elements/button.js +2 -1
- package/dist/default/elements/field-gesture.js +3 -0
- package/dist/default/elements/sonic-element.js +1 -1
- package/dist/default/elements/split.js +3 -1
- package/dist/default/elements/switch.js +5 -3
- package/dist/default/elements/toggle.js +10 -5
- package/dist/default/elements/value-entry.js +5 -18
- package/dist/default/elements/waveform.js +15 -4
- package/dist/default/elements/xy.js +33 -5
- package/dist/default/lib/canvas-surface.js +5 -1
- package/dist/default/lib/double-press.js +30 -0
- package/dist/dev/elements/button.js +5 -11
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +2 -12
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.js +1 -5
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +2 -15
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +3 -0
- package/dist/dev/elements/field-gesture.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +1 -13
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/segmented.js +3 -10
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.js +2 -12
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +1 -22
- package/dist/dev/elements/sonic-element.js +7 -34
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js +3 -1
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.js +7 -8
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +13 -15
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-entry.js +5 -18
- package/dist/dev/elements/value-entry.js.map +1 -1
- package/dist/dev/elements/wave-element.js +2 -12
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +2 -0
- package/dist/dev/elements/waveform.js +15 -4
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +4 -0
- package/dist/dev/elements/xy.js +35 -17
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/lib/canvas-surface.js +5 -1
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/double-press.js +31 -0
- package/dist/dev/lib/double-press.js.map +1 -0
- package/dist/skins/amber.css +0 -1
- package/dist/skins/ivory.css +0 -1
- package/dist/skins/lime.css +0 -1
- package/dist/skins/slate.css +0 -1
- package/dist/styles/button.css +1 -0
- package/dist/styles/dial.css +1 -1
- package/dist/styles/envelope.css +16 -14
- package/dist/styles/led.css +7 -19
- package/dist/styles/material/bracket.css +1 -1
- package/dist/styles/material/cap.css +27 -4
- package/dist/styles/material/core.css +9 -1
- package/dist/styles/material/glass.css +1 -7
- package/dist/styles/material/lens.css +18 -1
- package/dist/styles/material/pane.css +2 -12
- package/dist/styles/material/readout.css +7 -1
- package/dist/styles/material/well.css +8 -12
- package/dist/styles/meter.css +6 -4
- package/dist/styles/number.css +8 -9
- package/dist/styles/screen.css +1 -3
- package/dist/styles/spectrum.css +8 -22
- package/dist/styles/toggle.css +4 -0
- package/dist/styles/waveform.css +12 -24
- package/dist/styles/wavestrip.css +12 -33
- package/dist/styles/xy.css +30 -28
- package/package.json +1 -1
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
import { createDoublePress } from "../lib/double-press.js";
|
|
2
|
+
|
|
1
3
|
//#region src/elements/value-entry.ts
|
|
2
|
-
const doublePressMs = 500;
|
|
3
|
-
const doublePressPx = 4;
|
|
4
|
-
function isDoublePress(previous, next) {
|
|
5
|
-
if (!previous) return false;
|
|
6
|
-
return next.time - previous.time < doublePressMs && Math.hypot(next.x - previous.x, next.y - previous.y) < doublePressPx;
|
|
7
|
-
}
|
|
8
4
|
var ValueEntry = class {
|
|
9
5
|
get isOpen() {
|
|
10
6
|
return this.#isOpen;
|
|
11
7
|
}
|
|
8
|
+
#doublePress = createDoublePress();
|
|
12
9
|
#input;
|
|
13
10
|
#isOpen = false;
|
|
14
|
-
#lastPress;
|
|
15
11
|
#openedText = "";
|
|
16
12
|
#options;
|
|
17
13
|
constructor(input, options) {
|
|
@@ -44,7 +40,7 @@ var ValueEntry = class {
|
|
|
44
40
|
if (isCommitting && this.#input.value !== this.#openedText) this.#options.commit(this.#input.value);
|
|
45
41
|
}
|
|
46
42
|
forgetPress() {
|
|
47
|
-
this.#
|
|
43
|
+
this.#doublePress.forget();
|
|
48
44
|
}
|
|
49
45
|
open() {
|
|
50
46
|
const input = this.#input;
|
|
@@ -57,16 +53,7 @@ var ValueEntry = class {
|
|
|
57
53
|
input.select();
|
|
58
54
|
}
|
|
59
55
|
press(event) {
|
|
60
|
-
|
|
61
|
-
time: event.timeStamp,
|
|
62
|
-
x: event.clientX,
|
|
63
|
-
y: event.clientY
|
|
64
|
-
};
|
|
65
|
-
const previous = this.#lastPress;
|
|
66
|
-
this.#lastPress = press;
|
|
67
|
-
if (!isDoublePress(previous, press)) return false;
|
|
68
|
-
this.#lastPress = void 0;
|
|
69
|
-
return true;
|
|
56
|
+
return this.#doublePress.press(event);
|
|
70
57
|
}
|
|
71
58
|
};
|
|
72
59
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-entry.js","names":[],"sources":["../../../src/elements/value-entry.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"value-entry.js","names":[],"sources":["../../../src/elements/value-entry.ts"],"sourcesContent":["import { createDoublePress } from '#lib/double-press.ts';\n\ninterface ValueEntryOptions {\n\tcommit: (text: string) => void;\n\ttext: () => string;\n\ttoggle: (isOpen: boolean) => void;\n}\n\nexport class ValueEntry {\n\tget isOpen(): boolean {\n\t\treturn this.#isOpen;\n\t}\n\n\treadonly #doublePress = createDoublePress();\n\n\treadonly #input: HTMLInputElement;\n\n\t#isOpen = false;\n\n\t#openedText = '';\n\n\treadonly #options: ValueEntryOptions;\n\n\tconstructor(input: HTMLInputElement, options: ValueEntryOptions) {\n\t\tthis.#input = input;\n\t\tthis.#options = options;\n\t}\n\n\tbind(control: HTMLElement, signal: AbortSignal): void {\n\t\tconst input = this.#input;\n\n\t\tinput.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.isComposing || (event.key !== 'Enter' && event.key !== 'Escape')) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.close(event.key === 'Enter');\n\t\t\t\tcontrol.focus();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tinput.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.close(true);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\tfor (const type of ['change', 'input']) {\n\t\t\tinput.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.close(false);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tclose(isCommitting: boolean): void {\n\t\tif (!this.#isOpen) return;\n\n\t\tthis.#isOpen = false;\n\t\tthis.#input.hidden = true;\n\t\tthis.#options.toggle(false);\n\t\tif (isCommitting && this.#input.value !== this.#openedText)\n\t\t\tthis.#options.commit(this.#input.value);\n\t}\n\n\tforgetPress(): void {\n\t\tthis.#doublePress.forget();\n\t}\n\n\topen(): void {\n\t\tconst input = this.#input;\n\n\t\tthis.#isOpen = true;\n\t\tthis.#openedText = this.#options.text();\n\t\tinput.value = this.#openedText;\n\t\tinput.hidden = false;\n\t\tthis.#options.toggle(true);\n\t\tinput.focus();\n\t\tinput.select();\n\t}\n\n\tpress(event: PointerEvent): boolean {\n\t\treturn this.#doublePress.press(event);\n\t}\n}\n"],"mappings":";;;AAQA,IAAa,aAAb,MAAwB;CACvB,IAAI,SAAkB;EACrB,OAAO,KAAK;CACb;CAEA,AAAS,eAAe,kBAAkB;CAE1C,AAAS;CAET,UAAU;CAEV,cAAc;CAEd,AAAS;CAET,YAAY,OAAyB,SAA4B;EAChE,KAAK,SAAS;EACd,KAAK,WAAW;CACjB;CAEA,KAAK,SAAsB,QAA2B;EACrD,MAAM,QAAQ,KAAK;EAEnB,MAAM,iBACL,YACC,UAAU;GACV,IAAI,MAAM,eAAgB,MAAM,QAAQ,WAAW,MAAM,QAAQ,UAAW;GAE5E,MAAM,eAAe;GACrB,KAAK,MAAM,MAAM,QAAQ,OAAO;GAChC,QAAQ,MAAM;EACf,GACA,EAAE,OAAO,CACV;EACA,MAAM,iBACL,cACM;GACL,KAAK,MAAM,IAAI;EAChB,GACA,EAAE,OAAO,CACV;EAEA,KAAK,MAAM,QAAQ,CAAC,UAAU,OAAO,GACpC,MAAM,iBACL,OACC,UAAU;GACV,MAAM,gBAAgB;EACvB,GACA,EAAE,OAAO,CACV;EAED,OAAO,iBACN,eACM;GACL,KAAK,MAAM,KAAK;EACjB,GACA,EAAE,MAAM,KAAK,CACd;CACD;CAEA,MAAM,cAA6B;EAClC,IAAI,CAAC,KAAK,SAAS;EAEnB,KAAK,UAAU;EACf,KAAK,OAAO,SAAS;EACrB,KAAK,SAAS,OAAO,KAAK;EAC1B,IAAI,gBAAgB,KAAK,OAAO,UAAU,KAAK,aAC9C,KAAK,SAAS,OAAO,KAAK,OAAO,KAAK;CACxC;CAEA,cAAoB;EACnB,KAAK,aAAa,OAAO;CAC1B;CAEA,OAAa;EACZ,MAAM,QAAQ,KAAK;EAEnB,KAAK,UAAU;EACf,KAAK,cAAc,KAAK,SAAS,KAAK;EACtC,MAAM,QAAQ,KAAK;EACnB,MAAM,SAAS;EACf,KAAK,SAAS,OAAO,IAAI;EACzB,MAAM,MAAM;EACZ,MAAM,OAAO;CACd;CAEA,MAAM,OAA8B;EACnC,OAAO,KAAK,aAAa,MAAM,KAAK;CACrC;AACD"}
|
|
@@ -73,18 +73,8 @@ var SonicWaveElement = class extends SonicValueElement {
|
|
|
73
73
|
});
|
|
74
74
|
this.render();
|
|
75
75
|
this.checkStyles(control, this.sheet, {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
selector: "[popover]"
|
|
79
|
-
},
|
|
80
|
-
material: [
|
|
81
|
-
"cap",
|
|
82
|
-
"glass",
|
|
83
|
-
"pane",
|
|
84
|
-
"well",
|
|
85
|
-
"readout",
|
|
86
|
-
"value"
|
|
87
|
-
]
|
|
76
|
+
property: "margin-bottom",
|
|
77
|
+
selector: "[popover]"
|
|
88
78
|
});
|
|
89
79
|
this.#surface.setFill(this.fill);
|
|
90
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n> 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 override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\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\tfill: { control, sizeProperty: this.sizeProperty },\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\tif (__DEV__)\n\t\t\tthis.checkStyles(control, this.sheet, {\n\t\t\t\
|
|
1
|
+
{"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n> 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 override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\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\tfill: { control, sizeProperty: this.sizeProperty },\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\tif (__DEV__)\n\t\t\tthis.checkStyles(control, this.sheet, {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\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 override scrubChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":";;;;;;AA6BA,SAAS,UAAU,QAAwC;CAC1D,OAAO,SAAS;EAAC,OAAO;EAAO,OAAO;EAAK,OAAO;EAAM,OAAO;CAAK,CAAC,CAAC,KAAK,GAAG,IAAI;AACnF;AAEA,IAAsB,mBAAtB,cAGU,kBAAkB;CAC3B,OAAyB,qBAAqB,CAAC,GAAG,kBAAkB,oBAAoB,MAAM;CAE9F,IAAI,gBAAwC;EAC3C,OAAO,KAAK,WAAW,EAAE,GAAG,KAAK,SAAS,IAAI;CAC/C;CAEA,IAAI,OAAgB;EACnB,OAAO,KAAK,aAAa,MAAM;CAChC;CAEA,IAAI,KAAK,WAAoB;EAC5B,KAAK,QAAQ,QAAQ,SAAS;CAC/B;CAEA,IAAI,UAA6B;EAChC,OAAO,KAAK,SAAS,KAAK,YAAY,EAAE,GAAG,OAAO,EAAE;CACrD;CAEA,IAAI,QAAQ,MAAwC;EACnD,KAAK,WAAW,CAAC,GAAI,QAAQ,CAAC,CAAE,CAAC,CAC/B,QAAQ,WAAW,OAAO,SAAS,OAAO,KAAK,CAAC,CAAC,CACjD,KAAK,YAAY,EAAE,GAAG,OAAO,EAAE,CAAC,CAChC,UAAU,OAAO,WAAW,MAAM,QAAQ,OAAO,KAAK;EACxD,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,KAAK,cAAc;EACnB,KAAK,OAAO;CACb;CAUA,AAA4B,WAA0B;EACrD,QAAQ;EACR,OAAO;EACP,QAAQ,YAAY,eAAe,SAAS,KAAK,QAAqB,QAAQ,CAAC,EAAE,IAAI;CACtF;CAMA;CAEA,WAA8B,CAAC;CAE/B;CAEA,AAAS,yBAAyB,MAAoB;EACrD,MAAM,yBAAyB,IAAI;EACnC,IAAI,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,IAAI;CACtD;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,QAAQ,SAAS;EACxC,MAAM,kBAAkB;CACzB;CAEA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,YAAY;EACjB,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,MAAM;IAAE;IAAS,cAAc,KAAK;GAAa;GACjD,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,MAAM,SAAS,KAAK;GAC1B;GACA,cAAc;IACb,KAAK,cAAc;GACpB;GACA;EACD,CAAC;EACD,KAAK,OAAO;EAEX,KAAK,YAAY,SAAS,KAAK,OAAO;GACrC,UAAU;GACV,UAAU;EACX,CAAC;EACF,KAAK,SAAS,QAAQ,KAAK,IAAI;CAChC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAIA,AAAU,aAAwC;EACjD,OAAO,KAAK;CACb;CAEA,AAAU,oBAA6C;EACtD,MAAM,UAAU,KAAK,QAAQ;EAE7B,OAAO,KAAK,SAAS,KAAK,EAAE,KAAK,YAAY;GAC5C,MAAM,KAAK,QAAQ,aAAa,KAAK;GACrC,MAAM,KAAK,QAAQ,SAAY,KAAK,QAAQ,aAAa,GAAG;GAE5D,OAAO,CAAC,KAAK,IAAI,IAAI,EAAE,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC;EAC3C,CAAC;CACF;CAOA,AAAmB,qBAA2B;EAC7C,KAAK,cAAc;CACpB;CAEA,AAAU,cAAoB;EAC7B,KAAK,YAAY,SAAS,KAAK,QAAQ,CAAC;CACzC;CAIA,AAAmB,eAAqB;EACvC,KAAK,OAAO;CACb;CAEA,AAAU,kBAAkB,SAAuB;EAClD,MAAM,UAAU,KAAK,SAAS,UAAU,WAAW,OAAO,SAAS,OAAO;EAC1E,IAAI,UAAU,OAAO,MAAM,UAAU,KAAK,QAAQ,GAAG;EAErD,KAAK,WAAW;EAChB,KAAK,cAAc,IAAI,MAAM,gBAAgB,EAAE,SAAS,KAAK,CAAC,CAAC;CAChE;CAEA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;AACD"}
|
|
@@ -68,6 +68,8 @@ export declare class SonicWaveform extends SonicWaveElement<Colour> {
|
|
|
68
68
|
protected isEmpty(): boolean;
|
|
69
69
|
protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour>): void;
|
|
70
70
|
protected renderMarkers(): void;
|
|
71
|
+
protected scrubChanged(): void;
|
|
72
|
+
protected scrubsKeyRepeat(): boolean;
|
|
71
73
|
}
|
|
72
74
|
//#endregion
|
|
73
75
|
export type { PeaksRequest };
|
|
@@ -287,6 +287,14 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
287
287
|
}]));
|
|
288
288
|
this.surface()?.rebuild();
|
|
289
289
|
}
|
|
290
|
+
scrubChanged() {
|
|
291
|
+
const isScrubbing = this.scrubState().from !== void 0;
|
|
292
|
+
this.#grabbed = isScrubbing ? this.#grabbed ?? this.#lastDrawn() : void 0;
|
|
293
|
+
super.scrubChanged();
|
|
294
|
+
}
|
|
295
|
+
scrubsKeyRepeat() {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
290
298
|
#duePending() {
|
|
291
299
|
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
292
300
|
if (due.length < this.#pending.length) this.surface()?.requestFrame();
|
|
@@ -294,10 +302,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
294
302
|
}
|
|
295
303
|
#grab() {
|
|
296
304
|
if (this.#zoomGesture?.isPinching() === true) return void 0;
|
|
297
|
-
const drawn = this.#
|
|
298
|
-
seconds: this.value,
|
|
299
|
-
startSeconds: this.value
|
|
300
|
-
};
|
|
305
|
+
const drawn = this.#lastDrawn();
|
|
301
306
|
this.#grabbed = drawn;
|
|
302
307
|
const mapping = this.mapping();
|
|
303
308
|
const [low, high] = mapping.bounds;
|
|
@@ -317,6 +322,12 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
317
322
|
seconds: this.value === from ? grabbed.seconds : this.value
|
|
318
323
|
};
|
|
319
324
|
}
|
|
325
|
+
#lastDrawn() {
|
|
326
|
+
return this.#drawn ?? {
|
|
327
|
+
seconds: this.value,
|
|
328
|
+
startSeconds: this.value
|
|
329
|
+
};
|
|
330
|
+
}
|
|
320
331
|
#readKindColours() {
|
|
321
332
|
const kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];
|
|
322
333
|
const probes = kinds.map((kind) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waveform.js","names":[],"sources":["../../../src/elements/waveform.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Drawn } from '#lib/frame-timeline.ts';\nimport type { PeaksRequest } from '#lib/peaks-request.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Milliseconds, PixelsPerSecond, Seconds } from '#lib/units.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\nimport type { ZoomGesture } from '#lib/zoom-gesture.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { frameTimeline } from '#lib/frame-timeline.ts';\nimport { isKind, writeKind } from '#lib/marker-band.ts';\nimport { createLabelRider } from '#lib/marker-rider.ts';\nimport { clamp, roundTo } from '#lib/math.ts';\nimport { createPeaksRequest } from '#lib/peaks-request.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { paintWaveform, waveformColours } from '#lib/waveform-scene.ts';\nimport { bindZoom, bindZoomKeys } from '#lib/zoom-gesture.ts';\n\nexport type { PeaksRequest } from '#lib/peaks-request.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-zoom': Event;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-waveform': SonicWaveform;\n\t}\n}\n\ntype Colour = keyof typeof waveformColours;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\n\nconst renderWaveform = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-waveform\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-waveform-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-waveform-scrim\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-ghost\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-playhead\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-waveform-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction placeLine(line: HTMLElement, at: number | undefined): void {\n\tconst isHidden = at === undefined || at < 0 || at > 1;\n\n\tif (line.hidden !== isHidden) line.hidden = isHidden;\n\tif (isHidden) return;\n\n\tconst translate = `${String(at * 100)}cqi`;\n\n\tif (line.style.getPropertyValue('translate') !== translate) {\n\t\tline.style.setProperty('translate', translate);\n\t}\n}\n\nexport class SonicWaveform extends SonicWaveElement<Colour> {\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.#peaksRequest.changed();\n\t}\n\n\t/** Spans being fetched; drawn as placeholders until cleared */\n\tget pending(): Array<[Seconds, Seconds]> {\n\t\treturn this.#pending.map(([start, end]) => [start, end]);\n\t}\n\n\tset pending(regions: TimeRegions | undefined) {\n\t\tconst pending = regions ? readRegions(regions) : [];\n\t\tif (String(pending) === String(this.#pending)) return;\n\n\t\tconst listedMs = this.#pendingListedMs;\n\t\tconst nowMs = performance.now();\n\n\t\tthis.#pending = pending;\n\t\tthis.#pendingListedMs = new Map(\n\t\t\tthis.#pending.map((region) => {\n\t\t\t\tconst key = region.join(':');\n\n\t\t\t\treturn [key, listedMs.get(key) ?? nowMs];\n\t\t\t}),\n\t\t);\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\t/** How long a span stays listed in `pending` before its placeholder draws */\n\tget pendingDelay(): Milliseconds {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: Milliseconds | undefined) {\n\t\tthis.reflect('pending-delay', value);\n\t}\n\n\tget playbackRate(): number {\n\t\tconst playbackRate = this.numberAttribute('playback-rate', 1);\n\n\t\treturn playbackRate > 0 ? playbackRate : 1;\n\t}\n\n\tset playbackRate(value: number | undefined) {\n\t\tthis.reflect('playback-rate', value);\n\t}\n\n\tget playing(): boolean {\n\t\treturn this.hasAttribute('playing');\n\t}\n\n\tset playing(isPlaying: boolean) {\n\t\tthis.reflect('playing', isPlaying);\n\t}\n\n\t/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */\n\tget readTime(): (() => Seconds | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => Seconds | undefined) | undefined) {\n\t\tthis.#readTime = read;\n\t\tthis.render();\n\t}\n\n\tget reducedMotion(): 'page' | 'scroll' {\n\t\treturn this.getAttribute('reduced-motion') === 'scroll' ? 'scroll' : 'page';\n\t}\n\n\tset reducedMotion(motion: 'page' | 'scroll' | undefined) {\n\t\tthis.reflect('reduced-motion', motion);\n\t}\n\n\tget renderLabel(): LabelRender | undefined {\n\t\treturn this.#renderLabel;\n\t}\n\n\tset renderLabel(render: LabelRender | undefined) {\n\t\tthis.#renderLabel = render;\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tget requestPeaks(): PeaksRequest | undefined {\n\t\treturn this.#requestPeaks;\n\t}\n\n\tset requestPeaks(request: PeaksRequest | undefined) {\n\t\tthis.#requestPeaks = request;\n\t\tthis.#peaksRequest.reset();\n\t}\n\n\tget zoom(): PixelsPerSecond {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom', value);\n\t}\n\n\tget zoomable(): boolean {\n\t\treturn this.hasAttribute('zoomable');\n\t}\n\n\tset zoomable(isZoomable: boolean) {\n\t\tthis.reflect('zoomable', isZoomable);\n\t}\n\n\tget zoomMax(): PixelsPerSecond {\n\t\tconst zoomMax = this.numberAttribute('zoom-max', defaultZoomMax);\n\n\t\treturn Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);\n\t}\n\n\tset zoomMax(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): PixelsPerSecond {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected readonly control = renderWaveform();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-waveform-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = waveformColours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\treadonly #clock = createTrackingClock();\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#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\treadonly #peaksRequest = createPeaksRequest(() => {\n\t\tthis.surface()?.invalidate();\n\t});\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\n\n\treadonly #playhead = requireChild(this.control, '.sonic-waveform-playhead', HTMLDivElement);\n\n\t#readTime: (() => number | undefined) | undefined;\n\n\t#renderLabel: LabelRender | undefined;\n\n\t#requestPeaks: PeaksRequest | undefined;\n\n\treadonly #rider = createLabelRider(this.control, {\n\t\tclassName: 'sonic-waveform-label',\n\t\tcolourProperty: '--_sonic-waveform-marker',\n\t\tfadeProperties: ['--_sonic-waveform-label-fade-start', '--_sonic-waveform-label-fade-end'],\n\t\tinsetProperty: '--_sonic-waveform-label-inset',\n\t\tparkProperty: '--_sonic-waveform-label-park',\n\t});\n\n\t#zoomGesture: undefined | ZoomGesture;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#zoomGesture?.sync();\n\t}\n\n\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\n\t\tconst zoomTarget = {\n\t\t\tclaim: () => {\n\t\t\t\tthis.abandonHold();\n\t\t\t},\n\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\tisZoomable: () => this.zoomable,\n\t\t\tzoom: () => this.zoom,\n\t\t\tzoomTo: (target: number) => {\n\t\t\t\tthis.#zoomTo(target);\n\t\t\t},\n\t\t};\n\n\t\tthis.#zoomGesture = bindZoom(this.control, zoomTarget, signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\t// After the value keys, which stand down once a key is taken\n\t\tbindZoomKeys(this.control, zoomTarget, signal);\n\t\tif (!('fonts' in document)) return;\n\n\t\tdocument.fonts.addEventListener(\n\t\t\t'loadingdone',\n\t\t\t() => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tsurface?.requestFrame();\n\t\t// No frame follows the position out of view, or before the first one paints\n\t\tif (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());\n\t}\n\n\tprotected 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 range: [number, number] = [this.min, Math.max(this.min, this.max)];\n\t\tconst timeline = frameTimeline({\n\t\t\tclockSeconds: this.#clock.read(frameMs, {\n\t\t\t\tisPlaying: this.playing,\n\t\t\t\tplaybackRate: this.playbackRate,\n\t\t\t\tseconds: this.#sourceSeconds(),\n\t\t\t}),\n\t\t\tframeMs,\n\t\t\t...(held ? { held } : {}),\n\t\t\tisPaged,\n\t\t\tisPlaying: this.playing,\n\t\t\tisStill: look.isReducedMotion,\n\t\t\tpending,\n\t\t\trange,\n\t\t\tsize,\n\t\t\tzoom: this.zoom,\n\t\t});\n\t\tconst { seconds, view } = timeline;\n\n\t\tthis.#drawn = { seconds, startSeconds: view.startSeconds };\n\t\tplaceLine(this.#playhead, timeline.playheadAt);\n\t\tplaceLine(this.#ghost, timeline.ghostAt);\n\t\tthis.showCurrentMarker(seconds);\n\t\tthis.toggleState('pending', view.pending.length > 0);\n\t\tthis.#rider.place({\n\t\t\tisPaged,\n\t\t\tplayheadSeconds: seconds,\n\t\t\tstartSeconds: view.startSeconds,\n\t\t\twidthPx: view.width / view.dpr,\n\t\t\twindowSeconds: view.width / view.pixelsPerSecond,\n\t\t});\n\t\tif (isDirty || timeline.paintKey !== this.#paintedKey) {\n\t\t\tthis.#paintedKey = timeline.paintKey;\n\t\t\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\t\t\tpaintWaveform(\n\t\t\t\tcontext,\n\t\t\t\t{ ...view, colours: look.colours },\n\t\t\t\t{\n\t\t\t\t\tkindColours: this.#kindColours,\n\t\t\t\t\tmarkers: this.markerList(),\n\t\t\t\t\tpeaks: this.#peaks,\n\t\t\t\t\trange,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.#peaksRequest.ask(this.#requestPeaks, timeline.wanted);\n\t\t}\n\t\tif (timeline.isMoving) this.surface()?.requestFrame();\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tconst bounds = this.mapping().bounds;\n\n\t\tconst render = this.#renderLabel;\n\n\t\tthis.#rider.measure(\n\t\t\tthis.markers.flatMap((marker) =>\n\t\t\t\tmarker.label === undefined || marker.label === ''\n\t\t\t\t\t? []\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tisDimmed: marker.dimmed === true,\n\t\t\t\t\t\t\t\t...(marker.kind === undefined ? {} : { kind: marker.kind }),\n\t\t\t\t\t\t\t\tstart: clamp(marker.start, ...bounds),\n\t\t\t\t\t\t\t\ttext: marker.label,\n\t\t\t\t\t\t\t\t...(render\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\twrite: (element: HTMLElement) => {\n\t\t\t\t\t\t\t\t\t\t\t\trender(marker, element);\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t),\n\t\t);\n\t\tthis.surface()?.rebuild();\n\t}\n\n\t#duePending(): Array<[number, number]> {\n\t\tconst due = dueRegions(this.#pending, this.#pendingListedMs, [\n\t\t\tperformance.now(),\n\t\t\tthis.pendingDelay,\n\t\t]);\n\n\t\tif (due.length < this.#pending.length) this.surface()?.requestFrame();\n\n\t\treturn due;\n\t}\n\n\t#grab(): undefined | ValueAxis {\n\t\tif (this.#zoomGesture?.isPinching() === true) return undefined;\n\n\t\tconst drawn = this.#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.scrubState();\n\t\tif (!grabbed || from === undefined) return undefined;\n\n\t\treturn { grabbed, seconds: this.value === from ? grabbed.seconds : this.value };\n\t}\n\n\t#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#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.scrubState().played;\n\t}\n\n\t#zoomTo(target: number): void {\n\t\tconst next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);\n\t\tif (next === this.zoom) return;\n\n\t\tthis.zoom = next;\n\t\tthis.dispatchEvent(new Event('sonic-zoom', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"waveform.js","names":[],"sources":["../../../src/elements/waveform.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Drawn } from '#lib/frame-timeline.ts';\nimport type { PeaksRequest } from '#lib/peaks-request.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Milliseconds, PixelsPerSecond, Seconds } from '#lib/units.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\nimport type { ZoomGesture } from '#lib/zoom-gesture.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { frameTimeline } from '#lib/frame-timeline.ts';\nimport { isKind, writeKind } from '#lib/marker-band.ts';\nimport { createLabelRider } from '#lib/marker-rider.ts';\nimport { clamp, roundTo } from '#lib/math.ts';\nimport { createPeaksRequest } from '#lib/peaks-request.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { paintWaveform, waveformColours } from '#lib/waveform-scene.ts';\nimport { bindZoom, bindZoomKeys } from '#lib/zoom-gesture.ts';\n\nexport type { PeaksRequest } from '#lib/peaks-request.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-zoom': Event;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-waveform': SonicWaveform;\n\t}\n}\n\ntype Colour = keyof typeof waveformColours;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\n\nconst renderWaveform = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-waveform\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-waveform-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-waveform-scrim\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-ghost\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-playhead\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-waveform-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction placeLine(line: HTMLElement, at: number | undefined): void {\n\tconst isHidden = at === undefined || at < 0 || at > 1;\n\n\tif (line.hidden !== isHidden) line.hidden = isHidden;\n\tif (isHidden) return;\n\n\tconst translate = `${String(at * 100)}cqi`;\n\n\tif (line.style.getPropertyValue('translate') !== translate) {\n\t\tline.style.setProperty('translate', translate);\n\t}\n}\n\nexport class SonicWaveform extends SonicWaveElement<Colour> {\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.#peaksRequest.changed();\n\t}\n\n\t/** Spans being fetched; drawn as placeholders until cleared */\n\tget pending(): Array<[Seconds, Seconds]> {\n\t\treturn this.#pending.map(([start, end]) => [start, end]);\n\t}\n\n\tset pending(regions: TimeRegions | undefined) {\n\t\tconst pending = regions ? readRegions(regions) : [];\n\t\tif (String(pending) === String(this.#pending)) return;\n\n\t\tconst listedMs = this.#pendingListedMs;\n\t\tconst nowMs = performance.now();\n\n\t\tthis.#pending = pending;\n\t\tthis.#pendingListedMs = new Map(\n\t\t\tthis.#pending.map((region) => {\n\t\t\t\tconst key = region.join(':');\n\n\t\t\t\treturn [key, listedMs.get(key) ?? nowMs];\n\t\t\t}),\n\t\t);\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\t/** How long a span stays listed in `pending` before its placeholder draws */\n\tget pendingDelay(): Milliseconds {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: Milliseconds | undefined) {\n\t\tthis.reflect('pending-delay', value);\n\t}\n\n\tget playbackRate(): number {\n\t\tconst playbackRate = this.numberAttribute('playback-rate', 1);\n\n\t\treturn playbackRate > 0 ? playbackRate : 1;\n\t}\n\n\tset playbackRate(value: number | undefined) {\n\t\tthis.reflect('playback-rate', value);\n\t}\n\n\tget playing(): boolean {\n\t\treturn this.hasAttribute('playing');\n\t}\n\n\tset playing(isPlaying: boolean) {\n\t\tthis.reflect('playing', isPlaying);\n\t}\n\n\t/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */\n\tget readTime(): (() => Seconds | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => Seconds | undefined) | undefined) {\n\t\tthis.#readTime = read;\n\t\tthis.render();\n\t}\n\n\tget reducedMotion(): 'page' | 'scroll' {\n\t\treturn this.getAttribute('reduced-motion') === 'scroll' ? 'scroll' : 'page';\n\t}\n\n\tset reducedMotion(motion: 'page' | 'scroll' | undefined) {\n\t\tthis.reflect('reduced-motion', motion);\n\t}\n\n\tget renderLabel(): LabelRender | undefined {\n\t\treturn this.#renderLabel;\n\t}\n\n\tset renderLabel(render: LabelRender | undefined) {\n\t\tthis.#renderLabel = render;\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tget requestPeaks(): PeaksRequest | undefined {\n\t\treturn this.#requestPeaks;\n\t}\n\n\tset requestPeaks(request: PeaksRequest | undefined) {\n\t\tthis.#requestPeaks = request;\n\t\tthis.#peaksRequest.reset();\n\t}\n\n\tget zoom(): PixelsPerSecond {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom', value);\n\t}\n\n\tget zoomable(): boolean {\n\t\treturn this.hasAttribute('zoomable');\n\t}\n\n\tset zoomable(isZoomable: boolean) {\n\t\tthis.reflect('zoomable', isZoomable);\n\t}\n\n\tget zoomMax(): PixelsPerSecond {\n\t\tconst zoomMax = this.numberAttribute('zoom-max', defaultZoomMax);\n\n\t\treturn Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);\n\t}\n\n\tset zoomMax(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): PixelsPerSecond {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected readonly control = renderWaveform();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-waveform-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = waveformColours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\treadonly #clock = createTrackingClock();\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#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\treadonly #peaksRequest = createPeaksRequest(() => {\n\t\tthis.surface()?.invalidate();\n\t});\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\n\n\treadonly #playhead = requireChild(this.control, '.sonic-waveform-playhead', HTMLDivElement);\n\n\t#readTime: (() => number | undefined) | undefined;\n\n\t#renderLabel: LabelRender | undefined;\n\n\t#requestPeaks: PeaksRequest | undefined;\n\n\treadonly #rider = createLabelRider(this.control, {\n\t\tclassName: 'sonic-waveform-label',\n\t\tcolourProperty: '--_sonic-waveform-marker',\n\t\tfadeProperties: ['--_sonic-waveform-label-fade-start', '--_sonic-waveform-label-fade-end'],\n\t\tinsetProperty: '--_sonic-waveform-label-inset',\n\t\tparkProperty: '--_sonic-waveform-label-park',\n\t});\n\n\t#zoomGesture: undefined | ZoomGesture;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#zoomGesture?.sync();\n\t}\n\n\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\n\t\tconst zoomTarget = {\n\t\t\tclaim: () => {\n\t\t\t\tthis.abandonHold();\n\t\t\t},\n\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\tisZoomable: () => this.zoomable,\n\t\t\tzoom: () => this.zoom,\n\t\t\tzoomTo: (target: number) => {\n\t\t\t\tthis.#zoomTo(target);\n\t\t\t},\n\t\t};\n\n\t\tthis.#zoomGesture = bindZoom(this.control, zoomTarget, signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\t// After the value keys, which stand down once a key is taken\n\t\tbindZoomKeys(this.control, zoomTarget, signal);\n\t\tif (!('fonts' in document)) return;\n\n\t\tdocument.fonts.addEventListener(\n\t\t\t'loadingdone',\n\t\t\t() => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tsurface?.requestFrame();\n\t\t// No frame follows the position out of view, or before the first one paints\n\t\tif (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());\n\t}\n\n\tprotected 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 range: [number, number] = [this.min, Math.max(this.min, this.max)];\n\t\tconst timeline = frameTimeline({\n\t\t\tclockSeconds: this.#clock.read(frameMs, {\n\t\t\t\tisPlaying: this.playing,\n\t\t\t\tplaybackRate: this.playbackRate,\n\t\t\t\tseconds: this.#sourceSeconds(),\n\t\t\t}),\n\t\t\tframeMs,\n\t\t\t...(held ? { held } : {}),\n\t\t\tisPaged,\n\t\t\tisPlaying: this.playing,\n\t\t\tisStill: look.isReducedMotion,\n\t\t\tpending,\n\t\t\trange,\n\t\t\tsize,\n\t\t\tzoom: this.zoom,\n\t\t});\n\t\tconst { seconds, view } = timeline;\n\n\t\tthis.#drawn = { seconds, startSeconds: view.startSeconds };\n\t\tplaceLine(this.#playhead, timeline.playheadAt);\n\t\tplaceLine(this.#ghost, timeline.ghostAt);\n\t\tthis.showCurrentMarker(seconds);\n\t\tthis.toggleState('pending', view.pending.length > 0);\n\t\tthis.#rider.place({\n\t\t\tisPaged,\n\t\t\tplayheadSeconds: seconds,\n\t\t\tstartSeconds: view.startSeconds,\n\t\t\twidthPx: view.width / view.dpr,\n\t\t\twindowSeconds: view.width / view.pixelsPerSecond,\n\t\t});\n\t\tif (isDirty || timeline.paintKey !== this.#paintedKey) {\n\t\t\tthis.#paintedKey = timeline.paintKey;\n\t\t\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\t\t\tpaintWaveform(\n\t\t\t\tcontext,\n\t\t\t\t{ ...view, colours: look.colours },\n\t\t\t\t{\n\t\t\t\t\tkindColours: this.#kindColours,\n\t\t\t\t\tmarkers: this.markerList(),\n\t\t\t\t\tpeaks: this.#peaks,\n\t\t\t\t\trange,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.#peaksRequest.ask(this.#requestPeaks, timeline.wanted);\n\t\t}\n\t\tif (timeline.isMoving) this.surface()?.requestFrame();\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tconst bounds = this.mapping().bounds;\n\n\t\tconst render = this.#renderLabel;\n\n\t\tthis.#rider.measure(\n\t\t\tthis.markers.flatMap((marker) =>\n\t\t\t\tmarker.label === undefined || marker.label === ''\n\t\t\t\t\t? []\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tisDimmed: marker.dimmed === true,\n\t\t\t\t\t\t\t\t...(marker.kind === undefined ? {} : { kind: marker.kind }),\n\t\t\t\t\t\t\t\tstart: clamp(marker.start, ...bounds),\n\t\t\t\t\t\t\t\ttext: marker.label,\n\t\t\t\t\t\t\t\t...(render\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\twrite: (element: HTMLElement) => {\n\t\t\t\t\t\t\t\t\t\t\t\trender(marker, element);\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t),\n\t\t);\n\t\tthis.surface()?.rebuild();\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tconst isScrubbing = this.scrubState().from !== undefined;\n\n\t\tthis.#grabbed = isScrubbing ? (this.#grabbed ?? this.#lastDrawn()) : undefined;\n\t\tsuper.scrubChanged();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t#duePending(): Array<[number, number]> {\n\t\tconst due = dueRegions(this.#pending, this.#pendingListedMs, [\n\t\t\tperformance.now(),\n\t\t\tthis.pendingDelay,\n\t\t]);\n\n\t\tif (due.length < this.#pending.length) this.surface()?.requestFrame();\n\n\t\treturn due;\n\t}\n\n\t#grab(): undefined | ValueAxis {\n\t\tif (this.#zoomGesture?.isPinching() === true) return undefined;\n\n\t\tconst drawn = this.#lastDrawn();\n\n\t\tthis.#grabbed = drawn;\n\n\t\tconst mapping = this.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn {\n\t\t\tfromProportion: mapping.proportionOf(drawn.seconds),\n\t\t\tisKeptOnCancel: true,\n\t\t\tposition: (event) => -event.clientX,\n\t\t\ttravelPx: Math.max(1, (high - low) * this.zoom),\n\t\t};\n\t}\n\n\t#held(): undefined | { grabbed: Drawn; seconds: number } {\n\t\tconst grabbed = this.#grabbed;\n\t\tconst { from } = this.scrubState();\n\t\tif (!grabbed || from === undefined) return undefined;\n\n\t\treturn { grabbed, seconds: this.value === from ? grabbed.seconds : this.value };\n\t}\n\n\t#lastDrawn(): Drawn {\n\t\treturn this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\t}\n\n\t#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#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.scrubState().played;\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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"}
|
|
@@ -10,11 +10,15 @@ declare global {
|
|
|
10
10
|
export declare class SonicXy extends SonicFormElement {
|
|
11
11
|
#private;
|
|
12
12
|
static readonly observedAttributes: string[];
|
|
13
|
+
get doublePress(): 'none' | 'reset';
|
|
14
|
+
set doublePress(gesture: 'none' | 'reset' | undefined);
|
|
15
|
+
get dragging(): boolean;
|
|
13
16
|
get formatValue(): ((value: number, axis: FieldAxis) => string) | undefined;
|
|
14
17
|
set formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined);
|
|
15
18
|
get pointerType(): string | undefined;
|
|
16
19
|
get readout(): boolean;
|
|
17
20
|
set readout(isEnabled: boolean);
|
|
21
|
+
get revealed(): boolean;
|
|
18
22
|
get x(): number;
|
|
19
23
|
set x(next: null | number | undefined);
|
|
20
24
|
get xDefault(): number | undefined;
|
package/dist/dev/elements/xy.js
CHANGED
|
@@ -4,6 +4,7 @@ import { SonicFormElement } from "./form-element.js";
|
|
|
4
4
|
import { requireChild, template } from "../lib/render.js";
|
|
5
5
|
import { Readout } from "./readout.js";
|
|
6
6
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
7
|
+
import { createDoublePress } from "../lib/double-press.js";
|
|
7
8
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
8
9
|
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
9
10
|
import { resetKeys } from "../lib/value-mapping.js";
|
|
@@ -66,6 +67,15 @@ var SonicXy = class extends SonicFormElement {
|
|
|
66
67
|
"y-step",
|
|
67
68
|
"y-taper"
|
|
68
69
|
];
|
|
70
|
+
get doublePress() {
|
|
71
|
+
return this.getAttribute("double-press") === "reset" ? "reset" : "none";
|
|
72
|
+
}
|
|
73
|
+
set doublePress(gesture) {
|
|
74
|
+
this.reflect("double-press", gesture);
|
|
75
|
+
}
|
|
76
|
+
get dragging() {
|
|
77
|
+
return this.hasState("dragging");
|
|
78
|
+
}
|
|
69
79
|
get formatValue() {
|
|
70
80
|
return this.#formatValue;
|
|
71
81
|
}
|
|
@@ -82,6 +92,9 @@ var SonicXy = class extends SonicFormElement {
|
|
|
82
92
|
set readout(isEnabled) {
|
|
83
93
|
this.reflect("readout", isEnabled);
|
|
84
94
|
}
|
|
95
|
+
get revealed() {
|
|
96
|
+
return this.hasState("revealed");
|
|
97
|
+
}
|
|
85
98
|
get x() {
|
|
86
99
|
return this.#models.x.value;
|
|
87
100
|
}
|
|
@@ -194,10 +207,12 @@ var SonicXy = class extends SonicFormElement {
|
|
|
194
207
|
this.#renderReadout();
|
|
195
208
|
});
|
|
196
209
|
#currentAxis = "x";
|
|
210
|
+
#doublePress = createDoublePress();
|
|
197
211
|
#xy = renderXy();
|
|
198
212
|
#field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
|
|
199
213
|
#formatValue;
|
|
200
214
|
#gesture;
|
|
215
|
+
#isRevealed = false;
|
|
201
216
|
#models = {
|
|
202
217
|
x: createValueModel(() => this.#spec("x")),
|
|
203
218
|
y: createValueModel(() => this.#spec("y"))
|
|
@@ -214,10 +229,11 @@ var SonicXy = class extends SonicFormElement {
|
|
|
214
229
|
this.#respec(name);
|
|
215
230
|
}
|
|
216
231
|
connectedCallback() {
|
|
217
|
-
this.upgradeProperties("readout", "xDefault", "xLabel", "xMax", "xMidpoint", "xMin", "xOrigin", "xStep", "xTaper", "yDefault", "yLabel", "yMax", "yMidpoint", "yMin", "yOrigin", "yStep", "yTaper", "formatValue", "x", "y");
|
|
232
|
+
this.upgradeProperties("doublePress", "readout", "xDefault", "xLabel", "xMax", "xMidpoint", "xMin", "xOrigin", "xStep", "xTaper", "yDefault", "yLabel", "yMax", "yMidpoint", "yMin", "yOrigin", "yStep", "yTaper", "formatValue", "x", "y");
|
|
218
233
|
super.connectedCallback();
|
|
219
234
|
}
|
|
220
235
|
formResetCallback() {
|
|
236
|
+
if (this.#gesture?.current()) return;
|
|
221
237
|
this.#respec(...axes);
|
|
222
238
|
}
|
|
223
239
|
connect(signal) {
|
|
@@ -225,18 +241,8 @@ var SonicXy = class extends SonicFormElement {
|
|
|
225
241
|
this.keepControl(xy, signal);
|
|
226
242
|
this.render();
|
|
227
243
|
this.checkStyles(xy, "xy.css", {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
selector: "[popover]"
|
|
231
|
-
},
|
|
232
|
-
material: [
|
|
233
|
-
"cap",
|
|
234
|
-
"glass",
|
|
235
|
-
"pane",
|
|
236
|
-
"well",
|
|
237
|
-
"readout",
|
|
238
|
-
["bracket", ".sonic-xy-bracket"]
|
|
239
|
-
]
|
|
244
|
+
property: "margin-bottom",
|
|
245
|
+
selector: "[popover]"
|
|
240
246
|
});
|
|
241
247
|
this.#bindPointer(xy, signal);
|
|
242
248
|
this.#bindKeys(xy, signal);
|
|
@@ -273,23 +279,26 @@ var SonicXy = class extends SonicFormElement {
|
|
|
273
279
|
const move = keyMoves.get(event.key);
|
|
274
280
|
if (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;
|
|
275
281
|
event.preventDefault();
|
|
276
|
-
if (move) this.#keyTo(...move);
|
|
277
|
-
else this.#reset();
|
|
278
282
|
this.#claim.reveal("keys");
|
|
279
283
|
this.#renderReadout();
|
|
284
|
+
if (move) this.#keyTo(...move);
|
|
285
|
+
else this.#reset();
|
|
280
286
|
}, { signal });
|
|
281
287
|
xy.addEventListener("focusout", (event) => {
|
|
282
288
|
if (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;
|
|
283
289
|
if (this.#claim.conceal("keys")) this.#renderReadout();
|
|
284
290
|
}, { signal });
|
|
285
291
|
signal.addEventListener("abort", () => {
|
|
286
|
-
this.#claim.conceal("keys");
|
|
292
|
+
if (this.#claim.conceal("keys")) this.#renderReadout();
|
|
287
293
|
}, { once: true });
|
|
288
294
|
}
|
|
289
295
|
#bindPointer(xy, signal) {
|
|
290
296
|
xy.addEventListener("mousedown", (event) => {
|
|
291
297
|
event.preventDefault();
|
|
292
298
|
}, { signal });
|
|
299
|
+
xy.addEventListener("pointercancel", () => {
|
|
300
|
+
this.#doublePress.forget();
|
|
301
|
+
}, { signal });
|
|
293
302
|
this.#gesture = bindFieldGesture(xy, {
|
|
294
303
|
claim: this.#claim,
|
|
295
304
|
grab: (event) => {
|
|
@@ -304,6 +313,10 @@ var SonicXy = class extends SonicFormElement {
|
|
|
304
313
|
input: (_hold, next) => {
|
|
305
314
|
if (!this.#input(next)) this.#renderReadout();
|
|
306
315
|
},
|
|
316
|
+
lift: (_hold, event) => {
|
|
317
|
+
if (!this.#doublePress.press(event) || this.doublePress !== "reset") return;
|
|
318
|
+
this.#reset();
|
|
319
|
+
},
|
|
307
320
|
release: (_hold, moved) => {
|
|
308
321
|
if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
309
322
|
},
|
|
@@ -407,11 +420,16 @@ var SonicXy = class extends SonicFormElement {
|
|
|
407
420
|
this.writeFormValue(fields, state);
|
|
408
421
|
}
|
|
409
422
|
#renderReadout() {
|
|
423
|
+
const { isRevealed } = this.#claim;
|
|
410
424
|
this.#readout.show({
|
|
411
425
|
anchor: this.#puck,
|
|
412
|
-
isOpen:
|
|
426
|
+
isOpen: isRevealed && this.readout,
|
|
413
427
|
text: this.#valueText("x")
|
|
414
428
|
});
|
|
429
|
+
if (isRevealed === this.#isRevealed) return;
|
|
430
|
+
this.#isRevealed = isRevealed;
|
|
431
|
+
this.toggleState("revealed", isRevealed);
|
|
432
|
+
this.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
|
|
415
433
|
}
|
|
416
434
|
#reset() {
|
|
417
435
|
this.#commit({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldGesture, FieldHold, FieldInput } from '#elements/field-gesture.ts';\nimport type { FieldAxis } from '#lib/field.ts';\nimport type { ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { pointerPosition } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion, toNumber } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\nconst axes = ['x', 'y'] as const;\n\nconst keyMoves = new Map<string, [FieldAxis, string]>([\n\t['ArrowDown', ['y', 'ArrowDown']],\n\t['ArrowLeft', ['x', 'ArrowLeft']],\n\t['ArrowRight', ['x', 'ArrowRight']],\n\t['ArrowUp', ['y', 'ArrowUp']],\n\t['End', ['x', 'PageUp']],\n\t['Home', ['x', 'PageDown']],\n\t['PageDown', ['y', 'PageDown']],\n\t['PageUp', ['y', 'PageUp']],\n]);\n\nconst orientations = { x: 'horizontal', y: 'vertical' } as const;\n\nconst renderXy = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-xy\">\n\t\t\t<div class=\"sonic-xy-bracket\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-field\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-puck\">\n\t\t\t\t<div class=\"sonic-xy-axis\" data-sonic-axis=\"x\" role=\"slider\" tabindex=\"0\"></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"sonic-xy-axis\"\n\t\t\t\t\tdata-sonic-axis=\"y\"\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t\t<div class=\"sonic-xy-readout\" aria-hidden=\"true\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicXy extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'name',\n\t\t'readout',\n\t\t'x',\n\t\t'x-label',\n\t\t'x-max',\n\t\t'x-midpoint',\n\t\t'x-min',\n\t\t'x-origin',\n\t\t'x-step',\n\t\t'x-taper',\n\t\t'y',\n\t\t'y-label',\n\t\t'y-max',\n\t\t'y-midpoint',\n\t\t'y-min',\n\t\t'y-origin',\n\t\t'y-step',\n\t\t'y-taper',\n\t];\n\n\tget formatValue(): ((value: number, axis: FieldAxis) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isEnabled: boolean) {\n\t\tthis.reflect('readout', isEnabled);\n\t}\n\n\tget x(): number {\n\t\treturn this.#models.x.value;\n\t}\n\n\tset x(next: null | number | undefined) {\n\t\tthis.#write('x', next);\n\t}\n\n\tget xDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-default');\n\t}\n\n\tset xDefault(value: number | undefined) {\n\t\tthis.reflect('x-default', value);\n\t}\n\n\tget xLabel(): string | undefined {\n\t\treturn this.getAttribute('x-label') ?? undefined;\n\t}\n\n\tset xLabel(label: string | undefined) {\n\t\tthis.reflect('x-label', label);\n\t}\n\n\tget xMax(): number {\n\t\treturn this.numberAttribute('x-max', 100);\n\t}\n\n\tset xMax(value: number | undefined) {\n\t\tthis.reflect('x-max', value);\n\t}\n\n\tget xMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-midpoint');\n\t}\n\n\tset xMidpoint(value: number | undefined) {\n\t\tthis.reflect('x-midpoint', value);\n\t}\n\n\tget xMin(): number {\n\t\treturn this.numberAttribute('x-min', 0);\n\t}\n\n\tset xMin(value: number | undefined) {\n\t\tthis.reflect('x-min', value);\n\t}\n\n\tget xOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-origin');\n\t}\n\n\tset xOrigin(value: number | undefined) {\n\t\tthis.reflect('x-origin', value);\n\t}\n\n\tget xStep(): number {\n\t\treturn this.numberAttribute('x-step', 1);\n\t}\n\n\tset xStep(value: number | undefined) {\n\t\tthis.reflect('x-step', value);\n\t}\n\n\tget xTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('x');\n\t}\n\n\tset xTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('x-taper', curve);\n\t}\n\n\tget y(): number {\n\t\treturn this.#models.y.value;\n\t}\n\n\tset y(next: null | number | undefined) {\n\t\tthis.#write('y', next);\n\t}\n\n\tget yDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-default');\n\t}\n\n\tset yDefault(value: number | undefined) {\n\t\tthis.reflect('y-default', value);\n\t}\n\n\tget yLabel(): string | undefined {\n\t\treturn this.getAttribute('y-label') ?? undefined;\n\t}\n\n\tset yLabel(label: string | undefined) {\n\t\tthis.reflect('y-label', label);\n\t}\n\n\tget yMax(): number {\n\t\treturn this.numberAttribute('y-max', 100);\n\t}\n\n\tset yMax(value: number | undefined) {\n\t\tthis.reflect('y-max', value);\n\t}\n\n\tget yMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-midpoint');\n\t}\n\n\tset yMidpoint(value: number | undefined) {\n\t\tthis.reflect('y-midpoint', value);\n\t}\n\n\tget yMin(): number {\n\t\treturn this.numberAttribute('y-min', 0);\n\t}\n\n\tset yMin(value: number | undefined) {\n\t\tthis.reflect('y-min', value);\n\t}\n\n\tget yOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-origin');\n\t}\n\n\tset yOrigin(value: number | undefined) {\n\t\tthis.reflect('y-origin', value);\n\t}\n\n\tget yStep(): number {\n\t\treturn this.numberAttribute('y-step', 1);\n\t}\n\n\tset yStep(value: number | undefined) {\n\t\tthis.reflect('y-step', value);\n\t}\n\n\tget yTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('y');\n\t}\n\n\tset yTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('y-taper', curve);\n\t}\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\t#currentAxis: FieldAxis = 'x';\n\n\treadonly #xy = renderXy();\n\n\treadonly #field = requireChild(this.#xy, '.sonic-xy-field', HTMLDivElement);\n\n\t#formatValue: ((value: number, axis: FieldAxis) => string) | undefined;\n\n\t#gesture: FieldGesture<FieldHold> | undefined;\n\n\treadonly #models: Record<FieldAxis, ValueModel> = {\n\t\tx: createValueModel(() => this.#spec('x')),\n\t\ty: createValueModel(() => this.#spec('y')),\n\t};\n\n\treadonly #parts: Record<FieldAxis, HTMLDivElement> = {\n\t\tx: requireChild(this.#xy, '[data-sonic-axis=\"x\"]', HTMLDivElement),\n\t\ty: requireChild(this.#xy, '[data-sonic-axis=\"y\"]', HTMLDivElement),\n\t};\n\n\treadonly #puck = requireChild(this.#xy, '.sonic-xy-puck', HTMLDivElement);\n\n\treadonly #readout = new Readout(requireChild(this.#xy, '.sonic-xy-readout', HTMLDivElement));\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'x' || name === 'y') && this.#gesture?.current()) return;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gesture?.end();\n\n\t\tthis.#respec(name);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'readout',\n\t\t\t'xDefault',\n\t\t\t'xLabel',\n\t\t\t'xMax',\n\t\t\t'xMidpoint',\n\t\t\t'xMin',\n\t\t\t'xOrigin',\n\t\t\t'xStep',\n\t\t\t'xTaper',\n\t\t\t'yDefault',\n\t\t\t'yLabel',\n\t\t\t'yMax',\n\t\t\t'yMidpoint',\n\t\t\t'yMin',\n\t\t\t'yOrigin',\n\t\t\t'yStep',\n\t\t\t'yTaper',\n\t\t\t'formatValue',\n\t\t\t'x',\n\t\t\t'y',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.#respec(...axes);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.#xy;\n\n\t\tthis.keepControl(xy, signal);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(xy, 'xy.css', {\n\t\t\t\tbox: { property: 'margin-bottom', selector: '[popover]' },\n\t\t\t\tmaterial: ['cap', 'glass', 'pane', 'well', 'readout', ['bracket', '.sonic-xy-bracket']],\n\t\t\t});\n\t\tthis.#bindPointer(xy, signal);\n\t\tthis.#bindKeys(xy, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#parts[this.#currentAxis];\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst { style } = this.#xy;\n\n\t\tfor (const axis of axes) {\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst mapping = model.mapping();\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));\n\t\t}\n\t\tthis.#renderAria();\n\t\tthis.#renderForm();\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [x = NaN, y = NaN] = state.split(',').map(Number);\n\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\t#axisText(axis: FieldAxis): string {\n\t\tconst { value } = this.#models[axis];\n\t\tconst label = this.getAttribute(`${axis}-label`) ?? axis.toUpperCase();\n\n\t\treturn `${label} ${this.#formatValue?.(value, axis) ?? String(value)}`;\n\t}\n\n\t#bindKeys(xy: HTMLElement, signal: AbortSignal): void {\n\t\txy.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented) return;\n\n\t\t\t\tconst move = keyMoves.get(event.key);\n\t\t\t\tif (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tif (move) this.#keyTo(...move);\n\t\t\t\telse this.#reset();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#renderReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\t// A key on the other axis moves focus between the two parts\n\t\t\t\tif (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#claim.conceal('keys');\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bindPointer(xy: HTMLElement, signal: AbortSignal): void {\n\t\t// A press on the glass would take focus off the axis part and select the page's text\n\t\txy.addEventListener(\n\t\t\t'mousedown',\n\t\t\t(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.isDisabled() || isMenuPress(event)) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tif (!isResetPress(event)) {\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\n\t\t\t\t\t\treturn this.#grab(event);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tinput: (_hold, next) => {\n\t\t\t\t\tif (!this.#input(next)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\trelease: (_hold, moved) => {\n\t\t\t\t\tif (moved.length > 0) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#commit(next: FieldInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): FieldHold {\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst { x, y } = this.#models;\n\t\tconst from = { x: x.value, y: y.value };\n\t\tconst travel = {\n\t\t\tx: Math.max(1, field.width - puck.width),\n\t\t\ty: Math.max(1, field.height - puck.height),\n\t\t};\n\t\tconst position = pointerPosition(event);\n\t\tconst pressed = {\n\t\t\tx: (position.x - field.left - puck.width / 2) / travel.x,\n\t\t\ty: (position.y + field.bottom - puck.height / 2) / travel.y,\n\t\t};\n\t\tconst isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);\n\n\t\tif (!isOnPuck) {\n\t\t\tthis.#input({ x: x.mapping().valueAt(pressed.x), y: y.mapping().valueAt(pressed.y) });\n\t\t}\n\n\t\tconst hold = (axis: FieldAxis) => ({\n\t\t\tfrom: from[axis],\n\t\t\tmodel: this.#models[axis],\n\t\t\t...(isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) }),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn { axes: { x: hold('x'), y: hold('y') } };\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: FieldInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;\n\t\t}\n\t\tif (!hasMoved) return false;\n\n\t\tthis.render();\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\t#keyTo(axis: FieldAxis, key: string): void {\n\t\tconst model = this.#models[axis];\n\t\tconst hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);\n\n\t\tthis.#currentAxis = axis;\n\t\tthis.render();\n\t\tthis.#parts[axis].focus();\n\t\tif (!hasMoved) return;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#renderAria(): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst part = this.#parts[axis];\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst [low, high] = model.mapping().bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\twriteAttribute(part, 'aria-orientation', orientations[axis]);\n\t\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\t\twriteAttribute(part, 'aria-valuenow', String(model.value));\n\t\t\twriteAttribute(part, 'aria-valuetext', this.#valueText(axis));\n\t\t\tthis.forwardNaming(part, true);\n\t\t\twriteAttribute(part, 'aria-hidden', isCurrent ? undefined : 'true');\n\t\t\twriteAttribute(part, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t\twriteAttribute(part, 'tabindex', isDisabled ? undefined : stop);\n\t\t}\n\t}\n\n\t#renderForm(): void {\n\t\tconst { name, x, y } = this;\n\t\tconst state = `${String(x)},${String(y)}`;\n\n\t\tif (name === '') {\n\t\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\t\tthis.writeFormValue(null, state);\n\t\t\treturn;\n\t\t}\n\n\t\tconst fields = new FormData();\n\n\t\tfields.set(`${name}.x`, String(x));\n\t\tfields.set(`${name}.y`, String(y));\n\t\tthis.writeFormValue(fields, state);\n\t}\n\n\t#renderReadout(): void {\n\t\tthis.#readout.show({\n\t\t\tanchor: this.#puck,\n\t\t\tisOpen: this.#claim.isRevealed && this.readout,\n\t\t\ttext: this.#valueText('x'),\n\t\t});\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit({ x: this.xDefault, y: this.yDefault });\n\t}\n\n\t#respec(...forgotten: Array<string>): void {\n\t\tfor (const axis of axes) {\n\t\t\tthis.#models[axis].respec(\n\t\t\t\tthis.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)),\n\t\t\t\t{ forgetAsk: forgotten.includes(axis) },\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#spec(axis: FieldAxis): ValueSpec {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn {\n\t\t\tmax: this.numberAttribute(`${axis}-max`, 100),\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: false,\n\t\t\tisWrapping: false,\n\t\t\tmin: this.numberAttribute(`${axis}-min`, 0),\n\t\t\tstep: this.numberAttribute(`${axis}-step`, 1),\n\t\t\ttaper: this.#taper(axis),\n\t\t};\n\t}\n\n\t#taper(axis: FieldAxis): ValueSpec['taper'] {\n\t\treturn this.getAttribute(`${axis}-taper`) === 'log' ? 'log' : 'linear';\n\t}\n\n\t#valueText(axis: FieldAxis): string {\n\t\treturn `${this.#axisText(axis)}, ${this.#axisText(axis === 'x' ? 'y' : 'x')}`;\n\t}\n\n\t#write(axis: FieldAxis, next: null | number | undefined): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.#respec(axis);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#models[axis].write(toNumber(next))) this.render();\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldGesture, FieldHold, FieldInput } from '#elements/field-gesture.ts';\nimport type { FieldAxis } from '#lib/field.ts';\nimport type { ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { createDoublePress } from '#lib/double-press.ts';\nimport { pointerPosition } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion, toNumber } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\nconst axes = ['x', 'y'] as const;\n\nconst keyMoves = new Map<string, [FieldAxis, string]>([\n\t['ArrowDown', ['y', 'ArrowDown']],\n\t['ArrowLeft', ['x', 'ArrowLeft']],\n\t['ArrowRight', ['x', 'ArrowRight']],\n\t['ArrowUp', ['y', 'ArrowUp']],\n\t['End', ['x', 'PageUp']],\n\t['Home', ['x', 'PageDown']],\n\t['PageDown', ['y', 'PageDown']],\n\t['PageUp', ['y', 'PageUp']],\n]);\n\nconst orientations = { x: 'horizontal', y: 'vertical' } as const;\n\nconst renderXy = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-xy\">\n\t\t\t<div class=\"sonic-xy-bracket\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-field\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-puck\">\n\t\t\t\t<div class=\"sonic-xy-axis\" data-sonic-axis=\"x\" role=\"slider\" tabindex=\"0\"></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"sonic-xy-axis\"\n\t\t\t\t\tdata-sonic-axis=\"y\"\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t\t<div class=\"sonic-xy-readout\" aria-hidden=\"true\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicXy extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'name',\n\t\t'readout',\n\t\t'x',\n\t\t'x-label',\n\t\t'x-max',\n\t\t'x-midpoint',\n\t\t'x-min',\n\t\t'x-origin',\n\t\t'x-step',\n\t\t'x-taper',\n\t\t'y',\n\t\t'y-label',\n\t\t'y-max',\n\t\t'y-midpoint',\n\t\t'y-min',\n\t\t'y-origin',\n\t\t'y-step',\n\t\t'y-taper',\n\t];\n\n\tget doublePress(): 'none' | 'reset' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'none';\n\t}\n\n\tset doublePress(gesture: 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget formatValue(): ((value: number, axis: FieldAxis) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isEnabled: boolean) {\n\t\tthis.reflect('readout', isEnabled);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\tget x(): number {\n\t\treturn this.#models.x.value;\n\t}\n\n\tset x(next: null | number | undefined) {\n\t\tthis.#write('x', next);\n\t}\n\n\tget xDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-default');\n\t}\n\n\tset xDefault(value: number | undefined) {\n\t\tthis.reflect('x-default', value);\n\t}\n\n\tget xLabel(): string | undefined {\n\t\treturn this.getAttribute('x-label') ?? undefined;\n\t}\n\n\tset xLabel(label: string | undefined) {\n\t\tthis.reflect('x-label', label);\n\t}\n\n\tget xMax(): number {\n\t\treturn this.numberAttribute('x-max', 100);\n\t}\n\n\tset xMax(value: number | undefined) {\n\t\tthis.reflect('x-max', value);\n\t}\n\n\tget xMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-midpoint');\n\t}\n\n\tset xMidpoint(value: number | undefined) {\n\t\tthis.reflect('x-midpoint', value);\n\t}\n\n\tget xMin(): number {\n\t\treturn this.numberAttribute('x-min', 0);\n\t}\n\n\tset xMin(value: number | undefined) {\n\t\tthis.reflect('x-min', value);\n\t}\n\n\tget xOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-origin');\n\t}\n\n\tset xOrigin(value: number | undefined) {\n\t\tthis.reflect('x-origin', value);\n\t}\n\n\tget xStep(): number {\n\t\treturn this.numberAttribute('x-step', 1);\n\t}\n\n\tset xStep(value: number | undefined) {\n\t\tthis.reflect('x-step', value);\n\t}\n\n\tget xTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('x');\n\t}\n\n\tset xTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('x-taper', curve);\n\t}\n\n\tget y(): number {\n\t\treturn this.#models.y.value;\n\t}\n\n\tset y(next: null | number | undefined) {\n\t\tthis.#write('y', next);\n\t}\n\n\tget yDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-default');\n\t}\n\n\tset yDefault(value: number | undefined) {\n\t\tthis.reflect('y-default', value);\n\t}\n\n\tget yLabel(): string | undefined {\n\t\treturn this.getAttribute('y-label') ?? undefined;\n\t}\n\n\tset yLabel(label: string | undefined) {\n\t\tthis.reflect('y-label', label);\n\t}\n\n\tget yMax(): number {\n\t\treturn this.numberAttribute('y-max', 100);\n\t}\n\n\tset yMax(value: number | undefined) {\n\t\tthis.reflect('y-max', value);\n\t}\n\n\tget yMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-midpoint');\n\t}\n\n\tset yMidpoint(value: number | undefined) {\n\t\tthis.reflect('y-midpoint', value);\n\t}\n\n\tget yMin(): number {\n\t\treturn this.numberAttribute('y-min', 0);\n\t}\n\n\tset yMin(value: number | undefined) {\n\t\tthis.reflect('y-min', value);\n\t}\n\n\tget yOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-origin');\n\t}\n\n\tset yOrigin(value: number | undefined) {\n\t\tthis.reflect('y-origin', value);\n\t}\n\n\tget yStep(): number {\n\t\treturn this.numberAttribute('y-step', 1);\n\t}\n\n\tset yStep(value: number | undefined) {\n\t\tthis.reflect('y-step', value);\n\t}\n\n\tget yTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('y');\n\t}\n\n\tset yTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('y-taper', curve);\n\t}\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\t#currentAxis: FieldAxis = 'x';\n\n\treadonly #doublePress = createDoublePress();\n\n\treadonly #xy = renderXy();\n\n\treadonly #field = requireChild(this.#xy, '.sonic-xy-field', HTMLDivElement);\n\n\t#formatValue: ((value: number, axis: FieldAxis) => string) | undefined;\n\n\t#gesture: FieldGesture<FieldHold> | undefined;\n\n\t#isRevealed = false;\n\n\treadonly #models: Record<FieldAxis, ValueModel> = {\n\t\tx: createValueModel(() => this.#spec('x')),\n\t\ty: createValueModel(() => this.#spec('y')),\n\t};\n\n\treadonly #parts: Record<FieldAxis, HTMLDivElement> = {\n\t\tx: requireChild(this.#xy, '[data-sonic-axis=\"x\"]', HTMLDivElement),\n\t\ty: requireChild(this.#xy, '[data-sonic-axis=\"y\"]', HTMLDivElement),\n\t};\n\n\treadonly #puck = requireChild(this.#xy, '.sonic-xy-puck', HTMLDivElement);\n\n\treadonly #readout = new Readout(requireChild(this.#xy, '.sonic-xy-readout', HTMLDivElement));\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'x' || name === 'y') && this.#gesture?.current()) return;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gesture?.end();\n\n\t\tthis.#respec(name);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'doublePress',\n\t\t\t'readout',\n\t\t\t'xDefault',\n\t\t\t'xLabel',\n\t\t\t'xMax',\n\t\t\t'xMidpoint',\n\t\t\t'xMin',\n\t\t\t'xOrigin',\n\t\t\t'xStep',\n\t\t\t'xTaper',\n\t\t\t'yDefault',\n\t\t\t'yLabel',\n\t\t\t'yMax',\n\t\t\t'yMidpoint',\n\t\t\t'yMin',\n\t\t\t'yOrigin',\n\t\t\t'yStep',\n\t\t\t'yTaper',\n\t\t\t'formatValue',\n\t\t\t'x',\n\t\t\t'y',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\tthis.#respec(...axes);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.#xy;\n\n\t\tthis.keepControl(xy, signal);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(xy, 'xy.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.#bindPointer(xy, signal);\n\t\tthis.#bindKeys(xy, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#parts[this.#currentAxis];\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst { style } = this.#xy;\n\n\t\tfor (const axis of axes) {\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst mapping = model.mapping();\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));\n\t\t}\n\t\tthis.#renderAria();\n\t\tthis.#renderForm();\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [x = NaN, y = NaN] = state.split(',').map(Number);\n\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\t#axisText(axis: FieldAxis): string {\n\t\tconst { value } = this.#models[axis];\n\t\tconst label = this.getAttribute(`${axis}-label`) ?? axis.toUpperCase();\n\n\t\treturn `${label} ${this.#formatValue?.(value, axis) ?? String(value)}`;\n\t}\n\n\t#bindKeys(xy: HTMLElement, signal: AbortSignal): void {\n\t\txy.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented) return;\n\n\t\t\t\tconst move = keyMoves.get(event.key);\n\t\t\t\tif (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#renderReadout();\n\t\t\t\tif (move) this.#keyTo(...move);\n\t\t\t\telse this.#reset();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\t// A key on the other axis moves focus between the two parts\n\t\t\t\tif (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bindPointer(xy: HTMLElement, signal: AbortSignal): void {\n\t\t// A press on the field would take focus off the axis part and select the page's text\n\t\txy.addEventListener(\n\t\t\t'mousedown',\n\t\t\t(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t() => {\n\t\t\t\tthis.#doublePress.forget();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.isDisabled() || isMenuPress(event)) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tif (!isResetPress(event)) {\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\n\t\t\t\t\t\treturn this.#grab(event);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tinput: (_hold, next) => {\n\t\t\t\t\tif (!this.#input(next)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\tlift: (_hold, event) => {\n\t\t\t\t\tif (!this.#doublePress.press(event) || this.doublePress !== 'reset') return;\n\n\t\t\t\t\tthis.#reset();\n\t\t\t\t},\n\t\t\t\trelease: (_hold, moved) => {\n\t\t\t\t\tif (moved.length > 0) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#commit(next: FieldInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): FieldHold {\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst { x, y } = this.#models;\n\t\tconst from = { x: x.value, y: y.value };\n\t\tconst travel = {\n\t\t\tx: Math.max(1, field.width - puck.width),\n\t\t\ty: Math.max(1, field.height - puck.height),\n\t\t};\n\t\tconst position = pointerPosition(event);\n\t\tconst pressed = {\n\t\t\tx: (position.x - field.left - puck.width / 2) / travel.x,\n\t\t\ty: (position.y + field.bottom - puck.height / 2) / travel.y,\n\t\t};\n\t\tconst isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);\n\n\t\tif (!isOnPuck) {\n\t\t\tthis.#input({ x: x.mapping().valueAt(pressed.x), y: y.mapping().valueAt(pressed.y) });\n\t\t}\n\n\t\tconst hold = (axis: FieldAxis) => ({\n\t\t\tfrom: from[axis],\n\t\t\tmodel: this.#models[axis],\n\t\t\t...(isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) }),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn { axes: { x: hold('x'), y: hold('y') } };\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: FieldInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;\n\t\t}\n\t\tif (!hasMoved) return false;\n\n\t\tthis.render();\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\t#keyTo(axis: FieldAxis, key: string): void {\n\t\tconst model = this.#models[axis];\n\t\tconst hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);\n\n\t\tthis.#currentAxis = axis;\n\t\tthis.render();\n\t\tthis.#parts[axis].focus();\n\t\tif (!hasMoved) return;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#renderAria(): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst part = this.#parts[axis];\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst [low, high] = model.mapping().bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\twriteAttribute(part, 'aria-orientation', orientations[axis]);\n\t\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\t\twriteAttribute(part, 'aria-valuenow', String(model.value));\n\t\t\twriteAttribute(part, 'aria-valuetext', this.#valueText(axis));\n\t\t\tthis.forwardNaming(part, true);\n\t\t\twriteAttribute(part, 'aria-hidden', isCurrent ? undefined : 'true');\n\t\t\twriteAttribute(part, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t\twriteAttribute(part, 'tabindex', isDisabled ? undefined : stop);\n\t\t}\n\t}\n\n\t#renderForm(): void {\n\t\tconst { name, x, y } = this;\n\t\tconst state = `${String(x)},${String(y)}`;\n\n\t\tif (name === '') {\n\t\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\t\tthis.writeFormValue(null, state);\n\t\t\treturn;\n\t\t}\n\n\t\tconst fields = new FormData();\n\n\t\tfields.set(`${name}.x`, String(x));\n\t\tfields.set(`${name}.y`, String(y));\n\t\tthis.writeFormValue(fields, state);\n\t}\n\n\t#renderReadout(): void {\n\t\tconst { isRevealed } = this.#claim;\n\n\t\tthis.#readout.show({\n\t\t\tanchor: this.#puck,\n\t\t\tisOpen: isRevealed && this.readout,\n\t\t\ttext: this.#valueText('x'),\n\t\t});\n\t\tif (isRevealed === this.#isRevealed) return;\n\n\t\tthis.#isRevealed = isRevealed;\n\t\tthis.toggleState('revealed', isRevealed);\n\t\tthis.dispatchEvent(new Event('sonic-reveal', { bubbles: true }));\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit({ x: this.xDefault, y: this.yDefault });\n\t}\n\n\t#respec(...forgotten: Array<string>): void {\n\t\tfor (const axis of axes) {\n\t\t\tthis.#models[axis].respec(\n\t\t\t\tthis.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)),\n\t\t\t\t{ forgetAsk: forgotten.includes(axis) },\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#spec(axis: FieldAxis): ValueSpec {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn {\n\t\t\tmax: this.numberAttribute(`${axis}-max`, 100),\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: false,\n\t\t\tisWrapping: false,\n\t\t\tmin: this.numberAttribute(`${axis}-min`, 0),\n\t\t\tstep: this.numberAttribute(`${axis}-step`, 1),\n\t\t\ttaper: this.#taper(axis),\n\t\t};\n\t}\n\n\t#taper(axis: FieldAxis): ValueSpec['taper'] {\n\t\treturn this.getAttribute(`${axis}-taper`) === 'log' ? 'log' : 'linear';\n\t}\n\n\t#valueText(axis: FieldAxis): string {\n\t\treturn `${this.#axisText(axis)}, ${this.#axisText(axis === 'x' ? 'y' : 'x')}`;\n\t}\n\n\t#write(axis: FieldAxis, next: null | number | undefined): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.#respec(axis);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#models[axis].write(toNumber(next))) this.render();\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,MAAM,OAAO,CAAC,KAAK,GAAG;AAEtB,MAAM,2BAAW,IAAI,IAAiC;CACrD,CAAC,aAAa,CAAC,KAAK,WAAW,CAAC;CAChC,CAAC,aAAa,CAAC,KAAK,WAAW,CAAC;CAChC,CAAC,cAAc,CAAC,KAAK,YAAY,CAAC;CAClC,CAAC,WAAW,CAAC,KAAK,SAAS,CAAC;CAC5B,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;CACvB,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC;CAC1B,CAAC,YAAY,CAAC,KAAK,UAAU,CAAC;CAC9B,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC;AAC3B,CAAC;AAED,MAAM,eAAe;CAAE,GAAG;CAAc,GAAG;AAAW;AAEtD,MAAM,WAAW,SACL;;;;;;;;;;;;;;;;IAiBX,cACD;AAEA,IAAa,UAAb,cAA6B,iBAAiB;CAC7C,OAAyB,qBAAqB;EAC7C,GAAG,iBAAiB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD;CAEA,IAAI,cAAgC;EACnC,OAAO,KAAK,aAAa,cAAc,MAAM,UAAU,UAAU;CAClE;CAEA,IAAI,YAAY,SAAuC;EACtD,KAAK,QAAQ,gBAAgB,OAAO;CACrC;CAEA,IAAI,WAAoB;EACvB,OAAO,KAAK,SAAS,UAAU;CAChC;CAEA,IAAI,cAAwE;EAC3E,OAAO,KAAK;CACb;CAEA,IAAI,YAAY,QAAkE;EACjF,KAAK,eAAe;EACpB,KAAK,OAAO;CACb;CAGA,IAAI,cAAkC;EACrC,OAAO,KAAK,UAAU,YAAY;CACnC;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAEA,IAAI,WAAoB;EACvB,OAAO,KAAK,SAAS,UAAU;CAChC;CAEA,IAAI,IAAY;EACf,OAAO,KAAK,QAAQ,EAAE;CACvB;CAEA,IAAI,EAAE,MAAiC;EACtC,KAAK,OAAO,KAAK,IAAI;CACtB;CAEA,IAAI,WAA+B;EAClC,OAAO,KAAK,wBAAwB,WAAW;CAChD;CAEA,IAAI,SAAS,OAA2B;EACvC,KAAK,QAAQ,aAAa,KAAK;CAChC;CAEA,IAAI,SAA6B;EAChC,OAAO,KAAK,aAAa,SAAS,KAAK;CACxC;CAEA,IAAI,OAAO,OAA2B;EACrC,KAAK,QAAQ,WAAW,KAAK;CAC9B;CAEA,IAAI,OAAe;EAClB,OAAO,KAAK,gBAAgB,SAAS,GAAG;CACzC;CAEA,IAAI,KAAK,OAA2B;EACnC,KAAK,QAAQ,SAAS,KAAK;CAC5B;CAEA,IAAI,YAAgC;EACnC,OAAO,KAAK,wBAAwB,YAAY;CACjD;CAEA,IAAI,UAAU,OAA2B;EACxC,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,IAAI,OAAe;EAClB,OAAO,KAAK,gBAAgB,SAAS,CAAC;CACvC;CAEA,IAAI,KAAK,OAA2B;EACnC,KAAK,QAAQ,SAAS,KAAK;CAC5B;CAEA,IAAI,UAA8B;EACjC,OAAO,KAAK,wBAAwB,UAAU;CAC/C;CAEA,IAAI,QAAQ,OAA2B;EACtC,KAAK,QAAQ,YAAY,KAAK;CAC/B;CAEA,IAAI,QAAgB;EACnB,OAAO,KAAK,gBAAgB,UAAU,CAAC;CACxC;CAEA,IAAI,MAAM,OAA2B;EACpC,KAAK,QAAQ,UAAU,KAAK;CAC7B;CAEA,IAAI,SAA6B;EAChC,OAAO,KAAK,OAAO,GAAG;CACvB;CAEA,IAAI,OAAO,OAAuC;EACjD,KAAK,QAAQ,WAAW,KAAK;CAC9B;CAEA,IAAI,IAAY;EACf,OAAO,KAAK,QAAQ,EAAE;CACvB;CAEA,IAAI,EAAE,MAAiC;EACtC,KAAK,OAAO,KAAK,IAAI;CACtB;CAEA,IAAI,WAA+B;EAClC,OAAO,KAAK,wBAAwB,WAAW;CAChD;CAEA,IAAI,SAAS,OAA2B;EACvC,KAAK,QAAQ,aAAa,KAAK;CAChC;CAEA,IAAI,SAA6B;EAChC,OAAO,KAAK,aAAa,SAAS,KAAK;CACxC;CAEA,IAAI,OAAO,OAA2B;EACrC,KAAK,QAAQ,WAAW,KAAK;CAC9B;CAEA,IAAI,OAAe;EAClB,OAAO,KAAK,gBAAgB,SAAS,GAAG;CACzC;CAEA,IAAI,KAAK,OAA2B;EACnC,KAAK,QAAQ,SAAS,KAAK;CAC5B;CAEA,IAAI,YAAgC;EACnC,OAAO,KAAK,wBAAwB,YAAY;CACjD;CAEA,IAAI,UAAU,OAA2B;EACxC,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,IAAI,OAAe;EAClB,OAAO,KAAK,gBAAgB,SAAS,CAAC;CACvC;CAEA,IAAI,KAAK,OAA2B;EACnC,KAAK,QAAQ,SAAS,KAAK;CAC5B;CAEA,IAAI,UAA8B;EACjC,OAAO,KAAK,wBAAwB,UAAU;CAC/C;CAEA,IAAI,QAAQ,OAA2B;EACtC,KAAK,QAAQ,YAAY,KAAK;CAC/B;CAEA,IAAI,QAAgB;EACnB,OAAO,KAAK,gBAAgB,UAAU,CAAC;CACxC;CAEA,IAAI,MAAM,OAA2B;EACpC,KAAK,QAAQ,UAAU,KAAK;CAC7B;CAEA,IAAI,SAA6B;EAChC,OAAO,KAAK,OAAO,GAAG;CACvB;CAEA,IAAI,OAAO,OAAuC;EACjD,KAAK,QAAQ,WAAW,KAAK;CAC9B;CAEA,AAAS,SAAS,IAAI,mBAAmB;EACxC,KAAK,eAAe;CACrB,CAAC;CAED,eAA0B;CAE1B,AAAS,eAAe,kBAAkB;CAE1C,AAAS,MAAM,SAAS;CAExB,AAAS,SAAS,aAAa,KAAK,KAAK,mBAAmB,cAAc;CAE1E;CAEA;CAEA,cAAc;CAEd,AAAS,UAAyC;EACjD,GAAG,uBAAuB,KAAK,MAAM,GAAG,CAAC;EACzC,GAAG,uBAAuB,KAAK,MAAM,GAAG,CAAC;CAC1C;CAEA,AAAS,SAA4C;EACpD,GAAG,aAAa,KAAK,KAAK,2BAAyB,cAAc;EACjE,GAAG,aAAa,KAAK,KAAK,2BAAyB,cAAc;CAClE;CAEA,AAAS,QAAQ,aAAa,KAAK,KAAK,kBAAkB,cAAc;CAExE,AAAS,WAAW,IAAI,QAAQ,aAAa,KAAK,KAAK,qBAAqB,cAAc,CAAC;CAE3F,yBAAyB,MAAoB;EAC5C,KAAK,SAAS,OAAO,SAAS,QAAQ,KAAK,UAAU,QAAQ,GAAG;EAChE,IAAI,SAAS,cAAc,KAAK,WAAW,GAAG,KAAK,UAAU,IAAI;EAEjE,KAAK,QAAQ,IAAI;CAClB;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBACJ,eACA,WACA,YACA,UACA,QACA,aACA,QACA,WACA,SACA,UACA,YACA,UACA,QACA,aACA,QACA,WACA,SACA,UACA,eACA,KACA,GACD;EACA,MAAM,kBAAkB;CACzB;CAEA,AAAS,oBAA0B;EAClC,IAAI,KAAK,UAAU,QAAQ,GAAG;EAE9B,KAAK,QAAQ,GAAG,IAAI;CACrB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,KAAK,KAAK;EAEhB,KAAK,YAAY,IAAI,MAAM;EAC3B,KAAK,OAAO;EAEX,KAAK,YAAY,IAAI,UAAU;GAAE,UAAU;GAAiB,UAAU;EAAY,CAAC;EACpF,KAAK,aAAa,IAAI,MAAM;EAC5B,KAAK,UAAU,IAAI,MAAM;CAC1B;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK,OAAO,KAAK;CACzB;CAEA,AAAU,SAAe;EACxB,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,MAAM,EAAE,UAAU,KAAK;EAEvB,KAAK,MAAM,QAAQ,MAAM;GACxB,MAAM,QAAQ,KAAK,QAAQ;GAC3B,MAAM,UAAU,MAAM,QAAQ;GAC9B,MAAM,SAAS,KAAK,wBAAwB,GAAG,KAAK,QAAQ,KAAK,QAAQ,OAAO;GAEhF,MAAM,YAAY,eAAe,QAAQ,OAAO,QAAQ,aAAa,MAAM,KAAK,CAAC,CAAC;GAClF,MAAM,YAAY,eAAe,KAAK,UAAU,OAAO,QAAQ,aAAa,MAAM,CAAC,CAAC;EACrF;EACA,KAAK,YAAY;EACjB,KAAK,YAAY;EACjB,KAAK,eAAe;CACrB;CAEA,AAAU,aAAa,OAAqB;EAC3C,MAAM,CAAC,IAAI,KAAK,IAAI,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;EAEtD,KAAK,IAAI;EACT,KAAK,IAAI;CACV;CAEA,UAAU,MAAyB;EAClC,MAAM,EAAE,UAAU,KAAK,QAAQ;EAG/B,OAAO,GAFO,KAAK,aAAa,GAAG,KAAK,OAAO,KAAK,KAAK,YAAY,EAErD,GAAG,KAAK,eAAe,OAAO,IAAI,KAAK,OAAO,KAAK;CACpE;CAEA,UAAU,IAAiB,QAA2B;EACrD,GAAG,iBACF,YACC,UAAU;GACV,IAAI,KAAK,WAAW,KAAK,MAAM,kBAAkB;GAEjD,MAAM,OAAO,SAAS,IAAI,MAAM,GAAG;GACnC,IAAI,CAAC,QAAQ,EAAE,UAAU,IAAI,MAAM,GAAG,KAAK,KAAK,YAAY,IAAI;GAEhE,MAAM,eAAe;GACrB,KAAK,OAAO,OAAO,MAAM;GACzB,KAAK,eAAe;GACpB,IAAI,MAAM,KAAK,OAAO,GAAG,IAAI;QACxB,KAAK,OAAO;EAClB,GACA,EAAE,OAAO,CACV;EACA,GAAG,iBACF,aACC,UAAU;GAEV,IAAI,MAAM,yBAAyB,QAAQ,GAAG,SAAS,MAAM,aAAa,GAAG;GAC7E,IAAI,KAAK,OAAO,QAAQ,MAAM,GAAG,KAAK,eAAe;EACtD,GACA,EAAE,OAAO,CACV;EACA,OAAO,iBACN,eACM;GACL,IAAI,KAAK,OAAO,QAAQ,MAAM,GAAG,KAAK,eAAe;EACtD,GACA,EAAE,MAAM,KAAK,CACd;CACD;CAEA,aAAa,IAAiB,QAA2B;EAExD,GAAG,iBACF,cACC,UAAU;GACV,MAAM,eAAe;EACtB,GACA,EAAE,OAAO,CACV;EACA,GAAG,iBACF,uBACM;GACL,KAAK,aAAa,OAAO;EAC1B,GACA,EAAE,OAAO,CACV;EACA,KAAK,WAAW,iBACf,IACA;GACC,OAAO,KAAK;GACZ,OAAO,UAAU;IAChB,IAAI,KAAK,WAAW,KAAK,YAAY,KAAK,GAAG;IAE7C,eAAe,KAAK,YAAY,CAAC;IACjC,IAAI,CAAC,aAAa,KAAK,GAAG;KACzB,KAAK,YAAY,YAAY,IAAI;KAEjC,OAAO,KAAK,MAAM,KAAK;IACxB;IAEA,KAAK,OAAO;GAGb;GACA,QAAQ,OAAO,SAAS;IACvB,IAAI,CAAC,KAAK,OAAO,IAAI,GAAG,KAAK,eAAe;GAC7C;GACA,OAAO,OAAO,UAAU;IACvB,IAAI,CAAC,KAAK,aAAa,MAAM,KAAK,KAAK,KAAK,gBAAgB,SAAS;IAErE,KAAK,OAAO;GACb;GACA,UAAU,OAAO,UAAU;IAC1B,IAAI,MAAM,SAAS,GAAG,KAAK,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;GAChF;GACA,SAAS,eAAe;IACvB,KAAK,YAAY,YAAY,UAAU;GACxC;EACD,GACA,MACD;CACD;CAEA,QAAQ,MAAwB;EAC/B,IAAI,KAAK,OAAO,IAAI,GAAG,KAAK,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;CACjF;CAEA,MAAM,OAAgC;EACrC,MAAM,QAAQ,KAAK,OAAO,sBAAsB;EAChD,MAAM,OAAO,KAAK,MAAM,sBAAsB;EAC9C,MAAM,EAAE,GAAG,MAAM,KAAK;EACtB,MAAM,OAAO;GAAE,GAAG,EAAE;GAAO,GAAG,EAAE;EAAM;EACtC,MAAM,SAAS;GACd,GAAG,KAAK,IAAI,GAAG,MAAM,QAAQ,KAAK,KAAK;GACvC,GAAG,KAAK,IAAI,GAAG,MAAM,SAAS,KAAK,MAAM;EAC1C;EACA,MAAM,WAAW,gBAAgB,KAAK;EACtC,MAAM,UAAU;GACf,IAAI,SAAS,IAAI,MAAM,OAAO,KAAK,QAAQ,KAAK,OAAO;GACvD,IAAI,SAAS,IAAI,MAAM,SAAS,KAAK,SAAS,KAAK,OAAO;EAC3D;EACA,MAAM,WAAW,MAAM,kBAAkB,QAAQ,KAAK,MAAM,SAAS,MAAM,MAAM;EAEjF,IAAI,CAAC,UACJ,KAAK,OAAO;GAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,QAAQ,QAAQ,CAAC;GAAG,GAAG,EAAE,QAAQ,CAAC,CAAC,QAAQ,QAAQ,CAAC;EAAE,CAAC;EAGrF,MAAM,QAAQ,UAAqB;GAClC,MAAM,KAAK;GACX,OAAO,KAAK,QAAQ;GACpB,GAAI,WAAW,CAAC,IAAI,EAAE,YAAY,gBAAgB,QAAQ,KAAK,EAAE;GACjE,UAAU,OAAO;EAClB;EAEA,OAAO,EAAE,MAAM;GAAE,GAAG,KAAK,GAAG;GAAG,GAAG,KAAK,GAAG;EAAE,EAAE;CAC/C;CAEA,cAAuB;EACtB,OAAO,KAAK,aAAa,UAAa,KAAK,aAAa;CACzD;CAEA,OAAO,MAA2B;EACjC,IAAI,WAAW;EAEf,KAAK,MAAM,QAAQ,MAClB,IAAI,KAAK,QAAQ,KAAK,CAAC,MAAM,KAAK,SAAS,GAAG,GAAG,WAAW;EAE7D,IAAI,CAAC,UAAU,OAAO;EAEtB,KAAK,OAAO;EACZ,KAAK,cAAc,IAAI,MAAM,SAAS;GAAE,SAAS;GAAM,UAAU;EAAK,CAAC,CAAC;EAExE,OAAO;CACR;CAEA,OAAO,MAAiB,KAAmB;EAC1C,MAAM,QAAQ,KAAK,QAAQ;EAC3B,MAAM,WAAW,MAAM,MAAM,MAAM,QAAQ,CAAC,CAAC,UAAU,KAAK,MAAM,KAAK,KAAK,GAAG;EAE/E,KAAK,eAAe;EACpB,KAAK,OAAO;EACZ,KAAK,OAAO,KAAK,CAAC,MAAM;EACxB,IAAI,CAAC,UAAU;EAEf,KAAK,cAAc,IAAI,MAAM,SAAS;GAAE,SAAS;GAAM,UAAU;EAAK,CAAC,CAAC;EACxE,KAAK,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;CAC1D;CAEA,cAAoB;EACnB,MAAM,aAAa,KAAK,WAAW;EAEnC,KAAK,MAAM,QAAQ,MAAM;GACxB,MAAM,OAAO,KAAK,OAAO;GACzB,MAAM,QAAQ,KAAK,QAAQ;GAC3B,MAAM,CAAC,KAAK,QAAQ,MAAM,QAAQ,CAAC,CAAC;GACpC,MAAM,YAAY,SAAS,KAAK;GAChC,MAAM,OAAO,YAAY,MAAM;GAE/B,eAAe,MAAM,oBAAoB,aAAa,KAAK;GAC3D,eAAe,MAAM,iBAAiB,OAAO,GAAG,CAAC;GACjD,eAAe,MAAM,iBAAiB,OAAO,IAAI,CAAC;GAClD,eAAe,MAAM,iBAAiB,OAAO,MAAM,KAAK,CAAC;GACzD,eAAe,MAAM,kBAAkB,KAAK,WAAW,IAAI,CAAC;GAC5D,KAAK,cAAc,MAAM,IAAI;GAC7B,eAAe,MAAM,eAAe,YAAY,SAAY,MAAM;GAClE,eAAe,MAAM,iBAAiB,aAAa,SAAS,MAAS;GACrE,eAAe,MAAM,YAAY,aAAa,SAAY,IAAI;EAC/D;CACD;CAEA,cAAoB;EACnB,MAAM,EAAE,MAAM,GAAG,MAAM;EACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC;EAEtC,IAAI,SAAS,IAAI;GAEhB,KAAK,eAAe,MAAM,KAAK;GAC/B;EACD;EAEA,MAAM,SAAS,IAAI,SAAS;EAE5B,OAAO,IAAI,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC;EACjC,OAAO,IAAI,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC;EACjC,KAAK,eAAe,QAAQ,KAAK;CAClC;CAEA,iBAAuB;EACtB,MAAM,EAAE,eAAe,KAAK;EAE5B,KAAK,SAAS,KAAK;GAClB,QAAQ,KAAK;GACb,QAAQ,cAAc,KAAK;GAC3B,MAAM,KAAK,WAAW,GAAG;EAC1B,CAAC;EACD,IAAI,eAAe,KAAK,aAAa;EAErC,KAAK,cAAc;EACnB,KAAK,YAAY,YAAY,UAAU;EACvC,KAAK,cAAc,IAAI,MAAM,gBAAgB,EAAE,SAAS,KAAK,CAAC,CAAC;CAChE;CAEA,SAAe;EACd,KAAK,QAAQ;GAAE,GAAG,KAAK;GAAU,GAAG,KAAK;EAAS,CAAC;CACpD;CAEA,QAAQ,GAAG,WAAgC;EAC1C,KAAK,MAAM,QAAQ,MAClB,KAAK,QAAQ,KAAK,CAAC,OAClB,KAAK,gBAAgB,MAAM,KAAK,gBAAgB,GAAG,KAAK,OAAO,CAAC,CAAC,GACjE,EAAE,WAAW,UAAU,SAAS,IAAI,EAAE,CACvC;EAED,KAAK,OAAO;CACb;CAEA,MAAM,MAA4B;EACjC,MAAM,WAAW,KAAK,wBAAwB,GAAG,KAAK,UAAU;EAEhE,OAAO;GACN,KAAK,KAAK,gBAAgB,GAAG,KAAK,OAAO,GAAG;GAC5C,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;GAC7C,WAAW;GACX,YAAY;GACZ,KAAK,KAAK,gBAAgB,GAAG,KAAK,OAAO,CAAC;GAC1C,MAAM,KAAK,gBAAgB,GAAG,KAAK,QAAQ,CAAC;GAC5C,OAAO,KAAK,OAAO,IAAI;EACxB;CACD;CAEA,OAAO,MAAqC;EAC3C,OAAO,KAAK,aAAa,GAAG,KAAK,OAAO,MAAM,QAAQ,QAAQ;CAC/D;CAEA,WAAW,MAAyB;EACnC,OAAO,GAAG,KAAK,UAAU,IAAI,EAAE,IAAI,KAAK,UAAU,SAAS,MAAM,MAAM,GAAG;CAC3E;CAEA,OAAO,MAAiB,MAAuC;EAC9D,IAAI,KAAK,UAAU,QAAQ,GAAG;EAG9B,IAAI,SAAS,UAAa,SAAS,MAAM;GACxC,KAAK,QAAQ,IAAI;GACjB;EACD;EAEA,IAAI,KAAK,QAAQ,KAAK,CAAC,MAAM,SAAS,IAAI,CAAC,GAAG,KAAK,OAAO;CAC3D;AACD"}
|
|
@@ -89,7 +89,11 @@ var CanvasSurface = class {
|
|
|
89
89
|
#armTransitions() {
|
|
90
90
|
const { canvas, colours, numbers = {} } = this.#options;
|
|
91
91
|
const { style } = canvas;
|
|
92
|
-
const watched = [
|
|
92
|
+
const watched = [
|
|
93
|
+
"color",
|
|
94
|
+
...Object.values(colours),
|
|
95
|
+
...Object.values(numbers)
|
|
96
|
+
];
|
|
93
97
|
style.setProperty("transition-property", watched.join(", "));
|
|
94
98
|
style.setProperty("transition-duration", "0.001ms");
|
|
95
99
|
style.setProperty("transition-timing-function", "step-start");
|