@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,186 @@
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 };
186
+ //# sourceMappingURL=radio-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { placeChildren } from '#lib/render.ts';\n\n// RTL is not mirrored\nconst keySteps = new Map([\n\t['ArrowDown', 1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', -1],\n]);\n\ninterface Press {\n\toption: HTMLButtonElement | undefined;\n\tpointerId: number;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\tconst box = option.getBoundingClientRect();\n\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#press: Press | undefined;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(option: HTMLButtonElement): void {\n\t\tthis.select(option);\n\t}\n\n\tprotected chooseByKey(option: HTMLButtonElement, _event: KeyboardEvent): void {\n\t\tthis.select(option);\n\t\toption.focus();\n\t}\n\n\tprotected claimPress(_option: HTMLButtonElement, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tthis.mirrorChildren(\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => child instanceof Element && child.matches('[data-sonic-value]'),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.#copyOptions(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tif (option) this.chooseByClick(option);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tconst next = option && this.#keyTarget(option, event.key);\n\t\t\t\tif (!next) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\treturn option;\n\t}\n\n\tprotected render(): void {\n\t\tconst options = this.options();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst focusable = checked ?? options[0];\n\n\t\tfor (const option of options) {\n\t\t\toption.disabled = this.isDisabled();\n\t\t\toption.setAttribute('aria-checked', String(option === checked));\n\t\t\toption.tabIndex = option === focusable ? 0 : -1;\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(option: HTMLButtonElement): void {\n\t\tconst next = optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tconst isOwn = (event: PointerEvent): boolean => event.pointerId === this.#press?.pointerId;\n\t\tconst release = (event: PointerEvent): void => {\n\t\t\tif (isOwn(event)) this.#hold(undefined);\n\t\t};\n\n\t\tgroup.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tif (!option || event.button !== 0 || this.isDisabled()) return;\n\t\t\t\tif (this.claimPress(option, event)) return;\n\n\t\t\t\tgroup.setPointerCapture(event.pointerId);\n\t\t\t\tthis.#hold({ option, pointerId: event.pointerId });\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (!isOwn(event)) return;\n\n\t\t\t\tthis.#hold({\n\t\t\t\t\toption: this.options().find((option) => isUnder(option, event.clientX, event.clientY)),\n\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t});\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tconst pressed = isOwn(event) ? this.#press?.option : undefined;\n\n\t\t\t\trelease(event);\n\t\t\t\tif (!pressed || this.isDisabled()) return;\n\n\t\t\t\tconst option = this.releaseTarget(pressed);\n\t\t\t\tif (!option) return;\n\n\t\t\t\tthis.select(option);\n\t\t\t\tif (group.matches(':focus-within')) option.focus();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener('pointercancel', release, { signal });\n\t\tgroup.addEventListener('lostpointercapture', release, { signal });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#hold(undefined);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#hold(press: Press | undefined): void {\n\t\tthis.#press = press;\n\t\tfor (const option of this.options()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', option === press?.option);\n\t\t}\n\t}\n\n\t#keyTarget(option: HTMLButtonElement, key: string): HTMLButtonElement | undefined {\n\t\tconst options = this.options();\n\n\t\tif (key === 'Home') return options[0];\n\t\tif (key === 'End') return options.at(-1);\n\n\t\tconst step = keySteps.get(key);\n\t\tif (step === undefined) return undefined;\n\n\t\tconst index = options.indexOf(option) + step;\n\t\tif (this.isWrapping()) return options[(index + options.length) % options.length];\n\n\t\treturn options[Math.min(Math.max(index, 0), options.length - 1)];\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,82 @@
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 };
82
+ //# sourceMappingURL=readout-claim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readout-claim.js","names":[],"sources":["../../../src/elements/readout-claim.ts"],"sourcesContent":["const keyRevealMs = 1000;\n\nexport const revealMs = 250;\n\nexport class ReadoutClaim {\n\tget isRevealed(): boolean {\n\t\treturn this.#isDragging || this.#keyTimer !== undefined;\n\t}\n\n\t#holdTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t#hover: number | undefined;\n\n\t#isDismissed = false;\n\n\t#isDragging = false;\n\n\t#isEditing = false;\n\n\t#keyTimer: ReturnType<typeof setTimeout> | undefined;\n\n\treadonly #onLapse: () => void;\n\n\tconstructor(onLapse: () => void) {\n\t\tthis.#onLapse = onLapse;\n\t}\n\n\tconceal(by: 'drag' | 'keys'): boolean {\n\t\tif (by === 'drag') {\n\t\t\tconst wasDragging = this.#isDragging;\n\n\t\t\tclearTimeout(this.#holdTimer);\n\t\t\tthis.#isDragging = false;\n\n\t\t\treturn wasDragging;\n\t\t}\n\n\t\tclearTimeout(this.#keyTimer);\n\n\t\tconst wasTimed = this.#keyTimer !== undefined;\n\n\t\tthis.#keyTimer = undefined;\n\n\t\treturn wasTimed;\n\t}\n\n\t// Stays dismissed until the hover ends, or the next pointer move would bring it straight back\n\tdismiss(): boolean {\n\t\tconst wasHovered = this.#hover !== undefined;\n\n\t\tthis.#hover = undefined;\n\t\tthis.#isDismissed = wasHovered || this.#isDismissed;\n\n\t\treturn wasHovered;\n\t}\n\n\tedit(isOpen: boolean): void {\n\t\tthis.#isEditing = isOpen;\n\t\tif (isOpen) this.#hover = undefined;\n\t}\n\n\thover(value?: number): void {\n\t\tif (value === undefined) this.#isDismissed = false;\n\t\tif (this.#isDismissed || this.#isEditing || this.isRevealed) return;\n\n\t\tthis.#hover = value;\n\t}\n\n\tpress(isDown: boolean): void {\n\t\tthis.conceal('drag');\n\t\tif (!isDown) {\n\t\t\tthis.#onLapse();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#holdTimer = setTimeout(() => {\n\t\t\tthis.reveal('drag');\n\t\t\tthis.#onLapse();\n\t\t}, revealMs);\n\t}\n\n\treveal(by: 'drag' | 'keys'): void {\n\t\tthis.#hover = undefined;\n\t\tif (by === 'drag') {\n\t\t\tthis.#isDragging = true;\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(this.#keyTimer);\n\t\tthis.#keyTimer = setTimeout(() => {\n\t\t\tthis.#keyTimer = undefined;\n\t\t\tthis.#onLapse();\n\t\t}, keyRevealMs);\n\t}\n\n\tshown(current: number, isReadout: boolean): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing) return { isOpen: true, value: undefined };\n\n\t\tconst isValueShown = this.isRevealed || this.#hover !== undefined;\n\n\t\treturn { isOpen: isValueShown && isReadout, value: this.#hover ?? current };\n\t}\n}\n"],"mappings":";AAAA,MAAM,cAAc;AAEpB,MAAa,WAAW;AAExB,IAAa,eAAb,MAA0B;CACzB,IAAI,aAAsB;EACzB,OAAO,KAAK,eAAe,KAAK,cAAc;CAC/C;CAEA;CAEA;CAEA,eAAe;CAEf,cAAc;CAEd,aAAa;CAEb;CAEA,AAAS;CAET,YAAY,SAAqB;EAChC,KAAK,WAAW;CACjB;CAEA,QAAQ,IAA8B;EACrC,IAAI,OAAO,QAAQ;GAClB,MAAM,cAAc,KAAK;GAEzB,aAAa,KAAK,UAAU;GAC5B,KAAK,cAAc;GAEnB,OAAO;EACR;EAEA,aAAa,KAAK,SAAS;EAE3B,MAAM,WAAW,KAAK,cAAc;EAEpC,KAAK,YAAY;EAEjB,OAAO;CACR;CAGA,UAAmB;EAClB,MAAM,aAAa,KAAK,WAAW;EAEnC,KAAK,SAAS;EACd,KAAK,eAAe,cAAc,KAAK;EAEvC,OAAO;CACR;CAEA,KAAK,QAAuB;EAC3B,KAAK,aAAa;EAClB,IAAI,QAAQ,KAAK,SAAS;CAC3B;CAEA,MAAM,OAAsB;EAC3B,IAAI,UAAU,QAAW,KAAK,eAAe;EAC7C,IAAI,KAAK,gBAAgB,KAAK,cAAc,KAAK,YAAY;EAE7D,KAAK,SAAS;CACf;CAEA,MAAM,QAAuB;EAC5B,KAAK,QAAQ,MAAM;EACnB,IAAI,CAAC,QAAQ;GACZ,KAAK,SAAS;GACd;EACD;EAEA,KAAK,aAAa,iBAAiB;GAClC,KAAK,OAAO,MAAM;GAClB,KAAK,SAAS;EACf,MAAW;CACZ;CAEA,OAAO,IAA2B;EACjC,KAAK,SAAS;EACd,IAAI,OAAO,QAAQ;GAClB,KAAK,cAAc;GACnB;EACD;EAEA,aAAa,KAAK,SAAS;EAC3B,KAAK,YAAY,iBAAiB;GACjC,KAAK,YAAY;GACjB,KAAK,SAAS;EACf,GAAG,WAAW;CACf;CAEA,MAAM,SAAiB,WAAoE;EAC1F,IAAI,KAAK,YAAY,OAAO;GAAE,QAAQ;GAAM,OAAO;EAAU;EAI7D,OAAO;GAAE,SAFY,KAAK,cAAc,KAAK,WAAW,WAEvB;GAAW,OAAO,KAAK,UAAU;EAAQ;CAC3E;AACD"}
@@ -0,0 +1,36 @@
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 };
36
+ //# sourceMappingURL=readout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readout.js","names":[],"sources":["../../../src/elements/readout.ts"],"sourcesContent":["import { requireChild } from '#lib/render.ts';\n\ninterface ReadoutView {\n\tanchor: HTMLElement;\n\tisOpen: boolean;\n\ttext: string | undefined;\n}\n\n// happy-dom has no popover API\nconst canPopover = 'togglePopover' in HTMLElement.prototype;\n\nlet readoutCount = 0;\n\nexport class Readout {\n\t#anchor: HTMLElement | undefined;\n\n\treadonly #bubble: HTMLElement;\n\n\treadonly #name = `--sonic-readout-${String((readoutCount += 1))}`;\n\n\treadonly #text: HTMLElement;\n\n\tconstructor(bubble: HTMLElement) {\n\t\tthis.#bubble = bubble;\n\t\tthis.#text = requireChild(bubble, 'span', HTMLElement);\n\t\tbubble.style.setProperty('position-anchor', this.#name);\n\t}\n\n\tclose(): void {\n\t\tthis.#toggle(false);\n\t}\n\n\tshow({ anchor, isOpen, text }: ReadoutView): void {\n\t\tif (anchor !== this.#anchor) {\n\t\t\tthis.#anchor?.style.removeProperty('anchor-name');\n\t\t\tanchor.style.setProperty('anchor-name', this.#name);\n\t\t\tthis.#anchor = anchor;\n\t\t}\n\n\t\tthis.#text.hidden = text === undefined;\n\t\tif (text !== undefined) this.#text.textContent = text;\n\t\tthis.#toggle(isOpen);\n\t}\n\n\t#toggle(isOpen: boolean): void {\n\t\tif (canPopover && this.#bubble.isConnected) this.#bubble.togglePopover(isOpen);\n\t}\n}\n"],"mappings":";;;AASA,MAAM,aAAa,mBAAmB,YAAY;AAElD,IAAI,eAAe;AAEnB,IAAa,UAAb,MAAqB;CACpB;CAEA,AAAS;CAET,AAAS,QAAQ,mBAAmB,OAAQ,gBAAgB,CAAE;CAE9D,AAAS;CAET,YAAY,QAAqB;EAChC,KAAK,UAAU;EACf,KAAK,QAAQ,aAAa,QAAQ,QAAQ,WAAW;EACrD,OAAO,MAAM,YAAY,mBAAmB,KAAK,KAAK;CACvD;CAEA,QAAc;EACb,KAAK,QAAQ,KAAK;CACnB;CAEA,KAAK,EAAE,QAAQ,QAAQ,QAA2B;EACjD,IAAI,WAAW,KAAK,SAAS;GAC5B,KAAK,SAAS,MAAM,eAAe,aAAa;GAChD,OAAO,MAAM,YAAY,eAAe,KAAK,KAAK;GAClD,KAAK,UAAU;EAChB;EAEA,KAAK,MAAM,SAAS,SAAS;EAC7B,IAAI,SAAS,QAAW,KAAK,MAAM,cAAc;EACjD,KAAK,QAAQ,MAAM;CACpB;CAEA,QAAQ,QAAuB;EAC9B,IAAI,cAAc,KAAK,QAAQ,aAAa,KAAK,QAAQ,cAAc,MAAM;CAC9E;AACD"}
@@ -1,20 +1,14 @@
1
- import { SonicFormElement } from "./form-element.js";
1
+ import { SonicRadioGroupElement } from "./radio-group.js";
2
2
  //#region src/elements/segmented.d.ts
