@xsynaptic/sonic-ui 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -2
- package/README.md +5 -3
- package/dist/default/define/region.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/envelope.js +11 -11
- package/dist/default/elements/form-element.js +10 -0
- package/dist/default/elements/readout.js +3 -2
- package/dist/default/elements/region-layer.js +251 -0
- package/dist/default/elements/region.js +157 -0
- package/dist/default/elements/sonic-element.js +26 -4
- package/dist/default/elements/split.js +2 -2
- package/dist/default/elements/toggle.js +2 -2
- package/dist/default/elements/value-element.js +9 -5
- package/dist/default/elements/value-gestures.js +7 -5
- package/dist/default/elements/wave-element.js +12 -3
- package/dist/default/elements/waveform.js +38 -2
- package/dist/default/elements/wavestrip.js +90 -17
- package/dist/default/elements/xy.js +3 -11
- package/dist/default/index.js +2 -1
- package/dist/default/lib/clock.js +1 -0
- package/dist/default/lib/drag-step.js +1 -1
- package/dist/default/lib/marker-band.js +40 -29
- package/dist/default/lib/region-drag.js +48 -0
- package/dist/default/lib/spoken-duration.js +3 -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/envelope.js +11 -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/readout.js +3 -2
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/region-layer.js +252 -0
- package/dist/dev/elements/region-layer.js.map +1 -0
- package/dist/dev/elements/region.d.ts +29 -0
- package/dist/dev/elements/region.js +165 -0
- package/dist/dev/elements/region.js.map +1 -0
- package/dist/dev/elements/sonic-element.d.ts +1 -0
- package/dist/dev/elements/sonic-element.js +27 -5
- 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 +2 -2
- 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 +9 -5
- 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 +7 -1
- package/dist/dev/elements/wave-element.js +12 -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.js +3 -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/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/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/dev/lib/spoken-duration.js +3 -0
- package/dist/dev/lib/spoken-duration.js.map +1 -1
- package/dist/styles/button.css +42 -15
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +48 -33
- package/dist/styles/envelope.css +7 -6
- package/dist/styles/led.css +28 -13
- package/dist/styles/material/arc.css +2 -2
- package/dist/styles/material/cap.css +25 -15
- package/dist/styles/material/core.css +53 -1
- package/dist/styles/material/glass.css +12 -2
- package/dist/styles/material/groove.css +3 -3
- package/dist/styles/material/keycap.css +5 -5
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +21 -11
- package/dist/styles/material/readout.css +23 -10
- package/dist/styles/material/{bracket.css → reticle.css} +15 -8
- package/dist/styles/material/scale.css +4 -4
- package/dist/styles/material/value.css +12 -3
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/material.css +1 -1
- package/dist/styles/meter.css +24 -11
- package/dist/styles/number.css +2 -1
- package/dist/styles/panel.css +5 -6
- package/dist/styles/region.css +88 -0
- package/dist/styles/ring.css +5 -4
- package/dist/styles/screen.css +7 -5
- package/dist/styles/segmented.css +8 -7
- package/dist/styles/slider.css +40 -32
- package/dist/styles/spectrum.css +9 -1
- package/dist/styles/switch.css +52 -53
- package/dist/styles/toggle.css +33 -38
- package/dist/styles/waveform.css +5 -4
- package/dist/styles/wavestrip.css +15 -12
- package/dist/styles/xy.css +19 -19
- package/package.json +3 -2
|
@@ -0,0 +1,252 @@
|
|
|
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.#stripKey() !== this.#key) this.render();
|
|
55
|
+
}
|
|
56
|
+
isHeld() {
|
|
57
|
+
return this.#held !== void 0;
|
|
58
|
+
}
|
|
59
|
+
render() {
|
|
60
|
+
const host = this.#host;
|
|
61
|
+
const drawn = [...host.element.children].filter((child) => child instanceof SonicRegion).map((region) => this.#draw(region));
|
|
62
|
+
this.#drawn = drawn;
|
|
63
|
+
this.#key = this.#stripKey();
|
|
64
|
+
placeChildren(host.layer, drawn.map(({ part }) => part));
|
|
65
|
+
this.#dropGone();
|
|
66
|
+
}
|
|
67
|
+
take(event) {
|
|
68
|
+
const host = this.#host;
|
|
69
|
+
const { startPx, travelPx } = host.axis();
|
|
70
|
+
const mapping = host.mapping();
|
|
71
|
+
const index = regionAt(this.#drawn.map((entry) => {
|
|
72
|
+
if (!this.#isEnabled(entry)) return;
|
|
73
|
+
const { end, start } = entry.link.span();
|
|
74
|
+
return [mapping.proportionOf(start) * travelPx, mapping.proportionOf(end) * travelPx];
|
|
75
|
+
}), event.clientX - startPx);
|
|
76
|
+
const pressed = index === void 0 ? void 0 : this.#drawn[index];
|
|
77
|
+
if (!pressed) return false;
|
|
78
|
+
this.#endKeyScrub();
|
|
79
|
+
const from = pressed.link.span();
|
|
80
|
+
this.#held = {
|
|
81
|
+
drawn: pressed,
|
|
82
|
+
from,
|
|
83
|
+
outside: host.outside(),
|
|
84
|
+
pointerId: event.pointerId,
|
|
85
|
+
press: event,
|
|
86
|
+
state: startRegionDrag({
|
|
87
|
+
from,
|
|
88
|
+
position: event.clientX,
|
|
89
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
90
|
+
widthPx: travelPx
|
|
91
|
+
})
|
|
92
|
+
};
|
|
93
|
+
pressed.link.hold("pointer");
|
|
94
|
+
capturePointer(host.control, event.pointerId);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
#bindKeys(signal) {
|
|
98
|
+
const { layer } = this.#host;
|
|
99
|
+
layer.addEventListener("keydown", (event) => {
|
|
100
|
+
const entry = this.#drawn.find(({ part }) => part === event.target);
|
|
101
|
+
if (!entry || event.defaultPrevented || this.#held) return;
|
|
102
|
+
if (this.#isEnabled(entry)) this.#keyTo(entry, event);
|
|
103
|
+
}, { signal });
|
|
104
|
+
layer.addEventListener("keyup", (event) => {
|
|
105
|
+
if (event.key === "Meta" || event.key === this.#keyScrub?.key) this.#endKeyScrub();
|
|
106
|
+
}, { signal });
|
|
107
|
+
layer.addEventListener("focusin", () => {
|
|
108
|
+
this.render();
|
|
109
|
+
}, { signal });
|
|
110
|
+
layer.addEventListener("focusout", () => {
|
|
111
|
+
this.#endKeyScrub();
|
|
112
|
+
}, { signal });
|
|
113
|
+
}
|
|
114
|
+
#bindPointer(signal) {
|
|
115
|
+
const whileHeld = (type, handle) => {
|
|
116
|
+
this.#host.control.addEventListener(type, (event) => {
|
|
117
|
+
const held = this.#held;
|
|
118
|
+
if (held && event instanceof PointerEvent && held.pointerId === event.pointerId) handle(held, event);
|
|
119
|
+
}, { signal });
|
|
120
|
+
};
|
|
121
|
+
whileHeld("pointermove", (hold, event) => {
|
|
122
|
+
this.#dragTo(hold, event);
|
|
123
|
+
});
|
|
124
|
+
whileHeld("pointerup", (hold) => {
|
|
125
|
+
const isPress = !hold.state.isEngaged;
|
|
126
|
+
this.#release();
|
|
127
|
+
if (isPress) this.#host.seek(hold.press);
|
|
128
|
+
});
|
|
129
|
+
whileHeld("pointercancel", () => {
|
|
130
|
+
this.#release(true);
|
|
131
|
+
});
|
|
132
|
+
whileHeld("lostpointercapture", () => {
|
|
133
|
+
this.#release();
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
#dragTo(hold, event) {
|
|
137
|
+
const wasEngaged = hold.state.isEngaged;
|
|
138
|
+
const { span, state } = stepRegionDrag(this.#host.mapping(), hold.state, {
|
|
139
|
+
isFine: event.shiftKey,
|
|
140
|
+
position: event.clientX
|
|
141
|
+
});
|
|
142
|
+
hold.state = state;
|
|
143
|
+
if (!span) return;
|
|
144
|
+
if (!wasEngaged) focusByPointer(hold.drawn.part);
|
|
145
|
+
hold.drawn.link.input(hold.outside?.(event) === true ? hold.from : span);
|
|
146
|
+
}
|
|
147
|
+
#draw(region) {
|
|
148
|
+
const host = this.#host;
|
|
149
|
+
const known = this.#drawn.find((entry) => entry.region === region);
|
|
150
|
+
const entry = known ?? {
|
|
151
|
+
kind: void 0,
|
|
152
|
+
link: linkRegion(region),
|
|
153
|
+
part: renderPart(),
|
|
154
|
+
region
|
|
155
|
+
};
|
|
156
|
+
const { link, part } = entry;
|
|
157
|
+
const mapping = host.mapping();
|
|
158
|
+
const span = link.span();
|
|
159
|
+
const [low, high] = regionTravel(span, mapping.bounds);
|
|
160
|
+
const canMove = this.#isEnabled(entry);
|
|
161
|
+
writeProperty(part, "--_sonic-region-from", String(mapping.proportionOf(span.start)));
|
|
162
|
+
writeProperty(part, "--_sonic-region-to", String(mapping.proportionOf(span.end)));
|
|
163
|
+
if (!known || entry.kind !== region.kind) {
|
|
164
|
+
entry.kind = region.kind;
|
|
165
|
+
writeKind(part, region.kind, "--_sonic-ink");
|
|
166
|
+
}
|
|
167
|
+
writeAttribute(part, "data-sonic-clipped", clippedEnds(region, mapping.bounds));
|
|
168
|
+
writeAttribute(part, "aria-valuemin", String(low));
|
|
169
|
+
writeAttribute(part, "aria-valuemax", String(high));
|
|
170
|
+
writeAttribute(part, "aria-valuenow", String(span.start));
|
|
171
|
+
writeAttribute(part, "aria-valuetext", host.speak(span));
|
|
172
|
+
writeAttribute(part, "aria-disabled", canMove ? void 0 : "true");
|
|
173
|
+
writeAttribute(part, "tabindex", canMove ? "0" : void 0);
|
|
174
|
+
link.draw(part);
|
|
175
|
+
return entry;
|
|
176
|
+
}
|
|
177
|
+
#dropGone() {
|
|
178
|
+
const isGone = (entry) => !this.#drawn.includes(entry) || !this.#isEnabled(entry);
|
|
179
|
+
if (this.#held && isGone(this.#held.drawn)) this.#release();
|
|
180
|
+
if (this.#keyScrub && isGone(this.#keyScrub.drawn)) this.#endKeyScrub();
|
|
181
|
+
}
|
|
182
|
+
#endKeyScrub() {
|
|
183
|
+
const scrub = this.#keyScrub;
|
|
184
|
+
if (!scrub) return;
|
|
185
|
+
const { link } = scrub.drawn;
|
|
186
|
+
this.#keyScrub = void 0;
|
|
187
|
+
link.hold(void 0);
|
|
188
|
+
if (hasMoved(scrub.from, link.span())) link.change();
|
|
189
|
+
}
|
|
190
|
+
#isEnabled({ link }) {
|
|
191
|
+
return !this.#host.isDisabled() && !link.isDisabled();
|
|
192
|
+
}
|
|
193
|
+
#keyTo(entry, event) {
|
|
194
|
+
const mapping = this.#host.mapping();
|
|
195
|
+
const from = entry.link.span();
|
|
196
|
+
const start = mapping.keyTarget(event.key, from.start);
|
|
197
|
+
if (start === void 0) return;
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
const next = moveRegionTo(mapping, from, start);
|
|
200
|
+
if (!event.repeat) {
|
|
201
|
+
this.#endKeyScrub();
|
|
202
|
+
if (entry.link.input(next)) entry.link.change();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (this.#keyScrub?.drawn !== entry || this.#keyScrub.key !== event.key) {
|
|
206
|
+
this.#endKeyScrub();
|
|
207
|
+
this.#keyScrub = {
|
|
208
|
+
drawn: entry,
|
|
209
|
+
from,
|
|
210
|
+
key: event.key
|
|
211
|
+
};
|
|
212
|
+
entry.link.hold("keys");
|
|
213
|
+
}
|
|
214
|
+
entry.link.input(next);
|
|
215
|
+
}
|
|
216
|
+
#redraw(region) {
|
|
217
|
+
if (this.#drawn.every((entry) => entry.region !== region)) {
|
|
218
|
+
this.render();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.#draw(region);
|
|
222
|
+
this.#dropGone();
|
|
223
|
+
}
|
|
224
|
+
#release(isRestored = false) {
|
|
225
|
+
const hold = this.#held;
|
|
226
|
+
if (!hold) return;
|
|
227
|
+
const { link } = hold.drawn;
|
|
228
|
+
this.#held = void 0;
|
|
229
|
+
if (isRestored) link.input(hold.from);
|
|
230
|
+
link.hold(void 0);
|
|
231
|
+
if (hasMoved(hold.from, link.span())) link.change();
|
|
232
|
+
}
|
|
233
|
+
#stripKey() {
|
|
234
|
+
const host = this.#host;
|
|
235
|
+
const mapping = host.mapping();
|
|
236
|
+
const [low, high] = mapping.bounds;
|
|
237
|
+
return [
|
|
238
|
+
low,
|
|
239
|
+
high,
|
|
240
|
+
mapping.proportionOf((low + high) / 2),
|
|
241
|
+
host.isDisabled(),
|
|
242
|
+
host.speak({
|
|
243
|
+
end: high,
|
|
244
|
+
start: high
|
|
245
|
+
})
|
|
246
|
+
].join("|");
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
export { RegionLayer };
|
|
252
|
+
//# 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.#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\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 ? '0' : 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,29 @@
|
|
|
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
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=region.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
];
|
|
29
|
+
static {
|
|
30
|
+
link = (element) => ({
|
|
31
|
+
change: () => {
|
|
32
|
+
element.#report("change");
|
|
33
|
+
},
|
|
34
|
+
draw: (part) => {
|
|
35
|
+
element.#draw(part);
|
|
36
|
+
},
|
|
37
|
+
hold: (by) => {
|
|
38
|
+
element.#held = by;
|
|
39
|
+
element.toggleState("dragging", by === "pointer");
|
|
40
|
+
},
|
|
41
|
+
input: (span) => element.#input(span),
|
|
42
|
+
isDisabled: () => element.isDisabled(),
|
|
43
|
+
span: () => {
|
|
44
|
+
const { end, start } = element.#span;
|
|
45
|
+
return {
|
|
46
|
+
end: Math.max(start, end),
|
|
47
|
+
start: Math.min(start, end)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
get dragging() {
|
|
53
|
+
return this.hasState("dragging");
|
|
54
|
+
}
|
|
55
|
+
get end() {
|
|
56
|
+
return this.#span.end;
|
|
57
|
+
}
|
|
58
|
+
set end(next) {
|
|
59
|
+
if (!this.#held) this.reflect("end", next);
|
|
60
|
+
}
|
|
61
|
+
/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */
|
|
62
|
+
get kind() {
|
|
63
|
+
return this.getAttribute("kind") ?? void 0;
|
|
64
|
+
}
|
|
65
|
+
set kind(kind) {
|
|
66
|
+
this.reflect("kind", kind);
|
|
67
|
+
}
|
|
68
|
+
get start() {
|
|
69
|
+
return this.#span.start;
|
|
70
|
+
}
|
|
71
|
+
set start(next) {
|
|
72
|
+
if (!this.#held) this.reflect("start", next);
|
|
73
|
+
}
|
|
74
|
+
#formState = "";
|
|
75
|
+
#held;
|
|
76
|
+
#part;
|
|
77
|
+
#span = {
|
|
78
|
+
end: 0,
|
|
79
|
+
start: 0
|
|
80
|
+
};
|
|
81
|
+
attributeChangedCallback(name) {
|
|
82
|
+
const isSpan = name === "end" || name === "start";
|
|
83
|
+
if (isSpan && this.#held) return;
|
|
84
|
+
if (isSpan) this.#readSpan(name);
|
|
85
|
+
this.render();
|
|
86
|
+
}
|
|
87
|
+
connectedCallback() {
|
|
88
|
+
this.upgradeProperties("end", "kind", "start");
|
|
89
|
+
super.connectedCallback();
|
|
90
|
+
}
|
|
91
|
+
formResetCallback() {
|
|
92
|
+
if (this.#held) return;
|
|
93
|
+
this.#readSpan("start");
|
|
94
|
+
this.#readSpan("end");
|
|
95
|
+
this.render();
|
|
96
|
+
}
|
|
97
|
+
connect() {
|
|
98
|
+
this.render();
|
|
99
|
+
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>");
|
|
100
|
+
}
|
|
101
|
+
focusTarget() {
|
|
102
|
+
return this.#part;
|
|
103
|
+
}
|
|
104
|
+
render() {
|
|
105
|
+
if (!this.isBound() || !this.parentElement) return;
|
|
106
|
+
owners.get(this.parentElement)?.(this);
|
|
107
|
+
}
|
|
108
|
+
restoreState(state) {
|
|
109
|
+
const [start = NaN, end = NaN] = state.split(",").map(Number);
|
|
110
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return;
|
|
111
|
+
this.#span = {
|
|
112
|
+
end,
|
|
113
|
+
start
|
|
114
|
+
};
|
|
115
|
+
this.render();
|
|
116
|
+
}
|
|
117
|
+
#draw(part) {
|
|
118
|
+
this.#part = part;
|
|
119
|
+
this.forwardNaming(part, true);
|
|
120
|
+
this.#writeForm();
|
|
121
|
+
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");
|
|
122
|
+
}
|
|
123
|
+
#input(span) {
|
|
124
|
+
const hasMoved = span.start !== this.#span.start || span.end !== this.#span.end;
|
|
125
|
+
if (hasMoved) {
|
|
126
|
+
this.#span = span;
|
|
127
|
+
this.render();
|
|
128
|
+
this.#report("input");
|
|
129
|
+
}
|
|
130
|
+
return hasMoved;
|
|
131
|
+
}
|
|
132
|
+
#readSpan(name) {
|
|
133
|
+
const { end, start } = this.#span;
|
|
134
|
+
if (name === "end") {
|
|
135
|
+
this.#span = {
|
|
136
|
+
end: this.numberAttribute("end", start),
|
|
137
|
+
start
|
|
138
|
+
};
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const next = this.numberAttribute("start", 0);
|
|
142
|
+
this.#span = {
|
|
143
|
+
end: this.hasAttribute("end") ? end : next,
|
|
144
|
+
start: next
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
#report(type) {
|
|
148
|
+
this.dispatchEvent(new Event(type, {
|
|
149
|
+
bubbles: true,
|
|
150
|
+
composed: type === "input"
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
#writeForm() {
|
|
154
|
+
const { end, start } = this.#span;
|
|
155
|
+
const state = `${String(start)},${String(end)}`;
|
|
156
|
+
const formState = `${this.name}=${state}`;
|
|
157
|
+
if (formState === this.#formState) return;
|
|
158
|
+
this.#formState = formState;
|
|
159
|
+
this.writeFormFields([["start", start], ["end", end]], state);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
export { SonicRegion, linkRegion, ownRegions };
|
|
165
|
+
//# 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];\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\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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"}
|
|
@@ -29,6 +29,7 @@ export declare abstract class SonicElement extends HTMLElement {
|
|
|
29
29
|
protected reflect(name: string, value: boolean | null | number | string | undefined): void;
|
|
30
30
|
protected toggleState(state: string, isOn: boolean): void;
|
|
31
31
|
protected upgradeProperties(...names: Array<keyof this>): void;
|
|
32
|
+
protected usesChild(_child: Node): boolean;
|
|
32
33
|
}
|
|
33
34
|
//#endregion
|
|
34
35
|
//# sourceMappingURL=sonic-element.d.ts.map
|
|
@@ -9,6 +9,8 @@ const namingAttributes = [
|
|
|
9
9
|
"aria-labelledby"
|
|
10
10
|
];
|
|
11
11
|
const checkedSheets = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
const canMatchStates = CSS.supports("selector(:state(a))");
|
|
13
|
+
const canReferElements = "ariaLabelledByElements" in Element.prototype;
|
|
12
14
|
function isLost(drawn, { token }) {
|
|
13
15
|
return getComputedStyle(drawn).getPropertyValue(token) === "";
|
|
14
16
|
}
|
|
@@ -44,8 +46,9 @@ function writeLabelledBy(target, labels) {
|
|
|
44
46
|
if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
|
|
45
47
|
target.ariaLabelledByElements = labels;
|
|
46
48
|
}
|
|
47
|
-
var SonicElement = class extends HTMLElement {
|
|
49
|
+
var SonicElement = class SonicElement extends HTMLElement {
|
|
48
50
|
static observedAttributes = [...namingAttributes, "disabled"];
|
|
51
|
+
static #labelCount = 0;
|
|
49
52
|
get disabled() {
|
|
50
53
|
return this.hasAttribute("disabled");
|
|
51
54
|
}
|
|
@@ -54,7 +57,15 @@ var SonicElement = class extends HTMLElement {
|
|
|
54
57
|
}
|
|
55
58
|
internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
|
|
56
59
|
#connection;
|
|
60
|
+
#customStates = canMatchStates ? this.internals?.states : void 0;
|
|
57
61
|
#states = /* @__PURE__ */ new Set();
|
|
62
|
+
static #labelId(label) {
|
|
63
|
+
if (label.id === "") {
|
|
64
|
+
SonicElement.#labelCount += 1;
|
|
65
|
+
label.id = `sonic-label-${String(SonicElement.#labelCount)}`;
|
|
66
|
+
}
|
|
67
|
+
return label.id;
|
|
68
|
+
}
|
|
58
69
|
blur() {
|
|
59
70
|
this.#focused()?.blur();
|
|
60
71
|
}
|
|
@@ -134,7 +145,7 @@ var SonicElement = class extends HTMLElement {
|
|
|
134
145
|
const value = this.getAttribute(name);
|
|
135
146
|
writeAttribute(target, name, isNamed && value !== null ? value : void 0);
|
|
136
147
|
}
|
|
137
|
-
if (labels.length > 0)
|
|
148
|
+
if (labels.length > 0) this.#writeLabels(target, labels);
|
|
138
149
|
}
|
|
139
150
|
hasState(state) {
|
|
140
151
|
return this.#states.has(state);
|
|
@@ -148,7 +159,7 @@ var SonicElement = class extends HTMLElement {
|
|
|
148
159
|
keepControl(control, signal) {
|
|
149
160
|
const keep = () => {
|
|
150
161
|
appendOnce(this, control);
|
|
151
|
-
checkChildren(this, [...this.childNodes].filter((child) => child !== control));
|
|
162
|
+
checkChildren(this, [...this.childNodes].filter((child) => child !== control && !this.usesChild(child)));
|
|
152
163
|
};
|
|
153
164
|
const observer = new MutationObserver(keep);
|
|
154
165
|
keep();
|
|
@@ -178,11 +189,11 @@ var SonicElement = class extends HTMLElement {
|
|
|
178
189
|
toggleState(state, isOn) {
|
|
179
190
|
if (isOn) {
|
|
180
191
|
this.#states.add(state);
|
|
181
|
-
this
|
|
192
|
+
this.#customStates?.add(state);
|
|
182
193
|
return;
|
|
183
194
|
}
|
|
184
195
|
this.#states.delete(state);
|
|
185
|
-
this
|
|
196
|
+
this.#customStates?.delete(state);
|
|
186
197
|
}
|
|
187
198
|
upgradeProperties(...names) {
|
|
188
199
|
for (const name of names) {
|
|
@@ -192,6 +203,9 @@ var SonicElement = class extends HTMLElement {
|
|
|
192
203
|
this[name] = value;
|
|
193
204
|
}
|
|
194
205
|
}
|
|
206
|
+
usesChild(_child) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
195
209
|
#checkBox(control, checked, box) {
|
|
196
210
|
if (!control.isConnected) return;
|
|
197
211
|
if (!checked.has("host")) this.#checkHost(checked);
|
|
@@ -210,6 +224,14 @@ var SonicElement = class extends HTMLElement {
|
|
|
210
224
|
const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
|
|
211
225
|
return active instanceof HTMLElement && this.contains(active) ? active : void 0;
|
|
212
226
|
}
|
|
227
|
+
#writeLabels(target, labels) {
|
|
228
|
+
if (canReferElements) {
|
|
229
|
+
writeLabelledBy(target, labels);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const ids = labels.map((label) => SonicElement.#labelId(label));
|
|
233
|
+
writeAttribute(target, "aria-labelledby", ids.join(" "));
|
|
234
|
+
}
|
|
213
235
|
};
|
|
214
236
|
|
|
215
237
|
//#endregion
|