@xsynaptic/sonic-ui 0.25.1 → 0.27.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 (119) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
  4. package/dist/default/elements/button.js +12 -6
  5. package/dist/default/elements/dial.js +9 -10
  6. package/dist/default/elements/envelope.js +26 -20
  7. package/dist/default/elements/field-gesture.js +49 -8
  8. package/dist/default/elements/meter.js +6 -5
  9. package/dist/default/elements/number.js +5 -11
  10. package/dist/default/elements/position-group.js +4 -4
  11. package/dist/default/elements/radio-group.js +7 -7
  12. package/dist/default/elements/region-layer.js +3 -1
  13. package/dist/default/elements/region.js +5 -1
  14. package/dist/default/elements/segmented.js +7 -4
  15. package/dist/default/elements/slider.js +12 -10
  16. package/dist/default/elements/sonic-element.js +10 -13
  17. package/dist/default/elements/spectrum.js +3 -3
  18. package/dist/default/elements/switch.js +11 -6
  19. package/dist/default/elements/toggle.js +16 -10
  20. package/dist/default/elements/value-element.js +26 -4
  21. package/dist/default/elements/wave-element.js +11 -9
  22. package/dist/default/elements/waveform.js +1 -4
  23. package/dist/default/elements/wavestrip.js +1 -1
  24. package/dist/default/elements/xy.js +34 -44
  25. package/dist/default/lib/canvas-surface.js +35 -2
  26. package/dist/default/lib/key-press.js +2 -2
  27. package/dist/default/lib/render.js +2 -2
  28. package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
  29. package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
  30. package/dist/dev/elements/analyser-element.js.map +1 -0
  31. package/dist/dev/elements/button.d.ts +4 -0
  32. package/dist/dev/elements/button.js +12 -11
  33. package/dist/dev/elements/button.js.map +1 -1
  34. package/dist/dev/elements/dial.d.ts +4 -1
  35. package/dist/dev/elements/dial.js +9 -14
  36. package/dist/dev/elements/dial.js.map +1 -1
  37. package/dist/dev/elements/envelope.d.ts +5 -0
  38. package/dist/dev/elements/envelope.js +26 -24
  39. package/dist/dev/elements/envelope.js.map +1 -1
  40. package/dist/dev/elements/field-gesture.js +49 -8
  41. package/dist/dev/elements/field-gesture.js.map +1 -1
  42. package/dist/dev/elements/meter.d.ts +2 -0
  43. package/dist/dev/elements/meter.js +6 -6
  44. package/dist/dev/elements/meter.js.map +1 -1
  45. package/dist/dev/elements/number.d.ts +2 -1
  46. package/dist/dev/elements/number.js +5 -12
  47. package/dist/dev/elements/number.js.map +1 -1
  48. package/dist/dev/elements/position-group.js +4 -4
  49. package/dist/dev/elements/position-group.js.map +1 -1
  50. package/dist/dev/elements/radio-group.d.ts +1 -1
  51. package/dist/dev/elements/radio-group.js +7 -7
  52. package/dist/dev/elements/radio-group.js.map +1 -1
  53. package/dist/dev/elements/region-layer.js +3 -1
  54. package/dist/dev/elements/region-layer.js.map +1 -1
  55. package/dist/dev/elements/region.d.ts +1 -0
  56. package/dist/dev/elements/region.js +5 -1
  57. package/dist/dev/elements/region.js.map +1 -1
  58. package/dist/dev/elements/segmented.d.ts +4 -2
  59. package/dist/dev/elements/segmented.js +7 -9
  60. package/dist/dev/elements/segmented.js.map +1 -1
  61. package/dist/dev/elements/slider.d.ts +4 -1
  62. package/dist/dev/elements/slider.js +12 -14
  63. package/dist/dev/elements/slider.js.map +1 -1
  64. package/dist/dev/elements/sonic-element.d.ts +6 -7
  65. package/dist/dev/elements/sonic-element.js +16 -91
  66. package/dist/dev/elements/sonic-element.js.map +1 -1
  67. package/dist/dev/elements/spectrum.d.ts +2 -2
  68. package/dist/dev/elements/spectrum.js +3 -3
  69. package/dist/dev/elements/spectrum.js.map +1 -1
  70. package/dist/dev/elements/switch.d.ts +4 -1
  71. package/dist/dev/elements/switch.js +11 -10
  72. package/dist/dev/elements/switch.js.map +1 -1
  73. package/dist/dev/elements/toggle.d.ts +4 -1
  74. package/dist/dev/elements/toggle.js +16 -15
  75. package/dist/dev/elements/toggle.js.map +1 -1
  76. package/dist/dev/elements/value-element.d.ts +4 -3
  77. package/dist/dev/elements/value-element.js +26 -4
  78. package/dist/dev/elements/value-element.js.map +1 -1
  79. package/dist/dev/elements/wave-element.d.ts +2 -1
  80. package/dist/dev/elements/wave-element.js +11 -13
  81. package/dist/dev/elements/wave-element.js.map +1 -1
  82. package/dist/dev/elements/waveform.d.ts +0 -1
  83. package/dist/dev/elements/waveform.js +1 -4
  84. package/dist/dev/elements/waveform.js.map +1 -1
  85. package/dist/dev/elements/wavestrip.js +1 -1
  86. package/dist/dev/elements/wavestrip.js.map +1 -1
  87. package/dist/dev/elements/xy.d.ts +4 -0
  88. package/dist/dev/elements/xy.js +34 -48
  89. package/dist/dev/elements/xy.js.map +1 -1
  90. package/dist/dev/index.d.ts +3 -1
  91. package/dist/dev/lib/canvas-surface.js +35 -2
  92. package/dist/dev/lib/canvas-surface.js.map +1 -1
  93. package/dist/dev/lib/check-styles.d.ts +8 -0
  94. package/dist/dev/lib/check-styles.js +87 -0
  95. package/dist/dev/lib/check-styles.js.map +1 -0
  96. package/dist/dev/lib/key-press.js +2 -2
  97. package/dist/dev/lib/key-press.js.map +1 -1
  98. package/dist/dev/lib/render.js +2 -2
  99. package/dist/dev/lib/render.js.map +1 -1
  100. package/dist/styles/button.css +11 -9
  101. package/dist/styles/dial.css +6 -6
  102. package/dist/styles/envelope.css +6 -0
  103. package/dist/styles/led.css +8 -8
  104. package/dist/styles/material/cap.css +13 -2
  105. package/dist/styles/material/pane.css +7 -1
  106. package/dist/styles/material/readout.css +10 -2
  107. package/dist/styles/material/reticle.css +2 -2
  108. package/dist/styles/meter.css +11 -11
  109. package/dist/styles/number.css +1 -1
  110. package/dist/styles/region.css +6 -0
  111. package/dist/styles/segmented.css +3 -3
  112. package/dist/styles/slider.css +12 -12
  113. package/dist/styles/switch.css +6 -0
  114. package/dist/styles/toggle.css +3 -3
  115. package/dist/styles/waveform.css +7 -1
  116. package/dist/styles/wavestrip.css +6 -0
  117. package/dist/styles/xy.css +15 -3
  118. package/package.json +1 -1
  119. package/dist/dev/elements/display-element.js.map +0 -1
