@xsynaptic/sonic-ui 0.26.0 → 0.28.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 (127) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -0
  3. package/dist/default/define/ticker.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
  6. package/dist/default/elements/button.js +12 -9
  7. package/dist/default/elements/dial.js +9 -13
  8. package/dist/default/elements/envelope.js +26 -23
  9. package/dist/default/elements/field-gesture.js +49 -8
  10. package/dist/default/elements/meter.js +6 -8
  11. package/dist/default/elements/number.js +5 -14
  12. package/dist/default/elements/position-group.js +4 -4
  13. package/dist/default/elements/radio-group.js +6 -9
  14. package/dist/default/elements/segmented.js +7 -4
  15. package/dist/default/elements/slider.js +12 -13
  16. package/dist/default/elements/sonic-element.js +7 -5
  17. package/dist/default/elements/spectrum.js +3 -3
  18. package/dist/default/elements/switch.js +11 -6
  19. package/dist/default/elements/ticker.js +313 -0
  20. package/dist/default/elements/toggle.js +14 -8
  21. package/dist/default/elements/value-element.js +11 -3
  22. package/dist/default/elements/wave-element.js +11 -12
  23. package/dist/default/elements/waveform.js +1 -4
  24. package/dist/default/elements/wavestrip.js +3 -6
  25. package/dist/default/elements/xy.js +34 -47
  26. package/dist/default/index.js +2 -1
  27. package/dist/default/lib/cancel-zone.js +12 -0
  28. package/dist/default/lib/canvas-surface.js +13 -1
  29. package/dist/default/lib/distribute.js +2 -3
  30. package/dist/default/lib/mirror-children.js +1 -1
  31. package/dist/default/lib/pointer-drag.js +8 -2
  32. package/dist/default/lib/render.js +2 -2
  33. package/dist/default/lib/ticker-leg.js +54 -0
  34. package/dist/dev/define/ticker.d.ts +2 -0
  35. package/dist/dev/define/ticker.js +8 -0
  36. package/dist/dev/define/ticker.js.map +1 -0
  37. package/dist/dev/define.d.ts +3 -1
  38. package/dist/dev/define.js +1 -0
  39. package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
  40. package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
  41. package/dist/dev/elements/analyser-element.js.map +1 -0
  42. package/dist/dev/elements/button.d.ts +4 -1
  43. package/dist/dev/elements/button.js +12 -14
  44. package/dist/dev/elements/button.js.map +1 -1
  45. package/dist/dev/elements/dial.d.ts +4 -2
  46. package/dist/dev/elements/dial.js +9 -17
  47. package/dist/dev/elements/dial.js.map +1 -1
  48. package/dist/dev/elements/envelope.d.ts +5 -1
  49. package/dist/dev/elements/envelope.js +26 -27
  50. package/dist/dev/elements/envelope.js.map +1 -1
  51. package/dist/dev/elements/field-gesture.js +49 -8
  52. package/dist/dev/elements/field-gesture.js.map +1 -1
  53. package/dist/dev/elements/meter.d.ts +2 -1
  54. package/dist/dev/elements/meter.js +6 -9
  55. package/dist/dev/elements/meter.js.map +1 -1
  56. package/dist/dev/elements/number.d.ts +2 -2
  57. package/dist/dev/elements/number.js +5 -15
  58. package/dist/dev/elements/number.js.map +1 -1
  59. package/dist/dev/elements/position-group.js +4 -4
  60. package/dist/dev/elements/position-group.js.map +1 -1
  61. package/dist/dev/elements/radio-group.d.ts +1 -2
  62. package/dist/dev/elements/radio-group.js +6 -9
  63. package/dist/dev/elements/radio-group.js.map +1 -1
  64. package/dist/dev/elements/segmented.d.ts +4 -2
  65. package/dist/dev/elements/segmented.js +7 -9
  66. package/dist/dev/elements/segmented.js.map +1 -1
  67. package/dist/dev/elements/slider.d.ts +4 -2
  68. package/dist/dev/elements/slider.js +12 -17
  69. package/dist/dev/elements/slider.js.map +1 -1
  70. package/dist/dev/elements/sonic-element.d.ts +6 -7
  71. package/dist/dev/elements/sonic-element.js +14 -83
  72. package/dist/dev/elements/sonic-element.js.map +1 -1
  73. package/dist/dev/elements/spectrum.d.ts +2 -2
  74. package/dist/dev/elements/spectrum.js +3 -3
  75. package/dist/dev/elements/spectrum.js.map +1 -1
  76. package/dist/dev/elements/switch.d.ts +4 -1
  77. package/dist/dev/elements/switch.js +11 -10
  78. package/dist/dev/elements/switch.js.map +1 -1
  79. package/dist/dev/elements/ticker.d.ts +25 -0
  80. package/dist/dev/elements/ticker.js +314 -0
  81. package/dist/dev/elements/ticker.js.map +1 -0
  82. package/dist/dev/elements/toggle.d.ts +4 -1
  83. package/dist/dev/elements/toggle.js +14 -13
  84. package/dist/dev/elements/toggle.js.map +1 -1
  85. package/dist/dev/elements/value-element.d.ts +4 -3
  86. package/dist/dev/elements/value-element.js +11 -3
  87. package/dist/dev/elements/value-element.js.map +1 -1
  88. package/dist/dev/elements/wave-element.d.ts +2 -2
  89. package/dist/dev/elements/wave-element.js +11 -16
  90. package/dist/dev/elements/wave-element.js.map +1 -1
  91. package/dist/dev/elements/waveform.d.ts +0 -1
  92. package/dist/dev/elements/waveform.js +1 -4
  93. package/dist/dev/elements/waveform.js.map +1 -1
  94. package/dist/dev/elements/wavestrip.js +3 -6
  95. package/dist/dev/elements/wavestrip.js.map +1 -1
  96. package/dist/dev/elements/xy.d.ts +4 -1
  97. package/dist/dev/elements/xy.js +34 -51
  98. package/dist/dev/elements/xy.js.map +1 -1
  99. package/dist/dev/index.d.ts +4 -1
  100. package/dist/dev/index.js +2 -1
  101. package/dist/dev/lib/cancel-zone.js +13 -0
  102. package/dist/dev/lib/cancel-zone.js.map +1 -0
  103. package/dist/dev/lib/canvas-surface.js +13 -1
  104. package/dist/dev/lib/canvas-surface.js.map +1 -1
  105. package/dist/dev/lib/check-styles.d.ts +8 -0
  106. package/dist/dev/lib/check-styles.js +95 -0
  107. package/dist/dev/lib/check-styles.js.map +1 -0
  108. package/dist/dev/lib/distribute.js +2 -3
  109. package/dist/dev/lib/distribute.js.map +1 -1
  110. package/dist/dev/lib/mirror-children.js +9 -7
  111. package/dist/dev/lib/mirror-children.js.map +1 -1
  112. package/dist/dev/lib/pointer-drag.js +8 -2
  113. package/dist/dev/lib/pointer-drag.js.map +1 -1
  114. package/dist/dev/lib/render.js +2 -2
  115. package/dist/dev/lib/render.js.map +1 -1
  116. package/dist/dev/lib/ticker-leg.js +55 -0
  117. package/dist/dev/lib/ticker-leg.js.map +1 -0
  118. package/dist/styles/controls.css +1 -0
  119. package/dist/styles/dial.css +28 -5
  120. package/dist/styles/material/cap.css +16 -5
  121. package/dist/styles/material/core.css +1 -0
  122. package/dist/styles/slider.css +2 -1
  123. package/dist/styles/switch.css +3 -1
  124. package/dist/styles/ticker.css +115 -0
  125. package/dist/styles/toggle.css +7 -5
  126. package/package.json +2 -1
  127. package/dist/dev/elements/display-element.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { clamp, clampProportion } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
