@xsynaptic/sonic-ui 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/define/dial.d.ts +2 -0
  4. package/dist/define/dial.js +8 -0
  5. package/dist/define/dial.js.map +1 -0
  6. package/dist/define/key.d.ts +2 -0
  7. package/dist/define/key.js +8 -0
  8. package/dist/define/key.js.map +1 -0
  9. package/dist/define/meter.d.ts +2 -0
  10. package/dist/define/meter.js +8 -0
  11. package/dist/define/meter.js.map +1 -0
  12. package/dist/define/segmented.d.ts +2 -0
  13. package/dist/define/segmented.js +8 -0
  14. package/dist/define/segmented.js.map +1 -0
  15. package/dist/define/slider.d.ts +2 -0
  16. package/dist/define/slider.js +8 -0
  17. package/dist/define/slider.js.map +1 -0
  18. package/dist/define.d.ts +11 -0
  19. package/dist/define.js +5 -0
  20. package/dist/elements/define-once.js +8 -0
  21. package/dist/elements/define-once.js.map +1 -0
  22. package/dist/elements/dial.d.ts +15 -0
  23. package/dist/elements/dial.js +57 -0
  24. package/dist/elements/dial.js.map +1 -0
  25. package/dist/elements/key.d.ts +17 -0
  26. package/dist/elements/key.js +99 -0
  27. package/dist/elements/key.js.map +1 -0
  28. package/dist/elements/meter.d.ts +17 -0
  29. package/dist/elements/meter.js +119 -0
  30. package/dist/elements/meter.js.map +1 -0
  31. package/dist/elements/range-element.d.ts +32 -0
  32. package/dist/elements/range-element.js +348 -0
  33. package/dist/elements/range-element.js.map +1 -0
  34. package/dist/elements/range-entry.js +73 -0
  35. package/dist/elements/range-entry.js.map +1 -0
  36. package/dist/elements/segmented.d.ts +17 -0
  37. package/dist/elements/segmented.js +104 -0
  38. package/dist/elements/segmented.js.map +1 -0
  39. package/dist/elements/slider.d.ts +15 -0
  40. package/dist/elements/slider.js +70 -0
  41. package/dist/elements/slider.js.map +1 -0
  42. package/dist/elements/sonic-element.d.ts +20 -0
  43. package/dist/elements/sonic-element.js +94 -0
  44. package/dist/elements/sonic-element.js.map +1 -0
  45. package/dist/index.d.ts +6 -0
  46. package/dist/index.js +7 -0
  47. package/dist/lib/read-px-property.js +9 -0
  48. package/dist/lib/read-px-property.js.map +1 -0
  49. package/dist/lib/render.js +19 -0
  50. package/dist/lib/render.js.map +1 -0
  51. package/dist/lib/taper.js +33 -0
  52. package/dist/lib/taper.js.map +1 -0
  53. package/dist/lib/write-attribute.js +12 -0
  54. package/dist/lib/write-attribute.js.map +1 -0
  55. package/dist/skins/ivory.css +8 -0
  56. package/dist/skins/lime.css +10 -0
  57. package/dist/skins/slate.css +10 -0
  58. package/dist/styles/controls.css +8 -0
  59. package/dist/styles/dial.css +265 -0
  60. package/dist/styles/key.css +137 -0
  61. package/dist/styles/material.css +229 -0
  62. package/dist/styles/meter.css +271 -0
  63. package/dist/styles/screen.css +47 -0
  64. package/dist/styles/segmented.css +147 -0
  65. package/dist/styles/slider.css +204 -0
  66. package/package.json +76 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Synaptic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @xsynaptic/sonic-ui
