@xsynaptic/sonic-ui 0.6.1 → 0.9.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.
Files changed (105) hide show
  1. package/README.md +2 -0
  2. package/dist/default/define/spectrum.js +7 -0
  3. package/dist/default/define.js +1 -0
  4. package/dist/default/elements/button.js +30 -2
  5. package/dist/default/elements/dial.js +12 -3
  6. package/dist/default/elements/display-element.js +77 -0
  7. package/dist/default/elements/envelope.js +3 -3
  8. package/dist/default/elements/modulation.js +45 -0
  9. package/dist/default/elements/readout-claim.js +8 -3
  10. package/dist/default/elements/slider.js +68 -4
  11. package/dist/default/elements/spectrum.js +230 -0
  12. package/dist/default/elements/value-element.js +114 -317
  13. package/dist/default/elements/value-gestures.js +301 -0
  14. package/dist/default/elements/wave-element.js +59 -9
  15. package/dist/default/elements/waveform.js +61 -12
  16. package/dist/default/elements/wavestrip.js +25 -49
  17. package/dist/default/elements/xy.js +10 -21
  18. package/dist/default/index.js +2 -1
  19. package/dist/default/lib/asked-value.js +21 -0
  20. package/dist/default/lib/canvas-surface.js +10 -4
  21. package/dist/default/lib/frame-timeline.js +2 -2
  22. package/dist/default/lib/hover-preview.js +46 -0
  23. package/dist/default/lib/marker-rider.js +12 -6
  24. package/dist/default/lib/pointer-drag.js +7 -2
  25. package/dist/default/lib/read-ms-property.js +10 -0
  26. package/dist/default/lib/spectrum-columns.js +74 -0
  27. package/dist/default/lib/spectrum-levels.js +55 -0
  28. package/dist/default/lib/strip-scene.js +12 -1
  29. package/dist/default/lib/time-regions.js +5 -1
  30. package/dist/dev/define/spectrum.d.ts +2 -0
  31. package/dist/dev/define/spectrum.js +8 -0
  32. package/dist/dev/define/spectrum.js.map +1 -0
  33. package/dist/dev/define.d.ts +3 -1
  34. package/dist/dev/define.js +1 -0
  35. package/dist/dev/elements/button.d.ts +2 -0
  36. package/dist/dev/elements/button.js +30 -2
  37. package/dist/dev/elements/button.js.map +1 -1
  38. package/dist/dev/elements/dial.js +12 -3
  39. package/dist/dev/elements/dial.js.map +1 -1
  40. package/dist/dev/elements/display-element.d.ts +29 -0
  41. package/dist/dev/elements/display-element.js +78 -0
  42. package/dist/dev/elements/display-element.js.map +1 -0
  43. package/dist/dev/elements/envelope.js +3 -3
  44. package/dist/dev/elements/envelope.js.map +1 -1
  45. package/dist/dev/elements/modulation.js +46 -0
  46. package/dist/dev/elements/modulation.js.map +1 -0
  47. package/dist/dev/elements/readout-claim.js +8 -3
  48. package/dist/dev/elements/readout-claim.js.map +1 -1
  49. package/dist/dev/elements/slider.d.ts +5 -0
  50. package/dist/dev/elements/slider.js +68 -4
  51. package/dist/dev/elements/slider.js.map +1 -1
  52. package/dist/dev/elements/spectrum.d.ts +59 -0
  53. package/dist/dev/elements/spectrum.js +231 -0
  54. package/dist/dev/elements/spectrum.js.map +1 -0
  55. package/dist/dev/elements/value-element.d.ts +13 -14
  56. package/dist/dev/elements/value-element.js +114 -317
  57. package/dist/dev/elements/value-element.js.map +1 -1
  58. package/dist/dev/elements/value-gestures.d.ts +10 -0
  59. package/dist/dev/elements/value-gestures.js +302 -0
  60. package/dist/dev/elements/value-gestures.js.map +1 -0
  61. package/dist/dev/elements/wave-element.d.ts +8 -2
  62. package/dist/dev/elements/wave-element.js +59 -9
  63. package/dist/dev/elements/wave-element.js.map +1 -1
  64. package/dist/dev/elements/waveform.d.ts +12 -4
  65. package/dist/dev/elements/waveform.js +61 -12
  66. package/dist/dev/elements/waveform.js.map +1 -1
  67. package/dist/dev/elements/wavestrip.d.ts +3 -1
  68. package/dist/dev/elements/wavestrip.js +25 -49
  69. package/dist/dev/elements/wavestrip.js.map +1 -1
  70. package/dist/dev/elements/xy.js +10 -21
  71. package/dist/dev/elements/xy.js.map +1 -1
  72. package/dist/dev/index.d.ts +4 -1
  73. package/dist/dev/index.js +2 -1
  74. package/dist/dev/lib/asked-value.js +22 -0
  75. package/dist/dev/lib/asked-value.js.map +1 -0
  76. package/dist/dev/lib/canvas-surface.d.ts +5 -3
  77. package/dist/dev/lib/canvas-surface.js +10 -4
  78. package/dist/dev/lib/canvas-surface.js.map +1 -1
  79. package/dist/dev/lib/frame-timeline.js +2 -2
  80. package/dist/dev/lib/frame-timeline.js.map +1 -1
  81. package/dist/dev/lib/hover-preview.js +47 -0
  82. package/dist/dev/lib/hover-preview.js.map +1 -0
  83. package/dist/dev/lib/marker-rider.js +12 -6
  84. package/dist/dev/lib/marker-rider.js.map +1 -1
  85. package/dist/dev/lib/pointer-drag.js +7 -2
  86. package/dist/dev/lib/pointer-drag.js.map +1 -1
  87. package/dist/dev/lib/read-ms-property.js +11 -0
  88. package/dist/dev/lib/read-ms-property.js.map +1 -0
  89. package/dist/dev/lib/spectrum-columns.js +75 -0
  90. package/dist/dev/lib/spectrum-columns.js.map +1 -0
  91. package/dist/dev/lib/spectrum-levels.js +56 -0
  92. package/dist/dev/lib/spectrum-levels.js.map +1 -0
  93. package/dist/dev/lib/strip-scene.js +12 -1
  94. package/dist/dev/lib/strip-scene.js.map +1 -1
  95. package/dist/dev/lib/time-regions.js +5 -1
  96. package/dist/dev/lib/time-regions.js.map +1 -1
  97. package/dist/styles/button.css +7 -1
  98. package/dist/styles/controls.css +1 -0
  99. package/dist/styles/material.css +36 -3
  100. package/dist/styles/meter.css +22 -18
  101. package/dist/styles/slider.css +42 -5
  102. package/dist/styles/spectrum.css +105 -0
  103. package/dist/styles/waveform.css +48 -16
  104. package/dist/styles/wavestrip.css +26 -5
  105. package/package.json +2 -1
