@xsynaptic/sonic-ui 0.4.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 (240) 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 +16 -22
  13. package/dist/default/elements/envelope.js +367 -0
  14. package/dist/default/elements/meter.js +149 -61
  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 +42 -51
  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 +12 -3
  46. package/dist/default/lib/modifier-press.js +11 -0
  47. package/dist/default/lib/number-list.js +6 -6
  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 +23 -17
  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} +8 -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 +16 -22
  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 +10 -5
  100. package/dist/dev/elements/meter.js +149 -61
  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 +6 -6
  116. package/dist/dev/elements/slider.js +42 -51
  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 +12 -3
  180. package/dist/dev/lib/math.js.map +1 -1
  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 +6 -6
  184. package/dist/dev/lib/number-list.js.map +1 -1
  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 +23 -17
  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 +88 -42
  211. package/dist/styles/envelope.css +159 -0
  212. package/dist/styles/led.css +36 -5
  213. package/dist/styles/material.css +277 -76
  214. package/dist/styles/meter.css +33 -48
  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 +21 -48
  219. package/dist/styles/slider.css +175 -39
  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 -169
  227. package/dist/default/elements/range-element.js +0 -546
  228. package/dist/default/lib/detent.js +0 -17
  229. package/dist/dev/define/key.d.ts +0 -2
  230. package/dist/dev/define/key.js +0 -8
  231. package/dist/dev/define/key.js.map +0 -1
  232. package/dist/dev/elements/key.js +0 -170
  233. package/dist/dev/elements/key.js.map +0 -1
  234. package/dist/dev/elements/range-element.d.ts +0 -64
  235. package/dist/dev/elements/range-element.js +0 -547
  236. package/dist/dev/elements/range-element.js.map +0 -1
  237. package/dist/dev/elements/range-entry.js.map +0 -1
  238. package/dist/dev/lib/detent.js +0 -18
  239. package/dist/dev/lib/detent.js.map +0 -1
  240. package/dist/styles/key.css +0 -242
@@ -1,6 +1,5 @@
1
1
  import { requireChild, template } from "../lib/render.js";
2
- import { readPxProperty } from "../lib/read-px-property.js";
3
- import { SonicRangeElement } from "./range-element.js";
2
+ import { SonicValueElement } from "./value-element.js";
4
3
 
5
4
  //#region src/elements/number.ts
6
5
  const renderNumber = template(`
@@ -16,10 +15,19 @@ const renderNumber = template(`
16
15
  />
17
16
  </div>
18
17
  `, HTMLDivElement);
19
- const fallbackTravelPx = 160;
20
- var SonicNumber = class extends SonicRangeElement {
18
+ var SonicNumber = class extends SonicValueElement {
19
+ get press() {
20
+ return this.getAttribute("press") === "step" ? "step" : "none";
21
+ }
22
+ set press(gesture) {
23
+ this.reflect("press", gesture);
24
+ }
21
25
  #number = renderNumber();
22
26
  #digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
27
+ connectedCallback() {
28
+ this.upgradeProperties("press");
29
+ super.connectedCallback();
30
+ }
23
31
  connect(signal) {
24
32
  const number = this.#number;
25
33
  this.appendOnce(number);
@@ -27,18 +35,23 @@ var SonicNumber = class extends SonicRangeElement {
27
35
  this.checkStyles(number, "number.css");
28
36
  this.bindGestures(number, signal, () => ({
29
37
  position: (event) => -event.clientY,
30
- travelPx: readPxProperty(getComputedStyle(number), "--_sonic-number-travel", fallbackTravelPx)
38
+ travelPx: this.travelPx(number, "--_sonic-number-travel")
31
39
  }));
32
40
  }
33
41
  controlRole() {
34
42
  return "spinbutton";
35
43
  }
44
+ draw() {
45
+ this.#digits.textContent = this.valueText;
46
+ }
36
47
  focusTarget() {
37
48
  return this.#number;
38
49
  }
39
- render() {
40
- this.#digits.textContent = this.valueText();
41
- this.renderAria(this.#number);
50
+ tapTarget() {
51
+ if (this.press !== "step") return void 0;
52
+ const mapping = this.mapping();
53
+ const next = mapping.keyTarget("ArrowUp", this.value);
54
+ return next === void 0 || next === this.value ? mapping.bounds[0] : next;
42
55
  }
43
56
  };
44
57
 
@@ -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