@xsynaptic/sonic-ui 0.3.0 → 0.5.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 (237) hide show
  1. package/README.md +2 -21
  2. package/dist/default/dat.js +50 -0
  3. package/dist/default/define/button.js +7 -0
  4. package/dist/default/define/envelope.js +7 -0
  5. package/dist/default/define/split.js +7 -0
  6. package/dist/default/define/switch.js +7 -0
  7. package/dist/default/define/waveform.js +7 -0
  8. package/dist/default/define/wavestrip.js +7 -0
  9. package/dist/default/define/xy.js +7 -0
  10. package/dist/default/define.js +7 -1
  11. package/dist/default/elements/button.js +165 -0
  12. package/dist/default/elements/dial.js +19 -23
  13. package/dist/default/elements/envelope.js +367 -0
  14. package/dist/default/elements/meter.js +168 -26
  15. package/dist/default/elements/number.js +21 -8
  16. package/dist/default/elements/radio-group.js +185 -0
  17. package/dist/default/elements/readout-claim.js +81 -0
  18. package/dist/default/elements/readout.js +35 -0
  19. package/dist/default/elements/segmented.js +9 -159
  20. package/dist/default/elements/slider.js +64 -27
  21. package/dist/default/elements/sonic-element.js +24 -13
  22. package/dist/default/elements/split.js +249 -0
  23. package/dist/default/elements/switch.js +279 -0
  24. package/dist/default/elements/value-element.js +691 -0
  25. package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
  26. package/dist/default/elements/wave-element.js +77 -0
  27. package/dist/default/elements/waveform.js +305 -0
  28. package/dist/default/elements/wavestrip.js +182 -0
  29. package/dist/default/elements/xy.js +473 -0
  30. package/dist/default/index.js +10 -2
  31. package/dist/default/lib/adsr-shape.js +108 -0
  32. package/dist/default/lib/ballistics.js +55 -0
  33. package/dist/default/lib/canvas-surface.js +192 -0
  34. package/dist/default/lib/copy-node.js +32 -2
  35. package/dist/default/lib/crossfade.js +22 -0
  36. package/dist/default/lib/distribute.js +215 -0
  37. package/dist/default/lib/drag-step.js +99 -0
  38. package/dist/default/lib/envelope-curve.js +9 -0
  39. package/dist/default/lib/field.js +94 -0
  40. package/dist/default/lib/focus-by-pointer.js +28 -0
  41. package/dist/default/lib/frame-timeline.js +62 -0
  42. package/dist/default/lib/hold.js +38 -0
  43. package/dist/default/lib/marker-band.js +36 -0
  44. package/dist/default/lib/marker-rider.js +114 -0
  45. package/dist/default/lib/math.js +25 -0
  46. package/dist/default/lib/modifier-press.js +11 -0
  47. package/dist/default/lib/number-list.js +14 -0
  48. package/dist/default/lib/pointer-drag.js +43 -0
  49. package/dist/default/lib/render.js +20 -5
  50. package/dist/default/lib/resample-peaks.js +36 -0
  51. package/dist/default/lib/strip-scene.js +48 -0
  52. package/dist/default/lib/taper.js +32 -16
  53. package/dist/default/lib/time-regions.js +8 -0
  54. package/dist/default/lib/tracking-clock.js +35 -0
  55. package/dist/default/lib/value-mapping.js +115 -0
  56. package/dist/default/lib/waveform-buckets.js +41 -0
  57. package/dist/dev/dat.d.ts +19 -0
  58. package/dist/dev/dat.js +51 -0
  59. package/dist/dev/dat.js.map +1 -0
  60. package/dist/dev/define/button.d.ts +2 -0
  61. package/dist/dev/define/button.js +8 -0
  62. package/dist/dev/define/button.js.map +1 -0
  63. package/dist/dev/define/dial.js.map +1 -1
  64. package/dist/dev/define/envelope.d.ts +2 -0
  65. package/dist/dev/define/envelope.js +8 -0
  66. package/dist/dev/define/envelope.js.map +1 -0
  67. package/dist/dev/define/meter.js.map +1 -1
  68. package/dist/dev/define/number.js.map +1 -1
  69. package/dist/dev/define/segmented.js.map +1 -1
  70. package/dist/dev/define/slider.js.map +1 -1
  71. package/dist/dev/define/split.d.ts +2 -0
  72. package/dist/dev/define/split.js +8 -0
  73. package/dist/dev/define/split.js.map +1 -0
  74. package/dist/dev/define/switch.d.ts +2 -0
  75. package/dist/dev/define/switch.js +8 -0
  76. package/dist/dev/define/switch.js.map +1 -0
  77. package/dist/dev/define/waveform.d.ts +2 -0
  78. package/dist/dev/define/waveform.js +8 -0
  79. package/dist/dev/define/waveform.js.map +1 -0
  80. package/dist/dev/define/wavestrip.d.ts +2 -0
  81. package/dist/dev/define/wavestrip.js +8 -0
  82. package/dist/dev/define/wavestrip.js.map +1 -0
  83. package/dist/dev/define/xy.d.ts +2 -0
  84. package/dist/dev/define/xy.js +8 -0
  85. package/dist/dev/define/xy.js.map +1 -0
  86. package/dist/dev/define.d.ts +15 -3
  87. package/dist/dev/define.js +7 -1
  88. package/dist/dev/elements/{key.d.ts → button.d.ts} +12 -6
  89. package/dist/dev/elements/button.js +166 -0
  90. package/dist/dev/elements/button.js.map +1 -0
  91. package/dist/dev/elements/dial.d.ts +6 -4
  92. package/dist/dev/elements/dial.js +19 -23
  93. package/dist/dev/elements/dial.js.map +1 -1
  94. package/dist/dev/elements/envelope.d.ts +36 -0
  95. package/dist/dev/elements/envelope.js +372 -0
  96. package/dist/dev/elements/envelope.js.map +1 -0
  97. package/dist/dev/elements/form-element.d.ts +1 -1
  98. package/dist/dev/elements/form-element.js.map +1 -1
  99. package/dist/dev/elements/meter.d.ts +12 -1
  100. package/dist/dev/elements/meter.js +168 -26
  101. package/dist/dev/elements/meter.js.map +1 -1
  102. package/dist/dev/elements/number.d.ts +7 -3
  103. package/dist/dev/elements/number.js +21 -8
  104. package/dist/dev/elements/number.js.map +1 -1
  105. package/dist/dev/elements/radio-group.d.ts +25 -0
  106. package/dist/dev/elements/radio-group.js +186 -0
  107. package/dist/dev/elements/radio-group.js.map +1 -0
  108. package/dist/dev/elements/readout-claim.js +82 -0
  109. package/dist/dev/elements/readout-claim.js.map +1 -0
  110. package/dist/dev/elements/readout.js +36 -0
  111. package/dist/dev/elements/readout.js.map +1 -0
  112. package/dist/dev/elements/segmented.d.ts +4 -10
  113. package/dist/dev/elements/segmented.js +9 -159
  114. package/dist/dev/elements/segmented.js.map +1 -1
  115. package/dist/dev/elements/slider.d.ts +9 -3
  116. package/dist/dev/elements/slider.js +64 -27
  117. package/dist/dev/elements/slider.js.map +1 -1
  118. package/dist/dev/elements/sonic-element.d.ts +4 -3
  119. package/dist/dev/elements/sonic-element.js +51 -22
  120. package/dist/dev/elements/sonic-element.js.map +1 -1
  121. package/dist/dev/elements/split.d.ts +21 -0
  122. package/dist/dev/elements/split.js +258 -0
  123. package/dist/dev/elements/split.js.map +1 -0
  124. package/dist/dev/elements/switch.d.ts +35 -0
  125. package/dist/dev/elements/switch.js +280 -0
  126. package/dist/dev/elements/switch.js.map +1 -0
  127. package/dist/dev/elements/value-element.d.ts +84 -0
  128. package/dist/dev/elements/value-element.js +692 -0
  129. package/dist/dev/elements/value-element.js.map +1 -0
  130. package/dist/dev/elements/{range-entry.js → value-entry.js} +4 -4
  131. package/dist/dev/elements/value-entry.js.map +1 -0
  132. package/dist/dev/elements/wave-element.d.ts +36 -0
  133. package/dist/dev/elements/wave-element.js +78 -0
  134. package/dist/dev/elements/wave-element.js.map +1 -0
  135. package/dist/dev/elements/waveform.d.ts +55 -0
  136. package/dist/dev/elements/waveform.js +306 -0
  137. package/dist/dev/elements/waveform.js.map +1 -0
  138. package/dist/dev/elements/wavestrip.d.ts +51 -0
  139. package/dist/dev/elements/wavestrip.js +183 -0
  140. package/dist/dev/elements/wavestrip.js.map +1 -0
  141. package/dist/dev/elements/xy.d.ts +62 -0
  142. package/dist/dev/elements/xy.js +474 -0
  143. package/dist/dev/elements/xy.js.map +1 -0
  144. package/dist/dev/index.d.ts +11 -2
  145. package/dist/dev/index.js +10 -2
  146. package/dist/dev/lib/adsr-shape.js +109 -0
  147. package/dist/dev/lib/adsr-shape.js.map +1 -0
  148. package/dist/dev/lib/ballistics.js +56 -0
  149. package/dist/dev/lib/ballistics.js.map +1 -0
  150. package/dist/dev/lib/canvas-surface.d.ts +25 -0
  151. package/dist/dev/lib/canvas-surface.js +193 -0
  152. package/dist/dev/lib/canvas-surface.js.map +1 -0
  153. package/dist/dev/lib/copy-node.js +32 -2
  154. package/dist/dev/lib/copy-node.js.map +1 -1
  155. package/dist/dev/lib/crossfade.d.ts +12 -0
  156. package/dist/dev/lib/crossfade.js +23 -0
  157. package/dist/dev/lib/crossfade.js.map +1 -0
  158. package/dist/dev/lib/distribute.d.ts +4 -0
  159. package/dist/dev/lib/distribute.js +216 -0
  160. package/dist/dev/lib/distribute.js.map +1 -0
  161. package/dist/dev/lib/drag-step.js +100 -0
  162. package/dist/dev/lib/drag-step.js.map +1 -0
  163. package/dist/dev/lib/envelope-curve.d.ts +4 -0
  164. package/dist/dev/lib/envelope-curve.js +10 -0
  165. package/dist/dev/lib/envelope-curve.js.map +1 -0
  166. package/dist/dev/lib/field.d.ts +4 -0
  167. package/dist/dev/lib/field.js +95 -0
  168. package/dist/dev/lib/field.js.map +1 -0
  169. package/dist/dev/lib/focus-by-pointer.js +29 -0
  170. package/dist/dev/lib/focus-by-pointer.js.map +1 -0
  171. package/dist/dev/lib/frame-timeline.js +63 -0
  172. package/dist/dev/lib/frame-timeline.js.map +1 -0
  173. package/dist/dev/lib/hold.js +39 -0
  174. package/dist/dev/lib/hold.js.map +1 -0
  175. package/dist/dev/lib/marker-band.js +37 -0
  176. package/dist/dev/lib/marker-band.js.map +1 -0
  177. package/dist/dev/lib/marker-rider.js +115 -0
  178. package/dist/dev/lib/marker-rider.js.map +1 -0
  179. package/dist/dev/lib/math.js +26 -0
  180. package/dist/dev/lib/math.js.map +1 -0
  181. package/dist/dev/lib/modifier-press.js +12 -0
  182. package/dist/dev/lib/modifier-press.js.map +1 -0
  183. package/dist/dev/lib/number-list.js +15 -0
  184. package/dist/dev/lib/number-list.js.map +1 -0
  185. package/dist/dev/lib/pointer-drag.js +44 -0
  186. package/dist/dev/lib/pointer-drag.js.map +1 -0
  187. package/dist/dev/lib/read-px-property.js.map +1 -1
  188. package/dist/dev/lib/render.js +20 -5
  189. package/dist/dev/lib/render.js.map +1 -1
  190. package/dist/dev/lib/resample-peaks.js +37 -0
  191. package/dist/dev/lib/resample-peaks.js.map +1 -0
  192. package/dist/dev/lib/strip-scene.js +49 -0
  193. package/dist/dev/lib/strip-scene.js.map +1 -0
  194. package/dist/dev/lib/taper.js +32 -16
  195. package/dist/dev/lib/taper.js.map +1 -1
  196. package/dist/dev/lib/time-regions.d.ts +4 -0
  197. package/dist/dev/lib/time-regions.js +9 -0
  198. package/dist/dev/lib/time-regions.js.map +1 -0
  199. package/dist/dev/lib/tracking-clock.js +36 -0
  200. package/dist/dev/lib/tracking-clock.js.map +1 -0
  201. package/dist/dev/lib/value-mapping.d.ts +23 -0
  202. package/dist/dev/lib/value-mapping.js +116 -0
  203. package/dist/dev/lib/value-mapping.js.map +1 -0
  204. package/dist/dev/lib/waveform-buckets.d.ts +9 -0
  205. package/dist/dev/lib/waveform-buckets.js +42 -0
  206. package/dist/dev/lib/waveform-buckets.js.map +1 -0
  207. package/dist/skins/ivory.css +1 -2
  208. package/dist/styles/button.css +212 -0
  209. package/dist/styles/controls.css +8 -1
  210. package/dist/styles/dial.css +130 -44
  211. package/dist/styles/envelope.css +159 -0
  212. package/dist/styles/led.css +44 -6
  213. package/dist/styles/material.css +321 -71
  214. package/dist/styles/meter.css +109 -28
  215. package/dist/styles/number.css +5 -1
  216. package/dist/styles/panel.css +59 -0
  217. package/dist/styles/ring.css +74 -0
  218. package/dist/styles/segmented.css +22 -54
  219. package/dist/styles/slider.css +256 -58
  220. package/dist/styles/switch.css +347 -0
  221. package/dist/styles/waveform.css +173 -0
  222. package/dist/styles/wavestrip.css +215 -0
  223. package/dist/styles/xy.css +204 -0
  224. package/package.json +14 -2
  225. package/dist/default/define/key.js +0 -7
  226. package/dist/default/elements/key.js +0 -144
  227. package/dist/default/elements/range-element.js +0 -433
  228. package/dist/dev/define/key.d.ts +0 -2
  229. package/dist/dev/define/key.js +0 -8
  230. package/dist/dev/define/key.js.map +0 -1
  231. package/dist/dev/elements/key.js +0 -145
  232. package/dist/dev/elements/key.js.map +0 -1
  233. package/dist/dev/elements/range-element.d.ts +0 -56
  234. package/dist/dev/elements/range-element.js +0 -434
  235. package/dist/dev/elements/range-element.js.map +0 -1
  236. package/dist/dev/elements/range-entry.js.map +0 -1
  237. package/dist/styles/key.css +0 -197
