@xsynaptic/sonic-ui 0.1.0 → 0.2.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 (117) hide show
  1. package/README.md +8 -1
  2. package/dist/default/define/dial.js +7 -0
  3. package/dist/default/define/key.js +7 -0
  4. package/dist/default/define/meter.js +7 -0
  5. package/dist/default/define/segmented.js +7 -0
  6. package/dist/default/define/slider.js +7 -0
  7. package/dist/default/elements/define-once.js +7 -0
  8. package/dist/{elements → default/elements}/dial.js +15 -3
  9. package/dist/default/elements/form-element.js +56 -0
  10. package/dist/default/elements/key.js +144 -0
  11. package/dist/{elements → default/elements}/meter.js +27 -16
  12. package/dist/default/elements/range-element.js +410 -0
  13. package/dist/{elements → default/elements}/range-entry.js +5 -4
  14. package/dist/default/elements/segmented.js +173 -0
  15. package/dist/{elements → default/elements}/slider.js +23 -8
  16. package/dist/default/elements/sonic-element.js +153 -0
  17. package/dist/{index.js → default/index.js} +2 -1
  18. package/dist/default/lib/copy-node.js +30 -0
  19. package/dist/default/lib/percent.js +12 -0
  20. package/dist/default/lib/read-px-property.js +8 -0
  21. package/dist/{lib → default/lib}/render.js +9 -2
  22. package/dist/default/lib/taper.js +32 -0
  23. package/dist/default/lib/write-attribute.js +11 -0
  24. package/dist/dev/define/dial.js.map +1 -0
  25. package/dist/dev/define/key.js.map +1 -0
  26. package/dist/dev/define/meter.js.map +1 -0
  27. package/dist/dev/define/segmented.js.map +1 -0
  28. package/dist/dev/define/slider.js.map +1 -0
  29. package/dist/dev/define.js +5 -0
  30. package/dist/dev/elements/define-once.js.map +1 -0
  31. package/dist/{elements → dev/elements}/dial.d.ts +4 -0
  32. package/dist/dev/elements/dial.js +70 -0
  33. package/dist/dev/elements/dial.js.map +1 -0
  34. package/dist/dev/elements/form-element.d.ts +22 -0
  35. package/dist/dev/elements/form-element.js +57 -0
  36. package/dist/dev/elements/form-element.js.map +1 -0
  37. package/dist/dev/elements/key.d.ts +30 -0
  38. package/dist/{elements → dev/elements}/key.js +71 -25
  39. package/dist/dev/elements/key.js.map +1 -0
  40. package/dist/{elements → dev/elements}/meter.d.ts +6 -0
  41. package/dist/dev/elements/meter.js +131 -0
  42. package/dist/dev/elements/meter.js.map +1 -0
  43. package/dist/{elements → dev/elements}/range-element.d.ts +25 -5
  44. package/dist/{elements → dev/elements}/range-element.js +116 -53
  45. package/dist/dev/elements/range-element.js.map +1 -0
  46. package/dist/dev/elements/range-entry.js +75 -0
  47. package/dist/dev/elements/range-entry.js.map +1 -0
  48. package/dist/{elements → dev/elements}/segmented.d.ts +5 -2
  49. package/dist/dev/elements/segmented.js +174 -0
  50. package/dist/dev/elements/segmented.js.map +1 -0
  51. package/dist/{elements → dev/elements}/slider.d.ts +6 -0
  52. package/dist/dev/elements/slider.js +86 -0
  53. package/dist/dev/elements/slider.js.map +1 -0
  54. package/dist/dev/elements/sonic-element.d.ts +33 -0
  55. package/dist/dev/elements/sonic-element.js +169 -0
  56. package/dist/dev/elements/sonic-element.js.map +1 -0
  57. package/dist/{index.d.ts → dev/index.d.ts} +2 -1
  58. package/dist/dev/index.js +8 -0
  59. package/dist/dev/lib/copy-node.js +31 -0
  60. package/dist/dev/lib/copy-node.js.map +1 -0
  61. package/dist/dev/lib/percent.d.ts +5 -0
  62. package/dist/dev/lib/percent.js +13 -0
  63. package/dist/dev/lib/percent.js.map +1 -0
  64. package/dist/dev/lib/read-px-property.js.map +1 -0
  65. package/dist/dev/lib/render.js +27 -0
  66. package/dist/dev/lib/render.js.map +1 -0
  67. package/dist/dev/lib/taper.js.map +1 -0
  68. package/dist/dev/lib/write-attribute.js.map +1 -0
  69. package/dist/skins/ivory.css +3 -3
  70. package/dist/skins/slate.css +4 -4
  71. package/dist/styles/controls.css +0 -1
  72. package/dist/styles/dial.css +33 -13
  73. package/dist/styles/key.css +30 -10
  74. package/dist/styles/material.css +58 -10
  75. package/dist/styles/meter.css +25 -5
  76. package/dist/styles/screen.css +7 -1
  77. package/dist/styles/segmented.css +24 -7
  78. package/dist/styles/slider.css +17 -4
  79. package/package.json +13 -12
  80. package/dist/define/dial.js.map +0 -1
  81. package/dist/define/key.js.map +0 -1
  82. package/dist/define/meter.js.map +0 -1
  83. package/dist/define/segmented.js.map +0 -1
  84. package/dist/define/slider.js.map +0 -1
  85. package/dist/elements/define-once.js.map +0 -1
  86. package/dist/elements/dial.js.map +0 -1
  87. package/dist/elements/key.d.ts +0 -17
  88. package/dist/elements/key.js.map +0 -1
  89. package/dist/elements/meter.js.map +0 -1
  90. package/dist/elements/range-element.js.map +0 -1
  91. package/dist/elements/range-entry.js.map +0 -1
  92. package/dist/elements/segmented.js +0 -104
  93. package/dist/elements/segmented.js.map +0 -1
  94. package/dist/elements/slider.js.map +0 -1
  95. package/dist/elements/sonic-element.d.ts +0 -20
  96. package/dist/elements/sonic-element.js +0 -94
  97. package/dist/elements/sonic-element.js.map +0 -1
  98. package/dist/lib/read-px-property.js.map +0 -1
  99. package/dist/lib/render.js.map +0 -1
  100. package/dist/lib/taper.js.map +0 -1
  101. package/dist/lib/write-attribute.js.map +0 -1
  102. /package/dist/{define.js → default/define.js} +0 -0
  103. /package/dist/{define → dev/define}/dial.d.ts +0 -0
  104. /package/dist/{define → dev/define}/dial.js +0 -0
  105. /package/dist/{define → dev/define}/key.d.ts +0 -0
  106. /package/dist/{define → dev/define}/key.js +0 -0
  107. /package/dist/{define → dev/define}/meter.d.ts +0 -0
  108. /package/dist/{define → dev/define}/meter.js +0 -0
  109. /package/dist/{define → dev/define}/segmented.d.ts +0 -0
  110. /package/dist/{define → dev/define}/segmented.js +0 -0
  111. /package/dist/{define → dev/define}/slider.d.ts +0 -0
  112. /package/dist/{define → dev/define}/slider.js +0 -0
  113. /package/dist/{define.d.ts → dev/define.d.ts} +0 -0
  114. /package/dist/{elements → dev/elements}/define-once.js +0 -0
  115. /package/dist/{lib → dev/lib}/read-px-property.js +0 -0
  116. /package/dist/{lib → dev/lib}/taper.js +0 -0
  117. /package/dist/{lib → dev/lib}/write-attribute.js +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @xsynaptic/sonic-ui
