@xsynaptic/sonic-ui 0.24.0 → 0.25.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +5 -3
  3. package/dist/default/define/region.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/envelope.js +11 -11
  6. package/dist/default/elements/form-element.js +10 -0
  7. package/dist/default/elements/readout.js +3 -2
  8. package/dist/default/elements/region-layer.js +251 -0
  9. package/dist/default/elements/region.js +157 -0
  10. package/dist/default/elements/sonic-element.js +26 -4
  11. package/dist/default/elements/split.js +2 -2
  12. package/dist/default/elements/toggle.js +2 -2
  13. package/dist/default/elements/value-element.js +9 -5
  14. package/dist/default/elements/value-gestures.js +7 -5
  15. package/dist/default/elements/wave-element.js +12 -3
  16. package/dist/default/elements/waveform.js +38 -2
  17. package/dist/default/elements/wavestrip.js +90 -17
  18. package/dist/default/elements/xy.js +3 -11
  19. package/dist/default/index.js +2 -1
  20. package/dist/default/lib/clock.js +1 -0
  21. package/dist/default/lib/drag-step.js +1 -1
  22. package/dist/default/lib/marker-band.js +40 -29
  23. package/dist/default/lib/region-drag.js +48 -0
  24. package/dist/default/lib/spoken-duration.js +3 -0
  25. package/dist/dev/define/region.d.ts +2 -0
  26. package/dist/dev/define/region.js +8 -0
  27. package/dist/dev/define/region.js.map +1 -0
  28. package/dist/dev/define.d.ts +3 -1
  29. package/dist/dev/define.js +1 -0
  30. package/dist/dev/elements/envelope.js +11 -11
  31. package/dist/dev/elements/envelope.js.map +1 -1
  32. package/dist/dev/elements/form-element.d.ts +1 -0
  33. package/dist/dev/elements/form-element.js +10 -0
  34. package/dist/dev/elements/form-element.js.map +1 -1
  35. package/dist/dev/elements/readout.js +3 -2
  36. package/dist/dev/elements/readout.js.map +1 -1
  37. package/dist/dev/elements/region-layer.js +252 -0
  38. package/dist/dev/elements/region-layer.js.map +1 -0
  39. package/dist/dev/elements/region.d.ts +29 -0
  40. package/dist/dev/elements/region.js +165 -0
  41. package/dist/dev/elements/region.js.map +1 -0
  42. package/dist/dev/elements/sonic-element.d.ts +1 -0
  43. package/dist/dev/elements/sonic-element.js +27 -5
  44. package/dist/dev/elements/sonic-element.js.map +1 -1
  45. package/dist/dev/elements/split.js +2 -2
  46. package/dist/dev/elements/split.js.map +1 -1
  47. package/dist/dev/elements/toggle.js +2 -2
  48. package/dist/dev/elements/toggle.js.map +1 -1
  49. package/dist/dev/elements/value-element.d.ts +3 -1
  50. package/dist/dev/elements/value-element.js +9 -5
  51. package/dist/dev/elements/value-element.js.map +1 -1
  52. package/dist/dev/elements/value-gestures.js +7 -5
  53. package/dist/dev/elements/value-gestures.js.map +1 -1
  54. package/dist/dev/elements/wave-element.d.ts +7 -1
  55. package/dist/dev/elements/wave-element.js +12 -3
  56. package/dist/dev/elements/wave-element.js.map +1 -1
  57. package/dist/dev/elements/waveform.d.ts +7 -0
  58. package/dist/dev/elements/waveform.js +38 -2
  59. package/dist/dev/elements/waveform.js.map +1 -1
  60. package/dist/dev/elements/wavestrip.d.ts +17 -1
  61. package/dist/dev/elements/wavestrip.js +90 -17
  62. package/dist/dev/elements/wavestrip.js.map +1 -1
  63. package/dist/dev/elements/xy.js +3 -11
  64. package/dist/dev/elements/xy.js.map +1 -1
  65. package/dist/dev/index.d.ts +3 -2
  66. package/dist/dev/index.js +2 -1
  67. package/dist/dev/lib/clock.js +1 -0
  68. package/dist/dev/lib/clock.js.map +1 -1
  69. package/dist/dev/lib/drag-step.js +1 -1
  70. package/dist/dev/lib/drag-step.js.map +1 -1
  71. package/dist/dev/lib/marker-band.d.ts +7 -0
  72. package/dist/dev/lib/marker-band.js +40 -29
  73. package/dist/dev/lib/marker-band.js.map +1 -1
  74. package/dist/dev/lib/region-drag.js +49 -0
  75. package/dist/dev/lib/region-drag.js.map +1 -0
  76. package/dist/dev/lib/spoken-duration.js +3 -0
  77. package/dist/dev/lib/spoken-duration.js.map +1 -1
  78. package/dist/styles/button.css +42 -15
  79. package/dist/styles/controls.css +1 -0
  80. package/dist/styles/dial.css +48 -33
  81. package/dist/styles/envelope.css +7 -6
  82. package/dist/styles/led.css +28 -13
  83. package/dist/styles/material/arc.css +2 -2
  84. package/dist/styles/material/cap.css +25 -15
  85. package/dist/styles/material/core.css +53 -1
  86. package/dist/styles/material/glass.css +12 -2
  87. package/dist/styles/material/groove.css +3 -3
  88. package/dist/styles/material/keycap.css +5 -5
  89. package/dist/styles/material/lens.css +1 -1
  90. package/dist/styles/material/pane.css +21 -11
  91. package/dist/styles/material/readout.css +23 -10
  92. package/dist/styles/material/{bracket.css → reticle.css} +15 -8
  93. package/dist/styles/material/scale.css +4 -4
  94. package/dist/styles/material/value.css +12 -3
  95. package/dist/styles/material/well.css +2 -2
  96. package/dist/styles/material.css +1 -1
  97. package/dist/styles/meter.css +24 -11
  98. package/dist/styles/number.css +2 -1
  99. package/dist/styles/panel.css +5 -6
  100. package/dist/styles/region.css +88 -0
  101. package/dist/styles/ring.css +5 -4
  102. package/dist/styles/screen.css +7 -5
  103. package/dist/styles/segmented.css +8 -7
  104. package/dist/styles/slider.css +40 -32
  105. package/dist/styles/spectrum.css +9 -1
  106. package/dist/styles/switch.css +52 -53
  107. package/dist/styles/toggle.css +33 -38
  108. package/dist/styles/waveform.css +5 -4
  109. package/dist/styles/wavestrip.css +15 -12
  110. package/dist/styles/xy.css +19 -19
  111. package/package.json +3 -2
