@xsynaptic/sonic-ui 0.5.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 (118) hide show
  1. package/README.md +3 -1
  2. package/dist/default/dat.js +5 -4
  3. package/dist/default/define/button.js +1 -1
  4. package/dist/default/define.js +2 -2
  5. package/dist/default/elements/button.js +3 -3
  6. package/dist/default/elements/envelope.js +32 -16
  7. package/dist/default/elements/meter.js +11 -7
  8. package/dist/default/elements/number.js +1 -1
  9. package/dist/default/elements/radio-group.js +43 -45
  10. package/dist/default/elements/readout.js +1 -1
  11. package/dist/default/elements/sonic-element.js +56 -29
  12. package/dist/default/elements/switch.js +32 -38
  13. package/dist/default/elements/value-element.js +111 -108
  14. package/dist/default/elements/wave-element.js +5 -2
  15. package/dist/default/elements/waveform.js +35 -12
  16. package/dist/default/elements/wavestrip.js +18 -15
  17. package/dist/default/elements/xy.js +32 -18
  18. package/dist/default/index.js +2 -2
  19. package/dist/default/lib/adsr-shape.js +5 -0
  20. package/dist/default/lib/ballistics.js +2 -2
  21. package/dist/default/lib/canvas-surface.js +13 -2
  22. package/dist/default/lib/distribute.js +1 -1
  23. package/dist/default/lib/drag-step.js +4 -4
  24. package/dist/default/lib/frame-timeline.js +2 -1
  25. package/dist/default/lib/marker-band.js +54 -10
  26. package/dist/default/lib/marker-rider.js +10 -4
  27. package/dist/default/lib/math.js +6 -1
  28. package/dist/default/lib/pointer-drag.js +8 -5
  29. package/dist/default/lib/resample-peaks.js +11 -1
  30. package/dist/default/lib/write-attribute.js +1 -0
  31. package/dist/dev/dat.d.ts +2 -2
  32. package/dist/dev/dat.js +5 -4
  33. package/dist/dev/dat.js.map +1 -1
  34. package/dist/dev/define/button.js +1 -1
  35. package/dist/dev/define.d.ts +4 -4
  36. package/dist/dev/define.js +2 -2
  37. package/dist/dev/elements/button.js +3 -3
  38. package/dist/dev/elements/button.js.map +1 -1
  39. package/dist/dev/elements/dial.d.ts +1 -1
  40. package/dist/dev/elements/dial.js.map +1 -1
  41. package/dist/dev/elements/envelope.js +32 -16
  42. package/dist/dev/elements/envelope.js.map +1 -1
  43. package/dist/dev/elements/meter.d.ts +1 -1
  44. package/dist/dev/elements/meter.js +11 -7
  45. package/dist/dev/elements/meter.js.map +1 -1
  46. package/dist/dev/elements/number.js +1 -1
  47. package/dist/dev/elements/number.js.map +1 -1
  48. package/dist/dev/elements/radio-group.js +43 -45
  49. package/dist/dev/elements/radio-group.js.map +1 -1
  50. package/dist/dev/elements/readout.js +1 -1
  51. package/dist/dev/elements/readout.js.map +1 -1
  52. package/dist/dev/elements/slider.d.ts +1 -1
  53. package/dist/dev/elements/slider.js.map +1 -1
  54. package/dist/dev/elements/sonic-element.d.ts +4 -2
  55. package/dist/dev/elements/sonic-element.js +79 -29
  56. package/dist/dev/elements/sonic-element.js.map +1 -1
  57. package/dist/dev/elements/switch.js +32 -38
  58. package/dist/dev/elements/switch.js.map +1 -1
  59. package/dist/dev/elements/value-element.d.ts +3 -3
  60. package/dist/dev/elements/value-element.js +111 -108
  61. package/dist/dev/elements/value-element.js.map +1 -1
  62. package/dist/dev/elements/wave-element.d.ts +1 -0
  63. package/dist/dev/elements/wave-element.js +5 -2
  64. package/dist/dev/elements/wave-element.js.map +1 -1
  65. package/dist/dev/elements/waveform.d.ts +1 -1
  66. package/dist/dev/elements/waveform.js +35 -12
  67. package/dist/dev/elements/waveform.js.map +1 -1
  68. package/dist/dev/elements/wavestrip.js +18 -15
  69. package/dist/dev/elements/wavestrip.js.map +1 -1
  70. package/dist/dev/elements/xy.d.ts +3 -3
  71. package/dist/dev/elements/xy.js +32 -18
  72. package/dist/dev/elements/xy.js.map +1 -1
  73. package/dist/dev/index.d.ts +2 -2
  74. package/dist/dev/index.js +2 -2
  75. package/dist/dev/lib/adsr-shape.js +5 -0
  76. package/dist/dev/lib/adsr-shape.js.map +1 -1
  77. package/dist/dev/lib/ballistics.js +2 -2
  78. package/dist/dev/lib/ballistics.js.map +1 -1
  79. package/dist/dev/lib/canvas-surface.d.ts +1 -0
  80. package/dist/dev/lib/canvas-surface.js +13 -2
  81. package/dist/dev/lib/canvas-surface.js.map +1 -1
  82. package/dist/dev/lib/distribute.js +1 -1
  83. package/dist/dev/lib/distribute.js.map +1 -1
  84. package/dist/dev/lib/drag-step.js +4 -4
  85. package/dist/dev/lib/drag-step.js.map +1 -1
  86. package/dist/dev/lib/frame-timeline.js +2 -1
  87. package/dist/dev/lib/frame-timeline.js.map +1 -1
  88. package/dist/dev/lib/marker-band.js +54 -10
  89. package/dist/dev/lib/marker-band.js.map +1 -1
  90. package/dist/dev/lib/marker-rider.js +10 -4
  91. package/dist/dev/lib/marker-rider.js.map +1 -1
  92. package/dist/dev/lib/math.js +6 -1
  93. package/dist/dev/lib/math.js.map +1 -1
  94. package/dist/dev/lib/pointer-drag.js +8 -5
  95. package/dist/dev/lib/pointer-drag.js.map +1 -1
  96. package/dist/dev/lib/resample-peaks.js +11 -1
  97. package/dist/dev/lib/resample-peaks.js.map +1 -1
  98. package/dist/dev/lib/write-attribute.js +1 -0
  99. package/dist/dev/lib/write-attribute.js.map +1 -1
  100. package/dist/skins/amber.css +8 -0
  101. package/dist/skins/flat.css +25 -0
  102. package/dist/skins/ivory.css +1 -0
  103. package/dist/skins/lime.css +2 -0
  104. package/dist/skins/slate.css +2 -0
  105. package/dist/styles/button.css +38 -10
  106. package/dist/styles/dial.css +24 -16
  107. package/dist/styles/envelope.css +56 -11
  108. package/dist/styles/led.css +3 -3
  109. package/dist/styles/material.css +83 -41
  110. package/dist/styles/meter.css +8 -4
  111. package/dist/styles/panel.css +5 -3
  112. package/dist/styles/segmented.css +27 -5
  113. package/dist/styles/slider.css +2 -1
  114. package/dist/styles/switch.css +59 -20
  115. package/dist/styles/waveform.css +28 -5
  116. package/dist/styles/wavestrip.css +23 -6
  117. package/dist/styles/xy.css +61 -24
  118. package/package.json +2 -5
