@xsynaptic/sonic-ui 0.27.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.
- package/CHANGELOG.md +13 -0
- package/README.md +1 -0
- package/dist/default/define/ticker.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/sonic-element.js +1 -1
- package/dist/default/elements/ticker.js +313 -0
- package/dist/default/elements/wavestrip.js +2 -5
- package/dist/default/index.js +2 -1
- package/dist/default/lib/cancel-zone.js +12 -0
- package/dist/default/lib/distribute.js +2 -3
- package/dist/default/lib/mirror-children.js +1 -1
- package/dist/default/lib/pointer-drag.js +8 -2
- package/dist/default/lib/ticker-leg.js +54 -0
- package/dist/dev/define/ticker.d.ts +2 -0
- package/dist/dev/define/ticker.js +8 -0
- package/dist/dev/define/ticker.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/sonic-element.d.ts +1 -0
- package/dist/dev/elements/sonic-element.js +3 -2
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/ticker.d.ts +25 -0
- package/dist/dev/elements/ticker.js +314 -0
- package/dist/dev/elements/ticker.js.map +1 -0
- package/dist/dev/elements/wavestrip.js +2 -5
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/index.d.ts +2 -1
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/cancel-zone.js +13 -0
- package/dist/dev/lib/cancel-zone.js.map +1 -0
- package/dist/dev/lib/check-styles.js +20 -12
- package/dist/dev/lib/check-styles.js.map +1 -1
- package/dist/dev/lib/distribute.js +2 -3
- package/dist/dev/lib/distribute.js.map +1 -1
- package/dist/dev/lib/mirror-children.js +9 -7
- package/dist/dev/lib/mirror-children.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +8 -2
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/ticker-leg.js +55 -0
- package/dist/dev/lib/ticker-leg.js.map +1 -0
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +28 -5
- package/dist/styles/material/cap.css +16 -5
- package/dist/styles/material/core.css +1 -0
- package/dist/styles/slider.css +2 -1
- package/dist/styles/switch.css +3 -1
- package/dist/styles/ticker.css +115 -0
- package/dist/styles/toggle.css +7 -5
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `--sonic-gloss` scales a cap's sheen apart from `--sonic-relief`: 0 is matte, 1 the default. A slider cap has no sheen until the token is set, so there `1` is not the same as unset.
|
|
8
|
+
- - `<sonic-ticker>`: one line of content that scrolls there and back when it does not fit its box, registered by `./define/ticker` with `ticker.css`, which needs no material sheet. It stands still with an ellipsis under reduced motion, and pauses under a mouse, while a link round it has keyboard focus, off screen, and while `paused` is set. A drag along the line moves the tape; with `cancellable` a drag that strays past `--sonic-cancel-zone`, shared with the wave strip, puts it back. `text` replaces the content. A link among the children is copied and still navigates.
|
|
9
|
+
- Ticker surface: tokens `--sonic-ticker-rate` (a length a second, `40px`), `--sonic-ticker-hold` (`2s`) and `--sonic-ticker-fade` (`0.75rem`); hook classes `.sonic-ticker` and `.sonic-ticker-tape`; states `data-sonic-overflowing`, `data-sonic-dragging`, `data-sonic-cancelling` and `data-sonic-paused`, and `data-sonic-clipped` naming each end with content past it.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Switch and toggle: the bat and the cap land with a small overshoot, and the switch's flip takes 120ms, up from 90ms.
|
|
14
|
+
- Split: a move the siblings cannot absorb on their own steps is turned back, so the total holds across mixed step grids.
|
|
15
|
+
|
|
3
16
|
## 0.27.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to st
|
|
|
88
88
|
| `slider.css` | core, cap, glass, readout, value, groove, scale |
|
|
89
89
|
| `spectrum.css` | core |
|
|
90
90
|
| `switch.css` | core, cap |
|
|
91
|
+
| `ticker.css` | |
|
|
91
92
|
| `toggle.css` | core, cap, well, keycap |
|
|
92
93
|
| `waveform.css` | core, cap, glass, readout, value |
|
|
93
94
|
| `wavestrip.css` | core, cap, glass, readout, value |
|
package/dist/default/define.js
CHANGED
|
@@ -48,7 +48,7 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
48
48
|
this.#connection = void 0;
|
|
49
49
|
throw error;
|
|
50
50
|
}
|
|
51
|
-
const { boxProbe: box, control, sheet } = this;
|
|
51
|
+
const { boxProbe: box, control, sheet, sheetToken: token } = this;
|
|
52
52
|
}
|
|
53
53
|
connectedMoveCallback() {}
|
|
54
54
|
disconnectedCallback() {
|
|
@@ -0,0 +1,313 @@
|
|
|
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 };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { clamp, clampProportion } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
|
-
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
3
|
import { scrubRegions } from "../lib/scrub.js";
|
|
5
4
|
import { sortedRegions } from "../lib/time-regions.js";
|
|
5
|
+
import { outsideCancelZone } from "../lib/cancel-zone.js";
|
|
6
6
|
import { SonicWaveElement } from "./wave-element.js";
|
|
7
7
|
import { createMarkerBand } from "../lib/marker-band.js";
|
|
8
8
|
import { RegionLayer } from "./region-layer.js";
|
|
@@ -22,7 +22,6 @@ const lengths = {
|
|
|
22
22
|
pitch: "--_sonic-wavestrip-bar-pitch",
|
|
23
23
|
radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
|
|
24
24
|
};
|
|
25
|
-
const cancelZonePx = 48;
|
|
26
25
|
const renderWavestrip = template(`
|
|
27
26
|
<div class="sonic-wavestrip">
|
|
28
27
|
<canvas class="sonic-wavestrip-canvas" role="slider" tabindex="0"></canvas>
|
|
@@ -256,9 +255,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
256
255
|
}
|
|
257
256
|
#outside() {
|
|
258
257
|
if (!this.cancellable) return void 0;
|
|
259
|
-
|
|
260
|
-
const zone = readPxProperty(getComputedStyle(this.control), "--_sonic-cancel-zone", cancelZonePx);
|
|
261
|
-
return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
|
|
258
|
+
return outsideCancelZone(this.control, "--_sonic-cancel-zone");
|
|
262
259
|
}
|
|
263
260
|
#placeReadout() {
|
|
264
261
|
const at = this.mapping().proportionOf(this.readoutValue());
|
package/dist/default/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { SonicSlider } from "./elements/slider.js";
|
|
|
10
10
|
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
11
11
|
import { SonicSplit } from "./elements/split.js";
|
|
12
12
|
import { SonicSwitch } from "./elements/switch.js";
|
|
13
|
+
import { SonicTicker } from "./elements/ticker.js";
|
|
13
14
|
import { SonicToggle } from "./elements/toggle.js";
|
|
14
15
|
import { formatClock, parseClock } from "./lib/clock.js";
|
|
15
16
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
@@ -18,4 +19,4 @@ import { SonicXy } from "./elements/xy.js";
|
|
|
18
19
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
19
20
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
20
21
|
|
|
21
|
-
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
22
|
+
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicTicker, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readPxProperty } from "./read-px-property.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/cancel-zone.ts
|
|
4
|
+
const cancelZonePx = 48;
|
|
5
|
+
function outsideCancelZone(control, property) {
|
|
6
|
+
const box = control.getBoundingClientRect();
|
|
7
|
+
const zone = readPxProperty(getComputedStyle(control), property, cancelZonePx);
|
|
8
|
+
return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { outsideCancelZone };
|
|
@@ -187,8 +187,7 @@ function close(members, rule, held) {
|
|
|
187
187
|
const { landing, move, mover } = held;
|
|
188
188
|
const { index } = move;
|
|
189
189
|
const landed = landing.values[index] ?? mover.value;
|
|
190
|
-
|
|
191
|
-
return nearest(targets.flatMap((target) => {
|
|
190
|
+
return nearest(moverTargets(members, landing, index).flatMap((target) => {
|
|
192
191
|
const attempt = spread(members, rule, {
|
|
193
192
|
index,
|
|
194
193
|
target
|
|
@@ -197,7 +196,7 @@ function close(members, rule, held) {
|
|
|
197
196
|
}), move, {
|
|
198
197
|
landed,
|
|
199
198
|
start: mover.value
|
|
200
|
-
}) ??
|
|
199
|
+
}) ?? members.map(({ value }) => value);
|
|
201
200
|
}
|
|
202
201
|
function distribute(members, rule, move) {
|
|
203
202
|
const landing = spread(members, rule, move);
|
|
@@ -14,7 +14,7 @@ function isCopiedChange(host, record, { control, isCopied }) {
|
|
|
14
14
|
if (child === void 0) return false;
|
|
15
15
|
return isCopied(child) || record.type === "attributes" && record.target === child;
|
|
16
16
|
}
|
|
17
|
-
function mirrorChildren(host, { control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
|
|
17
|
+
function mirrorChildren(host, { canCopyLinks = false, control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
|
|
18
18
|
const slots = hostSlots.get(host) ?? attachSlots(host);
|
|
19
19
|
let copies = /* @__PURE__ */ new Map();
|
|
20
20
|
hostSlots.set(host, slots);
|
|
@@ -5,6 +5,7 @@ function capturePointer(target, pointerId) {
|
|
|
5
5
|
} catch {}
|
|
6
6
|
}
|
|
7
7
|
function bindDrag(target, handlers, signal) {
|
|
8
|
+
const isCaptureLate = handlers.isCaptureLate === true;
|
|
8
9
|
let held;
|
|
9
10
|
const end = () => {
|
|
10
11
|
if (!held) return;
|
|
@@ -28,14 +29,19 @@ function bindDrag(target, handlers, signal) {
|
|
|
28
29
|
pointerId: event.pointerId
|
|
29
30
|
};
|
|
30
31
|
handlers.toggle?.(true);
|
|
31
|
-
capturePointer(target, event.pointerId);
|
|
32
|
+
if (!isCaptureLate) capturePointer(target, event.pointerId);
|
|
32
33
|
}, { signal });
|
|
33
34
|
whileHeld("pointermove", handlers.move);
|
|
34
35
|
whileHeld("pointercancel", (drag) => {
|
|
35
36
|
handlers.cancel?.(drag);
|
|
36
37
|
end();
|
|
37
38
|
});
|
|
38
|
-
whileHeld("lostpointercapture",
|
|
39
|
+
whileHeld("lostpointercapture", (_drag, event) => {
|
|
40
|
+
if (!isCaptureLate || event.target === target) end();
|
|
41
|
+
});
|
|
42
|
+
if (isCaptureLate) whileHeld("pointerleave", (_drag, event) => {
|
|
43
|
+
if (!target.hasPointerCapture(event.pointerId)) end();
|
|
44
|
+
});
|
|
39
45
|
whileHeld("pointerup", (drag, event) => {
|
|
40
46
|
end();
|
|
41
47
|
handlers.lift?.(drag, event);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { clamp, clampProportion } from "./math.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/ticker-leg.ts
|
|
4
|
+
const fadeApproach = 3;
|
|
5
|
+
function tickerLeg({ distance, hold: fullHold, rate }, { from, heading, hold = fullHold }) {
|
|
6
|
+
if (!(distance > 0) || !(rate > 0)) return void 0;
|
|
7
|
+
const start = clamp(from, 0, distance);
|
|
8
|
+
const isOutbound = start === (heading > 0 ? distance : 0) ? heading < 0 : heading > 0;
|
|
9
|
+
const to = isOutbound ? distance : 0;
|
|
10
|
+
return {
|
|
11
|
+
duration: Math.abs(to - start) / rate * 1e3,
|
|
12
|
+
from: start,
|
|
13
|
+
heading: isOutbound ? 1 : -1,
|
|
14
|
+
hold: Math.max(0, hold),
|
|
15
|
+
to
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function legPosition(leg, elapsed) {
|
|
19
|
+
return leg.from + (leg.to - leg.from) * clampProportion((elapsed - leg.hold) / leg.duration);
|
|
20
|
+
}
|
|
21
|
+
function legDeparture(leg, elapsed) {
|
|
22
|
+
return {
|
|
23
|
+
from: legPosition(leg, elapsed),
|
|
24
|
+
heading: leg.heading,
|
|
25
|
+
hold: Math.max(0, leg.hold - elapsed)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function clippedEnds(position, distance) {
|
|
29
|
+
const ends = [...position > 0 ? ["start"] : [], ...position < distance ? ["end"] : []];
|
|
30
|
+
return ends.length > 0 ? ends.join(" ") : void 0;
|
|
31
|
+
}
|
|
32
|
+
function fadeWidth(past, fade) {
|
|
33
|
+
return clamp(past / fadeApproach, 0, Math.max(0, fade));
|
|
34
|
+
}
|
|
35
|
+
function legFade(leg, [pastFrom, pastTo], fade) {
|
|
36
|
+
const reach = Math.max(0, fade) * fadeApproach;
|
|
37
|
+
const msPerPx = leg.duration / Math.abs(leg.to - leg.from);
|
|
38
|
+
const from = Math.min(pastFrom, reach);
|
|
39
|
+
const to = Math.min(pastTo, reach);
|
|
40
|
+
return {
|
|
41
|
+
delay: leg.hold + (pastFrom - from) * msPerPx,
|
|
42
|
+
duration: Math.abs(to - from) * msPerPx,
|
|
43
|
+
widths: [from / fadeApproach, to / fadeApproach]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function legFades(leg, distance, fade) {
|
|
47
|
+
return {
|
|
48
|
+
end: legFade(leg, [distance - leg.from, distance - leg.to], fade),
|
|
49
|
+
start: legFade(leg, [leg.from, leg.to], fade)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { clippedEnds, fadeWidth, legDeparture, legFades, legPosition, tickerLeg };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticker.js","names":[],"sources":["../../../src/define/ticker.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicTicker } from '#elements/ticker.ts';\n\ndefineOnce('sonic-ticker', SonicTicker);\n\nexport type { SonicTicker } from '#elements/ticker.ts';\n"],"mappings":";;;;AAGA,WAAW,gBAAgB,WAAW"}
|
package/dist/dev/define.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { SonicSplit } from "./elements/split.js";
|
|
|
20
20
|
import "./define/split.js";
|
|
21
21
|
import { SonicSwitch } from "./elements/switch.js";
|
|
22
22
|
import "./define/switch.js";
|
|
23
|
+
import { SonicTicker } from "./elements/ticker.js";
|
|
24
|
+
import "./define/ticker.js";
|
|
23
25
|
import { SonicToggle } from "./elements/toggle.js";
|
|
24
26
|
import "./define/toggle.js";
|
|
25
27
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
@@ -28,4 +30,4 @@ import { SonicWavestrip } from "./elements/wavestrip.js";
|
|
|
28
30
|
import "./define/wavestrip.js";
|
|
29
31
|
import { SonicXy } from "./elements/xy.js";
|
|
30
32
|
import "./define/xy.js";
|
|
31
|
-
export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy };
|
|
33
|
+
export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicTicker, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy };
|
package/dist/dev/define.js
CHANGED
|
@@ -9,6 +9,7 @@ export declare abstract class SonicElement extends HTMLElement {
|
|
|
9
9
|
protected readonly control: HTMLElement | undefined;
|
|
10
10
|
protected readonly internals: ElementInternals | undefined;
|
|
11
11
|
protected readonly sheet: string | undefined;
|
|
12
|
+
protected readonly sheetToken: `--_sonic-${string}` | undefined;
|
|
12
13
|
blur(): void;
|
|
13
14
|
connectedCallback(): void;
|
|
14
15
|
connectedMoveCallback(): void;
|
|
@@ -49,11 +49,12 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
49
49
|
this.#connection = void 0;
|
|
50
50
|
throw error;
|
|
51
51
|
}
|
|
52
|
-
const { boxProbe: box, control, sheet } = this;
|
|
52
|
+
const { boxProbe: box, control, sheet, sheetToken: token } = this;
|
|
53
53
|
if (control && sheet !== void 0) checkStyles(this, {
|
|
54
54
|
box,
|
|
55
55
|
control,
|
|
56
|
-
sheet
|
|
56
|
+
sheet,
|
|
57
|
+
token
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
60
|
connectedMoveCallback() {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { BoxProbe } from '#lib/check-styles.ts';\n\nimport { checkStyles } from '#lib/check-styles.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst canReferElements = 'ariaLabelledByElements' in Element.prototype;\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tstatic #labelCount = 0;\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\tdeclare protected readonly boxProbe: BoxProbe | undefined;\n\n\tdeclare protected readonly control: HTMLElement | undefined;\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\tdeclare protected readonly sheet: string | undefined;\n\n\t#connection: AbortController | undefined;\n\n\treadonly #states = new Set<string>();\n\n\tstatic #labelId(label: Element): string {\n\t\tif (label.id === '') {\n\t\t\tSonicElement.#labelCount += 1;\n\t\t\tlabel.id = `sonic-label-${String(SonicElement.#labelCount)}`;\n\t\t}\n\n\t\treturn label.id;\n\t}\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\tthis.#connection = connection;\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst { boxProbe: box, control, sheet } = this;\n\n\t\tif (__DEV__ && control && sheet !== undefined) checkStyles(this, { box, control, sheet });\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) this.#writeLabels(target, labels);\n\t}\n\n\tprotected hasState(state: string): boolean {\n\t\treturn this.#states.has(state);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isBound(): boolean {\n\t\treturn this.#connection !== undefined;\n\t}\n\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A morph against server HTML, or a stray `innerHTML`, deletes the control\n\tprotected keepControl(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\t\tif (!control) return;\n\n\t\tconst keep = (): void => {\n\t\t\tappendOnce(this, control);\n\t\t\tif (__DEV__)\n\t\t\t\tcheckChildren(\n\t\t\t\t\tthis,\n\t\t\t\t\t[...this.childNodes].filter((child) => child !== control && !this.usesChild(child)),\n\t\t\t\t);\n\t\t};\n\t\tconst observer = new MutationObserver(keep);\n\n\t\tkeep();\n\t\tobserver.observe(this, { childList: true });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst parsed = toNumber(this.getAttribute(name));\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// Never compared; a host attribute set again resets the value a gesture left dirty\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (typeof value !== 'number' && typeof value !== 'string' && value !== true) {\n\t\t\tthis.removeAttribute(name);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAttribute(name, value === true ? '' : value.toString());\n\t}\n\n\tprotected stateTarget(): Element | undefined {\n\t\treturn this.control;\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.#states.add(state);\n\t\telse this.#states.delete(state);\n\t\tthis.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\tprotected usesChild(_child: Node): boolean {\n\t\treturn false;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#writeLabels(target: Element, labels: Array<Element>): void {\n\t\tif (canReferElements) {\n\t\t\twriteLabelledBy(target, labels);\n\t\t\treturn;\n\t\t}\n\n\t\tconst ids = labels.map((label) => SonicElement.#labelId(label));\n\n\t\twriteAttribute(target, 'aria-labelledby', ids.join(' '));\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { BoxProbe } from '#lib/check-styles.ts';\n\nimport { checkStyles } from '#lib/check-styles.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst canReferElements = 'ariaLabelledByElements' in Element.prototype;\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tstatic #labelCount = 0;\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\tdeclare protected readonly boxProbe: BoxProbe | undefined;\n\n\tdeclare protected readonly control: HTMLElement | undefined;\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\tdeclare protected readonly sheet: string | undefined;\n\n\tdeclare protected readonly sheetToken: `--_sonic-${string}` | undefined;\n\n\t#connection: AbortController | undefined;\n\n\treadonly #states = new Set<string>();\n\n\tstatic #labelId(label: Element): string {\n\t\tif (label.id === '') {\n\t\t\tSonicElement.#labelCount += 1;\n\t\t\tlabel.id = `sonic-label-${String(SonicElement.#labelCount)}`;\n\t\t}\n\n\t\treturn label.id;\n\t}\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\tthis.#connection = connection;\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst { boxProbe: box, control, sheet, sheetToken: token } = this;\n\n\t\tif (__DEV__ && control && sheet !== undefined)\n\t\t\tcheckStyles(this, { box, control, sheet, token });\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) this.#writeLabels(target, labels);\n\t}\n\n\tprotected hasState(state: string): boolean {\n\t\treturn this.#states.has(state);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isBound(): boolean {\n\t\treturn this.#connection !== undefined;\n\t}\n\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A morph against server HTML, or a stray `innerHTML`, deletes the control\n\tprotected keepControl(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\t\tif (!control) return;\n\n\t\tconst keep = (): void => {\n\t\t\tappendOnce(this, control);\n\t\t\tif (__DEV__)\n\t\t\t\tcheckChildren(\n\t\t\t\t\tthis,\n\t\t\t\t\t[...this.childNodes].filter((child) => child !== control && !this.usesChild(child)),\n\t\t\t\t);\n\t\t};\n\t\tconst observer = new MutationObserver(keep);\n\n\t\tkeep();\n\t\tobserver.observe(this, { childList: true });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst parsed = toNumber(this.getAttribute(name));\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// Never compared; a host attribute set again resets the value a gesture left dirty\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (typeof value !== 'number' && typeof value !== 'string' && value !== true) {\n\t\t\tthis.removeAttribute(name);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAttribute(name, value === true ? '' : value.toString());\n\t}\n\n\tprotected stateTarget(): Element | undefined {\n\t\treturn this.control;\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.#states.add(state);\n\t\telse this.#states.delete(state);\n\t\tthis.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\tprotected usesChild(_child: Node): boolean {\n\t\treturn false;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#writeLabels(target: Element, labels: Array<Element>): void {\n\t\tif (canReferElements) {\n\t\t\twriteLabelledBy(target, labels);\n\t\t\treturn;\n\t\t}\n\n\t\tconst ids = labels.map((label) => SonicElement.#labelId(label));\n\n\t\twriteAttribute(target, 'aria-labelledby', ids.join(' '));\n\t}\n}\n"],"mappings":"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"}
|