@xsynaptic/sonic-ui 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -0
  3. package/dist/default/define/ticker.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
  6. package/dist/default/elements/button.js +12 -9
  7. package/dist/default/elements/dial.js +9 -13
  8. package/dist/default/elements/envelope.js +26 -23
  9. package/dist/default/elements/field-gesture.js +49 -8
  10. package/dist/default/elements/meter.js +6 -8
  11. package/dist/default/elements/number.js +5 -14
  12. package/dist/default/elements/position-group.js +4 -4
  13. package/dist/default/elements/radio-group.js +6 -9
  14. package/dist/default/elements/segmented.js +7 -4
  15. package/dist/default/elements/slider.js +12 -13
  16. package/dist/default/elements/sonic-element.js +7 -5
  17. package/dist/default/elements/spectrum.js +3 -3
  18. package/dist/default/elements/switch.js +11 -6
  19. package/dist/default/elements/ticker.js +313 -0
  20. package/dist/default/elements/toggle.js +14 -8
  21. package/dist/default/elements/value-element.js +11 -3
  22. package/dist/default/elements/wave-element.js +11 -12
  23. package/dist/default/elements/waveform.js +1 -4
  24. package/dist/default/elements/wavestrip.js +3 -6
  25. package/dist/default/elements/xy.js +34 -47
  26. package/dist/default/index.js +2 -1
  27. package/dist/default/lib/cancel-zone.js +12 -0
  28. package/dist/default/lib/canvas-surface.js +13 -1
  29. package/dist/default/lib/distribute.js +2 -3
  30. package/dist/default/lib/mirror-children.js +1 -1
  31. package/dist/default/lib/pointer-drag.js +8 -2
  32. package/dist/default/lib/render.js +2 -2
  33. package/dist/default/lib/ticker-leg.js +54 -0
  34. package/dist/dev/define/ticker.d.ts +2 -0
  35. package/dist/dev/define/ticker.js +8 -0
  36. package/dist/dev/define/ticker.js.map +1 -0
  37. package/dist/dev/define.d.ts +3 -1
  38. package/dist/dev/define.js +1 -0
  39. package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
  40. package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
  41. package/dist/dev/elements/analyser-element.js.map +1 -0
  42. package/dist/dev/elements/button.d.ts +4 -1
  43. package/dist/dev/elements/button.js +12 -14
  44. package/dist/dev/elements/button.js.map +1 -1
  45. package/dist/dev/elements/dial.d.ts +4 -2
  46. package/dist/dev/elements/dial.js +9 -17
  47. package/dist/dev/elements/dial.js.map +1 -1
  48. package/dist/dev/elements/envelope.d.ts +5 -1
  49. package/dist/dev/elements/envelope.js +26 -27
  50. package/dist/dev/elements/envelope.js.map +1 -1
  51. package/dist/dev/elements/field-gesture.js +49 -8
  52. package/dist/dev/elements/field-gesture.js.map +1 -1
  53. package/dist/dev/elements/meter.d.ts +2 -1
  54. package/dist/dev/elements/meter.js +6 -9
  55. package/dist/dev/elements/meter.js.map +1 -1
  56. package/dist/dev/elements/number.d.ts +2 -2
  57. package/dist/dev/elements/number.js +5 -15
  58. package/dist/dev/elements/number.js.map +1 -1
  59. package/dist/dev/elements/position-group.js +4 -4
  60. package/dist/dev/elements/position-group.js.map +1 -1
  61. package/dist/dev/elements/radio-group.d.ts +1 -2
  62. package/dist/dev/elements/radio-group.js +6 -9
  63. package/dist/dev/elements/radio-group.js.map +1 -1
  64. package/dist/dev/elements/segmented.d.ts +4 -2
  65. package/dist/dev/elements/segmented.js +7 -9
  66. package/dist/dev/elements/segmented.js.map +1 -1
  67. package/dist/dev/elements/slider.d.ts +4 -2
  68. package/dist/dev/elements/slider.js +12 -17
  69. package/dist/dev/elements/slider.js.map +1 -1
  70. package/dist/dev/elements/sonic-element.d.ts +6 -7
  71. package/dist/dev/elements/sonic-element.js +14 -83
  72. package/dist/dev/elements/sonic-element.js.map +1 -1
  73. package/dist/dev/elements/spectrum.d.ts +2 -2
  74. package/dist/dev/elements/spectrum.js +3 -3
  75. package/dist/dev/elements/spectrum.js.map +1 -1
  76. package/dist/dev/elements/switch.d.ts +4 -1
  77. package/dist/dev/elements/switch.js +11 -10
  78. package/dist/dev/elements/switch.js.map +1 -1
  79. package/dist/dev/elements/ticker.d.ts +25 -0
  80. package/dist/dev/elements/ticker.js +314 -0
  81. package/dist/dev/elements/ticker.js.map +1 -0
  82. package/dist/dev/elements/toggle.d.ts +4 -1
  83. package/dist/dev/elements/toggle.js +14 -13
  84. package/dist/dev/elements/toggle.js.map +1 -1
  85. package/dist/dev/elements/value-element.d.ts +4 -3
  86. package/dist/dev/elements/value-element.js +11 -3
  87. package/dist/dev/elements/value-element.js.map +1 -1
  88. package/dist/dev/elements/wave-element.d.ts +2 -2
  89. package/dist/dev/elements/wave-element.js +11 -16
  90. package/dist/dev/elements/wave-element.js.map +1 -1
  91. package/dist/dev/elements/waveform.d.ts +0 -1
  92. package/dist/dev/elements/waveform.js +1 -4
  93. package/dist/dev/elements/waveform.js.map +1 -1
  94. package/dist/dev/elements/wavestrip.js +3 -6
  95. package/dist/dev/elements/wavestrip.js.map +1 -1
  96. package/dist/dev/elements/xy.d.ts +4 -1
  97. package/dist/dev/elements/xy.js +34 -51
  98. package/dist/dev/elements/xy.js.map +1 -1
  99. package/dist/dev/index.d.ts +4 -1
  100. package/dist/dev/index.js +2 -1
  101. package/dist/dev/lib/cancel-zone.js +13 -0
  102. package/dist/dev/lib/cancel-zone.js.map +1 -0
  103. package/dist/dev/lib/canvas-surface.js +13 -1
  104. package/dist/dev/lib/canvas-surface.js.map +1 -1
  105. package/dist/dev/lib/check-styles.d.ts +8 -0
  106. package/dist/dev/lib/check-styles.js +95 -0
  107. package/dist/dev/lib/check-styles.js.map +1 -0
  108. package/dist/dev/lib/distribute.js +2 -3
  109. package/dist/dev/lib/distribute.js.map +1 -1
  110. package/dist/dev/lib/mirror-children.js +9 -7
  111. package/dist/dev/lib/mirror-children.js.map +1 -1
  112. package/dist/dev/lib/pointer-drag.js +8 -2
  113. package/dist/dev/lib/pointer-drag.js.map +1 -1
  114. package/dist/dev/lib/render.js +2 -2
  115. package/dist/dev/lib/render.js.map +1 -1
  116. package/dist/dev/lib/ticker-leg.js +55 -0
  117. package/dist/dev/lib/ticker-leg.js.map +1 -0
  118. package/dist/styles/controls.css +1 -0
  119. package/dist/styles/dial.css +28 -5
  120. package/dist/styles/material/cap.css +16 -5
  121. package/dist/styles/material/core.css +1 -0
  122. package/dist/styles/slider.css +2 -1
  123. package/dist/styles/switch.css +3 -1
  124. package/dist/styles/ticker.css +115 -0
  125. package/dist/styles/toggle.css +7 -5
  126. package/package.json +2 -1
  127. package/dist/dev/elements/display-element.js.map +0 -1