@@ -4,7 +4,7 @@ import { readPxProperty } from "../lib/read-px-property.js";
4
4
  import { sortedRegions } from "../lib/time-regions.js";
5
5
  import { SonicWaveElement } from "./wave-element.js";
6
6
  import { createMarkerBand } from "../lib/marker-band.js";
7
- import { stripBars, stripRegions } from "../lib/strip-scene.js";
7
+ import { stripBars, stripGroove, stripRegions } from "../lib/strip-scene.js";
8
8
 
9
9
  //#region src/elements/wavestrip.ts
10
10
  const colours = {
@@ -15,6 +15,7 @@ const colours = {
15
15
  };
16
16
  const lengths = {
17
17
  gapRatio: "--_sonic-wavestrip-bar-gap-ratio",
18
+ grooveRatio: "--_sonic-wavestrip-groove-ratio",
18
19
  pitch: "--_sonic-wavestrip-bar-pitch",
19
20
  radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
20
21
  };
@@ -72,11 +73,9 @@ var SonicWavestrip = class extends SonicWaveElement {
72
73
  colours = colours;
73
74
  lengths = lengths;
74
75
  sheet = "wavestrip.css";
76
+ sizeProperty = "--_sonic-wavestrip-size";
75
77
  #bars;
76
78
  #buffered = [];
77
- #escapeWatch;
78
- #hover;
79
- #isHoverDismissed = false;
80
79
  #markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
81
80
  #paintedKey = "";
82
81
  #peaks;
@@ -85,14 +84,26 @@ var SonicWavestrip = class extends SonicWaveElement {
85
84
  super.connectedCallback();
86
85
  }
87
86
  connect(signal) {
87
+ const { control } = this;
88
88
  super.connect(signal);
89
- this.#bindHover(this.control, signal);
90
- this.bindGestures(this.control, signal, (event) => this.#grab(event));
89
+ this.bindHover(control, signal, {
90
+ canShow: () => this.readout,
91
+ place: () => {
92
+ this.#placeReadout();
93
+ },
94
+ valueAt: (event) => {
95
+ const axis = this.#axis();
96
+ const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
97
+ const mapping = this.mapping();
98
+ return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
99
+ }
100
+ });
101
+ this.bindGestures(control, signal, (event) => this.#grab(event));
91
102
  }
92
103
  draw() {
93
104
  const surface = this.surface();
94
- if (this.isEditing() || this.isRevealed()) this.#hover = void 0;
95
105
  this.#placeReadout();
106
+ this.showCurrentMarker(this.playback());
96
107
  if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
97
108
  }
98
109
  isEmpty() {
@@ -113,9 +124,6 @@ var SonicWavestrip = class extends SonicWaveElement {
113
124
  look.colours[kind]
114
125
  ]);
115
126
  }
116
- previewValue() {
117
- return this.#hover;
118
- }
119
127
  renderMarkers() {
120
128
  this.#markerBand.render(this.markerList(), this.markerProportions());
121
129
  }
@@ -132,28 +140,18 @@ var SonicWavestrip = class extends SonicWaveElement {
132
140
  };
133
141
  }
134
142
  #barsFor({ isDirty, look, size }) {
135
- const peaks = this.#peaks;
136
- if (!peaks || peaks.length === 0) return void 0;
137
143
  if (!isDirty && this.#bars) return this.#bars;
144
+ const peaks = this.#peaks;
145
+ const groove = stripGroove(size, look.lengths.grooveRatio);
146
+ const drawn = groove ? [groove] : [];
147
+ const bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;
148
+ this.#bars = void 0;
149
+ if (bars.length === 0) return void 0;
138
150
  const path = new Path2D();
139
- for (const { height, radius, width, x, y } of stripBars(peaks, size, look.lengths)) path.roundRect(x, y, width, height, radius);
151
+ for (const { height, radius, width, x, y } of bars) path.roundRect(x, y, width, height, radius);
140
152
  this.#bars = path;
141
153
  return path;
142
154
  }
143
- #bindHover(strip, signal) {
144
- strip.addEventListener("pointermove", (event) => {
145
- if (event.pointerType === "touch" || event.buttons !== 0) return;
146
- if (!this.readout) return;
147
- const axis = this.#axis();
148
- const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
149
- const mapping = this.mapping();
150
- this.#hoverAt(this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at)));
151
- this.#placeReadout();
152
- }, { signal });
153
- for (const type of ["pointerdown", "pointerleave"]) strip.addEventListener(type, () => {
154
- this.#hoverAt(void 0);
155
- }, { signal });
156
- }
157
155
  #grab(event) {
158
156
  const axis = this.#axis();
159
157
  const at = (event.clientX - axis.startPx) / axis.travelPx;
@@ -169,12 +167,6 @@ var SonicWavestrip = class extends SonicWaveElement {
169
167
  outside
170
168
  } : grabbed;
171
169
  }
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
170
  #markerAt(event, { startPx, topPx, travelPx }) {