2
+
3
+ Native custom elements for audio interfaces: dials, sliders, keys, a segmented switch, a peak meter and a screen panel. Light DOM, drawn in CSS, skinned with `--sonic-*` custom properties. No framework, no runtime dependencies.
4
+
5
+ Early and unstable: any 0.x release may break the API.
6
+
7
+ ## Setup
8
+
9
+ ```sh
10
+ pnpm add @xsynaptic/sonic-ui
11
+ ```
12
+
13
+ ```js
14
+ import '@xsynaptic/sonic-ui/define';
15
+ ```
16
+
17
+ ```css
18
+ @layer base, sonic, components;
19
+
20
+ @import '@xsynaptic/sonic-ui/controls.css';
21
+ @import '@xsynaptic/sonic-ui/skins/slate.css';
22
+ ```
23
+
24
+ ```html
25
+ <sonic-dial aria-label="Cutoff" value="40"></sonic-dial>
26
+ <sonic-slider aria-label="Send" value="30"></sonic-slider>
27
+ <sonic-key aria-label="Mute" toggle>…</sonic-key>
28
+ ```
29
+
30
+ The styles sit in `@layer sonic`, so list it after your resets. A skin (`slate`, `lime` or `ivory`) applies under `.sonic-skin-<name>` on any ancestor. The package root exports the classes without registering them, for your own tag names.
31
+
32
+ ## Browsers
33
+
34
+ Current Chrome and Edge, Safari 26 and Firefox 147.
35
+
36
+ ## License
37
+
38
+ MIT
@@ -0,0 +1,2 @@
1
+ import { SonicDial } from "../elements/dial.js";
2
+ export type { SonicDial };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicDial } from "../elements/dial.js";
3
+
4
+ //#region src/define/dial.ts
5
+ defineOnce("sonic-dial", SonicDial);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=dial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dial.js","names":[],"sources":["../../src/define/dial.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicDial } from '#elements/dial.ts';\n\ndefineOnce('sonic-dial', SonicDial);\n\n// Type-only, so the tag map augmentation ships without a runtime export\n/** @public */\nexport type { SonicDial } from '#elements/dial.ts';\n"],"mappings":";;;;AAGA,WAAW,cAAc,SAAS"}
@@ -0,0 +1,2 @@
1
+ import { SonicKey } from "../elements/key.js";
2
+ export type { SonicKey };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicKey } from "../elements/key.js";
3
+
4
+ //#region src/define/key.ts
5
+ defineOnce("sonic-key", SonicKey);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key.js","names":[],"sources":["../../src/define/key.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicKey } from '#elements/key.ts';\n\ndefineOnce('sonic-key', SonicKey);\n\n// Type-only, so the tag map augmentation ships without a runtime export\n/** @public */\nexport type { SonicKey } from '#elements/key.ts';\n"],"mappings":";;;;AAGA,WAAW,aAAa,QAAQ"}
@@ -0,0 +1,2 @@
1
+ import { SonicMeter } from "../elements/meter.js";
2
+ export type { SonicMeter };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicMeter } from "../elements/meter.js";
3
+
4
+ //#region src/define/meter.ts
5
+ defineOnce("sonic-meter", SonicMeter);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.js","names":[],"sources":["../../src/define/meter.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicMeter } from '#elements/meter.ts';\n\ndefineOnce('sonic-meter', SonicMeter);\n\n// Type-only, so the tag map augmentation ships without a runtime export\n/** @public */\nexport type { SonicMeter } from '#elements/meter.ts';\n"],"mappings":";;;;AAGA,WAAW,eAAe,UAAU"}
@@ -0,0 +1,2 @@
1
+ import { SonicSegmented } from "../elements/segmented.js";
2
+ export type { SonicSegmented };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicSegmented } from "../elements/segmented.js";
3
+
4
+ //#region src/define/segmented.ts
5
+ defineOnce("sonic-segmented", SonicSegmented);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=segmented.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segmented.js","names":[],"sources":["../../src/define/segmented.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicSegmented } from '#elements/segmented.ts';\n\ndefineOnce('sonic-segmented', SonicSegmented);\n\n// Type-only, so the tag map augmentation ships without a runtime export\n/** @public */\nexport type { SonicSegmented } from '#elements/segmented.ts';\n"],"mappings":";;;;AAGA,WAAW,mBAAmB,cAAc"}
@@ -0,0 +1,2 @@
1
+ import { SonicSlider } from "../elements/slider.js";
2
+ export type { SonicSlider };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicSlider } from "../elements/slider.js";
3
+
4
+ //#region src/define/slider.ts
5
+ defineOnce("sonic-slider", SonicSlider);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=slider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../src/define/slider.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicSlider } from '#elements/slider.ts';\n\ndefineOnce('sonic-slider', SonicSlider);\n\n// Type-only, so the tag map augmentation ships without a runtime export\n/** @public */\nexport type { SonicSlider } from '#elements/slider.ts';\n"],"mappings":";;;;AAGA,WAAW,gBAAgB,WAAW"}
@@ -0,0 +1,11 @@
1
+ import { SonicDial } from "./elements/dial.js";
2
+ import "./define/dial.js";
3
+ import { SonicKey } from "./elements/key.js";
4
+ import "./define/key.js";
5
+ import { SonicMeter } from "./elements/meter.js";
6
+ import "./define/meter.js";
7
+ import { SonicSegmented } from "./elements/segmented.js";
8
+ import "./define/segmented.js";
9
+ import { SonicSlider } from "./elements/slider.js";
10
+ import "./define/slider.js";
11
+ export type { SonicDial, SonicKey, SonicMeter, SonicSegmented, SonicSlider };
package/dist/define.js ADDED
@@ -0,0 +1,5 @@
1
+ import "./define/dial.js";
2
+ import "./define/key.js";
3
+ import "./define/meter.js";
4
+ import "./define/segmented.js";
5
+ import "./define/slider.js";
@@ -0,0 +1,8 @@
1
+ //#region src/elements/define-once.ts
2
+ function defineOnce(tag, elementClass) {
3
+ if (!customElements.get(tag)) customElements.define(tag, elementClass);
4
+ }
5
+
6
+ //#endregion
7
+ export { defineOnce };
8
+ //# sourceMappingURL=define-once.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-once.js","names":[],"sources":["../../src/elements/define-once.ts"],"sourcesContent":["export function defineOnce(tag: string, elementClass: CustomElementConstructor): void {\n\tif (!customElements.get(tag)) customElements.define(tag, elementClass);\n}\n"],"mappings":";AAAA,SAAgB,WAAW,KAAa,cAA8C;CACrF,IAAI,CAAC,eAAe,IAAI,GAAG,GAAG,eAAe,OAAO,KAAK,YAAY;AACtE"}
@@ -0,0 +1,15 @@
1
+ import { SonicRangeElement } from "./range-element.js";
2
+ //#region src/elements/dial.d.ts
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'sonic-dial': SonicDial;
6
+ }
7
+ }
8
+ export declare class SonicDial extends SonicRangeElement {
9
+ #private;
10
+ static readonly observedAttributes: string[];
11
+ protected connect(signal: AbortSignal): void;
12
+ protected render(): void;
13
+ }
14
+ //#endregion
15
+ //# sourceMappingURL=dial.d.ts.map
@@ -0,0 +1,57 @@
1
+ import { 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/dial.ts
6
+ const renderDial = template(`
7
+ <div class="sonic-dial" role="slider" tabindex="0">
8
+ <div class="sonic-dial-notches"></div>
9
+ <div class="sonic-dial-ring"></div>
10
+ <div class="sonic-dial-modulation"></div>
11
+ <div class="sonic-dial-cap"></div>
12
+ <div class="sonic-dial-pointer"></div>
13
+ <div class="sonic-dial-readout" popover="manual">
14
+ <span></span>
15
+ <input
16
+ autocomplete="off"
17
+ class="sonic-dial-entry"
18
+ enterkeyhint="done"
19
+ hidden
20
+ spellcheck="false"
21
+ type="text"
22
+ />
23
+ </div>
24
+ </div>
25
+ `, HTMLDivElement);
26
+ const fallbackTravelPx = 160;
27
+ var SonicDial = class extends SonicRangeElement {
28
+ static observedAttributes = [...SonicRangeElement.observedAttributes, "modulation"];
29
+ #dial = renderDial();
30
+ connect(signal) {
31
+ const dial = this.#dial;
32
+ this.appendOnce(dial);
33
+ this.render();
34
+ this.checkStyles(dial, "dial.css");
35
+ this.bindGestures(dial, signal, () => ({
36
+ position: (event) => -event.clientY,
37
+ travelPx: readPxProperty(getComputedStyle(dial), "--_sonic-dial-travel", fallbackTravelPx)
38
+ }));
39
+ }
40
+ render() {
41
+ const dial = this.#dial;
42
+ const positions = this.positions();
43
+ const modulated = this.fraction(this.value + this.numberAttribute("modulation", 0));
44
+ const value = this.fraction(this.value);
45
+ dial.style.setProperty("--_sonic-dial-value", String(value));
46
+ dial.style.setProperty("--_sonic-dial-origin", String(this.originFraction()));
47
+ dial.style.setProperty("--_sonic-dial-modulation-from", String(Math.min(value, modulated)));
48
+ dial.style.setProperty("--_sonic-dial-modulation-to", String(Math.max(value, modulated)));
49
+ if (positions === void 0) dial.style.removeProperty("--_sonic-dial-positions");
50
+ else dial.style.setProperty("--_sonic-dial-positions", String(positions));
51
+ this.renderAria(dial);
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ export { SonicDial };
57
+ //# sourceMappingURL=dial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dial.js","names":[],"sources":["../../src/elements/dial.ts"],"sourcesContent":["import { SonicRangeElement } from '#elements/range-element.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-pointer\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst fallbackTravelPx = 160;\n\nexport class SonicDial extends SonicRangeElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRangeElement.observedAttributes,\n\t\t'modulation',\n\t];\n\n\treadonly #dial = renderDial();\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tthis.appendOnce(dial);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\t// Once per gesture; a computed style read per move forces a style recalc\n\t\t\ttravelPx: readPxProperty(getComputedStyle(dial), '--_sonic-dial-travel', fallbackTravelPx),\n\t\t}));\n\t}\n\n\tprotected render(): void {\n\t\tconst dial = this.#dial;\n\t\tconst positions = this.positions();\n\t\tconst modulated = this.fraction(this.value + this.numberAttribute('modulation', 0));\n\t\tconst value = this.fraction(this.value);\n\n\t\tdial.style.setProperty('--_sonic-dial-value', String(value));\n\t\tdial.style.setProperty('--_sonic-dial-origin', String(this.originFraction()));\n\t\tdial.style.setProperty('--_sonic-dial-modulation-from', String(Math.min(value, modulated)));\n\t\tdial.style.setProperty('--_sonic-dial-modulation-to', String(Math.max(value, modulated)));\n\t\tif (positions === undefined) dial.style.removeProperty('--_sonic-dial-positions');\n\t\telse dial.style.setProperty('--_sonic-dial-positions', String(positions));\n\t\tthis.renderAria(dial);\n\t}\n}\n"],"mappings":";;;;;AAUA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;IAoBX,cACD;AAEA,MAAM,mBAAmB;AAEzB,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB,CAC7C,GAAG,kBAAkB,oBACrB,YACD;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,KAAK,WAAW,IAAI;EACpB,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,UAAU;EACjC,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAE5B,UAAU,eAAe,iBAAiB,IAAI,GAAG,wBAAwB,gBAAgB;EAC1F,EAAE;CACH;CAEA,AAAU,SAAe;EACxB,MAAM,OAAO,KAAK;EAClB,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,YAAY,KAAK,SAAS,KAAK,QAAQ,KAAK,gBAAgB,cAAc,CAAC,CAAC;EAClF,MAAM,QAAQ,KAAK,SAAS,KAAK,KAAK;EAEtC,KAAK,MAAM,YAAY,uBAAuB,OAAO,KAAK,CAAC;EAC3D,KAAK,MAAM,YAAY,wBAAwB,OAAO,KAAK,eAAe,CAAC,CAAC;EAC5E,KAAK,MAAM,YAAY,iCAAiC,OAAO,KAAK,IAAI,OAAO,SAAS,CAAC,CAAC;EAC1F,KAAK,MAAM,YAAY,+BAA+B,OAAO,KAAK,IAAI,OAAO,SAAS,CAAC,CAAC;EACxF,IAAI,cAAc,QAAW,KAAK,MAAM,eAAe,yBAAyB;OAC3E,KAAK,MAAM,YAAY,2BAA2B,OAAO,SAAS,CAAC;EACxE,KAAK,WAAW,IAAI;CACrB;AACD"}
@@ -0,0 +1,17 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ //#region src/elements/key.d.ts
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'sonic-key': SonicKey;
6
+ }
7
+ }
8
+ export declare class SonicKey extends SonicElement {
9
+ #private;
10
+ static readonly observedAttributes: string[];
11
+ get pressed(): boolean;
12
+ set pressed(isPressed: boolean);
13
+ attributeChangedCallback(name: string): void;
14
+ protected connect(signal: AbortSignal): void;
15
+ }
16
+ //#endregion
17
+ //# sourceMappingURL=key.d.ts.map
@@ -0,0 +1,99 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ import { requireChild, template } from "../lib/render.js";
3
+
4
+ //#region src/elements/key.ts
5
+ const renderKey = template(`
6
+ <button class="sonic-key" type="button"><span class="sonic-key-cap"></span></button>
7
+ `, HTMLButtonElement);
8
+ const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
9
+ var SonicKey = class extends SonicElement {
10
+ static observedAttributes = [
11
+ ...SonicElement.observedAttributes,
12
+ "momentary",
13
+ "pressed",
14
+ "toggle"
15
+ ];
16
+ get pressed() {
17
+ return this.hasAttribute("pressed");
18
+ }
19
+ set pressed(isPressed) {
20
+ this.toggleAttribute("pressed", isPressed);
21
+ }
22
+ #holder;
23
+ #key = renderKey();
24
+ attributeChangedCallback(name) {
25
+ if (name === "disabled" && this.disabled) this.#release();
26
+ this.#render();
27
+ }
28
+ connect(signal) {
29
+ const key = this.#key;
30
+ const cap = requireChild(key, ".sonic-key-cap", HTMLSpanElement);
31
+ this.upgradeProperty("pressed");
32
+ this.adoptChildren(() => {
33
+ const arrivals = [...this.childNodes].filter((node) => node !== key);
34
+ if (key.parentNode === this) cap.append(...arrivals);
35
+ else cap.replaceChildren(...arrivals);
36
+ this.appendOnce(key);
37
+ }, signal);
38
+ this.#render();
39
+ this.checkStyles(key, "key.css");
40
+ signal.addEventListener("abort", () => {
41
+ this.#release();
42
+ }, { once: true });
43
+ key.addEventListener("click", () => {
44
+ if (!this.hasAttribute("toggle")) return;
45
+ this.pressed = !this.pressed;
46
+ this.dispatchEvent(new Event("change", { bubbles: true }));
47
+ }, { signal });
48
+ this.#bindMomentary(key, signal);
49
+ }
50
+ #bindMomentary(key, signal) {
51
+ const releasePointer = (event) => {
52
+ if (event.pointerId === this.#holder) this.#release();
53
+ };
54
+ key.addEventListener("pointerdown", (event) => {
55
+ if (event.button !== 0 || !this.#isMomentary()) return;
56
+ key.setPointerCapture(event.pointerId);
57
+ this.#hold(event.pointerId);
58
+ }, { signal });
59
+ key.addEventListener("lostpointercapture", releasePointer, { signal });
60
+ key.addEventListener("pointercancel", releasePointer, { signal });
61
+ key.addEventListener("pointerup", releasePointer, { signal });
62
+ key.addEventListener("keydown", (event) => {
63
+ if (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;
64
+ this.#hold(event.key);
65
+ }, { signal });
66
+ key.addEventListener("keyup", (event) => {
67
+ if (event.key === this.#holder) this.#release();
68
+ }, { signal });
69
+ key.addEventListener("blur", () => {
70
+ if (typeof this.#holder === "string") this.#release();
71
+ }, { signal });
72
+ }
73
+ #hold(holder) {
74
+ if (this.disabled || this.#holder !== void 0) return;
75
+ this.#holder = holder;
76
+ this.pressed = true;
77
+ this.dispatchEvent(new Event("change", { bubbles: true }));
78
+ }
79
+ #isMomentary() {
80
+ return this.hasAttribute("momentary") && !this.hasAttribute("toggle");
81
+ }
82
+ #release() {
83
+ if (this.#holder === void 0) return;
84
+ this.#holder = void 0;
85
+ this.pressed = false;
86
+ this.dispatchEvent(new Event("change", { bubbles: true }));
87
+ }
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
+ };
96
+
97
+ //#endregion
98
+ export { SonicKey };
99
+ //# sourceMappingURL=key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key.js","names":[],"sources":["../../src/elements/key.ts"],"sourcesContent":["import { SonicElement } from '#elements/sonic-element.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 SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'momentary',\n\t\t'pressed',\n\t\t'toggle',\n\t];\n\n\tget pressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset pressed(isPressed: boolean) {\n\t\tthis.toggleAttribute('pressed', isPressed);\n\t}\n\n\t// A pointer id or a key name\n\t#holder: number | string | undefined;\n\n\treadonly #key = renderKey();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'disabled' && this.disabled) this.#release();\n\t\tthis.#render();\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.upgradeProperty('pressed');\n\t\tthis.adoptChildren(() => {\n\t\t\tconst arrivals = [...this.childNodes].filter((node) => node !== key);\n\n\t\t\t// Arrivals after a replacement take the icon's place\n\t\t\tif (key.parentNode === this) cap.append(...arrivals);\n\t\t\telse cap.replaceChildren(...arrivals);\n\t\t\tthis.appendOnce(key);\n\t\t}, signal);\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.hasAttribute('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\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.disabled || 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.hasAttribute('momentary') && !this.hasAttribute('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\t#render(): void {\n\t\tconst key = this.#key;\n\n\t\tkey.disabled = this.disabled;\n\t\tkey.removeAttribute('aria-pressed');\n\t\tif (this.hasAttribute('toggle')) key.setAttribute('aria-pressed', String(this.pressed));\n\t\tthis.forwardNaming(key, true);\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ //#region src/elements/meter.d.ts
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'sonic-meter': SonicMeter;
6
+ }
7
+ }
8
+ export declare class SonicMeter extends SonicElement {
9
+ #private;
10
+ static readonly observedAttributes: string[];
11
+ get level(): number;
12
+ set level(amplitude: number);
13
+ attributeChangedCallback(): void;
14
+ protected connect(signal: AbortSignal): void;
15
+ }
16
+ //#endregion
17
+ //# sourceMappingURL=meter.d.ts.map
@@ -0,0 +1,119 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ import { template } from "../lib/render.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
+ #bar = -Infinity;
42
+ #clipAt = -Infinity;
43
+ #frame;
44
+ #lastFrame;
45
+ #level = 0;
46
+ #meter = renderMeter();
47
+ #peak = -Infinity;
48
+ #peakAt = -Infinity;
49
+ #target = -Infinity;
50
+ attributeChangedCallback() {
51
+ const now = performance.now();
52
+ this.#render(now);
53
+ this.#schedule(now);
54
+ }
55
+ connect(signal) {
56
+ const now = performance.now();
57
+ this.upgradeProperty("level");
58
+ this.appendOnce(this.#meter);
59
+ this.#render(now);
60
+ this.#schedule(now);
61
+ this.checkStyles(this.#meter, "meter.css");
62
+ signal.addEventListener("abort", () => {
63
+ if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
64
+ this.#frame = void 0;
65
+ this.#lastFrame = void 0;
66
+ }, { once: true });
67
+ }
68
+ #fraction(decibels) {
69
+ const min = this.#min();
70
+ const range = this.#max() - min;
71
+ return range > 0 ? Math.min(1, Math.max(0, (decibels - min) / range)) : 0;
72
+ }
73
+ #isSettled(now) {
74
+ const floor = this.#min();
75
+ return this.#bar <= Math.max(this.#target, floor) && this.#peak <= Math.max(this.#bar, floor) && now - this.#clipAt >= holdMs;
76
+ }
77
+ #max() {
78
+ return this.numberAttribute("max", 0);
79
+ }
80
+ #min() {
81
+ return this.numberAttribute("min", -60);
82
+ }
83
+ #render(now) {
84
+ const meter = this.#meter;
85
+ meter.style.setProperty("--_sonic-meter-min", String(this.#min()));
86
+ meter.style.setProperty("--_sonic-meter-max", String(this.#max()));
87
+ if (this.disabled) {
88
+ meter.style.setProperty("--_sonic-meter-level", "0");
89
+ meter.style.setProperty("--_sonic-meter-peak", "0");
90
+ meter.style.setProperty("--_sonic-meter-clipped", "0");
91
+ return;
92
+ }
93
+ meter.style.setProperty("--_sonic-meter-level", String(this.#fraction(this.#bar)));
94
+ meter.style.setProperty("--_sonic-meter-peak", String(this.#fraction(this.#peak)));
95
+ meter.style.setProperty("--_sonic-meter-clipped", now - this.#clipAt < holdMs ? "1" : "0");
96
+ }
97
+ #schedule(now) {
98
+ if (this.#frame !== void 0 || !this.isConnected || this.#isSettled(now)) return;
99
+ if (this.#lastFrame === void 0) this.#lastFrame = now;
100
+ this.#frame = requestAnimationFrame(this.#tick);
101
+ }
102
+ #tick = (time) => {
103
+ const fall = releaseDecibelsPerSecond * Math.max(0, time - (this.#lastFrame ?? time)) / 1e3;
104
+ this.#frame = void 0;
105
+ this.#lastFrame = time;
106
+ this.#bar = Math.max(this.#target, this.#min(), this.#bar - fall);
107
+ if (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);
108
+ this.#render(time);
109
+ if (this.#isSettled(time)) {
110
+ this.#lastFrame = void 0;
111
+ return;
112
+ }
113
+ this.#frame = requestAnimationFrame(this.#tick);
114
+ };
115
+ };
116
+
117
+ //#endregion
118
+ export { SonicMeter };
119
+ //# 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\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.upgradeProperty('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#max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\t#min(): number {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\t#render(now: number): void {\n\t\tconst meter = this.#meter;\n\n\t\t// The sheet places its colour zones on the scale\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,32 @@
1
+ import { SonicElement } from "./sonic-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 SonicElement {
8
+ #private;
9
+ static readonly observedAttributes: string[];
10
+ get formatValue(): ((value: number) => string) | undefined;
11
+ set formatValue(format: ((value: number) => string) | undefined);
12
+ get parseValue(): ((text: string) => number) | undefined;
13
+ set parseValue(parse: ((text: string) => number) | undefined);
14
+ get value(): number;
15
+ set value(next: number);
16
+ attributeChangedCallback(name: string): void;
17
+ connectedCallback(): void;
18
+ protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => RangeAxis | undefined): void;
19
+ protected clampRange(next: number): number;
20
+ protected fraction(value: number): number;
21
+ protected input(next: number): boolean;
22
+ protected max(): number;
23
+ protected min(): number;
24
+ protected originFraction(): number;
25
+ protected positions(): number | undefined;
26
+ protected range(): number;
27
+ protected abstract render(): void;
28
+ protected renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void;
29
+ protected valueAt(fraction: number): number;
30
+ }
31
+ //#endregion
32
+ //# sourceMappingURL=range-element.d.ts.map