@xsynaptic/sonic-ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/default/define/dial.js +7 -0
- package/dist/default/define/key.js +7 -0
- package/dist/default/define/meter.js +7 -0
- package/dist/default/define/number.js +7 -0
- package/dist/default/define/segmented.js +7 -0
- package/dist/default/define/slider.js +7 -0
- package/dist/{define.js → default/define.js} +1 -0
- package/dist/default/elements/define-once.js +7 -0
- package/dist/default/elements/dial.js +81 -0
- package/dist/default/elements/form-element.js +56 -0
- package/dist/default/elements/key.js +144 -0
- package/dist/{elements → default/elements}/meter.js +27 -16
- package/dist/default/elements/number.js +46 -0
- package/dist/default/elements/range-element.js +433 -0
- package/dist/{elements → default/elements}/range-entry.js +5 -4
- package/dist/default/elements/segmented.js +173 -0
- package/dist/default/elements/slider.js +99 -0
- package/dist/default/elements/sonic-element.js +153 -0
- package/dist/{index.js → default/index.js} +3 -1
- package/dist/default/lib/copy-node.js +30 -0
- package/dist/default/lib/percent.js +12 -0
- package/dist/default/lib/read-px-property.js +8 -0
- package/dist/{lib → default/lib}/render.js +9 -2
- package/dist/{lib → default/lib}/taper.js +4 -2
- package/dist/default/lib/write-attribute.js +11 -0
- package/dist/dev/define/dial.js.map +1 -0
- package/dist/dev/define/key.js.map +1 -0
- package/dist/dev/define/meter.js.map +1 -0
- package/dist/dev/define/number.d.ts +2 -0
- package/dist/dev/define/number.js +8 -0
- package/dist/dev/define/number.js.map +1 -0
- package/dist/dev/define/segmented.js.map +1 -0
- package/dist/dev/define/slider.js.map +1 -0
- package/dist/{define.d.ts → dev/define.d.ts} +3 -1
- package/dist/dev/define.js +6 -0
- package/dist/dev/elements/define-once.js.map +1 -0
- package/dist/{elements → dev/elements}/dial.d.ts +7 -0
- package/dist/{elements → dev/elements}/dial.js +31 -6
- package/dist/dev/elements/dial.js.map +1 -0
- package/dist/dev/elements/form-element.d.ts +22 -0
- package/dist/dev/elements/form-element.js +57 -0
- package/dist/dev/elements/form-element.js.map +1 -0
- package/dist/dev/elements/key.d.ts +30 -0
- package/dist/{elements → dev/elements}/key.js +71 -25
- package/dist/dev/elements/key.js.map +1 -0
- package/dist/{elements → dev/elements}/meter.d.ts +6 -0
- package/dist/dev/elements/meter.js +131 -0
- package/dist/dev/elements/meter.js.map +1 -0
- package/dist/dev/elements/number.d.ts +16 -0
- package/dist/dev/elements/number.js +47 -0
- package/dist/dev/elements/number.js.map +1 -0
- package/dist/dev/elements/range-element.d.ts +56 -0
- package/dist/{elements → dev/elements}/range-element.js +153 -67
- package/dist/dev/elements/range-element.js.map +1 -0
- package/dist/dev/elements/range-entry.js +75 -0
- package/dist/dev/elements/range-entry.js.map +1 -0
- package/dist/{elements → dev/elements}/segmented.d.ts +5 -2
- package/dist/dev/elements/segmented.js +174 -0
- package/dist/dev/elements/segmented.js.map +1 -0
- package/dist/{elements → dev/elements}/slider.d.ts +8 -0
- package/dist/{elements → dev/elements}/slider.js +37 -7
- package/dist/dev/elements/slider.js.map +1 -0
- package/dist/dev/elements/sonic-element.d.ts +33 -0
- package/dist/dev/elements/sonic-element.js +169 -0
- package/dist/dev/elements/sonic-element.js.map +1 -0
- package/dist/{index.d.ts → dev/index.d.ts} +3 -1
- package/dist/dev/index.js +9 -0
- package/dist/dev/lib/copy-node.js +31 -0
- package/dist/dev/lib/copy-node.js.map +1 -0
- package/dist/dev/lib/percent.d.ts +5 -0
- package/dist/dev/lib/percent.js +13 -0
- package/dist/dev/lib/percent.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -0
- package/dist/dev/lib/render.js +27 -0
- package/dist/dev/lib/render.js.map +1 -0
- package/dist/dev/lib/taper.js +36 -0
- package/dist/dev/lib/taper.js.map +1 -0
- package/dist/dev/lib/write-attribute.js.map +1 -0
- package/dist/skins/ivory.css +3 -3
- package/dist/skins/slate.css +4 -4
- package/dist/styles/controls.css +2 -1
- package/dist/styles/dial.css +70 -16
- package/dist/styles/key.css +71 -11
- package/dist/styles/led.css +75 -0
- package/dist/styles/material.css +154 -25
- package/dist/styles/meter.css +44 -8
- package/dist/styles/number.css +81 -0
- package/dist/styles/screen.css +6 -33
- package/dist/styles/segmented.css +45 -7
- package/dist/styles/slider.css +68 -4
- package/package.json +16 -13
- package/dist/define/dial.js.map +0 -1
- package/dist/define/key.js.map +0 -1
- package/dist/define/meter.js.map +0 -1
- package/dist/define/segmented.js.map +0 -1
- package/dist/define/slider.js.map +0 -1
- package/dist/elements/define-once.js.map +0 -1
- package/dist/elements/dial.js.map +0 -1
- package/dist/elements/key.d.ts +0 -17
- package/dist/elements/key.js.map +0 -1
- package/dist/elements/meter.js.map +0 -1
- package/dist/elements/range-element.d.ts +0 -32
- package/dist/elements/range-element.js.map +0 -1
- package/dist/elements/range-entry.js.map +0 -1
- package/dist/elements/segmented.js +0 -104
- package/dist/elements/segmented.js.map +0 -1
- package/dist/elements/slider.js.map +0 -1
- package/dist/elements/sonic-element.d.ts +0 -20
- package/dist/elements/sonic-element.js +0 -94
- package/dist/elements/sonic-element.js.map +0 -1
- package/dist/lib/read-px-property.js.map +0 -1
- package/dist/lib/render.js.map +0 -1
- package/dist/lib/taper.js.map +0 -1
- package/dist/lib/write-attribute.js.map +0 -1
- /package/dist/{define → dev/define}/dial.d.ts +0 -0
- /package/dist/{define → dev/define}/dial.js +0 -0
- /package/dist/{define → dev/define}/key.d.ts +0 -0
- /package/dist/{define → dev/define}/key.js +0 -0
- /package/dist/{define → dev/define}/meter.d.ts +0 -0
- /package/dist/{define → dev/define}/meter.js +0 -0
- /package/dist/{define → dev/define}/segmented.d.ts +0 -0
- /package/dist/{define → dev/define}/segmented.js +0 -0
- /package/dist/{define → dev/define}/slider.d.ts +0 -0
- /package/dist/{define → dev/define}/slider.js +0 -0
- /package/dist/{elements → dev/elements}/define-once.js +0 -0
- /package/dist/{lib → dev/lib}/read-px-property.js +0 -0
- /package/dist/{lib → dev/lib}/write-attribute.js +0 -0
|
@@ -1,52 +1,105 @@
|
|
|
1
|
-
import { SonicElement } from "./sonic-element.js";
|
|
2
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { SonicFormElement } from "./form-element.js";
|
|
3
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
3
4
|
|
|
4
5
|
//#region src/elements/key.ts
|
|
5
6
|
const renderKey = template(`
|
|
6
7
|
<button class="sonic-key" type="button"><span class="sonic-key-cap"></span></button>
|
|
7
8
|
`, HTMLButtonElement);
|
|
8
9
|
const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
9
|
-
var SonicKey = class extends
|
|
10
|
+
var SonicKey = class extends SonicFormElement {
|
|
10
11
|
static observedAttributes = [
|
|
11
|
-
...
|
|
12
|
+
...SonicFormElement.observedAttributes,
|
|
12
13
|
"momentary",
|
|
13
14
|
"pressed",
|
|
14
|
-
"toggle"
|
|
15
|
+
"toggle",
|
|
16
|
+
"value"
|
|
15
17
|
];
|
|
16
|
-
get
|
|
18
|
+
get defaultPressed() {
|
|
17
19
|
return this.hasAttribute("pressed");
|
|
18
20
|
}
|
|
21
|
+
set defaultPressed(isPressed) {
|
|
22
|
+
this.reflect("pressed", isPressed);
|
|
23
|
+
}
|
|
24
|
+
get momentary() {
|
|
25
|
+
return this.hasAttribute("momentary");
|
|
26
|
+
}
|
|
27
|
+
set momentary(isMomentary) {
|
|
28
|
+
this.reflect("momentary", isMomentary);
|
|
29
|
+
}
|
|
30
|
+
get pressed() {
|
|
31
|
+
return this.#isDirty ? this.#pressed : this.defaultPressed;
|
|
32
|
+
}
|
|
19
33
|
set pressed(isPressed) {
|
|
20
|
-
this
|
|
34
|
+
this.#isDirty = true;
|
|
35
|
+
this.#pressed = isPressed === true;
|
|
36
|
+
this.render();
|
|
37
|
+
}
|
|
38
|
+
get toggle() {
|
|
39
|
+
return this.hasAttribute("toggle");
|
|
40
|
+
}
|
|
41
|
+
set toggle(isToggle) {
|
|
42
|
+
this.reflect("toggle", isToggle);
|
|
43
|
+
}
|
|
44
|
+
get value() {
|
|
45
|
+
return this.getAttribute("value") ?? "on";
|
|
46
|
+
}
|
|
47
|
+
set value(value) {
|
|
48
|
+
this.reflect("value", value);
|
|
21
49
|
}
|
|
22
50
|
#holder;
|
|
51
|
+
#isDirty = false;
|
|
23
52
|
#key = renderKey();
|
|
53
|
+
#pressed = false;
|
|
24
54
|
attributeChangedCallback(name) {
|
|
25
|
-
if (name === "
|
|
26
|
-
this.#
|
|
55
|
+
if (name === "pressed") this.#isDirty = false;
|
|
56
|
+
if (name === "disabled" && this.isDisabled()) this.#release();
|
|
57
|
+
this.render();
|
|
58
|
+
}
|
|
59
|
+
formResetCallback() {
|
|
60
|
+
this.attributeChangedCallback("pressed");
|
|
61
|
+
}
|
|
62
|
+
activate() {
|
|
63
|
+
this.#key.click();
|
|
27
64
|
}
|
|
28
65
|
connect(signal) {
|
|
29
66
|
const key = this.#key;
|
|
30
67
|
const cap = requireChild(key, ".sonic-key-cap", HTMLSpanElement);
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
68
|
+
this.upgradeProperties("defaultPressed", "momentary", "toggle", "pressed", "value");
|
|
69
|
+
this.mirrorChildren({
|
|
70
|
+
control: key,
|
|
71
|
+
copy: (originals) => {
|
|
72
|
+
cap.replaceChildren(...originals.map((original) => copyNode(original)));
|
|
73
|
+
}
|
|
37
74
|
}, signal);
|
|
38
|
-
this
|
|
75
|
+
this.render();
|
|
39
76
|
this.checkStyles(key, "key.css");
|
|
40
77
|
signal.addEventListener("abort", () => {
|
|
41
78
|
this.#release();
|
|
42
79
|
}, { once: true });
|
|
43
80
|
key.addEventListener("click", () => {
|
|
44
|
-
if (!this.
|
|
81
|
+
if (!this.toggle) return;
|
|
45
82
|
this.pressed = !this.pressed;
|
|
46
83
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
47
84
|
}, { signal });
|
|
48
85
|
this.#bindMomentary(key, signal);
|
|
49
86
|
}
|
|
87
|
+
focusTarget() {
|
|
88
|
+
return this.#key;
|
|
89
|
+
}
|
|
90
|
+
render() {
|
|
91
|
+
const key = this.#key;
|
|
92
|
+
key.disabled = this.isDisabled();
|
|
93
|
+
key.removeAttribute("aria-pressed");
|
|
94
|
+
if (this.toggle) key.setAttribute("aria-pressed", String(this.pressed));
|
|
95
|
+
this.toggleState("pressed", this.pressed);
|
|
96
|
+
this.forwardNaming(key, true);
|
|
97
|
+
const submitted = this.toggle && this.pressed ? this.value : null;
|
|
98
|
+
this.writeFormValue(submitted, String(this.pressed));
|
|
99
|
+
}
|
|
100
|
+
restoreState(state) {
|
|
101
|
+
if (this.toggle) this.pressed = state === "true";
|
|
102
|
+
}
|
|
50
103
|
#bindMomentary(key, signal) {
|
|
51
104
|
const releasePointer = (event) => {
|
|
52
105
|
if (event.pointerId === this.#holder) this.#release();
|
|
@@ -71,13 +124,13 @@ var SonicKey = class extends SonicElement {
|
|
|
71
124
|
}, { signal });
|
|
72
125
|
}
|
|
73
126
|
#hold(holder) {
|
|
74
|
-
if (this.
|
|
127
|
+
if (this.isDisabled() || this.#holder !== void 0) return;
|
|
75
128
|
this.#holder = holder;
|
|
76
129
|
this.pressed = true;
|
|
77
130
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
78
131
|
}
|
|
79
132
|
#isMomentary() {
|
|
80
|
-
return this.
|
|
133
|
+
return this.momentary && !this.toggle;
|
|
81
134
|
}
|
|
82
135
|
#release() {
|
|
83
136
|
if (this.#holder === void 0) return;
|
|
@@ -85,13 +138,6 @@ var SonicKey = class extends SonicElement {
|
|
|
85
138
|
this.pressed = false;
|
|
86
139
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
87
140
|
}
|
|
88
|
-
#render() {
|
|
89
|
-
const key = this.#key;
|
|
90
|
-
key.disabled = this.disabled;
|
|
91
|
-
key.removeAttribute("aria-pressed");
|
|
92
|
-
if (this.hasAttribute("toggle")) key.setAttribute("aria-pressed", String(this.pressed));
|
|
93
|
-
this.forwardNaming(key, true);
|
|
94
|
-
}
|
|
95
141
|
};
|
|
96
142
|
|
|
97
143
|
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.js","names":[],"sources":["../../../src/elements/key.ts"],"sourcesContent":["import { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-key': SonicKey;\n\t}\n}\n\nconst renderKey = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-key\" type=\"button\"><span class=\"sonic-key-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nexport class SonicKey extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'momentary',\n\t\t'pressed',\n\t\t'toggle',\n\t\t'value',\n\t];\n\n\t// The `pressed` attribute, as `defaultChecked` holds a checkbox's `checked` attribute\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 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\t// The live state, which never writes the attribute, as `value` on the range\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 toggle(): boolean {\n\t\treturn this.hasAttribute('toggle');\n\t}\n\n\tset toggle(isToggle: boolean) {\n\t\tthis.reflect('toggle', isToggle);\n\t}\n\n\t// What a pressed toggle submits, reading `on` while unset as a checkbox does\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\t#holder: number | string | undefined;\n\n\t// Re-read from the attribute until a press or a write\n\t// An attribute write clears it, so a framework driving `pressed` keeps control\n\t#isDirty = false;\n\n\treadonly #key = renderKey();\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#release();\n\t\tthis.render();\n\t}\n\n\t// Back to the `pressed` attribute, as a checkbox resets to `checked`\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\t// As a checkbox's label toggles it\n\tprotected override activate(): void {\n\t\tthis.#key.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst key = this.#key;\n\t\tconst cap = requireChild(key, '.sonic-key-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties('defaultPressed', 'momentary', 'toggle', 'pressed', 'value');\n\t\tthis.mirrorChildren(\n\t\t\t{\n\t\t\t\tcontrol: key,\n\t\t\t\tcopy: (originals) => {\n\t\t\t\t\tcap.replaceChildren(...originals.map((original) => copyNode(original)));\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(key, 'key.css');\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#release();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\n\t\tkey.addEventListener(\n\t\t\t'click',\n\t\t\t() => {\n\t\t\t\tif (!this.toggle) 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(key, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#key;\n\t}\n\n\tprotected render(): void {\n\t\tconst key = this.#key;\n\n\t\tkey.disabled = this.isDisabled();\n\t\tkey.removeAttribute('aria-pressed');\n\t\tif (this.toggle) key.setAttribute('aria-pressed', String(this.pressed));\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(key, true);\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.toggle && this.pressed ? this.value : null;\n\n\t\t// The state restores an unpressed toggle too\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.toggle) this.pressed = state === 'true';\n\t}\n\n\t// Not `click`, which lands a tap late by the finger's dwell\n\t#bindMomentary(key: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst releasePointer = (event: PointerEvent): void => {\n\t\t\tif (event.pointerId === this.#holder) this.#release();\n\t\t};\n\n\t\tkey.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\tkey.setPointerCapture(event.pointerId);\n\t\t\t\tthis.#hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tkey.addEventListener('lostpointercapture', releasePointer, { signal });\n\t\tkey.addEventListener('pointercancel', releasePointer, { signal });\n\t\tkey.addEventListener('pointerup', releasePointer, { signal });\n\t\tkey.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\tthis.#hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tkey.addEventListener(\n\t\t\t'keyup',\n\t\t\t(event) => {\n\t\t\t\tif (event.key === this.#holder) this.#release();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// Keyboard holds only; a finger on the next pad takes focus while this one is still held\n\t\tkey.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tif (typeof this.#holder === 'string') this.#release();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#hold(holder: number | string): void {\n\t\tif (this.isDisabled() || this.#holder !== undefined) return;\n\n\t\tthis.#holder = holder;\n\t\tthis.pressed = true;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.toggle;\n\t}\n\n\t#release(): void {\n\t\tif (this.#holder === undefined) return;\n\n\t\tthis.#holder = undefined;\n\t\tthis.pressed = false;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -10,6 +10,12 @@ export declare class SonicMeter extends SonicElement {
|
|
|
10
10
|
static readonly observedAttributes: string[];
|
|
11
11
|
get level(): number;
|
|
12
12
|
set level(amplitude: number);
|
|
13
|
+
get max(): number;
|
|
14
|
+
set max(decibels: number | undefined);
|
|
15
|
+
get min(): number;
|
|
16
|
+
set min(decibels: number | undefined);
|
|
17
|
+
get orientation(): 'horizontal' | 'vertical';
|
|
18
|
+
set orientation(direction: 'horizontal' | 'vertical' | undefined);
|
|
13
19
|
attributeChangedCallback(): void;
|
|
14
20
|
protected connect(signal: AbortSignal): void;
|
|
15
21
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { template } from "../lib/render.js";
|
|
2
|
+
import { SonicElement } from "./sonic-element.js";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/meter.ts
|
|
5
|
+
const renderMeter = template(`
|
|
6
|
+
<div aria-hidden="true" class="sonic-meter">
|
|
7
|
+
<div class="sonic-meter-segments"></div>
|
|
8
|
+
<div class="sonic-meter-level"></div>
|
|
9
|
+
<div class="sonic-meter-clip"></div>
|
|
10
|
+
</div>
|
|
11
|
+
`, HTMLDivElement);
|
|
12
|
+
const releaseDecibelsPerSecond = 20;
|
|
13
|
+
const holdMs = 1500;
|
|
14
|
+
function toDecibels(amplitude) {
|
|
15
|
+
return 20 * Math.log10(Math.abs(amplitude));
|
|
16
|
+
}
|
|
17
|
+
var SonicMeter = class extends SonicElement {
|
|
18
|
+
static observedAttributes = [
|
|
19
|
+
"disabled",
|
|
20
|
+
"max",
|
|
21
|
+
"min"
|
|
22
|
+
];
|
|
23
|
+
get level() {
|
|
24
|
+
return this.#level;
|
|
25
|
+
}
|
|
26
|
+
set level(amplitude) {
|
|
27
|
+
if (!Number.isFinite(amplitude)) return;
|
|
28
|
+
const decibels = toDecibels(amplitude);
|
|
29
|
+
const now = performance.now();
|
|
30
|
+
this.#level = amplitude;
|
|
31
|
+
this.#target = decibels;
|
|
32
|
+
this.#bar = Math.max(this.#bar, decibels);
|
|
33
|
+
if (decibels >= this.#peak) {
|
|
34
|
+
this.#peak = decibels;
|
|
35
|
+
this.#peakAt = now;
|
|
36
|
+
}
|
|
37
|
+
if (decibels >= 0) this.#clipAt = now;
|
|
38
|
+
this.#render(now);
|
|
39
|
+
this.#schedule(now);
|
|
40
|
+
}
|
|
41
|
+
get max() {
|
|
42
|
+
return this.numberAttribute("max", 0);
|
|
43
|
+
}
|
|
44
|
+
set max(decibels) {
|
|
45
|
+
this.reflect("max", decibels);
|
|
46
|
+
}
|
|
47
|
+
get min() {
|
|
48
|
+
return this.numberAttribute("min", -60);
|
|
49
|
+
}
|
|
50
|
+
set min(decibels) {
|
|
51
|
+
this.reflect("min", decibels);
|
|
52
|
+
}
|
|
53
|
+
get orientation() {
|
|
54
|
+
return this.getAttribute("orientation") === "horizontal" ? "horizontal" : "vertical";
|
|
55
|
+
}
|
|
56
|
+
set orientation(direction) {
|
|
57
|
+
this.reflect("orientation", direction);
|
|
58
|
+
}
|
|
59
|
+
#bar = -Infinity;
|
|
60
|
+
#clipAt = -Infinity;
|
|
61
|
+
#frame;
|
|
62
|
+
#lastFrame;
|
|
63
|
+
#level = 0;
|
|
64
|
+
#meter = renderMeter();
|
|
65
|
+
#peak = -Infinity;
|
|
66
|
+
#peakAt = -Infinity;
|
|
67
|
+
#target = -Infinity;
|
|
68
|
+
attributeChangedCallback() {
|
|
69
|
+
const now = performance.now();
|
|
70
|
+
this.#render(now);
|
|
71
|
+
this.#schedule(now);
|
|
72
|
+
}
|
|
73
|
+
connect(signal) {
|
|
74
|
+
const now = performance.now();
|
|
75
|
+
this.upgradeProperties("max", "min", "orientation", "level");
|
|
76
|
+
this.appendOnce(this.#meter);
|
|
77
|
+
this.#render(now);
|
|
78
|
+
this.#schedule(now);
|
|
79
|
+
this.checkStyles(this.#meter, "meter.css");
|
|
80
|
+
signal.addEventListener("abort", () => {
|
|
81
|
+
if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
|
|
82
|
+
this.#frame = void 0;
|
|
83
|
+
this.#lastFrame = void 0;
|
|
84
|
+
}, { once: true });
|
|
85
|
+
}
|
|
86
|
+
#fraction(decibels) {
|
|
87
|
+
const min = this.min;
|
|
88
|
+
const range = this.max - min;
|
|
89
|
+
return range > 0 ? Math.min(1, Math.max(0, (decibels - min) / range)) : 0;
|
|
90
|
+
}
|
|
91
|
+
#isSettled(now) {
|
|
92
|
+
const floor = this.min;
|
|
93
|
+
return this.#bar <= Math.max(this.#target, floor) && this.#peak <= Math.max(this.#bar, floor) && now - this.#clipAt >= holdMs;
|
|
94
|
+
}
|
|
95
|
+
#render(now) {
|
|
96
|
+
const meter = this.#meter;
|
|
97
|
+
meter.style.setProperty("--_sonic-meter-min", String(this.min));
|
|
98
|
+
meter.style.setProperty("--_sonic-meter-max", String(this.max));
|
|
99
|
+
if (this.disabled) {
|
|
100
|
+
meter.style.setProperty("--_sonic-meter-level", "0");
|
|
101
|
+
meter.style.setProperty("--_sonic-meter-peak", "0");
|
|
102
|
+
meter.style.setProperty("--_sonic-meter-clipped", "0");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
meter.style.setProperty("--_sonic-meter-level", String(this.#fraction(this.#bar)));
|
|
106
|
+
meter.style.setProperty("--_sonic-meter-peak", String(this.#fraction(this.#peak)));
|
|
107
|
+
meter.style.setProperty("--_sonic-meter-clipped", now - this.#clipAt < holdMs ? "1" : "0");
|
|
108
|
+
}
|
|
109
|
+
#schedule(now) {
|
|
110
|
+
if (this.#frame !== void 0 || !this.isConnected || this.#isSettled(now)) return;
|
|
111
|
+
if (this.#lastFrame === void 0) this.#lastFrame = now;
|
|
112
|
+
this.#frame = requestAnimationFrame(this.#tick);
|
|
113
|
+
}
|
|
114
|
+
#tick = (time) => {
|
|
115
|
+
const fall = releaseDecibelsPerSecond * Math.max(0, time - (this.#lastFrame ?? time)) / 1e3;
|
|
116
|
+
this.#frame = void 0;
|
|
117
|
+
this.#lastFrame = time;
|
|
118
|
+
this.#bar = Math.max(this.#target, this.min, this.#bar - fall);
|
|
119
|
+
if (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);
|
|
120
|
+
this.#render(time);
|
|
121
|
+
if (this.#isSettled(time)) {
|
|
122
|
+
this.#lastFrame = void 0;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.#frame = requestAnimationFrame(this.#tick);
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { SonicMeter };
|
|
131
|
+
//# sourceMappingURL=meter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import { SonicElement } from '#elements/sonic-element.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-level\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst releaseDecibelsPerSecond = 20;\nconst holdMs = 1500;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\n// `level` is a linear amplitude; `min` and `max` are dBFS\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = ['disabled', 'max', 'min'];\n\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(amplitude: number) {\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst decibels = toDecibels(amplitude);\n\t\tconst now = performance.now();\n\n\t\tthis.#level = amplitude;\n\t\tthis.#target = decibels;\n\t\tthis.#bar = Math.max(this.#bar, decibels);\n\t\tif (decibels >= this.#peak) {\n\t\t\tthis.#peak = decibels;\n\t\t\tthis.#peakAt = now;\n\t\t}\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t#bar = -Infinity;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakAt = -Infinity;\n\n\t#target = -Infinity;\n\n\tattributeChangedCallback(): void {\n\t\tconst now = performance.now();\n\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties('max', 'min', 'orientation', 'level');\n\t\tthis.appendOnce(this.#meter);\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tthis.checkStyles(this.#meter, 'meter.css');\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#fraction(decibels: number): number {\n\t\tconst min = this.min;\n\t\tconst range = this.max - min;\n\n\t\treturn range > 0 ? Math.min(1, Math.max(0, (decibels - min) / range)) : 0;\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peak <= Math.max(this.#bar, floor) &&\n\t\t\tnow - this.#clipAt >= holdMs\n\t\t);\n\t}\n\n\t#render(now: number): void {\n\t\tconst meter = this.#meter;\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\n\t\t// Disabled puts the light out, as on the other controls; the ballistics keep running so it lights up mid-fall\n\t\tif (this.disabled) {\n\t\t\tmeter.style.setProperty('--_sonic-meter-level', '0');\n\t\t\tmeter.style.setProperty('--_sonic-meter-peak', '0');\n\t\t\tmeter.style.setProperty('--_sonic-meter-clipped', '0');\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-level', String(this.#fraction(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak', String(this.#fraction(this.#peak)));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t// Timed by the frame rather than counted, so a 120Hz display falls at the same speed\n\treadonly #tick = (time: number): void => {\n\t\tconst fall = (releaseDecibelsPerSecond * Math.max(0, time - (this.#lastFrame ?? time))) / 1000;\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#bar = Math.max(this.#target, this.min, this.#bar - fall);\n\t\tif (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
2
|
+
//#region src/elements/number.d.ts
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'sonic-number': SonicNumber;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare class SonicNumber extends SonicRangeElement {
|
|
9
|
+
#private;
|
|
10
|
+
protected connect(signal: AbortSignal): void;
|
|
11
|
+
protected controlRole(): 'spinbutton';
|
|
12
|
+
protected focusTarget(): HTMLElement;
|
|
13
|
+
protected render(): void;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//# sourceMappingURL=number.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
3
|
+
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
|
|
5
|
+
//#region src/elements/number.ts
|
|
6
|
+
const renderNumber = template(`
|
|
7
|
+
<div class="sonic-number" role="spinbutton" tabindex="0">
|
|
8
|
+
<span aria-hidden="true" class="sonic-number-value"></span>
|
|
9
|
+
<input
|
|
10
|
+
autocomplete="off"
|
|
11
|
+
class="sonic-number-entry"
|
|
12
|
+
enterkeyhint="done"
|
|
13
|
+
hidden
|
|
14
|
+
spellcheck="false"
|
|
15
|
+
type="text"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
`, HTMLDivElement);
|
|
19
|
+
const fallbackTravelPx = 160;
|
|
20
|
+
var SonicNumber = class extends SonicRangeElement {
|
|
21
|
+
#number = renderNumber();
|
|
22
|
+
#digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
|
|
23
|
+
connect(signal) {
|
|
24
|
+
const number = this.#number;
|
|
25
|
+
this.appendOnce(number);
|
|
26
|
+
this.render();
|
|
27
|
+
this.checkStyles(number, "number.css");
|
|
28
|
+
this.bindGestures(number, signal, () => ({
|
|
29
|
+
position: (event) => -event.clientY,
|
|
30
|
+
travelPx: readPxProperty(getComputedStyle(number), "--_sonic-number-travel", fallbackTravelPx)
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
controlRole() {
|
|
34
|
+
return "spinbutton";
|
|
35
|
+
}
|
|
36
|
+
focusTarget() {
|
|
37
|
+
return this.#number;
|
|
38
|
+
}
|
|
39
|
+
render() {
|
|
40
|
+
this.#digits.textContent = this.valueText();
|
|
41
|
+
this.renderAria(this.#number);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { SonicNumber };
|
|
47
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicRangeElement } from '#elements/range-element.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\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\nconst fallbackTravelPx = 160;\n\nexport class SonicNumber extends SonicRangeElement {\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.appendOnce(number);\n\t\tthis.render();\n\t\tthis.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: readPxProperty(\n\t\t\t\tgetComputedStyle(number),\n\t\t\t\t'--_sonic-number-travel',\n\t\t\t\tfallbackTravelPx,\n\t\t\t),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected render(): void {\n\t\tthis.#digits.textContent = this.valueText();\n\t\tthis.renderAria(this.#number);\n\t}\n}\n"],"mappings":";;;;;AAUA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,MAAM,mBAAmB;AAEzB,IAAa,cAAb,cAAiC,kBAAkB;CAClD,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,WAAW,MAAM;EACtB,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,YAAY;EACrC,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,eACT,iBAAiB,MAAM,GACvB,0BACA,gBACD;EACD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAU,SAAe;EACxB,KAAK,QAAQ,cAAc,KAAK,UAAU;EAC1C,KAAK,WAAW,KAAK,OAAO;CAC7B;AACD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
2
|
+
//#region src/elements/range-element.d.ts
|
|
3
|
+
export interface RangeAxis {
|
|
4
|
+
position: (event: PointerEvent) => number;
|
|
5
|
+
travelPx: number;
|
|
6
|
+
}
|
|
7
|
+
export declare abstract class SonicRangeElement extends SonicFormElement {
|
|
8
|
+
#private;
|
|
9
|
+
static readonly observedAttributes: string[];
|
|
10
|
+
get default(): number | undefined;
|
|
11
|
+
set default(value: number | undefined);
|
|
12
|
+
get dimmed(): boolean;
|
|
13
|
+
set dimmed(isDimmed: boolean);
|
|
14
|
+
get doublePress(): 'reset' | 'type';
|
|
15
|
+
set doublePress(gesture: 'reset' | 'type' | undefined);
|
|
16
|
+
get formatValue(): ((value: number) => string) | undefined;
|
|
17
|
+
set formatValue(format: ((value: number) => string) | undefined);
|
|
18
|
+
get max(): number;
|
|
19
|
+
set max(value: number | undefined);
|
|
20
|
+
get midpoint(): number | undefined;
|
|
21
|
+
set midpoint(value: number | undefined);
|
|
22
|
+
get min(): number;
|
|
23
|
+
set min(value: number | undefined);
|
|
24
|
+
get notched(): boolean;
|
|
25
|
+
set notched(isNotched: boolean);
|
|
26
|
+
get origin(): number | undefined;
|
|
27
|
+
set origin(value: number | undefined);
|
|
28
|
+
get parseValue(): ((text: string) => number) | undefined;
|
|
29
|
+
set parseValue(parse: ((text: string) => number) | undefined);
|
|
30
|
+
get readout(): boolean;
|
|
31
|
+
set readout(isShown: boolean);
|
|
32
|
+
get step(): number;
|
|
33
|
+
set step(value: number | undefined);
|
|
34
|
+
get taper(): 'linear' | 'log';
|
|
35
|
+
set taper(curve: 'linear' | 'log' | undefined);
|
|
36
|
+
get value(): number;
|
|
37
|
+
set value(next: number);
|
|
38
|
+
attributeChangedCallback(name: string): void;
|
|
39
|
+
connectedCallback(): void;
|
|
40
|
+
protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => RangeAxis | undefined): void;
|
|
41
|
+
protected clampRange(next: number): number;
|
|
42
|
+
protected controlRole(): 'slider' | 'spinbutton';
|
|
43
|
+
protected fraction(value: number): number;
|
|
44
|
+
protected input(next: number): boolean;
|
|
45
|
+
protected modulationFractions(amount: number): [number, number];
|
|
46
|
+
protected originFraction(): number;
|
|
47
|
+
protected positions(): number | undefined;
|
|
48
|
+
protected range(): number;
|
|
49
|
+
protected renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void;
|
|
50
|
+
protected restoreState(state: string): void;
|
|
51
|
+
protected valueAt(fraction: number): number;
|
|
52
|
+
protected valueText(): string;
|
|
53
|
+
protected wraps(): boolean;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//# sourceMappingURL=range-element.d.ts.map
|