@@ -8,7 +8,6 @@ const namingAttributes = [
8
8
  "aria-label",
9
9
  "aria-labelledby"
10
10
  ];
11
- const canMatchStates = CSS.supports("selector(:state(a))");
12
11
  const canReferElements = "ariaLabelledByElements" in Element.prototype;
13
12
  function writeLabelledBy(target, labels) {
14
13
  const current = target.ariaLabelledByElements;
@@ -26,7 +25,6 @@ var SonicElement = class SonicElement extends HTMLElement {
26
25
  }
27
26
  internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
28
27
  #connection;
29
- #customStates = canMatchStates ? this.internals?.states : void 0;
30
28
  #states = /* @__PURE__ */ new Set();
31
29
  static #labelId(label) {
32
30
  if (label.id === "") {
@@ -50,6 +48,7 @@ var SonicElement = class SonicElement extends HTMLElement {
50
48
  this.#connection = void 0;
51
49
  throw error;
52
50
  }
51
+ const { boxProbe: box, control, sheet } = this;
53
52
  }
54
53
  connectedMoveCallback() {}
55
54
  disconnectedCallback() {
@@ -69,7 +68,6 @@ var SonicElement = class SonicElement extends HTMLElement {
69
68
  }
70
69
  if (!this.isDisabled() && !this.#focused()) target.focus(options);
71
70
  }
72
- checkStyles(control, sheet, box) {}
73
71
  focusTarget() {}
74
72
  forwardNaming(target, isNamed) {
75
73
  const labels = isNamed ? this.namingLabels() : [];
@@ -89,7 +87,9 @@ var SonicElement = class SonicElement extends HTMLElement {
89
87
  isDisabled() {
90
88
  return this.disabled || this.matches(":disabled");
91
89
  }
92
- keepControl(control, signal) {
90
+ keepControl(signal) {
91
+ const control = this.control;
92
+ if (!control) return;
93
93
  const keep = () => {
94
94
  appendOnce(this, control);
95
95
  };
@@ -118,14 +118,13 @@ var SonicElement = class SonicElement extends HTMLElement {
118
118
  }
119
119
  this.setAttribute(name, value === true ? "" : value.toString());
120
120
  }
121
+ stateTarget() {
122
+ return this.control;
123
+ }
121
124
  toggleState(state, isOn) {
122
- if (isOn) {
123
- this.#states.add(state);
124
- this.#customStates?.add(state);
125
- return;
126
- }
127
- this.#states.delete(state);
128
- this.#customStates?.delete(state);
125
+ if (isOn) this.#states.add(state);
126
+ else this.#states.delete(state);
127
+ this.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);
129
128
  }
130
129
  upgradeProperties(...names) {
131
130
  for (const name of names) {
@@ -138,8 +137,6 @@ var SonicElement = class SonicElement extends HTMLElement {
138
137
  usesChild(_child) {
139
138
  return false;
140
139
  }
141
- #checkBox(control, checked, box) {}
142
- #checkHost(checked) {}
143
140
  #focused() {
144
141
  const root = this.getRootNode();
145
142
  const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
@@ -1,5 +1,5 @@
1
1
  import { requireChild, template } from "../lib/render.js";
2
- import { SonicDisplayElement } from "./display-element.js";
2
+ import { SonicAnalyserElement } from "./analyser-element.js";
3
3
  import { columnEdges, columnLevels, gridColumns, gridRows, levelRows, loudestBin, zoneRows } from "../lib/spectrum-columns.js";
4
4
  import { areSettled, createLevels, receiveLevels, stepLevels } from "../lib/spectrum-levels.js";
5
5
 
@@ -71,9 +71,9 @@ function paintHolds(context, { look, rowOf }, { bars, holds }) {
71
71
  context.fillStyle = look.colours.lit;
72
72
  context.fill();
73
73
  }
74
- var SonicSpectrum = class extends SonicDisplayElement {
74
+ var SonicSpectrum = class extends SonicAnalyserElement {
75
75
  static observedAttributes = [
76
- ...SonicDisplayElement.observedAttributes,
76
+ ...SonicAnalyserElement.observedAttributes,
77
77
  "frequency-max",
78
78
  "frequency-min",
79
79
  "grid",
@@ -20,20 +20,25 @@ const renderBare = template(`<button class="sonic-switch-position" role="switch"
20
20
  const pressCenterZone = .08;
21
21
  var SonicSwitch = class extends SonicPositionGroupElement {
22
22
  bare = renderBare();
23
+ boxProbe = {
24
+ property: "padding-top",
25
+ selector: ".sonic-switch-label"
26
+ };
27
+ control = renderSwitch();
23
28
  defaultOrientation = "vertical";
24
- group = renderSwitch();
25
- #bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
29
+ sheet = "switch.css";
30
+ #bat = requireChild(this.control, ".sonic-switch-bat", HTMLSpanElement);
26
31
  #isClickSwallowed = false;
27
32
  claimPress(index, event) {
28
33
  if (this.#isOnBat(event)) return true;
29
34
  const target = this.pressTarget(index);
30
35
  if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
31
- capturePointer(this.group, event.pointerId);
36
+ capturePointer(this.control, event.pointerId);
32
37
  this.hold(target, event.pointerId);
33
38
  return true;
34
39
  }
35
40
  connect(signal) {
36
- const group = this.group;
41
+ const group = this.control;
37
42
  super.connect(signal);
38
43
  this.bare.addEventListener("click", (event) => {
39
44
  const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
@@ -70,7 +75,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
70
75
  lift: (press, event) => {
71
76
  this.#isClickSwallowed = true;
72
77
  if (press.hasMoved || this.isDisabled()) return;
73
- if (!isUnder(this.group, event.clientX, event.clientY)) return;
78
+ if (!isUnder(this.control, event.clientX, event.clientY)) return;
74
79
  this.#pressBat(event);
75
80
  },
76
81
  move: (press, event) => {
@@ -101,7 +106,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
101
106
  #renderThrow(index) {
102
107
  const count = this.positions().length;
103
108
  const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
104
- this.group.style.setProperty("--_sonic-switch-at", String(at));
109
+ this.control.style.setProperty("--_sonic-switch-at", String(at));
105
110
  }
106
111
  #sideStep(from, event) {
107
112
  const box = this.#bat.getBoundingClientRect();
@@ -22,12 +22,18 @@ const renderBare = template(`<button class="sonic-toggle-position" role="switch"
22
22
  const pressTolerance = 2;
23
23
  var SonicToggle = class extends SonicPositionGroupElement {
24
24
  bare = renderBare();
25
+ boxProbe = {
26
+ property: "margin-top",
27
+ ratio: "--_sonic-toggle-gap-ratio",
28
+ selector: ".sonic-toggle-cap"
29
+ };
30
+ control = renderToggle();
25
31
  defaultOrientation = "horizontal";
26
- group = renderToggle();
27
- #cap = requireChild(this.group, ".sonic-toggle-cap", HTMLSpanElement);
32
+ sheet = "toggle.css";
33
+ #cap = requireChild(this.control, ".sonic-toggle-cap", HTMLSpanElement);
28
34
  #draggedTo;
29
35
  #hasWarnedOfLegends = false;
30
- #well = requireChild(this.group, ".sonic-toggle-well", HTMLSpanElement);
36
+ #well = requireChild(this.control, ".sonic-toggle-well", HTMLSpanElement);
31
37
  claimPress() {
32
38
  return true;
33
39
  }
@@ -49,7 +55,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
49
55
  }
50
56
  render() {
51
57
  if (!this.isBound()) return;
52
- this.group.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
58
+ this.control.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
53
59
  this.#placeCap(this.#draggedTo ?? this.#restingAt());
54
60
  super.render();
55
61
  for (const [index, option] of this.options().entries()) option.style.setProperty("--_sonic-toggle-index", String(index));
@@ -58,11 +64,11 @@ var SonicToggle = class extends SonicPositionGroupElement {
58
64
  return renderPosition();
59
65
  }
60
66
  #bindCap(signal) {
61
- bindDrag(this.group, {
67
+ bindDrag(this.control, {
62
68
  grab: (event) => this.#grab(event),
63
69
  lift: (drag, event) => {
64
70
  if (!drag.isPress || drag.isHolding || this.isDisabled()) return;
65
- if (!isUnder(this.group, event.clientX, event.clientY)) return;
71
+ if (!isUnder(this.control, event.clientX, event.clientY)) return;
66
72
  this.#press(drag.position);
67
73
  },
68
74
  move: (drag, event) => {
@@ -76,7 +82,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
76
82
  if (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId)) drag.isPress = false;
77
83
  },
78
84
  release: (drag) => {
79
- delete drag.marked.dataset.sonicPressed;
85
+ delete drag.marked.dataset.sonicActive;
80
86
  this.#draggedTo = void 0;
81
87
  this.toggleState("dragging", false);
82
88
  this.render();
@@ -85,7 +91,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
85
91
  }
86
92
  #checkLegends() {}
87
93
  #fixedLegend() {
88
- return [...this.children].find((child) => child !== this.group && !super.isMirrored(child));
94
+ return [...this.children].find((child) => child !== this.control && !super.isMirrored(child));
89
95
  }
90
96
  #grab(event) {
91
97
  const position = this.pressedIndex(event.target);
@@ -94,7 +100,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
94
100
  const travel = this.#travel(event, isOnCap);
95
101
  if (!travel || !isOnCap && this.isOptionDisabled(position)) return void 0;
96
102
  const marked = this.options()[position] ?? this.bare;
97
- marked.toggleAttribute("data-sonic-pressed", true);
103
+ marked.toggleAttribute("data-sonic-active", true);
98
104
  return {
99
105
  hasMoved: false,
100
106
  isHolding: this.#holdFrom(position, event.pointerId),
@@ -113,7 +119,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
113
119
  return true;
114
120
  }
115
121
  #placeCap(at) {
116
- this.group.style.setProperty("--_sonic-toggle-at", String(at));
122
+ this.control.style.setProperty("--_sonic-toggle-at", String(at));
117
123
  }
118
124
  #press(position) {
119
125
  const target = this.pressTarget(position);
@@ -233,6 +233,7 @@ var SonicValueElement = class extends SonicFormElement {
233
233
  #model = createValueModel(() => this.#spec());
234
234
  #parseValue;
235
235
  #readout;
236
+ #said;
236
237
  attributeChangedCallback(name) {
237
238
  if (name === "value" && this.#isHeld()) {
238
239
  this.#writeHeld(this.numberAttribute("value", this.min));
@@ -250,7 +251,8 @@ var SonicValueElement = class extends SonicFormElement {
250
251
  abandonHold() {
251
252
  this.#gestures?.abandon();
252
253
  }
253
- bindGestures(control, signal, grab) {
254
+ bindGestures(signal, grab) {
255
+ const control = this.control;
254
256
  const bubble = control.querySelector("[popover]");
255
257
  if (bubble) this.#readout = {
256
258
  anchor: control,
@@ -315,6 +317,9 @@ var SonicValueElement = class extends SonicFormElement {
315
317
  controlRole() {
316
318
  return "slider";
317
319
  }
320
+ focusTarget() {
321
+ return this.control;
322
+ }
318
323
  input(next) {
319
324
  const previous = this.#model.value;
320
325
  if (this.#model.input(next)) this.render();
@@ -373,8 +378,12 @@ var SonicValueElement = class extends SonicFormElement {
373
378
  }
374
379
  springTarget() {}
375
380
  tapTarget() {}
376
- travelPx(control, property) {
377
- return Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));
381
+ upwardAxis(travelProperty) {
382
+ const styles = getComputedStyle(this.control);
383
+ return {
384
+ position: (event) => -event.clientY,
385
+ travelPx: Math.max(1, readPxProperty(styles, travelProperty, fallbackTravelPx))
386
+ };
378
387
  }
379
388
  #entryText() {
380
389
  const value = this.#model.value;
@@ -405,7 +414,7 @@ var SonicValueElement = class extends SonicFormElement {
405
414
  writeAttribute(control, "aria-valuemax", String(high));
406
415
  writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
407
416
  if (orientation) writeAttribute(control, "aria-orientation", orientation);
408
- writeAttribute(control, "aria-valuetext", this.spokenText(this.#spokenNow()));
417
+ writeAttribute(control, "aria-valuetext", this.#spokenValueText());
409
418
  this.forwardNaming(control, true);
410
419
  this.#renderDisabled(control);
411
420
  this.renderReadout();
@@ -458,6 +467,19 @@ var SonicValueElement = class extends SonicFormElement {
458
467
  const step = this.spokenStep;
459
468
  return step !== void 0 && step > 0 ? trimFloat(Math.round(this.#model.value / step) * step) : this.#model.value;
460
469
  }
470
+ #spokenValueText() {
471
+ const format = this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak;
472
+ const value = this.#spokenNow();
473
+ const said = this.#said;
474
+ if (said && said.format === format && said.value === value) return said.text;
475
+ const text = format?.(value);
476
+ this.#said = {
477
+ format,
478
+ text,
479
+ value
480
+ };
481
+ return text;
482
+ }
461
483
  #textFor(value) {
462
484
  return (this.#formatValue ?? this.notation.format)(value);
463
485
  }
@@ -1,5 +1,5 @@
1
1
  import { SonicValueElement } from "./value-element.js";
2
- import { bindSurface } from "../lib/canvas-surface.js";
2
+ import { bindControlSurface } from "../lib/canvas-surface.js";
3
3
  import { formatClock, parseClock } from "../lib/clock.js";
4
4
  import { spokenDuration } from "../lib/spoken-duration.js";
5
5
 
@@ -32,6 +32,10 @@ var SonicWaveElement = class extends SonicValueElement {
32
32
  this.renderMarkers();
33
33
  this.render();
34
34
  }
35
+ boxProbe = {
36
+ property: "margin-bottom",
37
+ selector: "[popover]"
38
+ };
35
39
  notation = {
36
40
  format: formatClock,
37
41
  parse: parseClock,
@@ -65,15 +69,13 @@ var SonicWaveElement = class extends SonicValueElement {
65
69
  }
66
70
  connect(signal) {
67
71
  const control = this.control;
68
- this.keepControl(control, signal);
72
+ this.keepControl(signal);
69
73
  this.renderEmpty();
70
- this.#surface = bindSurface({
74
+ this.#surface = bindControlSurface({
71
75
  canvas: this.canvas,
72
76
  colours: this.colours,
73
- fill: {
74
- control,
75
- sizeProperty: this.sizeProperty
76
- },
77
+ control,
78
+ fill: this.fill,
77
79
  lengths: this.lengths,
78
80
  numbers: this.numbers,
79
81
  paint: (context, frame) => {
@@ -82,10 +84,10 @@ var SonicWaveElement = class extends SonicValueElement {
82
84
  resize: () => {
83
85
  this.renderMarkers();
84
86
  },
85
- signal
87
+ signal,
88
+ sizeProperty: this.sizeProperty
86
89
  });
87
90
  this.render();
88
- this.#surface.setFill(this.fill);
89
91
  }
90
92
  markerList() {
91
93
  return this.#markers;
@@ -231,7 +231,7 @@ var SonicWaveform = class extends SonicWaveElement {
231
231
  }
232
232
  };
233
233
  this.#zoomGesture = bindZoom(this.control, zoomTarget, signal);
234
- this.bindGestures(this.control, signal, () => this.#grab());
234
+ this.bindGestures(signal, () => this.#grab());
235
235
  bindZoomKeys(this.control, zoomTarget, signal);
236
236
  if (!("fonts" in document)) return;
237
237
  document.fonts.addEventListener("loadingdone", () => {
@@ -243,9 +243,6 @@ var SonicWaveform = class extends SonicWaveElement {
243
243
  surface?.requestFrame();
244
244
  if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
245
245
  }
246
- focusTarget() {
247
- return this.control;
248
- }
249
246
  isEmpty() {
250
247
  return !this.#peaks || this.#peaks.samples.length === 0;
251
248
  }
@@ -157,7 +157,7 @@ var SonicWavestrip = class extends SonicWaveElement {
157
157
  },
158
158
  valueAt: (event) => this.#markerStartAt(event, this.#axis()) ?? this.mapping().snap(this.valueFromPoint(event.clientX, event.clientY))
159
159
  });
160
- this.bindGestures(control, signal, (event) => this.#grab(event));
160
+ this.bindGestures(signal, (event) => this.#grab(event));
161
161
  }
162
162
  draw() {
163
163
  const surface = this.surface();
@@ -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();
@@ -275,49 +272,47 @@ var SonicXy = class extends SonicFormElement {
275
272
  const move = keyMoves.get(event.key);
276
273
  if (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;
277
274
  event.preventDefault();
278
- this.#claim.reveal("keys");
279
- this.#renderReadout();
275
+ this.#gesture?.revealKeys();
280
276
  if (move) this.#keyTo(...move);
281
277
  else this.#reset();
282
278
  }, { signal });
283
279
  xy.addEventListener("focusout", (event) => {
284
280
  if (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;
285
- if (this.#claim.conceal("keys")) this.#renderReadout();
281
+ this.#gesture?.concealKeys();
286
282
  }, { signal });
287
283
  signal.addEventListener("abort", () => {
288
- if (this.#claim.conceal("keys")) this.#renderReadout();
284
+ this.#gesture?.concealKeys();
289
285
  }, { once: true });
290
286
  }
291
287
  #bindPointer(xy, signal) {
292
288
  xy.addEventListener("mousedown", (event) => {
293
289
  event.preventDefault();
294
290
  }, { signal });
295
- xy.addEventListener("pointercancel", () => {
296
- this.#doublePress.forget();
297
- }, { signal });
298
291
  this.#gesture = bindFieldGesture(xy, {
299
- claim: this.#claim,
292
+ element: this,
300
293
  grab: (event) => {
301
- if (this.isDisabled() || isMenuPress(event)) return;
294
+ if (this.isDisabled()) return;
302
295
  focusByPointer(this.focusTarget());
303
- if (!isResetPress(event)) {
304
- this.toggleState("dragging", true);
305
- return this.#grab(event);
306
- }
307
- this.#reset();
296
+ this.toggleState("dragging", true);
297
+ return this.#grab(event);
308
298
  },
309
299
  input: (_hold, next) => {
310
- if (!this.#input(next)) this.#renderReadout();
311
- },
312
- lift: (_hold, event) => {
313
- if (!this.#doublePress.press(event) || this.doublePress !== "reset") return;
314
- this.#reset();
300
+ this.#input(next);
315
301
  },
316
302
  release: (_hold, moved) => {
317
303
  if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
318
304
  },
319
- toggle: (isDragging) => {
320
- 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);
321
316
  }
322
317
  }, signal);
323
318
  }
@@ -408,16 +403,11 @@ var SonicXy = class extends SonicFormElement {
408
403
  this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
409
404
  }
410
405
  #renderReadout() {
411
- const { isRevealed } = this.#claim;
412
406
  this.#readout.show({
413
407
  anchor: this.#puck,
414
- isOpen: isRevealed && this.readout,
408
+ isOpen: this.#gesture?.isRevealed() === true && this.readout,
415
409
  text: this.#valueText("x")
416
410
  });
417
- if (isRevealed === this.#isRevealed) return;
418
- this.#isRevealed = isRevealed;
419
- this.toggleState("revealed", isRevealed);
420
- this.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
421
411
  }
422
412
  #reset() {
423
413
  this.#commit({
@@ -10,6 +10,17 @@ function deviceBox(entry, css) {
10
10
  if (!device || Math.abs(device.inlineSize - css.inlineSize * dpr) > 1) return void 0;
11
11
  return Math.abs(device.blockSize - css.blockSize * dpr) > 1 ? void 0 : device;
12
12
  }
13
+ function canTakeColour(context, colour) {
14
+ const kept = context.fillStyle;
15
+ const isKept = (sentinel) => {
16
+ context.fillStyle = sentinel;
17
+ context.fillStyle = colour;
18
+ return context.fillStyle === sentinel;
19
+ };
20
+ const isRefused = isKept("#000000") && isKept("#ffffff");
21
+ context.fillStyle = kept;
22
+ return !isRefused;
23
+ }
13
24
  function readEach(properties, read) {
14
25
  return Object.fromEntries(Object.entries(properties).flatMap(([name, property]) => property === void 0 ? [] : [[name, read(property)]]));
15
26
  }
@@ -181,7 +192,7 @@ var CanvasSurface = class {
181
192
  const { canvas, colours, lengths = {}, numbers = {} } = this.#options;
182
193
  const styles = getComputedStyle(canvas);
183
194
  return {
184
- colours: readEach(colours, (property) => styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color)),
195
+ colours: readEach(colours, (property) => this.#resolve(styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color))),
185
196
  isReducedMotion: this.#reducedMotion.matches,
186
197
  lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0)),
187
198
  numbers: readEach(numbers, (property) => readPxProperty(styles, property, 0))
@@ -209,6 +220,16 @@ var CanvasSurface = class {
209
220
  if (previous.width === 0 || previous.height === 0) this.rebuild();
210
221
  else this.invalidate();
211
222
  }
223
+ #resolve(colour) {
224
+ const context = this.#context;
225
+ if (!context || canTakeColour(context, colour)) return colour;
226
+ const probe = document.createElement("span");
227
+ probe.style.color = colour;
228
+ this.#options.canvas.append(probe);
229
+ const resolved = getComputedStyle(probe).color;
230
+ probe.remove();
231
+ return resolved;
232
+ }
212
233
  #schedule() {
213
234
  if (this.#options.signal.aborted || this.#frame !== void 0 || !this.#isVisible) return;
214
235
  this.#frame = requestAnimationFrame((frameMs) => {
@@ -233,6 +254,18 @@ var CanvasSurface = class {
233
254
  function bindSurface(options) {
234
255
  return new CanvasSurface(options);
235
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
+ }
236
269
 
237
270
  //#endregion
238
- export { bindSurface };
271
+ export { bindControlSurface, bindSurface };
@@ -3,7 +3,7 @@ function bindKeyPress(root, signal) {
3
3
  let pressed;
4
4
  const release = () => {
5
5
  if (!pressed) return;
6
- delete pressed.dataset.sonicPressed;
6
+ delete pressed.dataset.sonicActive;
7
7
  pressed = void 0;
8
8
  };
9
9
  root.addEventListener("keydown", (event) => {
@@ -12,7 +12,7 @@ function bindKeyPress(root, signal) {
12
12
  if (!(part instanceof HTMLButtonElement) || part.disabled) return;
13
13
  release();
14
14
  pressed = part;
15
- part.dataset.sonicPressed = "";
15
+ part.dataset.sonicActive = "";
16
16
  }, { signal });
17
17
  root.addEventListener("keyup", (event) => {
18
18
  if (event.key === " " || event.key === "Meta") release();
@@ -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
  }
@@ -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