3
3
  declare global {
4
4
  interface HTMLElementTagNameMap {
5
5
  'sonic-segmented': SonicSegmented;
6
6
  }
7
7
  }
8
- export declare class SonicSegmented extends SonicFormElement {
9
- #private;
10
- static readonly observedAttributes: string[];
11
- get value(): string;
12
- set value(next: string);
13
- attributeChangedCallback(name: string): void;
8
+ export declare class SonicSegmented extends SonicRadioGroupElement {
9
+ protected readonly group: HTMLDivElement;
14
10
  protected connect(signal: AbortSignal): void;
15
- protected focusTarget(): HTMLElement | undefined;
16
- protected render(): void;
17
- protected restoreState(state: string): void;
11
+ protected renderOption(): HTMLButtonElement;
18
12
  }
19
13
  //#endregion
20
14
  //# sourceMappingURL=segmented.d.ts.map
@@ -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 +1 @@
1
- {"version":3,"file":"segmented.js","names":[],"sources":["../../../src/elements/segmented.ts"],"sourcesContent":["import { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-segmented': SonicSegmented;\n\t}\n}\n\nconst renderSegmented = template(\n\t/* HTML */ `<div class=\"sonic-segmented\" role=\"radiogroup\"></div>`,\n\tHTMLDivElement,\n);\n\nconst renderSegment = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-segmented-segment\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-segmented-cap\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\n// RTL is not mirrored\nconst keySteps = new Map([\n\t['ArrowDown', 1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', -1],\n]);\n\ninterface Press {\n\tpointerId: number;\n\tsegment: HTMLButtonElement | undefined;\n}\n\nfunction isUnder(segment: Element, x: number, y: number): boolean {\n\tconst box = segment.getBoundingClientRect();\n\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nfunction segmentValue(segment: Element): string | undefined {\n\treturn segment.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport class SonicSegmented extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\t// As on the range, the property never writes the `value` attribute back\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\t#press: Press | undefined;\n\n\treadonly #segmented = renderSegmented();\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst segmented = this.#segmented;\n\n\t\tthis.upgradeProperties('value');\n\t\tthis.mirrorChildren(\n\t\t\t{\n\t\t\t\tcontrol: segmented,\n\t\t\t\tcopy: (options) => {\n\t\t\t\t\tthis.#copyOptions(options);\n\t\t\t\t},\n\t\t\t\tisCopied: (child) => child instanceof Element && child.matches('[data-sonic-value]'),\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.checkStyles(segmented, 'segmented.css');\n\n\t\tsegmented.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst segment = this.#segmentOf(event.target);\n\t\t\t\tif (segment) this.#select(segment);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(segmented, signal);\n\t\tsegmented.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst segment = this.#segmentOf(event.target);\n\t\t\t\tconst next = segment && this.#keyTarget(segment, event.key);\n\t\t\t\tif (!next) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#select(next);\n\t\t\t\tnext.focus();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.#segments().find((segment) => segment.tabIndex === 0);\n\t}\n\n\t// As a radio group, only a checked option submits\n\tprotected render(): void {\n\t\tconst segmented = this.#segmented;\n\t\tconst segments = this.#segments();\n\t\tconst checked = segments.find((segment) => segmentValue(segment) === this.#value);\n\t\tconst focusable = checked ?? segments[0];\n\n\t\tfor (const segment of segments) {\n\t\t\tsegment.disabled = this.isDisabled();\n\t\t\tsegment.setAttribute('aria-checked', String(segment === checked));\n\t\t\tsegment.tabIndex = segment === focusable ? 0 : -1;\n\t\t}\n\t\tthis.forwardNaming(segmented, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\t#bindPress(segmented: HTMLElement, signal: AbortSignal): void {\n\t\tconst isOwn = (event: PointerEvent): boolean => event.pointerId === this.#press?.pointerId;\n\t\tconst release = (event: PointerEvent): void => {\n\t\t\tif (isOwn(event)) this.#hold(undefined);\n\t\t};\n\n\t\tsegmented.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tconst segment = this.#segmentOf(event.target);\n\t\t\t\tif (!segment || event.button !== 0 || this.isDisabled()) return;\n\n\t\t\t\tsegmented.setPointerCapture(event.pointerId);\n\t\t\t\tthis.#hold({ pointerId: event.pointerId, segment });\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsegmented.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (!isOwn(event)) return;\n\n\t\t\t\tthis.#hold({\n\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t\tsegment: this.#segments().find((segment) =>\n\t\t\t\t\t\tisUnder(segment, event.clientX, event.clientY),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsegmented.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tconst segment = isOwn(event) ? this.#press?.segment : undefined;\n\n\t\t\t\trelease(event);\n\t\t\t\tif (!segment || this.isDisabled()) return;\n\n\t\t\t\tthis.#select(segment);\n\t\t\t\tif (segmented.matches(':focus-within')) segment.focus();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsegmented.addEventListener('pointercancel', release, { signal });\n\t\tsegmented.addEventListener('lostpointercapture', release, { signal });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#hold(undefined);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t// Segments are kept by position, so focus follows its option's value across a reorder\n\t#copyOptions(options: Array<Node>): void {\n\t\tconst segmented = this.#segmented;\n\t\tconst segments = this.#segments();\n\t\tconst focused = this.#segmentOf(segmented.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && segmentValue(focused);\n\n\t\tfor (const [index, option] of options.entries()) {\n\t\t\tlet segment = segments[index];\n\t\t\tif (!segment) {\n\t\t\t\tsegment = renderSegment();\n\t\t\t\tsegmented.append(segment);\n\t\t\t}\n\n\t\t\tsegment.firstElementChild?.replaceChildren(copyNode(option));\n\t\t}\n\t\tfor (const segment of segments.slice(options.length)) segment.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#hold(press: Press | undefined): void {\n\t\tthis.#press = press;\n\t\tfor (const segment of this.#segments()) {\n\t\t\tsegment.toggleAttribute('data-sonic-pressed', segment === press?.segment);\n\t\t}\n\t}\n\n\t#keyTarget(segment: HTMLButtonElement, key: string): HTMLButtonElement | undefined {\n\t\tconst segments = this.#segments();\n\n\t\tif (key === 'Home') return segments[0];\n\t\tif (key === 'End') return segments.at(-1);\n\n\t\tconst step = keySteps.get(key);\n\t\tif (step === undefined) return undefined;\n\n\t\tconst index = segments.indexOf(segment) + step;\n\n\t\treturn segments[(index + segments.length) % segments.length];\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst segments = this.#segments();\n\t\tconst next =\n\t\t\tsegments.find((segment) => segmentValue(segment) === value) ??\n\t\t\tsegments.find((segment) => segment.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n\n\t#segmentOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst segment = target.closest('.sonic-segmented-segment');\n\n\t\treturn segment instanceof HTMLButtonElement && segment.parentElement === this.#segmented\n\t\t\t? segment\n\t\t\t: undefined;\n\t}\n\n\t#segments(): Array<HTMLButtonElement> {\n\t\treturn [...this.#segmented.children].filter(\n\t\t\t(child): child is HTMLButtonElement => child instanceof HTMLButtonElement,\n\t\t);\n\t}\n\n\t#select(segment: HTMLButtonElement): void {\n\t\tconst next = segmentValue(segment);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"segmented.js","names":[],"sources":["../../../src/elements/segmented.ts"],"sourcesContent":["import { SonicRadioGroupElement } from '#elements/radio-group.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-segmented': SonicSegmented;\n\t}\n}\n\nconst renderSegmented = template(\n\t/* HTML */ `<div class=\"sonic-segmented\" role=\"radiogroup\"></div>`,\n\tHTMLDivElement,\n);\n\nconst renderOption = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-segmented-option\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-segmented-cap\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nexport class SonicSegmented extends SonicRadioGroupElement {\n\tprotected readonly group = renderSegmented();\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.checkStyles(this.group, 'segmented.css');\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderOption();\n\t}\n}\n"],"mappings":";;;;AASA,MAAM,kBAAkB,SACZ,yDACX,cACD;AAEA,MAAM,eAAe,SACT;;;;IAKX,iBACD;AAEA,IAAa,iBAAb,cAAoC,uBAAuB;CAC1D,AAAmB,QAAQ,gBAAgB;CAE3C,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,MAAM;EACpB,KAAK,YAAY,KAAK,OAAO,eAAe;CAC7C;CAEA,AAAU,eAAkC;EAC3C,OAAO,aAAa;CACrB;AACD"}
@@ -1,23 +1,29 @@
1
- import { SonicRangeElement } from "./range-element.js";
1
+ import { SonicValueElement } from "./value-element.js";
2
2
  //#region src/elements/slider.d.ts
