@xsynaptic/sonic-ui 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +112 -19
- package/dist/default/dat.js +1 -0
- package/dist/default/elements/button.js +2 -0
- package/dist/default/elements/dial.js +2 -0
- package/dist/default/elements/display-element.js +9 -7
- package/dist/default/elements/envelope.js +31 -72
- package/dist/default/elements/field-gesture.js +81 -0
- package/dist/default/elements/meter.js +4 -0
- package/dist/default/elements/position-group.js +42 -26
- package/dist/default/elements/radio-group.js +59 -56
- package/dist/default/elements/scale.js +3 -3
- package/dist/default/elements/slider.js +11 -13
- package/dist/default/elements/split.js +14 -13
- package/dist/default/elements/switch.js +31 -46
- package/dist/default/elements/toggle.js +20 -44
- package/dist/default/elements/value-element.js +65 -91
- package/dist/default/elements/value-gestures.js +22 -22
- package/dist/default/elements/wave-element.js +9 -12
- package/dist/default/elements/waveform.js +42 -241
- package/dist/default/elements/wavestrip.js +10 -5
- package/dist/default/elements/xy.js +67 -112
- package/dist/default/lib/canvas-surface.js +25 -0
- package/dist/default/lib/crossfade.js +1 -0
- package/dist/default/lib/envelope-curve.js +1 -0
- package/dist/default/lib/peaks-request.js +44 -0
- package/dist/default/lib/positions.js +58 -0
- package/dist/default/lib/scrub.js +12 -0
- package/dist/default/lib/value-model.js +62 -0
- package/dist/default/lib/waveform-scene.js +108 -0
- package/dist/default/lib/zoom-gesture.js +97 -1
- package/dist/dev/dat.d.ts +1 -0
- package/dist/dev/dat.js +1 -0
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +2 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +2 -0
- package/dist/dev/elements/dial.js +2 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +5 -3
- package/dist/dev/elements/display-element.js +9 -7
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +31 -72
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +82 -0
- package/dist/dev/elements/field-gesture.js.map +1 -0
- package/dist/dev/elements/meter.d.ts +10 -5
- package/dist/dev/elements/meter.js +4 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/position-group.d.ts +12 -6
- package/dist/dev/elements/position-group.js +42 -26
- package/dist/dev/elements/position-group.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +11 -7
- package/dist/dev/elements/radio-group.js +59 -56
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/scale.js +3 -3
- package/dist/dev/elements/scale.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +4 -2
- package/dist/dev/elements/slider.js +11 -13
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/split.d.ts +1 -0
- package/dist/dev/elements/split.js +14 -13
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +2 -2
- package/dist/dev/elements/switch.js +31 -46
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +20 -44
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +5 -5
- package/dist/dev/elements/value-element.js +65 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +22 -22
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +6 -4
- package/dist/dev/elements/wave-element.js +9 -12
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +19 -21
- package/dist/dev/elements/waveform.js +42 -241
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +5 -1
- package/dist/dev/elements/wavestrip.js +10 -5
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +67 -112
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +2 -1
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +25 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +1 -0
- package/dist/dev/lib/crossfade.js +1 -0
- package/dist/dev/lib/crossfade.js.map +1 -1
- package/dist/dev/lib/envelope-curve.d.ts +1 -0
- package/dist/dev/lib/envelope-curve.js +1 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -1
- package/dist/dev/lib/peaks-request.d.ts +5 -0
- package/dist/dev/lib/peaks-request.js +45 -0
- package/dist/dev/lib/peaks-request.js.map +1 -0
- package/dist/dev/lib/positions.d.ts +7 -0
- package/dist/dev/lib/positions.js +59 -0
- package/dist/dev/lib/positions.js.map +1 -0
- package/dist/dev/lib/scrub.d.ts +8 -0
- package/dist/dev/lib/scrub.js +13 -0
- package/dist/dev/lib/scrub.js.map +1 -0
- package/dist/dev/lib/time-regions.d.ts +1 -0
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/dev/lib/units.d.ts +8 -0
- package/dist/dev/lib/value-model.js +63 -0
- package/dist/dev/lib/value-model.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +1 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -1
- package/dist/dev/lib/waveform-scene.d.ts +11 -0
- package/dist/dev/lib/waveform-scene.js +109 -0
- package/dist/dev/lib/waveform-scene.js.map +1 -0
- package/dist/dev/lib/zoom-gesture.js +97 -1
- package/dist/dev/lib/zoom-gesture.js.map +1 -1
- package/dist/styles/button.css +16 -6
- package/dist/styles/waveform.css +2 -1
- package/package.json +1 -1
- package/dist/default/lib/asked-value.js +0 -21
- package/dist/default/lib/fill-watch.js +0 -29
- package/dist/dev/lib/asked-value.js +0 -22
- package/dist/dev/lib/asked-value.js.map +0 -1
- package/dist/dev/lib/fill-watch.js +0 -30
- package/dist/dev/lib/fill-watch.js.map +0 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { clamp } from "../lib/math.js";
|
|
2
1
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
2
|
import { SonicFormElement } from "./form-element.js";
|
|
4
3
|
import { copyNode } from "../lib/copy-node.js";
|
|
@@ -6,14 +5,9 @@ import { bindKeyPress } from "../lib/key-press.js";
|
|
|
6
5
|
import { placeChildren } from "../lib/render.js";
|
|
7
6
|
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
8
7
|
import { bindDrag } from "../lib/pointer-drag.js";
|
|
8
|
+
import { keyTarget, stepFrom } from "../lib/positions.js";
|
|
9
9
|
|
|
10
10
|
//#region src/elements/radio-group.ts
|
|
11
|
-
const keySteps = /* @__PURE__ */ new Map([
|
|
12
|
-
["ArrowDown", 1],
|
|
13
|
-
["ArrowLeft", -1],
|
|
14
|
-
["ArrowRight", 1],
|
|
15
|
-
["ArrowUp", -1]
|
|
16
|
-
]);
|
|
17
11
|
function isInside(box, x, y) {
|
|
18
12
|
return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
|
|
19
13
|
}
|
|
@@ -38,14 +32,14 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
38
32
|
if (name === "value") this.#value = this.getAttribute("value") ?? "";
|
|
39
33
|
this.render();
|
|
40
34
|
}
|
|
41
|
-
chooseByClick(
|
|
42
|
-
this.select(
|
|
35
|
+
chooseByClick(index) {
|
|
36
|
+
this.select(index);
|
|
43
37
|
}
|
|
44
|
-
chooseByKey(
|
|
45
|
-
this.select(
|
|
46
|
-
|
|
38
|
+
chooseByKey(index, _event) {
|
|
39
|
+
this.select(index);
|
|
40
|
+
this.options()[index]?.focus();
|
|
47
41
|
}
|
|
48
|
-
claimPress(
|
|
42
|
+
claimPress(_index, _event) {
|
|
49
43
|
return false;
|
|
50
44
|
}
|
|
51
45
|
connect(signal) {
|
|
@@ -61,16 +55,19 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
61
55
|
}, signal);
|
|
62
56
|
group.addEventListener("click", (event) => {
|
|
63
57
|
if (event instanceof PointerEvent && event.pointerType !== "") return;
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
58
|
+
const index = this.indexOf(event.target);
|
|
59
|
+
if (index !== -1) this.chooseByClick(index);
|
|
66
60
|
}, { signal });
|
|
67
61
|
this.#bindPress(group, signal);
|
|
68
62
|
bindKeyPress(group, signal);
|
|
69
63
|
group.addEventListener("keydown", (event) => {
|
|
70
64
|
if (event.defaultPrevented) return;
|
|
71
|
-
const
|
|
72
|
-
const next =
|
|
73
|
-
|
|
65
|
+
const index = this.indexOf(event.target);
|
|
66
|
+
const next = index === -1 ? void 0 : keyTarget(this.positions(), index, {
|
|
67
|
+
isWrapping: this.isWrapping(),
|
|
68
|
+
key: event.key
|
|
69
|
+
});
|
|
70
|
+
if (next === void 0) return;
|
|
74
71
|
event.preventDefault();
|
|
75
72
|
this.chooseByKey(next, event);
|
|
76
73
|
}, { signal });
|
|
@@ -78,11 +75,15 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
78
75
|
focusTarget() {
|
|
79
76
|
return this.options().find((option) => option.tabIndex === 0);
|
|
80
77
|
}
|
|
78
|
+
indexOf(target) {
|
|
79
|
+
const option = this.optionOf(target);
|
|
80
|
+
return option ? this.options().indexOf(option) : -1;
|
|
81
|
+
}
|
|
81
82
|
isMirrored(child) {
|
|
82
83
|
return child instanceof Element && child.matches("[data-sonic-value]");
|
|
83
84
|
}
|
|
84
|
-
isOptionDisabled(
|
|
85
|
-
return
|
|
85
|
+
isOptionDisabled(index) {
|
|
86
|
+
return this.positions()[index]?.isDisabled !== false;
|
|
86
87
|
}
|
|
87
88
|
isWrapping() {
|
|
88
89
|
return true;
|
|
@@ -98,14 +99,27 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
98
99
|
placeCopies(copies) {
|
|
99
100
|
this.#copyOptions(copies);
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
-
return option
|
|
102
|
+
positions() {
|
|
103
|
+
return this.options().map((option) => {
|
|
104
|
+
const child = option.querySelector("[data-sonic-value]");
|
|
105
|
+
return {
|
|
106
|
+
isDisabled: child?.hasAttribute("data-sonic-disabled") === true,
|
|
107
|
+
isMomentary: child?.hasAttribute("data-sonic-momentary") === true
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
refocusAt(index) {
|
|
112
|
+
if (this.group.matches(":focus-within")) this.options()[index]?.focus();
|
|
113
|
+
}
|
|
114
|
+
releaseTarget(index) {
|
|
115
|
+
return index;
|
|
103
116
|
}
|
|
104
117
|
render() {
|
|
105
118
|
if (!this.isBound()) return;
|
|
106
119
|
const options = this.options();
|
|
120
|
+
const positions = this.positions();
|
|
107
121
|
const checked = options.find((option) => optionValue(option) === this.#value);
|
|
108
|
-
const enabled = this.isDisabled() ? [] : options.filter((
|
|
122
|
+
const enabled = this.isDisabled() ? [] : options.filter((_option, index) => positions[index]?.isDisabled === false);
|
|
109
123
|
const focusable = enabled.find((option) => option === checked) ?? enabled[0];
|
|
110
124
|
for (const option of options) {
|
|
111
125
|
writeAttribute(option, "disabled", enabled.includes(option) ? void 0 : "");
|
|
@@ -118,45 +132,42 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
118
132
|
restoreState(state) {
|
|
119
133
|
this.value = state;
|
|
120
134
|
}
|
|
121
|
-
select(
|
|
122
|
-
const
|
|
135
|
+
select(index) {
|
|
136
|
+
const option = this.options()[index];
|
|
137
|
+
const next = option && optionValue(option);
|
|
123
138
|
if (next === void 0 || next === this.#value) return;
|
|
124
139
|
this.value = next;
|
|
125
140
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
126
141
|
}
|
|
127
142
|
stepFrom(from, step) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
index = this.isWrapping() ? (next + options.length) % options.length : clamp(next, 0, options.length - 1);
|
|
133
|
-
const option = options[index];
|
|
134
|
-
if (option && !this.isOptionDisabled(option)) return option;
|
|
135
|
-
}
|
|
143
|
+
return stepFrom(this.positions(), from, {
|
|
144
|
+
isWrapping: this.isWrapping(),
|
|
145
|
+
step
|
|
146
|
+
});
|
|
136
147
|
}
|
|
137
148
|
#bindPress(group, signal) {
|
|
138
149
|
bindDrag(group, {
|
|
139
150
|
grab: (event) => {
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
142
|
-
if (this.claimPress(
|
|
151
|
+
const index = this.indexOf(event.target);
|
|
152
|
+
if (index === -1 || this.isDisabled() || this.isOptionDisabled(index)) return;
|
|
153
|
+
if (this.claimPress(index, event)) return;
|
|
143
154
|
const boxes = this.#optionBoxes();
|
|
144
|
-
this.#markPressed(
|
|
155
|
+
this.#markPressed(index);
|
|
145
156
|
return {
|
|
146
157
|
boxes,
|
|
147
|
-
|
|
158
|
+
index
|
|
148
159
|
};
|
|
149
160
|
},
|
|
150
161
|
lift: (press) => {
|
|
151
|
-
if (
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
this.select(
|
|
155
|
-
|
|
162
|
+
if (press.index === void 0 || this.isDisabled()) return;
|
|
163
|
+
const index = this.releaseTarget(press.index);
|
|
164
|
+
if (index === void 0) return;
|
|
165
|
+
this.select(index);
|
|
166
|
+
this.refocusAt(index);
|
|
156
167
|
},
|
|
157
168
|
move: (press, event) => {
|
|
158
|
-
press.
|
|
159
|
-
this.#markPressed(press.
|
|
169
|
+
press.index = press.boxes.find(([, box]) => isInside(box, event.clientX, event.clientY))?.[0];
|
|
170
|
+
this.#markPressed(press.index);
|
|
160
171
|
},
|
|
161
172
|
release: () => {
|
|
162
173
|
this.#markPressed(void 0);
|
|
@@ -180,20 +191,12 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
180
191
|
this.render();
|
|
181
192
|
if (focused) this.#refocus(focused, focusedValue);
|
|
182
193
|
}
|
|
183
|
-
#keyTarget(option, key) {
|
|
184
|
-
const options = this.options();
|
|
185
|
-
const isEnabled = (other) => !this.isOptionDisabled(other);
|
|
186
|
-
if (key === "Home") return options.find((other) => isEnabled(other));
|
|
187
|
-
if (key === "End") return options.findLast((other) => isEnabled(other));
|
|
188
|
-
const step = keySteps.get(key);
|
|
189
|
-
if (step === void 0) return void 0;
|
|
190
|
-
return this.stepFrom(options.indexOf(option), step) ?? option;
|
|
191
|
-
}
|
|
192
194
|
#markPressed(pressed) {
|
|
193
|
-
for (const option of this.options()) option.toggleAttribute("data-sonic-pressed",
|
|
195
|
+
for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-pressed", index === pressed);
|
|
194
196
|
}
|
|
195
197
|
#optionBoxes() {
|
|
196
|
-
|
|
198
|
+
const positions = this.positions();
|
|
199
|
+
return this.options().flatMap((option, index) => positions[index]?.isDisabled === false ? [[index, option.getBoundingClientRect()]] : []);
|
|
197
200
|
}
|
|
198
201
|
#refocus(focused, value) {
|
|
199
202
|
const options = this.options();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { clamp } from '#lib/math.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { placeChildren } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// RTL is not mirrored\nconst keySteps = new Map([\n\t['ArrowDown', 1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', -1],\n]);\n\ninterface Press {\n\tboxes: Array<[HTMLButtonElement, DOMRect]>;\n\toption: HTMLButtonElement | undefined;\n}\n\nfunction isInside(box: DOMRect, x: number, y: number): boolean {\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\treturn isInside(option.getBoundingClientRect(), x, y);\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(option: HTMLButtonElement): void {\n\t\tthis.select(option);\n\t}\n\n\tprotected chooseByKey(option: HTMLButtonElement, _event: KeyboardEvent): void {\n\t\tthis.select(option);\n\t\toption.focus();\n\t}\n\n\tprotected claimPress(_option: HTMLButtonElement, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => this.isMirrored(child),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.placeCopies(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tif (option) this.chooseByClick(option);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tbindKeyPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tconst next = option && this.#keyTarget(option, event.key);\n\t\t\t\tif (!next) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected isMirrored(child: Node): boolean {\n\t\treturn child instanceof Element && child.matches('[data-sonic-value]');\n\t}\n\n\tprotected isOptionDisabled(option: HTMLButtonElement): boolean {\n\t\treturn option.querySelector('[data-sonic-value]')?.hasAttribute('data-sonic-disabled') === true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected placeCopies(copies: Array<Node>): void {\n\t\tthis.#copyOptions(copies);\n\t}\n\n\tprotected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\treturn option;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst options = this.options();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst enabled = this.isDisabled()\n\t\t\t? []\n\t\t\t: options.filter((option) => !this.isOptionDisabled(option));\n\t\tconst focusable = enabled.find((option) => option === checked) ?? enabled[0];\n\n\t\tfor (const option of options) {\n\t\t\twriteAttribute(option, 'disabled', enabled.includes(option) ? undefined : '');\n\t\t\twriteAttribute(option, 'aria-checked', String(option === checked));\n\t\t\twriteAttribute(option, 'tabindex', option === focusable ? '0' : '-1');\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(option: HTMLButtonElement): void {\n\t\tconst next = optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\tprotected stepFrom(from: number, step: number): HTMLButtonElement | undefined {\n\t\tconst options = this.options();\n\t\tlet index = from;\n\n\t\tfor (const _ of options) {\n\t\t\tconst next = index + step;\n\n\t\t\tindex = this.isWrapping()\n\t\t\t\t? (next + options.length) % options.length\n\t\t\t\t: clamp(next, 0, options.length - 1);\n\n\t\t\tconst option = options[index];\n\t\t\tif (option && !this.isOptionDisabled(option)) return option;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<Press>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\t\tif (!option || this.isDisabled() || this.isOptionDisabled(option)) return;\n\t\t\t\t\tif (this.claimPress(option, event)) return;\n\n\t\t\t\t\tconst boxes = this.#optionBoxes();\n\n\t\t\t\t\tthis.#markPressed(option);\n\n\t\t\t\t\treturn { boxes, option };\n\t\t\t\t},\n\t\t\t\tlift: (press) => {\n\t\t\t\t\tif (!press.option || this.isDisabled()) return;\n\n\t\t\t\t\tconst option = this.releaseTarget(press.option);\n\t\t\t\t\tif (!option) return;\n\n\t\t\t\t\tthis.select(option);\n\t\t\t\t\tif (group.matches(':focus-within')) option.focus();\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tpress.option = press.boxes.find(([, box]) =>\n\t\t\t\t\t\tisInside(box, event.clientX, event.clientY),\n\t\t\t\t\t)?.[0];\n\t\t\t\t\tthis.#markPressed(press.option);\n\t\t\t\t},\n\t\t\t\trelease: () => {\n\t\t\t\t\tthis.#markPressed(undefined);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#keyTarget(option: HTMLButtonElement, key: string): HTMLButtonElement | undefined {\n\t\tconst options = this.options();\n\t\tconst isEnabled = (other: HTMLButtonElement): boolean => !this.isOptionDisabled(other);\n\n\t\tif (key === 'Home') return options.find((other) => isEnabled(other));\n\t\tif (key === 'End') return options.findLast((other) => isEnabled(other));\n\n\t\tconst step = keySteps.get(key);\n\t\tif (step === undefined) return undefined;\n\n\t\treturn this.stepFrom(options.indexOf(option), step) ?? option;\n\t}\n\n\t#markPressed(pressed: HTMLButtonElement | undefined): void {\n\t\tfor (const option of this.options()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', option === pressed);\n\t\t}\n\t}\n\n\t#optionBoxes(): Array<[HTMLButtonElement, DOMRect]> {\n\t\treturn this.options()\n\t\t\t.filter((option) => !this.isOptionDisabled(option))\n\t\t\t.map((option) => [option, option.getBoundingClientRect()]);\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import type { Position } from '#lib/positions.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { keyTarget, stepFrom } from '#lib/positions.ts';\nimport { placeChildren } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ninterface Press {\n\tboxes: Array<[number, DOMRect]>;\n\tindex: number | undefined;\n}\n\nfunction isInside(box: DOMRect, x: number, y: number): boolean {\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\treturn isInside(option.getBoundingClientRect(), x, y);\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(index: number): void {\n\t\tthis.select(index);\n\t}\n\n\tprotected chooseByKey(index: number, _event: KeyboardEvent): void {\n\t\tthis.select(index);\n\t\tthis.options()[index]?.focus();\n\t}\n\n\tprotected claimPress(_index: number, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => this.isMirrored(child),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.placeCopies(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\tif (index !== -1) this.chooseByClick(index);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tbindKeyPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\tconst next =\n\t\t\t\t\tindex === -1\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: keyTarget(this.positions(), index, { isWrapping: this.isWrapping(), key: event.key });\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected indexOf(target: EventTarget | null): number {\n\t\tconst option = this.optionOf(target);\n\n\t\treturn option ? this.options().indexOf(option) : -1;\n\t}\n\n\tprotected isMirrored(child: Node): boolean {\n\t\treturn child instanceof Element && child.matches('[data-sonic-value]');\n\t}\n\n\tprotected isOptionDisabled(index: number): boolean {\n\t\treturn this.positions()[index]?.isDisabled !== false;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected placeCopies(copies: Array<Node>): void {\n\t\tthis.#copyOptions(copies);\n\t}\n\n\tprotected positions(): Array<Position> {\n\t\treturn this.options().map((option) => {\n\t\t\tconst child = option.querySelector('[data-sonic-value]');\n\n\t\t\treturn {\n\t\t\t\tisDisabled: child?.hasAttribute('data-sonic-disabled') === true,\n\t\t\t\tisMomentary: child?.hasAttribute('data-sonic-momentary') === true,\n\t\t\t};\n\t\t});\n\t}\n\n\tprotected refocusAt(index: number): void {\n\t\tif (this.group.matches(':focus-within')) this.options()[index]?.focus();\n\t}\n\n\tprotected releaseTarget(index: number): number | undefined {\n\t\treturn index;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst options = this.options();\n\t\tconst positions = this.positions();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst enabled = this.isDisabled()\n\t\t\t? []\n\t\t\t: options.filter((_option, index) => positions[index]?.isDisabled === false);\n\t\tconst focusable = enabled.find((option) => option === checked) ?? enabled[0];\n\n\t\tfor (const option of options) {\n\t\t\twriteAttribute(option, 'disabled', enabled.includes(option) ? undefined : '');\n\t\t\twriteAttribute(option, 'aria-checked', String(option === checked));\n\t\t\twriteAttribute(option, 'tabindex', option === focusable ? '0' : '-1');\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(index: number): void {\n\t\tconst option = this.options()[index];\n\t\tconst next = option && optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\tprotected stepFrom(from: number, step: number): number | undefined {\n\t\treturn stepFrom(this.positions(), from, { isWrapping: this.isWrapping(), step });\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<Press>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\t\tif (index === -1 || this.isDisabled() || this.isOptionDisabled(index)) return;\n\t\t\t\t\tif (this.claimPress(index, event)) return;\n\n\t\t\t\t\tconst boxes = this.#optionBoxes();\n\n\t\t\t\t\tthis.#markPressed(index);\n\n\t\t\t\t\treturn { boxes, index };\n\t\t\t\t},\n\t\t\t\tlift: (press) => {\n\t\t\t\t\tif (press.index === undefined || this.isDisabled()) return;\n\n\t\t\t\t\tconst index = this.releaseTarget(press.index);\n\t\t\t\t\tif (index === undefined) return;\n\n\t\t\t\t\tthis.select(index);\n\t\t\t\t\tthis.refocusAt(index);\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tpress.index = press.boxes.find(([, box]) =>\n\t\t\t\t\t\tisInside(box, event.clientX, event.clientY),\n\t\t\t\t\t)?.[0];\n\t\t\t\t\tthis.#markPressed(press.index);\n\t\t\t\t},\n\t\t\t\trelease: () => {\n\t\t\t\t\tthis.#markPressed(undefined);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#markPressed(pressed: number | undefined): void {\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', index === pressed);\n\t\t}\n\t}\n\n\t#optionBoxes(): Array<[number, DOMRect]> {\n\t\tconst positions = this.positions();\n\n\t\treturn this.options().flatMap((option, index) =>\n\t\t\tpositions[index]?.isDisabled === false\n\t\t\t\t? [[index, option.getBoundingClientRect()] satisfies [number, DOMRect]]\n\t\t\t\t: [],\n\t\t);\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -17,9 +17,9 @@ function scaleMark(original) {
|
|
|
17
17
|
return mark;
|
|
18
18
|
}
|
|
19
19
|
function bindScale(element, { control, marks }, signal) {
|
|
20
|
-
const {
|
|
20
|
+
const { model } = linkValue(element);
|
|
21
21
|
const render = () => {
|
|
22
|
-
const { proportionOf } = mapping();
|
|
22
|
+
const { proportionOf } = model.mapping();
|
|
23
23
|
for (const mark of marks.children) {
|
|
24
24
|
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) continue;
|
|
25
25
|
const at = String(proportionOf(scaleValue(mark)));
|
|
@@ -36,7 +36,7 @@ function bindScale(element, { control, marks }, signal) {
|
|
|
36
36
|
render();
|
|
37
37
|
}
|
|
38
38
|
}, signal);
|
|
39
|
-
signal.addEventListener("abort", watch(render), { once: true });
|
|
39
|
+
signal.addEventListener("abort", model.watch(render), { once: true });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scale.js","names":[],"sources":["../../../src/elements/scale.ts"],"sourcesContent":["import type { SonicValueElement } from '#elements/value-element.ts';\n\nimport { linkValue } from '#elements/value-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { placeChildren } from '#lib/render.ts';\n\ntype ScaleMark = HTMLElement | SVGElement;\n\nfunction scaleValue(mark: ScaleMark): number {\n\tconst text = mark.dataset.sonicValue?.trim();\n\n\treturn text ? Number(text) : NaN;\n}\n\nfunction scaleMark(original: ChildNode): ScaleMark | undefined {\n\tconst mark = copyNode(original);\n\tif (!(mark instanceof HTMLElement || mark instanceof SVGElement)) return undefined;\n\tif (!Number.isFinite(scaleValue(mark))) return undefined;\n\n\tconst isTick = mark.childElementCount === 0 && mark.textContent.trim() === '';\n\n\tmark.classList.add(isTick ? 'sonic-scale-tick' : 'sonic-scale-label');\n\n\treturn mark;\n}\n\nexport function bindScale(\n\telement: SonicValueElement,\n\t{ control, marks }: { control: HTMLElement; marks: HTMLElement },\n\tsignal: AbortSignal,\n): void {\n\tconst {
|
|
1
|
+
{"version":3,"file":"scale.js","names":[],"sources":["../../../src/elements/scale.ts"],"sourcesContent":["import type { SonicValueElement } from '#elements/value-element.ts';\n\nimport { linkValue } from '#elements/value-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { placeChildren } from '#lib/render.ts';\n\ntype ScaleMark = HTMLElement | SVGElement;\n\nfunction scaleValue(mark: ScaleMark): number {\n\tconst text = mark.dataset.sonicValue?.trim();\n\n\treturn text ? Number(text) : NaN;\n}\n\nfunction scaleMark(original: ChildNode): ScaleMark | undefined {\n\tconst mark = copyNode(original);\n\tif (!(mark instanceof HTMLElement || mark instanceof SVGElement)) return undefined;\n\tif (!Number.isFinite(scaleValue(mark))) return undefined;\n\n\tconst isTick = mark.childElementCount === 0 && mark.textContent.trim() === '';\n\n\tmark.classList.add(isTick ? 'sonic-scale-tick' : 'sonic-scale-label');\n\n\treturn mark;\n}\n\nexport function bindScale(\n\telement: SonicValueElement,\n\t{ control, marks }: { control: HTMLElement; marks: HTMLElement },\n\tsignal: AbortSignal,\n): void {\n\tconst { model } = linkValue(element);\n\tconst render = (): void => {\n\t\tconst { proportionOf } = model.mapping();\n\n\t\tfor (const mark of marks.children) {\n\t\t\tif (!(mark instanceof HTMLElement || mark instanceof SVGElement)) continue;\n\n\t\t\tconst at = String(proportionOf(scaleValue(mark)));\n\n\t\t\tif (mark.style.getPropertyValue('--_sonic-scale-at') !== at) {\n\t\t\t\tmark.style.setProperty('--_sonic-scale-at', at);\n\t\t\t}\n\t\t}\n\t};\n\n\tmirrorChildren(\n\t\telement,\n\t\t{\n\t\t\tcontrol,\n\t\t\tcopy: scaleMark,\n\t\t\tisCopied: (child) => child instanceof Element && child.matches('[data-sonic-value]'),\n\t\t\tisPassed: (child) => child instanceof Element && child.matches('.sonic-led'),\n\t\t\tplace: (copies) => {\n\t\t\t\tplaceChildren(marks, copies);\n\t\t\t\trender();\n\t\t\t},\n\t\t},\n\t\tsignal,\n\t);\n\tsignal.addEventListener('abort', model.watch(render), { once: true });\n}\n"],"mappings":";;;;;;AASA,SAAS,WAAW,MAAyB;CAC5C,MAAM,OAAO,KAAK,QAAQ,YAAY,KAAK;CAE3C,OAAO,OAAO,OAAO,IAAI,IAAI;AAC9B;AAEA,SAAS,UAAU,UAA4C;CAC9D,MAAM,OAAO,SAAS,QAAQ;CAC9B,IAAI,EAAE,gBAAgB,eAAe,gBAAgB,aAAa,OAAO;CACzE,IAAI,CAAC,OAAO,SAAS,WAAW,IAAI,CAAC,GAAG,OAAO;CAE/C,MAAM,SAAS,KAAK,sBAAsB,KAAK,KAAK,YAAY,KAAK,MAAM;CAE3E,KAAK,UAAU,IAAI,SAAS,qBAAqB,mBAAmB;CAEpE,OAAO;AACR;AAEA,SAAgB,UACf,SACA,EAAE,SAAS,SACX,QACO;CACP,MAAM,EAAE,UAAU,UAAU,OAAO;CACnC,MAAM,eAAqB;EAC1B,MAAM,EAAE,iBAAiB,MAAM,QAAQ;EAEvC,KAAK,MAAM,QAAQ,MAAM,UAAU;GAClC,IAAI,EAAE,gBAAgB,eAAe,gBAAgB,aAAa;GAElE,MAAM,KAAK,OAAO,aAAa,WAAW,IAAI,CAAC,CAAC;GAEhD,IAAI,KAAK,MAAM,iBAAiB,mBAAmB,MAAM,IACxD,KAAK,MAAM,YAAY,qBAAqB,EAAE;EAEhD;CACD;CAEA,eACC,SACA;EACC;EACA,MAAM;EACN,WAAW,UAAU,iBAAiB,WAAW,MAAM,QAAQ,oBAAoB;EACnF,WAAW,UAAU,iBAAiB,WAAW,MAAM,QAAQ,YAAY;EAC3E,QAAQ,WAAW;GAClB,cAAc,OAAO,MAAM;GAC3B,OAAO;EACR;CACD,GACA,MACD;CACA,OAAO,iBAAiB,SAAS,MAAM,MAAM,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AACrE"}
|
|
@@ -9,12 +9,15 @@ declare global {
|
|
|
9
9
|
export declare class SonicSlider extends SonicValueElement {
|
|
10
10
|
#private;
|
|
11
11
|
static readonly observedAttributes: string[];
|
|
12
|
+
/** Loaded spans, in the slider's own values */
|
|
12
13
|
get buffered(): Array<[number, number]>;
|
|
13
14
|
set buffered(regions: TimeRegions | undefined);
|
|
14
15
|
get groovePress(): 'jump' | 'none';
|
|
15
16
|
set groovePress(gesture: 'jump' | 'none' | undefined);
|
|
17
|
+
/** How far a modulation source can push the value, in value units; signed */
|
|
16
18
|
get modulation(): number;
|
|
17
19
|
set modulation(amount: number | undefined);
|
|
20
|
+
/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */
|
|
18
21
|
get modulationValue(): number | undefined;
|
|
19
22
|
set modulationValue(value: null | number | undefined);
|
|
20
23
|
get orientation(): 'horizontal' | 'vertical';
|
|
@@ -28,8 +31,7 @@ export declare class SonicSlider extends SonicValueElement {
|
|
|
28
31
|
protected controlOrientation(): 'horizontal' | 'vertical';
|
|
29
32
|
protected draw(): void;
|
|
30
33
|
protected focusTarget(): HTMLElement;
|
|
31
|
-
protected
|
|
32
|
-
protected holdChanged(): void;
|
|
34
|
+
protected scrubChanged(): void;
|
|
33
35
|
protected scrubsKeyRepeat(): boolean;
|
|
34
36
|
protected springTarget(): number | undefined;
|
|
35
37
|
}
|
|
@@ -3,6 +3,7 @@ import { requireChild, template } from "../lib/render.js";
|
|
|
3
3
|
import { createModulation } from "./modulation.js";
|
|
4
4
|
import { SonicValueElement } from "./value-element.js";
|
|
5
5
|
import { bindScale } from "./scale.js";
|
|
6
|
+
import { scrubRegions } from "../lib/scrub.js";
|
|
6
7
|
import { sortedRegions } from "../lib/time-regions.js";
|
|
7
8
|
|
|
8
9
|
//#region src/elements/slider.ts
|
|
@@ -39,6 +40,7 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
39
40
|
"scrub",
|
|
40
41
|
"spring"
|
|
41
42
|
];
|
|
43
|
+
/** Loaded spans, in the slider's own values */
|
|
42
44
|
get buffered() {
|
|
43
45
|
return this.#buffered.map(([start, end]) => [start, end]);
|
|
44
46
|
}
|
|
@@ -52,12 +54,14 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
52
54
|
set groovePress(gesture) {
|
|
53
55
|
this.reflect("groove-press", gesture);
|
|
54
56
|
}
|
|
57
|
+
/** How far a modulation source can push the value, in value units; signed */
|
|
55
58
|
get modulation() {
|
|
56
59
|
return this.numberAttribute("modulation", 0);
|
|
57
60
|
}
|
|
58
61
|
set modulation(amount) {
|
|
59
62
|
this.reflect("modulation", amount);
|
|
60
63
|
}
|
|
64
|
+
/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */
|
|
61
65
|
get modulationValue() {
|
|
62
66
|
return this.#modulation.value();
|
|
63
67
|
}
|
|
@@ -146,11 +150,7 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
146
150
|
focusTarget() {
|
|
147
151
|
return this.#slider;
|
|
148
152
|
}
|
|
149
|
-
|
|
150
|
-
super.heldWrite(next);
|
|
151
|
-
if (this.scrub) this.render();
|
|
152
|
-
}
|
|
153
|
-
holdChanged() {
|
|
153
|
+
scrubChanged() {
|
|
154
154
|
if (this.scrub) this.render();
|
|
155
155
|
}
|
|
156
156
|
scrubsKeyRepeat() {
|
|
@@ -199,18 +199,16 @@ var SonicSlider = class extends SonicValueElement {
|
|
|
199
199
|
}
|
|
200
200
|
#renderScrub(slider) {
|
|
201
201
|
const { style } = slider;
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const scrubbed = proportionOf(this.value);
|
|
206
|
-
if (isHeld) style.setProperty("--_sonic-slider-played", String(played));
|
|
202
|
+
const state = this.scrubState();
|
|
203
|
+
const { played, scrub } = scrubRegions(state, this.value, this.mapping());
|
|
204
|
+
if (this.scrub && state.from !== void 0) style.setProperty("--_sonic-slider-played", String(played));
|
|
207
205
|
else style.removeProperty("--_sonic-slider-played");
|
|
208
|
-
if (
|
|
206
|
+
if (scrub === void 0 || !this.scrub) {
|
|
209
207
|
style.removeProperty("--_sonic-slider-scrub-region");
|
|
210
208
|
return;
|
|
211
209
|
}
|
|
212
|
-
const from = grooveStop(Math.min(played,
|
|
213
|
-
const to = grooveStop(Math.max(played,
|
|
210
|
+
const from = grooveStop(Math.min(played, scrub));
|
|
211
|
+
const to = grooveStop(Math.max(played, scrub));
|
|
214
212
|
style.setProperty("--_sonic-slider-scrub-region", `linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`);
|
|
215
213
|
}
|
|
216
214
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t'groove',\n\t\t\t['glass', '.sonic-slider-readout'],\n\t\t]);\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override heldWrite(next: number): void {\n\t\tsuper.heldWrite(next);\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override holdChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst isHeld = this.scrub && this.heldFrom() !== undefined;\n\t\tconst played = proportionOf(this.playback());\n\t\tconst scrubbed = proportionOf(this.value);\n\n\t\tif (isHeld) style.setProperty('--_sonic-slider-played', String(played));\n\t\telse style.removeProperty('--_sonic-slider-played');\n\n\t\tif (played === scrubbed || !isHeld || !this.isRevealed()) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrubbed));\n\t\tconst to = grooveStop(Math.max(played, scrubbed));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t'groove',\n\t\t\t['glass', '.sonic-slider-readout'],\n\t\t]);\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;AAqBA,SAAS,WAAW,YAA4B;CAC/C,IAAI,cAAc,GAAG,OAAO;CAC5B,IAAI,cAAc,GAAG,OAAO;CAE5B,OAAO,QAAQ,OAAO,UAAU,EAAE;AACnC;AAEA,MAAM,eAAe,SACT;;;;;;;;;;;;;;;;;;;IAoBX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;EACA;EACA;CACD;;CAIA,IAAI,WAAoC;EACvC,OAAO,KAAK,UAAU,KAAK,CAAC,OAAO,SAAS,CAAC,OAAO,GAAG,CAAC;CACzD;CAEA,IAAI,SAAS,SAAkC;EAC9C,KAAK,YAAY,cAAc,OAAO;EACtC,KAAK,OAAO;CACb;CAEA,IAAI,cAA+B;EAClC,OAAO,KAAK,aAAa,cAAc,MAAM,SAAS,SAAS;CAChE;CAEA,IAAI,YAAY,SAAsC;EACrD,KAAK,QAAQ,gBAAgB,OAAO;CACrC;;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,IAAI,cAAyC;EAC5C,OAAO,KAAK,aAAa,aAAa,MAAM,aAAa,aAAa;CACvE;CAEA,IAAI,YAAY,WAAkD;EACjE,KAAK,QAAQ,eAAe,SAAS;CACtC;CAEA,IAAI,QAAiB;EACpB,OAAO,KAAK,aAAa,OAAO;CACjC;CAEA,IAAI,MAAM,aAAsB;EAC/B,KAAK,QAAQ,SAAS,WAAW;CAClC;CAEA,IAAI,SAAkB;EACrB,OAAO,KAAK,aAAa,QAAQ;CAClC;CAEA,IAAI,OAAO,UAAmB;EAC7B,KAAK,QAAQ,UAAU,QAAQ;CAChC;CAEA,YAAqC,CAAC;CAEtC,AAAS,UAAU,aAAa;CAEhC,AAAS,cAAc,iBAAiB,KAAK,SAAS,WAAW,gBAAgB;EAChF,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBACJ,YACA,eACA,mBACA,cACA,eACA,SACA,QACD;EACA,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EACpB,MAAM,MAAM,aAAa,QAAQ,qBAAqB,cAAc;EAEpE,UACC,MACA;GAAE,SAAS;GAAQ,OAAO,aAAa,QAAQ,uBAAuB,cAAc;EAAE,GACtF,MACD;EACA,OAAO,iBACN,kBACC,UAAU;GACV,IAAI,MAAM,iBAAiB,yBAAyB,KAAK,YAAY,aAAa,KAAK;EACxF,GACA,EAAE,OAAO,CACV;EACA,KAAK,UAAU,QAAQ,QAAQ;GAC9B,eAAe,KAAK,SAAS,KAAK,gBAAgB;GAClD,aAAa;IACZ,KAAK,cAAc,MAAM;GAC1B;GACA,UAAU,UAAU;IACnB,MAAM,OAAO,KAAK,MAAM,QAAQ,GAAG;IACnC,MAAM,KAAK,iBAAiB,KAAK,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,QAAQ;IAChF,MAAM,UAAU,KAAK,QAAQ;IAE7B,OAAO,QAAQ,KAAK,QAAQ,QAAQ,EAAE,CAAC;GACxC;EACD,CAAC;EACD,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,cAAc;GACtC;GACA;GACA;GACA;GACA,CAAC,SAAS,uBAAuB;EAClC,CAAC;EACD,KAAK,aAAa,QAAQ,SAAS,UAAU;GAC5C,MAAM,OAAO,KAAK,MAAM,QAAQ,GAAG;GACnC,IAAI,MAAM,kBAAkB,QAAQ,IAAI,SAAS,MAAM,MAAM,GAAG,OAAO;GAEvE,IAAI,KAAK,gBAAgB,QAAQ;GAEjC,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,QAAQ,CAAC;GAExF,OAAO;EACR,CAAC;CACF;CAEA,AAAmB,qBAAgD;EAClE,OAAO,KAAK;CACb;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK;GACjB,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;EACD,KAAK,gBAAgB,KAAK,OAAO;EACjC,KAAK,aAAa,KAAK,OAAO;EAC9B,KAAK,cAAc,KAAK,OAAO;CAChC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,eAAqB;EACvC,IAAI,KAAK,OAAO,KAAK,OAAO;CAC7B;CAEA,AAAmB,kBAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,eAAmC;EACrD,OAAO,KAAK,SAAS,KAAK,YAAY,IAAI;CAC3C;CAEA,MAAM,QAAqB,KAA8B;EACxD,MAAM,MAAM,OAAO,sBAAsB;EACzC,MAAM,SAAS,IAAI,sBAAsB;EAEzC,OAAO,KAAK,gBAAgB,aACzB;GACA,WAAW,UAAU,CAAC,MAAM;GAC5B,SAAS,OAAO,SAAS,IAAI,IAAI;GACjC,UAAU,KAAK,IAAI,GAAG,IAAI,SAAS,OAAO,MAAM;EACjD,IACC;GACA,WAAW,UAAU,MAAM;GAC3B,SAAS,IAAI,OAAO,OAAO,QAAQ;GACnC,UAAU,KAAK,IAAI,GAAG,IAAI,QAAQ,OAAO,KAAK;EAC/C;CACH;CAEA,cAAc,QAA2B;EACxC,IAAI,CAAC,KAAK,OAAO;GAChB,OAAO,MAAM,eAAe,+BAA+B;GAC3D;EACD;EAEA,OAAO,MAAM,YACZ,iCACA,OAAO,KAAK,QAAQ,CAAC,CAAC,aAAa,KAAK,aAAa,CAAC,CAAC,CACxD;CACD;CAEA,gBAAgB,QAA2B;EAC1C,MAAM,WAAW,KAAK;EAEtB,IAAI,SAAS,WAAW,GAAG;GAC1B,OAAO,MAAM,eAAe,kCAAkC;GAC9D;EACD;EAEA,MAAM,EAAE,iBAAiB,KAAK,QAAQ;EACtC,MAAM,QAAQ,SAAS,SAAS,CAAC,OAAO,SAAS;GAChD,MAAM,OAAO,WAAW,aAAa,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC;GAC1D,MAAM,KAAK,WAAW,aAAa,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC;GAExD,OAAO;IACN,eAAe;IACf,iCAAiC,KAAK,GAAG;IACzC,eAAe;GAChB;EACD,CAAC;EAED,OAAO,MAAM,YACZ,oCACA,gDAAgD,MAAM,KAAK,IAAI,EAAE,EAClE;CACD;CAEA,aAAa,QAA2B;EACvC,MAAM,EAAE,UAAU;EAClB,MAAM,QAAQ,KAAK,WAAW;EAC9B,MAAM,EAAE,QAAQ,UAAU,aAAa,OAAO,KAAK,OAAO,KAAK,QAAQ,CAAC;EAExE,IAAI,KAAK,SAAS,MAAM,SAAS,QAChC,MAAM,YAAY,0BAA0B,OAAO,MAAM,CAAC;OACpD,MAAM,eAAe,wBAAwB;EAEpD,IAAI,UAAU,UAAa,CAAC,KAAK,OAAO;GACvC,MAAM,eAAe,8BAA8B;GACnD;EACD;EAEA,MAAM,OAAO,WAAW,KAAK,IAAI,QAAQ,KAAK,CAAC;EAC/C,MAAM,KAAK,WAAW,KAAK,IAAI,QAAQ,KAAK,CAAC;EAE7C,MAAM,YACL,gCACA,4DAA4D,KAAK,+BAA+B,KAAK,GAAG,GAAG,gBAAgB,GAAG,EAC/H;CACD;AACD"}
|
|
@@ -11,6 +11,7 @@ export declare class SonicSplit extends SonicElement {
|
|
|
11
11
|
static readonly observedAttributes: string[];
|
|
12
12
|
get mode(): SplitMode;
|
|
13
13
|
set mode(name: SplitMode | undefined);
|
|
14
|
+
/** What the members add up to; unset, it is their sum when they connect */
|
|
14
15
|
get total(): number | undefined;
|
|
15
16
|
set total(value: number | undefined);
|
|
16
17
|
attributeChangedCallback(name: string): void;
|
|
@@ -22,7 +22,7 @@ function gridLimits(mapping, [low, high]) {
|
|
|
22
22
|
return [bottom < low - gridSlop ? mapping.keyTarget("ArrowRight", bottom) ?? bottom : bottom, top > high + gridSlop ? mapping.keyTarget("ArrowLeft", top) ?? top : top];
|
|
23
23
|
}
|
|
24
24
|
function modelOf(member, value, isFree) {
|
|
25
|
-
const mapping = member.link.mapping();
|
|
25
|
+
const mapping = member.link.model.mapping();
|
|
26
26
|
const [first, last] = mapping.bounds;
|
|
27
27
|
return {
|
|
28
28
|
isFree,
|
|
@@ -46,6 +46,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
46
46
|
set mode(name) {
|
|
47
47
|
this.reflect("mode", name);
|
|
48
48
|
}
|
|
49
|
+
/** What the members add up to; unset, it is their sum when they connect */
|
|
49
50
|
get total() {
|
|
50
51
|
return this.optionalNumberAttribute("total");
|
|
51
52
|
}
|
|
@@ -105,7 +106,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
105
106
|
}
|
|
106
107
|
#compensate() {
|
|
107
108
|
this.#isQueued = false;
|
|
108
|
-
for (const mover of this.#members) if (mover.link.value
|
|
109
|
+
for (const mover of this.#members) if (mover.link.model.value !== mover.seen) this.#move(mover);
|
|
109
110
|
}
|
|
110
111
|
#find() {
|
|
111
112
|
const found = [];
|
|
@@ -121,18 +122,18 @@ var SonicSplit = class extends SonicElement {
|
|
|
121
122
|
}
|
|
122
123
|
#join(element) {
|
|
123
124
|
const link = linkValue(element);
|
|
124
|
-
const unwatch = link.watch(() => {
|
|
125
|
+
const unwatch = link.model.watch(() => {
|
|
125
126
|
if (this.#isWriting || this.#isQueued) return;
|
|
126
127
|
this.#isQueued = true;
|
|
127
128
|
queueMicrotask(() => {
|
|
128
129
|
if (this.#isQueued) this.#compensate();
|
|
129
130
|
});
|
|
130
131
|
});
|
|
131
|
-
link.
|
|
132
|
+
link.model.setLanding((target, direction) => this.#land(element, target, direction));
|
|
132
133
|
return {
|
|
133
134
|
element,
|
|
134
135
|
link,
|
|
135
|
-
seen: link.value
|
|
136
|
+
seen: link.model.value,
|
|
136
137
|
unwatch
|
|
137
138
|
};
|
|
138
139
|
}
|
|
@@ -155,7 +156,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
155
156
|
for (const [index, member] of this.#members.entries()) {
|
|
156
157
|
const model = this.#model(values, member);
|
|
157
158
|
const value = values[index] ?? member.seen;
|
|
158
|
-
member.link.
|
|
159
|
+
member.link.model.setLimit(isLocked(member) ? [value, value] : gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)));
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
#model(values, mover) {
|
|
@@ -177,7 +178,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
177
178
|
total: this.#total
|
|
178
179
|
}, {
|
|
179
180
|
index,
|
|
180
|
-
target: mover.link.value
|
|
181
|
+
target: mover.link.model.value
|
|
181
182
|
}), mover);
|
|
182
183
|
this.#limit();
|
|
183
184
|
}
|
|
@@ -187,7 +188,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
187
188
|
const snapshot = this.#snapshot;
|
|
188
189
|
if (snapshot?.mover.element !== event.target) return;
|
|
189
190
|
this.#snapshot = void 0;
|
|
190
|
-
const changed = this.#members.filter((member, index) => member !== snapshot.mover && member.link.value
|
|
191
|
+
const changed = this.#members.filter((member, index) => member !== snapshot.mover && member.link.model.value !== snapshot.values[index]);
|
|
191
192
|
queueMicrotask(() => {
|
|
192
193
|
this.#isWriting = true;
|
|
193
194
|
try {
|
|
@@ -213,8 +214,8 @@ var SonicSplit = class extends SonicElement {
|
|
|
213
214
|
#unbind() {
|
|
214
215
|
for (const { link, unwatch } of this.#members) {
|
|
215
216
|
unwatch();
|
|
216
|
-
link.
|
|
217
|
-
link.
|
|
217
|
+
link.model.setLanding(void 0);
|
|
218
|
+
link.model.setLimit(void 0);
|
|
218
219
|
}
|
|
219
220
|
this.#members = [];
|
|
220
221
|
this.#snapshot = void 0;
|
|
@@ -242,14 +243,14 @@ var SonicSplit = class extends SonicElement {
|
|
|
242
243
|
try {
|
|
243
244
|
for (const [index, member] of this.#members.entries()) {
|
|
244
245
|
const value = values[index];
|
|
245
|
-
if (value === void 0 || value === member.link.value
|
|
246
|
-
member.link.
|
|
246
|
+
if (value === void 0 || value === member.link.model.value) continue;
|
|
247
|
+
member.link.model.setLimit(void 0);
|
|
247
248
|
member.link.input(value, member === mover);
|
|
248
249
|
}
|
|
249
250
|
} finally {
|
|
250
251
|
this.#isWriting = false;
|
|
251
252
|
}
|
|
252
|
-
for (const member of this.#members) member.seen = member.link.value
|
|
253
|
+
for (const member of this.#members) member.seen = member.link.model.value;
|
|
253
254
|
}
|
|
254
255
|
};
|
|
255
256
|
|