2
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.
3
+ Native custom elements for audio interfaces: dials, sliders, keys, a segmented switch, a peak meter and a screen panel. Each control renders in the light DOM, drawn in CSS, skinned with `--sonic-*` custom properties. No framework, no runtime dependencies.
4
4
 
5
5
  Early and unstable: any 0.x release may break the API.
6
6
 
@@ -29,6 +29,13 @@ import '@xsynaptic/sonic-ui/define';
29
29
 
30
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
31
 
32
+ ## Usage
33
+
34
+ - **Attributes and properties** reflect each other: `dial.max = 200` writes `max="200"`. `value` and the key's `pressed` are the live state and never write their attribute, as on native inputs.
35
+ - **Events:** `change` when a control settles, and `input` while a dial or slider moves; both bubble.
36
+ - **Formatting:** `formatValue` sets the text the readout and assistive technology read, and `parseValue` reads typed entry back. `formatPercent` and `parsePercent` from the package root show 0 to 1 as a percentage.
37
+ - **Forms:** the dial, slider, key and switch submit under their `name`, reset, take a `<label>`, and follow a disabled `<fieldset>`, as native inputs do.
38
+
32
39
  ## Browsers
33
40
 
34
41
  Current Chrome and Edge, Safari 26 and Firefox 147.
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,7 @@
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 };
@@ -26,7 +26,17 @@ const renderDial = template(`
26
26
  const fallbackTravelPx = 160;
27
27
  var SonicDial = class extends SonicRangeElement {
28
28
  static observedAttributes = [...SonicRangeElement.observedAttributes, "modulation"];
29
+ get modulation() {
30
+ return this.numberAttribute("modulation", 0);
31
+ }
32
+ set modulation(amount) {
33
+ this.reflect("modulation", amount);
34
+ }
29
35
  #dial = renderDial();
36
+ connectedCallback() {
37
+ this.upgradeProperties("modulation");
38
+ super.connectedCallback();
39
+ }
30
40
  connect(signal) {
31
41
  const dial = this.#dial;
32
42
  this.appendOnce(dial);
@@ -37,10 +47,13 @@ var SonicDial = class extends SonicRangeElement {
37
47
  travelPx: readPxProperty(getComputedStyle(dial), "--_sonic-dial-travel", fallbackTravelPx)
38
48
  }));
39
49
  }
50
+ focusTarget() {
51
+ return this.#dial;
52
+ }
40
53
  render() {
41
54
  const dial = this.#dial;
42
55
  const positions = this.positions();
43
- const modulated = this.fraction(this.value + this.numberAttribute("modulation", 0));
56
+ const modulated = this.fraction(this.value + this.modulation);
44
57
  const value = this.fraction(this.value);
45
58
  dial.style.setProperty("--_sonic-dial-value", String(value));
46
59
  dial.style.setProperty("--_sonic-dial-origin", String(this.originFraction()));
@@ -53,5 +66,4 @@ var SonicDial = class extends SonicRangeElement {
53
66
  };
54
67
 
55
68
  //#endregion
56
- export { SonicDial };
57
- //# sourceMappingURL=dial.js.map
69
+ export { SonicDial };
@@ -0,0 +1,56 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+
3
+ //#region src/elements/form-element.ts
4
+ var SonicFormElement = class extends SonicElement {
5
+ static formAssociated = true;
6
+ get form() {
7
+ return this.internals?.form ?? null;
8
+ }
9
+ get labels() {
10
+ return this.internals?.labels ?? null;
11
+ }
12
+ get name() {
13
+ return this.getAttribute("name") ?? "";
14
+ }
15
+ set name(value) {
16
+ this.reflect("name", value);
17
+ }
18
+ constructor() {
19
+ super();
20
+ if (this.internals) this.internals.role = "none";
21
+ this.addEventListener("click", (event) => {
22
+ if (event.target === this) this.activate();
23
+ });
24
+ this.addEventListener("focusin", () => {
25
+ this.render();
26
+ });
27
+ }
28
+ connectedCallback() {
29
+ this.upgradeProperties("name");
30
+ super.connectedCallback();
31
+ }
32
+ formDisabledCallback() {
33
+ this.attributeChangedCallback("disabled");
34
+ }
35
+ formResetCallback() {
36
+ this.attributeChangedCallback("value");
37
+ }
38
+ formStateRestoreCallback(state) {
39
+ const prefix = `${this.localName}:`;
40
+ if (typeof state === "string" && state.startsWith(prefix)) this.restoreState(state.slice(prefix.length));
41
+ }
42
+ activate() {
43
+ this.focus();
44
+ }
45
+ namingLabels() {
46
+ const internals = this.internals;
47
+ if (!internals || this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby")) return [];
48
+ return [...internals.labels].filter((label) => label instanceof Element);
49
+ }
50
+ writeFormValue(value, state) {
51
+ this.internals?.setFormValue(value, `${this.localName}:${state}`);
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ export { SonicFormElement };
@@ -0,0 +1,144 @@
1
+ import { requireChild, template } from "../lib/render.js";
2
+ import { SonicFormElement } from "./form-element.js";
3
+ import { copyNode } from "../lib/copy-node.js";
4
+
5
+ //#region src/elements/key.ts
6
+ const renderKey = template(`
7
+ <button class="sonic-key" type="button"><span class="sonic-key-cap"></span></button>
8
+ `, HTMLButtonElement);
9
+ const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
10
+ var SonicKey = class extends SonicFormElement {
11
+ static observedAttributes = [
12
+ ...SonicFormElement.observedAttributes,
13
+ "momentary",
14
+ "pressed",
15
+ "toggle",
16
+ "value"
17
+ ];
18
+ get defaultPressed() {
19
+ return this.hasAttribute("pressed");
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
+ }
33
+ set pressed(isPressed) {
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);
49
+ }
50
+ #holder;
51
+ #isDirty = false;
52
+ #key = renderKey();
53
+ #pressed = false;
54
+ attributeChangedCallback(name) {
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();
64
+ }
65
+ connect(signal) {
66
+ const key = this.#key;
67
+ const cap = requireChild(key, ".sonic-key-cap", HTMLSpanElement);
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
+ }
74
+ }, signal);
75
+ this.render();
76
+ this.checkStyles(key, "key.css");
77
+ signal.addEventListener("abort", () => {
78
+ this.#release();
79
+ }, { once: true });
80
+ key.addEventListener("click", () => {
81
+ if (!this.toggle) return;
82
+ this.pressed = !this.pressed;
83
+ this.dispatchEvent(new Event("change", { bubbles: true }));
84
+ }, { signal });
85
+ this.#bindMomentary(key, signal);
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
+ }
103
+ #bindMomentary(key, signal) {
104
+ const releasePointer = (event) => {
105
+ if (event.pointerId === this.#holder) this.#release();
106
+ };
107
+ key.addEventListener("pointerdown", (event) => {
108
+ if (event.button !== 0 || !this.#isMomentary()) return;
109
+ key.setPointerCapture(event.pointerId);
110
+ this.#hold(event.pointerId);
111
+ }, { signal });
112
+ key.addEventListener("lostpointercapture", releasePointer, { signal });
113
+ key.addEventListener("pointercancel", releasePointer, { signal });
114
+ key.addEventListener("pointerup", releasePointer, { signal });
115
+ key.addEventListener("keydown", (event) => {
116
+ if (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;
117
+ this.#hold(event.key);
118
+ }, { signal });
119
+ key.addEventListener("keyup", (event) => {
120
+ if (event.key === this.#holder) this.#release();
121
+ }, { signal });
122
+ key.addEventListener("blur", () => {
123
+ if (typeof this.#holder === "string") this.#release();
124
+ }, { signal });
125
+ }
126
+ #hold(holder) {
127
+ if (this.isDisabled() || this.#holder !== void 0) return;
128
+ this.#holder = holder;
129
+ this.pressed = true;
130
+ this.dispatchEvent(new Event("change", { bubbles: true }));
131
+ }
132
+ #isMomentary() {
133
+ return this.momentary && !this.toggle;
134
+ }
135
+ #release() {
136
+ if (this.#holder === void 0) return;
137
+ this.#holder = void 0;
138
+ this.pressed = false;
139
+ this.dispatchEvent(new Event("change", { bubbles: true }));
140
+ }
141
+ };
142
+
143
+ //#endregion
144
+ export { SonicKey };
@@ -1,5 +1,5 @@
1
- import { SonicElement } from "./sonic-element.js";
2
1
  import { template } from "../lib/render.js";
2
+ import { SonicElement } from "./sonic-element.js";
3
3
 
4
4
  //#region src/elements/meter.ts
5
5
  const renderMeter = template(`
@@ -38,6 +38,24 @@ var SonicMeter = class extends SonicElement {
38
38
  this.#render(now);
39
39
  this.#schedule(now);
40
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
+ }
41
59
  #bar = -Infinity;
42
60
  #clipAt = -Infinity;
43
61
  #frame;
@@ -54,7 +72,7 @@ var SonicMeter = class extends SonicElement {
54
72
  }
55
73
  connect(signal) {
56
74
  const now = performance.now();
57
- this.upgradeProperty("level");
75
+ this.upgradeProperties("max", "min", "orientation", "level");
58
76
  this.appendOnce(this.#meter);
59
77
  this.#render(now);
60
78
  this.#schedule(now);
@@ -66,24 +84,18 @@ var SonicMeter = class extends SonicElement {
66
84
  }, { once: true });
67
85
  }
68
86
  #fraction(decibels) {
69
- const min = this.#min();
70
- const range = this.#max() - min;
87
+ const min = this.min;
88
+ const range = this.max - min;
71
89
  return range > 0 ? Math.min(1, Math.max(0, (decibels - min) / range)) : 0;
72
90
  }