3
3
  declare global {
4
4
  interface HTMLElementTagNameMap {
5
5
  'sonic-slider': SonicSlider;
6
6
  }
7
7
  }
8
- export declare class SonicSlider extends SonicRangeElement {
8
+ export declare class SonicSlider extends SonicValueElement {
9
9
  #private;
10
10
  static readonly observedAttributes: string[];
11
11
  get groovePress(): 'jump' | 'none';
12
12
  set groovePress(gesture: 'jump' | 'none' | undefined);
13
13
  get modulation(): number;
14
14
  set modulation(amount: number | undefined);
15
+ get modulationValue(): number | undefined;
16
+ set modulationValue(value: number | undefined);
15
17
  get orientation(): 'horizontal' | 'vertical';
16
18
  set orientation(direction: 'horizontal' | 'vertical' | undefined);
19
+ get spring(): boolean;
20
+ set spring(isSpring: boolean);
17
21
  connectedCallback(): void;
18
22
  protected connect(signal: AbortSignal): void;
23
+ protected controlOrientation(): 'horizontal' | 'vertical';
24
+ protected draw(): void;
19
25
  protected focusTarget(): HTMLElement;
20
- protected render(): void;
26
+ protected springTarget(): number | undefined;
21
27
  }
22
28
  //#endregion
23
29
  //# sourceMappingURL=slider.d.ts.map