@xsynaptic/sonic-ui 0.4.0 → 0.6.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 (247) hide show
  1. package/README.md +3 -20
  2. package/dist/default/dat.js +51 -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 +383 -0
  14. package/dist/default/elements/meter.js +155 -63
  15. package/dist/default/elements/number.js +22 -9
  16. package/dist/default/elements/radio-group.js +183 -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 +69 -31
  22. package/dist/default/elements/split.js +249 -0
  23. package/dist/default/elements/switch.js +273 -0
  24. package/dist/default/elements/value-element.js +694 -0
  25. package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
  26. package/dist/default/elements/wave-element.js +80 -0
  27. package/dist/default/elements/waveform.js +328 -0
  28. package/dist/default/elements/wavestrip.js +185 -0
  29. package/dist/default/elements/xy.js +487 -0
  30. package/dist/default/index.js +10 -2
  31. package/dist/default/lib/adsr-shape.js +113 -0
  32. package/dist/default/lib/ballistics.js +55 -0
  33. package/dist/default/lib/canvas-surface.js +203 -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 +63 -0
  42. package/dist/default/lib/hold.js +38 -0
  43. package/dist/default/lib/marker-band.js +80 -0
  44. package/dist/default/lib/marker-rider.js +120 -0
  45. package/dist/default/lib/math.js +17 -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 +46 -0
  49. package/dist/default/lib/render.js +20 -5
  50. package/dist/default/lib/resample-peaks.js +46 -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/default/lib/write-attribute.js +1 -0
  58. package/dist/dev/dat.d.ts +19 -0
  59. package/dist/dev/dat.js +52 -0
  60. package/dist/dev/dat.js.map +1 -0
  61. package/dist/dev/define/button.d.ts +2 -0
  62. package/dist/dev/define/button.js +8 -0
  63. package/dist/dev/define/button.js.map +1 -0
  64. package/dist/dev/define/dial.js.map +1 -1
  65. package/dist/dev/define/envelope.d.ts +2 -0
  66. package/dist/dev/define/envelope.js +8 -0
  67. package/dist/dev/define/envelope.js.map +1 -0
  68. package/dist/dev/define/meter.js.map +1 -1
  69. package/dist/dev/define/number.js.map +1 -1
  70. package/dist/dev/define/segmented.js.map +1 -1
  71. package/dist/dev/define/slider.js.map +1 -1
  72. package/dist/dev/define/split.d.ts +2 -0
  73. package/dist/dev/define/split.js +8 -0
  74. package/dist/dev/define/split.js.map +1 -0
  75. package/dist/dev/define/switch.d.ts +2 -0
  76. package/dist/dev/define/switch.js +8 -0
  77. package/dist/dev/define/switch.js.map +1 -0
  78. package/dist/dev/define/waveform.d.ts +2 -0
  79. package/dist/dev/define/waveform.js +8 -0
  80. package/dist/dev/define/waveform.js.map +1 -0
  81. package/dist/dev/define/wavestrip.d.ts +2 -0
  82. package/dist/dev/define/wavestrip.js +8 -0
  83. package/dist/dev/define/wavestrip.js.map +1 -0
  84. package/dist/dev/define/xy.d.ts +2 -0
  85. package/dist/dev/define/xy.js +8 -0
  86. package/dist/dev/define/xy.js.map +1 -0
  87. package/dist/dev/define.d.ts +15 -3
  88. package/dist/dev/define.js +7 -1
  89. package/dist/dev/elements/{key.d.ts → button.d.ts} +8 -6
  90. package/dist/dev/elements/button.js +166 -0
  91. package/dist/dev/elements/button.js.map +1 -0
  92. package/dist/dev/elements/dial.d.ts +6 -4
  93. package/dist/dev/elements/dial.js +16 -22
  94. package/dist/dev/elements/dial.js.map +1 -1
  95. package/dist/dev/elements/envelope.d.ts +36 -0
  96. package/dist/dev/elements/envelope.js +388 -0
  97. package/dist/dev/elements/envelope.js.map +1 -0
  98. package/dist/dev/elements/form-element.d.ts +1 -1
  99. package/dist/dev/elements/form-element.js.map +1 -1
  100. package/dist/dev/elements/meter.d.ts +11 -6
  101. package/dist/dev/elements/meter.js +155 -63
  102. package/dist/dev/elements/meter.js.map +1 -1
  103. package/dist/dev/elements/number.d.ts +7 -3
  104. package/dist/dev/elements/number.js +22 -9
  105. package/dist/dev/elements/number.js.map +1 -1
  106. package/dist/dev/elements/radio-group.d.ts +25 -0
  107. package/dist/dev/elements/radio-group.js +184 -0
  108. package/dist/dev/elements/radio-group.js.map +1 -0
  109. package/dist/dev/elements/readout-claim.js +82 -0
  110. package/dist/dev/elements/readout-claim.js.map +1 -0
  111. package/dist/dev/elements/readout.js +36 -0
  112. package/dist/dev/elements/readout.js.map +1 -0
  113. package/dist/dev/elements/segmented.d.ts +4 -10
  114. package/dist/dev/elements/segmented.js +9 -159
  115. package/dist/dev/elements/segmented.js.map +1 -1
  116. package/dist/dev/elements/slider.d.ts +6 -6
  117. package/dist/dev/elements/slider.js +42 -51
  118. package/dist/dev/elements/slider.js.map +1 -1
  119. package/dist/dev/elements/sonic-element.d.ts +6 -3
  120. package/dist/dev/elements/sonic-element.js +119 -40
  121. package/dist/dev/elements/sonic-element.js.map +1 -1
  122. package/dist/dev/elements/split.d.ts +21 -0
  123. package/dist/dev/elements/split.js +258 -0
  124. package/dist/dev/elements/split.js.map +1 -0
  125. package/dist/dev/elements/switch.d.ts +35 -0
  126. package/dist/dev/elements/switch.js +274 -0
  127. package/dist/dev/elements/switch.js.map +1 -0
  128. package/dist/dev/elements/value-element.d.ts +84 -0
  129. package/dist/dev/elements/value-element.js +695 -0
  130. package/dist/dev/elements/value-element.js.map +1 -0
  131. package/dist/dev/elements/{range-entry.js → value-entry.js} +4 -4
  132. package/dist/dev/elements/value-entry.js.map +1 -0
  133. package/dist/dev/elements/wave-element.d.ts +37 -0
  134. package/dist/dev/elements/wave-element.js +81 -0
  135. package/dist/dev/elements/wave-element.js.map +1 -0
  136. package/dist/dev/elements/waveform.d.ts +55 -0
  137. package/dist/dev/elements/waveform.js +329 -0
  138. package/dist/dev/elements/waveform.js.map +1 -0
  139. package/dist/dev/elements/wavestrip.d.ts +51 -0
  140. package/dist/dev/elements/wavestrip.js +186 -0
  141. package/dist/dev/elements/wavestrip.js.map +1 -0
  142. package/dist/dev/elements/xy.d.ts +62 -0
  143. package/dist/dev/elements/xy.js +488 -0
  144. package/dist/dev/elements/xy.js.map +1 -0
  145. package/dist/dev/index.d.ts +11 -2
  146. package/dist/dev/index.js +10 -2
  147. package/dist/dev/lib/adsr-shape.js +114 -0
  148. package/dist/dev/lib/adsr-shape.js.map +1 -0
  149. package/dist/dev/lib/ballistics.js +56 -0
  150. package/dist/dev/lib/ballistics.js.map +1 -0
  151. package/dist/dev/lib/canvas-surface.d.ts +26 -0
  152. package/dist/dev/lib/canvas-surface.js +204 -0
  153. package/dist/dev/lib/canvas-surface.js.map +1 -0
  154. package/dist/dev/lib/copy-node.js +32 -2
  155. package/dist/dev/lib/copy-node.js.map +1 -1
  156. package/dist/dev/lib/crossfade.d.ts +12 -0
  157. package/dist/dev/lib/crossfade.js +23 -0
  158. package/dist/dev/lib/crossfade.js.map +1 -0
  159. package/dist/dev/lib/distribute.d.ts +4 -0
  160. package/dist/dev/lib/distribute.js +216 -0
  161. package/dist/dev/lib/distribute.js.map +1 -0
  162. package/dist/dev/lib/drag-step.js +100 -0
  163. package/dist/dev/lib/drag-step.js.map +1 -0
  164. package/dist/dev/lib/envelope-curve.d.ts +4 -0
  165. package/dist/dev/lib/envelope-curve.js +10 -0
  166. package/dist/dev/lib/envelope-curve.js.map +1 -0
  167. package/dist/dev/lib/field.d.ts +4 -0
  168. package/dist/dev/lib/field.js +95 -0
  169. package/dist/dev/lib/field.js.map +1 -0
  170. package/dist/dev/lib/focus-by-pointer.js +29 -0
  171. package/dist/dev/lib/focus-by-pointer.js.map +1 -0
  172. package/dist/dev/lib/frame-timeline.js +64 -0
  173. package/dist/dev/lib/frame-timeline.js.map +1 -0
  174. package/dist/dev/lib/hold.js +39 -0
  175. package/dist/dev/lib/hold.js.map +1 -0
  176. package/dist/dev/lib/marker-band.js +81 -0
  177. package/dist/dev/lib/marker-band.js.map +1 -0
  178. package/dist/dev/lib/marker-rider.js +121 -0
  179. package/dist/dev/lib/marker-rider.js.map +1 -0
  180. package/dist/dev/lib/math.js +17 -3
  181. package/dist/dev/lib/math.js.map +1 -1
  182. package/dist/dev/lib/modifier-press.js +12 -0
  183. package/dist/dev/lib/modifier-press.js.map +1 -0
  184. package/dist/dev/lib/number-list.js +6 -6
  185. package/dist/dev/lib/number-list.js.map +1 -1
  186. package/dist/dev/lib/pointer-drag.js +47 -0
  187. package/dist/dev/lib/pointer-drag.js.map +1 -0
  188. package/dist/dev/lib/read-px-property.js.map +1 -1
  189. package/dist/dev/lib/render.js +20 -5
  190. package/dist/dev/lib/render.js.map +1 -1
  191. package/dist/dev/lib/resample-peaks.js +47 -0
  192. package/dist/dev/lib/resample-peaks.js.map +1 -0
  193. package/dist/dev/lib/strip-scene.js +49 -0
  194. package/dist/dev/lib/strip-scene.js.map +1 -0
  195. package/dist/dev/lib/taper.js +23 -17
  196. package/dist/dev/lib/taper.js.map +1 -1
  197. package/dist/dev/lib/time-regions.d.ts +4 -0
  198. package/dist/dev/lib/time-regions.js +9 -0
  199. package/dist/dev/lib/time-regions.js.map +1 -0
  200. package/dist/dev/lib/tracking-clock.js +36 -0
  201. package/dist/dev/lib/tracking-clock.js.map +1 -0
  202. package/dist/dev/lib/value-mapping.d.ts +23 -0
  203. package/dist/dev/lib/value-mapping.js +116 -0
  204. package/dist/dev/lib/value-mapping.js.map +1 -0
  205. package/dist/dev/lib/waveform-buckets.d.ts +9 -0
  206. package/dist/dev/lib/waveform-buckets.js +42 -0
  207. package/dist/dev/lib/waveform-buckets.js.map +1 -0
  208. package/dist/dev/lib/write-attribute.js +1 -0
  209. package/dist/dev/lib/write-attribute.js.map +1 -1
  210. package/dist/skins/amber.css +8 -0
  211. package/dist/skins/flat.css +25 -0
  212. package/dist/skins/ivory.css +2 -2
  213. package/dist/skins/lime.css +2 -0
  214. package/dist/skins/slate.css +2 -0
  215. package/dist/styles/button.css +240 -0
  216. package/dist/styles/controls.css +8 -1
  217. package/dist/styles/dial.css +110 -56
  218. package/dist/styles/envelope.css +204 -0
  219. package/dist/styles/led.css +39 -8
  220. package/dist/styles/material.css +343 -100
  221. package/dist/styles/meter.css +41 -52
  222. package/dist/styles/number.css +5 -1
  223. package/dist/styles/panel.css +61 -0
  224. package/dist/styles/ring.css +74 -0
  225. package/dist/styles/segmented.css +46 -51
  226. package/dist/styles/slider.css +177 -40
  227. package/dist/styles/switch.css +386 -0
  228. package/dist/styles/waveform.css +196 -0
  229. package/dist/styles/wavestrip.css +232 -0
  230. package/dist/styles/xy.css +241 -0
  231. package/package.json +15 -6
  232. package/dist/default/define/key.js +0 -7
  233. package/dist/default/elements/key.js +0 -169
  234. package/dist/default/elements/range-element.js +0 -546
  235. package/dist/default/lib/detent.js +0 -17
  236. package/dist/dev/define/key.d.ts +0 -2
  237. package/dist/dev/define/key.js +0 -8
  238. package/dist/dev/define/key.js.map +0 -1
  239. package/dist/dev/elements/key.js +0 -170
  240. package/dist/dev/elements/key.js.map +0 -1
  241. package/dist/dev/elements/range-element.d.ts +0 -64
  242. package/dist/dev/elements/range-element.js +0 -547
  243. package/dist/dev/elements/range-element.js.map +0 -1
  244. package/dist/dev/elements/range-entry.js.map +0 -1
  245. package/dist/dev/lib/detent.js +0 -18
  246. package/dist/dev/lib/detent.js.map +0 -1
  247. package/dist/styles/key.css +0 -242
