@xsynaptic/sonic-ui 0.20.0 → 0.22.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 +30 -0
- package/README.md +22 -108
- package/dist/default/elements/button.js +2 -1
- package/dist/default/elements/envelope.js +3 -6
- package/dist/default/elements/field-gesture.js +3 -0
- package/dist/default/elements/split.js +6 -7
- package/dist/default/elements/switch.js +5 -3
- package/dist/default/elements/toggle.js +10 -5
- package/dist/default/elements/value-element.js +3 -0
- package/dist/default/elements/value-entry.js +5 -18
- package/dist/default/elements/waveform.js +15 -4
- package/dist/default/elements/xy.js +33 -5
- package/dist/default/lib/double-press.js +30 -0
- package/dist/dev/elements/button.js +2 -1
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/envelope.js +3 -6
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +3 -0
- package/dist/dev/elements/field-gesture.js.map +1 -1
- package/dist/dev/elements/number.js +1 -4
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/split.js +6 -7
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.js +5 -3
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +10 -5
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.js +3 -0
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-entry.js +5 -18
- package/dist/dev/elements/value-entry.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +2 -0
- package/dist/dev/elements/waveform.js +15 -4
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +4 -0
- package/dist/dev/elements/xy.js +33 -5
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/lib/double-press.js +31 -0
- package/dist/dev/lib/double-press.js.map +1 -0
- package/dist/styles/button.css +1 -0
- package/dist/styles/dial.css +1 -1
- package/dist/styles/envelope.css +2 -1
- package/dist/styles/led.css +7 -19
- package/dist/styles/material/cap.css +26 -7
- package/dist/styles/material/core.css +9 -1
- package/dist/styles/material/glass.css +1 -2
- package/dist/styles/material/lens.css +18 -1
- package/dist/styles/material/pane.css +2 -5
- package/dist/styles/material/readout.css +11 -3
- package/dist/styles/material/value.css +1 -1
- package/dist/styles/material/well.css +8 -1
- package/dist/styles/meter.css +6 -4
- package/dist/styles/number.css +8 -9
- package/dist/styles/slider.css +29 -11
- package/dist/styles/toggle.css +4 -0
- package/dist/styles/xy.css +4 -19
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/lib/double-press.ts
|
|
2
|
+
const doublePressMs = 500;
|
|
3
|
+
const doublePressPx = 4;
|
|
4
|
+
function isDoublePress(previous, next) {
|
|
5
|
+
if (!previous) return false;
|
|
6
|
+
return next.time - previous.time < doublePressMs && Math.hypot(next.x - previous.x, next.y - previous.y) < doublePressPx;
|
|
7
|
+
}
|
|
8
|
+
function createDoublePress() {
|
|
9
|
+
let last;
|
|
10
|
+
return {
|
|
11
|
+
forget() {
|
|
12
|
+
last = void 0;
|
|
13
|
+
},
|
|
14
|
+
press(event) {
|
|
15
|
+
const press = {
|
|
16
|
+
time: event.timeStamp,
|
|
17
|
+
x: event.clientX,
|
|
18
|
+
y: event.clientY
|
|
19
|
+
};
|
|
20
|
+
const previous = last;
|
|
21
|
+
last = press;
|
|
22
|
+
if (!isDoublePress(previous, press)) return false;
|
|
23
|
+
last = void 0;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { createDoublePress };
|
|
@@ -172,7 +172,8 @@ var SonicButton = class extends SonicFormElement {
|
|
|
172
172
|
holding.hold(event.pointerId);
|
|
173
173
|
}, { signal });
|
|
174
174
|
button.addEventListener("keydown", (event) => {
|
|
175
|
-
if (event.repeat ||
|
|
175
|
+
if (event.repeat || event.defaultPrevented) return;
|
|
176
|
+
if (!holdKeys.has(event.key) || !this.#isMomentary()) return;
|
|
176
177
|
holding.hold(event.key);
|
|
177
178
|
}, { signal });
|
|
178
179
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\t/** Shows the legend child whose `data-sonic-when` has this name */\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\t/** Submitted with the form while a latching button is pressed */\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t});\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\t/** Shows the legend child whose `data-sonic-when` has this name */\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\t/** Submitted with the form while a latching button is pressed */\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t});\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || event.defaultPrevented) return;\n\t\t\t\tif (!holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -139,11 +139,10 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
139
139
|
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
140
140
|
#readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
|
|
141
141
|
#shape = adsrShape({});
|
|
142
|
-
#signal;
|
|
143
142
|
#waiting = /* @__PURE__ */ new Set();
|
|
144
143
|
#warned = /* @__PURE__ */ new Set();
|
|
145
144
|
attributeChangedCallback(name) {
|
|
146
|
-
if (!this
|
|
145
|
+
if (!this.isBound()) return;
|
|
147
146
|
if (name === "disabled") {
|
|
148
147
|
this.#gesture?.end();
|
|
149
148
|
this.#draw();
|
|
@@ -158,7 +157,6 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
158
157
|
}
|
|
159
158
|
connect(signal) {
|
|
160
159
|
const envelope = this.#envelope;
|
|
161
|
-
this.#signal = signal;
|
|
162
160
|
this.keepControl(envelope, signal);
|
|
163
161
|
this.#bind();
|
|
164
162
|
this.checkStyles(envelope, "envelope.css", {
|
|
@@ -167,7 +165,6 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
167
165
|
});
|
|
168
166
|
this.#bindPointer(envelope, signal);
|
|
169
167
|
signal.addEventListener("abort", () => {
|
|
170
|
-
this.#signal = void 0;
|
|
171
168
|
this.#unbind();
|
|
172
169
|
}, { once: true });
|
|
173
170
|
}
|
|
@@ -205,7 +202,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
205
202
|
release: ({ drives }, moved) => {
|
|
206
203
|
for (const { axis, binding } of drives) {
|
|
207
204
|
if (!moved.includes(axis)) continue;
|
|
208
|
-
binding.
|
|
205
|
+
binding.link.change();
|
|
209
206
|
}
|
|
210
207
|
},
|
|
211
208
|
toggle: (isDragging) => {
|
|
@@ -312,7 +309,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
312
309
|
this.#waiting.add(key);
|
|
313
310
|
arrives(() => {
|
|
314
311
|
this.#waiting.delete(key);
|
|
315
|
-
if (this
|
|
312
|
+
if (this.isBound()) this.#bind();
|
|
316
313
|
});
|
|
317
314
|
}
|
|
318
315
|
#renderReadout() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.js","names":[],"sources":["../../../src/elements/envelope.ts"],"sourcesContent":["import type { FieldGesture, FieldHold } from '#elements/field-gesture.ts';\nimport type { ValueLink } from '#elements/value-element.ts';\nimport type { AdsrCurves, AdsrProportions, AdsrShape } from '#lib/adsr-shape.ts';\nimport type { FieldAxis, FieldPoint } from '#lib/field.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { adsrPath, adsrShape, adsrStages, timeStages } from '#lib/adsr-shape.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-envelope': SonicEnvelope;\n\t}\n}\n\ninterface Binding {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tunwatch: () => void;\n}\n\ninterface Drive {\n\taxis: FieldAxis;\n\tbinding: Binding;\n}\n\ninterface HandleHold extends FieldHold {\n\tdrives: Array<Drive>;\n\tisFlipped: boolean;\n\tpart: HTMLElement;\n}\n\nconst curveStages = ['attack', 'decay', 'release'] as const;\n\nconst bindable = [...adsrStages, 'attack-curve', 'decay-curve', 'release-curve'] as const;\n\nconst bindableNames = new Set<string>(bindable);\n\nfunction isParent(node: Node): node is Node & ParentNode {\n\treturn 'querySelector' in node;\n}\n\nconst emptyPath = { fill: '', floor: { from: 0, to: 0 }, stroke: '' };\n\nfunction stageParts<Stage extends string>(\n\tenvelope: Element,\n\thook: string,\n\tstages: ReadonlyArray<Stage>,\n): Map<Stage, HTMLDivElement> {\n\treturn new Map(\n\t\tstages.map((stage) => [\n\t\t\tstage,\n\t\t\trequireChild(envelope, `.${hook}[data-sonic-stage=\"${stage}\"]`, HTMLDivElement),\n\t\t]),\n\t);\n}\n\nconst renderEnvelope = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-envelope\" aria-hidden=\"true\">\n\t\t\t<div class=\"sonic-envelope-bracket\"></div>\n\t\t\t<svg class=\"sonic-envelope-graph\" preserveAspectRatio=\"none\" viewBox=\"0 0 1 1\">\n\t\t\t\t<path class=\"sonic-envelope-fill\" />\n\t\t\t\t<rect class=\"sonic-envelope-fill sonic-envelope-floor\" y=\"1\" />\n\t\t\t\t<path class=\"sonic-envelope-line\" />\n\t\t\t</svg>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"delay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"hold\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-readout\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicEnvelope extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t...bindable,\n\t\t'readout',\n\t];\n\n\tget attack(): string {\n\t\treturn this.getAttribute('attack') ?? '';\n\t}\n\n\tset attack(id: string | undefined) {\n\t\tthis.reflect('attack', id);\n\t}\n\n\tget attackCurve(): string {\n\t\treturn this.getAttribute('attack-curve') ?? '';\n\t}\n\n\tset attackCurve(id: string | undefined) {\n\t\tthis.reflect('attack-curve', id);\n\t}\n\n\tget decay(): string {\n\t\treturn this.getAttribute('decay') ?? '';\n\t}\n\n\tset decay(id: string | undefined) {\n\t\tthis.reflect('decay', id);\n\t}\n\n\tget decayCurve(): string {\n\t\treturn this.getAttribute('decay-curve') ?? '';\n\t}\n\n\tset decayCurve(id: string | undefined) {\n\t\tthis.reflect('decay-curve', id);\n\t}\n\n\tget delay(): string {\n\t\treturn this.getAttribute('delay') ?? '';\n\t}\n\n\tset delay(id: string | undefined) {\n\t\tthis.reflect('delay', id);\n\t}\n\n\tget hold(): string {\n\t\treturn this.getAttribute('hold') ?? '';\n\t}\n\n\tset hold(id: string | undefined) {\n\t\tthis.reflect('hold', id);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isOn: boolean) {\n\t\tthis.reflect('readout', isOn);\n\t}\n\n\tget release(): string {\n\t\treturn this.getAttribute('release') ?? '';\n\t}\n\n\tset release(id: string | undefined) {\n\t\tthis.reflect('release', id);\n\t}\n\n\tget releaseCurve(): string {\n\t\treturn this.getAttribute('release-curve') ?? '';\n\t}\n\n\tset releaseCurve(id: string | undefined) {\n\t\tthis.reflect('release-curve', id);\n\t}\n\n\tget sustain(): string {\n\t\treturn this.getAttribute('sustain') ?? '';\n\t}\n\n\tset sustain(id: string | undefined) {\n\t\tthis.reflect('sustain', id);\n\t}\n\n\treadonly #bindings = new Map<string, Binding>();\n\n\treadonly #envelope = renderEnvelope();\n\n\treadonly #bracket = requireChild(this.#envelope, '.sonic-envelope-bracket', HTMLDivElement);\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\treadonly #curveHandles = stageParts(this.#envelope, 'sonic-envelope-curve', curveStages);\n\n\treadonly #fill = requireChild(this.#envelope, '.sonic-envelope-fill', SVGElement);\n\n\treadonly #floor = requireChild(this.#envelope, '.sonic-envelope-floor', SVGElement);\n\n\t#gesture: FieldGesture<HandleHold> | undefined;\n\n\treadonly #graph = requireChild(this.#envelope, '.sonic-envelope-graph', SVGElement);\n\n\treadonly #handles = stageParts(this.#envelope, 'sonic-envelope-handle', timeStages);\n\n\t#held: HTMLElement | undefined;\n\n\t#isDriving = false;\n\n\treadonly #line = requireChild(this.#envelope, '.sonic-envelope-line', SVGElement);\n\n\treadonly #readout = new Readout(\n\t\trequireChild(this.#envelope, '.sonic-envelope-readout', HTMLDivElement),\n\t);\n\n\t#shape: AdsrShape = adsrShape({});\n\n\t#signal: AbortSignal | undefined;\n\n\treadonly #waiting = new Set<unknown>();\n\n\treadonly #warned = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (!this.#signal) return;\n\t\tif (name === 'disabled') {\n\t\t\tthis.#gesture?.end();\n\t\t\tthis.#draw();\n\t\t\treturn;\n\t\t}\n\t\tif (bindableNames.has(name)) this.#bind();\n\t\telse this.#renderReadout();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(...adsrStages, 'attackCurve', 'decayCurve', 'readout', 'releaseCurve');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst envelope = this.#envelope;\n\n\t\tthis.#signal = signal;\n\t\tthis.keepControl(envelope, signal);\n\t\tthis.#bind();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(envelope, 'envelope.css', {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#bindPointer(envelope, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#signal = undefined;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bind(): void {\n\t\tthis.#gesture?.end();\n\t\tthis.#unbind();\n\t\tfor (const name of bindable) {\n\t\t\tconst element = this.#resolve(name);\n\t\t\tif (!element) continue;\n\n\t\t\tconst link = linkValue(element);\n\t\t\tconst unwatch = link.model.watch(() => {\n\t\t\t\tif (!this.#isDriving) this.#draw();\n\t\t\t});\n\n\t\t\tthis.#bindings.set(name, { element, link, unwatch });\n\t\t}\n\t\tthis.#draw();\n\t}\n\n\t#bindPointer(envelope: HTMLElement, signal: AbortSignal): void {\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\tenvelope,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tinput: ({ drives }, next) => {\n\t\t\t\t\tthis.#isDriving = true;\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tconst value = next[axis];\n\n\t\t\t\t\t\tif (value !== undefined) binding.link.input(value);\n\t\t\t\t\t}\n\t\t\t\t\tthis.#isDriving = false;\n\t\t\t\t\tthis.#draw();\n\t\t\t\t},\n\t\t\t\trelease: ({ drives }, moved) => {\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tif (!moved.includes(axis)) continue;\n\n\t\t\t\t\t\tbinding.element.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#curveDrives(stage: string): Array<Drive> {\n\t\tconst binding = this.#bindings.get(`${stage}-curve`);\n\n\t\treturn binding ? [{ axis: 'y', binding }] : [];\n\t}\n\n\t#draw(): void {\n\t\tconst shape = this.#readShape();\n\t\tconst path = this.#bindings.size === 0 ? emptyPath : adsrPath(shape);\n\n\t\tthis.#shape = shape;\n\t\twriteAttribute(this.#line, 'd', path.stroke);\n\t\twriteAttribute(this.#fill, 'd', path.fill);\n\t\twriteAttribute(this.#floor, 'x', String(path.floor.from));\n\t\twriteAttribute(this.#floor, 'width', String(path.floor.to - path.floor.from));\n\t\tfor (const [stage, part] of this.#handles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.handles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#drives(stage),\n\t\t\t});\n\t\t}\n\t\tfor (const [stage, part] of this.#curveHandles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.curveHandles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#curveDrives(stage),\n\t\t\t});\n\t\t}\n\t\tthis.#drawBracket();\n\t\tthis.#renderReadout();\n\t}\n\n\t#drawBracket(): void {\n\t\tconst part = this.#held;\n\t\tif (!part) return;\n\n\t\tfor (const name of ['--_sonic-envelope-x', '--_sonic-envelope-y']) {\n\t\t\tthis.#bracket.style.setProperty(name, part.style.getPropertyValue(name));\n\t\t}\n\t\twriteAttribute(\n\t\t\tthis.#bracket,\n\t\t\t'data-sonic-part',\n\t\t\tpart.classList.contains('sonic-envelope-curve') ? 'curve' : undefined,\n\t\t);\n\t}\n\n\t#drawPoint(\n\t\tpart: HTMLDivElement,\n\t\tpoint: { at: FieldPoint | undefined; drives: Array<Drive> },\n\t): void {\n\t\tconst { at, drives } = point;\n\n\t\twriteAttribute(part, 'hidden', at ? undefined : '');\n\t\tif (!at) return;\n\n\t\tconst isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());\n\n\t\tpart.style.setProperty('--_sonic-envelope-x', String(at.x));\n\t\tpart.style.setProperty('--_sonic-envelope-y', String(at.y));\n\t\twriteAttribute(part, 'data-sonic-axes', drives.map(({ axis }) => axis).join(''));\n\t\twriteAttribute(part, 'data-sonic-disabled', isStill ? '' : undefined);\n\t}\n\n\t#drives(stage: string): Array<Drive> {\n\t\tconst x = this.#bindings.get(stage);\n\t\tconst y = stage === 'decay' ? this.#bindings.get('sustain') : undefined;\n\n\t\treturn [\n\t\t\t...(x ? [{ axis: 'x' as const, binding: x }] : []),\n\t\t\t...(y ? [{ axis: 'y' as const, binding: y }] : []),\n\t\t];\n\t}\n\n\t#grab(event: PointerEvent): HandleHold | undefined {\n\t\tconst pressed = this.#pressed(event);\n\t\tif (!pressed || pressed.drives.length === 0) return undefined;\n\n\t\tthis.#held = pressed.part;\n\t\tthis.#drawBracket();\n\n\t\tconst box = this.#graph.getBoundingClientRect();\n\t\tconst travel = { x: Math.max(1, box.width * this.#shape.share), y: Math.max(1, box.height) };\n\t\tconst axes: HandleHold['axes'] = {};\n\n\t\tfor (const { axis, binding } of pressed.drives) {\n\t\t\taxes[axis] = { model: binding.link.model, travelPx: travel[axis] };\n\t\t}\n\n\t\treturn { ...pressed, axes };\n\t}\n\n\t#pressed(event: PointerEvent): Omit<HandleHold, 'axes'> | undefined {\n\t\tconst part =\n\t\t\tevent.target instanceof Element\n\t\t\t\t? event.target.closest<HTMLElement>('.sonic-envelope-handle, .sonic-envelope-curve')\n\t\t\t\t: undefined;\n\t\tconst stage = part?.dataset.sonicStage;\n\t\tif (!part || stage === undefined || this.isDisabled()) return undefined;\n\n\t\tconst isCurveHandle = part.classList.contains('sonic-envelope-curve');\n\t\tconst drives = isCurveHandle ? this.#curveDrives(stage) : this.#drives(stage);\n\n\t\treturn {\n\t\t\tdrives: drives.filter(({ binding }) => !binding.link.isDisabled()),\n\t\t\tisFlipped: isCurveHandle && stage === 'attack',\n\t\t\tpart,\n\t\t};\n\t}\n\n\t#readShape(): AdsrShape {\n\t\tconst proportions: AdsrProportions = {};\n\t\tconst curves: AdsrCurves = {};\n\n\t\tfor (const stage of adsrStages) {\n\t\t\tconst model = this.#bindings.get(stage)?.link.model;\n\n\t\t\tproportions[stage] = model?.mapping().proportionOf(model.value);\n\t\t}\n\t\tfor (const stage of curveStages) {\n\t\t\tcurves[stage] = this.#bindings.get(`${stage}-curve`)?.link.model.value;\n\t\t}\n\n\t\treturn adsrShape(proportions, curves);\n\t}\n\n\t#rebindOnce(key: unknown, arrives: (rebind: () => void) => void): void {\n\t\tif (this.#waiting.has(key)) return;\n\n\t\tthis.#waiting.add(key);\n\t\tarrives(() => {\n\t\t\tthis.#waiting.delete(key);\n\t\t\tif (this.#signal) this.#bind();\n\t\t});\n\t}\n\n\t#renderReadout(): void {\n\t\tconst drag = this.#gesture?.current();\n\n\t\tif (!drag) {\n\t\t\tthis.#readout.close();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readout.show({\n\t\t\tanchor: drag.part,\n\t\t\tisOpen: this.#claim.isRevealed && this.readout,\n\t\t\ttext: drag.drives.map(({ binding }) => binding.element.valueText).join(', '),\n\t\t});\n\t}\n\n\t#resolve(name: string): SonicValueElement | undefined {\n\t\tconst id = this.getAttribute(name);\n\t\tconst root = this.getRootNode();\n\t\tif (!id || !isParent(root)) return undefined;\n\n\t\tconst found = root.querySelector(`#${CSS.escape(id)}`);\n\t\tif (found instanceof SonicValueElement) return found;\n\n\t\tthis.#wait(id, found);\n\n\t\treturn undefined;\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { unwatch } of this.#bindings.values()) unwatch();\n\n\t\tthis.#bindings.clear();\n\t}\n\n\t#wait(id: string, found: Element | null): void {\n\t\tconst tag = found?.localName;\n\t\tconst { ownerDocument } = this;\n\n\t\tif (tag?.includes('-') && !customElements.get(tag)) {\n\t\t\tthis.#rebindOnce(tag, (rebind) => {\n\t\t\t\tvoid customElements.whenDefined(tag).then(rebind);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (!found && ownerDocument.readyState === 'loading') {\n\t\t\tthis.#rebindOnce(ownerDocument, (rebind) => {\n\t\t\t\townerDocument.addEventListener('DOMContentLoaded', rebind, { once: true });\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#warn(id);\n\t}\n\n\t#warn(id: string): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#warned.has(id)) return;\n\n\t\tthis.#warned.add(id);\n\t\tconsole.warn(`<sonic-envelope> found no dial, slider or number box with the id \"${id}\"`);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"envelope.js","names":[],"sources":["../../../src/elements/envelope.ts"],"sourcesContent":["import type { FieldGesture, FieldHold } from '#elements/field-gesture.ts';\nimport type { ValueLink } from '#elements/value-element.ts';\nimport type { AdsrCurves, AdsrProportions, AdsrShape } from '#lib/adsr-shape.ts';\nimport type { FieldAxis, FieldPoint } from '#lib/field.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { adsrPath, adsrShape, adsrStages, timeStages } from '#lib/adsr-shape.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-envelope': SonicEnvelope;\n\t}\n}\n\ninterface Binding {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tunwatch: () => void;\n}\n\ninterface Drive {\n\taxis: FieldAxis;\n\tbinding: Binding;\n}\n\ninterface HandleHold extends FieldHold {\n\tdrives: Array<Drive>;\n\tisFlipped: boolean;\n\tpart: HTMLElement;\n}\n\nconst curveStages = ['attack', 'decay', 'release'] as const;\n\nconst bindable = [...adsrStages, 'attack-curve', 'decay-curve', 'release-curve'] as const;\n\nconst bindableNames = new Set<string>(bindable);\n\nfunction isParent(node: Node): node is Node & ParentNode {\n\treturn 'querySelector' in node;\n}\n\nconst emptyPath = { fill: '', floor: { from: 0, to: 0 }, stroke: '' };\n\nfunction stageParts<Stage extends string>(\n\tenvelope: Element,\n\thook: string,\n\tstages: ReadonlyArray<Stage>,\n): Map<Stage, HTMLDivElement> {\n\treturn new Map(\n\t\tstages.map((stage) => [\n\t\t\tstage,\n\t\t\trequireChild(envelope, `.${hook}[data-sonic-stage=\"${stage}\"]`, HTMLDivElement),\n\t\t]),\n\t);\n}\n\nconst renderEnvelope = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-envelope\" aria-hidden=\"true\">\n\t\t\t<div class=\"sonic-envelope-bracket\"></div>\n\t\t\t<svg class=\"sonic-envelope-graph\" preserveAspectRatio=\"none\" viewBox=\"0 0 1 1\">\n\t\t\t\t<path class=\"sonic-envelope-fill\" />\n\t\t\t\t<rect class=\"sonic-envelope-fill sonic-envelope-floor\" y=\"1\" />\n\t\t\t\t<path class=\"sonic-envelope-line\" />\n\t\t\t</svg>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"delay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"hold\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-readout\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicEnvelope extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t...bindable,\n\t\t'readout',\n\t];\n\n\tget attack(): string {\n\t\treturn this.getAttribute('attack') ?? '';\n\t}\n\n\tset attack(id: string | undefined) {\n\t\tthis.reflect('attack', id);\n\t}\n\n\tget attackCurve(): string {\n\t\treturn this.getAttribute('attack-curve') ?? '';\n\t}\n\n\tset attackCurve(id: string | undefined) {\n\t\tthis.reflect('attack-curve', id);\n\t}\n\n\tget decay(): string {\n\t\treturn this.getAttribute('decay') ?? '';\n\t}\n\n\tset decay(id: string | undefined) {\n\t\tthis.reflect('decay', id);\n\t}\n\n\tget decayCurve(): string {\n\t\treturn this.getAttribute('decay-curve') ?? '';\n\t}\n\n\tset decayCurve(id: string | undefined) {\n\t\tthis.reflect('decay-curve', id);\n\t}\n\n\tget delay(): string {\n\t\treturn this.getAttribute('delay') ?? '';\n\t}\n\n\tset delay(id: string | undefined) {\n\t\tthis.reflect('delay', id);\n\t}\n\n\tget hold(): string {\n\t\treturn this.getAttribute('hold') ?? '';\n\t}\n\n\tset hold(id: string | undefined) {\n\t\tthis.reflect('hold', id);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isOn: boolean) {\n\t\tthis.reflect('readout', isOn);\n\t}\n\n\tget release(): string {\n\t\treturn this.getAttribute('release') ?? '';\n\t}\n\n\tset release(id: string | undefined) {\n\t\tthis.reflect('release', id);\n\t}\n\n\tget releaseCurve(): string {\n\t\treturn this.getAttribute('release-curve') ?? '';\n\t}\n\n\tset releaseCurve(id: string | undefined) {\n\t\tthis.reflect('release-curve', id);\n\t}\n\n\tget sustain(): string {\n\t\treturn this.getAttribute('sustain') ?? '';\n\t}\n\n\tset sustain(id: string | undefined) {\n\t\tthis.reflect('sustain', id);\n\t}\n\n\treadonly #bindings = new Map<string, Binding>();\n\n\treadonly #envelope = renderEnvelope();\n\n\treadonly #bracket = requireChild(this.#envelope, '.sonic-envelope-bracket', HTMLDivElement);\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\treadonly #curveHandles = stageParts(this.#envelope, 'sonic-envelope-curve', curveStages);\n\n\treadonly #fill = requireChild(this.#envelope, '.sonic-envelope-fill', SVGElement);\n\n\treadonly #floor = requireChild(this.#envelope, '.sonic-envelope-floor', SVGElement);\n\n\t#gesture: FieldGesture<HandleHold> | undefined;\n\n\treadonly #graph = requireChild(this.#envelope, '.sonic-envelope-graph', SVGElement);\n\n\treadonly #handles = stageParts(this.#envelope, 'sonic-envelope-handle', timeStages);\n\n\t#held: HTMLElement | undefined;\n\n\t#isDriving = false;\n\n\treadonly #line = requireChild(this.#envelope, '.sonic-envelope-line', SVGElement);\n\n\treadonly #readout = new Readout(\n\t\trequireChild(this.#envelope, '.sonic-envelope-readout', HTMLDivElement),\n\t);\n\n\t#shape: AdsrShape = adsrShape({});\n\n\treadonly #waiting = new Set<unknown>();\n\n\treadonly #warned = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (!this.isBound()) return;\n\t\tif (name === 'disabled') {\n\t\t\tthis.#gesture?.end();\n\t\t\tthis.#draw();\n\t\t\treturn;\n\t\t}\n\t\tif (bindableNames.has(name)) this.#bind();\n\t\telse this.#renderReadout();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(...adsrStages, 'attackCurve', 'decayCurve', 'readout', 'releaseCurve');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst envelope = this.#envelope;\n\n\t\tthis.keepControl(envelope, signal);\n\t\tthis.#bind();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(envelope, 'envelope.css', {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#bindPointer(envelope, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bind(): void {\n\t\tthis.#gesture?.end();\n\t\tthis.#unbind();\n\t\tfor (const name of bindable) {\n\t\t\tconst element = this.#resolve(name);\n\t\t\tif (!element) continue;\n\n\t\t\tconst link = linkValue(element);\n\t\t\tconst unwatch = link.model.watch(() => {\n\t\t\t\tif (!this.#isDriving) this.#draw();\n\t\t\t});\n\n\t\t\tthis.#bindings.set(name, { element, link, unwatch });\n\t\t}\n\t\tthis.#draw();\n\t}\n\n\t#bindPointer(envelope: HTMLElement, signal: AbortSignal): void {\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\tenvelope,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tinput: ({ drives }, next) => {\n\t\t\t\t\tthis.#isDriving = true;\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tconst value = next[axis];\n\n\t\t\t\t\t\tif (value !== undefined) binding.link.input(value);\n\t\t\t\t\t}\n\t\t\t\t\tthis.#isDriving = false;\n\t\t\t\t\tthis.#draw();\n\t\t\t\t},\n\t\t\t\trelease: ({ drives }, moved) => {\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tif (!moved.includes(axis)) continue;\n\n\t\t\t\t\t\tbinding.link.change();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#curveDrives(stage: string): Array<Drive> {\n\t\tconst binding = this.#bindings.get(`${stage}-curve`);\n\n\t\treturn binding ? [{ axis: 'y', binding }] : [];\n\t}\n\n\t#draw(): void {\n\t\tconst shape = this.#readShape();\n\t\tconst path = this.#bindings.size === 0 ? emptyPath : adsrPath(shape);\n\n\t\tthis.#shape = shape;\n\t\twriteAttribute(this.#line, 'd', path.stroke);\n\t\twriteAttribute(this.#fill, 'd', path.fill);\n\t\twriteAttribute(this.#floor, 'x', String(path.floor.from));\n\t\twriteAttribute(this.#floor, 'width', String(path.floor.to - path.floor.from));\n\t\tfor (const [stage, part] of this.#handles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.handles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#drives(stage),\n\t\t\t});\n\t\t}\n\t\tfor (const [stage, part] of this.#curveHandles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.curveHandles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#curveDrives(stage),\n\t\t\t});\n\t\t}\n\t\tthis.#drawBracket();\n\t\tthis.#renderReadout();\n\t}\n\n\t#drawBracket(): void {\n\t\tconst part = this.#held;\n\t\tif (!part) return;\n\n\t\tfor (const name of ['--_sonic-envelope-x', '--_sonic-envelope-y']) {\n\t\t\tthis.#bracket.style.setProperty(name, part.style.getPropertyValue(name));\n\t\t}\n\t\twriteAttribute(\n\t\t\tthis.#bracket,\n\t\t\t'data-sonic-part',\n\t\t\tpart.classList.contains('sonic-envelope-curve') ? 'curve' : undefined,\n\t\t);\n\t}\n\n\t#drawPoint(\n\t\tpart: HTMLDivElement,\n\t\tpoint: { at: FieldPoint | undefined; drives: Array<Drive> },\n\t): void {\n\t\tconst { at, drives } = point;\n\n\t\twriteAttribute(part, 'hidden', at ? undefined : '');\n\t\tif (!at) return;\n\n\t\tconst isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());\n\n\t\tpart.style.setProperty('--_sonic-envelope-x', String(at.x));\n\t\tpart.style.setProperty('--_sonic-envelope-y', String(at.y));\n\t\twriteAttribute(part, 'data-sonic-axes', drives.map(({ axis }) => axis).join(''));\n\t\twriteAttribute(part, 'data-sonic-disabled', isStill ? '' : undefined);\n\t}\n\n\t#drives(stage: string): Array<Drive> {\n\t\tconst x = this.#bindings.get(stage);\n\t\tconst y = stage === 'decay' ? this.#bindings.get('sustain') : undefined;\n\n\t\treturn [\n\t\t\t...(x ? [{ axis: 'x' as const, binding: x }] : []),\n\t\t\t...(y ? [{ axis: 'y' as const, binding: y }] : []),\n\t\t];\n\t}\n\n\t#grab(event: PointerEvent): HandleHold | undefined {\n\t\tconst pressed = this.#pressed(event);\n\t\tif (!pressed || pressed.drives.length === 0) return undefined;\n\n\t\tthis.#held = pressed.part;\n\t\tthis.#drawBracket();\n\n\t\tconst box = this.#graph.getBoundingClientRect();\n\t\tconst travel = { x: Math.max(1, box.width * this.#shape.share), y: Math.max(1, box.height) };\n\t\tconst axes: HandleHold['axes'] = {};\n\n\t\tfor (const { axis, binding } of pressed.drives) {\n\t\t\taxes[axis] = { model: binding.link.model, travelPx: travel[axis] };\n\t\t}\n\n\t\treturn { ...pressed, axes };\n\t}\n\n\t#pressed(event: PointerEvent): Omit<HandleHold, 'axes'> | undefined {\n\t\tconst part =\n\t\t\tevent.target instanceof Element\n\t\t\t\t? event.target.closest<HTMLElement>('.sonic-envelope-handle, .sonic-envelope-curve')\n\t\t\t\t: undefined;\n\t\tconst stage = part?.dataset.sonicStage;\n\t\tif (!part || stage === undefined || this.isDisabled()) return undefined;\n\n\t\tconst isCurveHandle = part.classList.contains('sonic-envelope-curve');\n\t\tconst drives = isCurveHandle ? this.#curveDrives(stage) : this.#drives(stage);\n\n\t\treturn {\n\t\t\tdrives: drives.filter(({ binding }) => !binding.link.isDisabled()),\n\t\t\tisFlipped: isCurveHandle && stage === 'attack',\n\t\t\tpart,\n\t\t};\n\t}\n\n\t#readShape(): AdsrShape {\n\t\tconst proportions: AdsrProportions = {};\n\t\tconst curves: AdsrCurves = {};\n\n\t\tfor (const stage of adsrStages) {\n\t\t\tconst model = this.#bindings.get(stage)?.link.model;\n\n\t\t\tproportions[stage] = model?.mapping().proportionOf(model.value);\n\t\t}\n\t\tfor (const stage of curveStages) {\n\t\t\tcurves[stage] = this.#bindings.get(`${stage}-curve`)?.link.model.value;\n\t\t}\n\n\t\treturn adsrShape(proportions, curves);\n\t}\n\n\t#rebindOnce(key: unknown, arrives: (rebind: () => void) => void): void {\n\t\tif (this.#waiting.has(key)) return;\n\n\t\tthis.#waiting.add(key);\n\t\tarrives(() => {\n\t\t\tthis.#waiting.delete(key);\n\t\t\tif (this.isBound()) this.#bind();\n\t\t});\n\t}\n\n\t#renderReadout(): void {\n\t\tconst drag = this.#gesture?.current();\n\n\t\tif (!drag) {\n\t\t\tthis.#readout.close();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readout.show({\n\t\t\tanchor: drag.part,\n\t\t\tisOpen: this.#claim.isRevealed && this.readout,\n\t\t\ttext: drag.drives.map(({ binding }) => binding.element.valueText).join(', '),\n\t\t});\n\t}\n\n\t#resolve(name: string): SonicValueElement | undefined {\n\t\tconst id = this.getAttribute(name);\n\t\tconst root = this.getRootNode();\n\t\tif (!id || !isParent(root)) return undefined;\n\n\t\tconst found = root.querySelector(`#${CSS.escape(id)}`);\n\t\tif (found instanceof SonicValueElement) return found;\n\n\t\tthis.#wait(id, found);\n\n\t\treturn undefined;\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { unwatch } of this.#bindings.values()) unwatch();\n\n\t\tthis.#bindings.clear();\n\t}\n\n\t#wait(id: string, found: Element | null): void {\n\t\tconst tag = found?.localName;\n\t\tconst { ownerDocument } = this;\n\n\t\tif (tag?.includes('-') && !customElements.get(tag)) {\n\t\t\tthis.#rebindOnce(tag, (rebind) => {\n\t\t\t\tvoid customElements.whenDefined(tag).then(rebind);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (!found && ownerDocument.readyState === 'loading') {\n\t\t\tthis.#rebindOnce(ownerDocument, (rebind) => {\n\t\t\t\townerDocument.addEventListener('DOMContentLoaded', rebind, { once: true });\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#warn(id);\n\t}\n\n\t#warn(id: string): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#warned.has(id)) return;\n\n\t\tthis.#warned.add(id);\n\t\tconsole.warn(`<sonic-envelope> found no dial, slider or number box with the id \"${id}\"`);\n\t}\n}\n"],"mappings":";;;;;;;;;;AAuCA,MAAM,cAAc;CAAC;CAAU;CAAS;AAAS;AAEjD,MAAM,WAAW;CAAC,GAAG;CAAY;CAAgB;CAAe;AAAe;AAE/E,MAAM,gBAAgB,IAAI,IAAY,QAAQ;AAE9C,SAAS,SAAS,MAAuC;CACxD,OAAO,mBAAmB;AAC3B;AAEA,MAAM,YAAY;CAAE,MAAM;CAAI,OAAO;EAAE,MAAM;EAAG,IAAI;CAAE;CAAG,QAAQ;AAAG;AAEpE,SAAS,WACR,UACA,MACA,QAC6B;CAC7B,OAAO,IAAI,IACV,OAAO,KAAK,UAAU,CACrB,OACA,aAAa,UAAU,IAAI,KAAK,qBAAqB,MAAM,KAAK,cAAc,CAC/E,CAAC,CACF;AACD;AAEA,MAAM,iBAAiB,SACX;;;;;;;;;;;;;;;;;;IAmBX,cACD;AAEA,IAAa,gBAAb,cAAmC,aAAa;CAC/C,OAAyB,qBAAqB;EAC7C,GAAG,aAAa;EAChB,GAAG;EACH;CACD;CAEA,IAAI,SAAiB;EACpB,OAAO,KAAK,aAAa,QAAQ,KAAK;CACvC;CAEA,IAAI,OAAO,IAAwB;EAClC,KAAK,QAAQ,UAAU,EAAE;CAC1B;CAEA,IAAI,cAAsB;EACzB,OAAO,KAAK,aAAa,cAAc,KAAK;CAC7C;CAEA,IAAI,YAAY,IAAwB;EACvC,KAAK,QAAQ,gBAAgB,EAAE;CAChC;CAEA,IAAI,QAAgB;EACnB,OAAO,KAAK,aAAa,OAAO,KAAK;CACtC;CAEA,IAAI,MAAM,IAAwB;EACjC,KAAK,QAAQ,SAAS,EAAE;CACzB;CAEA,IAAI,aAAqB;EACxB,OAAO,KAAK,aAAa,aAAa,KAAK;CAC5C;CAEA,IAAI,WAAW,IAAwB;EACtC,KAAK,QAAQ,eAAe,EAAE;CAC/B;CAEA,IAAI,QAAgB;EACnB,OAAO,KAAK,aAAa,OAAO,KAAK;CACtC;CAEA,IAAI,MAAM,IAAwB;EACjC,KAAK,QAAQ,SAAS,EAAE;CACzB;CAEA,IAAI,OAAe;EAClB,OAAO,KAAK,aAAa,MAAM,KAAK;CACrC;CAEA,IAAI,KAAK,IAAwB;EAChC,KAAK,QAAQ,QAAQ,EAAE;CACxB;CAGA,IAAI,cAAkC;EACrC,OAAO,KAAK,UAAU,YAAY;CACnC;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,MAAe;EAC1B,KAAK,QAAQ,WAAW,IAAI;CAC7B;CAEA,IAAI,UAAkB;EACrB,OAAO,KAAK,aAAa,SAAS,KAAK;CACxC;CAEA,IAAI,QAAQ,IAAwB;EACnC,KAAK,QAAQ,WAAW,EAAE;CAC3B;CAEA,IAAI,eAAuB;EAC1B,OAAO,KAAK,aAAa,eAAe,KAAK;CAC9C;CAEA,IAAI,aAAa,IAAwB;EACxC,KAAK,QAAQ,iBAAiB,EAAE;CACjC;CAEA,IAAI,UAAkB;EACrB,OAAO,KAAK,aAAa,SAAS,KAAK;CACxC;CAEA,IAAI,QAAQ,IAAwB;EACnC,KAAK,QAAQ,WAAW,EAAE;CAC3B;CAEA,AAAS,4BAAY,IAAI,IAAqB;CAE9C,AAAS,YAAY,eAAe;CAEpC,AAAS,WAAW,aAAa,KAAK,WAAW,2BAA2B,cAAc;CAE1F,AAAS,SAAS,IAAI,mBAAmB;EACxC,KAAK,eAAe;CACrB,CAAC;CAED,AAAS,gBAAgB,WAAW,KAAK,WAAW,wBAAwB,WAAW;CAEvF,AAAS,QAAQ,aAAa,KAAK,WAAW,wBAAwB,UAAU;CAEhF,AAAS,SAAS,aAAa,KAAK,WAAW,yBAAyB,UAAU;CAElF;CAEA,AAAS,SAAS,aAAa,KAAK,WAAW,yBAAyB,UAAU;CAElF,AAAS,WAAW,WAAW,KAAK,WAAW,yBAAyB,UAAU;CAElF;CAEA,aAAa;CAEb,AAAS,QAAQ,aAAa,KAAK,WAAW,wBAAwB,UAAU;CAEhF,AAAS,WAAW,IAAI,QACvB,aAAa,KAAK,WAAW,2BAA2B,cAAc,CACvE;CAEA,SAAoB,UAAU,CAAC,CAAC;CAEhC,AAAS,2BAAW,IAAI,IAAa;CAErC,AAAS,0BAAU,IAAI,IAAY;CAEnC,yBAAyB,MAAoB;EAC5C,IAAI,CAAC,KAAK,QAAQ,GAAG;EACrB,IAAI,SAAS,YAAY;GACxB,KAAK,UAAU,IAAI;GACnB,KAAK,MAAM;GACX;EACD;EACA,IAAI,cAAc,IAAI,IAAI,GAAG,KAAK,MAAM;OACnC,KAAK,eAAe;CAC1B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,GAAG,YAAY,eAAe,cAAc,WAAW,cAAc;EAC5F,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,WAAW,KAAK;EAEtB,KAAK,YAAY,UAAU,MAAM;EACjC,KAAK,MAAM;EAEV,KAAK,YAAY,UAAU,gBAAgB;GAC1C,UAAU;GACV,UAAU;EACX,CAAC;EACF,KAAK,aAAa,UAAU,MAAM;EAClC,OAAO,iBACN,eACM;GACL,KAAK,QAAQ;EACd,GACA,EAAE,MAAM,KAAK,CACd;CACD;CAEA,QAAc;EACb,KAAK,UAAU,IAAI;EACnB,KAAK,QAAQ;EACb,KAAK,MAAM,QAAQ,UAAU;GAC5B,MAAM,UAAU,KAAK,SAAS,IAAI;GAClC,IAAI,CAAC,SAAS;GAEd,MAAM,OAAO,UAAU,OAAO;GAC9B,MAAM,UAAU,KAAK,MAAM,YAAY;IACtC,IAAI,CAAC,KAAK,YAAY,KAAK,MAAM;GAClC,CAAC;GAED,KAAK,UAAU,IAAI,MAAM;IAAE;IAAS;IAAM;GAAQ,CAAC;EACpD;EACA,KAAK,MAAM;CACZ;CAEA,aAAa,UAAuB,QAA2B;EAC9D,KAAK,WAAW,iBACf,UACA;GACC,OAAO,KAAK;GACZ,OAAO,UAAU,KAAK,MAAM,KAAK;GACjC,QAAQ,EAAE,UAAU,SAAS;IAC5B,KAAK,aAAa;IAClB,KAAK,MAAM,EAAE,MAAM,aAAa,QAAQ;KACvC,MAAM,QAAQ,KAAK;KAEnB,IAAI,UAAU,QAAW,QAAQ,KAAK,MAAM,KAAK;IAClD;IACA,KAAK,aAAa;IAClB,KAAK,MAAM;GACZ;GACA,UAAU,EAAE,UAAU,UAAU;IAC/B,KAAK,MAAM,EAAE,MAAM,aAAa,QAAQ;KACvC,IAAI,CAAC,MAAM,SAAS,IAAI,GAAG;KAE3B,QAAQ,KAAK,OAAO;IACrB;GACD;GACA,SAAS,eAAe;IACvB,KAAK,YAAY,YAAY,UAAU;GACxC;EACD,GACA,MACD;CACD;CAEA,aAAa,OAA6B;EACzC,MAAM,UAAU,KAAK,UAAU,IAAI,GAAG,MAAM,OAAO;EAEnD,OAAO,UAAU,CAAC;GAAE,MAAM;GAAK;EAAQ,CAAC,IAAI,CAAC;CAC9C;CAEA,QAAc;EACb,MAAM,QAAQ,KAAK,WAAW;EAC9B,MAAM,OAAO,KAAK,UAAU,SAAS,IAAI,YAAY,SAAS,KAAK;EAEnE,KAAK,SAAS;EACd,eAAe,KAAK,OAAO,KAAK,KAAK,MAAM;EAC3C,eAAe,KAAK,OAAO,KAAK,KAAK,IAAI;EACzC,eAAe,KAAK,QAAQ,KAAK,OAAO,KAAK,MAAM,IAAI,CAAC;EACxD,eAAe,KAAK,QAAQ,SAAS,OAAO,KAAK,MAAM,KAAK,KAAK,MAAM,IAAI,CAAC;EAC5E,KAAK,MAAM,CAAC,OAAO,SAAS,KAAK,UAChC,KAAK,WAAW,MAAM;GACrB,IAAI,MAAM,QAAQ,MAAM,UAAU,MAAM,UAAU,KAAK;GACvD,QAAQ,KAAK,QAAQ,KAAK;EAC3B,CAAC;EAEF,KAAK,MAAM,CAAC,OAAO,SAAS,KAAK,eAChC,KAAK,WAAW,MAAM;GACrB,IAAI,MAAM,aAAa,MAAM,UAAU,MAAM,UAAU,KAAK;GAC5D,QAAQ,KAAK,aAAa,KAAK;EAChC,CAAC;EAEF,KAAK,aAAa;EAClB,KAAK,eAAe;CACrB;CAEA,eAAqB;EACpB,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,MAAM;EAEX,KAAK,MAAM,QAAQ,CAAC,uBAAuB,qBAAqB,GAC/D,KAAK,SAAS,MAAM,YAAY,MAAM,KAAK,MAAM,iBAAiB,IAAI,CAAC;EAExE,eACC,KAAK,UACL,mBACA,KAAK,UAAU,SAAS,sBAAsB,IAAI,UAAU,MAC7D;CACD;CAEA,WACC,MACA,OACO;EACP,MAAM,EAAE,IAAI,WAAW;EAEvB,eAAe,MAAM,UAAU,KAAK,SAAY,EAAE;EAClD,IAAI,CAAC,IAAI;EAET,MAAM,UAAU,KAAK,WAAW,KAAK,OAAO,OAAO,EAAE,cAAc,QAAQ,KAAK,WAAW,CAAC;EAE5F,KAAK,MAAM,YAAY,uBAAuB,OAAO,GAAG,CAAC,CAAC;EAC1D,KAAK,MAAM,YAAY,uBAAuB,OAAO,GAAG,CAAC,CAAC;EAC1D,eAAe,MAAM,mBAAmB,OAAO,KAAK,EAAE,WAAW,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;EAC/E,eAAe,MAAM,uBAAuB,UAAU,KAAK,MAAS;CACrE;CAEA,QAAQ,OAA6B;EACpC,MAAM,IAAI,KAAK,UAAU,IAAI,KAAK;EAClC,MAAM,IAAI,UAAU,UAAU,KAAK,UAAU,IAAI,SAAS,IAAI;EAE9D,OAAO,CACN,GAAI,IAAI,CAAC;GAAE,MAAM;GAAc,SAAS;EAAE,CAAC,IAAI,CAAC,GAChD,GAAI,IAAI,CAAC;GAAE,MAAM;GAAc,SAAS;EAAE,CAAC,IAAI,CAAC,CACjD;CACD;CAEA,MAAM,OAA6C;EAClD,MAAM,UAAU,KAAK,SAAS,KAAK;EACnC,IAAI,CAAC,WAAW,QAAQ,OAAO,WAAW,GAAG,OAAO;EAEpD,KAAK,QAAQ,QAAQ;EACrB,KAAK,aAAa;EAElB,MAAM,MAAM,KAAK,OAAO,sBAAsB;EAC9C,MAAM,SAAS;GAAE,GAAG,KAAK,IAAI,GAAG,IAAI,QAAQ,KAAK,OAAO,KAAK;GAAG,GAAG,KAAK,IAAI,GAAG,IAAI,MAAM;EAAE;EAC3F,MAAM,OAA2B,CAAC;EAElC,KAAK,MAAM,EAAE,MAAM,aAAa,QAAQ,QACvC,KAAK,QAAQ;GAAE,OAAO,QAAQ,KAAK;GAAO,UAAU,OAAO;EAAM;EAGlE,OAAO;GAAE,GAAG;GAAS;EAAK;CAC3B;CAEA,SAAS,OAA2D;EACnE,MAAM,OACL,MAAM,kBAAkB,UACrB,MAAM,OAAO,QAAqB,+CAA+C,IACjF;EACJ,MAAM,QAAQ,MAAM,QAAQ;EAC5B,IAAI,CAAC,QAAQ,UAAU,UAAa,KAAK,WAAW,GAAG,OAAO;EAE9D,MAAM,gBAAgB,KAAK,UAAU,SAAS,sBAAsB;EAGpE,OAAO;GACN,SAHc,gBAAgB,KAAK,aAAa,KAAK,IAAI,KAAK,QAAQ,KAAK,EAG7D,CAAC,QAAQ,EAAE,cAAc,CAAC,QAAQ,KAAK,WAAW,CAAC;GACjE,WAAW,iBAAiB,UAAU;GACtC;EACD;CACD;CAEA,aAAwB;EACvB,MAAM,cAA+B,CAAC;EACtC,MAAM,SAAqB,CAAC;EAE5B,KAAK,MAAM,SAAS,YAAY;GAC/B,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,KAAK;GAE9C,YAAY,SAAS,OAAO,QAAQ,CAAC,CAAC,aAAa,MAAM,KAAK;EAC/D;EACA,KAAK,MAAM,SAAS,aACnB,OAAO,SAAS,KAAK,UAAU,IAAI,GAAG,MAAM,OAAO,CAAC,EAAE,KAAK,MAAM;EAGlE,OAAO,UAAU,aAAa,MAAM;CACrC;CAEA,YAAY,KAAc,SAA6C;EACtE,IAAI,KAAK,SAAS,IAAI,GAAG,GAAG;EAE5B,KAAK,SAAS,IAAI,GAAG;EACrB,cAAc;GACb,KAAK,SAAS,OAAO,GAAG;GACxB,IAAI,KAAK,QAAQ,GAAG,KAAK,MAAM;EAChC,CAAC;CACF;CAEA,iBAAuB;EACtB,MAAM,OAAO,KAAK,UAAU,QAAQ;EAEpC,IAAI,CAAC,MAAM;GACV,KAAK,SAAS,MAAM;GACpB;EACD;EAEA,KAAK,SAAS,KAAK;GAClB,QAAQ,KAAK;GACb,QAAQ,KAAK,OAAO,cAAc,KAAK;GACvC,MAAM,KAAK,OAAO,KAAK,EAAE,cAAc,QAAQ,QAAQ,SAAS,CAAC,CAAC,KAAK,IAAI;EAC5E,CAAC;CACF;CAEA,SAAS,MAA6C;EACrD,MAAM,KAAK,KAAK,aAAa,IAAI;EACjC,MAAM,OAAO,KAAK,YAAY;EAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,GAAG,OAAO;EAEnC,MAAM,QAAQ,KAAK,cAAc,IAAI,IAAI,OAAO,EAAE,GAAG;EACrD,IAAI,iBAAiB,mBAAmB,OAAO;EAE/C,KAAK,MAAM,IAAI,KAAK;CAGrB;CAEA,UAAgB;EACf,KAAK,MAAM,EAAE,aAAa,KAAK,UAAU,OAAO,GAAG,QAAQ;EAE3D,KAAK,UAAU,MAAM;CACtB;CAEA,MAAM,IAAY,OAA6B;EAC9C,MAAM,MAAM,OAAO;EACnB,MAAM,EAAE,kBAAkB;EAE1B,IAAI,KAAK,SAAS,GAAG,KAAK,CAAC,eAAe,IAAI,GAAG,GAAG;GACnD,KAAK,YAAY,MAAM,WAAW;IACjC,AAAK,eAAe,YAAY,GAAG,CAAC,CAAC,KAAK,MAAM;GACjD,CAAC;GACD;EACD;EACA,IAAI,CAAC,SAAS,cAAc,eAAe,WAAW;GACrD,KAAK,YAAY,gBAAgB,WAAW;IAC3C,cAAc,iBAAiB,oBAAoB,QAAQ,EAAE,MAAM,KAAK,CAAC;GAC1E,CAAC;GACD;EACD;EAEA,KAAK,MAAM,EAAE;CACd;CAEA,MAAM,IAAkB;EAEvB,IAAI,KAAK,QAAQ,IAAI,EAAE,GAAG;EAE1B,KAAK,QAAQ,IAAI,EAAE;EACnB,QAAQ,KAAK,qEAAqE,GAAG,EAAE;CACxF;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-gesture.js","names":[],"sources":["../../../src/elements/field-gesture.ts"],"sourcesContent":["import type { ReadoutClaim } from '#elements/readout-claim.ts';\nimport type { FieldAxis, FieldDragState, FieldPoint } from '#lib/field.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { revealDelay } from '#elements/readout-claim.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from '#lib/field.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\n\ninterface FieldAxisHold {\n\tfrom?: number;\n\tmodel: ValueModel;\n\tproportion?: number;\n\ttravelPx: number;\n}\n\nexport interface FieldHold {\n\taxes: Partial<Record<FieldAxis, FieldAxisHold>>;\n\tisFlipped?: boolean;\n}\n\nexport interface FieldInput {\n\tx?: number | undefined;\n\ty?: number | undefined;\n}\n\ninterface FieldGestureOptions<Hold extends FieldHold> {\n\tclaim: ReadoutClaim;\n\tgrab: (event: PointerEvent) => Hold | undefined;\n\tinput: (hold: Hold, next: FieldInput) => void;\n\trelease: (hold: Hold, moved: Array<FieldAxis>) => void;\n\ttoggle: (isDragging: boolean) => void;\n}\n\nexport interface FieldGesture<Hold> {\n\tcurrent: () => Hold | undefined;\n\tend: () => void;\n\tpointerType: () => string | undefined;\n}\n\ninterface Held<Hold> {\n\tfrom: FieldInput;\n\thold: Hold;\n\tstate: FieldDragState;\n}\n\nconst fieldAxes = ['x', 'y'] as const;\n\nfunction flip<Point extends FieldPoint>(point: Point, hold: FieldHold): Point {\n\treturn hold.isFlipped === true ? { ...point, y: -point.y } : point;\n}\n\nfunction startAxis(hold: FieldAxisHold | undefined) {\n\tif (!hold) return;\n\n\tconst { model, travelPx } = hold;\n\tconst mapping = model.mapping();\n\tconst from = model.value;\n\n\treturn { from, mapping, proportion: hold.proportion ?? mapping.proportionOf(from), travelPx };\n}\n\nexport function bindFieldGesture<Hold extends FieldHold>(\n\ttarget: HTMLElement,\n\toptions: FieldGestureOptions<Hold>,\n\tsignal: AbortSignal,\n): FieldGesture<Hold> {\n\tconst { claim } = options;\n\n\tlet pointerType: string | undefined;\n\n\tconst drag = bindDrag<Held<Hold>>(\n\t\ttarget,\n\t\t{\n\t\t\tcancel: ({ from, hold }) => {\n\t\t\t\toptions.input(hold, from);\n\t\t\t},\n\t\t\tgrab: (event) => {\n\t\t\t\t// Set first, so an `input` the grab itself fires can read it\n\t\t\t\tpointerType = event.pointerType;\n\n\t\t\t\tconst hold = options.grab(event);\n\t\t\t\tif (!hold) {\n\t\t\t\t\tpointerType = undefined;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { x, y } = hold.axes;\n\n\t\t\t\tclaim.press(true, revealDelay(target));\n\n\t\t\t\treturn {\n\t\t\t\t\tfrom: { x: x?.from ?? x?.model.value, y: y?.from ?? y?.model.value },\n\t\t\t\t\thold,\n\t\t\t\t\tstate: startFieldDrag({\n\t\t\t\t\t\tposition: flip(pointerPosition(event), hold),\n\t\t\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\t\t\tx: startAxis(x),\n\t\t\t\t\t\ty: startAxis(y),\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t},\n\t\t\tmove: (held, event) => {\n\t\t\t\tconst { axes } = held.hold;\n\t\t\t\tconst step = stepFieldDrag(\n\t\t\t\t\t{ x: axes.x?.model.mapping(), y: axes.y?.model.mapping() },\n\t\t\t\t\theld.state,\n\t\t\t\t\tflip(pointerMove(event), held.hold),\n\t\t\t\t);\n\n\t\t\t\theld.state = step.state;\n\t\t\t\tif (step.state.isEngaged) claim.reveal('drag');\n\t\t\t\toptions.input(held.hold, step);\n\t\t\t},\n\t\t\trelease: ({ from, hold }) => {\n\t\t\t\tclaim.press(false);\n\t\t\t\toptions.release(\n\t\t\t\t\thold,\n\t\t\t\t\tfieldAxes.filter((axis) => hold.axes[axis]?.model.value !== from[axis]),\n\t\t\t\t);\n\t\t\t\tpointerType = undefined;\n\t\t\t},\n\t\t\ttoggle: options.toggle,\n\t\t},\n\t\tsignal,\n\t);\n\n\treturn { current: () => drag.current()?.hold, end: drag.end, pointerType: () => pointerType };\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"field-gesture.js","names":[],"sources":["../../../src/elements/field-gesture.ts"],"sourcesContent":["import type { ReadoutClaim } from '#elements/readout-claim.ts';\nimport type { FieldAxis, FieldDragState, FieldPoint } from '#lib/field.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { revealDelay } from '#elements/readout-claim.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from '#lib/field.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\n\ninterface FieldAxisHold {\n\tfrom?: number;\n\tmodel: ValueModel;\n\tproportion?: number;\n\ttravelPx: number;\n}\n\nexport interface FieldHold {\n\taxes: Partial<Record<FieldAxis, FieldAxisHold>>;\n\tisFlipped?: boolean;\n}\n\nexport interface FieldInput {\n\tx?: number | undefined;\n\ty?: number | undefined;\n}\n\ninterface FieldGestureOptions<Hold extends FieldHold> {\n\tclaim: ReadoutClaim;\n\tgrab: (event: PointerEvent) => Hold | undefined;\n\tinput: (hold: Hold, next: FieldInput) => void;\n\tlift?: (hold: Hold, event: PointerEvent) => void;\n\trelease: (hold: Hold, moved: Array<FieldAxis>) => void;\n\ttoggle: (isDragging: boolean) => void;\n}\n\nexport interface FieldGesture<Hold> {\n\tcurrent: () => Hold | undefined;\n\tend: () => void;\n\tpointerType: () => string | undefined;\n}\n\ninterface Held<Hold> {\n\tfrom: FieldInput;\n\thold: Hold;\n\tstate: FieldDragState;\n}\n\nconst fieldAxes = ['x', 'y'] as const;\n\nfunction flip<Point extends FieldPoint>(point: Point, hold: FieldHold): Point {\n\treturn hold.isFlipped === true ? { ...point, y: -point.y } : point;\n}\n\nfunction startAxis(hold: FieldAxisHold | undefined) {\n\tif (!hold) return;\n\n\tconst { model, travelPx } = hold;\n\tconst mapping = model.mapping();\n\tconst from = model.value;\n\n\treturn { from, mapping, proportion: hold.proportion ?? mapping.proportionOf(from), travelPx };\n}\n\nexport function bindFieldGesture<Hold extends FieldHold>(\n\ttarget: HTMLElement,\n\toptions: FieldGestureOptions<Hold>,\n\tsignal: AbortSignal,\n): FieldGesture<Hold> {\n\tconst { claim } = options;\n\n\tlet pointerType: string | undefined;\n\n\tconst drag = bindDrag<Held<Hold>>(\n\t\ttarget,\n\t\t{\n\t\t\tcancel: ({ from, hold }) => {\n\t\t\t\toptions.input(hold, from);\n\t\t\t},\n\t\t\tgrab: (event) => {\n\t\t\t\t// Set first, so an `input` the grab itself fires can read it\n\t\t\t\tpointerType = event.pointerType;\n\n\t\t\t\tconst hold = options.grab(event);\n\t\t\t\tif (!hold) {\n\t\t\t\t\tpointerType = undefined;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { x, y } = hold.axes;\n\n\t\t\t\tclaim.press(true, revealDelay(target));\n\n\t\t\t\treturn {\n\t\t\t\t\tfrom: { x: x?.from ?? x?.model.value, y: y?.from ?? y?.model.value },\n\t\t\t\t\thold,\n\t\t\t\t\tstate: startFieldDrag({\n\t\t\t\t\t\tposition: flip(pointerPosition(event), hold),\n\t\t\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\t\t\tx: startAxis(x),\n\t\t\t\t\t\ty: startAxis(y),\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t},\n\t\t\tlift: ({ hold }, event) => {\n\t\t\t\toptions.lift?.(hold, event);\n\t\t\t},\n\t\t\tmove: (held, event) => {\n\t\t\t\tconst { axes } = held.hold;\n\t\t\t\tconst step = stepFieldDrag(\n\t\t\t\t\t{ x: axes.x?.model.mapping(), y: axes.y?.model.mapping() },\n\t\t\t\t\theld.state,\n\t\t\t\t\tflip(pointerMove(event), held.hold),\n\t\t\t\t);\n\n\t\t\t\theld.state = step.state;\n\t\t\t\tif (step.state.isEngaged) claim.reveal('drag');\n\t\t\t\toptions.input(held.hold, step);\n\t\t\t},\n\t\t\trelease: ({ from, hold }) => {\n\t\t\t\tclaim.press(false);\n\t\t\t\toptions.release(\n\t\t\t\t\thold,\n\t\t\t\t\tfieldAxes.filter((axis) => hold.axes[axis]?.model.value !== from[axis]),\n\t\t\t\t);\n\t\t\t\tpointerType = undefined;\n\t\t\t},\n\t\t\ttoggle: options.toggle,\n\t\t},\n\t\tsignal,\n\t);\n\n\treturn { current: () => drag.current()?.hold, end: drag.end, pointerType: () => pointerType };\n}\n"],"mappings":";;;;;;AA+CA,MAAM,YAAY,CAAC,KAAK,GAAG;AAE3B,SAAS,KAA+B,OAAc,MAAwB;CAC7E,OAAO,KAAK,cAAc,OAAO;EAAE,GAAG;EAAO,GAAG,CAAC,MAAM;CAAE,IAAI;AAC9D;AAEA,SAAS,UAAU,MAAiC;CACnD,IAAI,CAAC,MAAM;CAEX,MAAM,EAAE,OAAO,aAAa;CAC5B,MAAM,UAAU,MAAM,QAAQ;CAC9B,MAAM,OAAO,MAAM;CAEnB,OAAO;EAAE;EAAM;EAAS,YAAY,KAAK,cAAc,QAAQ,aAAa,IAAI;EAAG;CAAS;AAC7F;AAEA,SAAgB,iBACf,QACA,SACA,QACqB;CACrB,MAAM,EAAE,UAAU;CAElB,IAAI;CAEJ,MAAM,OAAO,SACZ,QACA;EACC,SAAS,EAAE,MAAM,WAAW;GAC3B,QAAQ,MAAM,MAAM,IAAI;EACzB;EACA,OAAO,UAAU;GAEhB,cAAc,MAAM;GAEpB,MAAM,OAAO,QAAQ,KAAK,KAAK;GAC/B,IAAI,CAAC,MAAM;IACV,cAAc;IACd;GACD;GAEA,MAAM,EAAE,GAAG,MAAM,KAAK;GAEtB,MAAM,MAAM,MAAM,YAAY,MAAM,CAAC;GAErC,OAAO;IACN,MAAM;KAAE,GAAG,GAAG,QAAQ,GAAG,MAAM;KAAO,GAAG,GAAG,QAAQ,GAAG,MAAM;IAAM;IACnE;IACA,OAAO,eAAe;KACrB,UAAU,KAAK,gBAAgB,KAAK,GAAG,IAAI;KAC3C,aAAa,gBAAgB,MAAM,WAAW;KAC9C,GAAG,UAAU,CAAC;KACd,GAAG,UAAU,CAAC;IACf,CAAC;GACF;EACD;EACA,OAAO,EAAE,QAAQ,UAAU;GAC1B,QAAQ,OAAO,MAAM,KAAK;EAC3B;EACA,OAAO,MAAM,UAAU;GACtB,MAAM,EAAE,SAAS,KAAK;GACtB,MAAM,OAAO,cACZ;IAAE,GAAG,KAAK,GAAG,MAAM,QAAQ;IAAG,GAAG,KAAK,GAAG,MAAM,QAAQ;GAAE,GACzD,KAAK,OACL,KAAK,YAAY,KAAK,GAAG,KAAK,IAAI,CACnC;GAEA,KAAK,QAAQ,KAAK;GAClB,IAAI,KAAK,MAAM,WAAW,MAAM,OAAO,MAAM;GAC7C,QAAQ,MAAM,KAAK,MAAM,IAAI;EAC9B;EACA,UAAU,EAAE,MAAM,WAAW;GAC5B,MAAM,MAAM,KAAK;GACjB,QAAQ,QACP,MACA,UAAU,QAAQ,SAAS,KAAK,KAAK,KAAK,EAAE,MAAM,UAAU,KAAK,KAAK,CACvE;GACA,cAAc;EACf;EACA,QAAQ,QAAQ;CACjB,GACA,MACD;CAEA,OAAO;EAAE,eAAe,KAAK,QAAQ,CAAC,EAAE;EAAM,KAAK,KAAK;EAAK,mBAAmB;CAAY;AAC7F"}
|
|
@@ -32,10 +32,7 @@ var SonicNumber = class extends SonicValueElement {
|
|
|
32
32
|
const number = this.#number;
|
|
33
33
|
this.keepControl(number, signal);
|
|
34
34
|
this.render();
|
|
35
|
-
this.checkStyles(number, "number.css"
|
|
36
|
-
property: "padding-inline-start",
|
|
37
|
-
ratio: "--_sonic-number-inset-ratio"
|
|
38
|
-
});
|
|
35
|
+
this.checkStyles(number, "number.css");
|
|
39
36
|
this.bindGestures(number, signal, () => ({
|
|
40
37
|
position: (event) => -event.clientY,
|
|
41
38
|
travelPx: this.travelPx(number, "--_sonic-number-travel")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tif (__DEV__)
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tif (__DEV__) this.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(number, '--_sonic-number-travel'),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#digits.textContent = this.valueText;\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override tapTarget(): number | undefined {\n\t\tif (this.press !== 'step') return undefined;\n\n\t\tconst mapping = this.mapping();\n\t\tconst next = mapping.keyTarget('ArrowUp', this.value);\n\n\t\treturn next === undefined || next === this.value ? mapping.bounds[0] : next;\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,IAAI,QAAyB;EAC5B,OAAO,KAAK,aAAa,OAAO,MAAM,SAAS,SAAS;CACzD;CAEA,IAAI,MAAM,SAAsC;EAC/C,KAAK,QAAQ,SAAS,OAAO;CAC9B;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,OAAO;EAC9B,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,YAAY,QAAQ,MAAM;EAC/B,KAAK,OAAO;EACC,KAAK,YAAY,QAAQ,YAAY;EAClD,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,QAAQ,wBAAwB;EACzD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAU,OAAa;EACtB,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,YAAgC;EAClD,IAAI,KAAK,UAAU,QAAQ,OAAO;EAElC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OAAO,QAAQ,UAAU,WAAW,KAAK,KAAK;EAEpD,OAAO,SAAS,UAAa,SAAS,KAAK,QAAQ,QAAQ,OAAO,KAAK;CACxE;AACD"}
|
|
@@ -58,12 +58,12 @@ var SonicSplit = class extends SonicElement {
|
|
|
58
58
|
#isWarned = false;
|
|
59
59
|
#isWriting = false;
|
|
60
60
|
#members = [];
|
|
61
|
-
#signal;
|
|
62
61
|
#snapshot;
|
|
63
62
|
#total = 0;
|
|
64
63
|
#waiting = /* @__PURE__ */ new Set();
|
|
65
64
|
attributeChangedCallback(name) {
|
|
66
|
-
if ((name === "mode" || name === "total") && this
|
|
65
|
+
if ((name === "mode" || name === "total") && this.isBound()) this.#bind();
|
|
66
|
+
if (name === "disabled" && this.isBound()) this.#limit();
|
|
67
67
|
}
|
|
68
68
|
connectedCallback() {
|
|
69
69
|
this.upgradeProperties("mode", "total");
|
|
@@ -73,7 +73,6 @@ var SonicSplit = class extends SonicElement {
|
|
|
73
73
|
const observer = new MutationObserver((records) => {
|
|
74
74
|
if (records.some((record) => record.attributeName !== null && lockAttributes.includes(record.attributeName)) || !this.#isSameMembers(this.#find())) this.#bind();
|
|
75
75
|
});
|
|
76
|
-
this.#signal = signal;
|
|
77
76
|
if (this.internals) this.internals.role = "group";
|
|
78
77
|
observer.observe(this, {
|
|
79
78
|
attributeFilter: lockAttributes,
|
|
@@ -86,7 +85,6 @@ var SonicSplit = class extends SonicElement {
|
|
|
86
85
|
}, { signal });
|
|
87
86
|
signal.addEventListener("abort", () => {
|
|
88
87
|
observer.disconnect();
|
|
89
|
-
this.#signal = void 0;
|
|
90
88
|
this.#isTotalKept = false;
|
|
91
89
|
this.#unbind();
|
|
92
90
|
}, { once: true });
|
|
@@ -152,11 +150,12 @@ var SonicSplit = class extends SonicElement {
|
|
|
152
150
|
})[index] ?? target;
|
|
153
151
|
}
|
|
154
152
|
#limit() {
|
|
153
|
+
const isFrozen = this.isDisabled();
|
|
155
154
|
const values = this.#members.map(({ seen }) => seen);
|
|
156
155
|
for (const [index, member] of this.#members.entries()) {
|
|
157
156
|
const model = this.#model(values, member);
|
|
158
157
|
const value = values[index] ?? member.seen;
|
|
159
|
-
member.link.model.setLimit(isLocked(member) ? [value, value] : gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)));
|
|
158
|
+
member.link.model.setLimit(isFrozen || isLocked(member) ? [value, value] : gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)));
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
161
|
#model(values, mover) {
|
|
@@ -192,7 +191,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
192
191
|
queueMicrotask(() => {
|
|
193
192
|
this.#isWriting = true;
|
|
194
193
|
try {
|
|
195
|
-
for (const {
|
|
194
|
+
for (const { link } of changed) link.change();
|
|
196
195
|
} finally {
|
|
197
196
|
this.#isWriting = false;
|
|
198
197
|
}
|
|
@@ -226,7 +225,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
226
225
|
this.#waiting.add(tag);
|
|
227
226
|
customElements.whenDefined(tag).then(() => {
|
|
228
227
|
this.#waiting.delete(tag);
|
|
229
|
-
if (this
|
|
228
|
+
if (this.isBound()) this.#bind();
|
|
230
229
|
});
|
|
231
230
|
}
|
|
232
231
|
#warnReach() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridSlop = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridSlop ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridSlop ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.model.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\t/** What the members add up to; unset, it is their sum when they connect */\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#signal: AbortSignal | undefined;\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.#signal) this.#bind();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tthis.#signal = signal;\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t\tthis.#signal = undefined;\n\t\t\t\tthis.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.model.value !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.model.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.model.setLanding((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.model.value, unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.model.setLimit(\n\t\t\t\tisLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.model.value },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.model.value !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { element } of changed) {\n\t\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.model.setLanding(undefined);\n\t\t\tlink.model.setLimit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.#signal) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.model.value) continue;\n\n\t\t\t\tmember.link.model.setLimit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.model.value;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridSlop = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridSlop ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridSlop ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.model.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\t/** What the members add up to; unset, it is their sum when they connect */\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.isBound()) this.#bind();\n\t\tif (name === 'disabled' && this.isBound()) this.#limit();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t\tthis.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.model.value !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.model.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.model.setLanding((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.model.value, unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst isFrozen = this.isDisabled();\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.model.setLimit(\n\t\t\t\tisFrozen || isLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.model.value },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.model.value !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { link } of changed) link.change();\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.model.setLanding(undefined);\n\t\t\tlink.model.setLimit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.isBound()) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.model.value) continue;\n\n\t\t\t\tmember.link.model.setLimit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.model.value;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
2
|
+
import { bindDrag, capturePointer } from "../lib/pointer-drag.js";
|
|
3
3
|
import { soleStep } from "../lib/positions.js";
|
|
4
4
|
import { isUnder } from "./radio-group.js";
|
|
5
5
|
import { SonicPositionGroupElement } from "./position-group.js";
|
|
@@ -28,7 +28,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
|
|
|
28
28
|
if (this.#isOnBat(event)) return true;
|
|
29
29
|
const target = this.pressTarget(index);
|
|
30
30
|
if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
|
|
31
|
-
this.group
|
|
31
|
+
capturePointer(this.group, event.pointerId);
|
|
32
32
|
this.hold(target, event.pointerId);
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
@@ -72,8 +72,10 @@ var SonicSwitch = class extends SonicPositionGroupElement {
|
|
|
72
72
|
};
|
|
73
73
|
},
|
|
74
74
|
lift: (press, event) => {
|
|
75
|
-
if (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);
|
|
76
75
|
this.#isClickSwallowed = true;
|
|
76
|
+
if (press.hasMoved || this.isDisabled()) return;
|
|
77
|
+
if (!isUnder(this.group, event.clientX, event.clientY)) return;
|
|
78
|
+
this.#pressBat(event);
|
|
77
79
|
},
|
|
78
80
|
move: (press, event) => {
|
|
79
81
|
if (this.isDisabled()) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { soleStep } from '#lib/positions.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\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 pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(index: number, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(index);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\
|
|
1
|
+
{"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { bindDrag, capturePointer } from '#lib/pointer-drag.ts';\nimport { soleStep } from '#lib/positions.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\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 pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(index: number, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(index);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\tcapturePointer(this.group, event.pointerId);\n\t\tthis.hold(target, 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\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(group, 'switch.css', {\n\t\t\t\tproperty: 'padding-top',\n\t\t\t\tselector: '.sonic-switch-label',\n\t\t\t});\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\t}\n\n\tprotected override releaseTarget(index: number): number | undefined {\n\t\tconst target = this.pressTarget(index);\n\n\t\treturn this.isMomentary(target) || this.isOptionDisabled(target) ? undefined : target;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tsuper.render();\n\t\tthis.#renderThrow(this.checkedIndex());\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\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 = this.pressedIndex(event.target) >= 0;\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\tthis.#isClickSwallowed = true;\n\t\t\t\t\tif (press.hasMoved || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.group, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#pressBat(event);\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#hasSprungFrom(index: number): boolean {\n\t\tif (!this.isMomentary(index)) return false;\n\n\t\tthis.releaseHold();\n\n\t\treturn !this.isChecked(index);\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 from = this.checkedIndex();\n\t\tconst target = soleStep(this.positions(), from) ?? this.#sideStep(from, event);\n\t\tif (target === undefined) return;\n\n\t\tthis.chooseByClick(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.positions().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#sideStep(from: number, event: PointerEvent): number | undefined {\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 side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\t\tconst toward = this.stepFrom(from, side);\n\n\t\treturn toward !== undefined && toward !== from ? toward : this.stepFrom(from, -side);\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = this.stepFrom(from, direction);\n\t\tif (target === undefined || target === from || this.#hasSprungFrom(from)) return;\n\n\t\tif (this.isMomentary(target)) this.hold(target, pointerId);\n\t\telse this.select(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ const renderPosition = template(`
|
|
|
19
19
|
</button>
|
|
20
20
|
`, HTMLButtonElement);
|
|
21
21
|
const renderBare = template(`<button class="sonic-toggle-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
22
|
+
const pressSlop = 2;
|
|
22
23
|
var SonicToggle = class extends SonicPositionGroupElement {
|
|
23
24
|
bare = renderBare();
|
|
24
25
|
defaultOrientation = "horizontal";
|
|
@@ -66,17 +67,19 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
66
67
|
bindDrag(this.group, {
|
|
67
68
|
grab: (event) => this.#grab(event),
|
|
68
69
|
lift: (drag, event) => {
|
|
69
|
-
if (drag.
|
|
70
|
+
if (!drag.isPress || drag.isHolding || this.isDisabled()) return;
|
|
70
71
|
if (!isUnder(this.group, event.clientX, event.clientY)) return;
|
|
71
72
|
this.#press(drag.position);
|
|
72
73
|
},
|
|
73
74
|
move: (drag, event) => {
|
|
74
75
|
if (drag.isHolding || this.isDisabled()) return;
|
|
75
76
|
const pointer = this.along(event);
|
|
76
|
-
|
|
77
|
+
const moved = Math.abs(pointer - drag.start);
|
|
78
|
+
if (!drag.hasMoved && moved < drag.thresholdPx) return;
|
|
77
79
|
drag.hasMoved = true;
|
|
78
80
|
this.toggleState("dragging", true);
|
|
79
|
-
|
|
81
|
+
if (moved >= pressSlop * drag.thresholdPx) drag.isPress = false;
|
|
82
|
+
if (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId)) drag.isPress = false;
|
|
80
83
|
},
|
|
81
84
|
release: (drag) => {
|
|
82
85
|
delete drag.marked.dataset.sonicPressed;
|
|
@@ -107,6 +110,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
107
110
|
return {
|
|
108
111
|
hasMoved: false,
|
|
109
112
|
isHolding: this.#holdFrom(position, event.pointerId),
|
|
113
|
+
isPress: true,
|
|
110
114
|
marked,
|
|
111
115
|
position,
|
|
112
116
|
start: this.along(event),
|
|
@@ -136,12 +140,13 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
136
140
|
const crossed = Math.round(at);
|
|
137
141
|
this.#draggedTo = at;
|
|
138
142
|
this.#placeCap(at);
|
|
139
|
-
if (this.isChecked(crossed)) return;
|
|
143
|
+
if (this.isChecked(crossed)) return false;
|
|
140
144
|
this.releaseHold();
|
|
141
|
-
if (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return;
|
|
145
|
+
if (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;
|
|
142
146
|
if (this.isMomentary(crossed)) this.hold(crossed, pointerId);
|
|
143
147
|
else this.select(crossed);
|
|
144
148
|
this.refocusAt(crossed);
|
|
149
|
+
return true;
|
|
145
150
|
}
|
|
146
151
|
#travel(event, isOnCap) {
|
|
147
152
|
const [first, last] = enabledEnds(this.positions());
|