179
171
  const index = this.#markerBand.at({
180
172
  x: event.clientX - startPx,
@@ -201,22 +193,6 @@ var SonicWavestrip = class extends SonicWaveElement {
201
193
  ...isScrubbing ? { scrub: proportionOf(this.value) } : {}
202
194
  }, width);
203
195
  }
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
- }
220
196
  };
221
197
 
222
198
  //#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';\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":";;;;;;;;;AAuBA,MAAM,UAAU;CACf,UAAU;CACV,QAAQ;CACR,OAAO;CACP,MAAM;AACP;AAEA,MAAM,UAAU;CACf,UAAU;CACV,OAAO;CACP,aAAa;AACd;AAEA,MAAM,eAAe;AAErB,MAAM,kBAAkB,SACZ;;;;;;;;;;;;;;;;IAiBX,cACD;AAEA,SAAS,WACR,SACA,MACA,CAAC,MAAM,IAAI,QACJ;CACP,QAAQ,KAAK;CACb,QAAQ,UAAU;CAClB,QAAQ,KAAK,MAAM,GAAG,KAAK,MAAM,QAAQ,OAAO,MAAM;CACtD,QAAQ,KAAK;CACb,QAAQ,YAAY;CACpB,QAAQ,KAAK,IAAI;CACjB,QAAQ,QAAQ;AACjB;AAEA,IAAa,iBAAb,cAAoC,iBAAiC;CAEpE,IAAI,WAAoC;EACvC,OAAO,KAAK,UAAU,KAAK,CAAC,OAAO,SAAS,CAAC,OAAO,GAAG,CAAC;CACzD;CAEA,IAAI,SAAS,SAAkC;EAC9C,KAAK,YAAY,cAAc,OAAO;EACtC,KAAK,OAAO;CACb;CAEA,IAAI,cAAuB;EAC1B,OAAO,KAAK,aAAa,aAAa;CACvC;CAEA,IAAI,YAAY,eAAwB;EACvC,KAAK,QAAQ,eAAe,aAAa;CAC1C;CAEA,IAAI,QAAuC;EAC1C,OAAO,KAAK;CACb;CAEA,IAAI,MAAM,OAAsC;EAC/C,KAAK,SAAS;EACd,KAAK,YAAY;EACjB,KAAK,QAAQ,CAAC,EAAE,WAAW;CAC5B;CAEA,AAAmB,UAAU,gBAAgB;CAE7C,AAAmB,SAAS,aAC3B,KAAK,SACL,2BACA,iBACD;CAEA,AAAmB,UAAU;CAE7B,AAAmB,UAAU;CAE7B,AAAmB,QAAQ;CAE3B;CAEA,YAAqC,CAAC;CAEtC;CAEA;CAEA,oBAAoB;CAEpB,AAAS,cAAc,iBACtB,aAAa,KAAK,SAAS,4BAA4B,cAAc,CACtE;CAEA,cAAc;CAEd;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,eAAe,OAAO;EACzD,MAAM,kBAAkB;CACzB;CAEA,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,MAAM;EACpB,KAAK,WAAW,KAAK,SAAS,MAAM;EACpC,KAAK,aAAa,KAAK,SAAS,SAAS,UAAU,KAAK,MAAM,KAAK,CAAC;CACrE;CAEA,AAAU,OAAa;EACtB,MAAM,UAAU,KAAK,QAAQ;EAG7B,IAAI,KAAK,UAAU,KAAK,KAAK,WAAW,GAAG,KAAK,SAAS;EACzD,KAAK,cAAc;EACnB,IAAI,WAAW,KAAK,cAAc,QAAQ,KAAK,KAAK,CAAC,CAAC,QAAQ,KAAK,aAClE,QAAQ,aAAa;CAEvB;CAEA,AAAU,UAAmB;EAC5B,OAAO,CAAC,KAAK,UAAU,KAAK,OAAO,WAAW;CAC/C;CAEA,AAAU,MAAM,SAAmC,OAA2C;EAC7F,MAAM,EAAE,MAAM,SAAS;EACvB,MAAM,QAAQ,KAAK,cAAc,KAAK,KAAK;EAC3C,MAAM,OAAO,KAAK,SAAS,KAAK;EAEhC,KAAK,cAAc,MAAM;EACzB,QAAQ,UAAU,GAAG,GAAG,KAAK,OAAO,KAAK,MAAM;EAC/C,IAAI,CAAC,MAAM;EAEX,QAAQ,YAAY,KAAK,QAAQ;EACjC,QAAQ,KAAK,IAAI;EACjB,KAAK,MAAM,EAAE,MAAM,MAAM,QAAQ,MAAM,SACtC,WAAW,SAAS,MAAM;GAAC;GAAM;GAAI,KAAK,QAAQ;EAAK,CAAC;CAE1D;CAEA,AAAmB,eAAmC;EACrD,OAAO,KAAK;CACb;CAEA,AAAmB,gBAAsB;EACxC,KAAK,YAAY,OAAO,KAAK,WAAW,GAAG,KAAK,kBAAkB,CAAC;CACpE;CAEA,AAAmB,kBAA2B;EAC7C,OAAO;CACR;CAEA,QAAwD;EACvD,MAAM,MAAM,KAAK,OAAO,sBAAsB;EAE9C,OAAO;GACN,WAAW,UAAwB,MAAM;GACzC,SAAS,IAAI;GACb,OAAO,IAAI;GACX,UAAU,KAAK,IAAI,GAAG,IAAI,KAAK;EAChC;CACD;CAEA,SAAS,EAAE,SAAS,MAAM,QAA0D;EACnF,MAAM,QAAQ,KAAK;EACnB,IAAI,CAAC,SAAS,MAAM,WAAW,GAAG,OAAO;EACzC,IAAI,CAAC,WAAW,KAAK,OAAO,OAAO,KAAK;EAExC,MAAM,OAAO,IAAI,OAAO;EAExB,KAAK,MAAM,EAAE,QAAQ,QAAQ,OAAO,GAAG,OAAO,UAAU,OAAO,MAAM,KAAK,OAAO,GAChF,KAAK,UAAU,GAAG,GAAG,OAAO,QAAQ,MAAM;EAE3C,KAAK,QAAQ;EAEb,OAAO;CACR;CAEA,WAAW,OAAoB,QAA2B;EACzD,MAAM,iBACL,gBACC,UAAU;GACV,IAAI,MAAM,gBAAgB,WAAW,MAAM,YAAY,GAAG;GAC1D,IAAI,CAAC,KAAK,SAAS;GAEnB,MAAM,OAAO,KAAK,MAAM;GACxB,MAAM,KAAK,iBAAiB,MAAM,UAAU,KAAK,WAAW,KAAK,QAAQ;GACzE,MAAM,UAAU,KAAK,QAAQ;GAE7B,KAAK,SAAS,KAAK,UAAU,OAAO,IAAI,KAAK,QAAQ,KAAK,QAAQ,QAAQ,EAAE,CAAC,CAAC;GAC9E,KAAK,cAAc;EACpB,GACA,EAAE,OAAO,CACV;EACA,KAAK,MAAM,QAAQ,CAAC,eAAe,cAAc,GAChD,MAAM,iBACL,YACM;GACL,KAAK,SAAS,MAAS;EACxB,GACA,EAAE,OAAO,CACV;CAEF;CAEA,MAAM,OAAgC;EACrC,MAAM,OAAO,KAAK,MAAM;EACxB,MAAM,MAAM,MAAM,UAAU,KAAK,WAAW,KAAK;EACjD,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,UAAU;GACf,gBAAgB,gBAAgB,EAAE;GAClC,UAAU,KAAK;GACf,UAAU,KAAK;EAChB;EAEA,KAAK,MAAM,KAAK,UAAU,OAAO,IAAI,KAAK,KAAK,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;EAEpE,OAAO,UAAU;GAAE,GAAG;GAAS;EAAQ,IAAI;CAC5C;CAEA,SAAS,OAAiC;EACzC,IAAI,UAAU,QAAW,KAAK,oBAAoB;EAClD,IAAI,CAAC,KAAK,qBAAqB,CAAC,KAAK,UAAU,KAAK,CAAC,KAAK,WAAW,GAAG,KAAK,SAAS;EACtF,KAAK,cAAc;EACnB,KAAK,aAAa,UAAU,MAAS;CACtC;CAEA,UACC,OACA,EAAE,SAAS,OAAO,YACG;EACrB,MAAM,QAAQ,KAAK,YAAY,GAC9B;GAAE,GAAG,MAAM,UAAU;GAAS,GAAG,MAAM,UAAU;EAAM,GACvD,QACD;EAEA,OAAO,UAAU,SAAY,SAAY,KAAK,WAAW,CAAC,CAAC,MAAM,EAAE;CACpE;CAEA,WAA2D;EAC1D,IAAI,CAAC,KAAK,aAAa,OAAO;EAE9B,MAAM,MAAM,KAAK,QAAQ,sBAAsB;EAC/C,MAAM,OAAO,eACZ,iBAAiB,KAAK,OAAO,GAC7B,wBACA,YACD;EAEA,QAAQ,UAAU,MAAM,UAAU,IAAI,MAAM,QAAQ,MAAM,UAAU,IAAI,SAAS;CAClF;CAEA,gBAAsB;EACrB,MAAM,KAAK,KAAK,QAAQ,CAAC,CAAC,aAAa,KAAK,aAAa,CAAC;EAE1D,KAAK,QAAQ,MAAM,YAAY,iCAAiC,OAAO,EAAE,CAAC;CAC3E;CAEA,cAAc,OAA6B;EAC1C,MAAM,EAAE,iBAAiB,KAAK,QAAQ;EACtC,MAAM,cAAc,KAAK,SAAS,MAAM,UAAa,KAAK,WAAW;EAErE,OAAO,aACN;GACC,UAAU,KAAK,UAAU,KAAK,CAAC,OAAO,SAAS,CAAC,aAAa,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC;GACvF,QAAQ,aAAa,KAAK,SAAS,CAAC;GACpC,GAAI,cAAc,EAAE,OAAO,aAAa,KAAK,KAAK,EAAE,IAAI,CAAC;EAC1D,GACA,KACD;CACD;CAGA,aAAa,WAA0B;EACtC,IAAI,CAAC,WAAW;GACf,KAAK,cAAc,MAAM;GACzB,KAAK,eAAe;GACpB;EACD;EACA,IAAI,KAAK,cAAc;EAEvB,MAAM,QAAQ,IAAI,gBAAgB;EAElC,KAAK,eAAe;EACpB,KAAK,cAAc,iBAClB,YACC,UAAU;GACV,IAAI,MAAM,QAAQ,YAAY,KAAK,WAAW,QAAW;GAGzD,KAAK,SAAS;GACd,KAAK,oBAAoB;GACzB,KAAK,OAAO;EACb,GACA,EAAE,QAAQ,MAAM,OAAO,CACxB;CACD;AACD"}
