@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,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,4 +1,4 @@
1
- import { attachSlot } from "../lib/render.js";
1
+ import { attachSlots } from "../lib/render.js";
2
2
  import { writeAttribute } from "../lib/write-attribute.js";
3
3
 
4
4
  //#region src/elements/sonic-element.ts
@@ -22,7 +22,7 @@ var SonicElement = class extends HTMLElement {
22
22
  }
23
23
  internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
24
24
  #connection;
25
- #slot;
25
+ #slots;
26
26
  blur() {
27
27
  this.#focused()?.blur();
28
28
  }
@@ -56,8 +56,11 @@ var SonicElement = class extends HTMLElement {
56
56
  }
57
57
  if (!this.isDisabled() && !this.#focused()) target.focus(options);
58
58
  }
59
- appendOnce(child) {
60
- if (child.parentNode !== this) this.append(child);
59
+ appendOnce(control) {
60
+ const [hook] = control.classList;
61
+ const stale = [...this.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
62
+ for (const child of stale) child.remove();
63
+ if (control.parentNode !== this) this.append(control);
61
64
  }
62
65
  checkStyles(control, sheet) {}
63
66
  focusTarget() {}
@@ -73,17 +76,25 @@ var SonicElement = class extends HTMLElement {
73
76
  isDisabled() {
74
77
  return this.disabled || this.matches(":disabled");
75
78
  }
76
- mirrorChildren({ control, copy, isCopied = () => true }, signal) {
77
- const slot = this.#slot ?? attachSlot(this);
78
- this.#slot = slot;
79
- const mirror = () => {
80
- const children = [...this.childNodes].filter((child) => child !== control);
81
- copy(children.filter((child) => isCopied(child)));
79
+ mirrorChildren({ control, copy, isCopied = () => true, place }, signal) {
80
+ const slots = this.#slots ?? attachSlots(this);
81
+ let copies = /* @__PURE__ */ new Map();
82
+ this.#slots = slots;
83
+ const mirror = (touched) => {
82
84
  this.appendOnce(control);
83
- slot.assign(...children.filter((child) => (child instanceof Element || child instanceof Text) && !isCopied(child)), control);
85
+ const children = [...this.childNodes].filter((child) => child !== control);
86
+ const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
87
+ const kept = copies;
88
+ copies = new Map(children.filter((child) => isCopied(child)).map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
89
+ place([...copies.values()].filter((made) => made !== void 0));
90
+ slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
91
+ slots.kept.assign(...slotted.filter((child) => isCopied(child)));
84
92
  };
85
93
  const observer = new MutationObserver((records) => {
86
- if (records.some((record) => this.#isCopiedChange(record, control, isCopied))) mirror();
94
+ const changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));
95
+ if (changes.length === 0) return;
96
+ if (changes.some((record) => record.target === this)) mirror();
97
+ else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));
87
98
  });
88
99
  mirror();
89
100
  observer.observe(this, {
@@ -101,7 +112,7 @@ var SonicElement = class extends HTMLElement {
101
112
  }
102
113
  numberAttribute(name, fallback) {
103
114
  const attribute = this.getAttribute(name);
104
- if (attribute === null) return fallback;
115
+ if (attribute === null || attribute.trim() === "") return fallback;
105
116
  const parsed = Number(attribute);
106
117
  return Number.isFinite(parsed) ? parsed : fallback;
107
118
  }
@@ -0,0 +1,249 @@
1
+ import { SonicElement } from "./sonic-element.js";
2
+ import { SonicValueElement, linkValue } from "./value-element.js";
3
+ import { distribute, splitLimits } from "../lib/distribute.js";
4
+
5
+ //#region src/elements/split.ts
6
+ const modeNames = /* @__PURE__ */ new Set([
7
+ "cascade",
8
+ "equal",
9
+ "proportional"
10
+ ]);
11
+ const gridSlop = 1e-9;
12
+ const lockAttributes = ["data-sonic-locked", "disabled"];
13
+ function isMode(name) {
14
+ return name !== null && modeNames.has(name);
15
+ }
16
+ function isLocked(member) {
17
+ return "sonicLocked" in member.element.dataset || member.link.isDisabled();
18
+ }
19
+ function gridLimits(mapping, [low, high]) {
20
+ const bottom = mapping.snap(low);
21
+ const top = mapping.snap(high);
22
+ return [bottom < low - gridSlop ? mapping.keyTarget("ArrowRight", bottom) ?? bottom : bottom, top > high + gridSlop ? mapping.keyTarget("ArrowLeft", top) ?? top : top];
23
+ }
24
+ function modelOf(member, value, isFree) {
25
+ const mapping = member.link.mapping();
26
+ const [first, last] = mapping.bounds;
27
+ return {
28
+ isFree,
29
+ max: Math.max(first, last),
30
+ min: Math.min(first, last),
31
+ snap: mapping.snap,
32
+ stepFrom: (from, direction) => mapping.keyTarget(direction > 0 ? "ArrowRight" : "ArrowLeft", from) ?? from,
33
+ value
34
+ };
35
+ }
36
+ var SonicSplit = class extends SonicElement {
37
+ static observedAttributes = [
38
+ ...SonicElement.observedAttributes,
39
+ "mode",
40
+ "total"
41
+ ];
42
+ get mode() {
43
+ const name = this.getAttribute("mode");
44
+ return isMode(name) ? name : "proportional";
45
+ }
46
+ set mode(name) {
47
+ this.reflect("mode", name);
48
+ }
49
+ get total() {
50
+ return this.optionalNumberAttribute("total");
51
+ }
52
+ set total(value) {
53
+ this.reflect("total", value);
54
+ }
55
+ #isQueued = false;
56
+ #isTotalKept = false;
57
+ #isWarned = false;
58
+ #isWriting = false;
59
+ #members = [];
60
+ #signal;
61
+ #snapshot;
62
+ #total = 0;
63
+ #waiting = /* @__PURE__ */ new Set();
64
+ attributeChangedCallback(name) {
65
+ if ((name === "mode" || name === "total") && this.#signal) this.#bind();
66
+ }
67
+ connectedCallback() {
68
+ this.upgradeProperties("mode", "total");
69
+ super.connectedCallback();
70
+ }
71
+ connect(signal) {
72
+ const observer = new MutationObserver((records) => {
73
+ if (records.some((record) => record.attributeName !== null && lockAttributes.includes(record.attributeName)) || !this.#isSameMembers(this.#find())) this.#bind();
74
+ });
75
+ this.#signal = signal;
76
+ if (this.internals) this.internals.role = "group";
77
+ observer.observe(this, {
78
+ attributeFilter: lockAttributes,
79
+ attributes: true,
80
+ childList: true,
81
+ subtree: true
82
+ });
83
+ this.addEventListener("change", (event) => {
84
+ this.#release(event);
85
+ }, { signal });
86
+ signal.addEventListener("abort", () => {
87
+ observer.disconnect();
88
+ this.#signal = void 0;
89
+ this.#isTotalKept = false;
90
+ this.#unbind();
91
+ }, { once: true });
92
+ this.#bind();
93
+ }
94
+ #bind() {
95
+ const known = new Set(this.#members.map(({ element }) => element));
96
+ this.#unbind();
97
+ this.#members = this.#find().map((element) => this.#join(element));
98
+ const values = this.#members.map(({ seen }) => seen);
99
+ const sum = values.reduce((total, value) => total + value, 0);
100
+ this.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);
101
+ this.#isTotalKept = this.#members.length > 0;
102
+ if (this.#members.length > 0 && sum !== this.#total) this.#spread(values, known);
103
+ this.#limit();
104
+ this.#warnReach();
105
+ }
106
+ #compensate() {
107
+ this.#isQueued = false;
108
+ for (const mover of this.#members) if (mover.link.value() !== mover.seen) this.#move(mover);
109
+ }
110
+ #find() {
111
+ const found = [];
112
+ for (const element of this.querySelectorAll("*")) {
113
+ if (element.closest("sonic-split") !== this) continue;
114
+ if (element instanceof SonicValueElement) found.push(element);
115
+ else this.#waitFor(element.localName);
116
+ }
117
+ return found;
118
+ }
119
+ #isSameMembers(elements) {
120
+ return elements.length === this.#members.length && elements.every((element, index) => this.#members[index]?.element === element);
121
+ }
122
+ #join(element) {
123
+ const link = linkValue(element);
124
+ const unwatch = link.watch(() => {
125
+ if (this.#isWriting || this.#isQueued) return;
126
+ this.#isQueued = true;
127
+ queueMicrotask(() => {
128
+ if (this.#isQueued) this.#compensate();
129
+ });
130
+ });
131
+ link.land((target, direction) => this.#land(element, target, direction));
132
+ return {
133
+ element,
134
+ link,
135
+ seen: link.value(),
136
+ unwatch
137
+ };
138
+ }
139
+ #land(element, target, direction) {
140
+ if (this.#isQueued) this.#compensate();
141
+ const index = this.#members.findIndex((member) => member.element === element);
142
+ const mover = this.#members[index];
143
+ if (!mover) return target;
144
+ return distribute(this.#model(this.#members.map(({ seen }) => seen), mover), {
145
+ mode: this.mode,
146
+ total: this.#total
147
+ }, {
148
+ direction,
149
+ index,
150
+ target
151
+ })[index] ?? target;
152
+ }
153
+ #limit() {
154
+ const values = this.#members.map(({ seen }) => seen);
155
+ for (const [index, member] of this.#members.entries()) {
156
+ const model = this.#model(values, member);
157
+ const value = values[index] ?? member.seen;
158
+ member.link.limit(isLocked(member) ? [value, value] : gridLimits(member.link.mapping(), splitLimits(model, index, this.#total)));
159
+ }
160
+ }
161
+ #model(values, mover) {
162
+ return this.#members.map((member, index) => {
163
+ const isFixed = member !== mover && (isLocked(member) || member.link.isHeld());
164
+ return modelOf(member, values[index] ?? member.seen, !isFixed);
165
+ });
166
+ }
167
+ #move(mover) {
168
+ const index = this.#members.indexOf(mover);
169
+ const kept = this.#snapshot;
170
+ const snapshot = kept?.mover === mover && mover.link.isHeld() ? kept : {
171
+ mover,
172
+ values: this.#members.map(({ seen }) => seen)
173
+ };
174
+ this.#snapshot = snapshot;
175
+ this.#write(distribute(this.#model(snapshot.values, mover), {
176
+ mode: this.mode,
177
+ total: this.#total
178
+ }, {
179
+ index,
180
+ target: mover.link.value()
181
+ }), mover);
182
+ this.#limit();
183
+ }
184
+ #release(event) {
185
+ if (this.#isWriting) return;
186
+ if (this.#isQueued) this.#compensate();
187
+ const snapshot = this.#snapshot;
188
+ if (snapshot?.mover.element !== event.target) return;
189
+ this.#snapshot = void 0;
190
+ const changed = this.#members.filter((member, index) => member !== snapshot.mover && member.link.value() !== snapshot.values[index]);
191
+ queueMicrotask(() => {
192
+ this.#isWriting = true;
193
+ try {
194
+ for (const { element } of changed) element.dispatchEvent(new Event("change", { bubbles: true }));
195
+ } finally {
196
+ this.#isWriting = false;
197
+ }
198
+ });
199
+ }
200
+ #spread(values, known) {
201
+ const model = this.#model(values);
202
+ const joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));
203
+ const isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);
204
+ const spreadOver = model.map((member, index) => isAnyKnownFree && joined[index] === true ? {
205
+ ...member,
206
+ isFree: false
207
+ } : member);
208
+ this.#write(distribute(spreadOver, {
209
+ mode: this.mode,
210
+ total: this.#total
211
+ }));
212
+ }
213
+ #unbind() {
214
+ for (const { link, unwatch } of this.#members) {
215
+ unwatch();
216
+ link.land(void 0);
217
+ link.limit(void 0);
218
+ }
219
+ this.#members = [];
220
+ this.#snapshot = void 0;
221
+ this.#isQueued = false;
222
+ }
223
+ #waitFor(tag) {
224
+ if (!tag.includes("-") || customElements.get(tag) || this.#waiting.has(tag)) return;
225
+ this.#waiting.add(tag);
226
+ customElements.whenDefined(tag).then(() => {
227
+ this.#waiting.delete(tag);
228
+ if (this.#signal) this.#bind();
229
+ });
230
+ }
231
+ #warnReach() {}
232
+ #write(values, mover) {
233
+ this.#isWriting = true;
234
+ try {
235
+ for (const [index, member] of this.#members.entries()) {
236
+ const value = values[index];
237
+ if (value === void 0 || value === member.link.value()) continue;
238
+ member.link.limit(void 0);
239
+ member.link.input(value, member === mover);
240
+ }
241
+ } finally {
242
+ this.#isWriting = false;
243
+ }
244
+ for (const member of this.#members) member.seen = member.link.value();
245
+ }
246
+ };
247
+
248
+ //#endregion
249
+ export { SonicSplit };