@xsynaptic/sonic-ui 0.5.0 → 0.6.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 +3 -1
- package/dist/default/dat.js +5 -4
- package/dist/default/define/button.js +1 -1
- package/dist/default/define.js +2 -2
- package/dist/default/elements/button.js +3 -3
- package/dist/default/elements/envelope.js +32 -16
- package/dist/default/elements/meter.js +11 -7
- package/dist/default/elements/number.js +1 -1
- package/dist/default/elements/radio-group.js +43 -45
- package/dist/default/elements/readout.js +1 -1
- package/dist/default/elements/sonic-element.js +56 -29
- package/dist/default/elements/switch.js +32 -38
- package/dist/default/elements/value-element.js +111 -108
- package/dist/default/elements/wave-element.js +5 -2
- package/dist/default/elements/waveform.js +35 -12
- package/dist/default/elements/wavestrip.js +18 -15
- package/dist/default/elements/xy.js +32 -18
- package/dist/default/index.js +2 -2
- package/dist/default/lib/adsr-shape.js +5 -0
- package/dist/default/lib/ballistics.js +2 -2
- package/dist/default/lib/canvas-surface.js +13 -2
- package/dist/default/lib/distribute.js +1 -1
- package/dist/default/lib/drag-step.js +4 -4
- package/dist/default/lib/frame-timeline.js +2 -1
- package/dist/default/lib/marker-band.js +54 -10
- package/dist/default/lib/marker-rider.js +10 -4
- package/dist/default/lib/math.js +6 -1
- package/dist/default/lib/pointer-drag.js +8 -5
- package/dist/default/lib/resample-peaks.js +11 -1
- package/dist/default/lib/write-attribute.js +1 -0
- package/dist/dev/dat.d.ts +2 -2
- package/dist/dev/dat.js +5 -4
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/define/button.js +1 -1
- package/dist/dev/define.d.ts +4 -4
- package/dist/dev/define.js +2 -2
- package/dist/dev/elements/button.js +3 -3
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +1 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.js +32 -16
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +1 -1
- package/dist/dev/elements/meter.js +11 -7
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +1 -1
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.js +43 -45
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout.js +1 -1
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +1 -1
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +4 -2
- package/dist/dev/elements/sonic-element.js +79 -29
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/switch.js +32 -38
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +3 -3
- package/dist/dev/elements/value-element.js +111 -108
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +1 -0
- package/dist/dev/elements/wave-element.js +5 -2
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +1 -1
- package/dist/dev/elements/waveform.js +35 -12
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.js +18 -15
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +3 -3
- package/dist/dev/elements/xy.js +32 -18
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +2 -2
- package/dist/dev/index.js +2 -2
- package/dist/dev/lib/adsr-shape.js +5 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -1
- package/dist/dev/lib/ballistics.js +2 -2
- package/dist/dev/lib/ballistics.js.map +1 -1
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +13 -2
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/distribute.js +1 -1
- package/dist/dev/lib/distribute.js.map +1 -1
- package/dist/dev/lib/drag-step.js +4 -4
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -1
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/marker-band.js +54 -10
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/marker-rider.js +10 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/math.js +6 -1
- package/dist/dev/lib/math.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +8 -5
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +11 -1
- package/dist/dev/lib/resample-peaks.js.map +1 -1
- package/dist/dev/lib/write-attribute.js +1 -0
- package/dist/dev/lib/write-attribute.js.map +1 -1
- package/dist/skins/amber.css +8 -0
- package/dist/skins/flat.css +25 -0
- package/dist/skins/ivory.css +1 -0
- package/dist/skins/lime.css +2 -0
- package/dist/skins/slate.css +2 -0
- package/dist/styles/button.css +38 -10
- package/dist/styles/dial.css +24 -16
- package/dist/styles/envelope.css +56 -11
- package/dist/styles/led.css +3 -3
- package/dist/styles/material.css +83 -41
- package/dist/styles/meter.css +8 -4
- package/dist/styles/panel.css +5 -3
- package/dist/styles/segmented.css +27 -5
- package/dist/styles/slider.css +2 -1
- package/dist/styles/switch.css +59 -20
- package/dist/styles/waveform.css +28 -5
- package/dist/styles/wavestrip.css +23 -6
- package/dist/styles/xy.css +61 -24
- package/package.json +2 -5
|
@@ -2,6 +2,7 @@ import { clamp } from "../lib/math.js";
|
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
3
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
4
|
import { bindHold } from "../lib/hold.js";
|
|
5
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
5
6
|
import { SonicRadioGroupElement, isUnder, optionValue } from "./radio-group.js";
|
|
6
7
|
|
|
7
8
|
//#region src/elements/switch.ts
|
|
@@ -60,7 +61,6 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
60
61
|
group = renderSwitch();
|
|
61
62
|
#bare = renderBare();
|
|
62
63
|
#bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
|
|
63
|
-
#batPress;
|
|
64
64
|
#checked = false;
|
|
65
65
|
#holding;
|
|
66
66
|
#isClickSwallowed = false;
|
|
@@ -92,7 +92,7 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
92
92
|
option.focus();
|
|
93
93
|
}
|
|
94
94
|
claimPress(option, event) {
|
|
95
|
-
if (this.#
|
|
95
|
+
if (this.#isOnBat(event)) return true;
|
|
96
96
|
if (!this.#isMomentary(option)) return false;
|
|
97
97
|
this.group.setPointerCapture(event.pointerId);
|
|
98
98
|
this.#hold(option, event.pointerId);
|
|
@@ -108,9 +108,8 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
108
108
|
this.#isClickSwallowed = false;
|
|
109
109
|
if (!isSwallowed) this.#setChecked(!this.checked);
|
|
110
110
|
}, { signal });
|
|
111
|
-
this.#bare.addEventListener("pointerdown", (
|
|
111
|
+
this.#bare.addEventListener("pointerdown", () => {
|
|
112
112
|
this.#isClickSwallowed = false;
|
|
113
|
-
if (event.button === 0 && !this.isDisabled()) this.#grabBat(event);
|
|
114
113
|
}, { signal });
|
|
115
114
|
this.#bindBat(group, signal);
|
|
116
115
|
this.#holding = bindHold(group, {
|
|
@@ -138,12 +137,13 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
138
137
|
return option;
|
|
139
138
|
}
|
|
140
139
|
render() {
|
|
140
|
+
if (!this.isBound()) return;
|
|
141
141
|
const group = this.group;
|
|
142
142
|
const options = this.options();
|
|
143
143
|
if (options.length > 0) {
|
|
144
144
|
this.#bare.remove();
|
|
145
145
|
writeAttribute(group, "role", "radiogroup");
|
|
146
|
-
group
|
|
146
|
+
writeAttribute(group, "data-sonic-position-count", String(options.length));
|
|
147
147
|
super.render();
|
|
148
148
|
this.#renderThrow(this.#checkedIndex(options));
|
|
149
149
|
return;
|
|
@@ -153,8 +153,8 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
153
153
|
writeAttribute(group, "role", void 0);
|
|
154
154
|
delete group.dataset.sonicPositionCount;
|
|
155
155
|
if (bare.parentElement !== group) group.append(bare);
|
|
156
|
-
bare
|
|
157
|
-
bare
|
|
156
|
+
writeAttribute(bare, "disabled", this.isDisabled() ? "" : void 0);
|
|
157
|
+
writeAttribute(bare, "aria-checked", String(isChecked));
|
|
158
158
|
this.forwardNaming(group, false);
|
|
159
159
|
this.forwardNaming(bare, true);
|
|
160
160
|
const onSide = this.orientation === "horizontal" ? 1 : -1;
|
|
@@ -172,41 +172,32 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
172
172
|
return this.orientation === "horizontal" ? event.clientX : event.clientY;
|
|
173
173
|
}
|
|
174
174
|
#bindBat(group, signal) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
press
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
175
|
+
bindDrag(group, {
|
|
176
|
+
grab: (event) => {
|
|
177
|
+
if (!(event.target === this.#bare || this.optionOf(event.target) !== void 0) || this.isDisabled() || !this.#isOnBat(event)) return;
|
|
178
|
+
return {
|
|
179
|
+
hasMoved: false,
|
|
180
|
+
last: this.#along(event),
|
|
181
|
+
width: this.#bat.getBoundingClientRect().width
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
lift: (press, event) => {
|
|
185
|
+
if (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);
|
|
186
|
+
this.#isClickSwallowed = true;
|
|
187
|
+
},
|
|
188
|
+
move: (press, event) => {
|
|
189
|
+
if (this.isDisabled()) return;
|
|
190
|
+
const delta = this.#along(event) - press.last;
|
|
191
|
+
if (Math.abs(delta) < press.width / 4) return;
|
|
192
|
+
press.hasMoved = true;
|
|
193
|
+
press.last = this.#along(event);
|
|
194
|
+
this.#throwBy(Math.sign(delta), event.pointerId);
|
|
195
|
+
}
|
|
196
|
+
}, signal);
|
|
196
197
|
}
|
|
197
198
|
#checkedIndex(options) {
|
|
198
199
|
return options.findIndex((option) => optionValue(option) === this.value);
|
|
199
200
|
}
|
|
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
201
|
#hasSprungFrom(option) {
|
|
211
202
|
if (!option || !this.#isMomentary(option)) return false;
|
|
212
203
|
this.#holding?.release();
|
|
@@ -222,6 +213,9 @@ var SonicSwitch = class extends SonicRadioGroupElement {
|
|
|
222
213
|
const options = this.options();
|
|
223
214
|
return (option === options[0] || option === options.at(-1)) && options.length > 1 && option.querySelector("[data-sonic-value]")?.hasAttribute("data-sonic-momentary") === true;
|
|
224
215
|
}
|
|
216
|
+
#isOnBat(event) {
|
|
217
|
+
return isUnder(this.#bat, event.clientX, event.clientY);
|
|
218
|
+
}
|
|
225
219
|
#pressBat(event) {
|
|
226
220
|
const options = this.options();
|
|
227
221
|
if (options.length === 0) {
|
|
@@ -1 +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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"}
|
|
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 { bindDrag } from '#lib/pointer-drag.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\twidth: 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#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.#isOnBat(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() => {\n\t\t\t\tthis.#isClickSwallowed = false;\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\tif (!this.isBound()) return;\n\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\twriteAttribute(group, 'data-sonic-position-count', 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\twriteAttribute(bare, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(bare, '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\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition =\n\t\t\t\t\t\tevent.target === this.#bare || this.optionOf(event.target) !== undefined;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.#along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tif (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.#along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.#along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\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#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#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\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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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
1
2
|
import { TimeRegions } from "../lib/time-regions.js";
|
|
2
3
|
import { ValueMapping } from "../lib/value-mapping.js";
|
|
3
|
-
import { SonicFormElement } from "./form-element.js";
|
|
4
4
|
//#region src/elements/value-element.d.ts
|
|
5
5
|
export interface ValueAxis {
|
|
6
6
|
fromProportion?: number;
|
|
@@ -49,7 +49,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
|
|
|
49
49
|
get taper(): 'linear' | 'log';
|
|
50
50
|
set taper(curve: 'linear' | 'log' | undefined);
|
|
51
51
|
get value(): number;
|
|
52
|
-
set value(next: number);
|
|
52
|
+
set value(next: null | number | undefined);
|
|
53
53
|
get valueText(): string;
|
|
54
54
|
attributeChangedCallback(name: string): void;
|
|
55
55
|
connectedCallback(): void;
|
|
@@ -77,7 +77,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
|
|
|
77
77
|
protected springTarget(): number | undefined;
|
|
78
78
|
protected tapTarget(): number | undefined;
|
|
79
79
|
protected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number;
|
|
80
|
-
protected writeModulationValue(control: HTMLElement, prefix: 'dial' | 'slider', next: number | undefined): void;
|
|
80
|
+
protected writeModulationValue(control: HTMLElement, prefix: 'dial' | 'slider', next: null | number | undefined): void;
|
|
81
81
|
protected writeProportions(control: HTMLElement, prefix: 'dial' | 'slider', modulation: number): void;
|
|
82
82
|
}
|
|
83
83
|
//#endregion
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { clamp } from "../lib/math.js";
|
|
1
|
+
import { clamp, toNumber } from "../lib/math.js";
|
|
2
2
|
import { placeChildren, requireChild } from "../lib/render.js";
|
|
3
3
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
4
|
import { SonicFormElement } from "./form-element.js";
|
|
5
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
5
6
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
6
7
|
import { Readout } from "./readout.js";
|
|
7
8
|
import { ValueEntry } from "./value-entry.js";
|
|
8
|
-
import { copyNode } from "../lib/copy-node.js";
|
|
9
9
|
import { dragThresholdPx, startDrag, stepDrag } from "../lib/drag-step.js";
|
|
10
10
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
11
11
|
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
12
12
|
import { parseNumberList } from "../lib/number-list.js";
|
|
13
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
13
14
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
14
15
|
import { readRegions } from "../lib/time-regions.js";
|
|
15
16
|
import { resetKeys, valueMapping } from "../lib/value-mapping.js";
|
|
@@ -203,53 +204,57 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
203
204
|
return this.#value;
|
|
204
205
|
}
|
|
205
206
|
set value(next) {
|
|
207
|
+
if (next === void 0 || next === null) {
|
|
208
|
+
this.formResetCallback();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const value = toNumber(next);
|
|
206
212
|
if (this.#isHeld()) {
|
|
207
|
-
this.heldWrite(
|
|
213
|
+
this.heldWrite(value);
|
|
208
214
|
return;
|
|
209
215
|
}
|
|
210
|
-
this.#write(
|
|
216
|
+
this.#write(value);
|
|
217
|
+
if (Number.isFinite(value)) this.#asked = value;
|
|
211
218
|
}
|
|
212
219
|
get valueText() {
|
|
213
220
|
return this.#textFor(this.#value);
|
|
214
221
|
}
|
|
222
|
+
#asked;
|
|
215
223
|
#buffered = [];
|
|
216
224
|
#claim = new ReadoutClaim(() => {
|
|
217
225
|
this.#renderHold();
|
|
218
226
|
});
|
|
219
|
-
#drag;
|
|
220
227
|
#entry;
|
|
221
228
|
#escapeWatch;
|
|
222
229
|
#formatSpokenValue;
|
|
223
230
|
#formatValue;
|
|
224
231
|
#instance = String(instanceCount += 1);
|
|
225
|
-
#isDirty = false;
|
|
226
232
|
#isModulated = false;
|
|
227
233
|
#keyScrub;
|
|
228
234
|
#land;
|
|
229
235
|
#limit;
|
|
236
|
+
#mapping;
|
|
230
237
|
#modulationValue;
|
|
231
238
|
#parseValue;
|
|
239
|
+
#pointerDrag;
|
|
232
240
|
#positions;
|
|
233
241
|
#readout;
|
|
234
242
|
#scaleMarks;
|
|
235
243
|
#value = 0;
|
|
236
244
|
#watchers = /* @__PURE__ */ new Set();
|
|
237
245
|
attributeChangedCallback(name) {
|
|
246
|
+
this.#mapping = void 0;
|
|
238
247
|
this.#positions = parseNumberList(this.getAttribute("positions"));
|
|
239
248
|
if (name === "value") {
|
|
240
249
|
if (this.#isHeld()) {
|
|
241
250
|
this.heldWrite(this.numberAttribute("value", this.min));
|
|
242
251
|
return;
|
|
243
252
|
}
|
|
244
|
-
this.#
|
|
245
|
-
}
|
|
246
|
-
if (name === "disabled" && this.isDisabled()) {
|
|
247
|
-
this.#entry?.close(false);
|
|
248
|
-
this.#endDrag();
|
|
249
|
-
this.#endKeyScrub();
|
|
253
|
+
this.#asked = void 0;
|
|
250
254
|
}
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
if (name === "disabled" && this.isDisabled()) this.#endHolds();
|
|
256
|
+
this.#value = this.mapping().snap(this.#asked ?? this.numberAttribute("value", this.min));
|
|
257
|
+
if (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();
|
|
253
258
|
this.render();
|
|
254
259
|
}
|
|
255
260
|
connectedCallback() {
|
|
@@ -258,66 +263,61 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
258
263
|
}
|
|
259
264
|
bindGestures(control, signal, grab) {
|
|
260
265
|
const entry = this.#bindEntry(control, signal);
|
|
261
|
-
control
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
this.#pointerDrag = bindDrag(control, {
|
|
267
|
+
cancel: (drag) => {
|
|
268
|
+
if (drag.isKeptOnCancel !== true) this.input(drag.fromValue);
|
|
269
|
+
},
|
|
270
|
+
grab: (event) => {
|
|
271
|
+
if (isMenuPress(event) || this.isDisabled() || entry.isOpen) return;
|
|
272
|
+
this.toggleState("springing", false);
|
|
273
|
+
if (event.pointerType === "touch") event.preventDefault();
|
|
274
|
+
focusByPointer(control);
|
|
275
|
+
if (isResetPress(event)) {
|
|
276
|
+
this.#reset();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const fromValue = this.#value;
|
|
280
|
+
const axis = grab(event);
|
|
281
|
+
if (!axis) return;
|
|
282
|
+
const drag = {
|
|
283
|
+
...axis,
|
|
284
|
+
fromValue,
|
|
285
|
+
isOutside: false,
|
|
286
|
+
isRevealed: false,
|
|
287
|
+
revealTimer: void 0,
|
|
288
|
+
state: this.#startDrag(control, axis, event)
|
|
289
|
+
};
|
|
290
|
+
drag.revealTimer = setTimeout(() => {
|
|
291
|
+
this.#reveal(drag);
|
|
292
|
+
}, 250);
|
|
293
|
+
return drag;
|
|
294
|
+
},
|
|
295
|
+
lift: (drag, event) => {
|
|
296
|
+
const tap = drag.state.isEngaged ? void 0 : this.tapTarget();
|
|
297
|
+
if (tap !== void 0) {
|
|
298
|
+
entry.forgetPress();
|
|
299
|
+
this.#commit(tap);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (!entry.press(event) || this.springTarget() !== void 0) return;
|
|
303
|
+
if (this.doublePress === "reset") this.#reset();
|
|
304
|
+
else entry.open();
|
|
305
|
+
},
|
|
306
|
+
move: (drag, event) => {
|
|
307
|
+
this.#dragTo(drag, event);
|
|
308
|
+
},
|
|
309
|
+
release: (drag) => {
|
|
310
|
+
this.#releaseDrag(drag);
|
|
311
|
+
},
|
|
312
|
+
toggle: (isDragging) => {
|
|
313
|
+
this.toggleState("dragging", isDragging);
|
|
270
314
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (!axis) return;
|
|
274
|
-
const drag = {
|
|
275
|
-
...axis,
|
|
276
|
-
fromValue,
|
|
277
|
-
isOutside: false,
|
|
278
|
-
isRevealed: false,
|
|
279
|
-
pointerId: event.pointerId,
|
|
280
|
-
revealTimer: void 0,
|
|
281
|
-
state: this.#startDrag(control, axis, event)
|
|
282
|
-
};
|
|
283
|
-
drag.revealTimer = setTimeout(() => {
|
|
284
|
-
this.#reveal(drag);
|
|
285
|
-
}, 250);
|
|
286
|
-
this.#drag = drag;
|
|
287
|
-
control.setPointerCapture(event.pointerId);
|
|
288
|
-
this.toggleState("dragging", true);
|
|
289
|
-
}, { signal });
|
|
290
|
-
control.addEventListener("pointermove", (event) => {
|
|
291
|
-
const drag = this.#drag;
|
|
292
|
-
if (event.pointerId === drag?.pointerId) this.#dragTo(drag, event);
|
|
293
|
-
}, { signal });
|
|
294
|
-
const endDrag = (event) => {
|
|
295
|
-
if (event.pointerId === this.#drag?.pointerId) this.#endDrag();
|
|
296
|
-
};
|
|
297
|
-
control.addEventListener("pointercancel", (event) => {
|
|
298
|
-
const drag = this.#drag;
|
|
299
|
-
if (event.pointerId === drag?.pointerId && drag.isKeptOnCancel !== true) this.input(drag.fromValue);
|
|
300
|
-
endDrag(event);
|
|
315
|
+
}, signal);
|
|
316
|
+
control.addEventListener("pointercancel", () => {
|
|
301
317
|
entry.forgetPress();
|
|
302
318
|
}, { signal });
|
|
303
|
-
control.addEventListener("lostpointercapture", endDrag, { signal });
|
|
304
|
-
control.addEventListener("pointerup", (event) => {
|
|
305
|
-
const drag = this.#drag;
|
|
306
|
-
if (event.pointerId !== drag?.pointerId) return;
|
|
307
|
-
this.#endDrag();
|
|
308
|
-
const tap = drag.state.isEngaged ? void 0 : this.tapTarget();
|
|
309
|
-
if (tap !== void 0) {
|
|
310
|
-
entry.forgetPress();
|
|
311
|
-
this.#commit(tap);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (!entry.press(event) || this.springTarget() !== void 0) return;
|
|
315
|
-
if (this.doublePress === "reset") this.#reset();
|
|
316
|
-
else entry.open();
|
|
317
|
-
}, { signal });
|
|
318
319
|
this.#bindKeys(control, entry, signal);
|
|
319
320
|
signal.addEventListener("abort", () => {
|
|
320
|
-
this.#endDrag();
|
|
321
321
|
this.#endKeyScrub();
|
|
322
322
|
this.#concealKeyReveal();
|
|
323
323
|
}, { once: true });
|
|
@@ -328,6 +328,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
328
328
|
control,
|
|
329
329
|
copy: scaleMark,
|
|
330
330
|
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
|
|
331
|
+
isPassed: (child) => child instanceof Element && child.matches(".sonic-led"),
|
|
331
332
|
place: (copies) => {
|
|
332
333
|
placeChildren(marks, copies);
|
|
333
334
|
this.#renderScale();
|
|
@@ -339,7 +340,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
339
340
|
return "slider";
|
|
340
341
|
}
|
|
341
342
|
heldFrom() {
|
|
342
|
-
return this.#
|
|
343
|
+
return this.#dragging()?.fromValue ?? this.#keyScrub?.fromValue;
|
|
343
344
|
}
|
|
344
345
|
heldWrite(_next) {}
|
|
345
346
|
holdChanged() {}
|
|
@@ -365,8 +366,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
365
366
|
return false;
|
|
366
367
|
}
|
|
367
368
|
mapping() {
|
|
369
|
+
if (this.#mapping) return this.#mapping;
|
|
368
370
|
const { detent, midpoint } = this;
|
|
369
|
-
|
|
371
|
+
this.#mapping = valueMapping({
|
|
370
372
|
...detent === void 0 ? {} : { detent },
|
|
371
373
|
...this.#positions ? { positions: this.#positions } : {},
|
|
372
374
|
max: this.max,
|
|
@@ -377,6 +379,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
377
379
|
step: this.step,
|
|
378
380
|
taper: this.taper
|
|
379
381
|
});
|
|
382
|
+
return this.#mapping;
|
|
380
383
|
}
|
|
381
384
|
originValue() {
|
|
382
385
|
return this.origin ?? this.mapping().bounds[0];
|
|
@@ -389,10 +392,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
389
392
|
return this.#claim.shown(this.#value, this.readout).value ?? this.#value;
|
|
390
393
|
}
|
|
391
394
|
render() {
|
|
392
|
-
this.
|
|
393
|
-
this.#renderScale();
|
|
394
|
-
this.toggleState("at-origin", !this.isWrapping() && this.#value === this.mapping().snap(this.originValue()));
|
|
395
|
-
this.#renderAria(this.focusTarget(), this.controlOrientation());
|
|
395
|
+
if (this.isBound()) this.#renderControl();
|
|
396
396
|
for (const listener of this.#watchers) listener();
|
|
397
397
|
}
|
|
398
398
|
restoreState(state) {
|
|
@@ -407,9 +407,10 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
407
407
|
return Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));
|
|
408
408
|
}
|
|
409
409
|
writeModulationValue(control, prefix, next) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
this.#
|
|
410
|
+
const value = next === void 0 || next === null ? void 0 : toNumber(next);
|
|
411
|
+
if (value !== void 0 && !Number.isFinite(value)) return;
|
|
412
|
+
this.#modulationValue = value;
|
|
413
|
+
if (this.isBound()) this.#writeModulationProportion(control, prefix);
|
|
413
414
|
}
|
|
414
415
|
writeProportions(control, prefix, modulation) {
|
|
415
416
|
const { style } = control;
|
|
@@ -469,7 +470,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
469
470
|
control.addEventListener("keyup", (event) => {
|
|
470
471
|
const isMeta = event.key === "Meta";
|
|
471
472
|
if (isMeta || event.key === this.#keyScrub?.key) this.#endKeyScrub();
|
|
472
|
-
if (isMeta && this.#
|
|
473
|
+
if (isMeta && this.#dragging()) return;
|
|
473
474
|
if (isMeta || this.mapping().keyTarget(event.key, this.#value) !== void 0) this.#springBack();
|
|
474
475
|
}, { signal });
|
|
475
476
|
control.addEventListener("blur", () => {
|
|
@@ -478,10 +479,6 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
478
479
|
this.#springBack();
|
|
479
480
|
}, { signal });
|
|
480
481
|
}
|
|
481
|
-
#canPress(event, entry) {
|
|
482
|
-
if (event.button !== 0 || isMenuPress(event) || this.isDisabled() || entry.isOpen) return false;
|
|
483
|
-
return !this.#drag || this.#drag.pointerId === event.pointerId;
|
|
484
|
-
}
|
|
485
482
|
#commit(next, direction = 0) {
|
|
486
483
|
const landed = Number.isFinite(next) ? this.#land?.(next, direction) ?? next : next;
|
|
487
484
|
if (this.input(landed)) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
@@ -489,6 +486,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
489
486
|
#concealKeyReveal() {
|
|
490
487
|
if (this.#claim.conceal("keys")) this.#renderHold();
|
|
491
488
|
}
|
|
489
|
+
#dragging() {
|
|
490
|
+
return this.#pointerDrag?.current();
|
|
491
|
+
}
|
|
492
492
|
#dragTo(drag, event) {
|
|
493
493
|
const isOutside = drag.outside?.(event) === true;
|
|
494
494
|
const wasEngaged = drag.state.isEngaged;
|
|
@@ -506,16 +506,10 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
506
506
|
drag.isOutside = isOutside;
|
|
507
507
|
this.#renderDragReveal(drag);
|
|
508
508
|
}
|
|
509
|
-
#
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
this.#drag = void 0;
|
|
514
|
-
this.toggleState("dragging", false);
|
|
515
|
-
this.#claim.conceal("drag");
|
|
516
|
-
this.#renderHold();
|
|
517
|
-
if (this.#value !== drag.fromValue) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
518
|
-
this.#springBack();
|
|
509
|
+
#endHolds() {
|
|
510
|
+
this.#entry?.close(false);
|
|
511
|
+
this.#pointerDrag?.end();
|
|
512
|
+
this.#endKeyScrub();
|
|
519
513
|
}
|
|
520
514
|
#endKeyScrub() {
|
|
521
515
|
const scrub = this.#keyScrub;
|
|
@@ -525,7 +519,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
525
519
|
if (this.#value !== scrub.fromValue) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
526
520
|
}
|
|
527
521
|
#isHeld() {
|
|
528
|
-
return this.#
|
|
522
|
+
return this.#dragging() !== void 0 || this.#keyScrub !== void 0;
|
|
529
523
|
}
|
|
530
524
|
#keyTarget(key) {
|
|
531
525
|
if (resetKeys.has(key)) return this.default;
|
|
@@ -539,6 +533,13 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
539
533
|
this.#endKeyScrub();
|
|
540
534
|
this.#commit(next, next > this.#value ? 1 : -1);
|
|
541
535
|
}
|
|
536
|
+
#releaseDrag(drag) {
|
|
537
|
+
clearTimeout(drag.revealTimer);
|
|
538
|
+
this.#claim.conceal("drag");
|
|
539
|
+
this.#renderHold();
|
|
540
|
+
if (this.#value !== drag.fromValue) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
541
|
+
this.#springBack();
|
|
542
|
+
}
|
|
542
543
|
#renderAria(control, orientation) {
|
|
543
544
|
this.writeFormValue(String(this.#value), String(this.#value));
|
|
544
545
|
if (this.#entry?.isOpen) {
|
|
@@ -548,24 +549,26 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
548
549
|
return;
|
|
549
550
|
}
|
|
550
551
|
const [low, high] = this.mapping().bounds;
|
|
551
|
-
control
|
|
552
|
-
control
|
|
553
|
-
control
|
|
554
|
-
control
|
|
555
|
-
if (orientation) control
|
|
552
|
+
writeAttribute(control, "role", this.controlRole());
|
|
553
|
+
writeAttribute(control, "aria-valuemin", String(low));
|
|
554
|
+
writeAttribute(control, "aria-valuemax", String(high));
|
|
555
|
+
writeAttribute(control, "aria-valuenow", String(this.#value));
|
|
556
|
+
if (orientation) writeAttribute(control, "aria-orientation", orientation);
|
|
556
557
|
writeAttribute(control, "aria-valuetext", (this.#formatSpokenValue ?? this.#formatValue)?.(this.#value));
|
|
557
558
|
this.forwardNaming(control, true);
|
|
558
559
|
this.#renderDisabled(control);
|
|
559
560
|
this.#renderReadout();
|
|
560
561
|
}
|
|
562
|
+
#renderControl() {
|
|
563
|
+
this.draw();
|
|
564
|
+
this.#renderScale();
|
|
565
|
+
this.toggleState("at-origin", !this.isWrapping() && this.#value === this.mapping().snap(this.originValue()));
|
|
566
|
+
this.#renderAria(this.focusTarget(), this.controlOrientation());
|
|
567
|
+
}
|
|
561
568
|
#renderDisabled(control) {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
control.removeAttribute("aria-disabled");
|
|
568
|
-
control.setAttribute("tabindex", "0");
|
|
569
|
+
const isDisabled = this.isDisabled();
|
|
570
|
+
writeAttribute(control, "aria-disabled", isDisabled ? "true" : void 0);
|
|
571
|
+
writeAttribute(control, "tabindex", isDisabled ? void 0 : "0");
|
|
569
572
|
}
|
|
570
573
|
#renderDragReveal(drag) {
|
|
571
574
|
if (drag.isRevealed && !drag.isOutside) this.#claim.reveal("drag");
|
|
@@ -599,7 +602,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
599
602
|
this.#commit(this.default ?? NaN);
|
|
600
603
|
}
|
|
601
604
|
#reveal(drag) {
|
|
602
|
-
if (drag.isRevealed || this.#
|
|
605
|
+
if (drag.isRevealed || this.#dragging() !== drag) return;
|
|
603
606
|
drag.isRevealed = true;
|
|
604
607
|
this.#renderDragReveal(drag);
|
|
605
608
|
}
|
|
@@ -666,8 +669,8 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
666
669
|
}
|
|
667
670
|
#write(next) {
|
|
668
671
|
if (!Number.isFinite(next)) return;
|
|
669
|
-
this.#isDirty = true;
|
|
670
672
|
const clamped = this.mapping().snap(next);
|
|
673
|
+
this.#asked = clamped;
|
|
671
674
|
if (clamped === this.#value) return;
|
|
672
675
|
this.#value = clamped;
|
|
673
676
|
this.render();
|