1
+ {"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.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, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\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\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\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\tconst { control } = this;\n\n\t\tsuper.connect(signal);\n\t\tthis.bindHover(control, signal, {\n\t\t\tcanShow: () => this.readout,\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\tvalueAt: (event) => {\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\treturn this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.bindGestures(control, signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tthis.showCurrentMarker(this.playback());\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.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\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst peaks = this.#peaks;\n\t\tconst groove = stripGroove(size, look.lengths.grooveRatio);\n\t\tconst drawn = groove ? [groove] : [];\n\t\tconst bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;\n\n\t\tthis.#bars = undefined;\n\t\tif (bars.length === 0) return undefined;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of bars) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#grab(event: PointerEvent): 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#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"],"mappings":"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"}
@@ -2,13 +2,14 @@ import { clampProportion, toNumber } from "../lib/math.js";
2
2
  import { writeAttribute } from "../lib/write-attribute.js";
3
3
  import { SonicFormElement } from "./form-element.js";
4
4
  import { requireChild, template } from "../lib/render.js";
5
- import { ReadoutClaim } from "./readout-claim.js";
5
+ import { bindDrag } from "../lib/pointer-drag.js";
6
6
  import { Readout } from "./readout.js";
7
+ import { ReadoutClaim, revealDelay } from "./readout-claim.js";
7
8
  import { dragThresholdPx } from "../lib/drag-step.js";
