@xsynaptic/sonic-ui 0.24.1 → 0.26.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 +42 -2
- package/README.md +6 -4
- package/dist/default/define/region.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +3 -0
- package/dist/default/elements/dial.js +3 -0
- package/dist/default/elements/envelope.js +14 -11
- package/dist/default/elements/form-element.js +10 -0
- package/dist/default/elements/meter.js +3 -0
- package/dist/default/elements/number.js +3 -0
- package/dist/default/elements/radio-group.js +4 -1
- package/dist/default/elements/readout.js +1 -1
- package/dist/default/elements/region-layer.js +253 -0
- package/dist/default/elements/region.js +161 -0
- package/dist/default/elements/slider.js +3 -0
- package/dist/default/elements/sonic-element.js +7 -9
- package/dist/default/elements/split.js +2 -2
- package/dist/default/elements/toggle.js +4 -4
- package/dist/default/elements/value-element.js +21 -3
- package/dist/default/elements/value-gestures.js +7 -5
- package/dist/default/elements/wave-element.js +15 -3
- package/dist/default/elements/waveform.js +38 -2
- package/dist/default/elements/wavestrip.js +90 -17
- package/dist/default/elements/xy.js +6 -11
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +22 -1
- package/dist/default/lib/clock.js +1 -0
- package/dist/default/lib/drag-step.js +1 -1
- package/dist/default/lib/key-press.js +2 -2
- package/dist/default/lib/marker-band.js +40 -29
- package/dist/default/lib/region-drag.js +48 -0
- package/dist/dev/define/region.d.ts +2 -0
- package/dist/dev/define/region.js +8 -0
- package/dist/dev/define/region.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.d.ts +1 -0
- package/dist/dev/elements/button.js +3 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +1 -0
- package/dist/dev/elements/dial.js +3 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +1 -0
- package/dist/dev/elements/envelope.js +14 -11
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/form-element.d.ts +1 -0
- package/dist/dev/elements/form-element.js +10 -0
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +1 -0
- package/dist/dev/elements/meter.js +3 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +1 -0
- package/dist/dev/elements/number.js +3 -0
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +1 -0
- package/dist/dev/elements/radio-group.js +4 -1
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout.js +1 -1
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/region-layer.js +254 -0
- package/dist/dev/elements/region-layer.js.map +1 -0
- package/dist/dev/elements/region.d.ts +30 -0
- package/dist/dev/elements/region.js +169 -0
- package/dist/dev/elements/region.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +1 -0
- package/dist/dev/elements/slider.js +3 -0
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +2 -0
- package/dist/dev/elements/sonic-element.js +8 -10
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js +2 -2
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/toggle.js +4 -4
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +3 -1
- package/dist/dev/elements/value-element.js +21 -3
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +7 -5
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +8 -1
- package/dist/dev/elements/wave-element.js +15 -3
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +7 -0
- package/dist/dev/elements/waveform.js +38 -2
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +17 -1
- package/dist/dev/elements/wavestrip.js +90 -17
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +1 -0
- package/dist/dev/elements/xy.js +6 -11
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/canvas-surface.js +22 -1
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/clock.js +1 -0
- package/dist/dev/lib/clock.js.map +1 -1
- package/dist/dev/lib/drag-step.js +1 -1
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/key-press.js +2 -2
- package/dist/dev/lib/key-press.js.map +1 -1
- package/dist/dev/lib/marker-band.d.ts +7 -0
- package/dist/dev/lib/marker-band.js +40 -29
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/region-drag.js +49 -0
- package/dist/dev/lib/region-drag.js.map +1 -0
- package/dist/styles/button.css +23 -13
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +41 -24
- package/dist/styles/envelope.css +12 -5
- package/dist/styles/led.css +8 -8
- package/dist/styles/material/arc.css +2 -2
- package/dist/styles/material/cap.css +34 -13
- package/dist/styles/material/glass.css +1 -1
- package/dist/styles/material/groove.css +3 -3
- package/dist/styles/material/keycap.css +2 -2
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +20 -6
- package/dist/styles/material/readout.css +14 -5
- package/dist/styles/material/{bracket.css → reticle.css} +15 -8
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/material.css +1 -1
- package/dist/styles/meter.css +29 -16
- package/dist/styles/number.css +3 -2
- package/dist/styles/panel.css +3 -3
- package/dist/styles/region.css +94 -0
- package/dist/styles/ring.css +2 -1
- package/dist/styles/screen.css +7 -5
- package/dist/styles/segmented.css +5 -4
- package/dist/styles/slider.css +24 -15
- package/dist/styles/spectrum.css +9 -1
- package/dist/styles/switch.css +9 -2
- package/dist/styles/toggle.css +5 -4
- package/dist/styles/waveform.css +10 -3
- package/dist/styles/wavestrip.css +17 -8
- package/dist/styles/xy.css +24 -11
- package/package.json +3 -2
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
2
|
+
import { placeChildren, template } from "../lib/render.js";
|
|
3
|
+
import { capturePointer } from "../lib/pointer-drag.js";
|
|
4
|
+
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
5
|
+
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
6
|
+
import { SonicRegion, linkRegion, ownRegions } from "./region.js";
|
|
7
|
+
import { writeKind } from "../lib/marker-band.js";
|
|
8
|
+
import { moveRegionTo, regionAt, regionTravel, startRegionDrag, stepRegionDrag } from "../lib/region-drag.js";
|
|
9
|
+
|
|
10
|
+
//#region src/elements/region-layer.ts
|
|
11
|
+
const renderPart = template(`
|
|
12
|
+
<div class="sonic-region" role="slider" aria-orientation="horizontal">
|
|
13
|
+
<div class="sonic-region-bracket"></div>
|
|
14
|
+
</div>
|
|
15
|
+
`, HTMLDivElement);
|
|
16
|
+
function hasMoved(from, to) {
|
|
17
|
+
return from.start !== to.start || from.end !== to.end;
|
|
18
|
+
}
|
|
19
|
+
function clippedEnds(region, [low, high]) {
|
|
20
|
+
const ends = [...Math.min(region.start, region.end) < low ? ["start"] : [], ...Math.max(region.start, region.end) > high ? ["end"] : []];
|
|
21
|
+
return ends.length > 0 ? ends.join(" ") : void 0;
|
|
22
|
+
}
|
|
23
|
+
function writeProperty(part, name, value) {
|
|
24
|
+
if (part.style.getPropertyValue(name) !== value) part.style.setProperty(name, value);
|
|
25
|
+
}
|
|
26
|
+
var RegionLayer = class {
|
|
27
|
+
#drawn = [];
|
|
28
|
+
#held;
|
|
29
|
+
#host;
|
|
30
|
+
#key;
|
|
31
|
+
#keyScrub;
|
|
32
|
+
constructor(host, signal) {
|
|
33
|
+
const render = () => {
|
|
34
|
+
this.render();
|
|
35
|
+
};
|
|
36
|
+
const observer = new MutationObserver(render);
|
|
37
|
+
this.#host = host;
|
|
38
|
+
this.#bindPointer(signal);
|
|
39
|
+
this.#bindKeys(signal);
|
|
40
|
+
observer.observe(host.element, { childList: true });
|
|
41
|
+
ownRegions(host.element, (region) => {
|
|
42
|
+
this.#redraw(region);
|
|
43
|
+
}, signal);
|
|
44
|
+
signal.addEventListener("abort", () => {
|
|
45
|
+
observer.disconnect();
|
|
46
|
+
this.#release();
|
|
47
|
+
this.#endKeyScrub();
|
|
48
|
+
}, { once: true });
|
|
49
|
+
}
|
|
50
|
+
abandon() {
|
|
51
|
+
this.#release(true);
|
|
52
|
+
}
|
|
53
|
+
follow() {
|
|
54
|
+
if (this.#drawn.length === 0) return;
|
|
55
|
+
if (this.#stripKey() !== this.#key) this.render();
|
|
56
|
+
}
|
|
57
|
+
isHeld() {
|
|
58
|
+
return this.#held !== void 0;
|
|
59
|
+
}
|
|
60
|
+
render() {
|
|
61
|
+
const host = this.#host;
|
|
62
|
+
const drawn = [...host.element.children].filter((child) => child instanceof SonicRegion).map((region) => this.#draw(region));
|
|
63
|
+
this.#drawn = drawn;
|
|
64
|
+
this.#key = this.#stripKey();
|
|
65
|
+
placeChildren(host.layer, drawn.map(({ part }) => part));
|
|
66
|
+
this.#dropGone();
|
|
67
|
+
}
|
|
68
|
+
take(event) {
|
|
69
|
+
const host = this.#host;
|
|
70
|
+
const { startPx, travelPx } = host.axis();
|
|
71
|
+
const mapping = host.mapping();
|
|
72
|
+
const index = regionAt(this.#drawn.map((entry) => {
|
|
73
|
+
if (!this.#isEnabled(entry)) return;
|
|
74
|
+
const { end, start } = entry.link.span();
|
|
75
|
+
return [mapping.proportionOf(start) * travelPx, mapping.proportionOf(end) * travelPx];
|
|
76
|
+
}), event.clientX - startPx);
|
|
77
|
+
const pressed = index === void 0 ? void 0 : this.#drawn[index];
|
|
78
|
+
if (!pressed) return false;
|
|
79
|
+
this.#endKeyScrub();
|
|
80
|
+
const from = pressed.link.span();
|
|
81
|
+
this.#held = {
|
|
82
|
+
drawn: pressed,
|
|
83
|
+
from,
|
|
84
|
+
outside: host.outside(),
|
|
85
|
+
pointerId: event.pointerId,
|
|
86
|
+
press: event,
|
|
87
|
+
state: startRegionDrag({
|
|
88
|
+
from,
|
|
89
|
+
position: event.clientX,
|
|
90
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
91
|
+
widthPx: travelPx
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
pressed.link.hold("pointer");
|
|
95
|
+
capturePointer(host.control, event.pointerId);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
#bindKeys(signal) {
|
|
99
|
+
const { layer } = this.#host;
|
|
100
|
+
layer.addEventListener("keydown", (event) => {
|
|
101
|
+
const entry = this.#drawn.find(({ part }) => part === event.target);
|
|
102
|
+
if (!entry || event.defaultPrevented || this.#held) return;
|
|
103
|
+
if (this.#isEnabled(entry)) this.#keyTo(entry, event);
|
|
104
|
+
}, { signal });
|
|
105
|
+
layer.addEventListener("keyup", (event) => {
|
|
106
|
+
if (event.key === "Meta" || event.key === this.#keyScrub?.key) this.#endKeyScrub();
|
|
107
|
+
}, { signal });
|
|
108
|
+
layer.addEventListener("focusin", () => {
|
|
109
|
+
this.render();
|
|
110
|
+
}, { signal });
|
|
111
|
+
layer.addEventListener("focusout", () => {
|
|
112
|
+
this.#endKeyScrub();
|
|
113
|
+
}, { signal });
|
|
114
|
+
}
|
|
115
|
+
#bindPointer(signal) {
|
|
116
|
+
const whileHeld = (type, handle) => {
|
|
117
|
+
this.#host.control.addEventListener(type, (event) => {
|
|
118
|
+
const held = this.#held;
|
|
119
|
+
if (held && event instanceof PointerEvent && held.pointerId === event.pointerId) handle(held, event);
|
|
120
|
+
}, { signal });
|
|
121
|
+
};
|
|
122
|
+
whileHeld("pointermove", (hold, event) => {
|
|
123
|
+
this.#dragTo(hold, event);
|
|
124
|
+
});
|
|
125
|
+
whileHeld("pointerup", (hold) => {
|
|
126
|
+
const isPress = !hold.state.isEngaged;
|
|
127
|
+
this.#release();
|
|
128
|
+
if (isPress) this.#host.seek(hold.press);
|
|
129
|
+
});
|
|
130
|
+
whileHeld("pointercancel", () => {
|
|
131
|
+
this.#release(true);
|
|
132
|
+
});
|
|
133
|
+
whileHeld("lostpointercapture", () => {
|
|
134
|
+
this.#release();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
#dragTo(hold, event) {
|
|
138
|
+
const wasEngaged = hold.state.isEngaged;
|
|
139
|
+
const { span, state } = stepRegionDrag(this.#host.mapping(), hold.state, {
|
|
140
|
+
isFine: event.shiftKey,
|
|
141
|
+
position: event.clientX
|
|
142
|
+
});
|
|
143
|
+
hold.state = state;
|
|
144
|
+
if (!span) return;
|
|
145
|
+
if (!wasEngaged) focusByPointer(hold.drawn.part);
|
|
146
|
+
hold.drawn.link.input(hold.outside?.(event) === true ? hold.from : span);
|
|
147
|
+
}
|
|
148
|
+
#draw(region) {
|
|
149
|
+
const host = this.#host;
|
|
150
|
+
const known = this.#drawn.find((entry) => entry.region === region);
|
|
151
|
+
const entry = known ?? {
|
|
152
|
+
kind: void 0,
|
|
153
|
+
link: linkRegion(region),
|
|
154
|
+
part: renderPart(),
|
|
155
|
+
region
|
|
156
|
+
};
|
|
157
|
+
const { link, part } = entry;
|
|
158
|
+
const mapping = host.mapping();
|
|
159
|
+
const span = link.span();
|
|
160
|
+
const [low, high] = regionTravel(span, mapping.bounds);
|
|
161
|
+
const canMove = this.#isEnabled(entry);
|
|
162
|
+
const stop = region.getAttribute("tabindex") === "-1" ? "-1" : "0";
|
|
163
|
+
writeProperty(part, "--_sonic-region-from", String(mapping.proportionOf(span.start)));
|
|
164
|
+
writeProperty(part, "--_sonic-region-to", String(mapping.proportionOf(span.end)));
|
|
165
|
+
if (!known || entry.kind !== region.kind) {
|
|
166
|
+
entry.kind = region.kind;
|
|
167
|
+
writeKind(part, region.kind, "--_sonic-ink");
|
|
168
|
+
}
|
|
169
|
+
writeAttribute(part, "data-sonic-clipped", clippedEnds(region, mapping.bounds));
|
|
170
|
+
writeAttribute(part, "aria-valuemin", String(low));
|
|
171
|
+
writeAttribute(part, "aria-valuemax", String(high));
|
|
172
|
+
writeAttribute(part, "aria-valuenow", String(span.start));
|
|
173
|
+
writeAttribute(part, "aria-valuetext", host.speak(span));
|
|
174
|
+
writeAttribute(part, "aria-disabled", canMove ? void 0 : "true");
|
|
175
|
+
writeAttribute(part, "tabindex", canMove ? stop : void 0);
|
|
176
|
+
link.draw(part);
|
|
177
|
+
return entry;
|
|
178
|
+
}
|
|
179
|
+
#dropGone() {
|
|
180
|
+
const isGone = (entry) => !this.#drawn.includes(entry) || !this.#isEnabled(entry);
|
|
181
|
+
if (this.#held && isGone(this.#held.drawn)) this.#release();
|
|
182
|
+
if (this.#keyScrub && isGone(this.#keyScrub.drawn)) this.#endKeyScrub();
|
|
183
|
+
}
|
|
184
|
+
#endKeyScrub() {
|
|
185
|
+
const scrub = this.#keyScrub;
|
|
186
|
+
if (!scrub) return;
|
|
187
|
+
const { link } = scrub.drawn;
|
|
188
|
+
this.#keyScrub = void 0;
|
|
189
|
+
link.hold(void 0);
|
|
190
|
+
if (hasMoved(scrub.from, link.span())) link.change();
|
|
191
|
+
}
|
|
192
|
+
#isEnabled({ link }) {
|
|
193
|
+
return !this.#host.isDisabled() && !link.isDisabled();
|
|
194
|
+
}
|
|
195
|
+
#keyTo(entry, event) {
|
|
196
|
+
const mapping = this.#host.mapping();
|
|
197
|
+
const from = entry.link.span();
|
|
198
|
+
const start = mapping.keyTarget(event.key, from.start);
|
|
199
|
+
if (start === void 0) return;
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
const next = moveRegionTo(mapping, from, start);
|
|
202
|
+
if (!event.repeat) {
|
|
203
|
+
this.#endKeyScrub();
|
|
204
|
+
if (entry.link.input(next)) entry.link.change();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (this.#keyScrub?.drawn !== entry || this.#keyScrub.key !== event.key) {
|
|
208
|
+
this.#endKeyScrub();
|
|
209
|
+
this.#keyScrub = {
|
|
210
|
+
drawn: entry,
|
|
211
|
+
from,
|
|
212
|
+
key: event.key
|
|
213
|
+
};
|
|
214
|
+
entry.link.hold("keys");
|
|
215
|
+
}
|
|
216
|
+
entry.link.input(next);
|
|
217
|
+
}
|
|
218
|
+
#redraw(region) {
|
|
219
|
+
if (this.#drawn.every((entry) => entry.region !== region)) {
|
|
220
|
+
this.render();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.#draw(region);
|
|
224
|
+
this.#dropGone();
|
|
225
|
+
}
|
|
226
|
+
#release(isRestored = false) {
|
|
227
|
+
const hold = this.#held;
|
|
228
|
+
if (!hold) return;
|
|
229
|
+
const { link } = hold.drawn;
|
|
230
|
+
this.#held = void 0;
|
|
231
|
+
if (isRestored) link.input(hold.from);
|
|
232
|
+
link.hold(void 0);
|
|
233
|
+
if (hasMoved(hold.from, link.span())) link.change();
|
|
234
|
+
}
|
|
235
|
+
#stripKey() {
|
|
236
|
+
const host = this.#host;
|
|
237
|
+
const mapping = host.mapping();
|
|
238
|
+
const [low, high] = mapping.bounds;
|
|
239
|
+
return [
|
|
240
|
+
low,
|
|
241
|
+
high,
|
|
242
|
+
mapping.proportionOf((low + high) / 2),
|
|
243
|
+
host.isDisabled(),
|
|
244
|
+
host.speak({
|
|
245
|
+
end: high,
|
|
246
|
+
start: high
|
|
247
|
+
})
|
|
248
|
+
].join("|");
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
export { RegionLayer };
|
|
254
|
+
//# sourceMappingURL=region-layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"region-layer.js","names":[],"sources":["../../../src/elements/region-layer.ts"],"sourcesContent":["import type { RegionLink } from '#elements/region.ts';\nimport type { RegionDragState, RegionSpan } from '#lib/region-drag.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { linkRegion, ownRegions, SonicRegion } from '#elements/region.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { writeKind } from '#lib/marker-band.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport {\n\tmoveRegionTo,\n\tregionAt,\n\tregionTravel,\n\tstartRegionDrag,\n\tstepRegionDrag,\n} from '#lib/region-drag.ts';\nimport { placeChildren, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ninterface LayerHost {\n\taxis: () => { startPx: number; travelPx: number };\n\tcontrol: HTMLElement;\n\telement: HTMLElement;\n\tisDisabled: () => boolean;\n\tlayer: HTMLElement;\n\tmapping: () => ValueMapping;\n\toutside: () => ((event: PointerEvent) => boolean) | undefined;\n\tseek: (event: PointerEvent) => void;\n\tspeak: (span: RegionSpan) => string;\n}\n\ninterface Drawn {\n\tkind: string | undefined;\n\tlink: RegionLink;\n\tpart: HTMLElement;\n\tregion: SonicRegion;\n}\n\ninterface KeyScrub {\n\tdrawn: Drawn;\n\tfrom: RegionSpan;\n\tkey: string;\n}\n\ninterface PointerHold {\n\tdrawn: Drawn;\n\tfrom: RegionSpan;\n\toutside: ((event: PointerEvent) => boolean) | undefined;\n\tpointerId: number;\n\tpress: PointerEvent;\n\tstate: RegionDragState;\n}\n\nconst renderPart = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-region\" role=\"slider\" aria-orientation=\"horizontal\">\n\t\t\t<div class=\"sonic-region-bracket\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction hasMoved(from: RegionSpan, to: RegionSpan): boolean {\n\treturn from.start !== to.start || from.end !== to.end;\n}\n\nfunction clippedEnds(region: SonicRegion, [low, high]: [number, number]): string | undefined {\n\tconst ends = [\n\t\t...(Math.min(region.start, region.end) < low ? ['start'] : []),\n\t\t...(Math.max(region.start, region.end) > high ? ['end'] : []),\n\t];\n\n\treturn ends.length > 0 ? ends.join(' ') : undefined;\n}\n\nfunction writeProperty(part: HTMLElement, name: string, value: string): void {\n\tif (part.style.getPropertyValue(name) !== value) part.style.setProperty(name, value);\n}\n\nexport class RegionLayer {\n\t#drawn: Array<Drawn> = [];\n\n\t#held: PointerHold | undefined;\n\n\treadonly #host: LayerHost;\n\n\t#key: string | undefined;\n\n\t#keyScrub: KeyScrub | undefined;\n\n\tconstructor(host: LayerHost, signal: AbortSignal) {\n\t\tconst render = (): void => {\n\t\t\tthis.render();\n\t\t};\n\t\tconst observer = new MutationObserver(render);\n\n\t\tthis.#host = host;\n\t\tthis.#bindPointer(signal);\n\t\tthis.#bindKeys(signal);\n\t\tobserver.observe(host.element, { childList: true });\n\t\townRegions(\n\t\t\thost.element,\n\t\t\t(region) => {\n\t\t\t\tthis.#redraw(region);\n\t\t\t},\n\t\t\tsignal,\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.#release();\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tabandon(): void {\n\t\tthis.#release(true);\n\t}\n\n\tfollow(): void {\n\t\tif (this.#drawn.length === 0) return;\n\t\tif (this.#stripKey() !== this.#key) this.render();\n\t}\n\n\tisHeld(): boolean {\n\t\treturn this.#held !== undefined;\n\t}\n\n\trender(): void {\n\t\tconst host = this.#host;\n\t\tconst regions = [...host.element.children].filter((child) => child instanceof SonicRegion);\n\t\tconst drawn = regions.map((region) => this.#draw(region));\n\n\t\tthis.#drawn = drawn;\n\t\tthis.#key = this.#stripKey();\n\t\tplaceChildren(\n\t\t\thost.layer,\n\t\t\tdrawn.map(({ part }) => part),\n\t\t);\n\t\tthis.#dropGone();\n\t}\n\n\ttake(event: PointerEvent): boolean {\n\t\tconst host = this.#host;\n\t\tconst { startPx, travelPx } = host.axis();\n\t\tconst mapping = host.mapping();\n\t\tconst index = regionAt(\n\t\t\tthis.#drawn.map((entry) => {\n\t\t\t\tif (!this.#isEnabled(entry)) return;\n\n\t\t\t\tconst { end, start } = entry.link.span();\n\n\t\t\t\treturn [mapping.proportionOf(start) * travelPx, mapping.proportionOf(end) * travelPx];\n\t\t\t}),\n\t\t\tevent.clientX - startPx,\n\t\t);\n\t\tconst pressed = index === undefined ? undefined : this.#drawn[index];\n\t\tif (!pressed) return false;\n\n\t\tthis.#endKeyScrub();\n\n\t\tconst from = pressed.link.span();\n\n\t\tthis.#held = {\n\t\t\tdrawn: pressed,\n\t\t\tfrom,\n\t\t\toutside: host.outside(),\n\t\t\tpointerId: event.pointerId,\n\t\t\tpress: event,\n\t\t\tstate: startRegionDrag({\n\t\t\t\tfrom,\n\t\t\t\tposition: event.clientX,\n\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\twidthPx: travelPx,\n\t\t\t}),\n\t\t};\n\t\tpressed.link.hold('pointer');\n\t\tcapturePointer(host.control, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\t#bindKeys(signal: AbortSignal): void {\n\t\tconst { layer } = this.#host;\n\n\t\tlayer.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tconst entry = this.#drawn.find(({ part }) => part === event.target);\n\t\t\t\tif (!entry || event.defaultPrevented || this.#held) return;\n\n\t\t\t\tif (this.#isEnabled(entry)) this.#keyTo(entry, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tlayer.addEventListener(\n\t\t\t'keyup',\n\t\t\t(event) => {\n\t\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\t\tif (event.key === 'Meta' || event.key === this.#keyScrub?.key) this.#endKeyScrub();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// No event fires when a label arrives, so focus picks it up\n\t\tlayer.addEventListener(\n\t\t\t'focusin',\n\t\t\t() => {\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tlayer.addEventListener(\n\t\t\t'focusout',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#bindPointer(signal: AbortSignal): void {\n\t\tconst whileHeld = (\n\t\t\ttype: string,\n\t\t\thandle: (hold: PointerHold, event: PointerEvent) => void,\n\t\t): void => {\n\t\t\tthis.#host.control.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tconst held = this.#held;\n\n\t\t\t\t\tif (held && event instanceof PointerEvent && held.pointerId === event.pointerId) {\n\t\t\t\t\t\thandle(held, event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t};\n\n\t\twhileHeld('pointermove', (hold, event) => {\n\t\t\tthis.#dragTo(hold, event);\n\t\t});\n\t\twhileHeld('pointerup', (hold) => {\n\t\t\tconst isPress = !hold.state.isEngaged;\n\n\t\t\tthis.#release();\n\t\t\tif (isPress) this.#host.seek(hold.press);\n\t\t});\n\t\twhileHeld('pointercancel', () => {\n\t\t\tthis.#release(true);\n\t\t});\n\t\twhileHeld('lostpointercapture', () => {\n\t\t\tthis.#release();\n\t\t});\n\t}\n\n\t#dragTo(hold: PointerHold, event: PointerEvent): void {\n\t\tconst wasEngaged = hold.state.isEngaged;\n\t\tconst { span, state } = stepRegionDrag(this.#host.mapping(), hold.state, {\n\t\t\tisFine: event.shiftKey,\n\t\t\tposition: event.clientX,\n\t\t});\n\n\t\thold.state = state;\n\t\tif (!span) return;\n\n\t\tif (!wasEngaged) focusByPointer(hold.drawn.part);\n\t\thold.drawn.link.input(hold.outside?.(event) === true ? hold.from : span);\n\t}\n\n\t#draw(region: SonicRegion): Drawn {\n\t\tconst host = this.#host;\n\t\tconst known = this.#drawn.find((entry) => entry.region === region);\n\t\tconst entry = known ?? {\n\t\t\tkind: undefined,\n\t\t\tlink: linkRegion(region),\n\t\t\tpart: renderPart(),\n\t\t\tregion,\n\t\t};\n\t\tconst { link, part } = entry;\n\t\tconst mapping = host.mapping();\n\t\tconst span = link.span();\n\t\tconst [low, high] = regionTravel(span, mapping.bounds);\n\t\tconst canMove = this.#isEnabled(entry);\n\t\tconst stop = region.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteProperty(part, '--_sonic-region-from', String(mapping.proportionOf(span.start)));\n\t\twriteProperty(part, '--_sonic-region-to', String(mapping.proportionOf(span.end)));\n\t\tif (!known || entry.kind !== region.kind) {\n\t\t\tentry.kind = region.kind;\n\t\t\twriteKind(part, region.kind, '--_sonic-ink');\n\t\t}\n\t\twriteAttribute(part, 'data-sonic-clipped', clippedEnds(region, mapping.bounds));\n\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\twriteAttribute(part, 'aria-valuenow', String(span.start));\n\t\twriteAttribute(part, 'aria-valuetext', host.speak(span));\n\t\twriteAttribute(part, 'aria-disabled', canMove ? undefined : 'true');\n\t\twriteAttribute(part, 'tabindex', canMove ? stop : undefined);\n\t\tlink.draw(part);\n\n\t\treturn entry;\n\t}\n\n\t#dropGone(): void {\n\t\tconst isGone = (entry: Drawn): boolean =>\n\t\t\t!this.#drawn.includes(entry) || !this.#isEnabled(entry);\n\n\t\tif (this.#held && isGone(this.#held.drawn)) this.#release();\n\t\tif (this.#keyScrub && isGone(this.#keyScrub.drawn)) this.#endKeyScrub();\n\t}\n\n\t#endKeyScrub(): void {\n\t\tconst scrub = this.#keyScrub;\n\t\tif (!scrub) return;\n\n\t\tconst { link } = scrub.drawn;\n\n\t\tthis.#keyScrub = undefined;\n\t\tlink.hold(undefined);\n\t\tif (hasMoved(scrub.from, link.span())) link.change();\n\t}\n\n\t#isEnabled({ link }: Drawn): boolean {\n\t\treturn !this.#host.isDisabled() && !link.isDisabled();\n\t}\n\n\t#keyTo(entry: Drawn, event: KeyboardEvent): void {\n\t\tconst mapping = this.#host.mapping();\n\t\tconst from = entry.link.span();\n\t\tconst start = mapping.keyTarget(event.key, from.start);\n\t\tif (start === undefined) return;\n\n\t\tevent.preventDefault();\n\n\t\tconst next = moveRegionTo(mapping, from, start);\n\n\t\tif (!event.repeat) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tif (entry.link.input(next)) entry.link.change();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#keyScrub?.drawn !== entry || this.#keyScrub.key !== event.key) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tthis.#keyScrub = { drawn: entry, from, key: event.key };\n\t\t\tentry.link.hold('keys');\n\t\t}\n\t\tentry.link.input(next);\n\t}\n\n\t#redraw(region: SonicRegion): void {\n\t\tif (this.#drawn.every((entry) => entry.region !== region)) {\n\t\t\tthis.render();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#draw(region);\n\t\tthis.#dropGone();\n\t}\n\n\t#release(isRestored = false): void {\n\t\tconst hold = this.#held;\n\t\tif (!hold) return;\n\n\t\tconst { link } = hold.drawn;\n\n\t\tthis.#held = undefined;\n\t\tif (isRestored) link.input(hold.from);\n\t\tlink.hold(undefined);\n\t\tif (hasMoved(hold.from, link.span())) link.change();\n\t}\n\n\t// A changed value formatter is told by what it says of one time\n\t#stripKey(): string {\n\t\tconst host = this.#host;\n\t\tconst mapping = host.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn [\n\t\t\tlow,\n\t\t\thigh,\n\t\t\tmapping.proportionOf((low + high) / 2),\n\t\t\thost.isDisabled(),\n\t\t\thost.speak({ end: high, start: high }),\n\t\t].join('|');\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
2
|
+
import { Seconds } from "../lib/units.js";
|
|
3
|
+
//#region src/elements/region.d.ts
|
|
4
|
+
declare global {
|
|
5
|
+
interface HTMLElementTagNameMap {
|
|
6
|
+
'sonic-region': SonicRegion;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare class SonicRegion extends SonicFormElement {
|
|
10
|
+
#private;
|
|
11
|
+
static readonly observedAttributes: string[];
|
|
12
|
+
get dragging(): boolean;
|
|
13
|
+
get end(): Seconds;
|
|
14
|
+
set end(next: null | Seconds | undefined);
|
|
15
|
+
/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */
|
|
16
|
+
get kind(): string | undefined;
|
|
17
|
+
set kind(kind: string | undefined);
|
|
18
|
+
get start(): Seconds;
|
|
19
|
+
set start(next: null | Seconds | undefined);
|
|
20
|
+
attributeChangedCallback(name: string): void;
|
|
21
|
+
connectedCallback(): void;
|
|
22
|
+
formResetCallback(): void;
|
|
23
|
+
protected connect(): void;
|
|
24
|
+
protected focusTarget(): HTMLElement | undefined;
|
|
25
|
+
protected render(): void;
|
|
26
|
+
protected restoreState(state: string): void;
|
|
27
|
+
protected stateTarget(): HTMLElement | undefined;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//# sourceMappingURL=region.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
2
|
+
|
|
3
|
+
//#region src/elements/region.ts
|
|
4
|
+
const owners = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
const warned = /* @__PURE__ */ new Set();
|
|
6
|
+
let link;
|
|
7
|
+
function linkRegion(element) {
|
|
8
|
+
return link(element);
|
|
9
|
+
}
|
|
10
|
+
function ownRegions(host, changed, signal) {
|
|
11
|
+
owners.set(host, changed);
|
|
12
|
+
signal.addEventListener("abort", () => {
|
|
13
|
+
if (owners.get(host) === changed) owners.delete(host);
|
|
14
|
+
}, { once: true });
|
|
15
|
+
}
|
|
16
|
+
function warnOnce(key, message) {
|
|
17
|
+
if (warned.has(key)) return;
|
|
18
|
+
warned.add(key);
|
|
19
|
+
console.warn(message);
|
|
20
|
+
}
|
|
21
|
+
var SonicRegion = class extends SonicFormElement {
|
|
22
|
+
static observedAttributes = [
|
|
23
|
+
...SonicFormElement.observedAttributes,
|
|
24
|
+
"end",
|
|
25
|
+
"kind",
|
|
26
|
+
"name",
|
|
27
|
+
"start",
|
|
28
|
+
"tabindex"
|
|
29
|
+
];
|
|
30
|
+
static {
|
|
31
|
+
link = (element) => ({
|
|
32
|
+
change: () => {
|
|
33
|
+
element.#report("change");
|
|
34
|
+
},
|
|
35
|
+
draw: (part) => {
|
|
36
|
+
element.#draw(part);
|
|
37
|
+
},
|
|
38
|
+
hold: (by) => {
|
|
39
|
+
element.#held = by;
|
|
40
|
+
element.toggleState("dragging", by === "pointer");
|
|
41
|
+
},
|
|
42
|
+
input: (span) => element.#input(span),
|
|
43
|
+
isDisabled: () => element.isDisabled(),
|
|
44
|
+
span: () => {
|
|
45
|
+
const { end, start } = element.#span;
|
|
46
|
+
return {
|
|
47
|
+
end: Math.max(start, end),
|
|
48
|
+
start: Math.min(start, end)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
get dragging() {
|
|
54
|
+
return this.hasState("dragging");
|
|
55
|
+
}
|
|
56
|
+
get end() {
|
|
57
|
+
return this.#span.end;
|
|
58
|
+
}
|
|
59
|
+
set end(next) {
|
|
60
|
+
if (!this.#held) this.reflect("end", next);
|
|
61
|
+
}
|
|
62
|
+
/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */
|
|
63
|
+
get kind() {
|
|
64
|
+
return this.getAttribute("kind") ?? void 0;
|
|
65
|
+
}
|
|
66
|
+
set kind(kind) {
|
|
67
|
+
this.reflect("kind", kind);
|
|
68
|
+
}
|
|
69
|
+
get start() {
|
|
70
|
+
return this.#span.start;
|
|
71
|
+
}
|
|
72
|
+
set start(next) {
|
|
73
|
+
if (!this.#held) this.reflect("start", next);
|
|
74
|
+
}
|
|
75
|
+
#formState = "";
|
|
76
|
+
#held;
|
|
77
|
+
#part;
|
|
78
|
+
#span = {
|
|
79
|
+
end: 0,
|
|
80
|
+
start: 0
|
|
81
|
+
};
|
|
82
|
+
attributeChangedCallback(name) {
|
|
83
|
+
const isSpan = name === "end" || name === "start";
|
|
84
|
+
if (isSpan && this.#held) return;
|
|
85
|
+
if (isSpan) this.#readSpan(name);
|
|
86
|
+
this.render();
|
|
87
|
+
}
|
|
88
|
+
connectedCallback() {
|
|
89
|
+
this.upgradeProperties("end", "kind", "start");
|
|
90
|
+
super.connectedCallback();
|
|
91
|
+
}
|
|
92
|
+
formResetCallback() {
|
|
93
|
+
if (this.#held) return;
|
|
94
|
+
this.#readSpan("start");
|
|
95
|
+
this.#readSpan("end");
|
|
96
|
+
this.render();
|
|
97
|
+
}
|
|
98
|
+
connect() {
|
|
99
|
+
this.render();
|
|
100
|
+
if (this.parentElement?.localName !== "sonic-wavestrip") warnOnce("parent", "<sonic-region> draws nothing of its own, so it shows only as a child of <sonic-wavestrip>");
|
|
101
|
+
}
|
|
102
|
+
focusTarget() {
|
|
103
|
+
return this.#part;
|
|
104
|
+
}
|
|
105
|
+
render() {
|
|
106
|
+
if (!this.isBound() || !this.parentElement) return;
|
|
107
|
+
owners.get(this.parentElement)?.(this);
|
|
108
|
+
}
|
|
109
|
+
restoreState(state) {
|
|
110
|
+
const [start = NaN, end = NaN] = state.split(",").map(Number);
|
|
111
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return;
|
|
112
|
+
this.#span = {
|
|
113
|
+
end,
|
|
114
|
+
start
|
|
115
|
+
};
|
|
116
|
+
this.render();
|
|
117
|
+
}
|
|
118
|
+
stateTarget() {
|
|
119
|
+
return this.#part;
|
|
120
|
+
}
|
|
121
|
+
#draw(part) {
|
|
122
|
+
this.#part = part;
|
|
123
|
+
this.forwardNaming(part, true);
|
|
124
|
+
this.#writeForm();
|
|
125
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby") && this.namingLabels().length === 0) warnOnce("name", "<sonic-region> is a slider with no name; give it an aria-label or aria-labelledby");
|
|
126
|
+
}
|
|
127
|
+
#input(span) {
|
|
128
|
+
const hasMoved = span.start !== this.#span.start || span.end !== this.#span.end;
|
|
129
|
+
if (hasMoved) {
|
|
130
|
+
this.#span = span;
|
|
131
|
+
this.render();
|
|
132
|
+
this.#report("input");
|
|
133
|
+
}
|
|
134
|
+
return hasMoved;
|
|
135
|
+
}
|
|
136
|
+
#readSpan(name) {
|
|
137
|
+
const { end, start } = this.#span;
|
|
138
|
+
if (name === "end") {
|
|
139
|
+
this.#span = {
|
|
140
|
+
end: this.numberAttribute("end", start),
|
|
141
|
+
start
|
|
142
|
+
};
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const next = this.numberAttribute("start", 0);
|
|
146
|
+
this.#span = {
|
|
147
|
+
end: this.hasAttribute("end") ? end : next,
|
|
148
|
+
start: next
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
#report(type) {
|
|
152
|
+
this.dispatchEvent(new Event(type, {
|
|
153
|
+
bubbles: true,
|
|
154
|
+
composed: type === "input"
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
#writeForm() {
|
|
158
|
+
const { end, start } = this.#span;
|
|
159
|
+
const state = `${String(start)},${String(end)}`;
|
|
160
|
+
const formState = `${this.name}=${state}`;
|
|
161
|
+
if (formState === this.#formState) return;
|
|
162
|
+
this.#formState = formState;
|
|
163
|
+
this.writeFormFields([["start", start], ["end", end]], state);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
export { SonicRegion, linkRegion, ownRegions };
|
|
169
|
+
//# sourceMappingURL=region.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"region.js","names":[],"sources":["../../../src/elements/region.ts"],"sourcesContent":["import type { RegionSpan } from '#lib/region-drag.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-region': SonicRegion;\n\t}\n}\n\ntype RegionHold = 'keys' | 'pointer';\n\nexport interface RegionLink {\n\tchange: () => void;\n\tdraw: (part: HTMLElement) => void;\n\thold: (by: RegionHold | undefined) => void;\n\tinput: (span: RegionSpan) => boolean;\n\tisDisabled: () => boolean;\n\tspan: () => RegionSpan;\n}\n\nconst owners = new WeakMap<Element, (region: SonicRegion) => void>();\n\nconst warned = new Set<string>();\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicRegion) => RegionLink;\n\nexport function linkRegion(element: SonicRegion): RegionLink {\n\treturn link(element);\n}\n\nexport function ownRegions(\n\thost: Element,\n\tchanged: (region: SonicRegion) => void,\n\tsignal: AbortSignal,\n): void {\n\towners.set(host, changed);\n\tsignal.addEventListener(\n\t\t'abort',\n\t\t() => {\n\t\t\tif (owners.get(host) === changed) owners.delete(host);\n\t\t},\n\t\t{ once: true },\n\t);\n}\n\nfunction warnOnce(key: string, message: string): void {\n\tif (!__DEV__ || warned.has(key)) return;\n\n\twarned.add(key);\n\tconsole.warn(message);\n}\n\nexport class SonicRegion extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'end',\n\t\t'kind',\n\t\t'name',\n\t\t'start',\n\t\t'tabindex',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.#report('change');\n\t\t\t},\n\t\t\tdraw: (part) => {\n\t\t\t\telement.#draw(part);\n\t\t\t},\n\t\t\thold: (by) => {\n\t\t\t\telement.#held = by;\n\t\t\t\telement.toggleState('dragging', by === 'pointer');\n\t\t\t},\n\t\t\tinput: (span) => element.#input(span),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tspan: () => {\n\t\t\t\tconst { end, start } = element.#span;\n\n\t\t\t\treturn { end: Math.max(start, end), start: Math.min(start, end) };\n\t\t\t},\n\t\t});\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget end(): Seconds {\n\t\treturn this.#span.end;\n\t}\n\n\tset end(next: null | Seconds | undefined) {\n\t\tif (!this.#held) this.reflect('end', next);\n\t}\n\n\t/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */\n\tget kind(): string | undefined {\n\t\treturn this.getAttribute('kind') ?? undefined;\n\t}\n\n\tset kind(kind: string | undefined) {\n\t\tthis.reflect('kind', kind);\n\t}\n\n\tget start(): Seconds {\n\t\treturn this.#span.start;\n\t}\n\n\tset start(next: null | Seconds | undefined) {\n\t\tif (!this.#held) this.reflect('start', next);\n\t}\n\n\t#formState = '';\n\n\t// A held region owns its values, so a host writing them on every `timeupdate` does not fight the gesture\n\t#held: RegionHold | undefined;\n\n\t#part: HTMLElement | undefined;\n\n\t#span: RegionSpan = { end: 0, start: 0 };\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst isSpan = name === 'end' || name === 'start';\n\n\t\tif (isSpan && this.#held) return;\n\t\tif (isSpan) this.#readSpan(name);\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('end', 'kind', 'start');\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#held) return;\n\n\t\tthis.#readSpan('start');\n\t\tthis.#readSpan('end');\n\t\tthis.render();\n\t}\n\n\tprotected connect(): void {\n\t\tthis.render();\n\t\tif (this.parentElement?.localName !== 'sonic-wavestrip') {\n\t\t\twarnOnce(\n\t\t\t\t'parent',\n\t\t\t\t'<sonic-region> draws nothing of its own, so it shows only as a child of <sonic-wavestrip>',\n\t\t\t);\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.#part;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound() || !this.parentElement) return;\n\n\t\towners.get(this.parentElement)?.(this);\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [start = NaN, end = NaN] = state.split(',').map(Number);\n\t\tif (!Number.isFinite(start) || !Number.isFinite(end)) return;\n\n\t\tthis.#span = { end, start };\n\t\tthis.render();\n\t}\n\n\tprotected override stateTarget(): HTMLElement | undefined {\n\t\treturn this.#part;\n\t}\n\n\t#draw(part: HTMLElement): void {\n\t\tthis.#part = part;\n\t\tthis.forwardNaming(part, true);\n\t\tthis.#writeForm();\n\t\tif (\n\t\t\t__DEV__ &&\n\t\t\t!this.hasAttribute('aria-label') &&\n\t\t\t!this.hasAttribute('aria-labelledby') &&\n\t\t\tthis.namingLabels().length === 0\n\t\t) {\n\t\t\twarnOnce(\n\t\t\t\t'name',\n\t\t\t\t'<sonic-region> is a slider with no name; give it an aria-label or aria-labelledby',\n\t\t\t);\n\t\t}\n\t}\n\n\t#input(span: RegionSpan): boolean {\n\t\tconst hasMoved = span.start !== this.#span.start || span.end !== this.#span.end;\n\n\t\tif (hasMoved) {\n\t\t\tthis.#span = span;\n\t\t\tthis.render();\n\t\t\tthis.#report('input');\n\t\t}\n\n\t\treturn hasMoved;\n\t}\n\n\t// One end at a time, so a write to one leaves the other where a gesture put it\n\t#readSpan(name: 'end' | 'start'): void {\n\t\tconst { end, start } = this.#span;\n\n\t\tif (name === 'end') {\n\t\t\tthis.#span = { end: this.numberAttribute('end', start), start };\n\t\t\treturn;\n\t\t}\n\n\t\tconst next = this.numberAttribute('start', 0);\n\n\t\tthis.#span = { end: this.hasAttribute('end') ? end : next, start: next };\n\t}\n\n\t#report(type: 'change' | 'input'): void {\n\t\tthis.dispatchEvent(new Event(type, { bubbles: true, composed: type === 'input' }));\n\t}\n\n\t#writeForm(): void {\n\t\tconst { end, start } = this.#span;\n\t\tconst state = `${String(start)},${String(end)}`;\n\t\tconst formState = `${this.name}=${state}`;\n\t\tif (formState === this.#formState) return;\n\n\t\tthis.#formState = formState;\n\t\tthis.writeFormFields(\n\t\t\t[\n\t\t\t\t['start', start],\n\t\t\t\t['end', end],\n\t\t\t],\n\t\t\tstate,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -34,6 +34,7 @@ export declare class SonicSlider extends SonicValueElement {
|
|
|
34
34
|
protected scrubChanged(): void;
|
|
35
35
|
protected scrubsKeyRepeat(): boolean;
|
|
36
36
|
protected springTarget(): number | undefined;
|
|
37
|
+
protected stateTarget(): HTMLElement;
|
|
37
38
|
}
|
|
38
39
|
//#endregion
|
|
39
40
|
//# sourceMappingURL=slider.d.ts.map
|
|
@@ -156,6 +156,9 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
156
156
|
springTarget() {
|
|
157
157
|
return this.spring ? this.originValue() : void 0;
|
|
158
158
|
}
|
|
159
|
+
stateTarget() {
|
|
160
|
+
return this.#slider;
|
|
161
|
+
}
|
|
159
162
|
#axis(slider, cap) {
|
|
160
163
|
const box = slider.getBoundingClientRect();
|
|
161
164
|
const capBox = cap.getBoundingClientRect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(slider, 'slider.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(slider, 'slider.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -27,8 +27,10 @@ export declare abstract class SonicElement extends HTMLElement {
|
|
|
27
27
|
protected numberAttribute(name: string, fallback: number): number;
|
|
28
28
|
protected optionalNumberAttribute(name: string): number | undefined;
|
|
29
29
|
protected reflect(name: string, value: boolean | null | number | string | undefined): void;
|
|
30
|
+
protected stateTarget(): Element | undefined;
|
|
30
31
|
protected toggleState(state: string, isOn: boolean): void;
|
|
31
32
|
protected upgradeProperties(...names: Array<keyof this>): void;
|
|
33
|
+
protected usesChild(_child: Node): boolean;
|
|
32
34
|
}
|
|
33
35
|
//#endregion
|
|
34
36
|
//# sourceMappingURL=sonic-element.d.ts.map
|
|
@@ -9,7 +9,6 @@ const namingAttributes = [
|
|
|
9
9
|
"aria-labelledby"
|
|
10
10
|
];
|
|
11
11
|
const checkedSheets = /* @__PURE__ */ new WeakMap();
|
|
12
|
-
const canMatchStates = CSS.supports("selector(:state(a))");
|
|
13
12
|
const canReferElements = "ariaLabelledByElements" in Element.prototype;
|
|
14
13
|
function isLost(drawn, { token }) {
|
|
15
14
|
return getComputedStyle(drawn).getPropertyValue(token) === "";
|
|
@@ -57,7 +56,6 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
57
56
|
}
|
|
58
57
|
internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
|
|
59
58
|
#connection;
|
|
60
|
-
#customStates = canMatchStates ? this.internals?.states : void 0;
|
|
61
59
|
#states = /* @__PURE__ */ new Set();
|
|
62
60
|
static #labelId(label) {
|
|
63
61
|
if (label.id === "") {
|
|
@@ -159,7 +157,7 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
159
157
|
keepControl(control, signal) {
|
|
160
158
|
const keep = () => {
|
|
161
159
|
appendOnce(this, control);
|
|
162
|
-
checkChildren(this, [...this.childNodes].filter((child) => child !== control));
|
|
160
|
+
checkChildren(this, [...this.childNodes].filter((child) => child !== control && !this.usesChild(child)));
|
|
163
161
|
};
|
|
164
162
|
const observer = new MutationObserver(keep);
|
|
165
163
|
keep();
|
|
@@ -186,14 +184,11 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
186
184
|
}
|
|
187
185
|
this.setAttribute(name, value === true ? "" : value.toString());
|
|
188
186
|
}
|
|
187
|
+
stateTarget() {}
|
|
189
188
|
toggleState(state, isOn) {
|
|
190
|
-
if (isOn)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
this.#states.delete(state);
|
|
196
|
-
this.#customStates?.delete(state);
|
|
189
|
+
if (isOn) this.#states.add(state);
|
|
190
|
+
else this.#states.delete(state);
|
|
191
|
+
this.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);
|
|
197
192
|
}
|
|
198
193
|
upgradeProperties(...names) {
|
|
199
194
|
for (const name of names) {
|
|
@@ -203,6 +198,9 @@ var SonicElement = class SonicElement extends HTMLElement {
|
|
|
203
198
|
this[name] = value;
|
|
204
199
|
}
|
|
205
200
|
}
|
|
201
|
+
usesChild(_child) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
206
204
|
#checkBox(control, checked, box) {
|
|
207
205
|
if (!control.isConnected) return;
|
|
208
206
|
if (!checked.has("host")) this.#checkHost(checked);
|