@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
@@ -0,0 +1,153 @@
1
+ import { attachSlot } from "../lib/render.js";
2
+ import { writeAttribute } from "../lib/write-attribute.js";
3
+
4
+ //#region src/elements/sonic-element.ts
5
+ const namingAttributes = [
6
+ "aria-describedby",
7
+ "aria-label",
8
+ "aria-labelledby"
9
+ ];
10
+ function writeLabelledBy(target, labels) {
11
+ const current = target.ariaLabelledByElements;
12
+ if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
13
+ target.ariaLabelledByElements = labels;
14
+ }
15
+ var SonicElement = class extends HTMLElement {
16
+ static observedAttributes = [...namingAttributes, "disabled"];
17
+ get disabled() {
18
+ return this.hasAttribute("disabled");
19
+ }
20
+ set disabled(isDisabled) {
21
+ this.reflect("disabled", isDisabled);
22
+ }
23
+ internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
24
+ #connection;
25
+ #slot;
26
+ blur() {
27
+ this.#focused()?.blur();
28
+ }
29
+ connectedCallback() {
30
+ this.upgradeProperties("disabled");
31
+ if (this.#connection) return;
32
+ const connection = new AbortController();
33
+ try {
34
+ this.connect(connection.signal);
35
+ } catch (error) {
36
+ connection.abort();
37
+ throw error;
38
+ }
39
+ this.#connection = connection;
40
+ }
41
+ connectedMoveCallback() {}
42
+ disconnectedCallback() {
43
+ const connection = this.#connection;
44
+ if (!connection) return;
45
+ queueMicrotask(() => {
46
+ if (this.isConnected || this.#connection !== connection) return;
47
+ connection.abort();
48
+ this.#connection = void 0;
49
+ });
50
+ }
51
+ focus(options) {
52
+ const target = this.focusTarget();
53
+ if (!target) {
54
+ super.focus(options);
55
+ return;
56
+ }
57
+ if (!this.isDisabled() && !this.#focused()) target.focus(options);
58
+ }
59
+ appendOnce(child) {
60
+ if (child.parentNode !== this) this.append(child);
61
+ }
62
+ checkStyles(control, sheet) {}
63
+ focusTarget() {}
64
+ forwardNaming(target, isNamed) {
65
+ const labels = isNamed ? this.namingLabels() : [];
66
+ for (const name of namingAttributes) {
67
+ if (name === "aria-labelledby" && labels.length > 0) continue;
68
+ const value = this.getAttribute(name);
69
+ writeAttribute(target, name, isNamed && value !== null ? value : void 0);
70
+ }
71
+ if (labels.length > 0) writeLabelledBy(target, labels);
72
+ }
73
+ isDisabled() {
74
+ return this.disabled || this.matches(":disabled");
75
+ }
76
+ mirrorChildren({ control, copy, isCopied = () => true }, signal) {
77
+ const slot = this.#slot ?? attachSlot(this);
78
+ this.#slot = slot;
79
+ const mirror = () => {
80
+ const children = [...this.childNodes].filter((child) => child !== control);
81
+ copy(children.filter((child) => isCopied(child)));
82
+ this.appendOnce(control);
83
+ slot.assign(...children.filter((child) => (child instanceof Element || child instanceof Text) && !isCopied(child)), control);
84
+ };
85
+ const observer = new MutationObserver((records) => {
86
+ if (records.some((record) => this.#isCopiedChange(record, control, isCopied))) mirror();
87
+ });
88
+ mirror();
89
+ observer.observe(this, {
90
+ attributes: true,
91
+ characterData: true,
92
+ childList: true,
93
+ subtree: true
94
+ });
95
+ signal.addEventListener("abort", () => {
96
+ observer.disconnect();
97
+ }, { once: true });
98
+ }
99
+ namingLabels() {
100
+ return [];
101
+ }
102
+ numberAttribute(name, fallback) {
103
+ const attribute = this.getAttribute(name);
104
+ if (attribute === null) return fallback;
105
+ const parsed = Number(attribute);
106
+ return Number.isFinite(parsed) ? parsed : fallback;
107
+ }
108
+ optionalNumberAttribute(name) {
109
+ const value = this.numberAttribute(name, NaN);
110
+ return Number.isNaN(value) ? void 0 : value;
111
+ }
112
+ reflect(name, value) {
113
+ if (value === true) {
114
+ this.setAttribute(name, "");
115
+ return;
116
+ }
117
+ writeAttribute(this, name, value === false || value === null ? void 0 : value?.toString());
118
+ }
119
+ toggleState(state, isOn) {
120
+ if (isOn) this.internals?.states.add(state);
121
+ else this.internals?.states.delete(state);
122
+ }
123
+ upgradeProperties(...names) {
124
+ for (const name of names) {
125
+ if (!Object.hasOwn(this, name)) continue;
126
+ const value = this[name];
127
+ Reflect.deleteProperty(this, name);
128
+ this[name] = value;
129
+ }
130
+ }
131
+ #childHolding(node) {
132
+ let current = node;
133
+ while (current && current.parentNode !== this) current = current.parentNode;
134
+ return current ?? void 0;
135
+ }
136
+ #focused() {
137
+ const root = this.getRootNode();
138
+ const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
139
+ return active instanceof HTMLElement && this.contains(active) ? active : void 0;
140
+ }
141
+ #isCopiedChange(record, control, isCopied) {
142
+ if (record.target === this) {
143
+ if (record.type === "attributes") return false;
144
+ return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
145
+ }
146
+ const child = this.#childHolding(record.target);
147
+ if (child === void 0 || child === control) return false;
148
+ return isCopied(child) || record.type === "attributes" && record.target === child;
149
+ }
150
+ };
151
+
152
+ //#endregion
153
+ export { SonicElement };
@@ -3,5 +3,6 @@ import { SonicKey } from "./elements/key.js";
3
3
  import { SonicMeter } from "./elements/meter.js";
4
4
  import { SonicSegmented } from "./elements/segmented.js";
5
5
  import { SonicSlider } from "./elements/slider.js";
6
+ import { formatPercent, parsePercent } from "./lib/percent.js";
6
7
 
7
- export { SonicDial, SonicKey, SonicMeter, SonicSegmented, SonicSlider };
8
+ export { SonicDial, SonicKey, SonicMeter, SonicSegmented, SonicSlider, formatPercent, parsePercent };
@@ -0,0 +1,30 @@
1
+ //#region src/lib/copy-node.ts
2
+ const copyPrefix = "sonic-copy-";
3
+ const idListAttributes = /* @__PURE__ */ new Set(["aria-describedby", "aria-labelledby"]);
4
+ const hrefAttributes = /* @__PURE__ */ new Set(["href", "xlink:href"]);
5
+ const urlReference = /url\(\s*(['"]?)#([^'")\s]+)\1\s*\)/g;
6
+ function rewriteReferences(name, value, ids) {
7
+ if (hrefAttributes.has(name)) return value.startsWith("#") && ids.has(value.slice(1)) ? `#${copyPrefix}${value.slice(1)}` : value;
8
+ if (idListAttributes.has(name)) return value.split(/\s+/).map((id) => ids.has(id) ? copyPrefix + id : id).join(" ");
9
+ return value.replaceAll(urlReference, (reference, quote, id) => ids.has(id) ? `url(${quote}#${copyPrefix}${id}${quote})` : reference);
10
+ }
11
+ function prefixIds(element, ids) {
12
+ if (element.id !== "") element.id = copyPrefix + element.id;
13
+ for (const attribute of element.attributes) {
14
+ if (attribute.name === "id") continue;
15
+ const value = rewriteReferences(attribute.name, attribute.value, ids);
16
+ if (value !== attribute.value) attribute.value = value;
17
+ }
18
+ }
19
+ function copyNode(node) {
20
+ const copy = node.cloneNode(true);
21
+ if (!(copy instanceof Element)) return copy;
22
+ const elements = [copy, ...copy.querySelectorAll("*")];
23
+ const ids = new Set(elements.map((element) => element.id).filter((id) => id !== ""));
24
+ if (ids.size === 0) return copy;
25
+ for (const element of elements) prefixIds(element, ids);
26
+ return copy;
27
+ }
28
+
29
+ //#endregion
30
+ export { copyNode };
@@ -0,0 +1,12 @@
1
+ //#region src/lib/percent.ts
2
+ function formatPercent(value) {
3
+ return `${String(Math.round(value * 100))}%`;
4
+ }
5
+ function parsePercent(text) {
6
+ const digits = text.trim().replace(/\s*%$/, "");
7
+ if (digits === "") return NaN;
8
+ return Number(digits) / 100;
9
+ }
10
+
11
+ //#endregion
12
+ export { formatPercent, parsePercent };
@@ -0,0 +1,8 @@
1
+ //#region src/lib/read-px-property.ts
2
+ function readPxProperty(styles, property, fallback) {
3
+ const parsed = Number.parseFloat(styles.getPropertyValue(property));
4
+ return Number.isFinite(parsed) ? parsed : fallback;
5
+ }
6
+
7
+ //#endregion
8
+ export { readPxProperty };
@@ -13,7 +13,14 @@ function template(html, root) {
13
13
  return clone;
14
14
  };
15
15
  }
16
+ function attachSlot(host) {
17
+ const slot = document.createElement("slot");
18
+ host.attachShadow({
19
+ mode: "open",
20
+ slotAssignment: "manual"
21
+ }).append(slot);
22
+ return slot;
23
+ }
16
24
 
17
25
  //#endregion
18
- export { requireChild, template };
19
- //# sourceMappingURL=render.js.map
26
+ export { attachSlot, requireChild, template };
@@ -0,0 +1,32 @@
1
+ //#region src/lib/taper.ts
2
+ function clampUnit(fraction) {
3
+ return Math.min(1, Math.max(0, fraction));
4
+ }
5
+ function linearTaper(min, max) {
6
+ const range = max - min;
7
+ return {
8
+ position: (value) => range > 0 ? clampUnit((value - min) / range) : 0,
9
+ value: (position) => min + clampUnit(position) * range
10
+ };
11
+ }
12
+ function logTaper(min, max) {
13
+ if (min <= 0 || max <= min) return void 0;
14
+ const span = Math.log(max / min);
15
+ return {
16
+ position: (value) => clampUnit(Math.log(Math.max(min, value) / min) / span),
17
+ value: (position) => min * Math.exp(clampUnit(position) * span)
18
+ };
19
+ }
20
+ function skewTaper(min, max, midpoint) {
21
+ const range = max - min;
22
+ const proportion = (midpoint - min) / range;
23
+ if (proportion === .5 || !(proportion > 0 && proportion < 1)) return void 0;
24
+ const skew = Math.log(.5) / Math.log(proportion);
25
+ return {
26
+ position: (value) => clampUnit((value - min) / range) ** skew,
27
+ value: (position) => min + range * clampUnit(position) ** (1 / skew)
28
+ };
29
+ }
30
+
31
+ //#endregion
32
+ export { clampUnit, linearTaper, logTaper, skewTaper };
@@ -0,0 +1,11 @@
1
+ //#region src/lib/write-attribute.ts
2
+ function writeAttribute(element, name, value) {
3
+ if (value === void 0) {
4
+ element.removeAttribute(name);
5
+ return;
6
+ }
7
+ element.setAttribute(name, value);
8
+ }
9
+
10
+ //#endregion
11
+ export { writeAttribute };
@@ -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 @@
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 @@
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 @@
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 @@
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,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 @@
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"}
@@ -8,7 +8,11 @@ declare global {
8
8
  export declare class SonicDial extends SonicRangeElement {
9
9
  #private;
10
10
  static readonly observedAttributes: string[];
11
+ get modulation(): number;
12
+ set modulation(amount: number | undefined);
13
+ connectedCallback(): void;
11
14
  protected connect(signal: AbortSignal): void;
15
+ protected focusTarget(): HTMLElement;
12
16
  protected render(): void;
13
17
  }
14
18
  //#endregion
@@ -0,0 +1,70 @@
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
+ get modulation() {
30
+ return this.numberAttribute("modulation", 0);
31
+ }
32
+ set modulation(amount) {
33
+ this.reflect("modulation", amount);
34
+ }
35
+ #dial = renderDial();
36
+ connectedCallback() {
37
+ this.upgradeProperties("modulation");
38
+ super.connectedCallback();
39
+ }
40
+ connect(signal) {
41
+ const dial = this.#dial;
42
+ this.appendOnce(dial);
43
+ this.render();
44
+ this.checkStyles(dial, "dial.css");
45
+ this.bindGestures(dial, signal, () => ({
46
+ position: (event) => -event.clientY,
47
+ travelPx: readPxProperty(getComputedStyle(dial), "--_sonic-dial-travel", fallbackTravelPx)
48
+ }));
49
+ }
50
+ focusTarget() {
51
+ return this.#dial;
52
+ }
53
+ render() {
54
+ const dial = this.#dial;
55
+ const positions = this.positions();
56
+ const modulated = this.fraction(this.value + this.modulation);
57
+ const value = this.fraction(this.value);
58
+ dial.style.setProperty("--_sonic-dial-value", String(value));
59
+ dial.style.setProperty("--_sonic-dial-origin", String(this.originFraction()));
60
+ dial.style.setProperty("--_sonic-dial-modulation-from", String(Math.min(value, modulated)));
61
+ dial.style.setProperty("--_sonic-dial-modulation-to", String(Math.max(value, modulated)));
62
+ if (positions === void 0) dial.style.removeProperty("--_sonic-dial-positions");
63
+ else dial.style.setProperty("--_sonic-dial-positions", String(positions));
64
+ this.renderAria(dial);
65
+ }
66
+ };
67
+
68
+ //#endregion
69
+ export { SonicDial };
70
+ //# 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\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\treadonly #dial = renderDial();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('modulation');\n\t\tsuper.connectedCallback();\n\t}\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 override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\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.modulation);\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,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY;EACnC,MAAM,kBAAkB;CACzB;CAEA,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,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAU,SAAe;EACxB,MAAM,OAAO,KAAK;EAClB,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,YAAY,KAAK,SAAS,KAAK,QAAQ,KAAK,UAAU;EAC5D,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,22 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ //#region src/elements/form-element.d.ts
3
+ export declare abstract class SonicFormElement extends SonicElement {
4
+ static readonly formAssociated = true;
5
+ get form(): HTMLFormElement | null;
6
+ get labels(): NodeList | null;
7
+ get name(): string;
8
+ set name(value: string | undefined);
9
+ constructor();
10
+ abstract attributeChangedCallback(name: string): void;
11
+ connectedCallback(): void;
12
+ formDisabledCallback(): void;
13
+ formResetCallback(): void;
14
+ formStateRestoreCallback(state: File | FormData | null | string): void;
15
+ protected activate(): void;
16
+ protected namingLabels(): Array<Element>;
17
+ protected abstract render(): void;
18
+ protected abstract restoreState(state: string): void;
19
+ protected writeFormValue(value: null | string, state: string): void;
20
+ }
21
+ //#endregion
22
+ //# sourceMappingURL=form-element.d.ts.map
@@ -0,0 +1,57 @@
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 };
57
+ //# sourceMappingURL=form-element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-element.js","names":[],"sources":["../../../src/elements/form-element.ts"],"sourcesContent":["import { SonicElement } from '#elements/sonic-element.ts';\n\n// A `<label>` names and clicks the host, which has no role and no box, so both pass to the control\nexport abstract class SonicFormElement extends SonicElement {\n\tstatic readonly formAssociated = true;\n\n\tget form(): HTMLFormElement | null {\n\t\t// eslint-disable-next-line unicorn/no-null -- as on native inputs\n\t\treturn this.internals?.form ?? null;\n\t}\n\n\tget labels(): NodeList | null {\n\t\t// eslint-disable-next-line unicorn/no-null -- as on native inputs\n\t\treturn this.internals?.labels ?? null;\n\t}\n\n\tget name(): string {\n\t\treturn this.getAttribute('name') ?? '';\n\t}\n\n\tset name(value: string | undefined) {\n\t\tthis.reflect('name', value);\n\t}\n\n\tconstructor() {\n\t\tsuper();\n\n\t\t// Otherwise the host takes the label's name and shows as a named generic beside the control\n\t\tif (this.internals) this.internals.role = 'none';\n\n\t\t// Only a label's click targets the host itself; a click inside the control bubbles here from its own target\n\t\tthis.addEventListener('click', (event) => {\n\t\t\tif (event.target === this) this.activate();\n\t\t});\n\n\t\t// No event fires when a label arrives, so focus picks it up\n\t\tthis.addEventListener('focusin', () => {\n\t\t\tthis.render();\n\t\t});\n\t}\n\n\tabstract attributeChangedCallback(name: string): void;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('name');\n\t\tsuper.connectedCallback();\n\t}\n\n\t// Never writes the attribute, or re-enabling the fieldset would leave the control disabled\n\tformDisabledCallback(): void {\n\t\tthis.attributeChangedCallback('disabled');\n\t}\n\n\t// Back to the `value` attribute; the key overrides it to return to `pressed`\n\tformResetCallback(): void {\n\t\tthis.attributeChangedCallback('value');\n\t}\n\n\t// Firefox hands a restored state to the wrong control when tags upgrade out of document order\n\t// Each state names its tag, and a control drops one another tag wrote\n\tformStateRestoreCallback(state: File | FormData | null | string): void {\n\t\tconst prefix = `${this.localName}:`;\n\n\t\tif (typeof state === 'string' && state.startsWith(prefix)) {\n\t\t\tthis.restoreState(state.slice(prefix.length));\n\t\t}\n\t}\n\n\tprotected activate(): void {\n\t\tthis.focus();\n\t}\n\n\t// An author's `aria-label` or `aria-labelledby` wins over a label, as on native inputs\n\tprotected override namingLabels(): Array<Element> {\n\t\tconst internals = this.internals;\n\t\tif (!internals || this.hasAttribute('aria-label') || this.hasAttribute('aria-labelledby')) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [...internals.labels].filter((label) => label instanceof Element);\n\t}\n\n\tprotected abstract render(): void;\n\n\tprotected abstract restoreState(state: string): void;\n\n\tprotected writeFormValue(value: null | string, state: string): void {\n\t\tthis.internals?.setFormValue(value, `${this.localName}:${state}`);\n\t}\n}\n"],"mappings":";;;AAGA,IAAsB,mBAAtB,cAA+C,aAAa;CAC3D,OAAgB,iBAAiB;CAEjC,IAAI,OAA+B;EAElC,OAAO,KAAK,WAAW,QAAQ;CAChC;CAEA,IAAI,SAA0B;EAE7B,OAAO,KAAK,WAAW,UAAU;CAClC;CAEA,IAAI,OAAe;EAClB,OAAO,KAAK,aAAa,MAAM,KAAK;CACrC;CAEA,IAAI,KAAK,OAA2B;EACnC,KAAK,QAAQ,QAAQ,KAAK;CAC3B;CAEA,cAAc;EACb,MAAM;EAGN,IAAI,KAAK,WAAW,KAAK,UAAU,OAAO;EAG1C,KAAK,iBAAiB,UAAU,UAAU;GACzC,IAAI,MAAM,WAAW,MAAM,KAAK,SAAS;EAC1C,CAAC;EAGD,KAAK,iBAAiB,iBAAiB;GACtC,KAAK,OAAO;EACb,CAAC;CACF;CAIA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,MAAM;EAC7B,MAAM,kBAAkB;CACzB;CAGA,uBAA6B;EAC5B,KAAK,yBAAyB,UAAU;CACzC;CAGA,oBAA0B;EACzB,KAAK,yBAAyB,OAAO;CACtC;CAIA,yBAAyB,OAA8C;EACtE,MAAM,SAAS,GAAG,KAAK,UAAU;EAEjC,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,MAAM,GACvD,KAAK,aAAa,MAAM,MAAM,OAAO,MAAM,CAAC;CAE9C;CAEA,AAAU,WAAiB;EAC1B,KAAK,MAAM;CACZ;CAGA,AAAmB,eAA+B;EACjD,MAAM,YAAY,KAAK;EACvB,IAAI,CAAC,aAAa,KAAK,aAAa,YAAY,KAAK,KAAK,aAAa,iBAAiB,GACvF,OAAO,CAAC;EAGT,OAAO,CAAC,GAAG,UAAU,MAAM,CAAC,CAAC,QAAQ,UAAU,iBAAiB,OAAO;CACxE;CAMA,AAAU,eAAe,OAAsB,OAAqB;EACnE,KAAK,WAAW,aAAa,OAAO,GAAG,KAAK,UAAU,GAAG,OAAO;CACjE;AACD"}
@@ -0,0 +1,30 @@
1
+ import { SonicFormElement } from "./form-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 SonicFormElement {
9
+ #private;
10
+ static readonly observedAttributes: string[];
11
+ get defaultPressed(): boolean;
12
+ set defaultPressed(isPressed: boolean);
13
+ get momentary(): boolean;
14
+ set momentary(isMomentary: boolean);
15
+ get pressed(): boolean;
16
+ set pressed(isPressed: boolean | undefined);
17
+ get toggle(): boolean;
18
+ set toggle(isToggle: boolean);
19
+ get value(): string;
20
+ set value(value: string | undefined);
21
+ attributeChangedCallback(name: string): void;
22
+ formResetCallback(): void;
23
+ protected activate(): void;
24
+ protected connect(signal: AbortSignal): void;
25
+ protected focusTarget(): HTMLElement;
26
+ protected render(): void;
27
+ protected restoreState(state: string): void;
28
+ }
29
+ //#endregion
30
+ //# sourceMappingURL=key.d.ts.map