@@ -2,7 +2,7 @@ import { clampProportion } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
3
  import { readPxProperty } from "../lib/read-px-property.js";
4
4
  import { SonicWaveElement } from "./wave-element.js";
5
- import { createMarkerBand, nearestMarker } from "../lib/marker-band.js";
5
+ import { createMarkerBand } from "../lib/marker-band.js";
6
6
  import { stripBars, stripRegions } from "../lib/strip-scene.js";
7
7
 
8
8
  //#region src/elements/wavestrip.ts
@@ -101,7 +101,7 @@ var SonicWavestrip = class extends SonicWaveElement {
101
101
  ]);
102
102
  }
103
103
  renderMarkers() {
104
- this.#markerBand.render(this.markers, this.markerProportions());
104
+ this.#markerBand.render(this.markerList(), this.markerProportions());
105
105
  }
106
106
  scrubsKeyRepeat() {
107
107
  return true;
@@ -111,6 +111,7 @@ var SonicWavestrip = class extends SonicWaveElement {
111
111
  return {
112
112
  position: (event) => event.clientX,
113
113
  startPx: box.left,
114
+ topPx: box.top,
114
115
  travelPx: Math.max(1, box.width)
115
116
  };
116
117
  }
@@ -126,10 +127,11 @@ var SonicWavestrip = class extends SonicWaveElement {
126
127
  #bindHover(strip, signal) {
127
128
  strip.addEventListener("pointermove", (event) => {
128
129
  if (event.pointerType === "touch" || event.buttons !== 0) return;
129
- if (!this.readout || this.isDisabled()) return;
130
+ if (!this.readout) return;
130
131
  const axis = this.#axis();
131
132
  const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
132
- this.hoverReadout(this.#markerAt(at, axis.travelPx) ?? this.mapping().valueAt(at));
133
+ const mapping = this.mapping();
134
+ this.hoverReadout(this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at)));
133
135
  this.#placeReadout();
134
136
  }, { signal });
135
137
  for (const type of ["pointerdown", "pointerleave"]) strip.addEventListener(type, () => {
@@ -137,25 +139,26 @@ var SonicWavestrip = class extends SonicWaveElement {
137
139
  }, { signal });
138
140
  }
139
141
  #grab(event) {
140
- const { startPx, ...axis } = this.#axis();
141
- const at = (event.clientX - startPx) / axis.travelPx;
142
+ const axis = this.#axis();
143
+ const at = (event.clientX - axis.startPx) / axis.travelPx;
142
144
  const outside = this.#outside();
143
145
  const grabbed = {
144
- ...axis,
145
- fromProportion: clampProportion(at)
146
+ fromProportion: clampProportion(at),
147
+ position: axis.position,
148
+ travelPx: axis.travelPx
146
149
  };
147
- this.input(this.#markerAt(at, axis.travelPx) ?? this.mapping().valueAt(at));
150
+ this.input(this.#markerAt(event, axis) ?? this.mapping().valueAt(at));
148
151
  return outside ? {
149
152
  ...grabbed,
150
153
  outside
151
154
  } : grabbed;
152
155
  }
153
- #markerAt(at, travelPx) {
154
- const markers = this.markers;
155
- if (markers.length === 0) return void 0;
156
- const { proportionOf } = this.mapping();
157
- const index = nearestMarker(at, markers.map(({ start }) => proportionOf(start)), this.#markerBand.reachPx() / travelPx);
158
- return index === void 0 ? void 0 : markers[index]?.start;
156
+ #markerAt(event, { startPx, topPx, travelPx }) {
157
+ const index = this.#markerBand.at({
158
+ x: event.clientX - startPx,
159
+ y: event.clientY - topPx
160
+ }, travelPx);
161
+ return index === void 0 ? void 0 : this.markerList()[index]?.start;
159
162
  }
160
163
  #outside() {
161
164
  if (!this.cancellable) return void 0;
@@ -1,4 +1,4 @@
1
- import { clampProportion } from "../lib/math.js";
1
+ import { clampProportion, toNumber } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
3
  import { writeAttribute } from "../lib/write-attribute.js";
4
4
  import { SonicFormElement } from "./form-element.js";
@@ -7,9 +7,9 @@ import { Readout } from "./readout.js";
7
7
  import { dragThresholdPx } from "../lib/drag-step.js";
8
8
  import { focusByPointer } from "../lib/focus-by-pointer.js";
9
9
  import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
10
+ import { bindDrag } from "../lib/pointer-drag.js";
10
11
  import { resetKeys, valueMapping } from "../lib/value-mapping.js";
11
12
  import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
12
- import { bindDrag } from "../lib/pointer-drag.js";
13
13
 
14
14
  //#region src/elements/xy.ts
15
15
  const axes = ["x", "y"];
