@xsynaptic/sonic-ui 0.4.0 → 0.5.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/README.md +2 -21
- package/dist/default/dat.js +50 -0
- package/dist/default/define/button.js +7 -0
- package/dist/default/define/envelope.js +7 -0
- package/dist/default/define/split.js +7 -0
- package/dist/default/define/switch.js +7 -0
- package/dist/default/define/waveform.js +7 -0
- package/dist/default/define/wavestrip.js +7 -0
- package/dist/default/define/xy.js +7 -0
- package/dist/default/define.js +7 -1
- package/dist/default/elements/button.js +165 -0
- package/dist/default/elements/dial.js +16 -22
- package/dist/default/elements/envelope.js +367 -0
- package/dist/default/elements/meter.js +149 -61
- package/dist/default/elements/number.js +21 -8
- package/dist/default/elements/radio-group.js +185 -0
- package/dist/default/elements/readout-claim.js +81 -0
- package/dist/default/elements/readout.js +35 -0
- package/dist/default/elements/segmented.js +9 -159
- package/dist/default/elements/slider.js +42 -51
- package/dist/default/elements/sonic-element.js +24 -13
- package/dist/default/elements/split.js +249 -0
- package/dist/default/elements/switch.js +279 -0
- package/dist/default/elements/value-element.js +691 -0
- package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
- package/dist/default/elements/wave-element.js +77 -0
- package/dist/default/elements/waveform.js +305 -0
- package/dist/default/elements/wavestrip.js +182 -0
- package/dist/default/elements/xy.js +473 -0
- package/dist/default/index.js +10 -2
- package/dist/default/lib/adsr-shape.js +108 -0
- package/dist/default/lib/ballistics.js +55 -0
- package/dist/default/lib/canvas-surface.js +192 -0
- package/dist/default/lib/copy-node.js +32 -2
- package/dist/default/lib/crossfade.js +22 -0
- package/dist/default/lib/distribute.js +215 -0
- package/dist/default/lib/drag-step.js +99 -0
- package/dist/default/lib/envelope-curve.js +9 -0
- package/dist/default/lib/field.js +94 -0
- package/dist/default/lib/focus-by-pointer.js +28 -0
- package/dist/default/lib/frame-timeline.js +62 -0
- package/dist/default/lib/hold.js +38 -0
- package/dist/default/lib/marker-band.js +36 -0
- package/dist/default/lib/marker-rider.js +114 -0
- package/dist/default/lib/math.js +12 -3
- package/dist/default/lib/modifier-press.js +11 -0
- package/dist/default/lib/number-list.js +6 -6
- package/dist/default/lib/pointer-drag.js +43 -0
- package/dist/default/lib/render.js +20 -5
- package/dist/default/lib/resample-peaks.js +36 -0
- package/dist/default/lib/strip-scene.js +48 -0
- package/dist/default/lib/taper.js +23 -17
- package/dist/default/lib/time-regions.js +8 -0
- package/dist/default/lib/tracking-clock.js +35 -0
- package/dist/default/lib/value-mapping.js +115 -0
- package/dist/default/lib/waveform-buckets.js +41 -0
- package/dist/dev/dat.d.ts +19 -0
- package/dist/dev/dat.js +51 -0
- package/dist/dev/dat.js.map +1 -0
- package/dist/dev/define/button.d.ts +2 -0
- package/dist/dev/define/button.js +8 -0
- package/dist/dev/define/button.js.map +1 -0
- package/dist/dev/define/dial.js.map +1 -1
- package/dist/dev/define/envelope.d.ts +2 -0
- package/dist/dev/define/envelope.js +8 -0
- package/dist/dev/define/envelope.js.map +1 -0
- package/dist/dev/define/meter.js.map +1 -1
- package/dist/dev/define/number.js.map +1 -1
- package/dist/dev/define/segmented.js.map +1 -1
- package/dist/dev/define/slider.js.map +1 -1
- package/dist/dev/define/split.d.ts +2 -0
- package/dist/dev/define/split.js +8 -0
- package/dist/dev/define/split.js.map +1 -0
- package/dist/dev/define/switch.d.ts +2 -0
- package/dist/dev/define/switch.js +8 -0
- package/dist/dev/define/switch.js.map +1 -0
- package/dist/dev/define/waveform.d.ts +2 -0
- package/dist/dev/define/waveform.js +8 -0
- package/dist/dev/define/waveform.js.map +1 -0
- package/dist/dev/define/wavestrip.d.ts +2 -0
- package/dist/dev/define/wavestrip.js +8 -0
- package/dist/dev/define/wavestrip.js.map +1 -0
- package/dist/dev/define/xy.d.ts +2 -0
- package/dist/dev/define/xy.js +8 -0
- package/dist/dev/define/xy.js.map +1 -0
- package/dist/dev/define.d.ts +15 -3
- package/dist/dev/define.js +7 -1
- package/dist/dev/elements/{key.d.ts → button.d.ts} +8 -6
- package/dist/dev/elements/button.js +166 -0
- package/dist/dev/elements/button.js.map +1 -0
- package/dist/dev/elements/dial.d.ts +6 -4
- package/dist/dev/elements/dial.js +16 -22
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +36 -0
- package/dist/dev/elements/envelope.js +372 -0
- package/dist/dev/elements/envelope.js.map +1 -0
- package/dist/dev/elements/form-element.d.ts +1 -1
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +10 -5
- package/dist/dev/elements/meter.js +149 -61
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +7 -3
- package/dist/dev/elements/number.js +21 -8
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +25 -0
- package/dist/dev/elements/radio-group.js +186 -0
- package/dist/dev/elements/radio-group.js.map +1 -0
- package/dist/dev/elements/readout-claim.js +82 -0
- package/dist/dev/elements/readout-claim.js.map +1 -0
- package/dist/dev/elements/readout.js +36 -0
- package/dist/dev/elements/readout.js.map +1 -0
- package/dist/dev/elements/segmented.d.ts +4 -10
- package/dist/dev/elements/segmented.js +9 -159
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +6 -6
- package/dist/dev/elements/slider.js +42 -51
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +4 -3
- package/dist/dev/elements/sonic-element.js +51 -22
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.d.ts +21 -0
- package/dist/dev/elements/split.js +258 -0
- package/dist/dev/elements/split.js.map +1 -0
- package/dist/dev/elements/switch.d.ts +35 -0
- package/dist/dev/elements/switch.js +280 -0
- package/dist/dev/elements/switch.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +84 -0
- package/dist/dev/elements/value-element.js +692 -0
- package/dist/dev/elements/value-element.js.map +1 -0
- package/dist/dev/elements/{range-entry.js → value-entry.js} +4 -4
- package/dist/dev/elements/value-entry.js.map +1 -0
- package/dist/dev/elements/wave-element.d.ts +36 -0
- package/dist/dev/elements/wave-element.js +78 -0
- package/dist/dev/elements/wave-element.js.map +1 -0
- package/dist/dev/elements/waveform.d.ts +55 -0
- package/dist/dev/elements/waveform.js +306 -0
- package/dist/dev/elements/waveform.js.map +1 -0
- package/dist/dev/elements/wavestrip.d.ts +51 -0
- package/dist/dev/elements/wavestrip.js +183 -0
- package/dist/dev/elements/wavestrip.js.map +1 -0
- package/dist/dev/elements/xy.d.ts +62 -0
- package/dist/dev/elements/xy.js +474 -0
- package/dist/dev/elements/xy.js.map +1 -0
- package/dist/dev/index.d.ts +11 -2
- package/dist/dev/index.js +10 -2
- package/dist/dev/lib/adsr-shape.js +109 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -0
- package/dist/dev/lib/ballistics.js +56 -0
- package/dist/dev/lib/ballistics.js.map +1 -0
- package/dist/dev/lib/canvas-surface.d.ts +25 -0
- package/dist/dev/lib/canvas-surface.js +193 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -0
- package/dist/dev/lib/copy-node.js +32 -2
- package/dist/dev/lib/copy-node.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +12 -0
- package/dist/dev/lib/crossfade.js +23 -0
- package/dist/dev/lib/crossfade.js.map +1 -0
- package/dist/dev/lib/distribute.d.ts +4 -0
- package/dist/dev/lib/distribute.js +216 -0
- package/dist/dev/lib/distribute.js.map +1 -0
- package/dist/dev/lib/drag-step.js +100 -0
- package/dist/dev/lib/drag-step.js.map +1 -0
- package/dist/dev/lib/envelope-curve.d.ts +4 -0
- package/dist/dev/lib/envelope-curve.js +10 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -0
- package/dist/dev/lib/field.d.ts +4 -0
- package/dist/dev/lib/field.js +95 -0
- package/dist/dev/lib/field.js.map +1 -0
- package/dist/dev/lib/focus-by-pointer.js +29 -0
- package/dist/dev/lib/focus-by-pointer.js.map +1 -0
- package/dist/dev/lib/frame-timeline.js +63 -0
- package/dist/dev/lib/frame-timeline.js.map +1 -0
- package/dist/dev/lib/hold.js +39 -0
- package/dist/dev/lib/hold.js.map +1 -0
- package/dist/dev/lib/marker-band.js +37 -0
- package/dist/dev/lib/marker-band.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +115 -0
- package/dist/dev/lib/marker-rider.js.map +1 -0
- package/dist/dev/lib/math.js +12 -3
- package/dist/dev/lib/math.js.map +1 -1
- package/dist/dev/lib/modifier-press.js +12 -0
- package/dist/dev/lib/modifier-press.js.map +1 -0
- package/dist/dev/lib/number-list.js +6 -6
- package/dist/dev/lib/number-list.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +44 -0
- package/dist/dev/lib/pointer-drag.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -1
- package/dist/dev/lib/render.js +20 -5
- package/dist/dev/lib/render.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +37 -0
- package/dist/dev/lib/resample-peaks.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +49 -0
- package/dist/dev/lib/strip-scene.js.map +1 -0
- package/dist/dev/lib/taper.js +23 -17
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/dev/lib/time-regions.d.ts +4 -0
- package/dist/dev/lib/time-regions.js +9 -0
- package/dist/dev/lib/time-regions.js.map +1 -0
- package/dist/dev/lib/tracking-clock.js +36 -0
- package/dist/dev/lib/tracking-clock.js.map +1 -0
- package/dist/dev/lib/value-mapping.d.ts +23 -0
- package/dist/dev/lib/value-mapping.js +116 -0
- package/dist/dev/lib/value-mapping.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +9 -0
- package/dist/dev/lib/waveform-buckets.js +42 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -0
- package/dist/skins/ivory.css +1 -2
- package/dist/styles/button.css +212 -0
- package/dist/styles/controls.css +8 -1
- package/dist/styles/dial.css +88 -42
- package/dist/styles/envelope.css +159 -0
- package/dist/styles/led.css +36 -5
- package/dist/styles/material.css +277 -76
- package/dist/styles/meter.css +33 -48
- package/dist/styles/number.css +5 -1
- package/dist/styles/panel.css +59 -0
- package/dist/styles/ring.css +74 -0
- package/dist/styles/segmented.css +21 -48
- package/dist/styles/slider.css +175 -39
- package/dist/styles/switch.css +347 -0
- package/dist/styles/waveform.css +173 -0
- package/dist/styles/wavestrip.css +215 -0
- package/dist/styles/xy.css +204 -0
- package/package.json +14 -2
- package/dist/default/define/key.js +0 -7
- package/dist/default/elements/key.js +0 -169
- package/dist/default/elements/range-element.js +0 -546
- package/dist/default/lib/detent.js +0 -17
- package/dist/dev/define/key.d.ts +0 -2
- package/dist/dev/define/key.js +0 -8
- package/dist/dev/define/key.js.map +0 -1
- package/dist/dev/elements/key.js +0 -170
- package/dist/dev/elements/key.js.map +0 -1
- package/dist/dev/elements/range-element.d.ts +0 -64
- package/dist/dev/elements/range-element.js +0 -547
- package/dist/dev/elements/range-element.js.map +0 -1
- package/dist/dev/elements/range-entry.js.map +0 -1
- package/dist/dev/lib/detent.js +0 -18
- package/dist/dev/lib/detent.js.map +0 -1
- package/dist/styles/key.css +0 -242
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { SonicElement } from "./sonic-element.js";
|
|
2
|
+
import { SonicValueElement, linkValue } from "./value-element.js";
|
|
3
|
+
import { distribute, splitLimits } from "../lib/distribute.js";
|
|
4
|
+
|
|
5
|
+
//#region src/elements/split.ts
|
|
6
|
+
const modeNames = /* @__PURE__ */ new Set([
|
|
7
|
+
"cascade",
|
|
8
|
+
"equal",
|
|
9
|
+
"proportional"
|
|
10
|
+
]);
|
|
11
|
+
const gridSlop = 1e-9;
|
|
12
|
+
const lockAttributes = ["data-sonic-locked", "disabled"];
|
|
13
|
+
function isMode(name) {
|
|
14
|
+
return name !== null && modeNames.has(name);
|
|
15
|
+
}
|
|
16
|
+
function isLocked(member) {
|
|
17
|
+
return "sonicLocked" in member.element.dataset || member.link.isDisabled();
|
|
18
|
+
}
|
|
19
|
+
function gridLimits(mapping, [low, high]) {
|
|
20
|
+
const bottom = mapping.snap(low);
|
|
21
|
+
const top = mapping.snap(high);
|
|
22
|
+
return [bottom < low - gridSlop ? mapping.keyTarget("ArrowRight", bottom) ?? bottom : bottom, top > high + gridSlop ? mapping.keyTarget("ArrowLeft", top) ?? top : top];
|
|
23
|
+
}
|
|
24
|
+
function modelOf(member, value, isFree) {
|
|
25
|
+
const mapping = member.link.mapping();
|
|
26
|
+
const [first, last] = mapping.bounds;
|
|
27
|
+
return {
|
|
28
|
+
isFree,
|
|
29
|
+
max: Math.max(first, last),
|
|
30
|
+
min: Math.min(first, last),
|
|
31
|
+
snap: mapping.snap,
|
|
32
|
+
stepFrom: (from, direction) => mapping.keyTarget(direction > 0 ? "ArrowRight" : "ArrowLeft", from) ?? from,
|
|
33
|
+
value
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
var SonicSplit = class extends SonicElement {
|
|
37
|
+
static observedAttributes = [
|
|
38
|
+
...SonicElement.observedAttributes,
|
|
39
|
+
"mode",
|
|
40
|
+
"total"
|
|
41
|
+
];
|
|
42
|
+
get mode() {
|
|
43
|
+
const name = this.getAttribute("mode");
|
|
44
|
+
return isMode(name) ? name : "proportional";
|
|
45
|
+
}
|
|
46
|
+
set mode(name) {
|
|
47
|
+
this.reflect("mode", name);
|
|
48
|
+
}
|
|
49
|
+
get total() {
|
|
50
|
+
return this.optionalNumberAttribute("total");
|
|
51
|
+
}
|
|
52
|
+
set total(value) {
|
|
53
|
+
this.reflect("total", value);
|
|
54
|
+
}
|
|
55
|
+
#isQueued = false;
|
|
56
|
+
#isTotalKept = false;
|
|
57
|
+
#isWarned = false;
|
|
58
|
+
#isWriting = false;
|
|
59
|
+
#members = [];
|
|
60
|
+
#signal;
|
|
61
|
+
#snapshot;
|
|
62
|
+
#total = 0;
|
|
63
|
+
#waiting = /* @__PURE__ */ new Set();
|
|
64
|
+
attributeChangedCallback(name) {
|
|
65
|
+
if ((name === "mode" || name === "total") && this.#signal) this.#bind();
|
|
66
|
+
}
|
|
67
|
+
connectedCallback() {
|
|
68
|
+
this.upgradeProperties("mode", "total");
|
|
69
|
+
super.connectedCallback();
|
|
70
|
+
}
|
|
71
|
+
connect(signal) {
|
|
72
|
+
const observer = new MutationObserver((records) => {
|
|
73
|
+
if (records.some((record) => record.attributeName !== null && lockAttributes.includes(record.attributeName)) || !this.#isSameMembers(this.#find())) this.#bind();
|
|
74
|
+
});
|
|
75
|
+
this.#signal = signal;
|
|
76
|
+
if (this.internals) this.internals.role = "group";
|
|
77
|
+
observer.observe(this, {
|
|
78
|
+
attributeFilter: lockAttributes,
|
|
79
|
+
attributes: true,
|
|
80
|
+
childList: true,
|
|
81
|
+
subtree: true
|
|
82
|
+
});
|
|
83
|
+
this.addEventListener("change", (event) => {
|
|
84
|
+
this.#release(event);
|
|
85
|
+
}, { signal });
|
|
86
|
+
signal.addEventListener("abort", () => {
|
|
87
|
+
observer.disconnect();
|
|
88
|
+
this.#signal = void 0;
|
|
89
|
+
this.#isTotalKept = false;
|
|
90
|
+
this.#unbind();
|
|
91
|
+
}, { once: true });
|
|
92
|
+
this.#bind();
|
|
93
|
+
}
|
|
94
|
+
#bind() {
|
|
95
|
+
const known = new Set(this.#members.map(({ element }) => element));
|
|
96
|
+
this.#unbind();
|
|
97
|
+
this.#members = this.#find().map((element) => this.#join(element));
|
|
98
|
+
const values = this.#members.map(({ seen }) => seen);
|
|
99
|
+
const sum = values.reduce((total, value) => total + value, 0);
|
|
100
|
+
this.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);
|
|
101
|
+
this.#isTotalKept = this.#members.length > 0;
|
|
102
|
+
if (this.#members.length > 0 && sum !== this.#total) this.#spread(values, known);
|
|
103
|
+
this.#limit();
|
|
104
|
+
this.#warnReach();
|
|
105
|
+
}
|
|
106
|
+
#compensate() {
|
|
107
|
+
this.#isQueued = false;
|
|
108
|
+
for (const mover of this.#members) if (mover.link.value() !== mover.seen) this.#move(mover);
|
|
109
|
+
}
|
|
110
|
+
#find() {
|
|
111
|
+
const found = [];
|
|
112
|
+
for (const element of this.querySelectorAll("*")) {
|
|
113
|
+
if (element.closest("sonic-split") !== this) continue;
|
|
114
|
+
if (element instanceof SonicValueElement) found.push(element);
|
|
115
|
+
else this.#waitFor(element.localName);
|
|
116
|
+
}
|
|
117
|
+
return found;
|
|
118
|
+
}
|
|
119
|
+
#isSameMembers(elements) {
|
|
120
|
+
return elements.length === this.#members.length && elements.every((element, index) => this.#members[index]?.element === element);
|
|
121
|
+
}
|
|
122
|
+
#join(element) {
|
|
123
|
+
const link = linkValue(element);
|
|
124
|
+
const unwatch = link.watch(() => {
|
|
125
|
+
if (this.#isWriting || this.#isQueued) return;
|
|
126
|
+
this.#isQueued = true;
|
|
127
|
+
queueMicrotask(() => {
|
|
128
|
+
if (this.#isQueued) this.#compensate();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
link.land((target, direction) => this.#land(element, target, direction));
|
|
132
|
+
return {
|
|
133
|
+
element,
|
|
134
|
+
link,
|
|
135
|
+
seen: link.value(),
|
|
136
|
+
unwatch
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
#land(element, target, direction) {
|
|
140
|
+
if (this.#isQueued) this.#compensate();
|
|
141
|
+
const index = this.#members.findIndex((member) => member.element === element);
|
|
142
|
+
const mover = this.#members[index];
|
|
143
|
+
if (!mover) return target;
|
|
144
|
+
return distribute(this.#model(this.#members.map(({ seen }) => seen), mover), {
|
|
145
|
+
mode: this.mode,
|
|
146
|
+
total: this.#total
|
|
147
|
+
}, {
|
|
148
|
+
direction,
|
|
149
|
+
index,
|
|
150
|
+
target
|
|
151
|
+
})[index] ?? target;
|
|
152
|
+
}
|
|
153
|
+
#limit() {
|
|
154
|
+
const values = this.#members.map(({ seen }) => seen);
|
|
155
|
+
for (const [index, member] of this.#members.entries()) {
|
|
156
|
+
const model = this.#model(values, member);
|
|
157
|
+
const value = values[index] ?? member.seen;
|
|
158
|
+
member.link.limit(isLocked(member) ? [value, value] : gridLimits(member.link.mapping(), splitLimits(model, index, this.#total)));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
#model(values, mover) {
|
|
162
|
+
return this.#members.map((member, index) => {
|
|
163
|
+
const isFixed = member !== mover && (isLocked(member) || member.link.isHeld());
|
|
164
|
+
return modelOf(member, values[index] ?? member.seen, !isFixed);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
#move(mover) {
|
|
168
|
+
const index = this.#members.indexOf(mover);
|
|
169
|
+
const kept = this.#snapshot;
|
|
170
|
+
const snapshot = kept?.mover === mover && mover.link.isHeld() ? kept : {
|
|
171
|
+
mover,
|
|
172
|
+
values: this.#members.map(({ seen }) => seen)
|
|
173
|
+
};
|
|
174
|
+
this.#snapshot = snapshot;
|
|
175
|
+
this.#write(distribute(this.#model(snapshot.values, mover), {
|
|
176
|
+
mode: this.mode,
|
|
177
|
+
total: this.#total
|
|
178
|
+
}, {
|
|
179
|
+
index,
|
|
180
|
+
target: mover.link.value()
|
|
181
|
+
}), mover);
|
|
182
|
+
this.#limit();
|
|
183
|
+
}
|
|
184
|
+
#release(event) {
|
|
185
|
+
if (this.#isWriting) return;
|
|
186
|
+
if (this.#isQueued) this.#compensate();
|
|
187
|
+
const snapshot = this.#snapshot;
|
|
188
|
+
if (snapshot?.mover.element !== event.target) return;
|
|
189
|
+
this.#snapshot = void 0;
|
|
190
|
+
const changed = this.#members.filter((member, index) => member !== snapshot.mover && member.link.value() !== snapshot.values[index]);
|
|
191
|
+
queueMicrotask(() => {
|
|
192
|
+
this.#isWriting = true;
|
|
193
|
+
try {
|
|
194
|
+
for (const { element } of changed) element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
195
|
+
} finally {
|
|
196
|
+
this.#isWriting = false;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
#spread(values, known) {
|
|
201
|
+
const model = this.#model(values);
|
|
202
|
+
const joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));
|
|
203
|
+
const isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);
|
|
204
|
+
const spreadOver = model.map((member, index) => isAnyKnownFree && joined[index] === true ? {
|
|
205
|
+
...member,
|
|
206
|
+
isFree: false
|
|
207
|
+
} : member);
|
|
208
|
+
this.#write(distribute(spreadOver, {
|
|
209
|
+
mode: this.mode,
|
|
210
|
+
total: this.#total
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
#unbind() {
|
|
214
|
+
for (const { link, unwatch } of this.#members) {
|
|
215
|
+
unwatch();
|
|
216
|
+
link.land(void 0);
|
|
217
|
+
link.limit(void 0);
|
|
218
|
+
}
|
|
219
|
+
this.#members = [];
|
|
220
|
+
this.#snapshot = void 0;
|
|
221
|
+
this.#isQueued = false;
|
|
222
|
+
}
|
|
223
|
+
#waitFor(tag) {
|
|
224
|
+
if (!tag.includes("-") || customElements.get(tag) || this.#waiting.has(tag)) return;
|
|
225
|
+
this.#waiting.add(tag);
|
|
226
|
+
customElements.whenDefined(tag).then(() => {
|
|
227
|
+
this.#waiting.delete(tag);
|
|
228
|
+
if (this.#signal) this.#bind();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
#warnReach() {
|
|
232
|
+
if (this.#isWarned || this.#members.length === 0) return;
|
|
233
|
+
const models = this.#model(this.#members.map(({ seen }) => seen));
|
|
234
|
+
const low = models.reduce((total, { min }) => total + min, 0);
|
|
235
|
+
const high = models.reduce((total, { max }) => total + max, 0);
|
|
236
|
+
if (this.#total >= low && this.#total <= high) return;
|
|
237
|
+
this.#isWarned = true;
|
|
238
|
+
console.warn(`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`);
|
|
239
|
+
}
|
|
240
|
+
#write(values, mover) {
|
|
241
|
+
this.#isWriting = true;
|
|
242
|
+
try {
|
|
243
|
+
for (const [index, member] of this.#members.entries()) {
|
|
244
|
+
const value = values[index];
|
|
245
|
+
if (value === void 0 || value === member.link.value()) continue;
|
|
246
|
+
member.link.limit(void 0);
|
|
247
|
+
member.link.input(value, member === mover);
|
|
248
|
+
}
|
|
249
|
+
} finally {
|
|
250
|
+
this.#isWriting = false;
|
|
251
|
+
}
|
|
252
|
+
for (const member of this.#members) member.seen = member.link.value();
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
export { SonicSplit };
|
|
258
|
+
//# sourceMappingURL=split.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridSlop = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridSlop ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridSlop ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#signal: AbortSignal | undefined;\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.#signal) this.#bind();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tthis.#signal = signal;\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t\tthis.#signal = undefined;\n\t\t\t\tthis.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.value() !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.land((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.value(), unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.limit(\n\t\t\t\tisLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.value() },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.value() !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { element } of changed) {\n\t\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.land(undefined);\n\t\t\tlink.limit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.#signal) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.value()) continue;\n\n\t\t\t\tmember.link.limit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.value();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SonicRadioGroupElement } from "./radio-group.js";
|
|
2
|
+
//#region src/elements/switch.d.ts
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'sonic-switch': SonicSwitch;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare class SonicSwitch extends SonicRadioGroupElement {
|
|
9
|
+
#private;
|
|
10
|
+
static readonly observedAttributes: string[];
|
|
11
|
+
get checked(): boolean;
|
|
12
|
+
set checked(isChecked: boolean | undefined);
|
|
13
|
+
get defaultChecked(): boolean;
|
|
14
|
+
set defaultChecked(isChecked: boolean);
|
|
15
|
+
get orientation(): 'horizontal' | 'vertical';
|
|
16
|
+
set orientation(direction: 'horizontal' | 'vertical' | undefined);
|
|
17
|
+
get value(): string;
|
|
18
|
+
set value(next: string);
|
|
19
|
+
protected readonly group: HTMLDivElement;
|
|
20
|
+
attributeChangedCallback(name: string): void;
|
|
21
|
+
formResetCallback(): void;
|
|
22
|
+
protected activate(): void;
|
|
23
|
+
protected chooseByClick(option: HTMLButtonElement): void;
|
|
24
|
+
protected chooseByKey(option: HTMLButtonElement, event: KeyboardEvent): void;
|
|
25
|
+
protected claimPress(option: HTMLButtonElement, event: PointerEvent): boolean;
|
|
26
|
+
protected connect(signal: AbortSignal): void;
|
|
27
|
+
protected focusTarget(): HTMLElement | undefined;
|
|
28
|
+
protected isWrapping(): boolean;
|
|
29
|
+
protected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined;
|
|
30
|
+
protected render(): void;
|
|
31
|
+
protected renderOption(): HTMLButtonElement;
|
|
32
|
+
protected restoreState(state: string): void;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//# sourceMappingURL=switch.d.ts.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { clamp } from "../lib/math.js";
|
|
2
|
+
import { requireChild, template } from "../lib/render.js";
|
|
3
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
|
+
import { bindHold } from "../lib/hold.js";
|
|
5
|
+
import { SonicRadioGroupElement, isUnder, optionValue } from "./radio-group.js";
|
|
6
|
+
|
|
7
|
+
//#region src/elements/switch.ts
|
|
8
|
+
const renderSwitch = template(`
|
|
9
|
+
<div class="sonic-switch">
|
|
10
|
+
<span aria-hidden="true" class="sonic-switch-bushing"></span>
|
|
11
|
+
<span aria-hidden="true" class="sonic-switch-bat"></span>
|
|
12
|
+
</div>
|
|
13
|
+
`, HTMLDivElement);
|
|
14
|
+
const renderPosition = template(`
|
|
15
|
+
<button class="sonic-switch-position" role="radio" type="button">
|
|
16
|
+
<span class="sonic-switch-label"></span>
|
|
17
|
+
</button>
|
|
18
|
+
`, HTMLButtonElement);
|
|
19
|
+
const renderBare = template(`<button class="sonic-switch-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
20
|
+
const pressCentreZone = .08;
|
|
21
|
+
function pressedIndex(index, count, offset) {
|
|
22
|
+
if (count === 2) return index === 0 ? 1 : 0;
|
|
23
|
+
const side = Math.abs(offset) < pressCentreZone ? 1 : Math.sign(offset);
|
|
24
|
+
const next = index + side;
|
|
25
|
+
return next < 0 || next >= count ? index - side : next;
|
|
26
|
+
}
|
|
27
|
+
var SonicSwitch = class extends SonicRadioGroupElement {
|
|
28
|
+
static observedAttributes = [
|
|
29
|
+
...SonicRadioGroupElement.observedAttributes,
|
|
30
|
+
"checked",
|
|
31
|
+
"orientation"
|
|
32
|
+
];
|
|
33
|
+
get checked() {
|
|
34
|
+
return this.#isDirty ? this.#checked : this.defaultChecked;
|
|
35
|
+
}
|
|
36
|
+
set checked(isChecked) {
|
|
37
|
+
this.#isDirty = true;
|
|
38
|
+
this.#checked = isChecked === true;
|
|
39
|
+
this.render();
|
|
40
|
+
}
|
|
41
|
+
get defaultChecked() {
|
|
42
|
+
return this.hasAttribute("checked");
|
|
43
|
+
}
|
|
44
|
+
set defaultChecked(isChecked) {
|
|
45
|
+
this.reflect("checked", isChecked);
|
|
46
|
+
}
|
|
47
|
+
get orientation() {
|
|
48
|
+
return this.getAttribute("orientation") === "horizontal" ? "horizontal" : "vertical";
|
|
49
|
+
}
|
|
50
|
+
set orientation(direction) {
|
|
51
|
+
this.reflect("orientation", direction);
|
|
52
|
+
}
|
|
53
|
+
get value() {
|
|
54
|
+
const value = super.value;
|
|
55
|
+
return value === "" && this.#isBare() ? "on" : value;
|
|
56
|
+
}
|
|
57
|
+
set value(next) {
|
|
58
|
+
super.value = next;
|
|
59
|
+
}
|
|
60
|
+
group = renderSwitch();
|
|
61
|
+
#bare = renderBare();
|
|
62
|
+
#bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
|
|
63
|
+
#batPress;
|
|
64
|
+
#checked = false;
|
|
65
|
+
#holding;
|
|
66
|
+
#isClickSwallowed = false;
|
|
67
|
+
#isDirty = false;
|
|
68
|
+
attributeChangedCallback(name) {
|
|
69
|
+
if (name === "checked") this.#isDirty = false;
|
|
70
|
+
if (this.isDisabled()) this.#holding?.release();
|
|
71
|
+
super.attributeChangedCallback(name);
|
|
72
|
+
}
|
|
73
|
+
formResetCallback() {
|
|
74
|
+
this.#isDirty = false;
|
|
75
|
+
super.formResetCallback();
|
|
76
|
+
}
|
|
77
|
+
activate() {
|
|
78
|
+
if (this.#isBare()) this.#bare.click();
|
|
79
|
+
else super.activate();
|
|
80
|
+
}
|
|
81
|
+
chooseByClick(option) {
|
|
82
|
+
super.chooseByClick(option);
|
|
83
|
+
if (this.#isMomentary(option)) this.#springBack();
|
|
84
|
+
}
|
|
85
|
+
chooseByKey(option, event) {
|
|
86
|
+
if (!this.#isMomentary(option)) {
|
|
87
|
+
super.chooseByKey(option, event);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (event.repeat) return;
|
|
91
|
+
this.#hold(option, event.key);
|
|
92
|
+
option.focus();
|
|
93
|
+
}
|
|
94
|
+
claimPress(option, event) {
|
|
95
|
+
if (this.#grabBat(event)) return true;
|
|
96
|
+
if (!this.#isMomentary(option)) return false;
|
|
97
|
+
this.group.setPointerCapture(event.pointerId);
|
|
98
|
+
this.#hold(option, event.pointerId);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
connect(signal) {
|
|
102
|
+
const group = this.group;
|
|
103
|
+
this.upgradeProperties("defaultChecked", "checked", "orientation");
|
|
104
|
+
super.connect(signal);
|
|
105
|
+
this.checkStyles(group, "switch.css");
|
|
106
|
+
this.#bare.addEventListener("click", (event) => {
|
|
107
|
+
const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
|
|
108
|
+
this.#isClickSwallowed = false;
|
|
109
|
+
if (!isSwallowed) this.#setChecked(!this.checked);
|
|
110
|
+
}, { signal });
|
|
111
|
+
this.#bare.addEventListener("pointerdown", (event) => {
|
|
112
|
+
this.#isClickSwallowed = false;
|
|
113
|
+
if (event.button === 0 && !this.isDisabled()) this.#grabBat(event);
|
|
114
|
+
}, { signal });
|
|
115
|
+
this.#bindBat(group, signal);
|
|
116
|
+
this.#holding = bindHold(group, {
|
|
117
|
+
canHold: () => !this.isDisabled(),
|
|
118
|
+
leave: "focusout",
|
|
119
|
+
onHold: () => {
|
|
120
|
+
this.toggleState("held", true);
|
|
121
|
+
},
|
|
122
|
+
onRelease: () => {
|
|
123
|
+
this.toggleState("held", false);
|
|
124
|
+
this.#springBack();
|
|
125
|
+
}
|
|
126
|
+
}, signal);
|
|
127
|
+
}
|
|
128
|
+
focusTarget() {
|
|
129
|
+
return this.#isBare() ? this.#bare : super.focusTarget();
|
|
130
|
+
}
|
|
131
|
+
isWrapping() {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
releaseTarget(option) {
|
|
135
|
+
if (this.#isMomentary(option)) return void 0;
|
|
136
|
+
const options = this.options();
|
|
137
|
+
if (options.length === 2 && optionValue(option) === this.value) return options.find((other) => other !== option);
|
|
138
|
+
return option;
|
|
139
|
+
}
|
|
140
|
+
render() {
|
|
141
|
+
const group = this.group;
|
|
142
|
+
const options = this.options();
|
|
143
|
+
if (options.length > 0) {
|
|
144
|
+
this.#bare.remove();
|
|
145
|
+
writeAttribute(group, "role", "radiogroup");
|
|
146
|
+
group.dataset.sonicPositionCount = String(options.length);
|
|
147
|
+
super.render();
|
|
148
|
+
this.#renderThrow(this.#checkedIndex(options));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const bare = this.#bare;
|
|
152
|
+
const isChecked = this.checked;
|
|
153
|
+
writeAttribute(group, "role", void 0);
|
|
154
|
+
delete group.dataset.sonicPositionCount;
|
|
155
|
+
if (bare.parentElement !== group) group.append(bare);
|
|
156
|
+
bare.disabled = this.isDisabled();
|
|
157
|
+
bare.setAttribute("aria-checked", String(isChecked));
|
|
158
|
+
this.forwardNaming(group, false);
|
|
159
|
+
this.forwardNaming(bare, true);
|
|
160
|
+
const onSide = this.orientation === "horizontal" ? 1 : -1;
|
|
161
|
+
group.style.setProperty("--_sonic-switch-at", String(isChecked ? onSide : -onSide));
|
|
162
|
+
this.writeFormValue(isChecked ? this.value : null, String(isChecked));
|
|
163
|
+
}
|
|
164
|
+
renderOption() {
|
|
165
|
+
return renderPosition();
|
|
166
|
+
}
|
|
167
|
+
restoreState(state) {
|
|
168
|
+
if (this.#isBare()) this.checked = state === "true";
|
|
169
|
+
else super.restoreState(state);
|
|
170
|
+
}
|
|
171
|
+
#along(event) {
|
|
172
|
+
return this.orientation === "horizontal" ? event.clientX : event.clientY;
|
|
173
|
+
}
|
|
174
|
+
#bindBat(group, signal) {
|
|
175
|
+
const drop = (event) => {
|
|
176
|
+
if (event.pointerId === this.#batPress?.pointerId) this.#batPress = void 0;
|
|
177
|
+
};
|
|
178
|
+
group.addEventListener("pointermove", (event) => {
|
|
179
|
+
const press = this.#batPress;
|
|
180
|
+
if (press?.pointerId !== event.pointerId || this.isDisabled()) return;
|
|
181
|
+
const delta = this.#along(event) - press.last;
|
|
182
|
+
if (Math.abs(delta) < this.#bat.getBoundingClientRect().width / 4) return;
|
|
183
|
+
press.hasMoved = true;
|
|
184
|
+
press.last = this.#along(event);
|
|
185
|
+
this.#throwBy(Math.sign(delta), event.pointerId);
|
|
186
|
+
}, { signal });
|
|
187
|
+
group.addEventListener("pointerup", (event) => {
|
|
188
|
+
const press = this.#batPress;
|
|
189
|
+
if (press?.pointerId !== event.pointerId) return;
|
|
190
|
+
this.#batPress = void 0;
|
|
191
|
+
if (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);
|
|
192
|
+
this.#isClickSwallowed = true;
|
|
193
|
+
}, { signal });
|
|
194
|
+
group.addEventListener("pointercancel", drop, { signal });
|
|
195
|
+
group.addEventListener("lostpointercapture", drop, { signal });
|
|
196
|
+
}
|
|
197
|
+
#checkedIndex(options) {
|
|
198
|
+
return options.findIndex((option) => optionValue(option) === this.value);
|
|
199
|
+
}
|
|
200
|
+
#grabBat(event) {
|
|
201
|
+
if (!isUnder(this.#bat, event.clientX, event.clientY)) return false;
|
|
202
|
+
this.group.setPointerCapture(event.pointerId);
|
|
203
|
+
this.#batPress = {
|
|
204
|
+
hasMoved: false,
|
|
205
|
+
last: this.#along(event),
|
|
206
|
+
pointerId: event.pointerId
|
|
207
|
+
};
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
#hasSprungFrom(option) {
|
|
211
|
+
if (!option || !this.#isMomentary(option)) return false;
|
|
212
|
+
this.#holding?.release();
|
|
213
|
+
return this.value !== optionValue(option);
|
|
214
|
+
}
|
|
215
|
+
#hold(option, by) {
|
|
216
|
+
if (this.#holding?.hold(by) === true) this.select(option);
|
|
217
|
+
}
|
|
218
|
+
#isBare() {
|
|
219
|
+
return this.options().length === 0;
|
|
220
|
+
}
|
|
221
|
+
#isMomentary(option) {
|
|
222
|
+
const options = this.options();
|
|
223
|
+
return (option === options[0] || option === options.at(-1)) && options.length > 1 && option.querySelector("[data-sonic-value]")?.hasAttribute("data-sonic-momentary") === true;
|
|
224
|
+
}
|
|
225
|
+
#pressBat(event) {
|
|
226
|
+
const options = this.options();
|
|
227
|
+
if (options.length === 0) {
|
|
228
|
+
this.#setChecked(!this.checked);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const box = this.#bat.getBoundingClientRect();
|
|
232
|
+
const [start, size] = this.orientation === "horizontal" ? [box.left, box.width] : [box.top, box.height];
|
|
233
|
+
const offset = (this.#along(event) - start) / size - .5;
|
|
234
|
+
const option = options[pressedIndex(this.#checkedIndex(options), options.length, offset)];
|
|
235
|
+
if (!option) return;
|
|
236
|
+
this.chooseByClick(option);
|
|
237
|
+
this.#refocus(options);
|
|
238
|
+
}
|
|
239
|
+
#refocus(options) {
|
|
240
|
+
if (this.group.matches(":focus-within")) options[this.#checkedIndex(options)]?.focus();
|
|
241
|
+
}
|
|
242
|
+
#renderThrow(index) {
|
|
243
|
+
const count = this.options().length;
|
|
244
|
+
const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
|
|
245
|
+
this.group.style.setProperty("--_sonic-switch-at", String(at));
|
|
246
|
+
}
|
|
247
|
+
#setChecked(isChecked) {
|
|
248
|
+
if (isChecked === this.checked) return;
|
|
249
|
+
this.checked = isChecked;
|
|
250
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
251
|
+
}
|
|
252
|
+
#springBack() {
|
|
253
|
+
const options = this.options();
|
|
254
|
+
const index = this.#checkedIndex(options);
|
|
255
|
+
const held = options[index];
|
|
256
|
+
if (!held || !this.#isMomentary(held)) return;
|
|
257
|
+
const rest = options[index === 0 ? 1 : index - 1];
|
|
258
|
+
if (!rest) return;
|
|
259
|
+
const isFocused = this.group.contains(this.ownerDocument.activeElement);
|
|
260
|
+
this.select(rest);
|
|
261
|
+
if (isFocused) rest.focus();
|
|
262
|
+
}
|
|
263
|
+
#throwBy(direction, pointerId) {
|
|
264
|
+
const options = this.options();
|
|
265
|
+
if (options.length === 0) {
|
|
266
|
+
this.#setChecked(direction === (this.orientation === "horizontal" ? 1 : -1));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const index = this.#checkedIndex(options);
|
|
270
|
+
const option = options[clamp(index + direction, 0, options.length - 1)];
|
|
271
|
+
if (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;
|
|
272
|
+
if (this.#isMomentary(option)) this.#hold(option, pointerId);
|
|
273
|
+
else this.select(option);
|
|
274
|
+
this.#refocus(options);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
//#endregion
|
|
279
|
+
export { SonicSwitch };
|
|
280
|
+
//# sourceMappingURL=switch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { isUnder, optionValue, SonicRadioGroupElement } from '#elements/radio-group.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { clamp } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\tpointerId: number;\n}\n\nconst pressCentreZone = 0.08;\n\nfunction pressedIndex(index: number, count: number, offset: number): number {\n\tif (count === 2) return index === 0 ? 1 : 0;\n\n\tconst side = Math.abs(offset) < pressCentreZone ? 1 : Math.sign(offset);\n\tconst next = index + side;\n\n\treturn next < 0 || next >= count ? index - side : next;\n}\n\nexport class SonicSwitch extends SonicRadioGroupElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRadioGroupElement.observedAttributes,\n\t\t'checked',\n\t\t'orientation',\n\t];\n\n\tget checked(): boolean {\n\t\treturn this.#isDirty ? this.#checked : this.defaultChecked;\n\t}\n\n\tset checked(isChecked: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#checked = isChecked === true;\n\t\tthis.render();\n\t}\n\n\tget defaultChecked(): boolean {\n\t\treturn this.hasAttribute('checked');\n\t}\n\n\tset defaultChecked(isChecked: boolean) {\n\t\tthis.reflect('checked', isChecked);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\toverride get value(): string {\n\t\tconst value = super.value;\n\n\t\treturn value === '' && this.#isBare() ? 'on' : value;\n\t}\n\n\toverride set value(next: string) {\n\t\tsuper.value = next;\n\t}\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bare = renderBare();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#batPress: BatPress | undefined;\n\n\t#checked = false;\n\n\t#holding: Hold | undefined;\n\n\t#isClickSwallowed = false;\n\n\t#isDirty = false;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tif (name === 'checked') this.#isDirty = false;\n\t\tif (this.isDisabled()) this.#holding?.release();\n\t\tsuper.attributeChangedCallback(name);\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.#isDirty = false;\n\t\tsuper.formResetCallback();\n\t}\n\n\tprotected override activate(): void {\n\t\tif (this.#isBare()) this.#bare.click();\n\t\telse super.activate();\n\t}\n\n\tprotected override chooseByClick(option: HTMLButtonElement): void {\n\t\tsuper.chooseByClick(option);\n\t\tif (this.#isMomentary(option)) this.#springBack();\n\t}\n\n\tprotected override chooseByKey(option: HTMLButtonElement, event: KeyboardEvent): void {\n\t\tif (!this.#isMomentary(option)) {\n\t\t\tsuper.chooseByKey(option, event);\n\t\t\treturn;\n\t\t}\n\t\tif (event.repeat) return;\n\n\t\tthis.#hold(option, event.key);\n\t\toption.focus();\n\t}\n\n\tprotected override claimPress(option: HTMLButtonElement, event: PointerEvent): boolean {\n\t\tif (this.#grabBat(event)) return true;\n\t\tif (!this.#isMomentary(option)) return false;\n\n\t\tthis.group.setPointerCapture(event.pointerId);\n\t\tthis.#hold(option, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('defaultChecked', 'checked', 'orientation');\n\t\tsuper.connect(signal);\n\t\tthis.checkStyles(group, 'switch.css');\n\n\t\tthis.#bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.#setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (event.button === 0 && !this.isDisabled()) this.#grabBat(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\n\t\tthis.#holding = bindHold(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled(),\n\t\t\t\tleave: 'focusout',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.toggleState('held', true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.toggleState('held', false);\n\t\t\t\t\tthis.#springBack();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.#isBare() ? this.#bare : super.focusTarget();\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected override releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\tif (this.#isMomentary(option)) return undefined;\n\n\t\tconst options = this.options();\n\t\tif (options.length === 2 && optionValue(option) === this.value) {\n\t\t\treturn options.find((other) => other !== option);\n\t\t}\n\n\t\treturn option;\n\t}\n\n\tprotected override render(): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\n\t\tif (options.length > 0) {\n\t\t\tthis.#bare.remove();\n\t\t\twriteAttribute(group, 'role', 'radiogroup');\n\t\t\tgroup.dataset.sonicPositionCount = String(options.length);\n\t\t\tsuper.render();\n\t\t\tthis.#renderThrow(this.#checkedIndex(options));\n\t\t\treturn;\n\t\t}\n\n\t\tconst bare = this.#bare;\n\t\tconst isChecked = this.checked;\n\n\t\twriteAttribute(group, 'role', undefined);\n\t\tdelete group.dataset.sonicPositionCount;\n\t\tif (bare.parentElement !== group) group.append(bare);\n\t\tbare.disabled = this.isDisabled();\n\t\tbare.setAttribute('aria-checked', String(isChecked));\n\t\tthis.forwardNaming(group, false);\n\t\tthis.forwardNaming(bare, true);\n\t\tconst onSide = this.orientation === 'horizontal' ? 1 : -1;\n\n\t\tgroup.style.setProperty('--_sonic-switch-at', String(isChecked ? onSide : -onSide));\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(isChecked ? this.value : null, String(isChecked));\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\tprotected override restoreState(state: string): void {\n\t\tif (this.#isBare()) this.checked = state === 'true';\n\t\telse super.restoreState(state);\n\t}\n\n\t#along(event: PointerEvent): number {\n\t\treturn this.orientation === 'horizontal' ? event.clientX : event.clientY;\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tconst drop = (event: PointerEvent): void => {\n\t\t\tif (event.pointerId === this.#batPress?.pointerId) this.#batPress = undefined;\n\t\t};\n\n\t\tgroup.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tconst press = this.#batPress;\n\t\t\t\tif (press?.pointerId !== event.pointerId || this.isDisabled()) return;\n\n\t\t\t\tconst delta = this.#along(event) - press.last;\n\t\t\t\tif (Math.abs(delta) < this.#bat.getBoundingClientRect().width / 4) return;\n\n\t\t\t\tpress.hasMoved = true;\n\t\t\t\tpress.last = this.#along(event);\n\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tconst press = this.#batPress;\n\t\t\t\tif (press?.pointerId !== event.pointerId) return;\n\n\t\t\t\tthis.#batPress = undefined;\n\t\t\t\tif (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);\n\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener('pointercancel', drop, { signal });\n\t\tgroup.addEventListener('lostpointercapture', drop, { signal });\n\t}\n\n\t#checkedIndex(options: Array<HTMLButtonElement>): number {\n\t\treturn options.findIndex((option) => optionValue(option) === this.value);\n\t}\n\n\t#grabBat(event: PointerEvent): boolean {\n\t\tif (!isUnder(this.#bat, event.clientX, event.clientY)) return false;\n\n\t\tthis.group.setPointerCapture(event.pointerId);\n\t\tthis.#batPress = { hasMoved: false, last: this.#along(event), pointerId: event.pointerId };\n\n\t\treturn true;\n\t}\n\n\t#hasSprungFrom(option: HTMLButtonElement | undefined): boolean {\n\t\tif (!option || !this.#isMomentary(option)) return false;\n\n\t\tthis.#holding?.release();\n\n\t\treturn this.value !== optionValue(option);\n\t}\n\n\t#hold(option: HTMLButtonElement, by: number | string): void {\n\t\tif (this.#holding?.hold(by) === true) this.select(option);\n\t}\n\n\t#isBare(): boolean {\n\t\treturn this.options().length === 0;\n\t}\n\n\t#isMomentary(option: HTMLButtonElement): boolean {\n\t\tconst options = this.options();\n\t\tconst isEnd = option === options[0] || option === options.at(-1);\n\n\t\treturn (\n\t\t\tisEnd &&\n\t\t\toptions.length > 1 &&\n\t\t\toption.querySelector('[data-sonic-value]')?.hasAttribute('data-sonic-momentary') === true\n\t\t);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.#setChecked(!this.checked);\n\t\t\treturn;\n\t\t}\n\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.#along(event) - start) / size - 0.5;\n\t\tconst option = options[pressedIndex(this.#checkedIndex(options), options.length, offset)];\n\t\tif (!option) return;\n\n\t\tthis.chooseByClick(option);\n\t\tthis.#refocus(options);\n\t}\n\n\t#refocus(options: Array<HTMLButtonElement>): void {\n\t\tif (this.group.matches(':focus-within')) options[this.#checkedIndex(options)]?.focus();\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.options().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#setChecked(isChecked: boolean): void {\n\t\tif (isChecked === this.checked) return;\n\n\t\tthis.checked = isChecked;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#springBack(): void {\n\t\tconst options = this.options();\n\t\tconst index = this.#checkedIndex(options);\n\t\tconst held = options[index];\n\t\tif (!held || !this.#isMomentary(held)) return;\n\n\t\tconst rest = options[index === 0 ? 1 : index - 1];\n\t\tif (!rest) return;\n\n\t\tconst isFocused = this.group.contains(this.ownerDocument.activeElement);\n\n\t\tthis.select(rest);\n\t\tif (isFocused) rest.focus();\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.#setChecked(direction === (this.orientation === 'horizontal' ? 1 : -1));\n\t\t\treturn;\n\t\t}\n\n\t\tconst index = this.#checkedIndex(options);\n\t\tconst option = options[clamp(index + direction, 0, options.length - 1)];\n\t\tif (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;\n\n\t\tif (this.#isMomentary(option)) this.#hold(option, pointerId);\n\t\telse this.select(option);\n\t\tthis.#refocus(options);\n\t}\n}\n"],"mappings":"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"}
|