@xsynaptic/sonic-ui 0.5.0 → 0.6.1
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/README.md +3 -1
- package/dist/default/dat.js +5 -4
- package/dist/default/define/button.js +1 -1
- package/dist/default/define.js +2 -2
- package/dist/default/elements/button.js +6 -5
- package/dist/default/elements/dial.js +5 -1
- package/dist/default/elements/envelope.js +46 -18
- package/dist/default/elements/meter.js +12 -8
- package/dist/default/elements/number.js +1 -1
- package/dist/default/elements/radio-group.js +46 -47
- package/dist/default/elements/readout-claim.js +0 -29
- package/dist/default/elements/readout.js +1 -1
- package/dist/default/elements/scale.js +43 -0
- package/dist/default/elements/slider.js +16 -3
- package/dist/default/elements/sonic-element.js +17 -55
- package/dist/default/elements/switch.js +33 -39
- package/dist/default/elements/value-element.js +142 -187
- package/dist/default/elements/wave-element.js +5 -2
- package/dist/default/elements/waveform.js +35 -12
- package/dist/default/elements/wavestrip.js +59 -18
- package/dist/default/elements/xy.js +34 -19
- package/dist/default/index.js +2 -2
- package/dist/default/lib/adsr-shape.js +5 -0
- package/dist/default/lib/ballistics.js +2 -2
- package/dist/default/lib/canvas-surface.js +13 -2
- package/dist/default/lib/distribute.js +1 -1
- package/dist/default/lib/drag-step.js +4 -4
- package/dist/default/lib/frame-timeline.js +2 -1
- package/dist/default/lib/marker-band.js +54 -10
- package/dist/default/lib/marker-rider.js +10 -4
- package/dist/default/lib/math.js +6 -1
- package/dist/default/lib/mirror-children.js +64 -0
- package/dist/default/lib/owned-control.js +10 -0
- package/dist/default/lib/pointer-drag.js +8 -5
- package/dist/default/lib/resample-peaks.js +11 -1
- package/dist/default/lib/time-regions.js +4 -1
- package/dist/default/lib/write-attribute.js +1 -0
- package/dist/dev/dat.d.ts +2 -2
- package/dist/dev/dat.js +5 -4
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/define/button.js +1 -1
- package/dist/dev/define.d.ts +4 -4
- package/dist/dev/define.js +2 -2
- package/dist/dev/elements/button.js +6 -5
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +1 -1
- package/dist/dev/elements/dial.js +5 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.js +46 -18
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +1 -1
- package/dist/dev/elements/meter.js +12 -8
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +1 -1
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.js +46 -47
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout-claim.js +0 -29
- package/dist/dev/elements/readout-claim.js.map +1 -1
- package/dist/dev/elements/readout.js +1 -1
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/scale.js +44 -0
- package/dist/dev/elements/scale.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +4 -1
- package/dist/dev/elements/slider.js +16 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +2 -8
- package/dist/dev/elements/sonic-element.js +18 -55
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/switch.js +33 -39
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +6 -8
- package/dist/dev/elements/value-element.js +142 -187
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +1 -0
- package/dist/dev/elements/wave-element.js +5 -2
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +1 -1
- package/dist/dev/elements/waveform.js +35 -12
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +4 -0
- package/dist/dev/elements/wavestrip.js +59 -18
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +3 -3
- package/dist/dev/elements/xy.js +34 -19
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +2 -2
- package/dist/dev/index.js +2 -2
- package/dist/dev/lib/adsr-shape.js +5 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -1
- package/dist/dev/lib/ballistics.js +2 -2
- package/dist/dev/lib/ballistics.js.map +1 -1
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +13 -2
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/distribute.js +1 -1
- package/dist/dev/lib/distribute.js.map +1 -1
- package/dist/dev/lib/drag-step.js +4 -4
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -1
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/marker-band.js +54 -10
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/marker-rider.js +10 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/math.js +6 -1
- package/dist/dev/lib/math.js.map +1 -1
- package/dist/dev/lib/mirror-children.js +80 -0
- package/dist/dev/lib/mirror-children.js.map +1 -0
- package/dist/dev/lib/owned-control.js +23 -0
- package/dist/dev/lib/owned-control.js.map +1 -0
- package/dist/dev/lib/pointer-drag.js +8 -5
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +11 -1
- package/dist/dev/lib/resample-peaks.js.map +1 -1
- package/dist/dev/lib/time-regions.js +4 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/dev/lib/write-attribute.js +1 -0
- package/dist/dev/lib/write-attribute.js.map +1 -1
- package/dist/skins/amber.css +8 -0
- package/dist/skins/flat.css +26 -0
- package/dist/skins/ivory.css +1 -0
- package/dist/skins/lime.css +2 -0
- package/dist/skins/slate.css +2 -0
- package/dist/styles/button.css +59 -10
- package/dist/styles/dial.css +24 -16
- package/dist/styles/envelope.css +58 -13
- package/dist/styles/led.css +20 -4
- package/dist/styles/material.css +143 -55
- package/dist/styles/meter.css +8 -4
- package/dist/styles/panel.css +7 -4
- package/dist/styles/segmented.css +27 -5
- package/dist/styles/slider.css +2 -1
- package/dist/styles/switch.css +65 -23
- package/dist/styles/waveform.css +28 -5
- package/dist/styles/wavestrip.css +23 -6
- package/dist/styles/xy.css +48 -27
- package/package.json +2 -5
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { clampProportion } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
3
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
import { sortedRegions } from "../lib/time-regions.js";
|
|
4
5
|
import { SonicWaveElement } from "./wave-element.js";
|
|
5
|
-
import { createMarkerBand
|
|
6
|
+
import { createMarkerBand } from "../lib/marker-band.js";
|
|
6
7
|
import { stripBars, stripRegions } from "../lib/strip-scene.js";
|
|
7
8
|
|
|
8
9
|
//#region src/elements/wavestrip.ts
|
|
@@ -45,6 +46,13 @@ function fillRegion(context, path, [from, to, style]) {
|
|
|
45
46
|
context.restore();
|
|
46
47
|
}
|
|
47
48
|
var SonicWavestrip = class extends SonicWaveElement {
|
|
49
|
+
get buffered() {
|
|
50
|
+
return this.#buffered.map(([start, end]) => [start, end]);
|
|
51
|
+
}
|
|
52
|
+
set buffered(regions) {
|
|
53
|
+
this.#buffered = sortedRegions(regions);
|
|
54
|
+
this.render();
|
|
55
|
+
}
|
|
48
56
|
get cancellable() {
|
|
49
57
|
return this.hasAttribute("cancellable");
|
|
50
58
|
}
|
|
@@ -65,11 +73,15 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
65
73
|
lengths = lengths;
|
|
66
74
|
sheet = "wavestrip.css";
|
|
67
75
|
#bars;
|
|
76
|
+
#buffered = [];
|
|
77
|
+
#escapeWatch;
|
|
78
|
+
#hover;
|
|
79
|
+
#isHoverDismissed = false;
|
|
68
80
|
#markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
|
|
69
81
|
#paintedKey = "";
|
|
70
82
|
#peaks;
|
|
71
83
|
connectedCallback() {
|
|
72
|
-
this.upgradeProperties("cancellable", "peaks");
|
|
84
|
+
this.upgradeProperties("buffered", "cancellable", "peaks");
|
|
73
85
|
super.connectedCallback();
|
|
74
86
|
}
|
|
75
87
|
connect(signal) {
|
|
@@ -79,6 +91,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
79
91
|
}
|
|
80
92
|
draw() {
|
|
81
93
|
const surface = this.surface();
|
|
94
|
+
if (this.isEditing() || this.isRevealed()) this.#hover = void 0;
|
|
82
95
|
this.#placeReadout();
|
|
83
96
|
if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
|
|
84
97
|
}
|
|
@@ -100,8 +113,11 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
100
113
|
look.colours[kind]
|
|
101
114
|
]);
|
|
102
115
|
}
|
|
116
|
+
previewValue() {
|
|
117
|
+
return this.#hover;
|
|
118
|
+
}
|
|
103
119
|
renderMarkers() {
|
|
104
|
-
this.#markerBand.render(this.
|
|
120
|
+
this.#markerBand.render(this.markerList(), this.markerProportions());
|
|
105
121
|
}
|
|
106
122
|
scrubsKeyRepeat() {
|
|
107
123
|
return true;
|
|
@@ -111,6 +127,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
111
127
|
return {
|
|
112
128
|
position: (event) => event.clientX,
|
|
113
129
|
startPx: box.left,
|
|
130
|
+
topPx: box.top,
|
|
114
131
|
travelPx: Math.max(1, box.width)
|
|
115
132
|
};
|
|
116
133
|
}
|
|
@@ -126,36 +143,44 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
126
143
|
#bindHover(strip, signal) {
|
|
127
144
|
strip.addEventListener("pointermove", (event) => {
|
|
128
145
|
if (event.pointerType === "touch" || event.buttons !== 0) return;
|
|
129
|
-
if (!this.readout
|
|
146
|
+
if (!this.readout) return;
|
|
130
147
|
const axis = this.#axis();
|
|
131
148
|
const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
|
|
132
|
-
|
|
149
|
+
const mapping = this.mapping();
|
|
150
|
+
this.#hoverAt(this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at)));
|
|
133
151
|
this.#placeReadout();
|
|
134
152
|
}, { signal });
|
|
135
153
|
for (const type of ["pointerdown", "pointerleave"]) strip.addEventListener(type, () => {
|
|
136
|
-
this
|
|
154
|
+
this.#hoverAt(void 0);
|
|
137
155
|
}, { signal });
|
|
138
156
|
}
|
|
139
157
|
#grab(event) {
|
|
140
|
-
const
|
|
141
|
-
const at = (event.clientX - startPx) / axis.travelPx;
|
|
158
|
+
const axis = this.#axis();
|
|
159
|
+
const at = (event.clientX - axis.startPx) / axis.travelPx;
|
|
142
160
|
const outside = this.#outside();
|
|
143
161
|
const grabbed = {
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
fromProportion: clampProportion(at),
|
|
163
|
+
position: axis.position,
|
|
164
|
+
travelPx: axis.travelPx
|
|
146
165
|
};
|
|
147
|
-
this.input(this.#markerAt(
|
|
166
|
+
this.input(this.#markerAt(event, axis) ?? this.mapping().valueAt(at));
|
|
148
167
|
return outside ? {
|
|
149
168
|
...grabbed,
|
|
150
169
|
outside
|
|
151
170
|
} : grabbed;
|
|
152
171
|
}
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
#hoverAt(value) {
|
|
173
|
+
if (value === void 0) this.#isHoverDismissed = false;
|
|
174
|
+
if (!this.#isHoverDismissed && !this.isEditing() && !this.isRevealed()) this.#hover = value;
|
|
175
|
+
this.renderReadout();
|
|
176
|
+
this.#watchEscape(value !== void 0);
|
|
177
|
+
}
|
|
178
|
+
#markerAt(event, { startPx, topPx, travelPx }) {
|
|
179
|
+
const index = this.#markerBand.at({
|
|
180
|
+
x: event.clientX - startPx,
|
|
181
|
+
y: event.clientY - topPx
|
|
182
|
+
}, travelPx);
|
|
183
|
+
return index === void 0 ? void 0 : this.markerList()[index]?.start;
|
|
159
184
|
}
|
|
160
185
|
#outside() {
|
|
161
186
|
if (!this.cancellable) return void 0;
|
|
@@ -171,11 +196,27 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
171
196
|
const { proportionOf } = this.mapping();
|
|
172
197
|
const isScrubbing = this.heldFrom() !== void 0 && this.isRevealed();
|
|
173
198
|
return stripRegions({
|
|
174
|
-
buffered: this
|
|
199
|
+
buffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
|
|
175
200
|
played: proportionOf(this.playback()),
|
|
176
201
|
...isScrubbing ? { scrub: proportionOf(this.value) } : {}
|
|
177
202
|
}, width);
|
|
178
203
|
}
|
|
204
|
+
#watchEscape(isHovered) {
|
|
205
|
+
if (!isHovered) {
|
|
206
|
+
this.#escapeWatch?.abort();
|
|
207
|
+
this.#escapeWatch = void 0;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (this.#escapeWatch) return;
|
|
211
|
+
const watch = new AbortController();
|
|
212
|
+
this.#escapeWatch = watch;
|
|
213
|
+
this.ownerDocument.addEventListener("keydown", (event) => {
|
|
214
|
+
if (event.key !== "Escape" || this.#hover === void 0) return;
|
|
215
|
+
this.#hover = void 0;
|
|
216
|
+
this.#isHoverDismissed = true;
|
|
217
|
+
this.render();
|
|
218
|
+
}, { signal: watch.signal });
|
|
219
|
+
}
|
|
179
220
|
};
|
|
180
221
|
|
|
181
222
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { createMarkerBand, nearestMarker } from '#lib/marker-band.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { stripBars, stripRegions } from '#lib/strip-scene.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-wavestrip-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tprotected readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly sheet = 'wavestrip.css';\n\n\t#bars: Path2D | undefined;\n\n\treadonly #markerBand = createMarkerBand(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('cancellable', 'peaks');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.#bindHover(this.control, signal);\n\t\tthis.bindGestures(this.control, signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markers, this.markerProportions());\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t#axis(): ValueAxis & { startPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tconst peaks = this.#peaks;\n\t\tif (!peaks || peaks.length === 0) return undefined;\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of stripBars(peaks, size, look.lengths)) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#bindHover(strip: HTMLElement, signal: AbortSignal): void {\n\t\tstrip.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerType === 'touch' || event.buttons !== 0) return;\n\t\t\t\tif (!this.readout || this.isDisabled()) return;\n\n\t\t\t\tconst axis = this.#axis();\n\t\t\t\tconst at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);\n\n\t\t\t\tthis.hoverReadout(this.#markerAt(at, axis.travelPx) ?? this.mapping().valueAt(at));\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tfor (const type of ['pointerdown', 'pointerleave']) {\n\t\t\tstrip.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t() => {\n\t\t\t\t\tthis.hoverReadout(undefined);\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t}\n\n\t#grab(event: PointerEvent): ValueAxis {\n\t\tconst { startPx, ...axis } = this.#axis();\n\t\tconst at = (event.clientX - startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = { ...axis, fromProportion: clampProportion(at) };\n\n\t\tthis.input(this.#markerAt(at, axis.travelPx) ?? this.mapping().valueAt(at));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#markerAt(at: number, travelPx: number): number | undefined {\n\t\tconst markers = this.markers;\n\t\tif (markers.length === 0) return undefined;\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst index = nearestMarker(\n\t\t\tat,\n\t\t\tmarkers.map(({ start }) => proportionOf(start)),\n\t\t\tthis.#markerBand.reachPx() / travelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : markers[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\tconst box = this.control.getBoundingClientRect();\n\t\tconst zone = readPxProperty(\n\t\t\tgetComputedStyle(this.control),\n\t\t\t'--_sonic-cancel-zone',\n\t\t\tcancelZonePx,\n\t\t);\n\n\t\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst isScrubbing = this.heldFrom() !== undefined && this.isRevealed();\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed: proportionOf(this.playback()),\n\t\t\t\t...(isScrubbing ? { scrub: proportionOf(this.value) } : {}),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { createMarkerBand } from '#lib/marker-band.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { stripBars, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-wavestrip-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tprotected readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly sheet = 'wavestrip.css';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\t#escapeWatch: AbortController | undefined;\n\n\t#hover: number | undefined;\n\n\t#isHoverDismissed = false;\n\n\treadonly #markerBand = createMarkerBand(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('buffered', 'cancellable', 'peaks');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.#bindHover(this.control, signal);\n\t\tthis.bindGestures(this.control, signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.isEditing() || this.isRevealed()) this.#hover = undefined;\n\t\tthis.#placeReadout();\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override previewValue(): number | undefined {\n\t\treturn this.#hover;\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markerList(), this.markerProportions());\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t#axis(): ValueAxis & { startPx: number; topPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttopPx: box.top,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tconst peaks = this.#peaks;\n\t\tif (!peaks || peaks.length === 0) return undefined;\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of stripBars(peaks, size, look.lengths)) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#bindHover(strip: HTMLElement, signal: AbortSignal): void {\n\t\tstrip.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerType === 'touch' || event.buttons !== 0) return;\n\t\t\t\tif (!this.readout) return;\n\n\t\t\t\tconst axis = this.#axis();\n\t\t\t\tconst at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\tthis.#hoverAt(this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at)));\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tfor (const type of ['pointerdown', 'pointerleave']) {\n\t\t\tstrip.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t() => {\n\t\t\t\t\tthis.#hoverAt(undefined);\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t}\n\n\t#grab(event: PointerEvent): ValueAxis {\n\t\tconst axis = this.#axis();\n\t\tconst at = (event.clientX - axis.startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = {\n\t\t\tfromProportion: clampProportion(at),\n\t\t\tposition: axis.position,\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\n\t\tthis.input(this.#markerAt(event, axis) ?? this.mapping().valueAt(at));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#hoverAt(value: number | undefined): void {\n\t\tif (value === undefined) this.#isHoverDismissed = false;\n\t\tif (!this.#isHoverDismissed && !this.isEditing() && !this.isRevealed()) this.#hover = value;\n\t\tthis.renderReadout();\n\t\tthis.#watchEscape(value !== undefined);\n\t}\n\n\t#markerAt(\n\t\tevent: PointerEvent,\n\t\t{ startPx, topPx, travelPx }: { startPx: number; topPx: number; travelPx: number },\n\t): number | undefined {\n\t\tconst index = this.#markerBand.at(\n\t\t\t{ x: event.clientX - startPx, y: event.clientY - topPx },\n\t\t\ttravelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : this.markerList()[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\tconst box = this.control.getBoundingClientRect();\n\t\tconst zone = readPxProperty(\n\t\t\tgetComputedStyle(this.control),\n\t\t\t'--_sonic-cancel-zone',\n\t\t\tcancelZonePx,\n\t\t);\n\n\t\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst isScrubbing = this.heldFrom() !== undefined && this.isRevealed();\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed: proportionOf(this.playback()),\n\t\t\t\t...(isScrubbing ? { scrub: proportionOf(this.value) } : {}),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n\n\t// A hover holds no focus, so the key is heard on the document (WCAG 1.4.13)\n\t#watchEscape(isHovered: boolean): void {\n\t\tif (!isHovered) {\n\t\t\tthis.#escapeWatch?.abort();\n\t\t\tthis.#escapeWatch = undefined;\n\t\t\treturn;\n\t\t}\n\t\tif (this.#escapeWatch) return;\n\n\t\tconst watch = new AbortController();\n\n\t\tthis.#escapeWatch = watch;\n\t\tthis.ownerDocument.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.key !== 'Escape' || this.#hover === undefined) return;\n\n\t\t\t\t// Stays dismissed until the hover ends, or the next pointer move would bring it straight back\n\t\t\t\tthis.#hover = undefined;\n\t\t\t\tthis.#isHoverDismissed = true;\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t\t{ signal: watch.signal },\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ValueSpec } from "../lib/value-mapping.js";
|
|
2
1
|
import { SonicFormElement } from "./form-element.js";
|
|
2
|
+
import { ValueSpec } from "../lib/value-mapping.js";
|
|
3
3
|
import { FieldAxis } from "../lib/field.js";
|
|
4
4
|
//#region src/elements/xy.d.ts
|
|
5
5
|
declare global {
|
|
@@ -15,7 +15,7 @@ export declare class SonicXy extends SonicFormElement {
|
|
|
15
15
|
get readout(): boolean;
|
|
16
16
|
set readout(isEnabled: boolean);
|
|
17
17
|
get x(): number;
|
|
18
|
-
set x(next: number);
|
|
18
|
+
set x(next: null | number | undefined);
|
|
19
19
|
get xDefault(): number | undefined;
|
|
20
20
|
set xDefault(value: number | undefined);
|
|
21
21
|
get xLabel(): string | undefined;
|
|
@@ -33,7 +33,7 @@ export declare class SonicXy extends SonicFormElement {
|
|
|
33
33
|
get xTaper(): ValueSpec['taper'];
|
|
34
34
|
set xTaper(curve: undefined | ValueSpec['taper']);
|
|
35
35
|
get y(): number;
|
|
36
|
-
set y(next: number);
|
|
36
|
+
set y(next: null | number | undefined);
|
|
37
37
|
get yDefault(): number | undefined;
|
|
38
38
|
set yDefault(value: number | undefined);
|
|
39
39
|
get yLabel(): string | undefined;
|
package/dist/dev/elements/xy.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { clampProportion } from "../lib/math.js";
|
|
2
|
-
import { requireChild, template } from "../lib/render.js";
|
|
1
|
+
import { clampProportion, toNumber } from "../lib/math.js";
|
|
3
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
|
+
import { requireChild, template } from "../lib/render.js";
|
|
5
5
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
6
6
|
import { Readout } from "./readout.js";
|
|
7
7
|
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
8
8
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
9
9
|
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
10
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
10
11
|
import { resetKeys, valueMapping } from "../lib/value-mapping.js";
|
|
11
12
|
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
12
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
13
13
|
|
|
14
14
|
//#region src/elements/xy.ts
|
|
15
15
|
const axes = ["x", "y"];
|
|
@@ -29,6 +29,7 @@ const orientations = {
|
|
|
29
29
|
};
|
|
30
30
|
const renderXy = template(`
|
|
31
31
|
<div class="sonic-xy">
|
|
32
|
+
<div class="sonic-xy-bracket" aria-hidden="true"></div>
|
|
32
33
|
<div class="sonic-xy-field" aria-hidden="true"></div>
|
|
33
34
|
<div class="sonic-xy-puck">
|
|
34
35
|
<div class="sonic-xy-axis" data-sonic-axis="x" role="slider" tabindex="0"></div>
|
|
@@ -193,6 +194,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
193
194
|
#xy = renderXy();
|
|
194
195
|
#field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
|
|
195
196
|
#formatValue;
|
|
197
|
+
#mapped;
|
|
196
198
|
#parts = {
|
|
197
199
|
x: requireChild(this.#xy, "[data-sonic-axis=\"x\"]", HTMLDivElement),
|
|
198
200
|
y: requireChild(this.#xy, "[data-sonic-axis=\"y\"]", HTMLDivElement)
|
|
@@ -202,18 +204,19 @@ var SonicXy = class extends SonicFormElement {
|
|
|
202
204
|
#readout = new Readout(requireChild(this.#xy, ".sonic-xy-readout", HTMLDivElement));
|
|
203
205
|
#values = {
|
|
204
206
|
x: {
|
|
205
|
-
|
|
207
|
+
asked: void 0,
|
|
206
208
|
value: 0
|
|
207
209
|
},
|
|
208
210
|
y: {
|
|
209
|
-
|
|
211
|
+
asked: void 0,
|
|
210
212
|
value: 0
|
|
211
213
|
}
|
|
212
214
|
};
|
|
213
215
|
attributeChangedCallback(name) {
|
|
216
|
+
this.#mapped = void 0;
|
|
214
217
|
if (name === "x" || name === "y") {
|
|
215
218
|
if (this.#pointerDrag?.current()) return;
|
|
216
|
-
this.#values[name].
|
|
219
|
+
this.#values[name].asked = void 0;
|
|
217
220
|
}
|
|
218
221
|
if (name === "disabled" && this.isDisabled()) this.#pointerDrag?.end();
|
|
219
222
|
this.#refresh();
|
|
@@ -223,12 +226,12 @@ var SonicXy = class extends SonicFormElement {
|
|
|
223
226
|
super.connectedCallback();
|
|
224
227
|
}
|
|
225
228
|
formResetCallback() {
|
|
226
|
-
for (const axis of axes) this.#values[axis].
|
|
229
|
+
for (const axis of axes) this.#values[axis].asked = void 0;
|
|
227
230
|
this.#refresh();
|
|
228
231
|
}
|
|
229
232
|
connect(signal) {
|
|
230
233
|
const xy = this.#xy;
|
|
231
|
-
this.
|
|
234
|
+
this.keepControl(xy, signal);
|
|
232
235
|
this.render();
|
|
233
236
|
this.checkStyles(xy, "xy.css");
|
|
234
237
|
this.#bindPointer(xy, signal);
|
|
@@ -238,6 +241,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
238
241
|
return this.#parts[this.#currentAxis];
|
|
239
242
|
}
|
|
240
243
|
render() {
|
|
244
|
+
if (!this.isBound()) return;
|
|
241
245
|
const { style } = this.#xy;
|
|
242
246
|
const mappings = this.#mappings();
|
|
243
247
|
for (const axis of axes) {
|
|
@@ -365,7 +369,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
365
369
|
return true;
|
|
366
370
|
}
|
|
367
371
|
#keyTo(axis, key) {
|
|
368
|
-
const next = this.#
|
|
372
|
+
const next = this.#mappings()[axis].keyTarget(key, this.#values[axis].value);
|
|
369
373
|
const hasMoved = this.#set(axis, next ?? NaN);
|
|
370
374
|
this.#currentAxis = axis;
|
|
371
375
|
this.render();
|
|
@@ -390,17 +394,19 @@ var SonicXy = class extends SonicFormElement {
|
|
|
390
394
|
});
|
|
391
395
|
}
|
|
392
396
|
#mappings() {
|
|
393
|
-
return
|
|
397
|
+
if (this.#mapped) return this.#mapped;
|
|
398
|
+
this.#mapped = {
|
|
394
399
|
x: this.#mapping("x"),
|
|
395
400
|
y: this.#mapping("y")
|
|
396
401
|
};
|
|
402
|
+
return this.#mapped;
|
|
397
403
|
}
|
|
398
404
|
#refresh() {
|
|
399
405
|
const mappings = this.#mappings();
|
|
400
406
|
for (const axis of axes) {
|
|
401
407
|
const axisValue = this.#values[axis];
|
|
402
408
|
const mapping = mappings[axis];
|
|
403
|
-
axisValue.value = mapping.snap(axisValue.
|
|
409
|
+
axisValue.value = mapping.snap(axisValue.asked ?? this.numberAttribute(axis, mapping.bounds[0]));
|
|
404
410
|
}
|
|
405
411
|
this.render();
|
|
406
412
|
}
|
|
@@ -412,11 +418,11 @@ var SonicXy = class extends SonicFormElement {
|
|
|
412
418
|
const [low, high] = mappings[axis].bounds;
|
|
413
419
|
const isCurrent = axis === this.#currentAxis;
|
|
414
420
|
const stop = isCurrent ? "0" : "-1";
|
|
415
|
-
part
|
|
416
|
-
part
|
|
417
|
-
part
|
|
418
|
-
part
|
|
419
|
-
part
|
|
421
|
+
writeAttribute(part, "aria-orientation", orientations[axis]);
|
|
422
|
+
writeAttribute(part, "aria-valuemin", String(low));
|
|
423
|
+
writeAttribute(part, "aria-valuemax", String(high));
|
|
424
|
+
writeAttribute(part, "aria-valuenow", String(this.#values[axis].value));
|
|
425
|
+
writeAttribute(part, "aria-valuetext", this.#valueText(axis));
|
|
420
426
|
this.forwardNaming(part, true);
|
|
421
427
|
writeAttribute(part, "aria-hidden", isCurrent ? void 0 : "true");
|
|
422
428
|
writeAttribute(part, "aria-disabled", isDisabled ? "true" : void 0);
|
|
@@ -451,8 +457,8 @@ var SonicXy = class extends SonicFormElement {
|
|
|
451
457
|
#set(axis, next) {
|
|
452
458
|
if (!Number.isFinite(next)) return false;
|
|
453
459
|
const axisValue = this.#values[axis];
|
|
454
|
-
const snapped = this.#
|
|
455
|
-
axisValue.
|
|
460
|
+
const snapped = this.#mappings()[axis].snap(next);
|
|
461
|
+
axisValue.asked = snapped;
|
|
456
462
|
if (snapped === axisValue.value) return false;
|
|
457
463
|
axisValue.value = snapped;
|
|
458
464
|
return true;
|
|
@@ -465,7 +471,16 @@ var SonicXy = class extends SonicFormElement {
|
|
|
465
471
|
}
|
|
466
472
|
#write(axis, next) {
|
|
467
473
|
if (this.#pointerDrag?.current()) return;
|
|
468
|
-
|
|
474
|
+
const axisValue = this.#values[axis];
|
|
475
|
+
if (next === void 0 || next === null) {
|
|
476
|
+
axisValue.asked = void 0;
|
|
477
|
+
this.#refresh();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const asked = toNumber(next);
|
|
481
|
+
const hasMoved = this.#set(axis, asked);
|
|
482
|
+
if (Number.isFinite(asked)) axisValue.asked = asked;
|
|
483
|
+
if (hasMoved) this.render();
|
|
469
484
|
}
|
|
470
485
|
};
|
|
471
486
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldAxis, FieldDragState, FieldPoint } from '#lib/field.ts';\nimport type { PointerDrag } from '#lib/pointer-drag.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys, valueMapping } from '#lib/value-mapping.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\ninterface AxisValue {\n\tisDirty: boolean;\n\tvalue: number;\n}\n\ninterface XyDrag {\n\tfrom: FieldPoint;\n\tstate: FieldDragState;\n}\n\ninterface XyInput {\n\tx: number | undefined;\n\ty: number | undefined;\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-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\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.#values.x.value;\n\t}\n\n\tset x(next: number) {\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.#values.y.value;\n\t}\n\n\tset y(next: number) {\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\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\t#pointerDrag: PointerDrag<XyDrag> | undefined;\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\treadonly #values: Record<FieldAxis, AxisValue> = {\n\t\tx: { isDirty: false, value: 0 },\n\t\ty: { isDirty: false, value: 0 },\n\t};\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'x' || name === 'y') {\n\t\t\tif (this.#pointerDrag?.current()) return;\n\n\t\t\tthis.#values[name].isDirty = false;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#pointerDrag?.end();\n\n\t\tthis.#refresh();\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\tfor (const axis of axes) this.#values[axis].isDirty = false;\n\n\t\tthis.#refresh();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.#xy;\n\n\t\tthis.appendOnce(xy);\n\t\tthis.render();\n\t\tthis.checkStyles(xy, 'xy.css');\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\tconst { style } = this.#xy;\n\t\tconst mappings = this.#mappings();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst mapping = mappings[axis];\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(\n\t\t\t\t`--_sonic-xy-${axis}`,\n\t\t\t\tString(mapping.proportionOf(this.#values[axis].value)),\n\t\t\t);\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.#values[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.#pointerDrag = bindDrag(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tcancel: (drag) => {\n\t\t\t\t\tthis.#input(drag.from);\n\t\t\t\t},\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)) return this.#grab(event);\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\tconst step = stepFieldDrag(this.#mappings(), drag.state, pointerMove(event));\n\n\t\t\t\t\tdrag.state = step.state;\n\t\t\t\t\tif (step.state.isEngaged) this.#claim.reveal('drag');\n\t\t\t\t\tif (!this.#input(step)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tthis.#claim.press(false);\n\t\t\t\t\tif (this.x === drag.from.x && this.y === drag.from.y) return;\n\n\t\t\t\t\tthis.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: XyInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): XyDrag {\n\t\tconst from = { x: this.x, y: this.y };\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst mappings = this.#mappings();\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: mappings.x.valueAt(pressed.x), y: mappings.y.valueAt(pressed.y) });\n\t\tthis.#claim.press(true);\n\n\t\tconst start = (axis: FieldAxis) => ({\n\t\t\tfrom: this.#values[axis].value,\n\t\t\tmapping: mappings[axis],\n\t\t\tproportion: isOnPuck\n\t\t\t\t? mappings[axis].proportionOf(this.#values[axis].value)\n\t\t\t\t: clampProportion(pressed[axis]),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tstate: startFieldDrag({\n\t\t\t\tposition,\n\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\tx: start('x'),\n\t\t\t\ty: start('y'),\n\t\t\t}),\n\t\t};\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: XyInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#set(axis, 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 next = this.#mapping(axis).keyTarget(key, this.#values[axis].value);\n\t\tconst hasMoved = this.#set(axis, next ?? 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#mapping(axis: FieldAxis): ValueMapping {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn valueMapping({\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#mappings(): Record<FieldAxis, ValueMapping> {\n\t\treturn { x: this.#mapping('x'), y: this.#mapping('y') };\n\t}\n\n\t#refresh(): void {\n\t\tconst mappings = this.#mappings();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst axisValue = this.#values[axis];\n\t\t\tconst mapping = mappings[axis];\n\n\t\t\taxisValue.value = mapping.snap(\n\t\t\t\taxisValue.isDirty ? axisValue.value : this.numberAttribute(axis, mapping.bounds[0]),\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#renderAria(): void {\n\t\tconst mappings = this.#mappings();\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 [low, high] = mappings[axis].bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\tpart.setAttribute('aria-orientation', orientations[axis]);\n\t\t\tpart.setAttribute('aria-valuemin', String(low));\n\t\t\tpart.setAttribute('aria-valuemax', String(high));\n\t\t\tpart.setAttribute('aria-valuenow', String(this.#values[axis].value));\n\t\t\tpart.setAttribute('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#set(axis: FieldAxis, next: number): boolean {\n\t\tif (!Number.isFinite(next)) return false;\n\n\t\tconst axisValue = this.#values[axis];\n\t\tconst snapped = this.#mapping(axis).snap(next);\n\n\t\taxisValue.isDirty = true;\n\t\tif (snapped === axisValue.value) return false;\n\n\t\taxisValue.value = snapped;\n\n\t\treturn true;\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: number): void {\n\t\tif (this.#pointerDrag?.current()) return;\n\t\tif (this.#set(axis, 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 { FieldAxis, FieldDragState, FieldPoint } from '#lib/field.ts';\nimport type { PointerDrag } from '#lib/pointer-drag.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } 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 { bindDrag } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys, valueMapping } from '#lib/value-mapping.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\ninterface AxisValue {\n\tasked: number | undefined;\n\tvalue: number;\n}\n\ninterface XyDrag {\n\tfrom: FieldPoint;\n\tstate: FieldDragState;\n}\n\ninterface XyInput {\n\tx: number | undefined;\n\ty: number | undefined;\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\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.#values.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.#values.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#mapped: Record<FieldAxis, ValueMapping> | undefined;\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\t#pointerDrag: PointerDrag<XyDrag> | undefined;\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\treadonly #values: Record<FieldAxis, AxisValue> = {\n\t\tx: { asked: undefined, value: 0 },\n\t\ty: { asked: undefined, value: 0 },\n\t};\n\n\tattributeChangedCallback(name: string): void {\n\t\tthis.#mapped = undefined;\n\t\tif (name === 'x' || name === 'y') {\n\t\t\tif (this.#pointerDrag?.current()) return;\n\n\t\t\tthis.#values[name].asked = undefined;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#pointerDrag?.end();\n\n\t\tthis.#refresh();\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\tfor (const axis of axes) this.#values[axis].asked = undefined;\n\n\t\tthis.#refresh();\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\tthis.checkStyles(xy, 'xy.css');\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\t\tconst mappings = this.#mappings();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst mapping = mappings[axis];\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(\n\t\t\t\t`--_sonic-xy-${axis}`,\n\t\t\t\tString(mapping.proportionOf(this.#values[axis].value)),\n\t\t\t);\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.#values[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.#pointerDrag = bindDrag(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tcancel: (drag) => {\n\t\t\t\t\tthis.#input(drag.from);\n\t\t\t\t},\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)) return this.#grab(event);\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\tconst step = stepFieldDrag(this.#mappings(), drag.state, pointerMove(event));\n\n\t\t\t\t\tdrag.state = step.state;\n\t\t\t\t\tif (step.state.isEngaged) this.#claim.reveal('drag');\n\t\t\t\t\tif (!this.#input(step)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tthis.#claim.press(false);\n\t\t\t\t\tif (this.x === drag.from.x && this.y === drag.from.y) return;\n\n\t\t\t\t\tthis.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: XyInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): XyDrag {\n\t\tconst from = { x: this.x, y: this.y };\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst mappings = this.#mappings();\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: mappings.x.valueAt(pressed.x), y: mappings.y.valueAt(pressed.y) });\n\t\tthis.#claim.press(true);\n\n\t\tconst start = (axis: FieldAxis) => ({\n\t\t\tfrom: this.#values[axis].value,\n\t\t\tmapping: mappings[axis],\n\t\t\tproportion: isOnPuck\n\t\t\t\t? mappings[axis].proportionOf(this.#values[axis].value)\n\t\t\t\t: clampProportion(pressed[axis]),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tstate: startFieldDrag({\n\t\t\t\tposition,\n\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\tx: start('x'),\n\t\t\t\ty: start('y'),\n\t\t\t}),\n\t\t};\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: XyInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#set(axis, 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 next = this.#mappings()[axis].keyTarget(key, this.#values[axis].value);\n\t\tconst hasMoved = this.#set(axis, next ?? 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#mapping(axis: FieldAxis): ValueMapping {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn valueMapping({\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#mappings(): Record<FieldAxis, ValueMapping> {\n\t\tif (this.#mapped) return this.#mapped;\n\n\t\tthis.#mapped = { x: this.#mapping('x'), y: this.#mapping('y') };\n\n\t\treturn this.#mapped;\n\t}\n\n\t#refresh(): void {\n\t\tconst mappings = this.#mappings();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst axisValue = this.#values[axis];\n\t\t\tconst mapping = mappings[axis];\n\n\t\t\taxisValue.value = mapping.snap(\n\t\t\t\taxisValue.asked ?? this.numberAttribute(axis, mapping.bounds[0]),\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#renderAria(): void {\n\t\tconst mappings = this.#mappings();\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 [low, high] = mappings[axis].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(this.#values[axis].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#set(axis: FieldAxis, next: number): boolean {\n\t\tif (!Number.isFinite(next)) return false;\n\n\t\tconst axisValue = this.#values[axis];\n\t\tconst snapped = this.#mappings()[axis].snap(next);\n\n\t\taxisValue.asked = snapped;\n\t\tif (snapped === axisValue.value) return false;\n\n\t\taxisValue.value = snapped;\n\n\t\treturn true;\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.#pointerDrag?.current()) return;\n\n\t\tconst axisValue = this.#values[axis];\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\taxisValue.asked = undefined;\n\t\t\tthis.#refresh();\n\t\t\treturn;\n\t\t}\n\n\t\tconst asked = toNumber(next);\n\t\tconst hasMoved = this.#set(axis, asked);\n\n\t\tif (Number.isFinite(asked)) axisValue.asked = asked;\n\t\tif (hasMoved) this.render();\n\t}\n}\n"],"mappings":"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"}
|
package/dist/dev/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { WaveformPeaks } from "./lib/waveform-buckets.js";
|
|
2
|
+
import { SonicButton } from "./elements/button.js";
|
|
2
3
|
import { SonicDial } from "./elements/dial.js";
|
|
3
4
|
import { SonicEnvelope } from "./elements/envelope.js";
|
|
4
|
-
import { SonicButton } from "./elements/button.js";
|
|
5
|
-
import { SonicSwitch } from "./elements/switch.js";
|
|
6
5
|
import { SonicMeter } from "./elements/meter.js";
|
|
7
6
|
import { SonicNumber } from "./elements/number.js";
|
|
8
7
|
import { SonicSegmented } from "./elements/segmented.js";
|
|
9
8
|
import { SonicSlider } from "./elements/slider.js";
|
|
10
9
|
import { SonicSplit } from "./elements/split.js";
|
|
10
|
+
import { SonicSwitch } from "./elements/switch.js";
|
|
11
11
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
12
12
|
import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
13
13
|
import { SonicXy } from "./elements/xy.js";
|
package/dist/dev/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { SonicButton } from "./elements/button.js";
|
|
1
2
|
import { SonicDial } from "./elements/dial.js";
|
|
2
3
|
import { envelopeCurve } from "./lib/envelope-curve.js";
|
|
3
4
|
import { SonicEnvelope } from "./elements/envelope.js";
|
|
4
|
-
import { SonicButton } from "./elements/button.js";
|
|
5
|
-
import { SonicSwitch } from "./elements/switch.js";
|
|
6
5
|
import { SonicMeter } from "./elements/meter.js";
|
|
7
6
|
import { SonicNumber } from "./elements/number.js";
|
|
8
7
|
import { SonicSegmented } from "./elements/segmented.js";
|
|
9
8
|
import { SonicSlider } from "./elements/slider.js";
|
|
10
9
|
import { SonicSplit } from "./elements/split.js";
|
|
10
|
+
import { SonicSwitch } from "./elements/switch.js";
|
|
11
11
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
12
12
|
import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
13
13
|
import { SonicXy } from "./elements/xy.js";
|
|
@@ -98,8 +98,13 @@ function adsrShape(proportions, curves = {}) {
|
|
|
98
98
|
}
|
|
99
99
|
function adsrPath({ points }) {
|
|
100
100
|
const stroke = points.map(({ x, y }, index) => `${index === 0 ? "M" : "L"}${String(roundTo(x, 4))} ${String(roundTo(1 - y, 4))}`).join("");
|
|
101
|
+
const floor = {
|
|
102
|
+
from: roundTo(points[0]?.x ?? 0, 4),
|
|
103
|
+
to: roundTo(points.at(-1)?.x ?? 0, 4)
|
|
104
|
+
};
|
|
101
105
|
return {
|
|
102
106
|
fill: `${stroke}Z`,
|
|
107
|
+
floor,
|
|
103
108
|
stroke
|
|
104
109
|
};
|
|
105
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adsr-shape.js","names":[],"sources":["../../../src/lib/adsr-shape.ts"],"sourcesContent":["import { envelopeCurve } from '#lib/envelope-curve.ts';\nimport { roundTo } from '#lib/math.ts';\n\nexport const adsrStages = ['delay', 'attack', 'hold', 'decay', 'sustain', 'release'] as const;\n\nexport const timeStages = ['delay', 'attack', 'hold', 'decay', 'release'] as const;\n\ntype AdsrStage = (typeof adsrStages)[number];\n\ntype TimeStage = (typeof timeStages)[number];\n\nexport type AdsrProportions = Partial<Record<AdsrStage, number | undefined>>;\n\nexport type AdsrCurves = Partial<Record<TimeStage, number | undefined>>;\n\ninterface AdsrPoint {\n\tx: number;\n\ty: number;\n}\n\ninterface AdsrHandle extends AdsrPoint {\n\tstage: TimeStage;\n}\n\nexport interface AdsrShape {\n\tcurveHandles: Array<AdsrHandle>;\n\thandles: Array<AdsrHandle>;\n\tpoints: Array<AdsrPoint>;\n\tshare: number;\n}\n\nconst curveSamples = 24;\n\nfunction along(from: AdsrPoint, to: AdsrPoint, at: { curve: number; position: number }): AdsrPoint {\n\treturn {\n\t\tx: from.x + (to.x - from.x) * at.position,\n\t\ty: from.y + (to.y - from.y) * envelopeCurve(at.position, at.curve),\n\t};\n}\n\nfunction curved(from: AdsrPoint, to: AdsrPoint, curve: number): Array<AdsrPoint> {\n\tif (envelopeCurve(0.5, curve) === 0.5) return [];\n\n\treturn Array.from({ length: curveSamples - 1 }, (_, index) =>\n\t\talong(from, to, { curve, position: (index + 1) / curveSamples }),\n\t);\n}\n\nfunction leg(\n\tfrom: AdsrPoint,\n\tto: AdsrPoint,\n\tcurve: number | undefined,\n): { curveHandle: AdsrPoint | undefined; points: Array<AdsrPoint> } {\n\tif (curve === undefined) return { curveHandle: undefined, points: [to] };\n\n\treturn {\n\t\tcurveHandle: along(from, to, { curve, position: 0.5 }),\n\t\tpoints: [...curved(from, to, curve), to],\n\t};\n}\n\nfunction isHandled(proportions: AdsrProportions, stage: TimeStage): boolean {\n\tconst proportion =\n\t\tstage === 'decay' ? (proportions.sustain ?? proportions.decay) : proportions[stage];\n\n\treturn proportion !== undefined;\n}\n\nexport function adsrShape(proportions: AdsrProportions, curves: AdsrCurves = {}): AdsrShape {\n\tconst sustain = proportions.sustain ?? 1;\n\tconst levels = { attack: 1, decay: sustain, delay: 0, hold: 1, release: 0 };\n\tconst share = 1 / (timeStages.filter((stage) => proportions[stage] !== undefined).length + 1);\n\tconst shape: AdsrShape = { curveHandles: [], handles: [], points: [{ x: 0, y: 0 }], share };\n\tlet from = { x: 0, y: 0 };\n\n\tfor (const stage of timeStages) {\n\t\tif (stage === 'release') {\n\t\t\tfrom = { x: from.x + share, y: sustain };\n\t\t\tshape.points.push(from);\n\t\t}\n\n\t\tconst to = { x: from.x + (proportions[stage] ?? 0) * share, y: levels[stage] };\n\t\tconst { curveHandle, points } = leg(from, to, curves[stage]);\n\n\t\tshape.points.push(...points);\n\t\tif (curveHandle) shape.curveHandles.push({ ...curveHandle, stage });\n\t\tif (isHandled(proportions, stage)) shape.handles.push({ ...to, stage });\n\t\tfrom = to;\n\t}\n\n\treturn shape;\n}\n\nexport function adsrPath({ points }: AdsrShape): {
|
|
1
|
+
{"version":3,"file":"adsr-shape.js","names":[],"sources":["../../../src/lib/adsr-shape.ts"],"sourcesContent":["import { envelopeCurve } from '#lib/envelope-curve.ts';\nimport { roundTo } from '#lib/math.ts';\n\nexport const adsrStages = ['delay', 'attack', 'hold', 'decay', 'sustain', 'release'] as const;\n\nexport const timeStages = ['delay', 'attack', 'hold', 'decay', 'release'] as const;\n\ntype AdsrStage = (typeof adsrStages)[number];\n\ntype TimeStage = (typeof timeStages)[number];\n\nexport type AdsrProportions = Partial<Record<AdsrStage, number | undefined>>;\n\nexport type AdsrCurves = Partial<Record<TimeStage, number | undefined>>;\n\ninterface AdsrPoint {\n\tx: number;\n\ty: number;\n}\n\ninterface AdsrHandle extends AdsrPoint {\n\tstage: TimeStage;\n}\n\nexport interface AdsrShape {\n\tcurveHandles: Array<AdsrHandle>;\n\thandles: Array<AdsrHandle>;\n\tpoints: Array<AdsrPoint>;\n\tshare: number;\n}\n\nconst curveSamples = 24;\n\nfunction along(from: AdsrPoint, to: AdsrPoint, at: { curve: number; position: number }): AdsrPoint {\n\treturn {\n\t\tx: from.x + (to.x - from.x) * at.position,\n\t\ty: from.y + (to.y - from.y) * envelopeCurve(at.position, at.curve),\n\t};\n}\n\nfunction curved(from: AdsrPoint, to: AdsrPoint, curve: number): Array<AdsrPoint> {\n\tif (envelopeCurve(0.5, curve) === 0.5) return [];\n\n\treturn Array.from({ length: curveSamples - 1 }, (_, index) =>\n\t\talong(from, to, { curve, position: (index + 1) / curveSamples }),\n\t);\n}\n\nfunction leg(\n\tfrom: AdsrPoint,\n\tto: AdsrPoint,\n\tcurve: number | undefined,\n): { curveHandle: AdsrPoint | undefined; points: Array<AdsrPoint> } {\n\tif (curve === undefined) return { curveHandle: undefined, points: [to] };\n\n\treturn {\n\t\tcurveHandle: along(from, to, { curve, position: 0.5 }),\n\t\tpoints: [...curved(from, to, curve), to],\n\t};\n}\n\nfunction isHandled(proportions: AdsrProportions, stage: TimeStage): boolean {\n\tconst proportion =\n\t\tstage === 'decay' ? (proportions.sustain ?? proportions.decay) : proportions[stage];\n\n\treturn proportion !== undefined;\n}\n\nexport function adsrShape(proportions: AdsrProportions, curves: AdsrCurves = {}): AdsrShape {\n\tconst sustain = proportions.sustain ?? 1;\n\tconst levels = { attack: 1, decay: sustain, delay: 0, hold: 1, release: 0 };\n\tconst share = 1 / (timeStages.filter((stage) => proportions[stage] !== undefined).length + 1);\n\tconst shape: AdsrShape = { curveHandles: [], handles: [], points: [{ x: 0, y: 0 }], share };\n\tlet from = { x: 0, y: 0 };\n\n\tfor (const stage of timeStages) {\n\t\tif (stage === 'release') {\n\t\t\tfrom = { x: from.x + share, y: sustain };\n\t\t\tshape.points.push(from);\n\t\t}\n\n\t\tconst to = { x: from.x + (proportions[stage] ?? 0) * share, y: levels[stage] };\n\t\tconst { curveHandle, points } = leg(from, to, curves[stage]);\n\n\t\tshape.points.push(...points);\n\t\tif (curveHandle) shape.curveHandles.push({ ...curveHandle, stage });\n\t\tif (isHandled(proportions, stage)) shape.handles.push({ ...to, stage });\n\t\tfrom = to;\n\t}\n\n\treturn shape;\n}\n\nexport function adsrPath({ points }: AdsrShape): {\n\tfill: string;\n\tfloor: { from: number; to: number };\n\tstroke: string;\n} {\n\tconst stroke = points\n\t\t.map(\n\t\t\t({ x, y }, index) =>\n\t\t\t\t`${index === 0 ? 'M' : 'L'}${String(roundTo(x, 4))} ${String(roundTo(1 - y, 4))}`,\n\t\t)\n\t\t.join('');\n\n\tconst floor = {\n\t\tfrom: roundTo(points[0]?.x ?? 0, 4),\n\t\tto: roundTo(points.at(-1)?.x ?? 0, 4),\n\t};\n\n\treturn { fill: `${stroke}Z`, floor, stroke };\n}\n"],"mappings":";;;;AAGA,MAAa,aAAa;CAAC;CAAS;CAAU;CAAQ;CAAS;CAAW;AAAS;AAEnF,MAAa,aAAa;CAAC;CAAS;CAAU;CAAQ;CAAS;AAAS;AA0BxE,MAAM,eAAe;AAErB,SAAS,MAAM,MAAiB,IAAe,IAAoD;CAClG,OAAO;EACN,GAAG,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG;EACjC,GAAG,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,cAAc,GAAG,UAAU,GAAG,KAAK;CAClE;AACD;AAEA,SAAS,OAAO,MAAiB,IAAe,OAAiC;CAChF,IAAI,cAAc,IAAK,KAAK,MAAM,IAAK,OAAO,CAAC;CAE/C,OAAO,MAAM,KAAK,EAAE,QAAQ,GAAiB,IAAI,GAAG,UACnD,MAAM,MAAM,IAAI;EAAE;EAAO,WAAW,QAAQ,KAAK;CAAa,CAAC,CAChE;AACD;AAEA,SAAS,IACR,MACA,IACA,OACmE;CACnE,IAAI,UAAU,QAAW,OAAO;EAAE,aAAa;EAAW,QAAQ,CAAC,EAAE;CAAE;CAEvE,OAAO;EACN,aAAa,MAAM,MAAM,IAAI;GAAE;GAAO,UAAU;EAAI,CAAC;EACrD,QAAQ,CAAC,GAAG,OAAO,MAAM,IAAI,KAAK,GAAG,EAAE;CACxC;AACD;AAEA,SAAS,UAAU,aAA8B,OAA2B;CAI3E,QAFC,UAAU,UAAW,YAAY,WAAW,YAAY,QAAS,YAAY,YAExD;AACvB;AAEA,SAAgB,UAAU,aAA8B,SAAqB,CAAC,GAAc;CAC3F,MAAM,UAAU,YAAY,WAAW;CACvC,MAAM,SAAS;EAAE,QAAQ;EAAG,OAAO;EAAS,OAAO;EAAG,MAAM;EAAG,SAAS;CAAE;CAC1E,MAAM,QAAQ,KAAK,WAAW,QAAQ,UAAU,YAAY,WAAW,MAAS,CAAC,CAAC,SAAS;CAC3F,MAAM,QAAmB;EAAE,cAAc,CAAC;EAAG,SAAS,CAAC;EAAG,QAAQ,CAAC;GAAE,GAAG;GAAG,GAAG;EAAE,CAAC;EAAG;CAAM;CAC1F,IAAI,OAAO;EAAE,GAAG;EAAG,GAAG;CAAE;CAExB,KAAK,MAAM,SAAS,YAAY;EAC/B,IAAI,UAAU,WAAW;GACxB,OAAO;IAAE,GAAG,KAAK,IAAI;IAAO,GAAG;GAAQ;GACvC,MAAM,OAAO,KAAK,IAAI;EACvB;EAEA,MAAM,KAAK;GAAE,GAAG,KAAK,KAAK,YAAY,UAAU,KAAK;GAAO,GAAG,OAAO;EAAO;EAC7E,MAAM,EAAE,aAAa,WAAW,IAAI,MAAM,IAAI,OAAO,MAAM;EAE3D,MAAM,OAAO,KAAK,GAAG,MAAM;EAC3B,IAAI,aAAa,MAAM,aAAa,KAAK;GAAE,GAAG;GAAa;EAAM,CAAC;EAClE,IAAI,UAAU,aAAa,KAAK,GAAG,MAAM,QAAQ,KAAK;GAAE,GAAG;GAAI;EAAM,CAAC;EACtE,OAAO;CACR;CAEA,OAAO;AACR;AAEA,SAAgB,SAAS,EAAE,UAIzB;CACD,MAAM,SAAS,OACb,KACC,EAAE,GAAG,KAAK,UACV,GAAG,UAAU,IAAI,MAAM,MAAM,OAAO,QAAQ,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,QAAQ,IAAI,GAAG,CAAC,CAAC,GAChF,CAAC,CACA,KAAK,EAAE;CAET,MAAM,QAAQ;EACb,MAAM,QAAQ,OAAO,EAAE,EAAE,KAAK,GAAG,CAAC;EAClC,IAAI,QAAQ,OAAO,GAAG,EAAE,CAAC,EAAE,KAAK,GAAG,CAAC;CACrC;CAEA,OAAO;EAAE,MAAM,GAAG,OAAO;EAAI;EAAO;CAAO;AAC5C"}
|
|
@@ -3,13 +3,13 @@ const burstTimeConstants = -Math.log(1 - 10 ** (-2 / 20));
|
|
|
3
3
|
function ppmRate({ burstMs, fallDecibels, fallSeconds }) {
|
|
4
4
|
return {
|
|
5
5
|
fallDecibelsPerSecond: fallDecibels / fallSeconds,
|
|
6
|
-
|
|
6
|
+
riseTimeConstantMs: burstMs / burstTimeConstants
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
9
|
const ballisticsRates = {
|
|
10
10
|
peak: {
|
|
11
11
|
fallDecibelsPerSecond: 20,
|
|
12
|
-
|
|
12
|
+
riseTimeConstantMs: 0
|
|
13
13
|
},
|
|
14
14
|
"ppm-1": ppmRate({
|
|
15
15
|
burstMs: 5,
|