@@ -0,0 +1,185 @@
1
+ import { placeChildren } from "../lib/render.js";
2
+ import { SonicFormElement } from "./form-element.js";
3
+ import { copyNode } from "../lib/copy-node.js";
4
+
5
+ //#region src/elements/radio-group.ts
6
+ const keySteps = /* @__PURE__ */ new Map([
7
+ ["ArrowDown", 1],
8
+ ["ArrowLeft", -1],
9
+ ["ArrowRight", 1],
10
+ ["ArrowUp", -1]
11
+ ]);
12
+ function isUnder(option, x, y) {
13
+ const box = option.getBoundingClientRect();
14
+ return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
15
+ }
16
+ function optionValue(option) {
17
+ return option.querySelector("[data-sonic-value]")?.dataset.sonicValue;
18
+ }
19
+ var SonicRadioGroupElement = class extends SonicFormElement {
20
+ static observedAttributes = [...SonicFormElement.observedAttributes, "value"];
21
+ get value() {
22
+ return this.#value;
23
+ }
24
+ set value(next) {
25
+ if (next === this.#value) return;
26
+ this.#value = next;
27
+ this.render();
28
+ }
29
+ #press;
30
+ #value = "";
31
+ attributeChangedCallback(name) {
32
+ if (name === "value") this.#value = this.getAttribute("value") ?? "";
33
+ this.render();
34
+ }
35
+ chooseByClick(option) {
36
+ this.select(option);
37
+ }
38
+ chooseByKey(option, _event) {
39
+ this.select(option);
40
+ option.focus();
41
+ }
42
+ claimPress(_option, _event) {
43
+ return false;
44
+ }
45
+ connect(signal) {
46
+ const group = this.group;
47
+ this.upgradeProperties("value");
48
+ this.mirrorChildren({
49
+ control: group,
50
+ copy: copyNode,
51
+ isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
52
+ place: (copies) => {
53
+ this.#copyOptions(copies);
54
+ }
55
+ }, signal);
56
+ group.addEventListener("click", (event) => {
57
+ if (event instanceof PointerEvent && event.pointerType !== "") return;
58
+ const option = this.optionOf(event.target);
59
+ if (option) this.chooseByClick(option);
60
+ }, { signal });
61
+ this.#bindPress(group, signal);
62
+ group.addEventListener("keydown", (event) => {
63
+ if (event.defaultPrevented) return;
64
+ const option = this.optionOf(event.target);
65
+ const next = option && this.#keyTarget(option, event.key);
66
+ if (!next) return;
67
+ event.preventDefault();
68
+ this.chooseByKey(next, event);
69
+ }, { signal });
70
+ }
71
+ focusTarget() {
72
+ return this.options().find((option) => option.tabIndex === 0);
73
+ }
74
+ isWrapping() {
75
+ return true;
76
+ }
77
+ optionOf(target) {
78
+ if (!(target instanceof Element)) return void 0;
79
+ const option = target.closest("[role=\"radio\"]");
80
+ return option instanceof HTMLButtonElement && option.parentElement === this.group ? option : void 0;
81
+ }
82
+ options() {
83
+ return [...this.group.children].filter((child) => child instanceof HTMLButtonElement && child.getAttribute("role") === "radio");
84
+ }
85
+ releaseTarget(option) {
86
+ return option;
87
+ }
88
+ render() {
89
+ const options = this.options();
90
+ const checked = options.find((option) => optionValue(option) === this.#value);
91
+ const focusable = checked ?? options[0];
92
+ for (const option of options) {
93
+ option.disabled = this.isDisabled();
94
+ option.setAttribute("aria-checked", String(option === checked));
95
+ option.tabIndex = option === focusable ? 0 : -1;
96
+ }
97
+ this.forwardNaming(this.group, true);
98
+ this.writeFormValue(checked ? this.#value : null, this.#value);
99
+ }
100
+ restoreState(state) {
101
+ this.value = state;
102
+ }
103
+ select(option) {
104
+ const next = optionValue(option);
105
+ if (next === void 0 || next === this.#value) return;
106
+ this.value = next;
107
+ this.dispatchEvent(new Event("change", { bubbles: true }));
108
+ }
109
+ #bindPress(group, signal) {
110
+ const isOwn = (event) => event.pointerId === this.#press?.pointerId;
111
+ const release = (event) => {
112
+ if (isOwn(event)) this.#hold(void 0);
113
+ };
114
+ group.addEventListener("pointerdown", (event) => {
115
+ const option = this.optionOf(event.target);
116
+ if (!option || event.button !== 0 || this.isDisabled()) return;
117
+ if (this.claimPress(option, event)) return;
118
+ group.setPointerCapture(event.pointerId);
119
+ this.#hold({
120
+ option,
121
+ pointerId: event.pointerId
122
+ });
123
+ }, { signal });
124
+ group.addEventListener("pointermove", (event) => {
125
+ if (!isOwn(event)) return;
126
+ this.#hold({
127
+ option: this.options().find((option) => isUnder(option, event.clientX, event.clientY)),
128
+ pointerId: event.pointerId
129
+ });
130
+ }, { signal });
131
+ group.addEventListener("pointerup", (event) => {
132
+ const pressed = isOwn(event) ? this.#press?.option : void 0;
133
+ release(event);
134
+ if (!pressed || this.isDisabled()) return;
135
+ const option = this.releaseTarget(pressed);
136
+ if (!option) return;
137
+ this.select(option);
138
+ if (group.matches(":focus-within")) option.focus();
139
+ }, { signal });
140
+ group.addEventListener("pointercancel", release, { signal });
141
+ group.addEventListener("lostpointercapture", release, { signal });
142
+ signal.addEventListener("abort", () => {
143
+ this.#hold(void 0);
144
+ }, { once: true });
145
+ }
146
+ #copyOptions(copies) {
147
+ const group = this.group;
148
+ const options = this.options();
149
+ const focused = this.optionOf(group.querySelector(":scope > :focus"));
150
+ const focusedValue = focused && optionValue(focused);
151
+ for (const [index, copy] of copies.entries()) {
152
+ let option = options[index];
153
+ if (!option) {
154
+ option = this.renderOption();
155
+ group.append(option);
156
+ }
157
+ if (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);
158
+ }
159
+ for (const option of options.slice(copies.length)) option.remove();
160
+ this.render();
161
+ if (focused) this.#refocus(focused, focusedValue);
162
+ }
163
+ #hold(press) {
164
+ this.#press = press;
165
+ for (const option of this.options()) option.toggleAttribute("data-sonic-pressed", option === press?.option);
166
+ }
167
+ #keyTarget(option, key) {
168
+ const options = this.options();
169
+ if (key === "Home") return options[0];
170
+ if (key === "End") return options.at(-1);
171
+ const step = keySteps.get(key);
172
+ if (step === void 0) return void 0;
173
+ const index = options.indexOf(option) + step;
174
+ if (this.isWrapping()) return options[(index + options.length) % options.length];
175
+ return options[Math.min(Math.max(index, 0), options.length - 1)];
176
+ }
177
+ #refocus(focused, value) {
178
+ const options = this.options();
179
+ const next = options.find((option) => optionValue(option) === value) ?? options.find((option) => option.tabIndex === 0);
180
+ if (next !== focused) next?.focus();
181
+ }
182
+ };
183
+
184
+ //#endregion
185
+ export { SonicRadioGroupElement, isUnder, optionValue };
@@ -0,0 +1,81 @@
1
+ //#region src/elements/readout-claim.ts
2
+ const keyRevealMs = 1e3;
3
+ const revealMs = 250;
4
+ var ReadoutClaim = class {
5
+ get isRevealed() {
6
+ return this.#isDragging || this.#keyTimer !== void 0;
7
+ }
8
+ #holdTimer;
9
+ #hover;
10
+ #isDismissed = false;
11
+ #isDragging = false;
12
+ #isEditing = false;
13
+ #keyTimer;
14
+ #onLapse;
15
+ constructor(onLapse) {
16
+ this.#onLapse = onLapse;
17
+ }
18
+ conceal(by) {
19
+ if (by === "drag") {
20
+ const wasDragging = this.#isDragging;
21
+ clearTimeout(this.#holdTimer);
22
+ this.#isDragging = false;
23
+ return wasDragging;
24
+ }
25
+ clearTimeout(this.#keyTimer);
26
+ const wasTimed = this.#keyTimer !== void 0;
27
+ this.#keyTimer = void 0;
28
+ return wasTimed;
29
+ }
30
+ dismiss() {
31
+ const wasHovered = this.#hover !== void 0;
32
+ this.#hover = void 0;
33
+ this.#isDismissed = wasHovered || this.#isDismissed;
34
+ return wasHovered;
35
+ }
36
+ edit(isOpen) {
37
+ this.#isEditing = isOpen;
38
+ if (isOpen) this.#hover = void 0;
39
+ }
40
+ hover(value) {
41
+ if (value === void 0) this.#isDismissed = false;
42
+ if (this.#isDismissed || this.#isEditing || this.isRevealed) return;
43
+ this.#hover = value;
44
+ }
45
+ press(isDown) {
46
+ this.conceal("drag");
47
+ if (!isDown) {
48
+ this.#onLapse();
49
+ return;
50
+ }
51
+ this.#holdTimer = setTimeout(() => {
52
+ this.reveal("drag");
53
+ this.#onLapse();
54
+ }, 250);
55
+ }
56
+ reveal(by) {
57
+ this.#hover = void 0;
58
+ if (by === "drag") {
59
+ this.#isDragging = true;
60
+ return;
61
+ }
62
+ clearTimeout(this.#keyTimer);
63
+ this.#keyTimer = setTimeout(() => {
64
+ this.#keyTimer = void 0;
65
+ this.#onLapse();
66
+ }, keyRevealMs);
67
+ }
68
+ shown(current, isReadout) {
69
+ if (this.#isEditing) return {
70
+ isOpen: true,
71
+ value: void 0
72
+ };
73
+ return {
74
+ isOpen: (this.isRevealed || this.#hover !== void 0) && isReadout,
75
+ value: this.#hover ?? current
76
+ };
77
+ }
78
+ };
79
+
80
+ //#endregion
81
+ export { ReadoutClaim };
@@ -0,0 +1,35 @@
1
+ import { requireChild } from "../lib/render.js";
2
+
3
+ //#region src/elements/readout.ts
4
+ const canPopover = "togglePopover" in HTMLElement.prototype;
5
+ let readoutCount = 0;
6
+ var Readout = class {
7
+ #anchor;
8
+ #bubble;
9
+ #name = `--sonic-readout-${String(readoutCount += 1)}`;
10
+ #text;
11
+ constructor(bubble) {
12
+ this.#bubble = bubble;
13
+ this.#text = requireChild(bubble, "span", HTMLElement);
14
+ bubble.style.setProperty("position-anchor", this.#name);
15
+ }
16
+ close() {
17
+ this.#toggle(false);
18
+ }
19
+ show({ anchor, isOpen, text }) {
20
+ if (anchor !== this.#anchor) {
21
+ this.#anchor?.style.removeProperty("anchor-name");
22
+ anchor.style.setProperty("anchor-name", this.#name);
23
+ this.#anchor = anchor;
24
+ }
25
+ this.#text.hidden = text === void 0;
26
+ if (text !== void 0) this.#text.textContent = text;
27
+ this.#toggle(isOpen);
28
+ }
29
+ #toggle(isOpen) {
30
+ if (canPopover && this.#bubble.isConnected) this.#bubble.togglePopover(isOpen);
31
+ }
32
+ };
33
+
34
+ //#endregion
35
+ export { Readout };
@@ -1,171 +1,21 @@
1
1
  import { template } from "../lib/render.js";
2
- import { SonicFormElement } from "./form-element.js";
3
- import { copyNode } from "../lib/copy-node.js";
2
+ import { SonicRadioGroupElement } from "./radio-group.js";
4
3
 
5
4
  //#region src/elements/segmented.ts
6
5
  const renderSegmented = template(`<div class="sonic-segmented" role="radiogroup"></div>`, HTMLDivElement);
7
- const renderSegment = template(`
8
- <button class="sonic-segmented-segment" role="radio" type="button">
6
+ const renderOption = template(`
7
+ <button class="sonic-segmented-option" role="radio" type="button">
9
8
  <span class="sonic-segmented-cap"></span>
10
9
  </button>
11
10
  `, HTMLButtonElement);
12
- const keySteps = /* @__PURE__ */ new Map([
13
- ["ArrowDown", 1],
14
- ["ArrowLeft", -1],
15
- ["ArrowRight", 1],
16
- ["ArrowUp", -1]
17
- ]);
18
- function isUnder(segment, x, y) {
19
- const box = segment.getBoundingClientRect();
20
- return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
21
- }
22
- function segmentValue(segment) {
23
- return segment.querySelector("[data-sonic-value]")?.dataset.sonicValue;
24
- }
25
- var SonicSegmented = class extends SonicFormElement {
26
- static observedAttributes = [...SonicFormElement.observedAttributes, "value"];
27
- get value() {
28
- return this.#value;
29
- }
30
- set value(next) {
31
- if (next === this.#value) return;
32
- this.#value = next;
33
- this.render();
34
- }
35
- #press;
36
- #segmented = renderSegmented();
37
- #value = "";
38
- attributeChangedCallback(name) {
39
- if (name === "value") this.#value = this.getAttribute("value") ?? "";
40
- this.render();
41
- }
11
+ var SonicSegmented = class extends SonicRadioGroupElement {
12
+ group = renderSegmented();
42
13
  connect(signal) {
43
- const segmented = this.#segmented;
44
- this.upgradeProperties("value");
45
- this.mirrorChildren({
46
- control: segmented,
47
- copy: (options) => {
48
- this.#copyOptions(options);
49
- },
50
- isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]")
51
- }, signal);
52
- this.checkStyles(segmented, "segmented.css");
53
- segmented.addEventListener("click", (event) => {
54
- if (event instanceof PointerEvent && event.pointerType !== "") return;
55
- const segment = this.#segmentOf(event.target);
56
- if (segment) this.#select(segment);
57
- }, { signal });
58
- this.#bindPress(segmented, signal);
59
- segmented.addEventListener("keydown", (event) => {
60
- if (event.defaultPrevented) return;
61
- const segment = this.#segmentOf(event.target);
62
- const next = segment && this.#keyTarget(segment, event.key);
63
- if (!next) return;
64
- event.preventDefault();
65
- this.#select(next);
66
- next.focus();
67
- }, { signal });
68
- }
69
- focusTarget() {
70
- return this.#segments().find((segment) => segment.tabIndex === 0);
71
- }
72
- render() {
73
- const segmented = this.#segmented;
74
- const segments = this.#segments();
75
- const checked = segments.find((segment) => segmentValue(segment) === this.#value);
76
- const focusable = checked ?? segments[0];
77
- for (const segment of segments) {
78
- segment.disabled = this.isDisabled();
79
- segment.setAttribute("aria-checked", String(segment === checked));
80
- segment.tabIndex = segment === focusable ? 0 : -1;
81
- }
82
- this.forwardNaming(segmented, true);
83
- this.writeFormValue(checked ? this.#value : null, this.#value);
84
- }
85
- restoreState(state) {
86
- this.value = state;
87
- }
88
- #bindPress(segmented, signal) {
89
- const isOwn = (event) => event.pointerId === this.#press?.pointerId;
90
- const release = (event) => {
91
- if (isOwn(event)) this.#hold(void 0);
92
- };
93
- segmented.addEventListener("pointerdown", (event) => {
94
- const segment = this.#segmentOf(event.target);
95
- if (!segment || event.button !== 0 || this.isDisabled()) return;
96
- segmented.setPointerCapture(event.pointerId);
97
- this.#hold({
98
- pointerId: event.pointerId,
99
- segment
100
- });
101
- }, { signal });
102
- segmented.addEventListener("pointermove", (event) => {
103
- if (!isOwn(event)) return;
104
- this.#hold({
105
- pointerId: event.pointerId,
106
- segment: this.#segments().find((segment) => isUnder(segment, event.clientX, event.clientY))
107
- });
108
- }, { signal });
109
- segmented.addEventListener("pointerup", (event) => {
110
- const segment = isOwn(event) ? this.#press?.segment : void 0;
111
- release(event);
112
- if (!segment || this.isDisabled()) return;
113
- this.#select(segment);
114
- if (segmented.matches(":focus-within")) segment.focus();
115
- }, { signal });
116
- segmented.addEventListener("pointercancel", release, { signal });
117
- segmented.addEventListener("lostpointercapture", release, { signal });
118
- signal.addEventListener("abort", () => {
119
- this.#hold(void 0);
120
- }, { once: true });
121
- }
122
- #copyOptions(options) {
123
- const segmented = this.#segmented;
124
- const segments = this.#segments();
125
- const focused = this.#segmentOf(segmented.querySelector(":scope > :focus"));
126
- const focusedValue = focused && segmentValue(focused);
127
- for (const [index, option] of options.entries()) {
128
- let segment = segments[index];
129
- if (!segment) {
130
- segment = renderSegment();
131
- segmented.append(segment);
132
- }
133
- segment.firstElementChild?.replaceChildren(copyNode(option));
134
- }
135
- for (const segment of segments.slice(options.length)) segment.remove();
136
- this.render();
137
- if (focused) this.#refocus(focused, focusedValue);
138
- }
139
- #hold(press) {
140
- this.#press = press;
141
- for (const segment of this.#segments()) segment.toggleAttribute("data-sonic-pressed", segment === press?.segment);
142
- }
143
- #keyTarget(segment, key) {
144
- const segments = this.#segments();
145
- if (key === "Home") return segments[0];
146
- if (key === "End") return segments.at(-1);
147
- const step = keySteps.get(key);
148
- if (step === void 0) return void 0;
149
- return segments[(segments.indexOf(segment) + step + segments.length) % segments.length];
150
- }
151
- #refocus(focused, value) {
152
- const segments = this.#segments();
153
- const next = segments.find((segment) => segmentValue(segment) === value) ?? segments.find((segment) => segment.tabIndex === 0);
154
- if (next !== focused) next?.focus();
155
- }
156
- #segmentOf(target) {
157
- if (!(target instanceof Element)) return void 0;
158
- const segment = target.closest(".sonic-segmented-segment");
159
- return segment instanceof HTMLButtonElement && segment.parentElement === this.#segmented ? segment : void 0;
160
- }
161
- #segments() {
162
- return [...this.#segmented.children].filter((child) => child instanceof HTMLButtonElement);
14
+ super.connect(signal);
15
+ this.checkStyles(this.group, "segmented.css");
163
16
  }
164
- #select(segment) {
165
- const next = segmentValue(segment);
166
- if (next === void 0 || next === this.#value) return;
167
- this.value = next;
168
- this.dispatchEvent(new Event("change", { bubbles: true }));
17
+ renderOption() {
18
+ return renderOption();
169
19
  }
170
20
  };
171
21
 
@@ -1,10 +1,16 @@
1
1
  import { requireChild, template } from "../lib/render.js";
2
- import { SonicRangeElement } from "./range-element.js";
2
+ import { SonicValueElement } from "./value-element.js";
3
3
 
4
4
  //#region src/elements/slider.ts
5
+ function grooveStop(proportion) {
6
+ if (proportion <= 0) return "0%";
7
+ if (proportion >= 1) return "100%";
8
+ return `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;
9
+ }
5
10
  const renderSlider = template(`
6
11
  <div class="sonic-slider" role="slider" tabindex="0">
7
- <div class="sonic-slider-marks"></div>
12
+ <div class="sonic-slider-scale" aria-hidden="true"></div>
13
+ <div class="sonic-slider-notches"></div>
8
14
  <div class="sonic-slider-groove"></div>
9
15
  <div class="sonic-slider-modulation"></div>
10
16
  <div class="sonic-slider-cap"></div>
@@ -21,11 +27,12 @@ const renderSlider = template(`
21
27
  </div>
22
28
  </div>
23
29
  `, HTMLDivElement);
24
- var SonicSlider = class extends SonicRangeElement {
30
+ var SonicSlider = class extends SonicValueElement {
25
31
  static observedAttributes = [
26
- ...SonicRangeElement.observedAttributes,
32
+ ...SonicValueElement.observedAttributes,
27
33
  "modulation",
28
- "orientation"
34
+ "orientation",
35
+ "spring"
29
36
  ];
30
37
  get groovePress() {
31
38
  return this.getAttribute("groove-press") === "none" ? "none" : "jump";
@@ -39,60 +46,90 @@ var SonicSlider = class extends SonicRangeElement {
39
46
  set modulation(amount) {
40
47
  this.reflect("modulation", amount);
41
48
  }
49
+ get modulationValue() {
50
+ return this.readModulationValue();
51
+ }
52
+ set modulationValue(value) {
53
+ this.writeModulationValue(this.#slider, "slider", value);
54
+ }
42
55
  get orientation() {
43
56
  return this.getAttribute("orientation") === "vertical" ? "vertical" : "horizontal";
44
57
  }
45
58
  set orientation(direction) {
46
59
  this.reflect("orientation", direction);
47
60
  }
61
+ get spring() {
62
+ return this.hasAttribute("spring");
63
+ }
64
+ set spring(isSpring) {
65
+ this.reflect("spring", isSpring);
66
+ }
48
67
  #slider = renderSlider();
49
68
  connectedCallback() {
50
- this.upgradeProperties("groovePress", "modulation", "orientation");
69
+ this.upgradeProperties("groovePress", "modulationValue", "modulation", "orientation", "spring");
51
70
  super.connectedCallback();
52
71
  }
53
72
  connect(signal) {
54
73
  const slider = this.#slider;
55
74
  const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
56
- this.appendOnce(slider);
75
+ this.bindScale(slider, requireChild(slider, ".sonic-slider-scale", HTMLDivElement), signal);
76
+ slider.addEventListener("transitionend", (event) => {
77
+ if (event.propertyName === "--_sonic-slider-value") this.toggleState("springing", false);
78
+ }, { signal });
57
79
  this.render();
58
80
  this.checkStyles(slider, "slider.css");
59
81
  this.bindGestures(slider, signal, (event) => {
60
82
  const axis = this.#axis(slider, cap);
61
83
  if (event.target instanceof Node && cap.contains(event.target)) return axis;
62
84
  if (this.groovePress === "none") return;
63
- this.input(this.valueAt((axis.position(event) - axis.startPx) / axis.travelPx));
85
+ this.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));
64
86
  return axis;
65
87
  });
66
88
  }
89
+ controlOrientation() {
90
+ return this.orientation;
91
+ }
92
+ draw() {
93
+ this.writeProportions(this.#slider, "slider", this.modulation);
94
+ this.#renderBuffered(this.#slider);
95
+ }
67
96
  focusTarget() {
68
97
  return this.#slider;
69
98
  }
70
- render() {
71
- const slider = this.#slider;
72
- const positions = this.positions();
73
- const [modulationFrom, modulationTo] = this.modulationFractions(this.modulation);
74
- slider.style.setProperty("--_sonic-slider-value", String(this.fraction(this.value)));
75
- slider.style.setProperty("--_sonic-slider-origin", String(this.originFraction()));
76
- slider.style.setProperty("--_sonic-slider-modulation-from", String(modulationFrom));
77
- slider.style.setProperty("--_sonic-slider-modulation-to", String(modulationTo));
78
- if (positions === void 0) slider.style.removeProperty("--_sonic-slider-positions");
79
- else slider.style.setProperty("--_sonic-slider-positions", String(positions));
80
- this.renderAria(slider, this.orientation);
99
+ springTarget() {
100
+ return this.spring ? this.originValue() : void 0;
81
101
  }
82
102
  #axis(slider, cap) {
83
- const track = slider.getBoundingClientRect();
103
+ const box = slider.getBoundingClientRect();
84
104
  const capBox = cap.getBoundingClientRect();
85
- if (this.orientation === "vertical") return {
105
+ return this.orientation === "vertical" ? {
86
106
  position: (event) => -event.clientY,
87
- startPx: capBox.height / 2 - track.bottom,
88
- travelPx: Math.max(1, track.height - capBox.height)
89
- };
90
- return {
107
+ startPx: capBox.height / 2 - box.bottom,
108
+ travelPx: Math.max(1, box.height - capBox.height)
109
+ } : {
91
110
  position: (event) => event.clientX,
92
- startPx: track.left + capBox.width / 2,
93
- travelPx: Math.max(1, track.width - capBox.width)
111
+ startPx: box.left + capBox.width / 2,
112
+ travelPx: Math.max(1, box.width - capBox.width)
94
113
  };
95
114
  }
115
+ #renderBuffered(slider) {
116
+ const buffered = this.buffered;
117
+ if (buffered.length === 0) {
118
+ slider.style.removeProperty("--_sonic-slider-buffered-regions");
119
+ return;
120
+ }
121
+ const { proportionOf } = this.mapping();
122
+ const stops = buffered.flatMap(([start, end]) => {
123
+ const from = grooveStop(proportionOf(Math.min(start, end)));
124
+ const to = grooveStop(proportionOf(Math.max(start, end)));
125
+ return [
126
+ `transparent ${from}`,
127
+ `var(--_sonic-slider-buffered) ${from} ${to}`,
128
+ `transparent ${to}`
129
+ ];
130
+ });
131
+ slider.style.setProperty("--_sonic-slider-buffered-regions", `linear-gradient(var(--_sonic-slider-toward), ${stops.join(", ")})`);
132
+ }
96
133
  };
97
134
 
98
135
  //#endregion