@@ -1,10 +1,11 @@
1
- import { clampProportion } from "../lib/math.js";
1
+ import { clamp, 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
4
  import { scrubRegions } from "../lib/scrub.js";
5
5
  import { sortedRegions } from "../lib/time-regions.js";
6
6
  import { SonicWaveElement } from "./wave-element.js";
7
7
  import { createMarkerBand } from "../lib/marker-band.js";
8
+ import { RegionLayer } from "./region-layer.js";
8
9
  import { stripBars, stripGroove, stripRegions } from "../lib/strip-scene.js";
9
10
 
10
11
  //#region src/elements/wavestrip.ts
@@ -23,9 +24,10 @@ const lengths = {
23
24
  };
24
25
  const cancelZonePx = 48;
25
26
  const renderWavestrip = template(`
26
- <div class="sonic-wavestrip" role="slider" tabindex="0">
27
- <canvas class="sonic-wavestrip-canvas" aria-hidden="true"></canvas>
27
+ <div class="sonic-wavestrip">
28
+ <canvas class="sonic-wavestrip-canvas" role="slider" tabindex="0"></canvas>
28
29
  <div class="sonic-wavestrip-markers" aria-hidden="true"></div>
30
+ <div class="sonic-wavestrip-regions"></div>
29
31
  <div class="sonic-wavestrip-readout" popover="manual">
30
32
  <span></span>
31
33
  <input
@@ -63,6 +65,14 @@ var SonicWavestrip = class extends SonicWaveElement {
63
65
  set cancellable(isCancellable) {
64
66
  this.reflect("cancellable", isCancellable);
65
67
  }
68
+ /** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */
69
+ get formatSpokenRegion() {
70
+ return this.#formatSpokenRegion;
71
+ }
72
+ set formatSpokenRegion(format) {
73
+ this.#formatSpokenRegion = format;
74
+ this.#regions?.render();
75
+ }
66
76
  /** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */
67
77
  get peaks() {
68
78
  return this.#peaks;
@@ -72,6 +82,25 @@ var SonicWavestrip = class extends SonicWaveElement {
72
82
  this.renderEmpty();
73
83
  this.surface()?.invalidate();
74
84
  }
85
+ /** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */
86
+ get preview() {
87
+ return this.#preview;
88
+ }
89
+ set preview(seconds) {
90
+ this.#preview = Number.isFinite(seconds) ? seconds ?? void 0 : void 0;
91
+ this.render();
92
+ }
93
+ /** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */
94
+ get renderMarker() {
95
+ return this.#renderMarker;
96
+ }
97
+ set renderMarker(render) {
98
+ this.#renderMarker = render;
99
+ this.#drawMarker = render && ((marker, element) => {
100
+ render({ ...marker }, element);
101
+ });
102
+ if (this.isBound()) this.renderMarkers();
103
+ }
75
104
  control = renderWavestrip();
76
105
  canvas = requireChild(this.control, ".sonic-wavestrip-canvas", HTMLCanvasElement);
77
106
  colours = colours;
@@ -81,43 +110,75 @@ var SonicWavestrip = class extends SonicWaveElement {
81
110
  sizeProperty = "--_sonic-wavestrip-size";
82
111
  #bars;
83
112
  #buffered = [];
113
+ #drawMarker;
114
+ #formatSpokenRegion;
84
115
  #markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
85
116
  #paintedKey = "";
86
117
  #peaks;
118
+ #preview;
119
+ #regions;
120
+ #renderMarker;
87
121
  /** Viewport x of a time, clamped to the strip */
88
122
  clientXOf(value) {
89
123
  const { startPx, travelPx } = this.#axis();
90
124
  return startPx + this.mapping().proportionOf(value) * travelPx;
91
125
  }
92
126
  connectedCallback() {
93
- this.upgradeProperties("buffered", "cancellable", "peaks");
127
+ this.upgradeProperties("buffered", "cancellable", "formatSpokenRegion", "peaks", "preview", "renderMarker");
94
128
  super.connectedCallback();
95
129
  }
130
+ valueFromPoint(clientX, _clientY) {
131
+ const { startPx, travelPx } = this.#axis();
132
+ const mapping = this.mapping();
133
+ return clamp(mapping.valueAt(clampProportion((clientX - startPx) / travelPx)), ...mapping.bounds);
134
+ }
96
135
  connect(signal) {
97
136
  const { control } = this;
137
+ this.#regions = new RegionLayer({
138
+ axis: () => this.#axis(),
139
+ control,
140
+ element: this,
141
+ isDisabled: () => this.isDisabled(),
142
+ layer: requireChild(control, ".sonic-wavestrip-regions", HTMLDivElement),
143
+ mapping: () => this.mapping(),
144
+ outside: () => this.#outside(),
145
+ seek: (event) => {
146
+ if (this.input(this.valueFromPoint(event.clientX, event.clientY))) this.dispatchEvent(new Event("change", { bubbles: true }));
147
+ },
148
+ speak: ({ end, start }) => this.#formatSpokenRegion?.(start, end) ?? this.spokenText(start) ?? this.notation.format(start)
149
+ }, signal);
98
150
  super.connect(signal);
99
151
  this.bindHover(control, signal, {
152
+ changed: () => {
153
+ if (this.#preview !== void 0) this.render();
154
+ },
100
155
  place: () => {
101
156
  this.#placeReadout();
102
157
  },
103
- valueAt: (event) => {
104
- const axis = this.#axis();
105
- const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
106
- const mapping = this.mapping();
107
- return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
108
- }
158
+ valueAt: (event) => this.#markerStartAt(event, this.#axis()) ?? this.mapping().snap(this.valueFromPoint(event.clientX, event.clientY))
109
159
  });
110
160
  this.bindGestures(control, signal, (event) => this.#grab(event));
111
161
  }
112
162
  draw() {
113
163
  const surface = this.surface();
114
164
  this.#placeReadout();
165
+ this.#regions?.follow();
115
166
  this.showCurrentMarker(this.scrubState().played);
116
167
  if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
117
168
  }
169
+ focusTarget() {
170
+ return this.canvas;
171
+ }
118
172
  isEmpty() {
119
173
  return !this.#peaks || this.#peaks.length === 0;
120
174
  }
175
+ /** The first point marker is the one a press there snaps to */
176
+ markersInReach(point, widthPx) {
177
+ const regions = this.markerProportions().flatMap(([from, to], index) => {
178
+ return this.markerList()[index]?.end !== void 0 && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];
179
+ });
180
+ return [...this.#markerBand.inReach(point, widthPx), ...regions];
181
+ }
121
182
  paint(context, frame) {
122
183
  const { look, size } = frame;
123
184
  const strip = this.#stripRegions(size.width);
@@ -134,11 +195,14 @@ var SonicWavestrip = class extends SonicWaveElement {
134
195
  ]);
135
196
  }
136
197
  renderMarkers() {
137
- this.#markerBand.render(this.markerList(), this.markerProportions());
198
+ this.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);
138
199
  }
139
200
  scrubsKeyRepeat() {
140
201
  return true;
141
202
  }
203
+ usesChild(child) {
204
+ return child instanceof Element && child.localName === "sonic-region";
205
+ }
142
206
  #axis() {
143
207
  const box = this.canvas.getBoundingClientRect();
144
208
  return {
@@ -163,6 +227,13 @@ var SonicWavestrip = class extends SonicWaveElement {
163
227
  }
164
228
  #grab(event) {
165
229
  const axis = this.#axis();
230
+ const marker = this.#markerStartAt(event, axis);
231
+ const regions = this.#regions;
232
+ if (regions?.isHeld() === true) {
233
+ regions.abandon();
234
+ return;
235
+ }
236
+ if (marker === void 0 && regions?.take(event) === true) return void 0;
166
237
  const at = (event.clientX - axis.startPx) / axis.travelPx;
167
238
  const outside = this.#outside();
168
239
  const grabbed = {
@@ -170,16 +241,16 @@ var SonicWavestrip = class extends SonicWaveElement {
170
241
  position: axis.position,
171
242
  travelPx: axis.travelPx
172
243
  };
173
- this.input(this.#markerAt(event, axis) ?? this.mapping().valueAt(at));
244
+ this.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));
174
245
  return outside ? {
175
246
  ...grabbed,
176
247
  outside
177
248
  } : grabbed;
178
249
  }
179
- #markerAt(event, { startPx, topPx, travelPx }) {
180
- const index = this.#markerBand.at({
181
- x: event.clientX - startPx,
182
- y: event.clientY - topPx
250
+ #markerStartAt({ clientX, clientY }, { startPx, topPx, travelPx }) {
251
+ const [index] = this.#markerBand.inReach({
252
+ x: clientX - startPx,
253
+ y: clientY - topPx
183
254
  }, travelPx);
184
255
  return index === void 0 ? void 0 : this.markerList()[index]?.start;
185
256
  }
@@ -196,7 +267,9 @@ var SonicWavestrip = class extends SonicWaveElement {
196
267
  #stripRegions(width) {
197
268
  const mapping = this.mapping();
198
269
  const { proportionOf } = mapping;
199
- const { played, scrub } = scrubRegions(this.scrubState(), this.value, mapping);
270
+ const state = this.scrubState();
271
+ const preview = state.from !== void 0 || this.hoverValue !== void 0 || this.#preview === void 0 ? void 0 : proportionOf(this.#preview);
272
+ const { played, scrub = preview } = scrubRegions(state, this.value, mapping);
200
273
  return stripRegions({
201
274
  buffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
202
275
  played,
@@ -1 +1 @@
1
- {"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { 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 { scrubRegions } from '#lib/scrub.ts';\nimport { stripBars, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\tgrooveSize: '--_sonic-wavestrip-groove-size',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\" 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/** Loaded spans, in seconds */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\t/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\tprotected readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly numbers = {};\n\n\tprotected readonly sheet = 'wavestrip.css';\n\n\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\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\t/** Viewport x of a time, clamped to the strip */\n\tclientXOf(value: Seconds): number {\n\t\tconst { startPx, travelPx } = this.#axis();\n\n\t\treturn startPx + this.mapping().proportionOf(value) * travelPx;\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('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\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.scrubState().played);\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected 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);\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 mapping = this.mapping();\n\t\tconst { proportionOf } = mapping;\n\t\tconst { played, scrub } = scrubRegions(this.scrubState(), this.value, mapping);\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed,\n\t\t\t\t...(scrub === undefined ? {} : { scrub }),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;AAyBA,MAAM,UAAU;CACf,UAAU;CACV,QAAQ;CACR,OAAO;CACP,MAAM;AACP;AAEA,MAAM,UAAU;CACf,UAAU;CACV,aAAa;CACb,YAAY;CACZ,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;;CAGpE,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;;CAGA,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,UAAU,CAAC;CAE9B,AAAmB,QAAQ;CAE3B,AAAmB,eAAe;CAElC;CAEA,YAAqC,CAAC;CAEtC,AAAS,cAAc,iBACtB,aAAa,KAAK,SAAS,4BAA4B,cAAc,CACtE;CAEA,cAAc;CAEd;;CAGA,UAAU,OAAwB;EACjC,MAAM,EAAE,SAAS,aAAa,KAAK,MAAM;EAEzC,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,aAAa,KAAK,IAAI;CACvD;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,eAAe,OAAO;EACzD,MAAM,kBAAkB;CACzB;CAEA,AAAmB,QAAQ,QAA2B;EACrD,MAAM,EAAE,YAAY;EAEpB,MAAM,QAAQ,MAAM;EACpB,KAAK,UAAU,SAAS,QAAQ;GAC/B,aAAa;IACZ,KAAK,cAAc;GACpB;GACA,UAAU,UAAU;IACnB,MAAM,OAAO,KAAK,MAAM;IACxB,MAAM,KAAK,iBAAiB,MAAM,UAAU,KAAK,WAAW,KAAK,QAAQ;IACzE,MAAM,UAAU,KAAK,QAAQ;IAE7B,OAAO,KAAK,UAAU,OAAO,IAAI,KAAK,QAAQ,KAAK,QAAQ,QAAQ,EAAE,CAAC;GACvE;EACD,CAAC;EACD,KAAK,aAAa,SAAS,SAAS,UAAU,KAAK,MAAM,KAAK,CAAC;CAChE;CAEA,AAAU,OAAa;EACtB,MAAM,UAAU,KAAK,QAAQ;EAE7B,KAAK,cAAc;EACnB,KAAK,kBAAkB,KAAK,WAAW,CAAC,CAAC,MAAM;EAC/C,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,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,IAAI,CAAC,WAAW,KAAK,OAAO,OAAO,KAAK;EAExC,MAAM,QAAQ,KAAK;EACnB,MAAM,SAAS,YAAY,MAAM,KAAK,OAAO;EAC7C,MAAM,QAAQ,SAAS,CAAC,MAAM,IAAI,CAAC;EACnC,MAAM,OAAO,SAAS,MAAM,SAAS,IAAI,UAAU,OAAO,MAAM,KAAK,OAAO,IAAI;EAEhF,KAAK,QAAQ;EACb,IAAI,KAAK,WAAW,GAAG,OAAO;EAE9B,MAAM,OAAO,IAAI,OAAO;EAExB,KAAK,MAAM,EAAE,QAAQ,QAAQ,OAAO,GAAG,OAAO,MAC7C,KAAK,UAAU,GAAG,GAAG,OAAO,QAAQ,MAAM;EAE3C,KAAK,QAAQ;EAEb,OAAO;CACR;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,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,UAAU,KAAK,QAAQ;EAC7B,MAAM,EAAE,iBAAiB;EACzB,MAAM,EAAE,QAAQ,UAAU,aAAa,KAAK,WAAW,GAAG,KAAK,OAAO,OAAO;EAE7E,OAAO,aACN;GACC,UAAU,KAAK,UAAU,KAAK,CAAC,OAAO,SAAS,CAAC,aAAa,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC;GACvF;GACA,GAAI,UAAU,SAAY,CAAC,IAAI,EAAE,MAAM;EACxC,GACA,KACD;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 { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { RegionLayer } from '#elements/region-layer.ts';\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { createMarkerBand } from '#lib/marker-band.ts';\nimport { clamp, clampProportion } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { stripBars, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nexport type MarkerRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\tgrooveSize: '--_sonic-wavestrip-groove-size',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" role=\"slider\" tabindex=\"0\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-regions\"></div>\n\t\t\t<div class=\"sonic-wavestrip-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-wavestrip-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\t/** Loaded spans, in seconds */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\t/** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */\n\tget formatSpokenRegion(): ((start: Seconds, end: Seconds) => string) | undefined {\n\t\treturn this.#formatSpokenRegion;\n\t}\n\n\tset formatSpokenRegion(format: ((start: Seconds, end: Seconds) => string) | undefined) {\n\t\tthis.#formatSpokenRegion = format;\n\t\tthis.#regions?.render();\n\t}\n\n\t/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\t/** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */\n\tget preview(): Seconds | undefined {\n\t\treturn this.#preview;\n\t}\n\n\tset preview(seconds: null | Seconds | undefined) {\n\t\tthis.#preview = Number.isFinite(seconds) ? (seconds ?? undefined) : undefined;\n\t\tthis.render();\n\t}\n\n\t/** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */\n\tget renderMarker(): MarkerRender | undefined {\n\t\treturn this.#renderMarker;\n\t}\n\n\tset renderMarker(render: MarkerRender | undefined) {\n\t\tthis.#renderMarker = render;\n\t\tthis.#drawMarker =\n\t\t\trender &&\n\t\t\t((marker, element) => {\n\t\t\t\trender({ ...marker }, element);\n\t\t\t});\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tprotected readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly numbers = {};\n\n\tprotected readonly sheet = 'wavestrip.css';\n\n\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\t#drawMarker: MarkerRender | undefined;\n\n\t#formatSpokenRegion: ((start: Seconds, end: Seconds) => string) | undefined;\n\n\treadonly #markerBand = createMarkerBand<WaveMarker>(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\t#preview: Seconds | undefined;\n\n\t#regions: RegionLayer | undefined;\n\n\t#renderMarker: MarkerRender | undefined;\n\n\t/** Viewport x of a time, clamped to the strip */\n\tclientXOf(value: Seconds): number {\n\t\tconst { startPx, travelPx } = this.#axis();\n\n\t\treturn startPx + this.mapping().proportionOf(value) * travelPx;\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'cancellable',\n\t\t\t'formatSpokenRegion',\n\t\t\t'peaks',\n\t\t\t'preview',\n\t\t\t'renderMarker',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { startPx, travelPx } = this.#axis();\n\t\tconst mapping = this.mapping();\n\n\t\treturn clamp(\n\t\t\tmapping.valueAt(clampProportion((clientX - startPx) / travelPx)),\n\t\t\t...mapping.bounds,\n\t\t);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst { control } = this;\n\n\t\tthis.#regions = new RegionLayer(\n\t\t\t{\n\t\t\t\taxis: () => this.#axis(),\n\t\t\t\tcontrol,\n\t\t\t\telement: this,\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tlayer: requireChild(control, '.sonic-wavestrip-regions', HTMLDivElement),\n\t\t\t\tmapping: () => this.mapping(),\n\t\t\t\toutside: () => this.#outside(),\n\t\t\t\tseek: (event) => {\n\t\t\t\t\tif (this.input(this.valueFromPoint(event.clientX, event.clientY))) {\n\t\t\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tspeak: ({ end, start }) =>\n\t\t\t\t\tthis.#formatSpokenRegion?.(start, end) ??\n\t\t\t\t\tthis.spokenText(start) ??\n\t\t\t\t\tthis.notation.format(start),\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsuper.connect(signal);\n\t\tthis.bindHover(control, signal, {\n\t\t\tchanged: () => {\n\t\t\t\tif (this.#preview !== undefined) this.render();\n\t\t\t},\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\tvalueAt: (event) =>\n\t\t\t\tthis.#markerStartAt(event, this.#axis()) ??\n\t\t\t\tthis.mapping().snap(this.valueFromPoint(event.clientX, event.clientY)),\n\t\t});\n\t\tthis.bindGestures(control, signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tthis.#regions?.follow();\n\t\tthis.showCurrentMarker(this.scrubState().played);\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.canvas;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\t/** The first point marker is the one a press there snaps to */\n\tprotected markersInReach(point: Point, widthPx: number): Array<number> {\n\t\tconst regions = this.markerProportions().flatMap(([from, to], index) => {\n\t\t\tconst isRegion = this.markerList()[index]?.end !== undefined;\n\n\t\t\treturn isRegion && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];\n\t\t});\n\n\t\treturn [...this.#markerBand.inReach(point, widthPx), ...regions];\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override usesChild(child: Node): boolean {\n\t\treturn child instanceof Element && child.localName === 'sonic-region';\n\t}\n\n\t#axis(): ValueAxis & { startPx: number; topPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttopPx: box.top,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst peaks = this.#peaks;\n\t\tconst groove = stripGroove(size, look.lengths);\n\t\tconst drawn = groove ? [groove] : [];\n\t\tconst bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;\n\n\t\tthis.#bars = undefined;\n\t\tif (bars.length === 0) return undefined;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of bars) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#grab(event: PointerEvent): undefined | ValueAxis {\n\t\tconst axis = this.#axis();\n\t\tconst marker = this.#markerStartAt(event, axis);\n\t\tconst regions = this.#regions;\n\n\t\tif (regions?.isHeld() === true) {\n\t\t\tregions.abandon();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (marker === undefined && regions?.take(event) === true) return undefined;\n\n\t\tconst at = (event.clientX - axis.startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = {\n\t\t\tfromProportion: clampProportion(at),\n\t\t\tposition: axis.position,\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\n\t\tthis.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#markerStartAt(\n\t\t{ clientX, clientY }: PointerEvent,\n\t\t{ startPx, topPx, travelPx }: { startPx: number; topPx: number; travelPx: number },\n\t): number | undefined {\n\t\tconst [index] = this.#markerBand.inReach(\n\t\t\t{ x: clientX - startPx, y: clientY - topPx },\n\t\t\ttravelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : this.markerList()[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\tconst box = this.control.getBoundingClientRect();\n\t\tconst zone = readPxProperty(\n\t\t\tgetComputedStyle(this.control),\n\t\t\t'--_sonic-cancel-zone',\n\t\t\tcancelZonePx,\n\t\t);\n\n\t\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst mapping = this.mapping();\n\t\tconst { proportionOf } = mapping;\n\t\tconst state = this.scrubState();\n\t\tconst isOwn = state.from !== undefined || this.hoverValue !== undefined;\n\t\tconst preview = isOwn || this.#preview === undefined ? undefined : proportionOf(this.#preview);\n\t\tconst { played, scrub = preview } = scrubRegions(state, this.value, mapping);\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed,\n\t\t\t\t...(scrub === undefined ? {} : { scrub }),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -30,7 +30,7 @@ const orientations = {
30
30
  };
31
31
  const renderXy = template(`
32
32
  <div class="sonic-xy">
33
- <div class="sonic-xy-bracket" aria-hidden="true"></div>
33
+ <div class="sonic-xy-reticle" aria-hidden="true"></div>
34
34
  <div class="sonic-xy-field" aria-hidden="true"></div>
35
35
  <div class="sonic-xy-puck">
36
36
  <div class="sonic-xy-axis" data-sonic-axis="x" role="slider" tabindex="0"></div>
@@ -408,16 +408,8 @@ var SonicXy = class extends SonicFormElement {
408
408
  }
409
409
  }
410
410
  #renderForm() {
411
- const { name, x, y } = this;
412
- const state = `${String(x)},${String(y)}`;
413
- if (name === "") {
414
- this.writeFormValue(null, state);
415
- return;
416
- }
417
- const fields = new FormData();
418
- fields.set(`${name}.x`, String(x));
419
- fields.set(`${name}.y`, String(y));
420
- this.writeFormValue(fields, state);
411
+ const { x, y } = this;
412
+ this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
421
413
  }