@@ -0,0 +1,314 @@
1
+ import { clamp } from "../lib/math.js";
2
+ import { writeAttribute } from "../lib/write-attribute.js";
3
+ import { SonicElement } from "./sonic-element.js";
4
+ import { copyNode } from "../lib/copy-node.js";
5
+ import { placeChildren, requireChild, template } from "../lib/render.js";
6
+ import { mirrorChildren } from "../lib/mirror-children.js";
7
+ import { bindDrag, capturePointer } from "../lib/pointer-drag.js";
8
+ import { readMsProperty } from "../lib/read-ms-property.js";
9
+ import { dragThresholdPx } from "../lib/drag-step.js";
10
+ import { readPxProperty } from "../lib/read-px-property.js";
11
+ import { outsideCancelZone } from "../lib/cancel-zone.js";
12
+ import { clippedEnds, fadeWidth, legDeparture, legFades, legPosition, tickerLeg } from "../lib/ticker-leg.js";
13
+
14
+ //#region src/elements/ticker.ts
15
+ const renderTicker = template(`<span class="sonic-ticker"><span class="sonic-ticker-tape"></span></span>`, HTMLSpanElement);
16
+ const fadePx = 12;
17
+ const holdMs = 2e3;
18
+ const ratePxPerSecond = 40;
19
+ var SonicTicker = class extends SonicElement {
20
+ static observedAttributes = ["paused"];
21
+ get cancellable() {
22
+ return this.hasAttribute("cancellable");
23
+ }
24
+ set cancellable(isCancellable) {
25
+ this.reflect("cancellable", isCancellable);
26
+ }
27
+ get paused() {
28
+ return this.hasAttribute("paused");
29
+ }
30
+ set paused(isPaused) {
31
+ this.reflect("paused", isPaused);
32
+ }
33
+ get text() {
34
+ return this.#originals().map((original) => original.textContent).join("");
35
+ }
36
+ set text(text) {
37
+ for (const original of this.#originals()) original.remove();
38
+ if (text !== void 0 && text !== "") this.prepend(text);
39
+ }
40
+ control = renderTicker();
41
+ sheet = "ticker.css";
42
+ sheetToken = "--_sonic-ticker-fade";
43
+ #fade = fadePx;
44
+ #heading = 1;
45
+ #isClickSwallowed = false;
46
+ #isFocusInside = false;
47
+ #isHovered = false;
48
+ #isOffScreen = false;
49
+ #isPressed = false;
50
+ #mark;
51
+ #measured = "";
52
+ #position = 0;
53
+ #reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
54
+ #running;
55
+ #travel = {
56
+ distance: 0,
57
+ hold: holdMs,
58
+ rate: ratePxPerSecond
59
+ };
60
+ attributeChangedCallback() {
61
+ this.render();
62
+ }
63
+ connect(signal) {
64
+ const control = this.control;
65
+ const tape = requireChild(control, ".sonic-ticker-tape", HTMLSpanElement);
66
+ const measure = () => {
67
+ this.#measure(tape);
68
+ };
69
+ const observer = new ResizeObserver(measure);
70
+ const drag = this.#bindDrag(tape, signal);
71
+ this.upgradeProperties("cancellable", "paused", "text");
72
+ mirrorChildren(this, {
73
+ canCopyLinks: true,
74
+ control,
75
+ copy: copyNode,
76
+ place: (copies) => {
77
+ placeChildren(tape, copies);
78
+ drag.end();
79
+ this.#measure(tape, {
80
+ from: 0,
81
+ heading: 1
82
+ });
83
+ }
84
+ }, signal);
85
+ this.render();
86
+ observer.observe(control);
87
+ observer.observe(tape);
88
+ if ("fonts" in document) document.fonts.addEventListener("loadingdone", measure, { signal });
89
+ this.#bindFocus(tape, signal);
90
+ this.#bindPauses(signal);
91
+ this.#reducedMotion.addEventListener("change", () => {
92
+ this.#run(tape);
93
+ }, { signal });
94
+ signal.addEventListener("abort", () => {
95
+ observer.disconnect();
96
+ this.#stop();
97
+ }, { once: true });
98
+ }
99
+ render() {
100
+ if (!this.isBound()) return;
101
+ this.toggleState("paused", this.paused);
102
+ this.#syncPause();
103
+ }
104
+ #bindDrag(tape, signal) {
105
+ const control = this.control;
106
+ control.addEventListener("click", (event) => {
107
+ if (!this.#isClickSwallowed) return;
108
+ this.#isClickSwallowed = false;
109
+ event.preventDefault();
110
+ event.stopPropagation();
111
+ }, {
112
+ capture: true,
113
+ signal
114
+ });
115
+ this.getRootNode().addEventListener("dragstart", (event) => {
116
+ if (this.#isPressed) event.preventDefault();
117
+ }, { signal });
118
+ return bindDrag(control, {
119
+ grab: (event) => {
120
+ if (this.#travel.distance <= 0) return;
121
+ return {
122
+ from: 0,
123
+ isDragging: false,
124
+ outside: void 0,
125
+ thresholdPx: dragThresholdPx(event.pointerType),
126
+ x: event.clientX
127
+ };
128
+ },
129
+ isCaptureLate: true,
130
+ move: (held, event) => {
131
+ if (!held.isDragging) {
132
+ if (Math.abs(event.clientX - held.x) < held.thresholdPx) return;
133
+ const { from, heading } = this.#where();
134
+ held.isDragging = true;
135
+ held.from = from;
136
+ held.outside = this.cancellable ? outsideCancelZone(control, "--_sonic-ticker-cancel-zone") : void 0;
137
+ held.x = event.clientX;
138
+ capturePointer(control, event.pointerId);
139
+ this.toggleState("dragging", true);
140
+ this.#stop();
141
+ this.#heading = heading;
142
+ }
143
+ const isOutside = held.outside?.(event) === true;
144
+ this.toggleState("cancelling", isOutside);
145
+ this.#place(tape, isOutside ? held.from : held.from - (event.clientX - held.x));
146
+ },
147
+ release: (held) => {
148
+ if (!held.isDragging) return;
149
+ this.#isClickSwallowed = true;
150
+ setTimeout(() => {
151
+ this.#isClickSwallowed = false;
152
+ }, 0);
153
+ this.toggleState("dragging", false);
154
+ this.toggleState("cancelling", false);
155
+ this.#run(tape);
156
+ },
157
+ toggle: (isPressed) => {
158
+ this.#isPressed = isPressed;
159
+ this.#syncPause();
160
+ }
161
+ }, signal);
162
+ }
163
+ #bindFocus(tape, signal) {
164
+ const control = this.control;
165
+ control.addEventListener("focusout", (event) => {
166
+ if (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;
167
+ control.scrollLeft = 0;
168
+ this.#isFocusInside = false;
169
+ this.#run(tape);
170
+ }, { signal });
171
+ control.addEventListener("focusin", (event) => {
172
+ this.#isFocusInside = true;
173
+ this.#run(tape);
174
+ if (event.target instanceof Element) event.target.scrollIntoView({ block: "nearest" });
175
+ }, { signal });
176
+ }
177
+ #bindPauses(signal) {
178
+ const control = this.control;
179
+ for (const type of ["pointerenter", "pointerleave"]) control.addEventListener(type, (event) => {
180
+ if (event.pointerType !== "mouse") return;
181
+ this.#isHovered = type === "pointerenter";
182
+ this.#syncPause();
183
+ }, { signal });
184
+ for (const type of ["focusin", "focusout"]) this.getRootNode().addEventListener(type, () => {
185
+ this.#syncPause();
186
+ }, { signal });
187
+ const view = new IntersectionObserver((entries) => {
188
+ this.#isOffScreen = entries.at(-1)?.isIntersecting === false;
189
+ this.#syncPause();
190
+ });
191
+ view.observe(control);
192
+ signal.addEventListener("abort", () => {
193
+ view.disconnect();
194
+ }, { once: true });
195
+ }
196
+ #isPaused() {
197
+ return this.paused || this.#isHovered || this.#isPressed || this.#isOffScreen || this.control.closest(":focus-visible") !== null;
198
+ }
199
+ #measure(tape, departure) {
200
+ const control = this.control;
201
+ const styles = getComputedStyle(control);
202
+ const box = control.clientWidth - readPxProperty(styles, "padding-left", 0) - readPxProperty(styles, "padding-right", 0);
203
+ const travel = {
204
+ distance: Math.max(0, Math.round(tape.offsetWidth - box)),
205
+ hold: readMsProperty(styles, "--_sonic-ticker-hold", holdMs),
206
+ rate: readPxProperty(styles, "--_sonic-ticker-rate", ratePxPerSecond)
207
+ };
208
+ const fade = readPxProperty(styles, "--_sonic-ticker-fade", fadePx);
209
+ const measured = [...Object.values(travel), fade].join(" ");
210
+ if (!departure && measured === this.#measured) return;
211
+ const from = departure ?? this.#where();
212
+ this.#fade = fade;
213
+ this.#measured = measured;
214
+ this.#travel = travel;
215
+ this.toggleState("overflowing", travel.distance > 0);
216
+ this.#run(tape, from);
217
+ }
218
+ #originals() {
219
+ return [...this.childNodes].filter((child) => child !== this.control);
220
+ }
221
+ #place(tape, position) {
222
+ const { distance } = this.#travel;
223
+ this.#position = clamp(position, 0, distance);
224
+ tape.style.translate = this.#position > 0 ? `${String(-this.#position)}px` : "";
225
+ writeAttribute(this.control, "data-sonic-clipped", clippedEnds(this.#position, distance));
226
+ this.#writeFades(this.#position);
227
+ }
228
+ #run(tape, departure = this.#where()) {
229
+ const leg = !this.#isFocusInside && !this.#reducedMotion.matches && !this.hasState("dragging") ? tickerLeg(this.#travel, departure) : void 0;
230
+ this.#stop();
231
+ this.#heading = departure.heading;
232
+ if (leg) this.#start(tape, leg);
233
+ else this.#place(tape, this.#isFocusInside ? 0 : departure.from);
234
+ }
235
+ #start(tape, leg) {
236
+ const animation = tape.animate({ translate: [`${String(-leg.from)}px`, `${String(-leg.to)}px`] }, {
237
+ delay: leg.hold,
238
+ duration: leg.duration,
239
+ fill: "both"
240
+ });
241
+ animation.addEventListener("finish", () => {
242
+ this.#run(tape, {
243
+ from: leg.to,
244
+ heading: leg.heading
245
+ });
246
+ });
247
+ this.#running = {
248
+ animation,
249
+ fades: [],
250
+ leg
251
+ };
252
+ tape.style.translate = "";
253
+ writeAttribute(this.control, "data-sonic-clipped", clippedEnds(leg.from, this.#travel.distance));
254
+ this.#syncPause();
255
+ }
256
+ #startFades(leg, elapsed) {
257
+ const fades = legFades(leg, this.#travel.distance, this.#fade);
258
+ return ["start", "end"].flatMap((end) => {
259
+ const { delay, duration, widths } = fades[end];
260
+ if (duration <= 0 || delay + duration <= elapsed) return [];
261
+ return this.control.animate({ [`--_sonic-ticker-fade-${end}`]: widths.map((width) => `${String(width)}px`) }, {
262
+ delay: delay - elapsed,
263
+ duration,
264
+ fill: "both"
265
+ });
266
+ });
267
+ }
268
+ #stop() {
269
+ const running = this.#running;
270
+ clearTimeout(this.#mark);
271
+ if (!running) return;
272
+ for (const fade of running.fades) fade.cancel();
273
+ running.animation.cancel();
274
+ this.#running = void 0;
275
+ }
276
+ #syncPause() {
277
+ const running = this.#running;
278
+ if (!running) return;
279
+ const { animation, leg } = running;
280
+ const elapsed = Number(animation.currentTime);
281
+ clearTimeout(this.#mark);
282
+ for (const fade of running.fades) fade.cancel();
283
+ running.fades = [];
284
+ this.#writeFades(legPosition(leg, elapsed));
285
+ if (elapsed >= leg.hold) writeAttribute(this.control, "data-sonic-clipped", "start end");
286
+ if (this.#isPaused()) {
287
+ animation.pause();
288
+ return;
289
+ }
290
+ if (animation.playState === "paused") animation.play();
291
+ running.fades = this.#startFades(leg, elapsed);
292
+ if (elapsed >= leg.hold) return;
293
+ this.#mark = setTimeout(() => {
294
+ writeAttribute(this.control, "data-sonic-clipped", "start end");
295
+ }, leg.hold - elapsed);
296
+ }
297
+ #where() {
298
+ const running = this.#running;
299
+ if (!running) return {
300
+ from: this.#position,
301
+ heading: this.#heading
302
+ };
303
+ return legDeparture(running.leg, Number(running.animation.currentTime));
304
+ }
305
+ #writeFades(position) {
306
+ const { style } = this.control;
307
+ style.setProperty("--_sonic-ticker-fade-start", `${String(fadeWidth(position, this.#fade))}px`);
308
+ style.setProperty("--_sonic-ticker-fade-end", `${String(fadeWidth(this.#travel.distance - position, this.#fade))}px`);
309
+ }
310
+ };
311
+
312
+ //#endregion
313
+ export { SonicTicker };
314
+ //# sourceMappingURL=ticker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticker.js","names":[],"sources":["../../../src/elements/ticker.ts"],"sourcesContent":["import type { TickerDeparture, TickerHeading, TickerLeg, TickerTravel } from '#lib/ticker-leg.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { outsideCancelZone } from '#lib/cancel-zone.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { clamp } from '#lib/math.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag, capturePointer } from '#lib/pointer-drag.ts';\nimport { readMsProperty } from '#lib/read-ms-property.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport {\n\tclippedEnds,\n\tfadeWidth,\n\tlegDeparture,\n\tlegFades,\n\tlegPosition,\n\ttickerLeg,\n} from '#lib/ticker-leg.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-ticker': SonicTicker;\n\t}\n}\n\nconst renderTicker = template(\n\t/* HTML */ `<span class=\"sonic-ticker\"><span class=\"sonic-ticker-tape\"></span></span>`,\n\tHTMLSpanElement,\n);\n\nconst fadePx = 12;\n\nconst holdMs = 2000;\n\nconst ratePxPerSecond = 40;\n\ninterface Running {\n\tanimation: Animation;\n\tfades: Array<Animation>;\n\tleg: TickerLeg;\n}\n\ninterface TapeDrag {\n\tfrom: number;\n\tisDragging: boolean;\n\toutside: ((event: PointerEvent) => boolean) | undefined;\n\tthresholdPx: number;\n\tx: number;\n}\n\nexport class SonicTicker extends SonicElement {\n\tstatic override readonly observedAttributes = ['paused'];\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.hasAttribute('paused');\n\t}\n\n\tset paused(isPaused: boolean) {\n\t\tthis.reflect('paused', isPaused);\n\t}\n\n\tget text(): string {\n\t\treturn this.#originals()\n\t\t\t.map((original) => original.textContent)\n\t\t\t.join('');\n\t}\n\n\tset text(text: string | undefined) {\n\t\tfor (const original of this.#originals()) original.remove();\n\t\tif (text !== undefined && text !== '') this.prepend(text);\n\t}\n\n\tprotected override readonly control = renderTicker();\n\n\tprotected override readonly sheet = 'ticker.css';\n\n\tprotected override readonly sheetToken = '--_sonic-ticker-fade';\n\n\t#fade = fadePx;\n\n\t#heading: TickerHeading = 1;\n\n\t#isClickSwallowed = false;\n\n\t#isFocusInside = false;\n\n\t#isHovered = false;\n\n\t#isOffScreen = false;\n\n\t#isPressed = false;\n\n\t#mark: ReturnType<typeof setTimeout> | undefined;\n\n\t#measured = '';\n\n\t#position = 0;\n\n\treadonly #reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\n\n\t#running: Running | undefined;\n\n\t#travel: TickerTravel = { distance: 0, hold: holdMs, rate: ratePxPerSecond };\n\n\tattributeChangedCallback(): void {\n\t\tthis.render();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\t\tconst tape = requireChild(control, '.sonic-ticker-tape', HTMLSpanElement);\n\t\tconst measure = (): void => {\n\t\t\tthis.#measure(tape);\n\t\t};\n\t\tconst observer = new ResizeObserver(measure);\n\t\tconst drag = this.#bindDrag(tape, signal);\n\n\t\tthis.upgradeProperties('cancellable', 'paused', 'text');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcanCopyLinks: true,\n\t\t\t\tcontrol,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(tape, copies);\n\t\t\t\t\tdrag.end();\n\t\t\t\t\tthis.#measure(tape, { from: 0, heading: 1 });\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\n\t\tobserver.observe(control);\n\t\tobserver.observe(tape);\n\t\tif ('fonts' in document) document.fonts.addEventListener('loadingdone', measure, { signal });\n\t\tthis.#bindFocus(tape, signal);\n\t\tthis.#bindPauses(signal);\n\t\tthis.#reducedMotion.addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.#run(tape);\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\tobserver.disconnect();\n\t\t\t\tthis.#stop();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tthis.toggleState('paused', this.paused);\n\t\tthis.#syncPause();\n\t}\n\n\t#bindDrag(tape: HTMLElement, signal: AbortSignal): { end: () => void } {\n\t\tconst control = this.control;\n\n\t\tcontrol.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (!this.#isClickSwallowed) return;\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tevent.preventDefault();\n\t\t\t\tevent.stopPropagation();\n\t\t\t},\n\t\t\t{ capture: true, signal },\n\t\t);\n\t\t// In WebKit, dragging a link that wraps the ticker fires `dragstart` on the link, outside the control\n\t\tthis.getRootNode().addEventListener(\n\t\t\t'dragstart',\n\t\t\t(event) => {\n\t\t\t\tif (this.#isPressed) event.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\treturn bindDrag<TapeDrag>(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.#travel.distance <= 0) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfrom: 0,\n\t\t\t\t\t\tisDragging: false,\n\t\t\t\t\t\toutside: undefined,\n\t\t\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\t\t\tx: event.clientX,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tisCaptureLate: true,\n\t\t\t\tmove: (held, event) => {\n\t\t\t\t\tif (!held.isDragging) {\n\t\t\t\t\t\tif (Math.abs(event.clientX - held.x) < held.thresholdPx) return;\n\n\t\t\t\t\t\tconst { from, heading } = this.#where();\n\n\t\t\t\t\t\theld.isDragging = true;\n\t\t\t\t\t\theld.from = from;\n\t\t\t\t\t\theld.outside = this.cancellable\n\t\t\t\t\t\t\t? outsideCancelZone(control, '--_sonic-ticker-cancel-zone')\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\theld.x = event.clientX;\n\t\t\t\t\t\tcapturePointer(control, event.pointerId);\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\t\tthis.#stop();\n\t\t\t\t\t\tthis.#heading = heading;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isOutside = held.outside?.(event) === true;\n\n\t\t\t\t\tthis.toggleState('cancelling', isOutside);\n\t\t\t\t\tthis.#place(tape, isOutside ? held.from : held.from - (event.clientX - held.x));\n\t\t\t\t},\n\t\t\t\trelease: (held) => {\n\t\t\t\t\tif (!held.isDragging) return;\n\n\t\t\t\t\t// Safari sends no click after a drag, so the flag is cleared on a timer or it would cancel the next real click\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\t\t}, 0);\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.toggleState('cancelling', false);\n\t\t\t\t\tthis.#run(tape);\n\t\t\t\t},\n\t\t\t\ttoggle: (isPressed) => {\n\t\t\t\t\tthis.#isPressed = isPressed;\n\t\t\t\t\tthis.#syncPause();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#bindFocus(tape: HTMLElement, signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tcontrol.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\tif (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;\n\n\t\t\t\tcontrol.scrollLeft = 0;\n\t\t\t\tthis.#isFocusInside = false;\n\t\t\t\tthis.#run(tape);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'focusin',\n\t\t\t(event) => {\n\t\t\t\tthis.#isFocusInside = true;\n\t\t\t\tthis.#run(tape);\n\t\t\t\tif (event.target instanceof Element) event.target.scrollIntoView({ block: 'nearest' });\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#bindPauses(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tfor (const type of ['pointerenter', 'pointerleave'] as const) {\n\t\t\tcontrol.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tif (event.pointerType !== 'mouse') return;\n\n\t\t\t\t\tthis.#isHovered = type === 'pointerenter';\n\t\t\t\t\tthis.#syncPause();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t\tfor (const type of ['focusin', 'focusout'] as const) {\n\t\t\tthis.getRootNode().addEventListener(\n\t\t\t\ttype,\n\t\t\t\t() => {\n\t\t\t\t\tthis.#syncPause();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\n\t\tconst view = new IntersectionObserver((entries) => {\n\t\t\tthis.#isOffScreen = entries.at(-1)?.isIntersecting === false;\n\t\t\tthis.#syncPause();\n\t\t});\n\n\t\tview.observe(control);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tview.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#isPaused(): boolean {\n\t\treturn (\n\t\t\tthis.paused ||\n\t\t\tthis.#isHovered ||\n\t\t\tthis.#isPressed ||\n\t\t\tthis.#isOffScreen ||\n\t\t\tthis.control.closest(':focus-visible') !== null\n\t\t);\n\t}\n\n\t#measure(tape: HTMLElement, departure?: TickerDeparture): void {\n\t\tconst control = this.control;\n\t\tconst styles = getComputedStyle(control);\n\t\tconst box =\n\t\t\tcontrol.clientWidth -\n\t\t\treadPxProperty(styles, 'padding-left', 0) -\n\t\t\treadPxProperty(styles, 'padding-right', 0);\n\t\tconst travel = {\n\t\t\tdistance: Math.max(0, Math.round(tape.offsetWidth - box)),\n\t\t\thold: readMsProperty(styles, '--_sonic-ticker-hold', holdMs),\n\t\t\trate: readPxProperty(styles, '--_sonic-ticker-rate', ratePxPerSecond),\n\t\t};\n\t\tconst fade = readPxProperty(styles, '--_sonic-ticker-fade', fadePx);\n\t\tconst measured = [...Object.values(travel), fade].join(' ');\n\t\tif (!departure && measured === this.#measured) return;\n\n\t\tconst from = departure ?? this.#where();\n\n\t\tthis.#fade = fade;\n\t\tthis.#measured = measured;\n\t\tthis.#travel = travel;\n\t\tthis.toggleState('overflowing', travel.distance > 0);\n\t\tthis.#run(tape, from);\n\t}\n\n\t#originals(): Array<ChildNode> {\n\t\treturn [...this.childNodes].filter((child) => child !== this.control);\n\t}\n\n\t#place(tape: HTMLElement, position: number): void {\n\t\tconst { distance } = this.#travel;\n\n\t\tthis.#position = clamp(position, 0, distance);\n\t\ttape.style.translate = this.#position > 0 ? `${String(-this.#position)}px` : '';\n\t\twriteAttribute(this.control, 'data-sonic-clipped', clippedEnds(this.#position, distance));\n\t\tthis.#writeFades(this.#position);\n\t}\n\n\t#run(tape: HTMLElement, departure: TickerDeparture = this.#where()): void {\n\t\tconst canTravel =\n\t\t\t!this.#isFocusInside && !this.#reducedMotion.matches && !this.hasState('dragging');\n\t\tconst leg = canTravel ? tickerLeg(this.#travel, departure) : undefined;\n\n\t\tthis.#stop();\n\t\tthis.#heading = departure.heading;\n\t\tif (leg) this.#start(tape, leg);\n\t\telse this.#place(tape, this.#isFocusInside ? 0 : departure.from);\n\t}\n\n\t#start(tape: HTMLElement, leg: TickerLeg): void {\n\t\tconst animation = tape.animate(\n\t\t\t{ translate: [`${String(-leg.from)}px`, `${String(-leg.to)}px`] },\n\t\t\t{ delay: leg.hold, duration: leg.duration, fill: 'both' },\n\t\t);\n\n\t\tanimation.addEventListener('finish', () => {\n\t\t\tthis.#run(tape, { from: leg.to, heading: leg.heading });\n\t\t});\n\t\tthis.#running = { animation, fades: [], leg };\n\t\ttape.style.translate = '';\n\t\twriteAttribute(\n\t\t\tthis.control,\n\t\t\t'data-sonic-clipped',\n\t\t\tclippedEnds(leg.from, this.#travel.distance),\n\t\t);\n\t\tthis.#syncPause();\n\t}\n\n\t#startFades(leg: TickerLeg, elapsed: number): Array<Animation> {\n\t\tconst fades = legFades(leg, this.#travel.distance, this.#fade);\n\n\t\treturn (['start', 'end'] as const).flatMap((end) => {\n\t\t\tconst { delay, duration, widths } = fades[end];\n\t\t\tif (duration <= 0 || delay + duration <= elapsed) return [];\n\n\t\t\treturn this.control.animate(\n\t\t\t\t{ [`--_sonic-ticker-fade-${end}`]: widths.map((width) => `${String(width)}px`) },\n\t\t\t\t{ delay: delay - elapsed, duration, fill: 'both' },\n\t\t\t);\n\t\t});\n\t}\n\n\t#stop(): void {\n\t\tconst running = this.#running;\n\n\t\tclearTimeout(this.#mark);\n\t\tif (!running) return;\n\n\t\tfor (const fade of running.fades) fade.cancel();\n\t\trunning.animation.cancel();\n\t\tthis.#running = undefined;\n\t}\n\n\t// Calling `play()` on a finished animation restarts it, so the fades are recreated on resume, not paused\n\t#syncPause(): void {\n\t\tconst running = this.#running;\n\t\tif (!running) return;\n\n\t\tconst { animation, leg } = running;\n\t\tconst elapsed = Number(animation.currentTime);\n\n\t\tclearTimeout(this.#mark);\n\t\tfor (const fade of running.fades) fade.cancel();\n\t\trunning.fades = [];\n\t\tthis.#writeFades(legPosition(leg, elapsed));\n\t\tif (elapsed >= leg.hold) writeAttribute(this.control, 'data-sonic-clipped', 'start end');\n\t\tif (this.#isPaused()) {\n\t\t\tanimation.pause();\n\t\t\treturn;\n\t\t}\n\t\tif (animation.playState === 'paused') animation.play();\n\t\trunning.fades = this.#startFades(leg, elapsed);\n\t\tif (elapsed >= leg.hold) return;\n\n\t\tthis.#mark = setTimeout(() => {\n\t\t\twriteAttribute(this.control, 'data-sonic-clipped', 'start end');\n\t\t}, leg.hold - elapsed);\n\t}\n\n\t#where(): TickerDeparture {\n\t\tconst running = this.#running;\n\t\tif (!running) return { from: this.#position, heading: this.#heading };\n\n\t\treturn legDeparture(running.leg, Number(running.animation.currentTime));\n\t}\n\n\t#writeFades(position: number): void {\n\t\tconst { style } = this.control;\n\n\t\tstyle.setProperty('--_sonic-ticker-fade-start', `${String(fadeWidth(position, this.#fade))}px`);\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-ticker-fade-end',\n\t\t\t`${String(fadeWidth(this.#travel.distance - position, this.#fade))}px`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { BoxProbe } from "../lib/check-styles.js";
1
2
  import { SonicPositionGroupElement } from "./position-group.js";