8
9
  import { focusByPointer } from "../lib/focus-by-pointer.js";
9
10
  import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
10
- import { bindDrag } from "../lib/pointer-drag.js";
11
11
  import { resetKeys, valueMapping } from "../lib/value-mapping.js";
12
+ import { moveTo, resnap, setAsked } from "../lib/asked-value.js";
12
13
  import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
13
14
 
14
15
  //#region src/elements/xy.ts
@@ -337,7 +338,7 @@ var SonicXy = class extends SonicFormElement {
337
338
  x: mappings.x.valueAt(pressed.x),
338
339
  y: mappings.y.valueAt(pressed.y)
339
340
  });
340
- this.#claim.press(true);
341
+ this.#claim.press(true, revealDelay(this.#xy));
341
342
  const start = (axis) => ({
342
343
  from: this.#values[axis].value,
343
344
  mapping: mappings[axis],
@@ -359,7 +360,7 @@ var SonicXy = class extends SonicFormElement {
359
360
  }
360
361
  #input(next) {
361
362
  let hasMoved = false;
362
- for (const axis of axes) if (this.#set(axis, next[axis] ?? NaN)) hasMoved = true;
363
+ for (const axis of axes) if (moveTo(this.#values[axis], this.#mappings()[axis], next[axis] ?? NaN)) hasMoved = true;
363
364
  if (!hasMoved) return false;
364
365
  this.render();
365
366
  this.dispatchEvent(new Event("input", {
@@ -369,8 +370,9 @@ var SonicXy = class extends SonicFormElement {
369
370
  return true;
370
371
  }
371
372
  #keyTo(axis, key) {
372
- const next = this.#mappings()[axis].keyTarget(key, this.#values[axis].value);
373
- const hasMoved = this.#set(axis, next ?? NaN);
373
+ const mapping = this.#mappings()[axis];
374
+ const next = mapping.keyTarget(key, this.#values[axis].value);
375
+ const hasMoved = moveTo(this.#values[axis], mapping, next ?? NaN);
374
376
  this.#currentAxis = axis;
375
377
  this.render();
376
378
  this.#parts[axis].focus();
@@ -404,9 +406,8 @@ var SonicXy = class extends SonicFormElement {
404
406
  #refresh() {
405
407
  const mappings = this.#mappings();
406
408
  for (const axis of axes) {
407
- const axisValue = this.#values[axis];
408
409
  const mapping = mappings[axis];
409
- axisValue.value = mapping.snap(axisValue.asked ?? this.numberAttribute(axis, mapping.bounds[0]));
410
+ resnap(this.#values[axis], mapping, this.numberAttribute(axis, mapping.bounds[0]));
410
411
  }
411
412
  this.render();
412
413
  }
@@ -454,15 +455,6 @@ var SonicXy = class extends SonicFormElement {
454
455
  y: this.yDefault
455
456
  });
456
457
  }
457
- #set(axis, next) {
458
- if (!Number.isFinite(next)) return false;
459
- const axisValue = this.#values[axis];
460
- const snapped = this.#mappings()[axis].snap(next);
461
- axisValue.asked = snapped;
462
- if (snapped === axisValue.value) return false;
463
- axisValue.value = snapped;
464
- return true;
465
- }
466
458
  #taper(axis) {
467
459
  return this.getAttribute(`${axis}-taper`) === "log" ? "log" : "linear";
468
460
  }
@@ -477,10 +469,7 @@ var SonicXy = class extends SonicFormElement {
477
469
  this.#refresh();
478
470
  return;
479
471
  }
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();
472
+ if (setAsked(axisValue, this.#mappings()[axis], toNumber(next))) this.render();
484
473
  }
485
474
  };
486
475
 
@@ -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, 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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"}
1
+ {"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { AskedValue } from '#lib/asked-value.ts';\nimport 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, revealDelay } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { moveTo, resnap, setAsked } from '#lib/asked-value.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 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, AskedValue> = {\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, revealDelay(this.#xy));\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 (moveTo(this.#values[axis], this.#mappings()[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 mapping = this.#mappings()[axis];\n\t\tconst next = mapping.keyTarget(key, this.#values[axis].value);\n\t\tconst hasMoved = moveTo(this.#values[axis], mapping, 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 mapping = mappings[axis];\n\n\t\t\tresnap(this.#values[axis], mapping, this.numberAttribute(axis, mapping.bounds[0]));\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#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\tif (setAsked(axisValue, this.#mappings()[axis], toNumber(next))) this.render();\n\t}\n}\n"],"mappings":"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"}
@@ -5,13 +5,16 @@ import { SonicEnvelope } from "./elements/envelope.js";
5
5
  import { SonicMeter } from "./elements/meter.js";
6
6
  import { SonicNumber } from "./elements/number.js";
7
7
  import { SonicSegmented } from "./elements/segmented.js";
8
+ import { TimeRegions } from "./lib/time-regions.js";
8
9
  import { SonicSlider } from "./elements/slider.js";
10
+ import { SonicSpectrum } from "./elements/spectrum.js";
9
11
  import { SonicSplit } from "./elements/split.js";
10
12
  import { SonicSwitch } from "./elements/switch.js";
13
+ import { WaveMarker } from "./elements/wave-element.js";
11
14
  import { SonicWaveform } from "./elements/waveform.js";
12
15
  import { SonicWavestrip } from "./elements/wavestrip.js";
13
16
  import { SonicXy } from "./elements/xy.js";
14
17
  import { crossfadeGains } from "./lib/crossfade.js";
15
18
  import { envelopeCurve } from "./lib/envelope-curve.js";
16
19
  import { formatPercent, parsePercent } from "./lib/percent.js";
17
- export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, type WaveformPeaks, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
20
+ export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, type TimeRegions, type WaveMarker, type WaveformPeaks, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
package/dist/dev/index.js CHANGED
@@ -6,6 +6,7 @@ import { SonicMeter } from "./elements/meter.js";
6
6
  import { SonicNumber } from "./elements/number.js";
7
7
  import { SonicSegmented } from "./elements/segmented.js";
8
8
  import { SonicSlider } from "./elements/slider.js";
9
+ import { SonicSpectrum } from "./elements/spectrum.js";
9
10
  import { SonicSplit } from "./elements/split.js";
10
11
  import { SonicSwitch } from "./elements/switch.js";
11
12
  import { SonicWaveform } from "./elements/waveform.js";
@@ -14,4 +15,4 @@ import { SonicXy } from "./elements/xy.js";
14
15
  import { crossfadeGains } from "./lib/crossfade.js";
15
16
  import { formatPercent, parsePercent } from "./lib/percent.js";
16
17
 
17
- export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
18
+ export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
@@ -0,0 +1,22 @@
1
+ //#region src/lib/asked-value.ts
2
+ function moveTo(cell, mapping, next) {
3
+ if (!Number.isFinite(next)) return false;
4
+ const snapped = mapping.snap(next);
5
+ cell.asked = snapped;
6
+ if (snapped === cell.value) return false;
7
+ cell.value = snapped;
8
+ return true;
9
+ }
10
+ function setAsked(cell, mapping, next) {
11
+ if (!Number.isFinite(next)) return false;
12
+ const hasMoved = moveTo(cell, mapping, next);
13
+ cell.asked = next;
14
+ return hasMoved;
15
+ }
16
+ function resnap(cell, mapping, fallback) {
17
+ cell.value = mapping.snap(cell.asked ?? fallback);
18
+ }
19
+
20
+ //#endregion
21
+ export { moveTo, resnap, setAsked };
22
+ //# sourceMappingURL=asked-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asked-value.js","names":[],"sources":["../../../src/lib/asked-value.ts"],"sourcesContent":["import type { ValueMapping } from '#lib/value-mapping.ts';\n\nexport interface AskedValue {\n\tasked: number | undefined;\n\tvalue: number;\n}\n\n// A gesture: what was asked is the snapped value\n// eslint-disable-next-line unicorn/consistent-boolean-name -- a writer; the result says whether the value moved\nexport function moveTo(cell: AskedValue, mapping: ValueMapping, next: number): boolean {\n\tif (!Number.isFinite(next)) return false;\n\n\tconst snapped = mapping.snap(next);\n\n\tcell.asked = snapped;\n\tif (snapped === cell.value) return false;\n\n\tcell.value = snapped;\n\n\treturn true;\n}\n\n// A host write: the raw ask survives a change of range\n// eslint-disable-next-line unicorn/consistent-boolean-name -- a writer; the result says whether the value moved\nexport function setAsked(cell: AskedValue, mapping: ValueMapping, next: number): boolean {\n\tif (!Number.isFinite(next)) return false;\n\n\tconst hasMoved = moveTo(cell, mapping, next);\n\n\tcell.asked = next;\n\n\treturn hasMoved;\n}\n\nexport function resnap(cell: AskedValue, mapping: ValueMapping, fallback: number): void {\n\tcell.value = mapping.snap(cell.asked ?? fallback);\n}\n"],"mappings":";AASA,SAAgB,OAAO,MAAkB,SAAuB,MAAuB;CACtF,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG,OAAO;CAEnC,MAAM,UAAU,QAAQ,KAAK,IAAI;CAEjC,KAAK,QAAQ;CACb,IAAI,YAAY,KAAK,OAAO,OAAO;CAEnC,KAAK,QAAQ;CAEb,OAAO;AACR;AAIA,SAAgB,SAAS,MAAkB,SAAuB,MAAuB;CACxF,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG,OAAO;CAEnC,MAAM,WAAW,OAAO,MAAM,SAAS,IAAI;CAE3C,KAAK,QAAQ;CAEb,OAAO;AACR;AAEA,SAAgB,OAAO,MAAkB,SAAuB,UAAwB;CACvF,KAAK,QAAQ,QAAQ,KAAK,KAAK,SAAS,QAAQ;AACjD"}
@@ -4,20 +4,22 @@ export interface SurfaceSize {
4
4
  height: number;
5
5
  width: number;
6
6
  }
7
- export interface SurfaceLook<Colour extends string, Length extends string = never> {
7
+ export interface SurfaceLook<Colour extends string, Length extends string = never, Numeric extends string = never> {
8
8
  colours: Record<Colour, string>;
9
9
  isReducedMotion: boolean;
10
10
  lengths: Record<Length, number>;
11
+ numbers: Record<Numeric, number>;
11
12
  }
12
- export interface SurfaceFrame<Colour extends string, Length extends string = never> {
13
+ export interface SurfaceFrame<Colour extends string, Length extends string = never, Numeric extends string = never> {
13
14
  frameMs: number;
14
15
  isDirty: boolean;
15
16
  isRebuilt: boolean;
16
- look: SurfaceLook<Colour, Length>;
17
+ look: SurfaceLook<Colour, Length, Numeric>;
17
18
  size: SurfaceSize;
18
19
  }
19
20
  export interface Surface {
20
21
  invalidate: () => void;
22
+ readonly isVisible: boolean;
21
23
  rebuild: () => void;
22
24
  requestFrame: () => void;
23
25
  readonly size: SurfaceSize;
@@ -14,6 +14,9 @@ function readEach(properties, read) {
14
14
  return Object.fromEntries(Object.entries(properties).flatMap(([name, property]) => property === void 0 ? [] : [[name, read(property)]]));
15
15
  }
16
16
  var CanvasSurface = class {
17
+ get isVisible() {
18
+ return this.#isVisible;
19
+ }
17
20
  get size() {
18
21
  return this.#size;
19
22
  }
@@ -63,8 +66,10 @@ var CanvasSurface = class {
63
66
  this.#schedule();
64
67
  }
65
68
  #armTransitions() {
66
- const { style } = this.#options.canvas;
67
- style.setProperty("transition-property", Object.values(this.#options.colours).join(", "));
69
+ const { canvas, colours, numbers = {} } = this.#options;
70
+ const { style } = canvas;
71
+ const watched = [...Object.values(colours), ...Object.values(numbers)];
72
+ style.setProperty("transition-property", watched.join(", "));
68
73
  style.setProperty("transition-duration", "0.001ms");
69
74
  style.setProperty("transition-timing-function", "step-start");
70
75
  style.setProperty("transition-behavior", "allow-discrete");
@@ -148,12 +153,13 @@ var CanvasSurface = class {
148
153
  }, { once: true });
149
154
  }
150
155
  #read() {
151
- const { canvas, colours, lengths = {} } = this.#options;
156
+ const { canvas, colours, lengths = {}, numbers = {} } = this.#options;
152
157
  const styles = getComputedStyle(canvas);
153
158
  return {
154
159
  colours: readEach(colours, (property) => styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color)),
155
160
  isReducedMotion: this.#reducedMotion.matches,
156
- lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0))
161
+ lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0)),
162
+ numbers: readEach(numbers, (property) => readPxProperty(styles, property, 0))
157
163
  };
158
164
  }
159
165
  #rescale() {