422
414
  #renderReadout() {
423
415
  const { isRevealed } = this.#claim;
@@ -1 +1 @@
1
- {"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldGesture, FieldHold, FieldInput } from '#elements/field-gesture.ts';\nimport type { FieldAxis } from '#lib/field.ts';\nimport type { ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { createDoublePress } from '#lib/double-press.ts';\nimport { pointerPosition } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion, toNumber } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\nconst axes = ['x', 'y'] as const;\n\nconst keyMoves = new Map<string, [FieldAxis, string]>([\n\t['ArrowDown', ['y', 'ArrowDown']],\n\t['ArrowLeft', ['x', 'ArrowLeft']],\n\t['ArrowRight', ['x', 'ArrowRight']],\n\t['ArrowUp', ['y', 'ArrowUp']],\n\t['End', ['x', 'PageUp']],\n\t['Home', ['x', 'PageDown']],\n\t['PageDown', ['y', 'PageDown']],\n\t['PageUp', ['y', 'PageUp']],\n]);\n\nconst orientations = { x: 'horizontal', y: 'vertical' } as const;\n\nconst renderXy = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-xy\">\n\t\t\t<div class=\"sonic-xy-bracket\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-field\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-puck\">\n\t\t\t\t<div class=\"sonic-xy-axis\" data-sonic-axis=\"x\" role=\"slider\" tabindex=\"0\"></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"sonic-xy-axis\"\n\t\t\t\t\tdata-sonic-axis=\"y\"\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t\t<div class=\"sonic-xy-readout\" aria-hidden=\"true\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicXy extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'name',\n\t\t'readout',\n\t\t'x',\n\t\t'x-label',\n\t\t'x-max',\n\t\t'x-midpoint',\n\t\t'x-min',\n\t\t'x-origin',\n\t\t'x-step',\n\t\t'x-taper',\n\t\t'y',\n\t\t'y-label',\n\t\t'y-max',\n\t\t'y-midpoint',\n\t\t'y-min',\n\t\t'y-origin',\n\t\t'y-step',\n\t\t'y-taper',\n\t];\n\n\tget doublePress(): 'none' | 'reset' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'none';\n\t}\n\n\tset doublePress(gesture: 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget formatValue(): ((value: number, axis: FieldAxis) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isEnabled: boolean) {\n\t\tthis.reflect('readout', isEnabled);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\tget x(): number {\n\t\treturn this.#models.x.value;\n\t}\n\n\tset x(next: null | number | undefined) {\n\t\tthis.#write('x', next);\n\t}\n\n\tget xDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-default');\n\t}\n\n\tset xDefault(value: number | undefined) {\n\t\tthis.reflect('x-default', value);\n\t}\n\n\tget xLabel(): string | undefined {\n\t\treturn this.getAttribute('x-label') ?? undefined;\n\t}\n\n\tset xLabel(label: string | undefined) {\n\t\tthis.reflect('x-label', label);\n\t}\n\n\tget xMax(): number {\n\t\treturn this.numberAttribute('x-max', 100);\n\t}\n\n\tset xMax(value: number | undefined) {\n\t\tthis.reflect('x-max', value);\n\t}\n\n\tget xMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-midpoint');\n\t}\n\n\tset xMidpoint(value: number | undefined) {\n\t\tthis.reflect('x-midpoint', value);\n\t}\n\n\tget xMin(): number {\n\t\treturn this.numberAttribute('x-min', 0);\n\t}\n\n\tset xMin(value: number | undefined) {\n\t\tthis.reflect('x-min', value);\n\t}\n\n\tget xOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-origin');\n\t}\n\n\tset xOrigin(value: number | undefined) {\n\t\tthis.reflect('x-origin', value);\n\t}\n\n\tget xStep(): number {\n\t\treturn this.numberAttribute('x-step', 1);\n\t}\n\n\tset xStep(value: number | undefined) {\n\t\tthis.reflect('x-step', value);\n\t}\n\n\tget xTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('x');\n\t}\n\n\tset xTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('x-taper', curve);\n\t}\n\n\tget y(): number {\n\t\treturn this.#models.y.value;\n\t}\n\n\tset y(next: null | number | undefined) {\n\t\tthis.#write('y', next);\n\t}\n\n\tget yDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-default');\n\t}\n\n\tset yDefault(value: number | undefined) {\n\t\tthis.reflect('y-default', value);\n\t}\n\n\tget yLabel(): string | undefined {\n\t\treturn this.getAttribute('y-label') ?? undefined;\n\t}\n\n\tset yLabel(label: string | undefined) {\n\t\tthis.reflect('y-label', label);\n\t}\n\n\tget yMax(): number {\n\t\treturn this.numberAttribute('y-max', 100);\n\t}\n\n\tset yMax(value: number | undefined) {\n\t\tthis.reflect('y-max', value);\n\t}\n\n\tget yMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-midpoint');\n\t}\n\n\tset yMidpoint(value: number | undefined) {\n\t\tthis.reflect('y-midpoint', value);\n\t}\n\n\tget yMin(): number {\n\t\treturn this.numberAttribute('y-min', 0);\n\t}\n\n\tset yMin(value: number | undefined) {\n\t\tthis.reflect('y-min', value);\n\t}\n\n\tget yOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-origin');\n\t}\n\n\tset yOrigin(value: number | undefined) {\n\t\tthis.reflect('y-origin', value);\n\t}\n\n\tget yStep(): number {\n\t\treturn this.numberAttribute('y-step', 1);\n\t}\n\n\tset yStep(value: number | undefined) {\n\t\tthis.reflect('y-step', value);\n\t}\n\n\tget yTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('y');\n\t}\n\n\tset yTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('y-taper', curve);\n\t}\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\t#currentAxis: FieldAxis = 'x';\n\n\treadonly #doublePress = createDoublePress();\n\n\treadonly #xy = renderXy();\n\n\treadonly #field = requireChild(this.#xy, '.sonic-xy-field', HTMLDivElement);\n\n\t#formatValue: ((value: number, axis: FieldAxis) => string) | undefined;\n\n\t#gesture: FieldGesture<FieldHold> | undefined;\n\n\t#isRevealed = false;\n\n\treadonly #models: Record<FieldAxis, ValueModel> = {\n\t\tx: createValueModel(() => this.#spec('x')),\n\t\ty: createValueModel(() => this.#spec('y')),\n\t};\n\n\treadonly #parts: Record<FieldAxis, HTMLDivElement> = {\n\t\tx: requireChild(this.#xy, '[data-sonic-axis=\"x\"]', HTMLDivElement),\n\t\ty: requireChild(this.#xy, '[data-sonic-axis=\"y\"]', HTMLDivElement),\n\t};\n\n\treadonly #puck = requireChild(this.#xy, '.sonic-xy-puck', HTMLDivElement);\n\n\treadonly #readout = new Readout(requireChild(this.#xy, '.sonic-xy-readout', HTMLDivElement));\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'x' || name === 'y') && this.#gesture?.current()) return;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gesture?.end();\n\n\t\tthis.#respec(name);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'doublePress',\n\t\t\t'readout',\n\t\t\t'xDefault',\n\t\t\t'xLabel',\n\t\t\t'xMax',\n\t\t\t'xMidpoint',\n\t\t\t'xMin',\n\t\t\t'xOrigin',\n\t\t\t'xStep',\n\t\t\t'xTaper',\n\t\t\t'yDefault',\n\t\t\t'yLabel',\n\t\t\t'yMax',\n\t\t\t'yMidpoint',\n\t\t\t'yMin',\n\t\t\t'yOrigin',\n\t\t\t'yStep',\n\t\t\t'yTaper',\n\t\t\t'formatValue',\n\t\t\t'x',\n\t\t\t'y',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\tthis.#respec(...axes);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.#xy;\n\n\t\tthis.keepControl(xy, signal);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(xy, 'xy.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.#bindPointer(xy, signal);\n\t\tthis.#bindKeys(xy, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#parts[this.#currentAxis];\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst { style } = this.#xy;\n\n\t\tfor (const axis of axes) {\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst mapping = model.mapping();\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));\n\t\t}\n\t\tthis.#renderAria();\n\t\tthis.#renderForm();\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [x = NaN, y = NaN] = state.split(',').map(Number);\n\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\t#axisText(axis: FieldAxis): string {\n\t\tconst { value } = this.#models[axis];\n\t\tconst label = this.getAttribute(`${axis}-label`) ?? axis.toUpperCase();\n\n\t\treturn `${label} ${this.#formatValue?.(value, axis) ?? String(value)}`;\n\t}\n\n\t#bindKeys(xy: HTMLElement, signal: AbortSignal): void {\n\t\txy.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented) return;\n\n\t\t\t\tconst move = keyMoves.get(event.key);\n\t\t\t\tif (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#renderReadout();\n\t\t\t\tif (move) this.#keyTo(...move);\n\t\t\t\telse this.#reset();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\t// A key on the other axis moves focus between the two parts\n\t\t\t\tif (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bindPointer(xy: HTMLElement, signal: AbortSignal): void {\n\t\t// A press on the field would take focus off the axis part and select the page's text\n\t\txy.addEventListener(\n\t\t\t'mousedown',\n\t\t\t(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t() => {\n\t\t\t\tthis.#doublePress.forget();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.isDisabled() || isMenuPress(event)) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tif (!isResetPress(event)) {\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\n\t\t\t\t\t\treturn this.#grab(event);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tinput: (_hold, next) => {\n\t\t\t\t\tif (!this.#input(next)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\tlift: (_hold, event) => {\n\t\t\t\t\tif (!this.#doublePress.press(event) || this.doublePress !== 'reset') return;\n\n\t\t\t\t\tthis.#reset();\n\t\t\t\t},\n\t\t\t\trelease: (_hold, moved) => {\n\t\t\t\t\tif (moved.length > 0) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#commit(next: FieldInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): FieldHold {\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst { x, y } = this.#models;\n\t\tconst from = { x: x.value, y: y.value };\n\t\tconst travel = {\n\t\t\tx: Math.max(1, field.width - puck.width),\n\t\t\ty: Math.max(1, field.height - puck.height),\n\t\t};\n\t\tconst position = pointerPosition(event);\n\t\tconst pressed = {\n\t\t\tx: (position.x - field.left - puck.width / 2) / travel.x,\n\t\t\ty: (position.y + field.bottom - puck.height / 2) / travel.y,\n\t\t};\n\t\tconst isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);\n\n\t\tif (!isOnPuck) {\n\t\t\tthis.#input({ x: x.mapping().valueAt(pressed.x), y: y.mapping().valueAt(pressed.y) });\n\t\t}\n\n\t\tconst hold = (axis: FieldAxis) => ({\n\t\t\tfrom: from[axis],\n\t\t\tmodel: this.#models[axis],\n\t\t\t...(isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) }),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn { axes: { x: hold('x'), y: hold('y') } };\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: FieldInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;\n\t\t}\n\t\tif (!hasMoved) return false;\n\n\t\tthis.render();\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\t#keyTo(axis: FieldAxis, key: string): void {\n\t\tconst model = this.#models[axis];\n\t\tconst hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);\n\n\t\tthis.#currentAxis = axis;\n\t\tthis.render();\n\t\tthis.#parts[axis].focus();\n\t\tif (!hasMoved) return;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#renderAria(): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst part = this.#parts[axis];\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst [low, high] = model.mapping().bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\twriteAttribute(part, 'aria-orientation', orientations[axis]);\n\t\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\t\twriteAttribute(part, 'aria-valuenow', String(model.value));\n\t\t\twriteAttribute(part, 'aria-valuetext', this.#valueText(axis));\n\t\t\tthis.forwardNaming(part, true);\n\t\t\twriteAttribute(part, 'aria-hidden', isCurrent ? undefined : 'true');\n\t\t\twriteAttribute(part, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t\twriteAttribute(part, 'tabindex', isDisabled ? undefined : stop);\n\t\t}\n\t}\n\n\t#renderForm(): void {\n\t\tconst { name, x, y } = this;\n\t\tconst state = `${String(x)},${String(y)}`;\n\n\t\tif (name === '') {\n\t\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\t\tthis.writeFormValue(null, state);\n\t\t\treturn;\n\t\t}\n\n\t\tconst fields = new FormData();\n\n\t\tfields.set(`${name}.x`, String(x));\n\t\tfields.set(`${name}.y`, String(y));\n\t\tthis.writeFormValue(fields, state);\n\t}\n\n\t#renderReadout(): void {\n\t\tconst { isRevealed } = this.#claim;\n\n\t\tthis.#readout.show({\n\t\t\tanchor: this.#puck,\n\t\t\tisOpen: isRevealed && this.readout,\n\t\t\ttext: this.#valueText('x'),\n\t\t});\n\t\tif (isRevealed === this.#isRevealed) return;\n\n\t\tthis.#isRevealed = isRevealed;\n\t\tthis.toggleState('revealed', isRevealed);\n\t\tthis.dispatchEvent(new Event('sonic-reveal', { bubbles: true }));\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit({ x: this.xDefault, y: this.yDefault });\n\t}\n\n\t#respec(...forgotten: Array<string>): void {\n\t\tfor (const axis of axes) {\n\t\t\tthis.#models[axis].respec(\n\t\t\t\tthis.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)),\n\t\t\t\t{ forgetAsk: forgotten.includes(axis) },\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#spec(axis: FieldAxis): ValueSpec {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn {\n\t\t\tmax: this.numberAttribute(`${axis}-max`, 100),\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: false,\n\t\t\tisWrapping: false,\n\t\t\tmin: this.numberAttribute(`${axis}-min`, 0),\n\t\t\tstep: this.numberAttribute(`${axis}-step`, 1),\n\t\t\ttaper: this.#taper(axis),\n\t\t};\n\t}\n\n\t#taper(axis: FieldAxis): ValueSpec['taper'] {\n\t\treturn this.getAttribute(`${axis}-taper`) === 'log' ? 'log' : 'linear';\n\t}\n\n\t#valueText(axis: FieldAxis): string {\n\t\treturn `${this.#axisText(axis)}, ${this.#axisText(axis === 'x' ? 'y' : 'x')}`;\n\t}\n\n\t#write(axis: FieldAxis, next: null | number | undefined): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.#respec(axis);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#models[axis].write(toNumber(next))) this.render();\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldGesture, FieldHold, FieldInput } from '#elements/field-gesture.ts';\nimport type { FieldAxis } from '#lib/field.ts';\nimport type { ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { createDoublePress } from '#lib/double-press.ts';\nimport { pointerPosition } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion, toNumber } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\nconst axes = ['x', 'y'] as const;\n\nconst keyMoves = new Map<string, [FieldAxis, string]>([\n\t['ArrowDown', ['y', 'ArrowDown']],\n\t['ArrowLeft', ['x', 'ArrowLeft']],\n\t['ArrowRight', ['x', 'ArrowRight']],\n\t['ArrowUp', ['y', 'ArrowUp']],\n\t['End', ['x', 'PageUp']],\n\t['Home', ['x', 'PageDown']],\n\t['PageDown', ['y', 'PageDown']],\n\t['PageUp', ['y', 'PageUp']],\n]);\n\nconst orientations = { x: 'horizontal', y: 'vertical' } as const;\n\nconst renderXy = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-xy\">\n\t\t\t<div class=\"sonic-xy-reticle\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-field\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-puck\">\n\t\t\t\t<div class=\"sonic-xy-axis\" data-sonic-axis=\"x\" role=\"slider\" tabindex=\"0\"></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"sonic-xy-axis\"\n\t\t\t\t\tdata-sonic-axis=\"y\"\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t\t<div class=\"sonic-xy-readout\" aria-hidden=\"true\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicXy extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'name',\n\t\t'readout',\n\t\t'x',\n\t\t'x-label',\n\t\t'x-max',\n\t\t'x-midpoint',\n\t\t'x-min',\n\t\t'x-origin',\n\t\t'x-step',\n\t\t'x-taper',\n\t\t'y',\n\t\t'y-label',\n\t\t'y-max',\n\t\t'y-midpoint',\n\t\t'y-min',\n\t\t'y-origin',\n\t\t'y-step',\n\t\t'y-taper',\n\t];\n\n\tget doublePress(): 'none' | 'reset' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'none';\n\t}\n\n\tset doublePress(gesture: 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget formatValue(): ((value: number, axis: FieldAxis) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isEnabled: boolean) {\n\t\tthis.reflect('readout', isEnabled);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\tget x(): number {\n\t\treturn this.#models.x.value;\n\t}\n\n\tset x(next: null | number | undefined) {\n\t\tthis.#write('x', next);\n\t}\n\n\tget xDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-default');\n\t}\n\n\tset xDefault(value: number | undefined) {\n\t\tthis.reflect('x-default', value);\n\t}\n\n\tget xLabel(): string | undefined {\n\t\treturn this.getAttribute('x-label') ?? undefined;\n\t}\n\n\tset xLabel(label: string | undefined) {\n\t\tthis.reflect('x-label', label);\n\t}\n\n\tget xMax(): number {\n\t\treturn this.numberAttribute('x-max', 100);\n\t}\n\n\tset xMax(value: number | undefined) {\n\t\tthis.reflect('x-max', value);\n\t}\n\n\tget xMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-midpoint');\n\t}\n\n\tset xMidpoint(value: number | undefined) {\n\t\tthis.reflect('x-midpoint', value);\n\t}\n\n\tget xMin(): number {\n\t\treturn this.numberAttribute('x-min', 0);\n\t}\n\n\tset xMin(value: number | undefined) {\n\t\tthis.reflect('x-min', value);\n\t}\n\n\tget xOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-origin');\n\t}\n\n\tset xOrigin(value: number | undefined) {\n\t\tthis.reflect('x-origin', value);\n\t}\n\n\tget xStep(): number {\n\t\treturn this.numberAttribute('x-step', 1);\n\t}\n\n\tset xStep(value: number | undefined) {\n\t\tthis.reflect('x-step', value);\n\t}\n\n\tget xTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('x');\n\t}\n\n\tset xTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('x-taper', curve);\n\t}\n\n\tget y(): number {\n\t\treturn this.#models.y.value;\n\t}\n\n\tset y(next: null | number | undefined) {\n\t\tthis.#write('y', next);\n\t}\n\n\tget yDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-default');\n\t}\n\n\tset yDefault(value: number | undefined) {\n\t\tthis.reflect('y-default', value);\n\t}\n\n\tget yLabel(): string | undefined {\n\t\treturn this.getAttribute('y-label') ?? undefined;\n\t}\n\n\tset yLabel(label: string | undefined) {\n\t\tthis.reflect('y-label', label);\n\t}\n\n\tget yMax(): number {\n\t\treturn this.numberAttribute('y-max', 100);\n\t}\n\n\tset yMax(value: number | undefined) {\n\t\tthis.reflect('y-max', value);\n\t}\n\n\tget yMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-midpoint');\n\t}\n\n\tset yMidpoint(value: number | undefined) {\n\t\tthis.reflect('y-midpoint', value);\n\t}\n\n\tget yMin(): number {\n\t\treturn this.numberAttribute('y-min', 0);\n\t}\n\n\tset yMin(value: number | undefined) {\n\t\tthis.reflect('y-min', value);\n\t}\n\n\tget yOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-origin');\n\t}\n\n\tset yOrigin(value: number | undefined) {\n\t\tthis.reflect('y-origin', value);\n\t}\n\n\tget yStep(): number {\n\t\treturn this.numberAttribute('y-step', 1);\n\t}\n\n\tset yStep(value: number | undefined) {\n\t\tthis.reflect('y-step', value);\n\t}\n\n\tget yTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('y');\n\t}\n\n\tset yTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('y-taper', curve);\n\t}\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\t#currentAxis: FieldAxis = 'x';\n\n\treadonly #doublePress = createDoublePress();\n\n\treadonly #xy = renderXy();\n\n\treadonly #field = requireChild(this.#xy, '.sonic-xy-field', HTMLDivElement);\n\n\t#formatValue: ((value: number, axis: FieldAxis) => string) | undefined;\n\n\t#gesture: FieldGesture<FieldHold> | undefined;\n\n\t#isRevealed = false;\n\n\treadonly #models: Record<FieldAxis, ValueModel> = {\n\t\tx: createValueModel(() => this.#spec('x')),\n\t\ty: createValueModel(() => this.#spec('y')),\n\t};\n\n\treadonly #parts: Record<FieldAxis, HTMLDivElement> = {\n\t\tx: requireChild(this.#xy, '[data-sonic-axis=\"x\"]', HTMLDivElement),\n\t\ty: requireChild(this.#xy, '[data-sonic-axis=\"y\"]', HTMLDivElement),\n\t};\n\n\treadonly #puck = requireChild(this.#xy, '.sonic-xy-puck', HTMLDivElement);\n\n\treadonly #readout = new Readout(requireChild(this.#xy, '.sonic-xy-readout', HTMLDivElement));\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'x' || name === 'y') && this.#gesture?.current()) return;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gesture?.end();\n\n\t\tthis.#respec(name);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'doublePress',\n\t\t\t'readout',\n\t\t\t'xDefault',\n\t\t\t'xLabel',\n\t\t\t'xMax',\n\t\t\t'xMidpoint',\n\t\t\t'xMin',\n\t\t\t'xOrigin',\n\t\t\t'xStep',\n\t\t\t'xTaper',\n\t\t\t'yDefault',\n\t\t\t'yLabel',\n\t\t\t'yMax',\n\t\t\t'yMidpoint',\n\t\t\t'yMin',\n\t\t\t'yOrigin',\n\t\t\t'yStep',\n\t\t\t'yTaper',\n\t\t\t'formatValue',\n\t\t\t'x',\n\t\t\t'y',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\tthis.#respec(...axes);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.#xy;\n\n\t\tthis.keepControl(xy, signal);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(xy, 'xy.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.#bindPointer(xy, signal);\n\t\tthis.#bindKeys(xy, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#parts[this.#currentAxis];\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst { style } = this.#xy;\n\n\t\tfor (const axis of axes) {\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst mapping = model.mapping();\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));\n\t\t}\n\t\tthis.#renderAria();\n\t\tthis.#renderForm();\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [x = NaN, y = NaN] = state.split(',').map(Number);\n\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\t#axisText(axis: FieldAxis): string {\n\t\tconst { value } = this.#models[axis];\n\t\tconst label = this.getAttribute(`${axis}-label`) ?? axis.toUpperCase();\n\n\t\treturn `${label} ${this.#formatValue?.(value, axis) ?? String(value)}`;\n\t}\n\n\t#bindKeys(xy: HTMLElement, signal: AbortSignal): void {\n\t\txy.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented) return;\n\n\t\t\t\tconst move = keyMoves.get(event.key);\n\t\t\t\tif (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#renderReadout();\n\t\t\t\tif (move) this.#keyTo(...move);\n\t\t\t\telse this.#reset();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\t// A key on the other axis moves focus between the two parts\n\t\t\t\tif (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bindPointer(xy: HTMLElement, signal: AbortSignal): void {\n\t\t// A press on the field would take focus off the axis part and select the page's text\n\t\txy.addEventListener(\n\t\t\t'mousedown',\n\t\t\t(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t() => {\n\t\t\t\tthis.#doublePress.forget();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.isDisabled() || isMenuPress(event)) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tif (!isResetPress(event)) {\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\n\t\t\t\t\t\treturn this.#grab(event);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tinput: (_hold, next) => {\n\t\t\t\t\tif (!this.#input(next)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\tlift: (_hold, event) => {\n\t\t\t\t\tif (!this.#doublePress.press(event) || this.doublePress !== 'reset') return;\n\n\t\t\t\t\tthis.#reset();\n\t\t\t\t},\n\t\t\t\trelease: (_hold, moved) => {\n\t\t\t\t\tif (moved.length > 0) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#commit(next: FieldInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): FieldHold {\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst { x, y } = this.#models;\n\t\tconst from = { x: x.value, y: y.value };\n\t\tconst travel = {\n\t\t\tx: Math.max(1, field.width - puck.width),\n\t\t\ty: Math.max(1, field.height - puck.height),\n\t\t};\n\t\tconst position = pointerPosition(event);\n\t\tconst pressed = {\n\t\t\tx: (position.x - field.left - puck.width / 2) / travel.x,\n\t\t\ty: (position.y + field.bottom - puck.height / 2) / travel.y,\n\t\t};\n\t\tconst isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);\n\n\t\tif (!isOnPuck) {\n\t\t\tthis.#input({ x: x.mapping().valueAt(pressed.x), y: y.mapping().valueAt(pressed.y) });\n\t\t}\n\n\t\tconst hold = (axis: FieldAxis) => ({\n\t\t\tfrom: from[axis],\n\t\t\tmodel: this.#models[axis],\n\t\t\t...(isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) }),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn { axes: { x: hold('x'), y: hold('y') } };\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: FieldInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;\n\t\t}\n\t\tif (!hasMoved) return false;\n\n\t\tthis.render();\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\t#keyTo(axis: FieldAxis, key: string): void {\n\t\tconst model = this.#models[axis];\n\t\tconst hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);\n\n\t\tthis.#currentAxis = axis;\n\t\tthis.render();\n\t\tthis.#parts[axis].focus();\n\t\tif (!hasMoved) return;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#renderAria(): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst part = this.#parts[axis];\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst [low, high] = model.mapping().bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\twriteAttribute(part, 'aria-orientation', orientations[axis]);\n\t\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\t\twriteAttribute(part, 'aria-valuenow', String(model.value));\n\t\t\twriteAttribute(part, 'aria-valuetext', this.#valueText(axis));\n\t\t\tthis.forwardNaming(part, true);\n\t\t\twriteAttribute(part, 'aria-hidden', isCurrent ? undefined : 'true');\n\t\t\twriteAttribute(part, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t\twriteAttribute(part, 'tabindex', isDisabled ? undefined : stop);\n\t\t}\n\t}\n\n\t#renderForm(): void {\n\t\tconst { x, y } = this;\n\n\t\tthis.writeFormFields(\n\t\t\t[\n\t\t\t\t['x', x],\n\t\t\t\t['y', y],\n\t\t\t],\n\t\t\t`${String(x)},${String(y)}`,\n\t\t);\n\t}\n\n\t#renderReadout(): void {\n\t\tconst { isRevealed } = this.#claim;\n\n\t\tthis.#readout.show({\n\t\t\tanchor: this.#puck,\n\t\t\tisOpen: isRevealed && this.readout,\n\t\t\ttext: this.#valueText('x'),\n\t\t});\n\t\tif (isRevealed === this.#isRevealed) return;\n\n\t\tthis.#isRevealed = isRevealed;\n\t\tthis.toggleState('revealed', isRevealed);\n\t\tthis.dispatchEvent(new Event('sonic-reveal', { bubbles: true }));\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit({ x: this.xDefault, y: this.yDefault });\n\t}\n\n\t#respec(...forgotten: Array<string>): void {\n\t\tfor (const axis of axes) {\n\t\t\tthis.#models[axis].respec(\n\t\t\t\tthis.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)),\n\t\t\t\t{ forgetAsk: forgotten.includes(axis) },\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#spec(axis: FieldAxis): ValueSpec {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn {\n\t\t\tmax: this.numberAttribute(`${axis}-max`, 100),\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: false,\n\t\t\tisWrapping: false,\n\t\t\tmin: this.numberAttribute(`${axis}-min`, 0),\n\t\t\tstep: this.numberAttribute(`${axis}-step`, 1),\n\t\t\ttaper: this.#taper(axis),\n\t\t};\n\t}\n\n\t#taper(axis: FieldAxis): ValueSpec['taper'] {\n\t\treturn this.getAttribute(`${axis}-taper`) === 'log' ? 'log' : 'linear';\n\t}\n\n\t#valueText(axis: FieldAxis): string {\n\t\treturn `${this.#axisText(axis)}, ${this.#axisText(axis === 'x' ? 'y' : 'x')}`;\n\t}\n\n\t#write(axis: FieldAxis, next: null | number | undefined): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.#respec(axis);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#models[axis].write(toNumber(next))) this.render();\n\t}\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { SonicDial } from "./elements/dial.js";
3
3
  import { SonicEnvelope } from "./elements/envelope.js";
4
4
  import { SonicMeter } from "./elements/meter.js";
5
5
  import { SonicNumber } from "./elements/number.js";
6
+ import { SonicRegion } from "./elements/region.js";
6
7
  import { SonicSegmented } from "./elements/segmented.js";
7
8
  import { TimeRegions } from "./lib/time-regions.js";
8
9
  import { SonicSlider } from "./elements/slider.js";
@@ -15,10 +16,10 @@ import { PeaksRequest } from "./lib/peaks-request.js";
15
16
  import { WaveformBands } from "./lib/waveform-bands.js";
16
17
  import { WaveformPeaks } from "./lib/waveform-buckets.js";
17
18
  import { LabelRender, SonicWaveform } from "./elements/waveform.js";
18
- import { SonicWavestrip } from "./elements/wavestrip.js";
19
+ import { MarkerRender, SonicWavestrip } from "./elements/wavestrip.js";
19
20
  import { SonicXy } from "./elements/xy.js";
20
21
  import { formatClock, parseClock } from "./lib/clock.js";
21
22
  import { crossfadeGains } from "./lib/crossfade.js";
22
23
  import { envelopeCurve } from "./lib/envelope-curve.js";
23
24
  import { formatPercent, parsePercent } from "./lib/percent.js";
24
- export { type LabelRender, type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type TimeRegions, type WaveMarker, type WaveformBands, type WaveformPeaks, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
25
+ export { type LabelRender, type MarkerRender, type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type TimeRegions, type WaveMarker, type WaveformBands, type WaveformPeaks, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
package/dist/dev/index.js CHANGED
@@ -4,6 +4,7 @@ import { envelopeCurve } from "./lib/envelope-curve.js";
4
4
  import { SonicEnvelope } from "./elements/envelope.js";
5
5
  import { SonicMeter } from "./elements/meter.js";
6
6
  import { SonicNumber } from "./elements/number.js";
7
+ import { SonicRegion } from "./elements/region.js";
7
8
  import { SonicSegmented } from "./elements/segmented.js";
8
9
  import { SonicSlider } from "./elements/slider.js";
9
10
  import { SonicSpectrum } from "./elements/spectrum.js";
@@ -17,4 +18,4 @@ import { SonicXy } from "./elements/xy.js";
17
18
  import { crossfadeGains } from "./lib/crossfade.js";
18
19
  import { formatPercent, parsePercent } from "./lib/percent.js";
19
20
 
20
- export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
21
+ export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
@@ -1,6 +1,7 @@
1
1
  //#region src/lib/clock.ts
2
2
  const sign = /^[-−]/;
3
3
  function formatClock(seconds) {
4
+ if (!Number.isFinite(seconds)) return "";
4
5
  const total = Math.floor(Math.abs(seconds));
5
6
  const hours = Math.floor(total / 3600);
6
7
  const minutes = Math.floor(total / 60) % 60;
@@ -1 +1 @@
1
- {"version":3,"file":"clock.js","names":[],"sources":["../../../src/lib/clock.ts"],"sourcesContent":["const sign = /^[-−]/;\n\nexport function formatClock(seconds: number): string {\n\tconst total = Math.floor(Math.abs(seconds));\n\tconst hours = Math.floor(total / 3600);\n\tconst minutes = Math.floor(total / 60) % 60;\n\tconst rest = String(total % 60).padStart(2, '0');\n\tconst prefix = seconds < 0 ? '−' : '';\n\n\tif (hours === 0) return `${prefix}${String(minutes)}:${rest}`;\n\n\treturn `${prefix}${String(hours)}:${String(minutes).padStart(2, '0')}:${rest}`;\n}\n\n// An emptied entry or an empty part reads as NaN, which the control ignores; `Number('')` is 0\nexport function parseClock(text: string): number {\n\tconst clock = text.trim();\n\tlet seconds = 0;\n\n\tfor (const part of clock.replace(sign, '').split(':')) {\n\t\tif (part.trim() === '') return NaN;\n\n\t\tseconds = seconds * 60 + Number(part);\n\t}\n\n\treturn sign.test(clock) ? -seconds : seconds;\n}\n"],"mappings":";AAAA,MAAM,OAAO;AAEb,SAAgB,YAAY,SAAyB;CACpD,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC;CAC1C,MAAM,QAAQ,KAAK,MAAM,QAAQ,IAAI;CACrC,MAAM,UAAU,KAAK,MAAM,QAAQ,EAAE,IAAI;CACzC,MAAM,OAAO,OAAO,QAAQ,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CAC/C,MAAM,SAAS,UAAU,IAAI,MAAM;CAEnC,IAAI,UAAU,GAAG,OAAO,GAAG,SAAS,OAAO,OAAO,EAAE,GAAG;CAEvD,OAAO,GAAG,SAAS,OAAO,KAAK,EAAE,GAAG,OAAO,OAAO,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG;AACzE;AAGA,SAAgB,WAAW,MAAsB;CAChD,MAAM,QAAQ,KAAK,KAAK;CACxB,IAAI,UAAU;CAEd,KAAK,MAAM,QAAQ,MAAM,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,GAAG;EACtD,IAAI,KAAK,KAAK,MAAM,IAAI,OAAO;EAE/B,UAAU,UAAU,KAAK,OAAO,IAAI;CACrC;CAEA,OAAO,KAAK,KAAK,KAAK,IAAI,CAAC,UAAU;AACtC"}
1
+ {"version":3,"file":"clock.js","names":[],"sources":["../../../src/lib/clock.ts"],"sourcesContent":["const sign = /^[-−]/;\n\n// A time not yet known writes as nothing, the text `parseClock` reads back as NaN\nexport function formatClock(seconds: number): string {\n\tif (!Number.isFinite(seconds)) return '';\n\n\tconst total = Math.floor(Math.abs(seconds));\n\tconst hours = Math.floor(total / 3600);\n\tconst minutes = Math.floor(total / 60) % 60;\n\tconst rest = String(total % 60).padStart(2, '0');\n\tconst prefix = seconds < 0 ? '−' : '';\n\n\tif (hours === 0) return `${prefix}${String(minutes)}:${rest}`;\n\n\treturn `${prefix}${String(hours)}:${String(minutes).padStart(2, '0')}:${rest}`;\n}\n\n// An emptied entry or an empty part reads as NaN, which the control ignores; `Number('')` is 0\nexport function parseClock(text: string): number {\n\tconst clock = text.trim();\n\tlet seconds = 0;\n\n\tfor (const part of clock.replace(sign, '').split(':')) {\n\t\tif (part.trim() === '') return NaN;\n\n\t\tseconds = seconds * 60 + Number(part);\n\t}\n\n\treturn sign.test(clock) ? -seconds : seconds;\n}\n"],"mappings":";AAAA,MAAM,OAAO;AAGb,SAAgB,YAAY,SAAyB;CACpD,IAAI,CAAC,OAAO,SAAS,OAAO,GAAG,OAAO;CAEtC,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC;CAC1C,MAAM,QAAQ,KAAK,MAAM,QAAQ,IAAI;CACrC,MAAM,UAAU,KAAK,MAAM,QAAQ,EAAE,IAAI;CACzC,MAAM,OAAO,OAAO,QAAQ,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CAC/C,MAAM,SAAS,UAAU,IAAI,MAAM;CAEnC,IAAI,UAAU,GAAG,OAAO,GAAG,SAAS,OAAO,OAAO,EAAE,GAAG;CAEvD,OAAO,GAAG,SAAS,OAAO,KAAK,EAAE,GAAG,OAAO,OAAO,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG;AACzE;AAGA,SAAgB,WAAW,MAAsB;CAChD,MAAM,QAAQ,KAAK,KAAK;CACxB,IAAI,UAAU;CAEd,KAAK,MAAM,QAAQ,MAAM,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,GAAG;EACtD,IAAI,KAAK,KAAK,MAAM,IAAI,OAAO;EAE/B,UAAU,UAAU,KAAK,OAAO,IAAI;CACrC;CAEA,OAAO,KAAK,KAAK,KAAK,IAAI,CAAC,UAAU;AACtC"}
@@ -96,5 +96,5 @@ function stepDrag(mapping, state, move) {
96
96
  }
97
97
 
98
98
  //#endregion
99
- export { dragThresholdPx, startDrag, stepDrag };
99
+ export { dragThresholdPx, fineFactor, startDrag, stepDrag };
100
100
  //# sourceMappingURL=drag-step.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"drag-step.js","names":[],"sources":["../../../src/lib/drag-step.ts"],"sourcesContent":["import type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { clampProportion, wrapProportion } from '#lib/math.ts';\n\ninterface DetentHold {\n\tproportion: number;\n\tslack: number | undefined;\n\tvalue: number;\n\tzone: number;\n}\n\nexport interface DragState {\n\tdetent?: DetentHold;\n\tisEngaged: boolean;\n\tlastPosition: number;\n\trawProportion: number;\n\tstartPosition: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n}\n\nexport interface DragMove {\n\tisFine: boolean;\n\tisOutside: boolean;\n\tposition: number;\n}\n\ninterface DragStart {\n\tdetent?: { value: number; zone: number };\n\tfrom: number;\n\tposition: number;\n\tproportion: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n}\n\ninterface DragStep {\n\tstate: DragState;\n\tvalue: number | undefined;\n}\n\nconst fineFactor = 0.1;\nconst mouseDragThresholdPx = 3;\nconst touchDragThresholdPx = 7;\n\nexport function dragThresholdPx(pointerType: string): number {\n\treturn pointerType === 'touch' ? touchDragThresholdPx : mouseDragThresholdPx;\n}\n\nfunction isAcross(from: number, to: number, proportion: number): boolean {\n\treturn from !== proportion && (from - proportion) * (to - proportion) <= 0;\n}\n\nfunction passDetent(\n\thold: DetentHold,\n\t[from, to]: [number, number],\n\tpace: number,\n): { proportion: number | undefined; slack: number | undefined } {\n\tif (hold.slack === undefined && !isAcross(from, to, hold.proportion)) {\n\t\treturn { proportion: to, slack: undefined };\n\t}\n\n\tconst slack = (hold.slack ?? 0) + (to - hold.proportion) / pace;\n\n\tif (Math.abs(slack) <= hold.zone) return { proportion: undefined, slack };\n\n\treturn {\n\t\tproportion: hold.proportion + (slack - Math.sign(slack) * hold.zone) * pace,\n\t\tslack: undefined,\n\t};\n}\n\nfunction nearestTurn(hold: DetentHold, from: number): DetentHold {\n\treturn { ...hold, proportion: hold.proportion + Math.round(from - hold.proportion) };\n}\n\nfunction land(mapping: ValueMapping, state: DragState, proportion: number): DragStep {\n\tconst rawProportion = mapping.isWrapping\n\t\t? wrapProportion(proportion)\n\t\t: clampProportion(proportion);\n\n\treturn {\n\t\tstate: { ...state, rawProportion },\n\t\tvalue: mapping.snap(mapping.valueAt(rawProportion)),\n\t};\n}\n\nexport function startDrag(mapping: ValueMapping, start: DragStart): DragState {\n\tconst { detent: wanted, from, position, proportion, ...fixed } = start;\n\tconst state = {\n\t\t...fixed,\n\t\tisEngaged: false,\n\t\tlastPosition: position,\n\t\trawProportion: proportion,\n\t\tstartPosition: position,\n\t};\n\tif (!wanted) return state;\n\n\tconst value = mapping.snap(wanted.value);\n\tconst detent = {\n\t\tproportion: mapping.proportionOf(value),\n\t\tslack: value === from ? 0 : undefined,\n\t\tvalue,\n\t\tzone: wanted.zone,\n\t};\n\n\treturn { ...state, detent };\n}\n\nexport function stepDrag(mapping: ValueMapping, state: DragState, move: DragMove): DragStep {\n\tconst isEngaged =\n\t\tstate.isEngaged ||\n\t\tmove.isOutside ||\n\t\tMath.abs(move.position - state.startPosition) >= state.thresholdPx;\n\n\tif (!isEngaged) return { state, value: undefined };\n\n\tconst pace = move.isFine ? fineFactor : 1;\n\tconst to = state.rawProportion + ((move.position - state.lastPosition) / state.travelPx) * pace;\n\tconst moved = { ...state, isEngaged: true, lastPosition: move.position };\n\tconst { detent } = state;\n\tif (!detent) return land(mapping, moved, to);\n\n\tconst near = mapping.isWrapping ? nearestTurn(detent, state.rawProportion) : detent;\n\tconst passed = passDetent(near, [state.rawProportion, to], pace);\n\tconst held = { ...moved, detent: { ...detent, slack: passed.slack } };\n\tif (passed.proportion === undefined) {\n\t\treturn { state: { ...held, rawProportion: detent.proportion }, value: detent.value };\n\t}\n\n\treturn land(mapping, held, passed.proportion);\n}\n"],"mappings":";;;AAyCA,MAAM,aAAa;AACnB,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAE7B,SAAgB,gBAAgB,aAA6B;CAC5D,OAAO,gBAAgB,UAAU,uBAAuB;AACzD;AAEA,SAAS,SAAS,MAAc,IAAY,YAA6B;CACxE,OAAO,SAAS,eAAe,OAAO,eAAe,KAAK,eAAe;AAC1E;AAEA,SAAS,WACR,MACA,CAAC,MAAM,KACP,MACgE;CAChE,IAAI,KAAK,UAAU,UAAa,CAAC,SAAS,MAAM,IAAI,KAAK,UAAU,GAClE,OAAO;EAAE,YAAY;EAAI,OAAO;CAAU;CAG3C,MAAM,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,cAAc;CAE3D,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM,OAAO;EAAE,YAAY;EAAW;CAAM;CAExE,OAAO;EACN,YAAY,KAAK,cAAc,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,QAAQ;EACvE,OAAO;CACR;AACD;AAEA,SAAS,YAAY,MAAkB,MAA0B;CAChE,OAAO;EAAE,GAAG;EAAM,YAAY,KAAK,aAAa,KAAK,MAAM,OAAO,KAAK,UAAU;CAAE;AACpF;AAEA,SAAS,KAAK,SAAuB,OAAkB,YAA8B;CACpF,MAAM,gBAAgB,QAAQ,aAC3B,eAAe,UAAU,IACzB,gBAAgB,UAAU;CAE7B,OAAO;EACN,OAAO;GAAE,GAAG;GAAO;EAAc;EACjC,OAAO,QAAQ,KAAK,QAAQ,QAAQ,aAAa,CAAC;CACnD;AACD;AAEA,SAAgB,UAAU,SAAuB,OAA6B;CAC7E,MAAM,EAAE,QAAQ,QAAQ,MAAM,UAAU,YAAY,GAAG,UAAU;CACjE,MAAM,QAAQ;EACb,GAAG;EACH,WAAW;EACX,cAAc;EACd,eAAe;EACf,eAAe;CAChB;CACA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;CACvC,MAAM,SAAS;EACd,YAAY,QAAQ,aAAa,KAAK;EACtC,OAAO,UAAU,OAAO,IAAI;EAC5B;EACA,MAAM,OAAO;CACd;CAEA,OAAO;EAAE,GAAG;EAAO;CAAO;AAC3B;AAEA,SAAgB,SAAS,SAAuB,OAAkB,MAA0B;CAM3F,IAAI,EAJH,MAAM,aACN,KAAK,aACL,KAAK,IAAI,KAAK,WAAW,MAAM,aAAa,KAAK,MAAM,cAExC,OAAO;EAAE;EAAO,OAAO;CAAU;CAEjD,MAAM,OAAO,KAAK,SAAS,aAAa;CACxC,MAAM,KAAK,MAAM,iBAAkB,KAAK,WAAW,MAAM,gBAAgB,MAAM,WAAY;CAC3F,MAAM,QAAQ;EAAE,GAAG;EAAO,WAAW;EAAM,cAAc,KAAK;CAAS;CACvE,MAAM,EAAE,WAAW;CACnB,IAAI,CAAC,QAAQ,OAAO,KAAK,SAAS,OAAO,EAAE;CAG3C,MAAM,SAAS,WADF,QAAQ,aAAa,YAAY,QAAQ,MAAM,aAAa,IAAI,QAC7C,CAAC,MAAM,eAAe,EAAE,GAAG,IAAI;CAC/D,MAAM,OAAO;EAAE,GAAG;EAAO,QAAQ;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAM;CAAE;CACpE,IAAI,OAAO,eAAe,QACzB,OAAO;EAAE,OAAO;GAAE,GAAG;GAAM,eAAe,OAAO;EAAW;EAAG,OAAO,OAAO;CAAM;CAGpF,OAAO,KAAK,SAAS,MAAM,OAAO,UAAU;AAC7C"}
1
+ {"version":3,"file":"drag-step.js","names":[],"sources":["../../../src/lib/drag-step.ts"],"sourcesContent":["import type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { clampProportion, wrapProportion } from '#lib/math.ts';\n\ninterface DetentHold {\n\tproportion: number;\n\tslack: number | undefined;\n\tvalue: number;\n\tzone: number;\n}\n\nexport interface DragState {\n\tdetent?: DetentHold;\n\tisEngaged: boolean;\n\tlastPosition: number;\n\trawProportion: number;\n\tstartPosition: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n}\n\nexport interface DragMove {\n\tisFine: boolean;\n\tisOutside: boolean;\n\tposition: number;\n}\n\ninterface DragStart {\n\tdetent?: { value: number; zone: number };\n\tfrom: number;\n\tposition: number;\n\tproportion: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n}\n\ninterface DragStep {\n\tstate: DragState;\n\tvalue: number | undefined;\n}\n\nexport const fineFactor = 0.1;\nconst mouseDragThresholdPx = 3;\nconst touchDragThresholdPx = 7;\n\nexport function dragThresholdPx(pointerType: string): number {\n\treturn pointerType === 'touch' ? touchDragThresholdPx : mouseDragThresholdPx;\n}\n\nfunction isAcross(from: number, to: number, proportion: number): boolean {\n\treturn from !== proportion && (from - proportion) * (to - proportion) <= 0;\n}\n\nfunction passDetent(\n\thold: DetentHold,\n\t[from, to]: [number, number],\n\tpace: number,\n): { proportion: number | undefined; slack: number | undefined } {\n\tif (hold.slack === undefined && !isAcross(from, to, hold.proportion)) {\n\t\treturn { proportion: to, slack: undefined };\n\t}\n\n\tconst slack = (hold.slack ?? 0) + (to - hold.proportion) / pace;\n\n\tif (Math.abs(slack) <= hold.zone) return { proportion: undefined, slack };\n\n\treturn {\n\t\tproportion: hold.proportion + (slack - Math.sign(slack) * hold.zone) * pace,\n\t\tslack: undefined,\n\t};\n}\n\nfunction nearestTurn(hold: DetentHold, from: number): DetentHold {\n\treturn { ...hold, proportion: hold.proportion + Math.round(from - hold.proportion) };\n}\n\nfunction land(mapping: ValueMapping, state: DragState, proportion: number): DragStep {\n\tconst rawProportion = mapping.isWrapping\n\t\t? wrapProportion(proportion)\n\t\t: clampProportion(proportion);\n\n\treturn {\n\t\tstate: { ...state, rawProportion },\n\t\tvalue: mapping.snap(mapping.valueAt(rawProportion)),\n\t};\n}\n\nexport function startDrag(mapping: ValueMapping, start: DragStart): DragState {\n\tconst { detent: wanted, from, position, proportion, ...fixed } = start;\n\tconst state = {\n\t\t...fixed,\n\t\tisEngaged: false,\n\t\tlastPosition: position,\n\t\trawProportion: proportion,\n\t\tstartPosition: position,\n\t};\n\tif (!wanted) return state;\n\n\tconst value = mapping.snap(wanted.value);\n\tconst detent = {\n\t\tproportion: mapping.proportionOf(value),\n\t\tslack: value === from ? 0 : undefined,\n\t\tvalue,\n\t\tzone: wanted.zone,\n\t};\n\n\treturn { ...state, detent };\n}\n\nexport function stepDrag(mapping: ValueMapping, state: DragState, move: DragMove): DragStep {\n\tconst isEngaged =\n\t\tstate.isEngaged ||\n\t\tmove.isOutside ||\n\t\tMath.abs(move.position - state.startPosition) >= state.thresholdPx;\n\n\tif (!isEngaged) return { state, value: undefined };\n\n\tconst pace = move.isFine ? fineFactor : 1;\n\tconst to = state.rawProportion + ((move.position - state.lastPosition) / state.travelPx) * pace;\n\tconst moved = { ...state, isEngaged: true, lastPosition: move.position };\n\tconst { detent } = state;\n\tif (!detent) return land(mapping, moved, to);\n\n\tconst near = mapping.isWrapping ? nearestTurn(detent, state.rawProportion) : detent;\n\tconst passed = passDetent(near, [state.rawProportion, to], pace);\n\tconst held = { ...moved, detent: { ...detent, slack: passed.slack } };\n\tif (passed.proportion === undefined) {\n\t\treturn { state: { ...held, rawProportion: detent.proportion }, value: detent.value };\n\t}\n\n\treturn land(mapping, held, passed.proportion);\n}\n"],"mappings":";;;AAyCA,MAAa,aAAa;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAE7B,SAAgB,gBAAgB,aAA6B;CAC5D,OAAO,gBAAgB,UAAU,uBAAuB;AACzD;AAEA,SAAS,SAAS,MAAc,IAAY,YAA6B;CACxE,OAAO,SAAS,eAAe,OAAO,eAAe,KAAK,eAAe;AAC1E;AAEA,SAAS,WACR,MACA,CAAC,MAAM,KACP,MACgE;CAChE,IAAI,KAAK,UAAU,UAAa,CAAC,SAAS,MAAM,IAAI,KAAK,UAAU,GAClE,OAAO;EAAE,YAAY;EAAI,OAAO;CAAU;CAG3C,MAAM,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,cAAc;CAE3D,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM,OAAO;EAAE,YAAY;EAAW;CAAM;CAExE,OAAO;EACN,YAAY,KAAK,cAAc,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,QAAQ;EACvE,OAAO;CACR;AACD;AAEA,SAAS,YAAY,MAAkB,MAA0B;CAChE,OAAO;EAAE,GAAG;EAAM,YAAY,KAAK,aAAa,KAAK,MAAM,OAAO,KAAK,UAAU;CAAE;AACpF;AAEA,SAAS,KAAK,SAAuB,OAAkB,YAA8B;CACpF,MAAM,gBAAgB,QAAQ,aAC3B,eAAe,UAAU,IACzB,gBAAgB,UAAU;CAE7B,OAAO;EACN,OAAO;GAAE,GAAG;GAAO;EAAc;EACjC,OAAO,QAAQ,KAAK,QAAQ,QAAQ,aAAa,CAAC;CACnD;AACD;AAEA,SAAgB,UAAU,SAAuB,OAA6B;CAC7E,MAAM,EAAE,QAAQ,QAAQ,MAAM,UAAU,YAAY,GAAG,UAAU;CACjE,MAAM,QAAQ;EACb,GAAG;EACH,WAAW;EACX,cAAc;EACd,eAAe;EACf,eAAe;CAChB;CACA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;CACvC,MAAM,SAAS;EACd,YAAY,QAAQ,aAAa,KAAK;EACtC,OAAO,UAAU,OAAO,IAAI;EAC5B;EACA,MAAM,OAAO;CACd;CAEA,OAAO;EAAE,GAAG;EAAO;CAAO;AAC3B;AAEA,SAAgB,SAAS,SAAuB,OAAkB,MAA0B;CAM3F,IAAI,EAJH,MAAM,aACN,KAAK,aACL,KAAK,IAAI,KAAK,WAAW,MAAM,aAAa,KAAK,MAAM,cAExC,OAAO;EAAE;EAAO,OAAO;CAAU;CAEjD,MAAM,OAAO,KAAK,SAAS,aAAa;CACxC,MAAM,KAAK,MAAM,iBAAkB,KAAK,WAAW,MAAM,gBAAgB,MAAM,WAAY;CAC3F,MAAM,QAAQ;EAAE,GAAG;EAAO,WAAW;EAAM,cAAc,KAAK;CAAS;CACvE,MAAM,EAAE,WAAW;CACnB,IAAI,CAAC,QAAQ,OAAO,KAAK,SAAS,OAAO,EAAE;CAG3C,MAAM,SAAS,WADF,QAAQ,aAAa,YAAY,QAAQ,MAAM,aAAa,IAAI,QAC7C,CAAC,MAAM,eAAe,EAAE,GAAG,IAAI;CAC/D,MAAM,OAAO;EAAE,GAAG;EAAO,QAAQ;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAM;CAAE;CACpE,IAAI,OAAO,eAAe,QACzB,OAAO;EAAE,OAAO;GAAE,GAAG;GAAM,eAAe,OAAO;EAAW;EAAG,OAAO,OAAO;CAAM;CAGpF,OAAO,KAAK,SAAS,MAAM,OAAO,UAAU;AAC7C"}
@@ -0,0 +1,7 @@
1
+ //#region src/lib/marker-band.d.ts
2
+ export interface Point {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ //#endregion
7
+ //# sourceMappingURL=marker-band.d.ts.map