2
3
  //#region src/elements/toggle.d.ts
3
4
  declare global {
@@ -8,8 +9,10 @@ declare global {
8
9
  export declare class SonicToggle extends SonicPositionGroupElement {
9
10
  #private;
10
11
  protected readonly bare: HTMLButtonElement;
12
+ protected readonly boxProbe: BoxProbe;
13
+ protected readonly control: HTMLDivElement;
11
14
  protected readonly defaultOrientation = "horizontal";
12
- protected readonly group: HTMLDivElement;
15
+ protected readonly sheet = "toggle.css";
13
16
  protected claimPress(): boolean;
14
17
  protected connect(signal: AbortSignal): void;
15
18
  protected isMirrored(child: Node): boolean;
@@ -22,22 +22,23 @@ const renderBare = template(`<button class="sonic-toggle-position" role="switch"
22
22
  const pressTolerance = 2;
23
23
  var SonicToggle = class extends SonicPositionGroupElement {
24
24
  bare = renderBare();
25
+ boxProbe = {
26
+ property: "margin-top",
27
+ ratio: "--_sonic-toggle-gap-ratio",
28
+ selector: ".sonic-toggle-cap"
29
+ };
30
+ control = renderToggle();
25
31
  defaultOrientation = "horizontal";
26
- group = renderToggle();
27
- #cap = requireChild(this.group, ".sonic-toggle-cap", HTMLSpanElement);
32
+ sheet = "toggle.css";
33
+ #cap = requireChild(this.control, ".sonic-toggle-cap", HTMLSpanElement);
28
34
  #draggedTo;
29
35
  #hasWarnedOfLegends = false;
30
- #well = requireChild(this.group, ".sonic-toggle-well", HTMLSpanElement);
36
+ #well = requireChild(this.control, ".sonic-toggle-well", HTMLSpanElement);
31
37
  claimPress() {
32
38
  return true;
33
39
  }
34
40
  connect(signal) {
35
41
  super.connect(signal);
36
- this.checkStyles(this.group, "toggle.css", {
37
- property: "margin-top",
38
- ratio: "--_sonic-toggle-gap-ratio",
39
- selector: ".sonic-toggle-cap"
40
- });
41
42
  this.bare.addEventListener("click", (event) => {
42
43
  if (event instanceof PointerEvent && event.pointerType !== "") return;
43
44
  this.setChecked(!this.checked);
@@ -55,7 +56,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
55
56
  render() {
56
57
  if (!this.isBound()) return;
57
58
  this.#checkLegends();
58
- this.group.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
59
+ this.control.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
59
60
  this.#placeCap(this.#draggedTo ?? this.#restingAt());
60
61
  super.render();
61
62
  for (const [index, option] of this.options().entries()) option.style.setProperty("--_sonic-toggle-index", String(index));
@@ -64,11 +65,11 @@ var SonicToggle = class extends SonicPositionGroupElement {
64
65
  return renderPosition();
65
66
  }
66
67
  #bindCap(signal) {
67
- bindDrag(this.group, {
68
+ bindDrag(this.control, {
68
69
  grab: (event) => this.#grab(event),
69
70
  lift: (drag, event) => {
70
71
  if (!drag.isPress || drag.isHolding || this.isDisabled()) return;
71
- if (!isUnder(this.group, event.clientX, event.clientY)) return;
72
+ if (!isUnder(this.control, event.clientX, event.clientY)) return;
72
73
  this.#press(drag.position);
73
74
  },
74
75
  move: (drag, event) => {
@@ -97,7 +98,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
97
98
  console.warn(`<sonic-toggle> lays out legends="outside" for two or three positions, and this one has ${String(count)}`);
98
99
  }
99
100
  #fixedLegend() {
100
- return [...this.children].find((child) => child !== this.group && !super.isMirrored(child));
101
+ return [...this.children].find((child) => child !== this.control && !super.isMirrored(child));
101
102
  }
102
103
  #grab(event) {
103
104
  const position = this.pressedIndex(event.target);
@@ -125,7 +126,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
125
126
  return true;
126
127
  }
127
128
  #placeCap(at) {
128
- this.group.style.setProperty("--_sonic-toggle-at", String(at));
129
+ this.control.style.setProperty("--_sonic-toggle-at", String(at));
129
130
  }
130
131
  #press(position) {
131
132
  const target = this.pressTarget(position);
@@ -1 +1 @@
1
- {"version":3,"file":"toggle.js","names":[],"sources":["../../../src/elements/toggle.ts"],"sourcesContent":["import type { ToggleTravel } from '#lib/toggle-travel.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { enabledEnds } from '#lib/positions.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-toggle': SonicToggle;\n\t}\n}\n\n// fallow-ignore-next-line code-duplication -- each control draws its own parts\nconst renderToggle = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-toggle\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-well\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-cap\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-toggle-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-toggle-legend\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-toggle-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\nconst pressTolerance = 2;\n\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tisPress: boolean;\n\tmarked: HTMLButtonElement;\n\tposition: number;\n\tstart: number;\n\tthresholdPx: number;\n\ttravel: ToggleTravel;\n}\n\nexport class SonicToggle extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'horizontal';\n\n\tprotected readonly group = renderToggle();\n\n\treadonly #cap = requireChild(this.group, '.sonic-toggle-cap', HTMLSpanElement);\n\n\t#draggedTo: number | undefined;\n\n\t#hasWarnedOfLegends = false;\n\n\treadonly #well = requireChild(this.group, '.sonic-toggle-well', HTMLSpanElement);\n\n\t// Every press is the toggle's own, so the base's slide-off never starts\n\tprotected override claimPress(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'toggle.css', {\n\t\t\t\tproperty: 'margin-top',\n\t\t\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\t\t\tselector: '.sonic-toggle-cap',\n\t\t\t});\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A pointer's press is taken on its release, before the click it sends\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tthis.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindCap(signal);\n\t}\n\n\tprotected override isMirrored(child: Node): boolean {\n\t\treturn super.isMirrored(child) || child === this.#fixedLegend();\n\t}\n\n\tprotected override placeCopies(copies: Array<Node>): void {\n\t\tconst positions = copies.filter((copy) => super.isMirrored(copy));\n\n\t\tplaceChildren(\n\t\t\tthis.#cap,\n\t\t\tcopies.filter((copy) => !positions.includes(copy)),\n\t\t);\n\t\tsuper.placeCopies(positions);\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tif (__DEV__) this.#checkLegends();\n\t\tthis.group.style.setProperty('--_sonic-toggle-count', String(this.positions().length));\n\t\tthis.#placeCap(this.#draggedTo ?? this.#restingAt());\n\t\tsuper.render();\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.style.setProperty('--_sonic-toggle-index', String(index));\n\t\t}\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindCap(signal: AbortSignal): void {\n\t\tbindDrag<CapDrag>(\n\t\t\tthis.group,\n\t\t\t{\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tif (!drag.isPress || drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.group, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#press(drag.position);\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\t// A held finger wobbles, and a drag back across halfway would let go\n\t\t\t\t\tif (drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tconst pointer = this.along(event);\n\t\t\t\t\tconst moved = Math.abs(pointer - drag.start);\n\t\t\t\t\tif (!drag.hasMoved && moved < drag.thresholdPx) return;\n\n\t\t\t\t\tdrag.hasMoved = true;\n\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\tif (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;\n\t\t\t\t\tif (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId))\n\t\t\t\t\t\tdrag.isPress = false;\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete drag.marked.dataset.sonicActive;\n\t\t\t\t\tthis.#draggedTo = undefined;\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkLegends(): void {\n\t\tif (!__DEV__ || this.#hasWarnedOfLegends) return;\n\n\t\tconst count = this.options().length;\n\t\tif (count <= 3 || this.getAttribute('legends') !== 'outside') return;\n\n\t\tthis.#hasWarnedOfLegends = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-toggle> lays out legends=\"outside\" for two or three positions, and this one has ${String(count)}`,\n\t\t);\n\t}\n\n\t#fixedLegend(): Element | undefined {\n\t\treturn [...this.children].find((child) => child !== this.group && !super.isMirrored(child));\n\t}\n\n\t#grab(event: PointerEvent): CapDrag | undefined {\n\t\tconst position = this.pressedIndex(event.target);\n\t\tif (this.isDisabled() || position < 0) return undefined;\n\n\t\tconst isOnCap = isUnder(this.#cap, event.clientX, event.clientY);\n\t\tconst travel = this.#travel(event, isOnCap);\n\t\tif (!travel || (!isOnCap && this.isOptionDisabled(position))) return undefined;\n\n\t\tconst marked = this.options()[position] ?? this.bare;\n\n\t\tmarked.toggleAttribute('data-sonic-active', true);\n\n\t\treturn {\n\t\t\thasMoved: false,\n\t\t\tisHolding: this.#holdFrom(position, event.pointerId),\n\t\t\tisPress: true,\n\t\t\tmarked,\n\t\t\tposition,\n\t\t\tstart: this.along(event),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravel,\n\t\t};\n\t}\n\n\t#holdFrom(position: number, pointerId: number): boolean {\n\t\tconst target = this.pressTarget(position);\n\t\tif (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;\n\n\t\tthis.hold(target, pointerId);\n\n\t\treturn true;\n\t}\n\n\t#placeCap(at: number): void {\n\t\tthis.group.style.setProperty('--_sonic-toggle-at', String(at));\n\t}\n\n\t#press(position: number): void {\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.refocusAt(target);\n\t}\n\n\t#restingAt(): number {\n\t\treturn Math.max(0, this.checkedIndex());\n\t}\n\n\t#slideTo(at: number, pointerId: number): boolean {\n\t\tconst crossed = Math.round(at);\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isChecked(crossed)) return false;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\n\n\t\treturn true;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = enabledEnds(this.positions());\n\t\tif (first < 0) return undefined;\n\n\t\tconst box = this.#well.getBoundingClientRect();\n\t\tconst [start, length, thickness] =\n\t\t\tthis.orientation === 'vertical'\n\t\t\t\t? [box.top, box.height, box.width]\n\t\t\t\t: [box.left, box.width, box.height];\n\n\t\treturn travelFrom({\n\t\t\tat: this.#restingAt(),\n\t\t\tcount: this.positions().length,\n\t\t\tfirst,\n\t\t\tisOnCap,\n\t\t\tlast,\n\t\t\tpointer: this.along(event),\n\t\t\twell: { length, start, thickness },\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;AAmBA,MAAM,eAAe,SACT;;;;;IAMX,cACD;AAEA,MAAM,iBAAiB,SACX;;;;IAKX,iBACD;AAEA,MAAM,aAAa,SACP,+EACX,iBACD;AAEA,MAAM,iBAAiB;AAavB,IAAa,cAAb,cAAiC,0BAA0B;CAC1D,AAAmB,OAAO,WAAW;CAErC,AAAmB,qBAAqB;CAExC,AAAmB,QAAQ,aAAa;CAExC,AAAS,OAAO,aAAa,KAAK,OAAO,qBAAqB,eAAe;CAE7E;CAEA,sBAAsB;CAEtB,AAAS,QAAQ,aAAa,KAAK,OAAO,sBAAsB,eAAe;CAG/E,AAAmB,aAAsB;EACxC,OAAO;CACR;CAEA,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,MAAM;EAEnB,KAAK,YAAY,KAAK,OAAO,cAAc;GAC1C,UAAU;GACV,OAAO;GACP,UAAU;EACX,CAAC;EACF,KAAK,KAAK,iBACT,UACC,UAAU;GAEV,IAAI,iBAAiB,gBAAgB,MAAM,gBAAgB,IAAI;GAE/D,KAAK,WAAW,CAAC,KAAK,OAAO;EAC9B,GACA,EAAE,OAAO,CACV;EACA,KAAK,SAAS,MAAM;CACrB;CAEA,AAAmB,WAAW,OAAsB;EACnD,OAAO,MAAM,WAAW,KAAK,KAAK,UAAU,KAAK,aAAa;CAC/D;CAEA,AAAmB,YAAY,QAA2B;EACzD,MAAM,YAAY,OAAO,QAAQ,SAAS,MAAM,WAAW,IAAI,CAAC;EAEhE,cACC,KAAK,MACL,OAAO,QAAQ,SAAS,CAAC,UAAU,SAAS,IAAI,CAAC,CAClD;EACA,MAAM,YAAY,SAAS;CAC5B;CAEA,AAAmB,SAAe;EACjC,IAAI,CAAC,KAAK,QAAQ,GAAG;EAER,KAAK,cAAc;EAChC,KAAK,MAAM,MAAM,YAAY,yBAAyB,OAAO,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;EACrF,KAAK,UAAU,KAAK,cAAc,KAAK,WAAW,CAAC;EACnD,MAAM,OAAO;EACb,KAAK,MAAM,CAAC,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,QAAQ,GACpD,OAAO,MAAM,YAAY,yBAAyB,OAAO,KAAK,CAAC;CAEjE;CAEA,AAAU,eAAkC;EAC3C,OAAO,eAAe;CACvB;CAEA,SAAS,QAA2B;EACnC,SACC,KAAK,OACL;GACC,OAAO,UAAU,KAAK,MAAM,KAAK;GACjC,OAAO,MAAM,UAAU;IACtB,IAAI,CAAC,KAAK,WAAW,KAAK,aAAa,KAAK,WAAW,GAAG;IAC1D,IAAI,CAAC,QAAQ,KAAK,OAAO,MAAM,SAAS,MAAM,OAAO,GAAG;IAExD,KAAK,OAAO,KAAK,QAAQ;GAC1B;GACA,OAAO,MAAM,UAAU;IAEtB,IAAI,KAAK,aAAa,KAAK,WAAW,GAAG;IACzC,MAAM,UAAU,KAAK,MAAM,KAAK;IAChC,MAAM,QAAQ,KAAK,IAAI,UAAU,KAAK,KAAK;IAC3C,IAAI,CAAC,KAAK,YAAY,QAAQ,KAAK,aAAa;IAEhD,KAAK,WAAW;IAChB,KAAK,YAAY,YAAY,IAAI;IACjC,IAAI,SAAS,iBAAiB,KAAK,aAAa,KAAK,UAAU;IAC/D,IAAI,KAAK,SAAS,WAAW,KAAK,QAAQ,OAAO,GAAG,MAAM,SAAS,GAClE,KAAK,UAAU;GACjB;GACA,UAAU,SAAS;IAClB,OAAO,KAAK,OAAO,QAAQ;IAC3B,KAAK,aAAa;IAClB,KAAK,YAAY,YAAY,KAAK;IAClC,KAAK,OAAO;GACb;EACD,GACA,MACD;CACD;CAEA,gBAAsB;EACrB,IAAgB,KAAK,qBAAqB;EAE1C,MAAM,QAAQ,KAAK,QAAQ,CAAC,CAAC;EAC7B,IAAI,SAAS,KAAK,KAAK,aAAa,SAAS,MAAM,WAAW;EAE9D,KAAK,sBAAsB;EAC3B,QAAQ,KACP,0FAA0F,OAAO,KAAK,GACvG;CACD;CAEA,eAAoC;EACnC,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,MAAM,UAAU,UAAU,KAAK,SAAS,CAAC,MAAM,WAAW,KAAK,CAAC;CAC3F;CAEA,MAAM,OAA0C;EAC/C,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,IAAI,KAAK,WAAW,KAAK,WAAW,GAAG,OAAO;EAE9C,MAAM,UAAU,QAAQ,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO;EAC/D,MAAM,SAAS,KAAK,QAAQ,OAAO,OAAO;EAC1C,IAAI,CAAC,UAAW,CAAC,WAAW,KAAK,iBAAiB,QAAQ,GAAI,OAAO;EAErE,MAAM,SAAS,KAAK,QAAQ,CAAC,CAAC,aAAa,KAAK;EAEhD,OAAO,gBAAgB,qBAAqB,IAAI;EAEhD,OAAO;GACN,UAAU;GACV,WAAW,KAAK,UAAU,UAAU,MAAM,SAAS;GACnD,SAAS;GACT;GACA;GACA,OAAO,KAAK,MAAM,KAAK;GACvB,aAAa,gBAAgB,MAAM,WAAW;GAC9C;EACD;CACD;CAEA,UAAU,UAAkB,WAA4B;EACvD,MAAM,SAAS,KAAK,YAAY,QAAQ;EACxC,IAAI,CAAC,KAAK,YAAY,MAAM,KAAK,KAAK,iBAAiB,MAAM,GAAG,OAAO;EAEvE,KAAK,KAAK,QAAQ,SAAS;EAE3B,OAAO;CACR;CAEA,UAAU,IAAkB;EAC3B,KAAK,MAAM,MAAM,YAAY,sBAAsB,OAAO,EAAE,CAAC;CAC9D;CAEA,OAAO,UAAwB;EAC9B,MAAM,SAAS,KAAK,YAAY,QAAQ;EACxC,IAAI,KAAK,iBAAiB,MAAM,GAAG;EAEnC,KAAK,OAAO,MAAM;EAClB,KAAK,UAAU,MAAM;CACtB;CAEA,aAAqB;EACpB,OAAO,KAAK,IAAI,GAAG,KAAK,aAAa,CAAC;CACvC;CAEA,SAAS,IAAY,WAA4B;EAChD,MAAM,UAAU,KAAK,MAAM,EAAE;EAE7B,KAAK,aAAa;EAClB,KAAK,UAAU,EAAE;EACjB,IAAI,KAAK,UAAU,OAAO,GAAG,OAAO;EAEpC,KAAK,YAAY;EACjB,IAAI,KAAK,iBAAiB,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,OAAO;EAEtE,IAAI,KAAK,YAAY,OAAO,GAAG,KAAK,KAAK,SAAS,SAAS;OACtD,KAAK,OAAO,OAAO;EACxB,KAAK,UAAU,OAAO;EAEtB,OAAO;CACR;CAEA,QAAQ,OAAqB,SAA4C;EACxE,MAAM,CAAC,OAAO,QAAQ,YAAY,KAAK,UAAU,CAAC;EAClD,IAAI,QAAQ,GAAG,OAAO;EAEtB,MAAM,MAAM,KAAK,MAAM,sBAAsB;EAC7C,MAAM,CAAC,OAAO,QAAQ,aACrB,KAAK,gBAAgB,aAClB;GAAC,IAAI;GAAK,IAAI;GAAQ,IAAI;EAAK,IAC/B;GAAC,IAAI;GAAM,IAAI;GAAO,IAAI;EAAM;EAEpC,OAAO,WAAW;GACjB,IAAI,KAAK,WAAW;GACpB,OAAO,KAAK,UAAU,CAAC,CAAC;GACxB;GACA;GACA;GACA,SAAS,KAAK,MAAM,KAAK;GACzB,MAAM;IAAE;IAAQ;IAAO;GAAU;EAClC,CAAC;CACF;AACD"}
1
+ {"version":3,"file":"toggle.js","names":[],"sources":["../../../src/elements/toggle.ts"],"sourcesContent":["import type { BoxProbe } from '#lib/check-styles.ts';\nimport type { ToggleTravel } from '#lib/toggle-travel.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { enabledEnds } from '#lib/positions.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-toggle': SonicToggle;\n\t}\n}\n\n// fallow-ignore-next-line code-duplication -- each control draws its own parts\nconst renderToggle = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-toggle\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-well\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-cap\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-toggle-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-toggle-legend\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-toggle-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\nconst pressTolerance = 2;\n\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tisPress: boolean;\n\tmarked: HTMLButtonElement;\n\tposition: number;\n\tstart: number;\n\tthresholdPx: number;\n\ttravel: ToggleTravel;\n}\n\nexport class SonicToggle extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected override readonly boxProbe: BoxProbe = {\n\t\tproperty: 'margin-top',\n\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\tselector: '.sonic-toggle-cap',\n\t};\n\n\tprotected override readonly control = renderToggle();\n\n\tprotected readonly defaultOrientation = 'horizontal';\n\n\tprotected override readonly sheet = 'toggle.css';\n\n\treadonly #cap = requireChild(this.control, '.sonic-toggle-cap', HTMLSpanElement);\n\n\t#draggedTo: number | undefined;\n\n\t#hasWarnedOfLegends = false;\n\n\treadonly #well = requireChild(this.control, '.sonic-toggle-well', HTMLSpanElement);\n\n\t// Every press is the toggle's own, so the base's slide-off never starts\n\tprotected override claimPress(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A pointer's press is taken on its release, before the click it sends\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tthis.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindCap(signal);\n\t}\n\n\tprotected override isMirrored(child: Node): boolean {\n\t\treturn super.isMirrored(child) || child === this.#fixedLegend();\n\t}\n\n\tprotected override placeCopies(copies: Array<Node>): void {\n\t\tconst positions = copies.filter((copy) => super.isMirrored(copy));\n\n\t\tplaceChildren(\n\t\t\tthis.#cap,\n\t\t\tcopies.filter((copy) => !positions.includes(copy)),\n\t\t);\n\t\tsuper.placeCopies(positions);\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tif (__DEV__) this.#checkLegends();\n\t\tthis.control.style.setProperty('--_sonic-toggle-count', String(this.positions().length));\n\t\tthis.#placeCap(this.#draggedTo ?? this.#restingAt());\n\t\tsuper.render();\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.style.setProperty('--_sonic-toggle-index', String(index));\n\t\t}\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindCap(signal: AbortSignal): void {\n\t\tbindDrag<CapDrag>(\n\t\t\tthis.control,\n\t\t\t{\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tif (!drag.isPress || drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.control, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#press(drag.position);\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\t// A held finger wobbles, and a drag back across halfway would let go\n\t\t\t\t\tif (drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tconst pointer = this.along(event);\n\t\t\t\t\tconst moved = Math.abs(pointer - drag.start);\n\t\t\t\t\tif (!drag.hasMoved && moved < drag.thresholdPx) return;\n\n\t\t\t\t\tdrag.hasMoved = true;\n\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\tif (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;\n\t\t\t\t\tif (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId))\n\t\t\t\t\t\tdrag.isPress = false;\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete drag.marked.dataset.sonicActive;\n\t\t\t\t\tthis.#draggedTo = undefined;\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkLegends(): void {\n\t\tif (!__DEV__ || this.#hasWarnedOfLegends) return;\n\n\t\tconst count = this.options().length;\n\t\tif (count <= 3 || this.getAttribute('legends') !== 'outside') return;\n\n\t\tthis.#hasWarnedOfLegends = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-toggle> lays out legends=\"outside\" for two or three positions, and this one has ${String(count)}`,\n\t\t);\n\t}\n\n\t#fixedLegend(): Element | undefined {\n\t\treturn [...this.children].find((child) => child !== this.control && !super.isMirrored(child));\n\t}\n\n\t#grab(event: PointerEvent): CapDrag | undefined {\n\t\tconst position = this.pressedIndex(event.target);\n\t\tif (this.isDisabled() || position < 0) return undefined;\n\n\t\tconst isOnCap = isUnder(this.#cap, event.clientX, event.clientY);\n\t\tconst travel = this.#travel(event, isOnCap);\n\t\tif (!travel || (!isOnCap && this.isOptionDisabled(position))) return undefined;\n\n\t\tconst marked = this.options()[position] ?? this.bare;\n\n\t\tmarked.toggleAttribute('data-sonic-active', true);\n\n\t\treturn {\n\t\t\thasMoved: false,\n\t\t\tisHolding: this.#holdFrom(position, event.pointerId),\n\t\t\tisPress: true,\n\t\t\tmarked,\n\t\t\tposition,\n\t\t\tstart: this.along(event),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravel,\n\t\t};\n\t}\n\n\t#holdFrom(position: number, pointerId: number): boolean {\n\t\tconst target = this.pressTarget(position);\n\t\tif (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;\n\n\t\tthis.hold(target, pointerId);\n\n\t\treturn true;\n\t}\n\n\t#placeCap(at: number): void {\n\t\tthis.control.style.setProperty('--_sonic-toggle-at', String(at));\n\t}\n\n\t#press(position: number): void {\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.refocusAt(target);\n\t}\n\n\t#restingAt(): number {\n\t\treturn Math.max(0, this.checkedIndex());\n\t}\n\n\t#slideTo(at: number, pointerId: number): boolean {\n\t\tconst crossed = Math.round(at);\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isChecked(crossed)) return false;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\n\n\t\treturn true;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = enabledEnds(this.positions());\n\t\tif (first < 0) return undefined;\n\n\t\tconst box = this.#well.getBoundingClientRect();\n\t\tconst [start, length, thickness] =\n\t\t\tthis.orientation === 'vertical'\n\t\t\t\t? [box.top, box.height, box.width]\n\t\t\t\t: [box.left, box.width, box.height];\n\n\t\treturn travelFrom({\n\t\t\tat: this.#restingAt(),\n\t\t\tcount: this.positions().length,\n\t\t\tfirst,\n\t\t\tisOnCap,\n\t\t\tlast,\n\t\t\tpointer: this.along(event),\n\t\t\twell: { length, start, thickness },\n\t\t});\n\t}\n}\n"],"mappings":"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"}
@@ -73,16 +73,17 @@ export declare abstract class SonicValueElement extends SonicFormElement {
73
73
  get value(): number;
74
74
  set value(next: null | number | undefined);
75
75
  get valueText(): string;
76
+ protected abstract readonly control: HTMLElement;
76
77
  protected readonly notation: ValueNotation;
77
78
  attributeChangedCallback(name: string): void;
78
79
  connectedCallback(): void;
79
80
  protected abandonHold(): void;
80
- protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void;
81
+ protected bindGestures(signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void;
81
82
  protected bindHover(control: HTMLElement, signal: AbortSignal, { canShow, changed, place, valueAt }: HoverBinding): void;
82
83
  protected controlOrientation(): 'horizontal' | 'vertical' | undefined;
83
84
  protected controlRole(): 'slider' | 'spinbutton';
84
85
  protected abstract draw(): void;
85
- protected abstract focusTarget(): HTMLElement;
86
+ protected focusTarget(): HTMLElement;
86
87
  protected input(next: number): boolean;
87
88
  protected isWrapping(): boolean;
88
89
  protected mapping(): ValueMapping;
@@ -98,7 +99,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
98
99
  protected spokenText(value: number): string | undefined;
99
100
  protected springTarget(): number | undefined;
100
101
  protected tapTarget(): number | undefined;
101
- protected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number;
102
+ protected upwardAxis(travelProperty: `--_sonic-${string}`): ValueAxis;
102
103
  }
103
104
  //#endregion
104
105
  //# sourceMappingURL=value-element.d.ts.map
@@ -251,7 +251,8 @@ var SonicValueElement = class extends SonicFormElement {
251
251
  abandonHold() {
252
252
  this.#gestures?.abandon();
253
253
  }
254
- bindGestures(control, signal, grab) {
254
+ bindGestures(signal, grab) {
255
+ const control = this.control;
255
256
  const bubble = control.querySelector("[popover]");
256
257
  if (bubble) this.#readout = {
257
258
  anchor: control,
@@ -316,6 +317,9 @@ var SonicValueElement = class extends SonicFormElement {
316
317
  controlRole() {
317
318
  return "slider";
318
319
  }
320
+ focusTarget() {
321
+ return this.control;
322
+ }
319
323
  input(next) {
320
324
  const previous = this.#model.value;
321
325
  if (this.#model.input(next)) this.render();
@@ -374,8 +378,12 @@ var SonicValueElement = class extends SonicFormElement {
374
378
  }
375
379
  springTarget() {}
376
380
  tapTarget() {}
377
- travelPx(control, property) {
378
- return Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));
381
+ upwardAxis(travelProperty) {
382
+ const styles = getComputedStyle(this.control);
383
+ return {
384
+ position: (event) => -event.clientY,
385
+ travelPx: Math.max(1, readPxProperty(styles, travelProperty, fallbackTravelPx))
386
+ };
379
387
  }
380
388
  #entryText() {
381
389
  const value = this.#model.value;