73
91
  #isSettled(now) {
74
- const floor = this.#min();
92
+ const floor = this.min;
75
93
  return this.#bar <= Math.max(this.#target, floor) && this.#peak <= Math.max(this.#bar, floor) && now - this.#clipAt >= holdMs;
76
94
  }
77
- #max() {
78
- return this.numberAttribute("max", 0);
79
- }
80
- #min() {
81
- return this.numberAttribute("min", -60);
82
- }
83
95
  #render(now) {
84
96
  const meter = this.#meter;
85
- meter.style.setProperty("--_sonic-meter-min", String(this.#min()));
86
- meter.style.setProperty("--_sonic-meter-max", String(this.#max()));
97
+ meter.style.setProperty("--_sonic-meter-min", String(this.min));
98
+ meter.style.setProperty("--_sonic-meter-max", String(this.max));
87
99
  if (this.disabled) {
88
100
  meter.style.setProperty("--_sonic-meter-level", "0");
89
101
  meter.style.setProperty("--_sonic-meter-peak", "0");
@@ -103,7 +115,7 @@ var SonicMeter = class extends SonicElement {
103
115
  const fall = releaseDecibelsPerSecond * Math.max(0, time - (this.#lastFrame ?? time)) / 1e3;
104
116
  this.#frame = void 0;
105
117
  this.#lastFrame = time;
106
- this.#bar = Math.max(this.#target, this.#min(), this.#bar - fall);
118
+ this.#bar = Math.max(this.#target, this.min, this.#bar - fall);
107
119
  if (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);
108
120
  this.#render(time);
109
121
  if (this.#isSettled(time)) {
@@ -115,5 +127,4 @@ var SonicMeter = class extends SonicElement {
115
127
  };
116
128
 
117
129
  //#endregion
118
- export { SonicMeter };
119
- //# sourceMappingURL=meter.js.map
130
+ export { SonicMeter };