@@ -193,6 +193,7 @@ var SonicXy = class extends SonicFormElement {
193
193
  #xy = renderXy();
194
194
  #field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
195
195
  #formatValue;
196
+ #mapped;
196
197
  #parts = {
197
198
  x: requireChild(this.#xy, "[data-sonic-axis=\"x\"]", HTMLDivElement),
198
199
  y: requireChild(this.#xy, "[data-sonic-axis=\"y\"]", HTMLDivElement)
@@ -202,18 +203,19 @@ var SonicXy = class extends SonicFormElement {
202
203
  #readout = new Readout(requireChild(this.#xy, ".sonic-xy-readout", HTMLDivElement));
203
204
  #values = {
204
205
  x: {
205
- isDirty: false,
206
+ asked: void 0,
206
207
  value: 0
207
208
  },
208
209
  y: {
209
- isDirty: false,
210
+ asked: void 0,
210
211
  value: 0
211
212
  }
212
213
  };
213
214
  attributeChangedCallback(name) {
215
+ this.#mapped = void 0;
214
216
  if (name === "x" || name === "y") {
215
217
  if (this.#pointerDrag?.current()) return;
216
- this.#values[name].isDirty = false;
218
+ this.#values[name].asked = void 0;
217
219
  }
218
220
  if (name === "disabled" && this.isDisabled()) this.#pointerDrag?.end();
219
221
  this.#refresh();
@@ -223,12 +225,12 @@ var SonicXy = class extends SonicFormElement {
223
225
  super.connectedCallback();
224
226
  }
225
227
  formResetCallback() {
226
- for (const axis of axes) this.#values[axis].isDirty = false;
228
+ for (const axis of axes) this.#values[axis].asked = void 0;
227
229
  this.#refresh();
228
230
  }
229
231
  connect(signal) {
230
232
  const xy = this.#xy;
231
- this.appendOnce(xy);
233
+ this.keepControl(xy, signal);
232
234
  this.render();
233
235
  this.checkStyles(xy, "xy.css");
234
236
  this.#bindPointer(xy, signal);
@@ -238,6 +240,7 @@ var SonicXy = class extends SonicFormElement {
238
240
  return this.#parts[this.#currentAxis];
239
241
  }
240
242
  render() {
243
+ if (!this.isBound()) return;
241
244
  const { style } = this.#xy;
242
245
  const mappings = this.#mappings();
243
246
  for (const axis of axes) {
@@ -365,7 +368,7 @@ var SonicXy = class extends SonicFormElement {
365
368
  return true;
366
369
  }
367
370
  #keyTo(axis, key) {
368
- const next = this.#mapping(axis).keyTarget(key, this.#values[axis].value);
371
+ const next = this.#mappings()[axis].keyTarget(key, this.#values[axis].value);
369
372
  const hasMoved = this.#set(axis, next ?? NaN);
370
373
  this.#currentAxis = axis;
371
374
  this.render();
@@ -390,17 +393,19 @@ var SonicXy = class extends SonicFormElement {
390
393
  });
391
394
  }
392
395
  #mappings() {
393
- return {
396
+ if (this.#mapped) return this.#mapped;
397
+ this.#mapped = {
394
398
  x: this.#mapping("x"),
395
399
  y: this.#mapping("y")
396
400
  };
401
+ return this.#mapped;
397
402
  }
398
403
  #refresh() {
399
404
  const mappings = this.#mappings();
400
405
  for (const axis of axes) {
401
406
  const axisValue = this.#values[axis];
402
407
  const mapping = mappings[axis];
403
- axisValue.value = mapping.snap(axisValue.isDirty ? axisValue.value : this.numberAttribute(axis, mapping.bounds[0]));
408
+ axisValue.value = mapping.snap(axisValue.asked ?? this.numberAttribute(axis, mapping.bounds[0]));
404
409
  }
405
410
  this.render();
406
411
  }
@@ -412,11 +417,11 @@ var SonicXy = class extends SonicFormElement {
412
417
  const [low, high] = mappings[axis].bounds;
413
418
  const isCurrent = axis === this.#currentAxis;
414
419
  const stop = isCurrent ? "0" : "-1";
415
- part.setAttribute("aria-orientation", orientations[axis]);
416
- part.setAttribute("aria-valuemin", String(low));
417
- part.setAttribute("aria-valuemax", String(high));
418
- part.setAttribute("aria-valuenow", String(this.#values[axis].value));
419
- part.setAttribute("aria-valuetext", this.#valueText(axis));
420
+ writeAttribute(part, "aria-orientation", orientations[axis]);
421
+ writeAttribute(part, "aria-valuemin", String(low));
422
+ writeAttribute(part, "aria-valuemax", String(high));
423
+ writeAttribute(part, "aria-valuenow", String(this.#values[axis].value));
424
+ writeAttribute(part, "aria-valuetext", this.#valueText(axis));
420
425
  this.forwardNaming(part, true);
421
426
  writeAttribute(part, "aria-hidden", isCurrent ? void 0 : "true");
422
427
  writeAttribute(part, "aria-disabled", isDisabled ? "true" : void 0);
@@ -451,8 +456,8 @@ var SonicXy = class extends SonicFormElement {
451
456
  #set(axis, next) {
452
457
  if (!Number.isFinite(next)) return false;
453
458
  const axisValue = this.#values[axis];
454
- const snapped = this.#mapping(axis).snap(next);
455
- axisValue.isDirty = true;
459
+ const snapped = this.#mappings()[axis].snap(next);
460
+ axisValue.asked = snapped;
456
461
  if (snapped === axisValue.value) return false;
457
462
  axisValue.value = snapped;
458
463
  return true;
@@ -465,7 +470,16 @@ var SonicXy = class extends SonicFormElement {
465
470
  }
466
471
  #write(axis, next) {
467
472
  if (this.#pointerDrag?.current()) return;
468
- if (this.#set(axis, next)) this.render();
473
+ const axisValue = this.#values[axis];
474
+ if (next === void 0 || next === null) {
475
+ axisValue.asked = void 0;
476
+ this.#refresh();
477
+ return;
478
+ }
479
+ const asked = toNumber(next);
480
+ const hasMoved = this.#set(axis, asked);
481
+ if (Number.isFinite(asked)) axisValue.asked = asked;
482
+ if (hasMoved) this.render();
469
483
  }
470
484
  };
471
485
 
@@ -1,13 +1,13 @@
1
+ import { SonicButton } from "./elements/button.js";
1
2
  import { SonicDial } from "./elements/dial.js";
2
3
  import { envelopeCurve } from "./lib/envelope-curve.js";
3
4
  import { SonicEnvelope } from "./elements/envelope.js";
4
- import { SonicButton } from "./elements/button.js";
5
- import { SonicSwitch } from "./elements/switch.js";
6
5
  import { SonicMeter } from "./elements/meter.js";
7
6
  import { SonicNumber } from "./elements/number.js";
8
7
  import { SonicSegmented } from "./elements/segmented.js";
9
8
  import { SonicSlider } from "./elements/slider.js";
10
9
  import { SonicSplit } from "./elements/split.js";
10
+ import { SonicSwitch } from "./elements/switch.js";
11
11
  import { SonicWaveform } from "./elements/waveform.js";
12
12
  import { SonicWavestrip } from "./elements/wavestrip.js";
13
13
  import { SonicXy } from "./elements/xy.js";
@@ -98,8 +98,13 @@ function adsrShape(proportions, curves = {}) {
98
98
  }
99
99
  function adsrPath({ points }) {
100
100
  const stroke = points.map(({ x, y }, index) => `${index === 0 ? "M" : "L"}${String(roundTo(x, 4))} ${String(roundTo(1 - y, 4))}`).join("");
101
+ const floor = {
102
+ from: roundTo(points[0]?.x ?? 0, 4),
103
+ to: roundTo(points.at(-1)?.x ?? 0, 4)
104
+ };
101
105
  return {
102
106
  fill: `${stroke}Z`,
107
+ floor,
103
108
  stroke
104
109
  };
105
110
  }
@@ -3,13 +3,13 @@ const burstTimeConstants = -Math.log(1 - 10 ** (-2 / 20));
3
3
  function ppmRate({ burstMs, fallDecibels, fallSeconds }) {
4
4
  return {
5
5
  fallDecibelsPerSecond: fallDecibels / fallSeconds,
6
- riseMs: burstMs / burstTimeConstants
6
+ riseTimeConstantMs: burstMs / burstTimeConstants
7
7
  };
8
8
  }
9
9
  const ballisticsRates = {
10
10
  peak: {
11
11
  fallDecibelsPerSecond: 20,
12
- riseMs: 0
12
+ riseTimeConstantMs: 0
13
13
  },
14
14
  "ppm-1": ppmRate({
15
15
  burstMs: 5,
@@ -69,12 +69,22 @@ var CanvasSurface = class {
69
69
  style.setProperty("transition-timing-function", "step-start");
70
70
  style.setProperty("transition-behavior", "allow-discrete");
71
71
  }
72
+ #current() {
73
+ if (this.#look && !this.#isRebuildWanted) return {
74
+ isRebuilt: false,
75
+ look: this.#look
76
+ };
77
+ return {
78
+ isRebuilt: true,
79
+ look: this.#read()
80
+ };
81
+ }
72
82
  #draw(frameMs) {
73
83
  this.#frame = void 0;
74
84
  const size = this.#size;
75
85
  if (size.width === 0 || size.height === 0) return;
76
86
  const { canvas, paint } = this.#options;
77
- const look = this.#isRebuildWanted || !this.#look ? this.#read() : this.#look;
87
+ const { isRebuilt, look } = this.#current();
78
88
  const isDirty = this.#isDirty;
79
89
  this.#look = look;
80
90
  this.#isDirty = false;
@@ -84,6 +94,7 @@ var CanvasSurface = class {
84
94
  if (this.#context) paint(this.#context, {
85
95
  frameMs,
86
96
  isDirty,
97
+ isRebuilt,
87
98
  look,
88
99
  size
89
100
  });
@@ -140,7 +151,7 @@ var CanvasSurface = class {
140
151
  const { canvas, colours, lengths = {} } = this.#options;
141
152
  const styles = getComputedStyle(canvas);
142
153
  return {
143
- colours: readEach(colours, (property) => styles.getPropertyValue(property).trim()),
154
+ colours: readEach(colours, (property) => styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color)),
144
155
  isReducedMotion: this.#reducedMotion.matches,
145
156
  lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0))
146
157
  };
@@ -77,7 +77,7 @@ function byShortfall(values, { exact, order }, direction) {
77
77
  const value = values[index] ?? NaN;
78
78
  return trimFloat(direction * ((exact.get(index) ?? value) - value));
79
79
  };
80
- return order.toReversed().sort((first, second) => shortfall(second) - shortfall(first));
80
+ return order.toReversed().toSorted((first, second) => shortfall(second) - shortfall(first));
81
81
  }
82
82
  function stepEach(members, values, pass) {
83
83
  let remainder = pass.owed;
@@ -10,18 +10,18 @@ function dragThresholdPx(pointerType) {
10
10
  function isAcross(from, to, proportion) {
11
11
  return from !== proportion && (from - proportion) * (to - proportion) <= 0;
12
12
  }
13
- function passDetent(hold, from, to) {
13
+ function passDetent(hold, [from, to], pace) {
14
14
  if (hold.slack === void 0 && !isAcross(from, to, hold.proportion)) return {
15
15
  proportion: to,
16
16
  slack: void 0
17
17
  };
18
- const slack = (hold.slack ?? 0) + to - hold.proportion;
18
+ const slack = (hold.slack ?? 0) + (to - hold.proportion) / pace;
19
19
  if (Math.abs(slack) <= hold.zone) return {
20
20
  proportion: void 0,
21
21
  slack
22
22
  };
23
23
  return {
24
- proportion: hold.proportion + slack - Math.sign(slack) * hold.zone,
24
+ proportion: hold.proportion + (slack - Math.sign(slack) * hold.zone) * pace,
25
25
  slack: void 0
26
26
  };
27
27
  }
@@ -77,7 +77,7 @@ function stepDrag(mapping, state, move) {
77
77
  };
78
78
  const { detent } = state;
79
79
  if (!detent) return land(mapping, moved, to);
80
- const passed = passDetent(mapping.isWrapping ? nearestTurn(detent, state.rawProportion) : detent, state.rawProportion, to);
80
+ const passed = passDetent(mapping.isWrapping ? nearestTurn(detent, state.rawProportion) : detent, [state.rawProportion, to], pace);
81
81
  const held = {
82
82
  ...moved,
83
83
  detent: {
@@ -2,6 +2,7 @@ import { trimFloat } from "./math.js";
2
2
 
3
3
  //#region src/lib/frame-timeline.ts
4
4
  const placeholderPeriodMs = 2500;
5
+ const lookAheadSeconds = 30;
5
6
  function windowStart(input, seconds, windowSeconds) {
6
7
  if (!input.isPaged) return seconds - windowSeconds / 2;
7
8
  if (input.held) return input.held.grabbed.startSeconds + seconds - input.held.grabbed.seconds;
@@ -31,7 +32,7 @@ function frameTimeline(input) {
31
32
  const pending = pendingIn(input, [startSeconds, startSeconds + windowSeconds]);
32
33
  const placeholder = placeholderPhase(input, pending);
33
34
  const phase = placeholder ?? 0;
34
- const wanted = overlap([startSeconds - windowSeconds, startSeconds + 2 * windowSeconds], range);
35
+ const wanted = overlap([startSeconds - windowSeconds, startSeconds + windowSeconds + Math.max(windowSeconds, lookAheadSeconds)], range);
35
36
  const at = (time) => (time - startSeconds) / windowSeconds;
36
37
  return {
37
38
  ...held ? { ghostAt: at(clockSeconds) } : {},
@@ -3,28 +3,72 @@ import { readPxProperty } from "./read-px-property.js";
3
3
  //#region src/lib/marker-band.ts
4
4
  const kindPattern = /^[a-z][a-z0-9-]*$/;
5
5
  const snapSlopPx = 4;
6
- function renderMarker(marker, [from, to]) {
6
+ function isKind(kind) {
7
+ return kind !== void 0 && kindPattern.test(kind);
8
+ }
9
+ function writeKind(part, kind, fallback) {
10
+ if (!isKind(kind)) {
11
+ part.style.removeProperty("--_sonic-marker");
12
+ return;
13
+ }
14
+ part.style.setProperty("--_sonic-marker", `var(--sonic-marker-${kind}, var(${fallback}))`);
15
+ }
16
+ function renderMarker(marker, [from, to], lane) {
7
17
  const part = document.createElement("div");
8
- part.className = marker.end === void 0 ? "sonic-wavestrip-marker" : "sonic-wavestrip-region";
18
+ part.className = lane === void 0 ? "sonic-wavestrip-region" : "sonic-wavestrip-marker";
19
+ if (lane !== void 0) part.style.setProperty("--_sonic-marker-lane", String(lane));
9
20
  part.style.setProperty("--_sonic-marker-from", String(from));
10
21
  part.style.setProperty("--_sonic-marker-to", String(to));
11
- if (marker.kind !== void 0 && kindPattern.test(marker.kind)) part.style.setProperty("--_sonic-marker", `var(--sonic-marker-${marker.kind}, var(--_sonic-marker-default))`);
22
+ writeKind(part, marker.kind, "--_sonic-marker-default");
12
23
  if (marker.dimmed === true) part.dataset.sonicDimmed = "";
13
24
  return part;
14
25
  }
15
26
  function createMarkerBand(band) {
27
+ let dots = [];
28
+ const px = (property) => readPxProperty(getComputedStyle(band), property, 0);
16
29
  return {
17
- reachPx: () => readPxProperty(getComputedStyle(band), "--_sonic-marker-size", 0) / 2 + snapSlopPx,
30
+ at: ({ x, y }, widthPx) => {
31
+ const size = px("--_sonic-marker-size");
32
+ const step = px("--_sonic-marker-step");
33
+ const nearest = nearestDot({
34
+ x,
35
+ y
36
+ }, dots.map(({ at, lane }) => ({
37
+ x: at * widthPx,
38
+ y: size / 2 + lane * step
39
+ })), size / 2 + snapSlopPx);
40
+ return nearest === void 0 ? void 0 : dots[nearest]?.index;
41
+ },
18
42
  render: (markers, proportions) => {
19
- band.replaceChildren(...markers.map((marker, index) => renderMarker(marker, proportions[index] ?? [0, 0])));
43
+ const widthPx = band.getBoundingClientRect().width;
44
+ const points = markers.flatMap((marker, index) => marker.end === void 0 ? [{
45
+ at: proportions[index]?.[0] ?? 0,
46
+ index
47
+ }] : []);
48
+ const lanes = markerLanes(points.map(({ at }) => at * widthPx), px("--_sonic-marker-step"));
49
+ dots = points.map((point, order) => ({
50
+ ...point,
51
+ lane: lanes[order] ?? 0
52
+ }));
53
+ const laneOf = new Map(dots.map(({ index, lane }) => [index, lane]));
54
+ band.replaceChildren(...markers.map((marker, index) => renderMarker(marker, proportions[index] ?? [0, 0], laneOf.get(index))));
20
55
  }
21
56
  };
22
57
  }
23
- function nearestMarker(proportion, markerProportions, reach) {
58
+ function markerLanes(positions, minDistance) {
59
+ const lastInLane = [];
60
+ return positions.map((position) => {
61
+ const free = lastInLane.findIndex((last) => position - last >= minDistance);
62
+ const lane = free === -1 ? lastInLane.length : free;
63
+ lastInLane[lane] = position;
64
+ return lane;
65
+ });
66
+ }
67
+ function nearestDot(point, dots, reach) {
24
68
  let nearest;
25
- let nearestDistance = reach;
26
- for (const [index, markerProportion] of markerProportions.entries()) {
27
- const distance = Math.abs(markerProportion - proportion);
69
+ let nearestDistance = reach * reach;
70
+ for (const [index, dot] of dots.entries()) {
71
+ const distance = (dot.x - point.x) ** 2 + (dot.y - point.y) ** 2;
28
72
  if (distance > nearestDistance) continue;
29
73
  nearest = index;
30
74
  nearestDistance = distance;
@@ -33,4 +77,4 @@ function nearestMarker(proportion, markerProportions, reach) {
33
77
  }
34
78
 
35
79
  //#endregion
36
- export { createMarkerBand, nearestMarker };
80
+ export { createMarkerBand, isKind, markerLanes, nearestDot, writeKind };
@@ -1,6 +1,7 @@
1
1
  import { clamp } from "./math.js";
2
2
  import { requireChild } from "./render.js";
3
3
  import { readPxProperty } from "./read-px-property.js";
4
+ import { writeKind } from "./marker-band.js";
4
5
 
5
6
  //#region src/lib/marker-rider.ts
6
7
  const fadeShare = .25;
@@ -43,6 +44,7 @@ function layoutRider(labels, view) {
43
44
  function labelSlot(element) {
44
45
  return {
45
46
  element,
47
+ kind: void 0,
46
48
  opacity: NaN,
47
49
  shownPx: NaN,
48
50
  x: NaN
@@ -63,13 +65,17 @@ function writePlacement(slot, { opacity, shownPx, x }, widthPx) {
63
65
  style.setProperty("opacity", opacity < 1 ? opacity.toFixed(3) : "");
64
66
  }
65
67
  }
66
- function writeLabel(slot, labels, placement) {
68
+ function writeLabel(slot, [labels, colourProperty], placement) {
67
69
  const { element } = slot;
68
70
  const label = placement && placement.shownPx > 0 ? labels[placement.index] : void 0;
69
71
  if (element.hidden !== !label) element.hidden = !label;
70
72
  if (!label || !placement) return;
71
73
  if (element.textContent !== label.text) element.textContent = label.text;
72
74
  element.toggleAttribute("data-sonic-dimmed", label.isDimmed);
75
+ if (slot.kind !== label.kind) {
76
+ slot.kind = label.kind;
77
+ writeKind(element, label.kind, colourProperty);
78
+ }
73
79
  writePlacement(slot, placement, label.widthPx);
74
80
  }
75
81
  function measureWidths(control, className, texts) {
@@ -85,7 +91,7 @@ function measureWidths(control, className, texts) {
85
91
  return widths;
86
92
  }
87
93
  function createLabelRider(control, options) {
88
- const { className, insetProperty } = options;
94
+ const { className, colourProperty, insetProperty } = options;
89
95
  const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
90
96
  const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
91
97
  let insetPx = 0;
@@ -104,8 +110,8 @@ function createLabelRider(control, options) {
104
110
  ...window,
105
111
  insetPx
106
112
  });
107
- writeLabel(parked, measured, layout.parked);
108
- writeLabel(arriving, measured, layout.arriving);
113
+ writeLabel(parked, [measured, colourProperty], layout.parked);
114
+ writeLabel(arriving, [measured, colourProperty], layout.arriving);
109
115
  }
110
116
  };
111
117
  }
@@ -2,6 +2,11 @@
2
2
  function clamp(value, min, max) {
3
3
  return Math.min(max, Math.max(min, value));
4
4
  }
5
+ function toNumber(value) {
6
+ if (typeof value === "number") return value;
7
+ if (typeof value !== "string" || value.trim() === "") return NaN;
8
+ return Number(value);
9
+ }
5
10
  function clampProportion(fraction) {
6
11
  return clamp(fraction, 0, 1);
7
12
  }
@@ -22,4 +27,4 @@ function roundTo(value, places) {
22
27
  }
23
28
 
24
29
  //#endregion
25
- export { clamp, clampProportion, decimalPlaces, roundTo, trimFloat, wrapProportion };
30
+ export { clamp, clampProportion, decimalPlaces, roundTo, toNumber, trimFloat, wrapProportion };
@@ -5,8 +5,8 @@ function bindDrag(target, handlers, signal) {
5
5
  if (!held) return;
6
6
  const { drag } = held;
7
7
  held = void 0;
8
- handlers.toggle(false);
9
- handlers.release(drag);
8
+ handlers.toggle?.(false);
9
+ handlers.release?.(drag);
10
10
  };
11
11
  const whileHeld = (type, handle) => {
12
12
  target.addEventListener(type, (event) => {
@@ -23,15 +23,18 @@ function bindDrag(target, handlers, signal) {
23
23
  pointerId: event.pointerId
24
24
  };
25
25
  target.setPointerCapture(event.pointerId);
26
- handlers.toggle(true);
26
+ handlers.toggle?.(true);
27
27
  }, { signal });
28
28
  whileHeld("pointermove", handlers.move);
29
29
  whileHeld("pointercancel", (drag) => {
30
- handlers.cancel(drag);
30
+ handlers.cancel?.(drag);
31
31
  end();
32
32
  });
33
33
  whileHeld("lostpointercapture", end);
34
- whileHeld("pointerup", end);
34
+ whileHeld("pointerup", (drag, event) => {
35
+ end();
36
+ handlers.lift?.(drag, event);
37
+ });
35
38
  signal.addEventListener("abort", end, { once: true });
36
39
  return {
37
40
  current: () => held?.drag,
@@ -11,7 +11,17 @@ function downsample(peaks, count) {
11
11
  for (let source = start; source < end; source += 1) total += peaks[source] ?? 0;
12
12
  resampled.push(total / (end - start));
13
13
  }
14
- return resampled;
14
+ return rescale(resampled, peakOf(peaks));
15
+ }
16
+ function peakOf(peaks) {
17
+ let loudest = 0;
18
+ for (const peak of peaks) loudest = Math.max(loudest, peak);
19
+ return loudest;
20
+ }
21
+ function rescale(bars, loudest) {
22
+ const tallest = peakOf(bars);
23
+ if (tallest <= 0) return bars;
24
+ return bars.map((bar) => bar * loudest / tallest);
15
25
  }
16
26
  function upsample(peaks, count) {
17
27
  const resampled = [];
@@ -4,6 +4,7 @@ function writeAttribute(element, name, value) {
4
4
  element.removeAttribute(name);
5
5
  return;
6
6
  }
7
+ if (element.getAttribute(name) === value) return;
7
8
  element.setAttribute(name, value);
8
9
  }
9
10
 
package/dist/dev/dat.d.ts CHANGED
@@ -12,8 +12,8 @@ export interface DatHeader {
12
12
  }
13
13
  export declare function readDatHeader(buffer: ArrayBuffer): DatHeader;
14
14
  export declare function datByteRange(header: DatHeader, fromPair: number, toPair: number): [number, number] | undefined;
15
- export declare function readDatSamples(header: DatHeader, body: ArrayBuffer): Int8Array | Int16Array;
16
- export declare function readDatPeaks(header: DatHeader, body: ArrayBuffer): WaveformPeaks;
15
+ export declare function readDatSamples(header: DatHeader, body: ArrayBuffer, byteOffset?: number): Int8Array | Int16Array;
16
+ export declare function readDatPeaks(header: DatHeader, body: ArrayBuffer, byteOffset?: number): WaveformPeaks;
17
17
  //#endregion
18
18
  export type { WaveformPeaks };
19
19
  //# sourceMappingURL=dat.d.ts.map
package/dist/dev/dat.js CHANGED
@@ -16,6 +16,7 @@ function readDatHeader(buffer) {
16
16
  if (channels < 1) throw new Error("A .dat header names no channels");
17
17
  const sampleRate = view.getInt32(8, true);
18
18
  const samplesPerPixel = view.getInt32(12, true);
19
+ if (samplesPerPixel < 1) throw new Error("A .dat header names no samples per pixel");
19
20
  return {
20
21
  bits: (view.getUint32(4, true) & 1) === 1 ? 8 : 16,
21
22
  channels,
@@ -34,15 +35,15 @@ function datByteRange(header, fromPair, toPair) {
34
35
  const pairBytes = header.channels * 2 * header.bits / 8;
35
36
  return [header.dataOffset + from * pairBytes, header.dataOffset + to * pairBytes - 1];
36
37
  }
37
- function readDatSamples(header, body) {
38
- return header.bits === 8 ? new Int8Array(body) : new Int16Array(body, 0, Math.floor(body.byteLength / 2));
38
+ function readDatSamples(header, body, byteOffset = 0) {
39
+ return header.bits === 8 ? new Int8Array(body, byteOffset) : new Int16Array(body, byteOffset, Math.floor((body.byteLength - byteOffset) / 2));
39
40
  }
40
- function readDatPeaks(header, body) {
41
+ function readDatPeaks(header, body, byteOffset = 0) {
41
42
  return {
42
43
  channels: header.channels,
43
44
  fullScale: header.bits === 8 ? 128 : 32768,
44
45
  pairsPerSecond: header.pairsPerSecond,
45
- samples: readDatSamples(header, body)
46
+ samples: readDatSamples(header, body, byteOffset)
46
47
  };
47
48
  }
48
49
 
@@ -1 +1 @@
1
- {"version":3,"file":"dat.js","names":[],"sources":["../../src/dat.ts"],"sourcesContent":["import type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { clamp } from '#lib/math.ts';\n\nexport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nexport interface DatHeader {\n\tbits: 8 | 16;\n\tchannels: number;\n\tdataOffset: number;\n\tpairs: number;\n\tpairsPerSecond: number;\n\tsampleRate: number;\n\tsamplesPerPixel: number;\n\tversion: 1 | 2;\n}\n\nfunction readVersion(view: DataView): 1 | 2 {\n\tconst version = view.getInt32(0, true);\n\tif (version === 1 || version === 2) return version;\n\n\tthrow new Error(`A .dat file of version ${String(version)} is not one this reads`);\n}\n\nexport function readDatHeader(buffer: ArrayBuffer): DatHeader {\n\tif (buffer.byteLength < 20) throw new Error('A .dat header takes at least 20 bytes');\n\n\tconst view = new DataView(buffer);\n\tconst version = readVersion(view);\n\tconst dataOffset = version === 1 ? 20 : 24;\n\tif (buffer.byteLength < dataOffset) {\n\t\tthrow new Error(`A version ${String(version)} .dat header takes ${String(dataOffset)} bytes`);\n\t}\n\n\tconst channels = version === 1 ? 1 : view.getInt32(20, true);\n\tif (channels < 1) throw new Error('A .dat header names no channels');\n\n\tconst sampleRate = view.getInt32(8, true);\n\tconst samplesPerPixel = view.getInt32(12, true);\n\n\treturn {\n\t\tbits: (view.getUint32(4, true) & 1) === 1 ? 8 : 16,\n\t\tchannels,\n\t\tdataOffset,\n\t\tpairs: view.getUint32(16, true),\n\t\tpairsPerSecond: sampleRate / samplesPerPixel,\n\t\tsampleRate,\n\t\tsamplesPerPixel,\n\t\tversion,\n\t};\n}\n\nexport function datByteRange(\n\theader: DatHeader,\n\tfromPair: number,\n\ttoPair: number,\n): [number, number] | undefined {\n\tconst from = clamp(Math.floor(fromPair), 0, header.pairs);\n\tconst to = clamp(Math.ceil(toPair), from, header.pairs);\n\t// A server ignores an inverted `Range` and sends the whole file\n\tif (!(to > from)) return undefined;\n\n\tconst pairBytes = (header.channels * 2 * header.bits) / 8;\n\n\treturn [header.dataOffset + from * pairBytes, header.dataOffset + to * pairBytes - 1];\n}\n\n// Typed arrays read the platform's byte order; every browser platform is little-endian, as the format is\n// A range response can end mid-sample; the odd byte is dropped\nexport function readDatSamples(header: DatHeader, body: ArrayBuffer): Int8Array | Int16Array {\n\treturn header.bits === 8\n\t\t? new Int8Array(body)\n\t\t: new Int16Array(body, 0, Math.floor(body.byteLength / 2));\n}\n\nexport function readDatPeaks(header: DatHeader, body: ArrayBuffer): WaveformPeaks {\n\treturn {\n\t\tchannels: header.channels,\n\t\tfullScale: header.bits === 8 ? 128 : 32_768,\n\t\tpairsPerSecond: header.pairsPerSecond,\n\t\tsamples: readDatSamples(header, body),\n\t};\n}\n"],"mappings":";;;AAiBA,SAAS,YAAY,MAAuB;CAC3C,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI;CACrC,IAAI,YAAY,KAAK,YAAY,GAAG,OAAO;CAE3C,MAAM,IAAI,MAAM,0BAA0B,OAAO,OAAO,EAAE,uBAAuB;AAClF;AAEA,SAAgB,cAAc,QAAgC;CAC7D,IAAI,OAAO,aAAa,IAAI,MAAM,IAAI,MAAM,uCAAuC;CAEnF,MAAM,OAAO,IAAI,SAAS,MAAM;CAChC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,aAAa,YAAY,IAAI,KAAK;CACxC,IAAI,OAAO,aAAa,YACvB,MAAM,IAAI,MAAM,aAAa,OAAO,OAAO,EAAE,qBAAqB,OAAO,UAAU,EAAE,OAAO;CAG7F,MAAM,WAAW,YAAY,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI;CAC3D,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,iCAAiC;CAEnE,MAAM,aAAa,KAAK,SAAS,GAAG,IAAI;CACxC,MAAM,kBAAkB,KAAK,SAAS,IAAI,IAAI;CAE9C,OAAO;EACN,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI,OAAO,IAAI,IAAI;EAChD;EACA;EACA,OAAO,KAAK,UAAU,IAAI,IAAI;EAC9B,gBAAgB,aAAa;EAC7B;EACA;EACA;CACD;AACD;AAEA,SAAgB,aACf,QACA,UACA,QAC+B;CAC/B,MAAM,OAAO,MAAM,KAAK,MAAM,QAAQ,GAAG,GAAG,OAAO,KAAK;CACxD,MAAM,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,MAAM,OAAO,KAAK;CAEtD,IAAI,EAAE,KAAK,OAAO,OAAO;CAEzB,MAAM,YAAa,OAAO,WAAW,IAAI,OAAO,OAAQ;CAExD,OAAO,CAAC,OAAO,aAAa,OAAO,WAAW,OAAO,aAAa,KAAK,YAAY,CAAC;AACrF;AAIA,SAAgB,eAAe,QAAmB,MAA2C;CAC5F,OAAO,OAAO,SAAS,IACpB,IAAI,UAAU,IAAI,IAClB,IAAI,WAAW,MAAM,GAAG,KAAK,MAAM,KAAK,aAAa,CAAC,CAAC;AAC3D;AAEA,SAAgB,aAAa,QAAmB,MAAkC;CACjF,OAAO;EACN,UAAU,OAAO;EACjB,WAAW,OAAO,SAAS,IAAI,MAAM;EACrC,gBAAgB,OAAO;EACvB,SAAS,eAAe,QAAQ,IAAI;CACrC;AACD"}
1
+ {"version":3,"file":"dat.js","names":[],"sources":["../../src/dat.ts"],"sourcesContent":["import type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { clamp } from '#lib/math.ts';\n\nexport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nexport interface DatHeader {\n\tbits: 8 | 16;\n\tchannels: number;\n\tdataOffset: number;\n\tpairs: number;\n\tpairsPerSecond: number;\n\tsampleRate: number;\n\tsamplesPerPixel: number;\n\tversion: 1 | 2;\n}\n\nfunction readVersion(view: DataView): 1 | 2 {\n\tconst version = view.getInt32(0, true);\n\tif (version === 1 || version === 2) return version;\n\n\tthrow new Error(`A .dat file of version ${String(version)} is not one this reads`);\n}\n\nexport function readDatHeader(buffer: ArrayBuffer): DatHeader {\n\tif (buffer.byteLength < 20) throw new Error('A .dat header takes at least 20 bytes');\n\n\tconst view = new DataView(buffer);\n\tconst version = readVersion(view);\n\tconst dataOffset = version === 1 ? 20 : 24;\n\tif (buffer.byteLength < dataOffset) {\n\t\tthrow new Error(`A version ${String(version)} .dat header takes ${String(dataOffset)} bytes`);\n\t}\n\n\tconst channels = version === 1 ? 1 : view.getInt32(20, true);\n\tif (channels < 1) throw new Error('A .dat header names no channels');\n\n\tconst sampleRate = view.getInt32(8, true);\n\tconst samplesPerPixel = view.getInt32(12, true);\n\tif (samplesPerPixel < 1) throw new Error('A .dat header names no samples per pixel');\n\n\treturn {\n\t\tbits: (view.getUint32(4, true) & 1) === 1 ? 8 : 16,\n\t\tchannels,\n\t\tdataOffset,\n\t\tpairs: view.getUint32(16, true),\n\t\tpairsPerSecond: sampleRate / samplesPerPixel,\n\t\tsampleRate,\n\t\tsamplesPerPixel,\n\t\tversion,\n\t};\n}\n\nexport function datByteRange(\n\theader: DatHeader,\n\tfromPair: number,\n\ttoPair: number,\n): [number, number] | undefined {\n\tconst from = clamp(Math.floor(fromPair), 0, header.pairs);\n\tconst to = clamp(Math.ceil(toPair), from, header.pairs);\n\t// A server ignores an inverted `Range` and sends the whole file\n\tif (!(to > from)) return undefined;\n\n\tconst pairBytes = (header.channels * 2 * header.bits) / 8;\n\n\treturn [header.dataOffset + from * pairBytes, header.dataOffset + to * pairBytes - 1];\n}\n\n// Typed arrays read the platform's byte order; every browser platform is little-endian, as the format is\n// A range response can end mid-sample; the odd byte is dropped\nexport function readDatSamples(\n\theader: DatHeader,\n\tbody: ArrayBuffer,\n\tbyteOffset = 0,\n): Int8Array | Int16Array {\n\treturn header.bits === 8\n\t\t? new Int8Array(body, byteOffset)\n\t\t: new Int16Array(body, byteOffset, Math.floor((body.byteLength - byteOffset) / 2));\n}\n\nexport function readDatPeaks(header: DatHeader, body: ArrayBuffer, byteOffset = 0): WaveformPeaks {\n\treturn {\n\t\tchannels: header.channels,\n\t\tfullScale: header.bits === 8 ? 128 : 32_768,\n\t\tpairsPerSecond: header.pairsPerSecond,\n\t\tsamples: readDatSamples(header, body, byteOffset),\n\t};\n}\n"],"mappings":";;;AAiBA,SAAS,YAAY,MAAuB;CAC3C,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI;CACrC,IAAI,YAAY,KAAK,YAAY,GAAG,OAAO;CAE3C,MAAM,IAAI,MAAM,0BAA0B,OAAO,OAAO,EAAE,uBAAuB;AAClF;AAEA,SAAgB,cAAc,QAAgC;CAC7D,IAAI,OAAO,aAAa,IAAI,MAAM,IAAI,MAAM,uCAAuC;CAEnF,MAAM,OAAO,IAAI,SAAS,MAAM;CAChC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,aAAa,YAAY,IAAI,KAAK;CACxC,IAAI,OAAO,aAAa,YACvB,MAAM,IAAI,MAAM,aAAa,OAAO,OAAO,EAAE,qBAAqB,OAAO,UAAU,EAAE,OAAO;CAG7F,MAAM,WAAW,YAAY,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI;CAC3D,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,iCAAiC;CAEnE,MAAM,aAAa,KAAK,SAAS,GAAG,IAAI;CACxC,MAAM,kBAAkB,KAAK,SAAS,IAAI,IAAI;CAC9C,IAAI,kBAAkB,GAAG,MAAM,IAAI,MAAM,0CAA0C;CAEnF,OAAO;EACN,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI,OAAO,IAAI,IAAI;EAChD;EACA;EACA,OAAO,KAAK,UAAU,IAAI,IAAI;EAC9B,gBAAgB,aAAa;EAC7B;EACA;EACA;CACD;AACD;AAEA,SAAgB,aACf,QACA,UACA,QAC+B;CAC/B,MAAM,OAAO,MAAM,KAAK,MAAM,QAAQ,GAAG,GAAG,OAAO,KAAK;CACxD,MAAM,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,MAAM,OAAO,KAAK;CAEtD,IAAI,EAAE,KAAK,OAAO,OAAO;CAEzB,MAAM,YAAa,OAAO,WAAW,IAAI,OAAO,OAAQ;CAExD,OAAO,CAAC,OAAO,aAAa,OAAO,WAAW,OAAO,aAAa,KAAK,YAAY,CAAC;AACrF;AAIA,SAAgB,eACf,QACA,MACA,aAAa,GACY;CACzB,OAAO,OAAO,SAAS,IACpB,IAAI,UAAU,MAAM,UAAU,IAC9B,IAAI,WAAW,MAAM,YAAY,KAAK,OAAO,KAAK,aAAa,cAAc,CAAC,CAAC;AACnF;AAEA,SAAgB,aAAa,QAAmB,MAAmB,aAAa,GAAkB;CACjG,OAAO;EACN,UAAU,OAAO;EACjB,WAAW,OAAO,SAAS,IAAI,MAAM;EACrC,gBAAgB,OAAO;EACvB,SAAS,eAAe,QAAQ,MAAM,UAAU;CACjD;AACD"}