- import { readPxProperty } from "../lib/read-px-property.js";
4
3
  import { scrubRegions } from "../lib/scrub.js";
5
4
  import { sortedRegions } from "../lib/time-regions.js";
5
+ import { outsideCancelZone } from "../lib/cancel-zone.js";
6
6
  import { SonicWaveElement } from "./wave-element.js";
7
7
  import { createMarkerBand } from "../lib/marker-band.js";
8
8
  import { RegionLayer } from "./region-layer.js";
@@ -22,7 +22,6 @@ const lengths = {
22
22
  pitch: "--_sonic-wavestrip-bar-pitch",
23
23
  radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
24
24
  };
25
- const cancelZonePx = 48;
26
25
  const renderWavestrip = template(`
27
26
  <div class="sonic-wavestrip">
28
27
  <canvas class="sonic-wavestrip-canvas" role="slider" tabindex="0"></canvas>
@@ -157,7 +156,7 @@ var SonicWavestrip = class extends SonicWaveElement {
157
156
  },
158
157
  valueAt: (event) => this.#markerStartAt(event, this.#axis()) ?? this.mapping().snap(this.valueFromPoint(event.clientX, event.clientY))
159
158
  });
160
- this.bindGestures(control, signal, (event) => this.#grab(event));
159
+ this.bindGestures(signal, (event) => this.#grab(event));
161
160
  }
162
161
  draw() {
163
162
  const surface = this.surface();
@@ -256,9 +255,7 @@ var SonicWavestrip = class extends SonicWaveElement {
256
255
  }
257
256
  #outside() {
258
257
  if (!this.cancellable) return void 0;
259
- const box = this.control.getBoundingClientRect();
260
- const zone = readPxProperty(getComputedStyle(this.control), "--_sonic-cancel-zone", cancelZonePx);
261
- return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
258
+ return outsideCancelZone(this.control, "--_sonic-cancel-zone");
262
259
  }
263
260
  #placeReadout() {
264
261
  const at = this.mapping().proportionOf(this.readoutValue());
@@ -3,10 +3,7 @@ import { writeAttribute } from "../lib/write-attribute.js";
3
3
  import { SonicFormElement } from "./form-element.js";
4
4
  import { requireChild, template } from "../lib/render.js";
5
5
  import { Readout } from "./readout.js";
6
- import { ReadoutClaim } from "./readout-claim.js";
7
- import { createDoublePress } from "../lib/double-press.js";
8
6
  import { focusByPointer } from "../lib/focus-by-pointer.js";
9
- import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
10
7
  import { resetKeys } from "../lib/value-mapping.js";
11
8
  import { createValueModel } from "../lib/value-model.js";
12
9
  import { pointerPosition } from "../lib/field.js";
@@ -203,26 +200,26 @@ var SonicXy = class extends SonicFormElement {
203
200
  set yTaper(curve) {
204
201
  this.reflect("y-taper", curve);
205
202
  }
206
- #claim = new ReadoutClaim(() => {
207
- this.#renderReadout();
208
- });
203
+ boxProbe = {
204
+ property: "margin-bottom",
205
+ selector: "[popover]"
206
+ };
207
+ control = renderXy();
208
+ sheet = "xy.css";
209
209
  #currentAxis = "x";
210
- #doublePress = createDoublePress();
211
- #xy = renderXy();
212
- #field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
210
+ #field = requireChild(this.control, ".sonic-xy-field", HTMLDivElement);
213
211
  #formatValue;
214
212
  #gesture;
215
- #isRevealed = false;
216
213
  #models = {
217
214
  x: createValueModel(() => this.#spec("x")),
218
215
  y: createValueModel(() => this.#spec("y"))
219
216
  };
220
217
  #parts = {
221
- x: requireChild(this.#xy, "[data-sonic-axis=\"x\"]", HTMLDivElement),
222
- y: requireChild(this.#xy, "[data-sonic-axis=\"y\"]", HTMLDivElement)
218
+ x: requireChild(this.control, "[data-sonic-axis=\"x\"]", HTMLDivElement),
219
+ y: requireChild(this.control, "[data-sonic-axis=\"y\"]", HTMLDivElement)
223
220
  };
224
- #puck = requireChild(this.#xy, ".sonic-xy-puck", HTMLDivElement);
225
- #readout = new Readout(requireChild(this.#xy, ".sonic-xy-readout", HTMLDivElement));
221
+ #puck = requireChild(this.control, ".sonic-xy-puck", HTMLDivElement);
222
+ #readout = new Readout(requireChild(this.control, ".sonic-xy-readout", HTMLDivElement));
226
223
  attributeChangedCallback(name) {
227
224
  if ((name === "x" || name === "y") && this.#gesture?.current()) return;
228
225
  if (name === "disabled" && this.isDisabled()) this.#gesture?.end();
@@ -237,8 +234,8 @@ var SonicXy = class extends SonicFormElement {
237
234
  this.#respec(...axes);
238
235
  }
239
236
  connect(signal) {
240
- const xy = this.#xy;
241
- this.keepControl(xy, signal);
237
+ const xy = this.control;
238
+ this.keepControl(signal);
242
239
  this.render();
243
240
  this.#bindPointer(xy, signal);
244
241
  this.#bindKeys(xy, signal);
@@ -248,7 +245,7 @@ var SonicXy = class extends SonicFormElement {
248
245
  }
249
246
  render() {
250
247
  if (!this.isBound()) return;
251
- const { style } = this.#xy;
248
+ const { style } = this.control;
252
249
  for (const axis of axes) {
253
250
  const model = this.#models[axis];
254
251
  const mapping = model.mapping();
@@ -265,9 +262,6 @@ var SonicXy = class extends SonicFormElement {
265
262
  this.x = x;
266
263
  this.y = y;
267
264
  }
268
- stateTarget() {
269
- return this.#xy;
270
- }
271
265
  #axisText(axis) {
272
266
  const { value } = this.#models[axis];
273
267
  return `${this.getAttribute(`${axis}-label`) ?? axis.toUpperCase()} ${this.#formatValue?.(value, axis) ?? String(value)}`;
@@ -278,49 +272,47 @@ var SonicXy = class extends SonicFormElement {
278
272
  const move = keyMoves.get(event.key);
279
273
  if (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;
280
274
  event.preventDefault();
281
- this.#claim.reveal("keys");
282
- this.#renderReadout();
275
+ this.#gesture?.revealKeys();
283
276
  if (move) this.#keyTo(...move);
284
277
  else this.#reset();
285
278
  }, { signal });
286
279
  xy.addEventListener("focusout", (event) => {
287
280
  if (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;
288
- if (this.#claim.conceal("keys")) this.#renderReadout();
281
+ this.#gesture?.concealKeys();
289
282
  }, { signal });
290
283
  signal.addEventListener("abort", () => {
291
- if (this.#claim.conceal("keys")) this.#renderReadout();
284
+ this.#gesture?.concealKeys();
292
285
  }, { once: true });
293
286
  }
294
287
  #bindPointer(xy, signal) {
295
288
  xy.addEventListener("mousedown", (event) => {
296
289
  event.preventDefault();
297
290
  }, { signal });
298
- xy.addEventListener("pointercancel", () => {
299
- this.#doublePress.forget();
300
- }, { signal });
301
291
  this.#gesture = bindFieldGesture(xy, {
302
- claim: this.#claim,
292
+ element: this,
303
293
  grab: (event) => {
304
- if (this.isDisabled() || isMenuPress(event)) return;
294
+ if (this.isDisabled()) return;
305
295
  focusByPointer(this.focusTarget());
306
- if (!isResetPress(event)) {
307
- this.toggleState("dragging", true);
308
- return this.#grab(event);
309
- }
310
- this.#reset();
296
+ this.toggleState("dragging", true);
297
+ return this.#grab(event);
311
298
  },
312
299
  input: (_hold, next) => {
313
- if (!this.#input(next)) this.#renderReadout();
314
- },
315
- lift: (_hold, event) => {
316
- if (!this.#doublePress.press(event) || this.doublePress !== "reset") return;
317
- this.#reset();
300
+ this.#input(next);
318
301
  },
319
302
  release: (_hold, moved) => {
320
303
  if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
321
304
  },
322
- toggle: (isDragging) => {
323
- this.toggleState("dragging", isDragging);
305
+ renderReadout: () => {
306
+ this.#renderReadout();
307
+ },
308
+ reset: () => {
309
+ if (this.isDisabled()) return;
310
+ focusByPointer(this.focusTarget());
311
+ this.#reset();
312
+ },
313
+ resetsOnDoublePress: () => this.doublePress === "reset",
314
+ toggle: (state, isOn) => {
315
+ this.toggleState(state, isOn);
324
316
  }
325
317
  }, signal);
326
318
  }
@@ -411,16 +403,11 @@ var SonicXy = class extends SonicFormElement {
411
403
  this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
412
404
  }
413
405
  #renderReadout() {
414
- const { isRevealed } = this.#claim;
415
406
  this.#readout.show({
416
407
  anchor: this.#puck,
417
- isOpen: isRevealed && this.readout,
408
+ isOpen: this.#gesture?.isRevealed() === true && this.readout,
418
409
  text: this.#valueText("x")
419
410
  });
420
- if (isRevealed === this.#isRevealed) return;
421
- this.#isRevealed = isRevealed;
422
- this.toggleState("revealed", isRevealed);
423
- this.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
424
411
  }
425
412
  #reset() {
426
413
  this.#commit({
@@ -10,6 +10,7 @@ import { SonicSlider } from "./elements/slider.js";
10
10
  import { SonicSpectrum } from "./elements/spectrum.js";
11
11
  import { SonicSplit } from "./elements/split.js";
12
12
  import { SonicSwitch } from "./elements/switch.js";
13
+ import { SonicTicker } from "./elements/ticker.js";
13
14
  import { SonicToggle } from "./elements/toggle.js";
14
15
  import { formatClock, parseClock } from "./lib/clock.js";
15
16
  import { SonicWaveform } from "./elements/waveform.js";
@@ -18,4 +19,4 @@ import { SonicXy } from "./elements/xy.js";
18
19
  import { crossfadeGains } from "./lib/crossfade.js";
19
20
  import { formatPercent, parsePercent } from "./lib/percent.js";
20
21
 
21
- export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
22
+ export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicTicker, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
@@ -0,0 +1,12 @@
1
+ import { readPxProperty } from "./read-px-property.js";
2
+
3
+ //#region src/lib/cancel-zone.ts
4
+ const cancelZonePx = 48;
5
+ function outsideCancelZone(control, property) {
6
+ const box = control.getBoundingClientRect();
7
+ const zone = readPxProperty(getComputedStyle(control), property, cancelZonePx);
8
+ return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
9
+ }
10
+
11
+ //#endregion
12
+ export { outsideCancelZone };
@@ -254,6 +254,18 @@ var CanvasSurface = class {
254
254
  function bindSurface(options) {
255
255
  return new CanvasSurface(options);
256
256
  }
257
+ function bindControlSurface(options) {
258
+ const { control, fill, sizeProperty, ...surfaceOptions } = options;
259
+ const surface = bindSurface({
260
+ ...surfaceOptions,
261
+ fill: {
262
+ control,
263
+ sizeProperty
264
+ }
265
+ });
266
+ surface.setFill(fill);
267
+ return surface;
268
+ }
257
269
 
258
270
  //#endregion
259
- export { bindSurface };
271
+ export { bindControlSurface, bindSurface };
@@ -187,8 +187,7 @@ function close(members, rule, held) {
187
187
  const { landing, move, mover } = held;
188
188
  const { index } = move;
189
189
  const landed = landing.values[index] ?? mover.value;
190
- const targets = moverTargets(members, landing, index);
191
- return nearest(targets.flatMap((target) => {
190
+ return nearest(moverTargets(members, landing, index).flatMap((target) => {
192
191
  const attempt = spread(members, rule, {
193
192
  index,
194
193
  target
@@ -197,7 +196,7 @@ function close(members, rule, held) {
197
196
  }), move, {
198
197
  landed,
199
198
  start: mover.value
200
- }) ?? landing.values.with(index, targets[0] ?? landed);
199
+ }) ?? members.map(({ value }) => value);
201
200
  }
202
201
  function distribute(members, rule, move) {
203
202
  const landing = spread(members, rule, move);
@@ -14,7 +14,7 @@ function isCopiedChange(host, record, { control, isCopied }) {
14
14
  if (child === void 0) return false;
15
15
  return isCopied(child) || record.type === "attributes" && record.target === child;
16
16
  }
17
- function mirrorChildren(host, { control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
17
+ function mirrorChildren(host, { canCopyLinks = false, control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
18
18
  const slots = hostSlots.get(host) ?? attachSlots(host);
19
19
  let copies = /* @__PURE__ */ new Map();
20
20
  hostSlots.set(host, slots);
@@ -5,6 +5,7 @@ function capturePointer(target, pointerId) {
5
5
  } catch {}
6
6
  }
7
7
  function bindDrag(target, handlers, signal) {
8
+ const isCaptureLate = handlers.isCaptureLate === true;
8
9
  let held;
9
10
  const end = () => {
10
11
  if (!held) return;
@@ -28,14 +29,19 @@ function bindDrag(target, handlers, signal) {
28
29
  pointerId: event.pointerId
29
30
  };
30
31
  handlers.toggle?.(true);
31
- capturePointer(target, event.pointerId);
32
+ if (!isCaptureLate) capturePointer(target, event.pointerId);
32
33
  }, { signal });
33
34
  whileHeld("pointermove", handlers.move);
34
35
  whileHeld("pointercancel", (drag) => {
35
36
  handlers.cancel?.(drag);
36
37
  end();
37
38
  });
38
- whileHeld("lostpointercapture", end);
39
+ whileHeld("lostpointercapture", (_drag, event) => {
40
+ if (!isCaptureLate || event.target === target) end();
41
+ });
42
+ if (isCaptureLate) whileHeld("pointerleave", (_drag, event) => {
43
+ if (!target.hasPointerCapture(event.pointerId)) end();
44
+ });
39
45
  whileHeld("pointerup", (drag, event) => {
40
46
  end();
41
47
  handlers.lift?.(drag, event);
@@ -1,7 +1,7 @@
1
1
  //#region src/lib/render.ts
2
2
  function requireChild(root, selector, child) {
3
3
  const found = root.querySelector(selector);
4
- if (!(found instanceof child)) throw new Error(`A template is missing its ${selector}`);
4
+ if (!(found instanceof child)) throw new TypeError(`A template is missing its ${selector}`);
5
5
  return found;
6
6
  }
7
7
  function template(html, root) {
@@ -9,7 +9,7 @@ function template(html, root) {
9
9
  parsed.innerHTML = html;
10
10
  return () => {
11
11
  const clone = document.importNode(parsed.content, true).firstElementChild;
12
- if (!(clone instanceof root)) throw new Error(`A template's root is not a ${root.name}`);
12
+ if (!(clone instanceof root)) throw new TypeError(`A template's root is not a ${root.name}`);
13
13
  return clone;
14
14
  };
15
15
  }
@@ -0,0 +1,54 @@
1
+ import { clamp, clampProportion } from "./math.js";
2
+
3
+ //#region src/lib/ticker-leg.ts
4
+ const fadeApproach = 3;
5
+ function tickerLeg({ distance, hold: fullHold, rate }, { from, heading, hold = fullHold }) {
6
+ if (!(distance > 0) || !(rate > 0)) return void 0;
7
+ const start = clamp(from, 0, distance);
8
+ const isOutbound = start === (heading > 0 ? distance : 0) ? heading < 0 : heading > 0;
9
+ const to = isOutbound ? distance : 0;
10
+ return {
11
+ duration: Math.abs(to - start) / rate * 1e3,
12
+ from: start,
13
+ heading: isOutbound ? 1 : -1,
14
+ hold: Math.max(0, hold),
15
+ to
16
+ };
17
+ }
18
+ function legPosition(leg, elapsed) {
19
+ return leg.from + (leg.to - leg.from) * clampProportion((elapsed - leg.hold) / leg.duration);
20
+ }
21
+ function legDeparture(leg, elapsed) {
22
+ return {
23
+ from: legPosition(leg, elapsed),
24
+ heading: leg.heading,
25
+ hold: Math.max(0, leg.hold - elapsed)
26
+ };
27
+ }
28
+ function clippedEnds(position, distance) {
29
+ const ends = [...position > 0 ? ["start"] : [], ...position < distance ? ["end"] : []];
30
+ return ends.length > 0 ? ends.join(" ") : void 0;
31
+ }
32
+ function fadeWidth(past, fade) {
33
+ return clamp(past / fadeApproach, 0, Math.max(0, fade));
34
+ }
35
+ function legFade(leg, [pastFrom, pastTo], fade) {
36
+ const reach = Math.max(0, fade) * fadeApproach;
37
+ const msPerPx = leg.duration / Math.abs(leg.to - leg.from);
38
+ const from = Math.min(pastFrom, reach);
39
+ const to = Math.min(pastTo, reach);
40
+ return {
41
+ delay: leg.hold + (pastFrom - from) * msPerPx,
42
+ duration: Math.abs(to - from) * msPerPx,
43
+ widths: [from / fadeApproach, to / fadeApproach]
44
+ };
45
+ }
46
+ function legFades(leg, distance, fade) {
47
+ return {
48
+ end: legFade(leg, [distance - leg.from, distance - leg.to], fade),
49
+ start: legFade(leg, [leg.from, leg.to], fade)
50
+ };
51
+ }
52
+
53
+ //#endregion
54
+ export { clippedEnds, fadeWidth, legDeparture, legFades, legPosition, tickerLeg };
@@ -0,0 +1,2 @@
1
+ import { SonicTicker } from "../elements/ticker.js";
2
+ export type { SonicTicker };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicTicker } from "../elements/ticker.js";
3
+
4
+ //#region src/define/ticker.ts
5
+ defineOnce("sonic-ticker", SonicTicker);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=ticker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticker.js","names":[],"sources":["../../../src/define/ticker.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicTicker } from '#elements/ticker.ts';\n\ndefineOnce('sonic-ticker', SonicTicker);\n\nexport type { SonicTicker } from '#elements/ticker.ts';\n"],"mappings":";;;;AAGA,WAAW,gBAAgB,WAAW"}
@@ -20,6 +20,8 @@ import { SonicSplit } from "./elements/split.js";
20
20
  import "./define/split.js";
21
21
  import { SonicSwitch } from "./elements/switch.js";
22
22
  import "./define/switch.js";
23
+ import { SonicTicker } from "./elements/ticker.js";
24
+ import "./define/ticker.js";
23
25
  import { SonicToggle } from "./elements/toggle.js";
24
26
  import "./define/toggle.js";
25
27
  import { SonicWaveform } from "./elements/waveform.js";
@@ -28,4 +30,4 @@ import { SonicWavestrip } from "./elements/wavestrip.js";
28
30
  import "./define/wavestrip.js";
29
31
  import { SonicXy } from "./elements/xy.js";
30
32
  import "./define/xy.js";
31
- export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy };
33
+ export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicTicker, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy };
@@ -9,6 +9,7 @@ import "./define/slider.js";
9
9
  import "./define/spectrum.js";
10
10
  import "./define/split.js";
11
11
  import "./define/switch.js";
12
+ import "./define/ticker.js";
12
13
  import "./define/toggle.js";
13
14
  import "./define/waveform.js";
14
15
  import "./define/wavestrip.js";
@@ -1,8 +1,8 @@
1
1
  import { SonicElement } from "./sonic-element.js";
2
2
  import { Decibels } from "../lib/units.js";
3
3
  import { Surface, SurfaceFrame } from "../lib/canvas-surface.js";
4
- //#region src/elements/display-element.d.ts
5
- export declare abstract class SonicDisplayElement<Colour extends string, Numeric extends string = never> extends SonicElement {
4
+ //#region src/elements/analyser-element.d.ts
5
+ export declare abstract class SonicAnalyserElement<Colour extends string, Numeric extends string = never> extends SonicElement {
6
6
  #private;
7
7
  static readonly observedAttributes: string[];
8
8
  get analyser(): AnalyserNode | undefined;
@@ -31,4 +31,4 @@ export declare abstract class SonicDisplayElement<Colour extends string, Numeric
31
31
  protected surface(): Surface | undefined;
32
32
  }
33
33
  //#endregion
34
- //# sourceMappingURL=display-element.d.ts.map
34
+ //# sourceMappingURL=analyser-element.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { SonicElement } from "./sonic-element.js";
2
- import { bindSurface } from "../lib/canvas-surface.js";
2
+ import { bindControlSurface } from "../lib/canvas-surface.js";
3
3
 
4
- //#region src/elements/display-element.ts
5
- var SonicDisplayElement = class extends SonicElement {
4
+ //#region src/elements/analyser-element.ts
5
+ var SonicAnalyserElement = class extends SonicElement {
6
6
  static observedAttributes = [
7
7
  ...SonicElement.observedAttributes,
8
8
  "fill",
@@ -55,22 +55,19 @@ var SonicDisplayElement = class extends SonicElement {
55
55
  }
56
56
  connect(signal) {
57
57
  const control = this.control;
58
- this.keepControl(control, signal);
59
- this.#surface = bindSurface({
58
+ this.keepControl(signal);
59
+ this.#surface = bindControlSurface({
60
60
  canvas: this.canvas,
61
61
  colours: this.colours,
62
- fill: {
63
- control,
64
- sizeProperty: this.sizeProperty
65
- },
62
+ control,
63
+ fill: this.fill,
66
64
  numbers: this.numbers,
67
65
  paint: (context, frame) => {
68
66
  this.#paint(context, frame);
69
67
  },
70
- signal
68
+ signal,
69
+ sizeProperty: this.sizeProperty
71
70
  });
72
- this.checkStyles(control, this.sheet);
73
- this.#surface.setFill(this.fill);
74
71
  }
75
72
  surface() {
76
73
  return this.#surface;
@@ -88,5 +85,5 @@ var SonicDisplayElement = class extends SonicElement {
88
85
  };
89
86
 
90
87
  //#endregion
91
- export { SonicDisplayElement };
92
- //# sourceMappingURL=display-element.js.map
88
+ export { SonicAnalyserElement };
89
+ //# sourceMappingURL=analyser-element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyser-element.js","names":[],"sources":["../../../src/elements/analyser-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindControlSurface } from '#lib/canvas-surface.ts';\n\nexport abstract class SonicAnalyserElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'fill',\n\t\t'max',\n\t\t'min',\n\t];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract override readonly control: HTMLElement;\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract override readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'fill', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */\n\tpush(decibels: Float32Array): void {\n\t\tthis.receive(decibels);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(signal);\n\t\tthis.#surface = bindControlSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tcontrol,\n\t\t\tfill: this.fill,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t\tsizeProperty: this.sizeProperty,\n\t\t});\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;AAMA,IAAsB,uBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAC7C,GAAG,aAAa;EAChB;EACA;EACA;CACD;CAEA,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,OAAgB;EACnB,OAAO,KAAK,aAAa,MAAM;CAChC;CAEA,IAAI,KAAK,WAAoB;EAC5B,KAAK,QAAQ,QAAQ,SAAS;CAC/B;CAGA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAcA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,IAAI;EACrD,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,QAAQ,OAAO,KAAK;EACvD,MAAM,kBAAkB;CACzB;;CAGA,KAAK,UAA8B;EAClC,KAAK,QAAQ,QAAQ;EACrB,KAAK,UAAU,aAAa;CAC7B;CAEA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,MAAM;EACvB,KAAK,WAAW,mBAAmB;GAClC,QAAQ,KAAK;GACb,SAAS,KAAK;GACd;GACA,MAAM,KAAK;GACX,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;GACA,cAAc,KAAK;EACpB,CAAC;CACF;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
@@ -1,3 +1,4 @@
1
+ import { BoxProbe } from "../lib/check-styles.js";
1
2
  import { SonicFormElement } from "./form-element.js";
2
3
  //#region src/elements/button.d.ts
3
4
  declare global {
@@ -28,6 +29,9 @@ export declare class SonicButton extends SonicFormElement {
28
29
  /** Submitted with the form while a latching button is pressed */
29
30
  get value(): string;
30
31
  set value(value: string | undefined);
32
+ protected readonly boxProbe: BoxProbe;
33
+ protected readonly control: HTMLButtonElement;
34
+ protected readonly sheet = "button.css";
31
35
  attributeChangedCallback(name: string): void;
32
36
  formResetCallback(): void;
33
37
  protected activate(): void;
@@ -35,7 +39,6 @@ export declare class SonicButton extends SonicFormElement {
35
39
  protected focusTarget(): HTMLElement;
36
40
  protected render(): void;
37
41
  protected restoreState(state: string): void;
38
- protected stateTarget(): HTMLElement;
39
42
  }
40
43
  //#endregion
41
44
  //# sourceMappingURL=button.d.ts.map
@@ -88,7 +88,13 @@ var SonicButton = class extends SonicFormElement {
88
88
  set value(value) {
89
89
  this.reflect("value", value);
90
90
  }
91
- #button = renderButton();
91
+ boxProbe = {
92
+ property: "padding-inline-start",
93
+ ratio: "--_sonic-button-padding-ratio",
94
+ selector: ".sonic-button-cap > :not(svg, .sonic-led)"
95
+ };
96
+ control = renderButton();
97
+ sheet = "button.css";
92
98
  #holding;
93
99
  #isDirty = false;
94
100
  #pressed = false;
@@ -101,10 +107,10 @@ var SonicButton = class extends SonicFormElement {
101
107
  this.attributeChangedCallback("pressed");
102
108
  }
103
109
  activate() {
104
- this.#button.click();
110
+ this.control.click();
105
111
  }
106
112
  connect(signal) {
107
- const button = this.#button;
113
+ const button = this.control;
108
114
  const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
109
115
  this.upgradeProperties("armed", "busy", "defaultPressed", "legend", "momentary", "softDisabled", "latching", "pressed", "value");
110
116
  mirrorChildren(this, {
@@ -116,11 +122,6 @@ var SonicButton = class extends SonicFormElement {
116
122
  }
117
123
  }, signal);
118
124
  this.render();
119
- this.checkStyles(button, "button.css", {
120
- property: "padding-inline-start",
121
- ratio: "--_sonic-button-padding-ratio",
122
- selector: ".sonic-button-cap > :not(svg, .sonic-led)"
123
- });
124
125
  button.addEventListener("click", (event) => {
125
126
  if (this.#isSoftDisabled()) {
126
127
  event.preventDefault();
@@ -135,11 +136,11 @@ var SonicButton = class extends SonicFormElement {
135
136
  bindKeyPress(button, signal);
136
137
  }
137
138
  focusTarget() {
138
- return this.#button;
139
+ return this.control;
139
140
  }
140
141
  render() {
141
142
  if (!this.isBound()) return;
142
- const button = this.#button;
143
+ const button = this.control;
143
144
  writeAttribute(button, "disabled", this.isDisabled() ? "" : void 0);
144
145
  writeAttribute(button, "aria-disabled", this.#isSoftDisabled() ? "true" : void 0);
145
146
  writeAttribute(button, "aria-busy", this.busy ? "true" : void 0);
@@ -154,9 +155,6 @@ var SonicButton = class extends SonicFormElement {
154
155
  restoreState(state) {
155
156
  if (this.latching) this.pressed = state === "true";
156
157
  }
157
- stateTarget() {
158
- return this.#button;
159
- }
160
158
  #bindMomentary(button, signal) {
161
159
  const holding = bindHold(button, {
162
160
  canHold: () => !this.isDisabled() && !this.#isSoftDisabled(),
@@ -195,7 +193,7 @@ var SonicButton = class extends SonicFormElement {
195
193
  }
196
194
  #showLegend() {
197
195
  const legend = this.legend;
198
- for (const part of this.#button.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
196
+ for (const part of this.control.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
199
197
  const when = part.dataset.sonicWhen ?? "";
200
198
  part.toggleAttribute("data-sonic-hidden", !pressWhens.has(when) && when !== legend);
201
199
  }