@@ -1,20 +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 readRanges(ranges) {
6
- if ("start" in ranges) return Array.from({ length: ranges.length }, (_entry, index) => [ranges.start(index), ranges.end(index)]);
7
- return [...ranges].map(([start, end]) => [start, end]);
8
- }
9
- function grooveStop(fraction) {
10
- if (fraction <= 0) return "0%";
11
- if (fraction >= 1) return "100%";
12
- return `calc(${String(fraction)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;
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)`;
13
9
  }
14
10
  const renderSlider = template(`
15
11
  <div class="sonic-slider" role="slider" tabindex="0">
16
12
  <div class="sonic-slider-scale" aria-hidden="true"></div>
17
- <div class="sonic-slider-marks"></div>
13
+ <div class="sonic-slider-notches"></div>
18
14
  <div class="sonic-slider-groove"></div>
19
15
  <div class="sonic-slider-modulation"></div>
20
16
  <div class="sonic-slider-cap"></div>
@@ -31,20 +27,13 @@ const renderSlider = template(`
31
27
  </div>
32
28
  </div>
33
29
  `, HTMLDivElement);
34
- var SonicSlider = class extends SonicRangeElement {
30
+ var SonicSlider = class extends SonicValueElement {
35
31
  static observedAttributes = [
36
- ...SonicRangeElement.observedAttributes,
32
+ ...SonicValueElement.observedAttributes,
37
33
  "modulation",
38
34
  "orientation",
39
35
  "spring"
40
36
  ];
41
- get buffered() {
42
- return this.#buffered.map(([start, end]) => [start, end]);
43
- }
44
- set buffered(ranges) {
45
- this.#buffered = ranges ? readRanges(ranges).toSorted((first, second) => first[0] - second[0]) : [];
46
- this.render();
47
- }
48
37
  get groovePress() {
49
38
  return this.getAttribute("groove-press") === "none" ? "none" : "jump";
50
39
  }
@@ -57,6 +46,12 @@ var SonicSlider = class extends SonicRangeElement {
57
46
  set modulation(amount) {
58
47
  this.reflect("modulation", amount);
59
48
  }
49
+ get modulationValue() {
50
+ return this.readModulationValue();
51
+ }
52
+ set modulationValue(value) {
53
+ this.writeModulationValue(this.#slider, "slider", value);
54
+ }
60
55
  get orientation() {
61
56
  return this.getAttribute("orientation") === "vertical" ? "vertical" : "horizontal";
62
57
  }
@@ -69,75 +64,71 @@ var SonicSlider = class extends SonicRangeElement {
69
64
  set spring(isSpring) {
70
65
  this.reflect("spring", isSpring);
71
66
  }
72
- #buffered = [];
73
67
  #slider = renderSlider();
74
68
  connectedCallback() {
75
- this.upgradeProperties("buffered", "groovePress", "modulation", "orientation", "spring");
69
+ this.upgradeProperties("groovePress", "modulationValue", "modulation", "orientation", "spring");
76
70
  super.connectedCallback();
77
71
  }
78
72
  connect(signal) {
79
73
  const slider = this.#slider;
80
74
  const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
81
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 });
82
79
  this.render();
83
80
  this.checkStyles(slider, "slider.css");
84
81
  this.bindGestures(slider, signal, (event) => {
85
82
  const axis = this.#axis(slider, cap);
86
83
  if (event.target instanceof Node && cap.contains(event.target)) return axis;
87
84
  if (this.groovePress === "none") return;
88
- this.input(this.valueAt((axis.position(event) - axis.startPx) / axis.travelPx));
85
+ this.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));
89
86
  return axis;
90
87
  });
91
88
  }
89
+ controlOrientation() {
90
+ return this.orientation;
91
+ }
92
+ draw() {
93
+ this.writeProportions(this.#slider, "slider", this.modulation);
94
+ this.#renderBuffered(this.#slider);
95
+ }
92
96
  focusTarget() {
93
97
  return this.#slider;
94
98
  }
95
- render() {
96
- const slider = this.#slider;
97
- const positions = this.positions();
98
- const [modulationFrom, modulationTo] = this.modulationFractions(this.modulation);
99
- slider.style.setProperty("--_sonic-slider-value", String(this.fraction(this.value)));
100
- slider.style.setProperty("--_sonic-slider-origin", String(this.originFraction()));
101
- slider.style.setProperty("--_sonic-slider-modulation-from", String(modulationFrom));
102
- slider.style.setProperty("--_sonic-slider-modulation-to", String(modulationTo));
103
- if (positions === void 0) slider.style.removeProperty("--_sonic-slider-positions");
104
- else slider.style.setProperty("--_sonic-slider-positions", String(positions));
105
- this.#renderBuffered(slider);
106
- this.renderScale();
107
- this.renderAria(slider, this.orientation);
108
- }
109
99
  springTarget() {
110
- return this.spring ? this.restValue() : void 0;
100
+ return this.spring ? this.originValue() : void 0;
111
101
  }
112
102
  #axis(slider, cap) {
113
- const track = slider.getBoundingClientRect();
103
+ const box = slider.getBoundingClientRect();
114
104
  const capBox = cap.getBoundingClientRect();
115
- if (this.orientation === "vertical") return {
105
+ return this.orientation === "vertical" ? {
116
106
  position: (event) => -event.clientY,
117
- startPx: capBox.height / 2 - track.bottom,
118
- travelPx: Math.max(1, track.height - capBox.height)
119
- };
120
- return {
107
+ startPx: capBox.height / 2 - box.bottom,
108
+ travelPx: Math.max(1, box.height - capBox.height)
109
+ } : {
121
110
  position: (event) => event.clientX,
122
- startPx: track.left + capBox.width / 2,
123
- travelPx: Math.max(1, track.width - capBox.width)
111
+ startPx: box.left + capBox.width / 2,
112
+ travelPx: Math.max(1, box.width - capBox.width)
124
113
  };
125
114
  }
126
115
  #renderBuffered(slider) {
127
- if (this.#buffered.length === 0) {
128
- slider.style.removeProperty("--_sonic-slider-buffered-ranges");
116
+ const buffered = this.buffered;
117
+ if (buffered.length === 0) {
118
+ slider.style.removeProperty("--_sonic-slider-buffered-regions");
129
119
  return;
130
120
  }
131
- const stops = this.#buffered.flatMap(([start, end]) => {
132
- const from = grooveStop(this.fraction(Math.min(start, end)));
133
- const to = grooveStop(this.fraction(Math.max(start, end)));
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)));
134
125
  return [
135
126
  `transparent ${from}`,
136
127
  `var(--_sonic-slider-buffered) ${from} ${to}`,
137
128
  `transparent ${to}`
138
129
  ];
139
130
  });
140
- slider.style.setProperty("--_sonic-slider-buffered-ranges", `linear-gradient(var(--_sonic-slider-toward), ${stops.join(", ")})`);
131
+ slider.style.setProperty("--_sonic-slider-buffered-regions", `linear-gradient(var(--_sonic-slider-toward), ${stops.join(", ")})`);
141
132
  }
142
133
  };
143
134
 
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { RangeAxis } from '#elements/range-element.ts';\n\nimport { SonicRangeElement } from '#elements/range-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends RangeAxis {\n\t// The cap's centre at the minimum, on the same scale as `position`\n\tstartPx: number;\n}\n\nexport type BufferedRanges = Iterable<readonly [number, number]> | TimeRanges;\n\nfunction readRanges(ranges: BufferedRanges): Array<[number, number]> {\n\tif ('start' in ranges) {\n\t\treturn Array.from({ length: ranges.length }, (_entry, index) => [\n\t\t\tranges.start(index),\n\t\t\tranges.end(index),\n\t\t]);\n\t}\n\n\treturn [...ranges].map(([start, end]) => [start, end]);\n}\n\n// Along the groove, which runs past the travel by half its width at each end\nfunction grooveStop(fraction: number): string {\n\tif (fraction <= 0) return '0%';\n\tif (fraction >= 1) return '100%';\n\n\treturn `calc(${String(fraction)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-marks\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicRangeElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRangeElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'spring',\n\t];\n\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(ranges: BufferedRanges | undefined) {\n\t\tthis.#buffered = ranges\n\t\t\t? readRanges(ranges).toSorted((first, second) => first[0] - second[0])\n\t\t\t: [];\n\t\tthis.render();\n\t}\n\n\t// Named for the absent attribute, as `input.type` reads `text`\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('buffered', 'groovePress', 'modulation', 'orientation', 'spring');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tthis.bindScale(slider, requireChild(slider, '.sonic-slider-scale', HTMLDivElement), signal);\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected render(): void {\n\t\tconst slider = this.#slider;\n\t\tconst positions = this.positions();\n\t\tconst [modulationFrom, modulationTo] = this.modulationFractions(this.modulation);\n\n\t\tslider.style.setProperty('--_sonic-slider-value', String(this.fraction(this.value)));\n\t\tslider.style.setProperty('--_sonic-slider-origin', String(this.originFraction()));\n\t\tslider.style.setProperty('--_sonic-slider-modulation-from', String(modulationFrom));\n\t\tslider.style.setProperty('--_sonic-slider-modulation-to', String(modulationTo));\n\t\tif (positions === undefined) slider.style.removeProperty('--_sonic-slider-positions');\n\t\telse slider.style.setProperty('--_sonic-slider-positions', String(positions));\n\t\tthis.#renderBuffered(slider);\n\t\tthis.renderScale();\n\t\tthis.renderAria(slider, this.orientation);\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.restValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst track = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\tif (this.orientation === 'vertical') {\n\t\t\treturn {\n\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\tstartPx: capBox.height / 2 - track.bottom,\n\t\t\t\ttravelPx: Math.max(1, track.height - capBox.height),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tposition: (event) => event.clientX,\n\t\t\tstartPx: track.left + capBox.width / 2,\n\t\t\ttravelPx: Math.max(1, track.width - capBox.width),\n\t\t};\n\t}\n\n\t// Hard stops in one gradient, so any number of ranges costs one layer\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tif (this.#buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-ranges');\n\t\t\treturn;\n\t\t}\n\n\t\tconst stops = this.#buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(this.fraction(Math.min(start, end)));\n\t\t\tconst to = grooveStop(this.fraction(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-ranges',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-element.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'spring',\n\t];\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\tget modulationValue(): number | undefined {\n\t\treturn this.readModulationValue();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.writeModulationValue(this.#slider, 'slider', value);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('groovePress', 'modulationValue', 'modulation', 'orientation', 'spring');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tthis.bindScale(slider, requireChild(slider, '.sonic-slider-scale', HTMLDivElement), signal);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.writeProportions(this.#slider, 'slider', this.modulation);\n\t\tthis.#renderBuffered(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n}\n"],"mappings":";;;;AAeA,SAAS,WAAW,YAA4B;CAC/C,IAAI,cAAc,GAAG,OAAO;CAC5B,IAAI,cAAc,GAAG,OAAO;CAE5B,OAAO,QAAQ,OAAO,UAAU,EAAE;AACnC;AAEA,MAAM,eAAe,SACT;;;;;;;;;;;;;;;;;;;IAoBX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;EACA;CACD;CAEA,IAAI,cAA+B;EAClC,OAAO,KAAK,aAAa,cAAc,MAAM,SAAS,SAAS;CAChE;CAEA,IAAI,YAAY,SAAsC;EACrD,KAAK,QAAQ,gBAAgB,OAAO;CACrC;CAEA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAEA,IAAI,kBAAsC;EACzC,OAAO,KAAK,oBAAoB;CACjC;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,qBAAqB,KAAK,SAAS,UAAU,KAAK;CACxD;CAEA,IAAI,cAAyC;EAC5C,OAAO,KAAK,aAAa,aAAa,MAAM,aAAa,aAAa;CACvE;CAEA,IAAI,YAAY,WAAkD;EACjE,KAAK,QAAQ,eAAe,SAAS;CACtC;CAEA,IAAI,SAAkB;EACrB,OAAO,KAAK,aAAa,QAAQ;CAClC;CAEA,IAAI,OAAO,UAAmB;EAC7B,KAAK,QAAQ,UAAU,QAAQ;CAChC;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,eAAe,mBAAmB,cAAc,eAAe,QAAQ;EAC9F,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EACpB,MAAM,MAAM,aAAa,QAAQ,qBAAqB,cAAc;EAEpE,KAAK,UAAU,QAAQ,aAAa,QAAQ,uBAAuB,cAAc,GAAG,MAAM;EAC1F,OAAO,iBACN,kBACC,UAAU;GACV,IAAI,MAAM,iBAAiB,yBAAyB,KAAK,YAAY,aAAa,KAAK;EACxF,GACA,EAAE,OAAO,CACV;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,YAAY;EACrC,KAAK,aAAa,QAAQ,SAAS,UAAU;GAC5C,MAAM,OAAO,KAAK,MAAM,QAAQ,GAAG;GACnC,IAAI,MAAM,kBAAkB,QAAQ,IAAI,SAAS,MAAM,MAAM,GAAG,OAAO;GAEvE,IAAI,KAAK,gBAAgB,QAAQ;GAEjC,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,QAAQ,CAAC;GAExF,OAAO;EACR,CAAC;CACF;CAEA,AAAmB,qBAAgD;EAClE,OAAO,KAAK;CACb;CAEA,AAAU,OAAa;EACtB,KAAK,iBAAiB,KAAK,SAAS,UAAU,KAAK,UAAU;EAC7D,KAAK,gBAAgB,KAAK,OAAO;CAClC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,eAAmC;EACrD,OAAO,KAAK,SAAS,KAAK,YAAY,IAAI;CAC3C;CAEA,MAAM,QAAqB,KAA8B;EACxD,MAAM,MAAM,OAAO,sBAAsB;EACzC,MAAM,SAAS,IAAI,sBAAsB;EAEzC,OAAO,KAAK,gBAAgB,aACzB;GACA,WAAW,UAAU,CAAC,MAAM;GAC5B,SAAS,OAAO,SAAS,IAAI,IAAI;GACjC,UAAU,KAAK,IAAI,GAAG,IAAI,SAAS,OAAO,MAAM;EACjD,IACC;GACA,WAAW,UAAU,MAAM;GAC3B,SAAS,IAAI,OAAO,OAAO,QAAQ;GACnC,UAAU,KAAK,IAAI,GAAG,IAAI,QAAQ,OAAO,KAAK;EAC/C;CACH;CAEA,gBAAgB,QAA2B;EAC1C,MAAM,WAAW,KAAK;EAEtB,IAAI,SAAS,WAAW,GAAG;GAC1B,OAAO,MAAM,eAAe,kCAAkC;GAC9D;EACD;EAEA,MAAM,EAAE,iBAAiB,KAAK,QAAQ;EACtC,MAAM,QAAQ,SAAS,SAAS,CAAC,OAAO,SAAS;GAChD,MAAM,OAAO,WAAW,aAAa,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC;GAC1D,MAAM,KAAK,WAAW,aAAa,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC;GAExD,OAAO;IACN,eAAe;IACf,iCAAiC,KAAK,GAAG;IACzC,eAAe;GAChB;EACD,CAAC;EAED,OAAO,MAAM,YACZ,oCACA,gDAAgD,MAAM,KAAK,IAAI,EAAE,EAClE;CACD;AACD"}
@@ -1,8 +1,10 @@
1
1
  //#region src/elements/sonic-element.d.ts
2
2
  interface Mirror {
3
3
  control: Element;
4
- copy: (originals: Array<ChildNode>) => void;
4
+ copy: (original: ChildNode) => Node | undefined;
5
5
  isCopied?: (child: Node) => boolean;
6
+ isPassed?: (child: Node) => boolean;
7
+ place: (copies: Array<Node>) => void;
6
8
  }
7
9
  export declare abstract class SonicElement extends HTMLElement {
8
10
  #private;
@@ -15,13 +17,14 @@ export declare abstract class SonicElement extends HTMLElement {
15
17
  connectedMoveCallback(): void;
16
18
  disconnectedCallback(): void;
17
19
  focus(options?: FocusOptions): void;
18
- protected appendOnce(child: Element): void;
19
20
  protected checkStyles(control: HTMLElement, sheet: string): void;
20
21
  protected abstract connect(signal: AbortSignal): void;
21
22
  protected focusTarget(): HTMLElement | undefined;
22
23
  protected forwardNaming(target: Element, isNamed: boolean): void;
24
+ protected isBound(): boolean;
23
25
  protected isDisabled(): boolean;
24
- protected mirrorChildren({ control, copy, isCopied }: Mirror, signal: AbortSignal): void;
26
+ protected keepControl(control: Element, signal: AbortSignal): void;
27
+ protected mirrorChildren({ control, copy, isCopied, isPassed, place }: Mirror, signal: AbortSignal): void;
25
28
  protected namingLabels(): Array<Element>;
26
29
  protected numberAttribute(name: string, fallback: number): number;
27
30
  protected optionalNumberAttribute(name: string): number | undefined;
@@ -1,4 +1,5 @@
1
- import { attachSlot } from "../lib/render.js";
1
+ import { toNumber } from "../lib/math.js";
2
+ import { attachSlots } from "../lib/render.js";
2
3
  import { writeAttribute } from "../lib/write-attribute.js";
3
4
 
4
5
  //#region src/elements/sonic-element.ts
@@ -7,12 +8,23 @@ const namingAttributes = [
7
8
  "aria-label",
8
9
  "aria-labelledby"
9
10
  ];
10
- const checkedClasses = /* @__PURE__ */ new WeakSet();
11
+ const checkedSheets = /* @__PURE__ */ new WeakMap();
12
+ const checkedMirrors = /* @__PURE__ */ new WeakSet();
13
+ const checkedChildren = /* @__PURE__ */ new WeakSet();
14
+ const interactive = "a[href], button, input, select, textarea, [tabindex]";
11
15
  function writeLabelledBy(target, labels) {
12
16
  const current = target.ariaLabelledByElements;
13
17
  if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
14
18
  target.ariaLabelledByElements = labels;
15
19
  }
20
+ function unmirrorable(original) {
21
+ if (!(original instanceof Element)) return void 0;
22
+ return [original, ...original.querySelectorAll("*")].find((element) => element.matches(interactive) || element.localName.includes("-"));
23
+ }
24
+ function isContent(child) {
25
+ if (child instanceof Element) return true;
26
+ return child instanceof Text && child.data.trim() !== "";
27
+ }
16
28
  var SonicElement = class extends HTMLElement {
17
29
  static observedAttributes = [...namingAttributes, "disabled"];
18
30
  get disabled() {
@@ -23,7 +35,7 @@ var SonicElement = class extends HTMLElement {
23
35
  }
24
36
  internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
25
37
  #connection;
26
- #slot;
38
+ #slots;
27
39
  blur() {
28
40
  this.#focused()?.blur();
29
41
  }
@@ -31,13 +43,14 @@ var SonicElement = class extends HTMLElement {
31
43
  this.upgradeProperties("disabled");
32
44
  if (this.#connection) return;
33
45
  const connection = new AbortController();
46
+ this.#connection = connection;
34
47
  try {
35
48
  this.connect(connection.signal);
36
49
  } catch (error) {
37
50
  connection.abort();
51
+ this.#connection = void 0;
38
52
  throw error;
39
53
  }
40
- this.#connection = connection;
41
54
  }
42
55
  connectedMoveCallback() {}
43
56
  disconnectedCallback() {
@@ -57,22 +70,37 @@ var SonicElement = class extends HTMLElement {
57
70
  }
58
71
  if (!this.isDisabled() && !this.#focused()) target.focus(options);
59
72
  }
60
- appendOnce(child) {
61
- if (child.parentNode !== this) this.append(child);
62
- }
63
73
  checkStyles(control, sheet) {
64
74
  const elementClass = this.constructor;
65
- if (checkedClasses.has(elementClass)) return;
66
- checkedClasses.add(elementClass);
75
+ const checked = checkedSheets.get(elementClass) ?? /* @__PURE__ */ new Set();
76
+ const part = ({ selector }) => selector === void 0 ? control : this.querySelector(selector) ?? void 0;
77
+ const probes = [
78
+ {
79
+ sheet: "material.css",
80
+ token: "--_sonic-unlit"
81
+ },
82
+ {
83
+ sheet,
84
+ token: "--_sonic-unit"
85
+ },
86
+ {
87
+ selector: ".sonic-led",
88
+ sheet: "led.css",
89
+ token: "--_sonic-led-lens-ratio"
90
+ }
91
+ ].filter((probe) => !checked.has(probe.sheet) && part(probe) !== void 0);
92
+ if (probes.length === 0) return;
93
+ checkedSheets.set(elementClass, checked);
94
+ for (const probe of probes) checked.add(probe.sheet);
67
95
  requestAnimationFrame(() => {
68
- if (!control.isConnected) {
69
- checkedClasses.delete(elementClass);
70
- return;
96
+ const missing = [];
97
+ for (const probe of probes) {
98
+ const drawn = control.isConnected ? part(probe) : void 0;
99
+ if (!drawn) checked.delete(probe.sheet);
100
+ else if (getComputedStyle(drawn).getPropertyValue(probe.token) === "") missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);
71
101
  }
72
- const styles = getComputedStyle(control);
73
- const missing = [styles.getPropertyValue("--_sonic-unlit") === "" ? "material.css" : "", styles.getPropertyValue("--_sonic-unit") === "" ? sheet : ""].filter(Boolean);
74
102
  if (missing.length === 0) return;
75
- console.warn(`<${this.localName}> draws blank without ${missing.map((file) => `@xsynaptic/sonic-ui/${file}`).join(" and ")} (or controls.css)`);
103
+ console.warn(`<${this.localName}> draws blank without ${missing.join(" and ")} (or controls.css)`);
76
104
  });
77
105
  }
78
106
  focusTarget() {}
@@ -85,28 +113,64 @@ var SonicElement = class extends HTMLElement {
85
113
  }
86
114
  if (labels.length > 0) writeLabelledBy(target, labels);
87
115
  }
116
+ isBound() {
117
+ return this.#connection !== void 0;
118
+ }
88
119
  isDisabled() {
89
120
  return this.disabled || this.matches(":disabled");
90
121
  }
91
- mirrorChildren({ control, copy, isCopied = () => true }, signal) {
92
- const slot = this.#slot ?? attachSlot(this);
93
- this.#slot = slot;
94
- const mirror = () => {
122
+ keepControl(control, signal) {
123
+ const keep = () => {
124
+ this.#appendOnce(control);
125
+ this.#checkChildren([...this.childNodes].filter((child) => child !== control));
126
+ };
127
+ const observer = new MutationObserver(keep);
128
+ keep();
129
+ observer.observe(this, { childList: true });
130
+ signal.addEventListener("abort", () => {
131
+ observer.disconnect();
132
+ }, { once: true });
133
+ }
134
+ mirrorChildren({ control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
135
+ const slots = this.#slots ?? attachSlots(this);
136
+ let copies = /* @__PURE__ */ new Map();
137
+ this.#slots = slots;
138
+ const mirror = (touched) => {
139
+ this.#appendOnce(control);
95
140
  const children = [...this.childNodes].filter((child) => child !== control);
96
- copy(children.filter((child) => isCopied(child)));
97
- this.appendOnce(control);
98
- slot.assign(...children.filter((child) => (child instanceof Element || child instanceof Text) && !isCopied(child)), control);
141
+ const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
142
+ const kept = copies;
143
+ const originals = children.filter((child) => isCopied(child));
144
+ this.#checkMirrored(originals);
145
+ this.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));
146
+ copies = new Map(originals.map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
147
+ place([...copies.values()].filter((made) => made !== void 0));
148
+ slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
149
+ slots.kept.assign(...slotted.filter((child) => isCopied(child)));
150
+ };
151
+ const watch = () => {
152
+ observer.disconnect();
153
+ observer.observe(this, { childList: true });
154
+ for (const child of this.childNodes) {
155
+ if (child === control) continue;
156
+ observer.observe(child, {
157
+ attributes: true,
158
+ characterData: true,
159
+ childList: true,
160
+ subtree: true
161
+ });
162
+ }
99
163
  };
100
164
  const observer = new MutationObserver((records) => {
101
- if (records.some((record) => this.#isCopiedChange(record, control, isCopied))) mirror();
165
+ const changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));
166
+ if (changes.length === 0) return;
167
+ if (changes.some((record) => record.target === this)) {
168
+ mirror();
169
+ watch();
170
+ } else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));
102
171
  });
103
172
  mirror();
104
- observer.observe(this, {
105
- attributes: true,
106
- characterData: true,
107
- childList: true,
108
- subtree: true
109
- });
173
+ watch();
110
174
  signal.addEventListener("abort", () => {
111
175
  observer.disconnect();
112
176
  }, { once: true });
@@ -115,9 +179,7 @@ var SonicElement = class extends HTMLElement {
115
179
  return [];
116
180
  }
117
181
  numberAttribute(name, fallback) {
118
- const attribute = this.getAttribute(name);
119
- if (attribute === null) return fallback;
120
- const parsed = Number(attribute);
182
+ const parsed = toNumber(this.getAttribute(name));
121
183
  return Number.isFinite(parsed) ? parsed : fallback;
122
184
  }
123
185
  optionalNumberAttribute(name) {
@@ -125,11 +187,11 @@ var SonicElement = class extends HTMLElement {
125
187
  return Number.isNaN(value) ? void 0 : value;
126
188
  }
127
189
  reflect(name, value) {
128
- if (value === true) {
129
- this.setAttribute(name, "");
190
+ if (typeof value !== "number" && typeof value !== "string" && value !== true) {
191
+ this.removeAttribute(name);
130
192
  return;
131
193
  }
132
- writeAttribute(this, name, value === false || value === null ? void 0 : value?.toString());
194
+ this.setAttribute(name, value === true ? "" : value.toString());
133
195
  }
134
196
  toggleState(state, isOn) {
135
197
  if (isOn) this.internals?.states.add(state);
@@ -143,6 +205,26 @@ var SonicElement = class extends HTMLElement {
143
205
  this[name] = value;
144
206
  }
145
207
  }
208
+ #appendOnce(control) {
209
+ const [hook] = control.classList;
210
+ const stale = [...this.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
211
+ for (const child of stale) child.remove();
212
+ if (control.parentNode !== this) this.append(control);
213
+ }
214
+ #checkChildren(undocumented) {
215
+ if (checkedChildren.has(this.constructor)) return;
216
+ const found = undocumented.find((child) => isContent(child));
217
+ if (!found) return;
218
+ checkedChildren.add(this.constructor);
219
+ console.warn(`<${this.localName}> uses only the children it documents, so the ${found instanceof Element ? `<${found.localName}>` : "text"} inside it is unsupported`);
220
+ }
221
+ #checkMirrored(originals) {
222
+ if (checkedMirrors.has(this.constructor)) return;
223
+ const [found] = originals.flatMap((original) => unmirrorable(original) ?? []);
224
+ if (!found) return;
225
+ checkedMirrors.add(this.constructor);
226
+ console.warn(`<${this.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`);
227
+ }
146
228
  #childHolding(node) {
147
229
  let current = node;
148
230
  while (current && current.parentNode !== this) current = current.parentNode;
@@ -154,12 +236,9 @@ var SonicElement = class extends HTMLElement {
154
236
  return active instanceof HTMLElement && this.contains(active) ? active : void 0;
155
237
  }
156
238
  #isCopiedChange(record, control, isCopied) {
157
- if (record.target === this) {
158
- if (record.type === "attributes") return false;
159
- return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
160
- }
239
+ if (record.target === this) return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
161
240
  const child = this.#childHolding(record.target);
162
- if (child === void 0 || child === control) return false;
241
+ if (child === void 0) return false;
163
242
  return isCopied(child) || record.type === "attributes" && record.target === child;
164
243
  }
165
244
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import { attachSlot } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// Replaced at build: `true` in the `development` tree, `false` in the default one\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\n// Forwarding another attribute is a public API decision\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedClasses = new WeakSet<object>();\n\n// Rewriting the same labels on every render would churn the attribute through a drag\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\ninterface Mirror {\n\tcontrol: Element;\n\tcopy: (originals: Array<ChildNode>) => void;\n\t// Children left out render beside the control, as they are\n\tisCopied?: (child: Node) => boolean;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract, so only its subclasses are ever defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\t#connection: AbortController | undefined;\n\n\t#slot: HTMLSlotElement | undefined;\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\t// A throw leaves nothing bound behind it\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthrow error;\n\t\t}\n\n\t\tthis.#connection = connection;\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\t// As `delegatesFocus` does, focus already inside the control stays where it is\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected appendOnce(child: Element): void {\n\t\tif (child.parentNode !== this) this.append(child);\n\t}\n\n\t// A missing sheet draws the control blank without an error\n\tprotected checkStyles(control: HTMLElement, sheet: string): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst elementClass = this.constructor;\n\t\tif (checkedClasses.has(elementClass)) return;\n\n\t\tcheckedClasses.add(elementClass);\n\t\trequestAnimationFrame(() => {\n\t\t\t// A detached control computes no styles\n\t\t\tif (!control.isConnected) {\n\t\t\t\tcheckedClasses.delete(elementClass);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst styles = getComputedStyle(control);\n\t\t\tconst missing = [\n\t\t\t\tstyles.getPropertyValue('--_sonic-unlit') === '' ? 'material.css' : '',\n\t\t\t\tstyles.getPropertyValue('--_sonic-unit') === '' ? sheet : '',\n\t\t\t].filter(Boolean);\n\t\t\tif (missing.length === 0) return;\n\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> draws blank without ${missing.map((file) => `@xsynaptic/sonic-ui/${file}`).join(' and ')} (or controls.css)`,\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) writeLabelledBy(target, labels);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A framework keeps editing the children it owns, so the control copies them rather than taking them\n\tprotected mirrorChildren(\n\t\t{ control, copy, isCopied = () => true }: Mirror,\n\t\tsignal: AbortSignal,\n\t): void {\n\t\tconst slot = this.#slot ?? attachSlot(this);\n\n\t\tthis.#slot = slot;\n\t\tconst mirror = (): void => {\n\t\t\tconst children = [...this.childNodes].filter((child) => child !== control);\n\n\t\t\tcopy(children.filter((child) => isCopied(child)));\n\t\t\tthis.appendOnce(control);\n\t\t\tslot.assign(\n\t\t\t\t...children.filter(\n\t\t\t\t\t(child): child is Element | Text =>\n\t\t\t\t\t\t(child instanceof Element || child instanceof Text) && !isCopied(child),\n\t\t\t\t),\n\t\t\t\tcontrol,\n\t\t\t);\n\t\t};\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tif (records.some((record) => this.#isCopiedChange(record, control, isCopied))) mirror();\n\t\t});\n\n\t\tmirror();\n\t\tobserver.observe(this, {\n\t\t\tattributes: true,\n\t\t\tcharacterData: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst attribute = this.getAttribute(name);\n\t\tif (attribute === null) return fallback;\n\n\t\tconst parsed = Number(attribute);\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// `undefined`, `null` and `false` remove the attribute, as the Lit kits reflect\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (value === true) {\n\t\t\tthis.setAttribute(name, '');\n\t\t\treturn;\n\t\t}\n\n\t\twriteAttribute(this, name, value === false || value === null ? undefined : value?.toString());\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.internals?.states.add(state);\n\t\telse this.internals?.states.delete(state);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\t#childHolding(node: Node): Node | undefined {\n\t\tlet current: Node | null = node;\n\n\t\twhile (current && current.parentNode !== this) current = current.parentNode;\n\n\t\treturn current ?? undefined;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#isCopiedChange(\n\t\trecord: MutationRecord,\n\t\tcontrol: Element,\n\t\tisCopied: (child: Node) => boolean,\n\t): boolean {\n\t\tif (record.target === this) {\n\t\t\tif (record.type === 'attributes') return false;\n\n\t\t\t// The control's own arrival, or every mirror would run twice\n\t\t\treturn (\n\t\t\t\trecord.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control)\n\t\t\t);\n\t\t}\n\n\t\t// A node removed since keeps reporting until delivery, and belongs to no child\n\t\tconst child = this.#childHolding(record.target);\n\n\t\tif (child === undefined || child === control) return false;\n\n\t\t// A child's own attribute can move it out of the copies, which `isCopied` no longer sees\n\t\treturn isCopied(child) || (record.type === 'attributes' && record.target === child);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { MirrorSlots } from '#lib/render.ts';\n\nimport { toNumber } from '#lib/math.ts';\nimport { attachSlots } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\nconst checkedMirrors = new WeakSet<object>();\n\nconst checkedChildren = new WeakSet<object>();\n\nconst interactive = 'a[href], button, input, select, textarea, [tabindex]';\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\nfunction unmirrorable(original: Node): Element | undefined {\n\tif (!(original instanceof Element)) return undefined;\n\n\treturn [original, ...original.querySelectorAll('*')].find(\n\t\t(element) => element.matches(interactive) || element.localName.includes('-'),\n\t);\n}\n\nfunction isContent(child: Node): boolean {\n\tif (child instanceof Element) return true;\n\n\treturn child instanceof Text && child.data.trim() !== '';\n}\n\ninterface Mirror {\n\tcontrol: Element;\n\tcopy: (original: ChildNode) => Node | undefined;\n\tisCopied?: (child: Node) => boolean;\n\tisPassed?: (child: Node) => boolean;\n\tplace: (copies: Array<Node>) => void;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\t#connection: AbortController | undefined;\n\n\t#slots: MirrorSlots | undefined;\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\tthis.#connection = connection;\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected checkStyles(control: HTMLElement, sheet: string): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst elementClass = this.constructor;\n\t\tconst checked = checkedSheets.get(elementClass) ?? new Set<string>();\n\t\tconst part = ({ selector }: StyleProbe): Element | undefined =>\n\t\t\tselector === undefined ? control : (this.querySelector(selector) ?? undefined);\n\t\t// An LED inherits `--_sonic-unit` from its control, so a token of its own probes it\n\t\tconst probes: Array<StyleProbe> = [\n\t\t\t{ sheet: 'material.css', token: '--_sonic-unlit' },\n\t\t\t{ sheet, token: '--_sonic-unit' },\n\t\t\t{ selector: '.sonic-led', sheet: 'led.css', token: '--_sonic-led-lens-ratio' },\n\t\t].filter((probe) => !checked.has(probe.sheet) && part(probe) !== undefined);\n\t\tif (probes.length === 0) return;\n\n\t\tcheckedSheets.set(elementClass, checked);\n\t\tfor (const probe of probes) checked.add(probe.sheet);\n\t\trequestAnimationFrame(() => {\n\t\t\tconst missing: Array<string> = [];\n\n\t\t\tfor (const probe of probes) {\n\t\t\t\tconst drawn = control.isConnected ? part(probe) : undefined;\n\n\t\t\t\tif (!drawn) checked.delete(probe.sheet);\n\t\t\t\telse if (getComputedStyle(drawn).getPropertyValue(probe.token) === '') {\n\t\t\t\t\tmissing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (missing.length === 0) return;\n\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> draws blank without ${missing.join(' and ')} (or controls.css)`,\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) writeLabelledBy(target, labels);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isBound(): boolean {\n\t\treturn this.#connection !== undefined;\n\t}\n\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A morph against server HTML, or a stray `innerHTML`, deletes the control\n\tprotected keepControl(control: Element, signal: AbortSignal): void {\n\t\tconst keep = (): void => {\n\t\t\tthis.#appendOnce(control);\n\t\t\tthis.#checkChildren([...this.childNodes].filter((child) => child !== control));\n\t\t};\n\t\tconst observer = new MutationObserver(keep);\n\n\t\tkeep();\n\t\tobserver.observe(this, { childList: true });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected mirrorChildren(\n\t\t{ control, copy, isCopied = () => true, isPassed = () => false, place }: Mirror,\n\t\tsignal: AbortSignal,\n\t): void {\n\t\tconst slots = this.#slots ?? attachSlots(this);\n\t\tlet copies = new Map<ChildNode, Node | undefined>();\n\n\t\tthis.#slots = slots;\n\t\tconst mirror = (touched?: Set<Node | undefined>): void => {\n\t\t\tthis.#appendOnce(control);\n\n\t\t\tconst children = [...this.childNodes].filter((child) => child !== control);\n\t\t\tconst slotted = children.filter(\n\t\t\t\t(child): child is Element | Text => child instanceof Element || child instanceof Text,\n\t\t\t);\n\t\t\tconst kept = copies;\n\t\t\tconst originals = children.filter((child) => isCopied(child));\n\n\t\t\tthis.#checkMirrored(originals);\n\t\t\tthis.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));\n\t\t\tcopies = new Map(\n\t\t\t\toriginals.map((original) => [\n\t\t\t\t\toriginal,\n\t\t\t\t\ttouched && !touched.has(original) && kept.has(original)\n\t\t\t\t\t\t? kept.get(original)\n\t\t\t\t\t\t: copy(original),\n\t\t\t\t]),\n\t\t\t);\n\t\t\tplace([...copies.values()].filter((made) => made !== undefined));\n\t\t\tslots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);\n\t\t\tslots.kept.assign(...slotted.filter((child) => isCopied(child)));\n\t\t};\n\t\t// Each child is watched, never the control, so a value write wakes nothing\n\t\tconst watch = (): void => {\n\t\t\tobserver.disconnect();\n\t\t\tobserver.observe(this, { childList: true });\n\t\t\tfor (const child of this.childNodes) {\n\t\t\t\tif (child === control) continue;\n\n\t\t\t\tobserver.observe(child, {\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tcharacterData: true,\n\t\t\t\t\tchildList: true,\n\t\t\t\t\tsubtree: true,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));\n\t\t\tif (changes.length === 0) return;\n\n\t\t\tif (changes.some((record) => record.target === this)) {\n\t\t\t\tmirror();\n\t\t\t\twatch();\n\t\t\t} else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));\n\t\t});\n\n\t\tmirror();\n\t\twatch();\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst parsed = toNumber(this.getAttribute(name));\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// Never compared; a host attribute set again resets the value a gesture left dirty\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (typeof value !== 'number' && typeof value !== 'string' && value !== true) {\n\t\t\tthis.removeAttribute(name);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAttribute(name, value === true ? '' : value.toString());\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.internals?.states.add(state);\n\t\telse this.internals?.states.delete(state);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\t#appendOnce(control: Element): void {\n\t\tconst [hook] = control.classList;\n\t\tconst stale = [...this.children].filter(\n\t\t\t(child) => child !== control && hook !== undefined && child.classList.contains(hook),\n\t\t);\n\n\t\tfor (const child of stale) child.remove();\n\t\tif (control.parentNode !== this) this.append(control);\n\t}\n\n\t#checkChildren(undocumented: Array<Node>): void {\n\t\tif (!__DEV__ || checkedChildren.has(this.constructor)) return;\n\n\t\tconst found = undocumented.find((child) => isContent(child));\n\t\tif (!found) return;\n\n\t\tcheckedChildren.add(this.constructor);\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> uses only the children it documents, so the ${\n\t\t\t\tfound instanceof Element ? `<${found.localName}>` : 'text'\n\t\t\t} inside it is unsupported`,\n\t\t);\n\t}\n\n\t#checkMirrored(originals: Array<Node>): void {\n\t\tif (!__DEV__ || checkedMirrors.has(this.constructor)) return;\n\n\t\tconst [found] = originals.flatMap((original) => unmirrorable(original) ?? []);\n\t\tif (!found) return;\n\n\t\tcheckedMirrors.add(this.constructor);\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`,\n\t\t);\n\t}\n\n\t#childHolding(node: Node): Node | undefined {\n\t\tlet current: Node | null = node;\n\n\t\twhile (current && current.parentNode !== this) current = current.parentNode;\n\n\t\treturn current ?? undefined;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#isCopiedChange(\n\t\trecord: MutationRecord,\n\t\tcontrol: Element,\n\t\tisCopied: (child: Node) => boolean,\n\t): boolean {\n\t\tif (record.target === this) {\n\t\t\treturn (\n\t\t\t\trecord.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control)\n\t\t\t);\n\t\t}\n\n\t\tconst child = this.#childHolding(record.target);\n\n\t\tif (child === undefined) return false;\n\n\t\treturn isCopied(child) || (record.type === 'attributes' && record.target === child);\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ import { SplitMode } from "../lib/distribute.js";
3
+ //#region src/elements/split.d.ts
4
+ declare global {
5
+ interface HTMLElementTagNameMap {
6
+ 'sonic-split': SonicSplit;
7
+ }
8
+ }
9
+ export declare class SonicSplit extends SonicElement {
10
+ #private;
11
+ static readonly observedAttributes: string[];
12
+ get mode(): SplitMode;
13
+ set mode(name: SplitMode | undefined);
14
+ get total(): number | undefined;
15
+ set total(value: number | undefined);
16
+ attributeChangedCallback(name: string): void;
17
+ connectedCallback(): void;
18
+ protected connect(signal: AbortSignal): void;
19
+ }
20
+ //#endregion
21
+ //# sourceMappingURL=split.d.ts.map