@xsynaptic/sonic-ui 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -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 +331 -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/meter.d.ts +1 -0
- package/dist/dev/elements/position-group.d.ts +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/spectrum.d.ts +1 -0
- package/dist/dev/elements/ticker.d.ts +25 -0
- package/dist/dev/elements/ticker.js +332 -0
- package/dist/dev/elements/ticker.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +1 -0
- package/dist/dev/elements/wavestrip.d.ts +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/crossfade.d.ts +1 -0
- 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 +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.28.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Ticker: setting `text` to the text it already shows no longer sends the tape back to the start, and `disabled` now holds the ticker at rest with no scrolling and no drag.
|
|
8
|
+
|
|
9
|
+
## 0.28.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- `--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.
|
|
14
|
+
- - `<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.
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Switch and toggle: the bat and the cap land with a small overshoot, and the switch's flip takes 120ms, up from 90ms.
|
|
20
|
+
- Split: a move the siblings cannot absorb on their own steps is turned back, so the total holds across mixed step grids.
|
|
21
|
+
|
|
3
22
|
## 0.27.0
|
|
4
23
|
|
|
5
24
|
### 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,331 @@
|
|
|
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 = ["disabled", "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
|
+
const next = text ?? "";
|
|
38
|
+
if (this.#holds(next)) return;
|
|
39
|
+
for (const original of this.#originals()) original.remove();
|
|
40
|
+
if (next !== "") this.prepend(next);
|
|
41
|
+
}
|
|
42
|
+
control = renderTicker();
|
|
43
|
+
sheet = "ticker.css";
|
|
44
|
+
sheetToken = "--_sonic-ticker-fade";
|
|
45
|
+
#drag;
|
|
46
|
+
#fade = fadePx;
|
|
47
|
+
#heading = 1;
|
|
48
|
+
#isClickSwallowed = false;
|
|
49
|
+
#isFocusInside = false;
|
|
50
|
+
#isHovered = false;
|
|
51
|
+
#isOffScreen = false;
|
|
52
|
+
#isPressed = false;
|
|
53
|
+
#mark;
|
|
54
|
+
#measured = "";
|
|
55
|
+
#position = 0;
|
|
56
|
+
#reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
|
57
|
+
#running;
|
|
58
|
+
#tape = requireChild(this.control, ".sonic-ticker-tape", HTMLSpanElement);
|
|
59
|
+
#travel = {
|
|
60
|
+
distance: 0,
|
|
61
|
+
hold: holdMs,
|
|
62
|
+
rate: ratePxPerSecond
|
|
63
|
+
};
|
|
64
|
+
attributeChangedCallback(name) {
|
|
65
|
+
if (name === "disabled" && this.isBound()) {
|
|
66
|
+
this.#drag?.end();
|
|
67
|
+
this.#run(this.#tape, {
|
|
68
|
+
from: 0,
|
|
69
|
+
heading: 1
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
this.render();
|
|
73
|
+
}
|
|
74
|
+
connect(signal) {
|
|
75
|
+
const control = this.control;
|
|
76
|
+
const tape = this.#tape;
|
|
77
|
+
const measure = () => {
|
|
78
|
+
this.#measure(tape);
|
|
79
|
+
};
|
|
80
|
+
const observer = new ResizeObserver(measure);
|
|
81
|
+
const drag = this.#bindDrag(tape, signal);
|
|
82
|
+
this.#drag = drag;
|
|
83
|
+
this.upgradeProperties("cancellable", "paused", "text");
|
|
84
|
+
mirrorChildren(this, {
|
|
85
|
+
canCopyLinks: true,
|
|
86
|
+
control,
|
|
87
|
+
copy: copyNode,
|
|
88
|
+
place: (copies) => {
|
|
89
|
+
placeChildren(tape, copies);
|
|
90
|
+
drag.end();
|
|
91
|
+
this.#measure(tape, {
|
|
92
|
+
from: 0,
|
|
93
|
+
heading: 1
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}, signal);
|
|
97
|
+
this.render();
|
|
98
|
+
observer.observe(control);
|
|
99
|
+
observer.observe(tape);
|
|
100
|
+
if ("fonts" in document) document.fonts.addEventListener("loadingdone", measure, { signal });
|
|
101
|
+
this.#bindFocus(tape, signal);
|
|
102
|
+
this.#bindPauses(signal);
|
|
103
|
+
this.#reducedMotion.addEventListener("change", () => {
|
|
104
|
+
this.#run(tape);
|
|
105
|
+
}, { signal });
|
|
106
|
+
signal.addEventListener("abort", () => {
|
|
107
|
+
observer.disconnect();
|
|
108
|
+
this.#stop();
|
|
109
|
+
}, { once: true });
|
|
110
|
+
}
|
|
111
|
+
render() {
|
|
112
|
+
if (!this.isBound()) return;
|
|
113
|
+
this.toggleState("paused", this.paused);
|
|
114
|
+
this.#syncPause();
|
|
115
|
+
}
|
|
116
|
+
#bindDrag(tape, signal) {
|
|
117
|
+
const control = this.control;
|
|
118
|
+
control.addEventListener("click", (event) => {
|
|
119
|
+
if (!this.#isClickSwallowed) return;
|
|
120
|
+
this.#isClickSwallowed = false;
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
event.stopPropagation();
|
|
123
|
+
}, {
|
|
124
|
+
capture: true,
|
|
125
|
+
signal
|
|
126
|
+
});
|
|
127
|
+
this.getRootNode().addEventListener("dragstart", (event) => {
|
|
128
|
+
if (this.#isPressed) event.preventDefault();
|
|
129
|
+
}, { signal });
|
|
130
|
+
return bindDrag(control, {
|
|
131
|
+
grab: (event) => {
|
|
132
|
+
if (this.#travel.distance <= 0 || this.isDisabled()) return;
|
|
133
|
+
return {
|
|
134
|
+
from: 0,
|
|
135
|
+
isDragging: false,
|
|
136
|
+
outside: void 0,
|
|
137
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
138
|
+
x: event.clientX
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
isCaptureLate: true,
|
|
142
|
+
move: (held, event) => {
|
|
143
|
+
if (!held.isDragging) {
|
|
144
|
+
if (Math.abs(event.clientX - held.x) < held.thresholdPx) return;
|
|
145
|
+
const { from, heading } = this.#where();
|
|
146
|
+
held.isDragging = true;
|
|
147
|
+
held.from = from;
|
|
148
|
+
held.outside = this.cancellable ? outsideCancelZone(control, "--_sonic-ticker-cancel-zone") : void 0;
|
|
149
|
+
held.x = event.clientX;
|
|
150
|
+
capturePointer(control, event.pointerId);
|
|
151
|
+
this.toggleState("dragging", true);
|
|
152
|
+
this.#stop();
|
|
153
|
+
this.#heading = heading;
|
|
154
|
+
}
|
|
155
|
+
const isOutside = held.outside?.(event) === true;
|
|
156
|
+
this.toggleState("cancelling", isOutside);
|
|
157
|
+
this.#place(tape, isOutside ? held.from : held.from - (event.clientX - held.x));
|
|
158
|
+
},
|
|
159
|
+
release: (held) => {
|
|
160
|
+
if (!held.isDragging) return;
|
|
161
|
+
this.#isClickSwallowed = true;
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
this.#isClickSwallowed = false;
|
|
164
|
+
}, 0);
|
|
165
|
+
this.toggleState("dragging", false);
|
|
166
|
+
this.toggleState("cancelling", false);
|
|
167
|
+
this.#run(tape);
|
|
168
|
+
},
|
|
169
|
+
toggle: (isPressed) => {
|
|
170
|
+
this.#isPressed = isPressed;
|
|
171
|
+
this.#syncPause();
|
|
172
|
+
}
|
|
173
|
+
}, signal);
|
|
174
|
+
}
|
|
175
|
+
#bindFocus(tape, signal) {
|
|
176
|
+
const control = this.control;
|
|
177
|
+
control.addEventListener("focusout", (event) => {
|
|
178
|
+
if (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;
|
|
179
|
+
control.scrollLeft = 0;
|
|
180
|
+
this.#isFocusInside = false;
|
|
181
|
+
this.#run(tape);
|
|
182
|
+
}, { signal });
|
|
183
|
+
control.addEventListener("focusin", (event) => {
|
|
184
|
+
this.#isFocusInside = true;
|
|
185
|
+
this.#run(tape);
|
|
186
|
+
if (event.target instanceof Element) event.target.scrollIntoView({ block: "nearest" });
|
|
187
|
+
}, { signal });
|
|
188
|
+
}
|
|
189
|
+
#bindPauses(signal) {
|
|
190
|
+
const control = this.control;
|
|
191
|
+
for (const type of ["pointerenter", "pointerleave"]) control.addEventListener(type, (event) => {
|
|
192
|
+
if (event.pointerType !== "mouse") return;
|
|
193
|
+
this.#isHovered = type === "pointerenter";
|
|
194
|
+
this.#syncPause();
|
|
195
|
+
}, { signal });
|
|
196
|
+
for (const type of ["focusin", "focusout"]) this.getRootNode().addEventListener(type, () => {
|
|
197
|
+
this.#syncPause();
|
|
198
|
+
}, { signal });
|
|
199
|
+
const view = new IntersectionObserver((entries) => {
|
|
200
|
+
this.#isOffScreen = entries.at(-1)?.isIntersecting === false;
|
|
201
|
+
this.#syncPause();
|
|
202
|
+
});
|
|
203
|
+
view.observe(control);
|
|
204
|
+
signal.addEventListener("abort", () => {
|
|
205
|
+
view.disconnect();
|
|
206
|
+
}, { once: true });
|
|
207
|
+
}
|
|
208
|
+
#holds(text) {
|
|
209
|
+
const [only, ...rest] = this.#originals();
|
|
210
|
+
if (only === void 0) return text === "";
|
|
211
|
+
if (rest.length > 0 || !(only instanceof Text)) return false;
|
|
212
|
+
return only.data === text;
|
|
213
|
+
}
|
|
214
|
+
#isPaused() {
|
|
215
|
+
return this.paused || this.#isHovered || this.#isPressed || this.#isOffScreen || this.control.closest(":focus-visible") !== null;
|
|
216
|
+
}
|
|
217
|
+
#measure(tape, departure) {
|
|
218
|
+
const control = this.control;
|
|
219
|
+
const styles = getComputedStyle(control);
|
|
220
|
+
const box = control.clientWidth - readPxProperty(styles, "padding-left", 0) - readPxProperty(styles, "padding-right", 0);
|
|
221
|
+
const travel = {
|
|
222
|
+
distance: Math.max(0, Math.round(tape.offsetWidth - box)),
|
|
223
|
+
hold: readMsProperty(styles, "--_sonic-ticker-hold", holdMs),
|
|
224
|
+
rate: readPxProperty(styles, "--_sonic-ticker-rate", ratePxPerSecond)
|
|
225
|
+
};
|
|
226
|
+
const fade = readPxProperty(styles, "--_sonic-ticker-fade", fadePx);
|
|
227
|
+
const measured = [...Object.values(travel), fade].join(" ");
|
|
228
|
+
if (!departure && measured === this.#measured) return;
|
|
229
|
+
const from = departure ?? this.#where();
|
|
230
|
+
this.#fade = fade;
|
|
231
|
+
this.#measured = measured;
|
|
232
|
+
this.#travel = travel;
|
|
233
|
+
this.toggleState("overflowing", travel.distance > 0);
|
|
234
|
+
this.#run(tape, from);
|
|
235
|
+
}
|
|
236
|
+
#originals() {
|
|
237
|
+
return [...this.childNodes].filter((child) => child !== this.control);
|
|
238
|
+
}
|
|
239
|
+
#place(tape, position) {
|
|
240
|
+
const { distance } = this.#travel;
|
|
241
|
+
this.#position = clamp(position, 0, distance);
|
|
242
|
+
tape.style.translate = this.#position > 0 ? `${String(-this.#position)}px` : "";
|
|
243
|
+
writeAttribute(this.control, "data-sonic-clipped", clippedEnds(this.#position, distance));
|
|
244
|
+
this.#writeFades(this.#position);
|
|
245
|
+
}
|
|
246
|
+
#run(tape, departure = this.#where()) {
|
|
247
|
+
const leg = !this.#isFocusInside && !this.#reducedMotion.matches && !this.hasState("dragging") && !this.isDisabled() ? tickerLeg(this.#travel, departure) : void 0;
|
|
248
|
+
this.#stop();
|
|
249
|
+
this.#heading = departure.heading;
|
|
250
|
+
if (leg) this.#start(tape, leg);
|
|
251
|
+
else this.#place(tape, this.#isFocusInside ? 0 : departure.from);
|
|
252
|
+
}
|
|
253
|
+
#start(tape, leg) {
|
|
254
|
+
const animation = tape.animate({ translate: [`${String(-leg.from)}px`, `${String(-leg.to)}px`] }, {
|
|
255
|
+
delay: leg.hold,
|
|
256
|
+
duration: leg.duration,
|
|
257
|
+
fill: "both"
|
|
258
|
+
});
|
|
259
|
+
animation.addEventListener("finish", () => {
|
|
260
|
+
this.#run(tape, {
|
|
261
|
+
from: leg.to,
|
|
262
|
+
heading: leg.heading
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
this.#running = {
|
|
266
|
+
animation,
|
|
267
|
+
fades: [],
|
|
268
|
+
leg
|
|
269
|
+
};
|
|
270
|
+
tape.style.translate = "";
|
|
271
|
+
writeAttribute(this.control, "data-sonic-clipped", clippedEnds(leg.from, this.#travel.distance));
|
|
272
|
+
this.#syncPause();
|
|
273
|
+
}
|
|
274
|
+
#startFades(leg, elapsed) {
|
|
275
|
+
const fades = legFades(leg, this.#travel.distance, this.#fade);
|
|
276
|
+
return ["start", "end"].flatMap((end) => {
|
|
277
|
+
const { delay, duration, widths } = fades[end];
|
|
278
|
+
if (duration <= 0 || delay + duration <= elapsed) return [];
|
|
279
|
+
return this.control.animate({ [`--_sonic-ticker-fade-${end}`]: widths.map((width) => `${String(width)}px`) }, {
|
|
280
|
+
delay: delay - elapsed,
|
|
281
|
+
duration,
|
|
282
|
+
fill: "both"
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
#stop() {
|
|
287
|
+
const running = this.#running;
|
|
288
|
+
clearTimeout(this.#mark);
|
|
289
|
+
if (!running) return;
|
|
290
|
+
for (const fade of running.fades) fade.cancel();
|
|
291
|
+
running.animation.cancel();
|
|
292
|
+
this.#running = void 0;
|
|
293
|
+
}
|
|
294
|
+
#syncPause() {
|
|
295
|
+
const running = this.#running;
|
|
296
|
+
if (!running) return;
|
|
297
|
+
const { animation, leg } = running;
|
|
298
|
+
const elapsed = Number(animation.currentTime);
|
|
299
|
+
clearTimeout(this.#mark);
|
|
300
|
+
for (const fade of running.fades) fade.cancel();
|
|
301
|
+
running.fades = [];
|
|
302
|
+
this.#writeFades(legPosition(leg, elapsed));
|
|
303
|
+
if (elapsed >= leg.hold) writeAttribute(this.control, "data-sonic-clipped", "start end");
|
|
304
|
+
if (this.#isPaused()) {
|
|
305
|
+
animation.pause();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (animation.playState === "paused") animation.play();
|
|
309
|
+
running.fades = this.#startFades(leg, elapsed);
|
|
310
|
+
if (elapsed >= leg.hold) return;
|
|
311
|
+
this.#mark = setTimeout(() => {
|
|
312
|
+
writeAttribute(this.control, "data-sonic-clipped", "start end");
|
|
313
|
+
}, leg.hold - elapsed);
|
|
314
|
+
}
|
|
315
|
+
#where() {
|
|
316
|
+
const running = this.#running;
|
|
317
|
+
if (!running) return {
|
|
318
|
+
from: this.#position,
|
|
319
|
+
heading: this.#heading
|
|
320
|
+
};
|
|
321
|
+
return legDeparture(running.leg, Number(running.animation.currentTime));
|
|
322
|
+
}
|
|
323
|
+
#writeFades(position) {
|
|
324
|
+
const { style } = this.control;
|
|
325
|
+
style.setProperty("--_sonic-ticker-fade-start", `${String(fadeWidth(position, this.#fade))}px`);
|
|
326
|
+
style.setProperty("--_sonic-ticker-fade-end", `${String(fadeWidth(this.#travel.distance - position, this.#fade))}px`);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
//#endregion
|
|
331
|
+
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() {}
|