@xsynaptic/sonic-ui 0.12.0 → 0.13.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 (88) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +35 -0
  3. package/dist/default/elements/button.js +5 -1
  4. package/dist/default/elements/dial.js +7 -1
  5. package/dist/default/elements/display-element.js +5 -1
  6. package/dist/default/elements/envelope.js +6 -1
  7. package/dist/default/elements/meter.js +1 -1
  8. package/dist/default/elements/number.js +7 -1
  9. package/dist/default/elements/segmented.js +5 -1
  10. package/dist/default/elements/slider.js +7 -1
  11. package/dist/default/elements/sonic-element.js +1 -1
  12. package/dist/default/elements/switch.js +1 -1
  13. package/dist/default/elements/toggle.js +5 -1
  14. package/dist/default/elements/value-element.js +3 -0
  15. package/dist/default/elements/value-gestures.js +7 -0
  16. package/dist/default/elements/wave-element.js +8 -1
  17. package/dist/default/elements/waveform.js +129 -3
  18. package/dist/default/elements/xy.js +7 -1
  19. package/dist/default/lib/zoom-gesture.js +19 -0
  20. package/dist/dev/elements/button.js +5 -1
  21. package/dist/dev/elements/button.js.map +1 -1
  22. package/dist/dev/elements/dial.js +7 -1
  23. package/dist/dev/elements/dial.js.map +1 -1
  24. package/dist/dev/elements/display-element.js +5 -1
  25. package/dist/dev/elements/display-element.js.map +1 -1
  26. package/dist/dev/elements/envelope.js +6 -1
  27. package/dist/dev/elements/envelope.js.map +1 -1
  28. package/dist/dev/elements/meter.js +1 -1
  29. package/dist/dev/elements/meter.js.map +1 -1
  30. package/dist/dev/elements/number.js +7 -1
  31. package/dist/dev/elements/number.js.map +1 -1
  32. package/dist/dev/elements/segmented.js +5 -1
  33. package/dist/dev/elements/segmented.js.map +1 -1
  34. package/dist/dev/elements/slider.js +7 -1
  35. package/dist/dev/elements/slider.js.map +1 -1
  36. package/dist/dev/elements/sonic-element.d.ts +16 -1
  37. package/dist/dev/elements/sonic-element.js +27 -5
  38. package/dist/dev/elements/sonic-element.js.map +1 -1
  39. package/dist/dev/elements/switch.js +1 -1
  40. package/dist/dev/elements/switch.js.map +1 -1
  41. package/dist/dev/elements/toggle.js +5 -1
  42. package/dist/dev/elements/toggle.js.map +1 -1
  43. package/dist/dev/elements/value-element.d.ts +1 -0
  44. package/dist/dev/elements/value-element.js +3 -0
  45. package/dist/dev/elements/value-element.js.map +1 -1
  46. package/dist/dev/elements/value-gestures.js +7 -0
  47. package/dist/dev/elements/value-gestures.js.map +1 -1
  48. package/dist/dev/elements/wave-element.js +8 -1
  49. package/dist/dev/elements/wave-element.js.map +1 -1
  50. package/dist/dev/elements/waveform.d.ts +10 -0
  51. package/dist/dev/elements/waveform.js +129 -3
  52. package/dist/dev/elements/waveform.js.map +1 -1
  53. package/dist/dev/elements/xy.js +7 -1
  54. package/dist/dev/elements/xy.js.map +1 -1
  55. package/dist/dev/lib/zoom-gesture.js +20 -0
  56. package/dist/dev/lib/zoom-gesture.js.map +1 -0
  57. package/dist/styles/button.css +3 -1
  58. package/dist/styles/dial.css +3 -1
  59. package/dist/styles/envelope.css +3 -1
  60. package/dist/styles/led.css +3 -1
  61. package/dist/styles/material/arc.css +21 -0
  62. package/dist/styles/material/bracket.css +31 -0
  63. package/dist/styles/material/cap.css +179 -0
  64. package/dist/styles/material/core.css +103 -0
  65. package/dist/styles/material/glass.css +62 -0
  66. package/dist/styles/material/groove.css +10 -0
  67. package/dist/styles/material/keycap.css +95 -0
  68. package/dist/styles/material/lens.css +11 -0
  69. package/dist/styles/material/pane.css +97 -0
  70. package/dist/styles/material/readout.css +87 -0
  71. package/dist/styles/material/scale.css +30 -0
  72. package/dist/styles/material/value.css +43 -0
  73. package/dist/styles/material/well.css +24 -0
  74. package/dist/styles/material.css +15 -772
  75. package/dist/styles/meter.css +3 -1
  76. package/dist/styles/number.css +3 -1
  77. package/dist/styles/panel.css +3 -1
  78. package/dist/styles/ring.css +3 -1
  79. package/dist/styles/screen.css +3 -1
  80. package/dist/styles/segmented.css +3 -1
  81. package/dist/styles/slider.css +3 -1
  82. package/dist/styles/spectrum.css +3 -1
  83. package/dist/styles/switch.css +3 -1
  84. package/dist/styles/toggle.css +3 -1
  85. package/dist/styles/waveform.css +3 -1
  86. package/dist/styles/wavestrip.css +3 -1
  87. package/dist/styles/xy.css +3 -1
  88. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @xsynaptic/sonic-ui
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - The material is split into sheets under `material/*.css`, so you can load only what your controls need; the README lists them. `material.css` and `controls.css` work as before, and sheets now load in any order.
8
+
9
+ New public surface: the material sheets, and the sublayers `sonic.material` and `sonic.control`. A rule written directly in `@layer sonic` now always wins over the control sheets.
10
+
11
+ - Waveform: `zoomable` opts in to zoom by ctrl or cmd wheel, two-finger pinch and the `+` and `-` keys, between `zoom-min` and `zoom-max` (20 and 280 by default). `sonic-zoom` fires for a gesture and never for a write to `zoom`. A second finger ends a scrub without a seek. Without `zoomable` nothing changes.
12
+
3
13
  ## 0.12.0
4
14
 
5
15
  ### Minor Changes
package/README.md CHANGED
@@ -27,6 +27,39 @@ A display takes its signal as data: `spectrum.analyser = node`, or `spectrum.pus
27
27
 
28
28
  Under a hydrating framework, register the definitions once hydration is done (in React, from an effect); a control owns one child of its host and uses only the children it documents.
29
29
 
30
+ ## Loading less
31
+
32
+ `controls.css` loads everything. To load less, import each control's sheet and the material sheets it needs, in any order:
33
+
34
+ ```css
35
+ @import '@xsynaptic/sonic-ui/material/core.css';
36
+ @import '@xsynaptic/sonic-ui/material/lens.css';
37
+ @import '@xsynaptic/sonic-ui/material/groove.css';
38
+ @import '@xsynaptic/sonic-ui/meter.css';
39
+ ```
40
+
41
+ | Sheet | Needs from `material/` |
42
+ | --------------- | ----------------------------------------------- |
43
+ | `button.css` | core, cap, well, keycap |
44
+ | `dial.css` | core, cap, glass, readout, value, arc, scale |
45
+ | `envelope.css` | core, glass, pane, well, readout, bracket |
46
+ | `led.css` | core, lens |
47
+ | `meter.css` | core, lens, groove |
48
+ | `number.css` | core, cap, glass, pane, well, value |
49
+ | `panel.css` | core |
50
+ | `ring.css` | core, arc |
51
+ | `screen.css` | core, glass, pane, well |
52
+ | `segmented.css` | core, cap, well, keycap |
53
+ | `slider.css` | core, cap, glass, readout, value, groove, scale |
54
+ | `spectrum.css` | core, glass, pane, well |
55
+ | `switch.css` | core, cap |
56
+ | `toggle.css` | core, cap, well, keycap |
57
+ | `waveform.css` | core, cap, glass, pane, well, readout, value |
58
+ | `wavestrip.css` | core, cap, glass, pane, well, readout, value |
59
+ | `xy.css` | core, cap, glass, pane, well, readout, bracket |
60
+
61
+ An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`. The development build warns about a missing sheet.
62
+
30
63
  ## Wave controls
31
64
 
32
65
  `<sonic-wavestrip>` is the overview of a track, scrubbed to seek; `<sonic-waveform>` is the close-up that scrolls under a fixed playhead. Both are value controls in seconds that take their data as properties (`peaks`, `markers`); the types list the rest.
@@ -37,6 +70,8 @@ The strip draws one part per marker inside `.sonic-wavestrip-markers`, in `start
37
70
 
38
71
  The waveform calls `requestPeaks(fromSeconds, toSeconds)` as its window moves, often every frame, so return at once for a span already loaded or on its way. Return a promise, or several, and the waveform repaints as each settles; an `async` function is the wrong shape, since it hands back a new settled promise each call. `pending` is yours to keep: list the spans being fetched, and clear each as it lands.
39
72
 
73
+ With `zoomable`, a ctrl or cmd wheel, a pinch and the `+` and `-` keys set the waveform's `zoom` and fire `sonic-zoom`; a write to `zoom` fires nothing.
74
+
40
75
  With `fill` a control takes its container's height and its ratios follow. `--sonic-wavestrip-marker-size`, `--sonic-wavestrip-groove-size` and `--sonic-waveform-label-font-size` hold an absolute length instead.
41
76
 
42
77
  Targets current Chrome and Edge, Safari 26 and Firefox 147. MIT licensed.
@@ -114,7 +114,11 @@ var SonicButton = class extends SonicFormElement {
114
114
  }
115
115
  }, signal);
116
116
  this.render();
117
- this.checkStyles(button, "button.css");
117
+ this.checkStyles(button, "button.css", [
118
+ "cap",
119
+ "well",
120
+ "keycap"
121
+ ]);
118
122
  button.addEventListener("click", (event) => {
119
123
  if (this.#isSoftDisabled()) {
120
124
  event.preventDefault();
@@ -64,7 +64,13 @@ var SonicDial = class extends SonicValueElement {
64
64
  marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
65
65
  }, signal);
66
66
  this.render();
67
- this.checkStyles(dial, "dial.css");
67
+ this.checkStyles(dial, "dial.css", [
68
+ "cap",
69
+ "readout",
70
+ "value",
71
+ ["glass", ".sonic-dial-readout"],
72
+ ["arc", ".sonic-dial-ring"]
73
+ ]);
68
74
  this.bindGestures(dial, signal, () => ({
69
75
  position: (event) => -event.clientY,
70
76
  travelPx: this.travelPx(dial, "--_sonic-dial-travel")
@@ -66,7 +66,11 @@ var SonicDisplayElement = class extends SonicElement {
66
66
  },
67
67
  signal
68
68
  });
69
- this.checkStyles(control, this.sheet);
69
+ this.checkStyles(control, this.sheet, [
70
+ "glass",
71
+ "pane",
72
+ "well"
73
+ ]);
70
74
  this.#syncFill = bindFill(control, this.sizeProperty, signal);
71
75
  this.#syncFill(this.fill);
72
76
  }
@@ -170,7 +170,12 @@ var SonicEnvelope = class extends SonicElement {
170
170
  this.#signal = signal;
171
171
  this.keepControl(envelope, signal);
172
172
  this.#bind();
173
- this.checkStyles(envelope, "envelope.css");
173
+ this.checkStyles(envelope, "envelope.css", [
174
+ "glass",
175
+ "pane",
176
+ "well",
177
+ "readout"
178
+ ]);
174
179
  this.#bindPointer(envelope, signal);
175
180
  signal.addEventListener("abort", () => {
176
181
  this.#signal = void 0;
@@ -138,7 +138,7 @@ var SonicMeter = class extends SonicElement {
138
138
  this.#renderSegments();
139
139
  this.#render(now);
140
140
  this.#schedule(now);
141
- this.checkStyles(this.#meter, "meter.css");
141
+ this.checkStyles(this.#meter, "meter.css", ["lens", "groove"]);
142
142
  signal.addEventListener("abort", () => {
143
143
  if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
144
144
  this.#frame = void 0;
@@ -32,7 +32,13 @@ var SonicNumber = class extends SonicValueElement {
32
32
  const number = this.#number;
33
33
  this.keepControl(number, signal);
34
34
  this.render();
35
- this.checkStyles(number, "number.css");
35
+ this.checkStyles(number, "number.css", [
36
+ "cap",
37
+ "glass",
38
+ "pane",
39
+ "well",
40
+ "value"
41
+ ]);
36
42
  this.bindGestures(number, signal, () => ({
37
43
  position: (event) => -event.clientY,
38
44
  travelPx: this.travelPx(number, "--_sonic-number-travel")
@@ -12,7 +12,11 @@ var SonicSegmented = class extends SonicRadioGroupElement {
12
12
  group = renderSegmented();
13
13
  connect(signal) {
14
14
  super.connect(signal);
15
- this.checkStyles(this.group, "segmented.css");
15
+ this.checkStyles(this.group, "segmented.css", [
16
+ "well",
17
+ "keycap",
18
+ ["cap", ".sonic-segmented-option"]
19
+ ]);
16
20
  }
17
21
  renderOption() {
18
22
  return renderOption();
@@ -114,7 +114,13 @@ var SonicSlider = class extends SonicValueElement {
114
114
  }
115
115
  });
116
116
  this.render();
117
- this.checkStyles(slider, "slider.css");
117
+ this.checkStyles(slider, "slider.css", [
118
+ "cap",
119
+ "readout",
120
+ "value",
121
+ "groove",
122
+ ["glass", ".sonic-slider-readout"]
123
+ ]);
118
124
  this.bindGestures(slider, signal, (event) => {
119
125
  const axis = this.#axis(slider, cap);
120
126
  if (event.target instanceof Node && cap.contains(event.target)) return axis;
@@ -58,7 +58,7 @@ var SonicElement = class extends HTMLElement {
58
58
  }
59
59
  if (!this.isDisabled() && !this.#focused()) target.focus(options);
60
60
  }
61
- checkStyles(control, sheet) {}
61
+ checkStyles(control, sheet, material = []) {}
62
62
  focusTarget() {}
63
63
  forwardNaming(target, isNamed) {
64
64
  const labels = isNamed ? this.namingLabels() : [];
@@ -34,7 +34,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
34
34
  connect(signal) {
35
35
  const group = this.group;
36
36
  super.connect(signal);
37
- this.checkStyles(group, "switch.css");
37
+ this.checkStyles(group, "switch.css", ["cap"]);
38
38
  this.bare.addEventListener("click", (event) => {
39
39
  const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
40
40
  this.#isClickSwallowed = false;
@@ -31,7 +31,11 @@ var SonicToggle = class extends SonicPositionGroupElement {
31
31
  }
32
32
  connect(signal) {
33
33
  super.connect(signal);
34
- this.checkStyles(this.group, "toggle.css");
34
+ this.checkStyles(this.group, "toggle.css", [
35
+ "cap",
36
+ "well",
37
+ "keycap"
38
+ ]);
35
39
  this.bare.addEventListener("click", (event) => {
36
40
  if (event instanceof PointerEvent && event.pointerType !== "") return;
37
41
  this.setChecked(!this.checked);
@@ -259,6 +259,9 @@ var SonicValueElement = class extends SonicFormElement {
259
259
  this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "spokenStep", "step", "taper", "positions", "detent", "formatEntry", "formatValue", "formatSpokenValue", "keyStep", "parseValue", "value");
260
260
  super.connectedCallback();
261
261
  }
262
+ abandonHold() {
263
+ this.#gestures?.abandon();
264
+ }
262
265
  bindGestures(control, signal, grab) {
263
266
  const bubble = control.querySelector("[popover]");
264
267
  if (bubble) this.#readout = {
@@ -27,6 +27,13 @@ var ValueGestures = class {
27
27
  this.#host = host;
28
28
  this.#control = control;
29
29
  }
30
+ abandon() {
31
+ const drag = this.#dragging();
32
+ if (!drag) return;
33
+ this.#host.input(drag.fromValue);
34
+ this.#entry?.forgetPress();
35
+ this.#pointerDrag?.end();
36
+ }
30
37
  bind(signal, grab) {
31
38
  const control = this.#control;
32
39
  const host = this.#host;
@@ -63,7 +63,14 @@ var SonicWaveElement = class extends SonicValueElement {
63
63
  signal
64
64
  });
65
65
  this.render();
66
- this.checkStyles(control, this.sheet);
66
+ this.checkStyles(control, this.sheet, [
67
+ "cap",
68
+ "glass",
69
+ "pane",
70
+ "well",
71
+ "readout",
72
+ "value"
73
+ ]);
67
74
  this.#syncFill = bindFill(control, this.sizeProperty, signal);
68
75
  this.#syncFill(this.fill);
69
76
  }
@@ -1,5 +1,6 @@
1
- import { clamp } from "../lib/math.js";
1
+ import { clamp, roundTo } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
+ import { capturePointer } from "../lib/pointer-drag.js";
3
4
  import { dueRegions, readRegions } from "../lib/time-regions.js";
4
5
  import { SonicWaveElement } from "./wave-element.js";
5
6
  import { frameTimeline } from "../lib/frame-timeline.js";
@@ -7,6 +8,7 @@ import { isKind, writeKind } from "../lib/marker-band.js";
7
8
  import { createLabelRider } from "../lib/marker-rider.js";
8
9
  import { createTrackingClock } from "../lib/tracking-clock.js";
9
10
  import { waveformBuckets } from "../lib/waveform-buckets.js";
11
+ import { pinchFactor, wheelFactor, zoomKeyFactor } from "../lib/zoom-gesture.js";
10
12
 
11
13
  //#region src/elements/waveform.ts
12
14
  const colours = {
@@ -18,6 +20,13 @@ const colours = {
18
20
  waveEdge: "--_sonic-waveform-wave-edge"
19
21
  };
20
22
  const defaultZoom = 70;
23
+ const defaultZoomMin = 20;
24
+ const defaultZoomMax = 280;
25
+ const zoomKeys = /* @__PURE__ */ new Map([
26
+ ["+", 1],
27
+ ["-", -1],
28
+ ["=", 1]
29
+ ]);
21
30
  const amplitudeMargin = .94;
22
31
  const hatchPitchPx = 8;
23
32
  const placeholderWavelengthPx = 48;
@@ -117,7 +126,10 @@ var SonicWaveform = class extends SonicWaveElement {
117
126
  "playing",
118
127
  "playback-rate",
119
128
  "reduced-motion",
120
- "zoom"
129
+ "zoom",
130
+ "zoom-max",
131
+ "zoom-min",
132
+ "zoomable"
121
133
  ];
122
134
  get peaks() {
123
135
  return this.#peaks;
@@ -189,6 +201,26 @@ var SonicWaveform = class extends SonicWaveElement {
189
201
  set zoom(value) {
190
202
  this.reflect("zoom", value);
191
203
  }
204
+ get zoomable() {
205
+ return this.hasAttribute("zoomable");
206
+ }
207
+ set zoomable(isZoomable) {
208
+ this.reflect("zoomable", isZoomable);
209
+ }
210
+ get zoomMax() {
211
+ const zoomMax = this.numberAttribute("zoom-max", defaultZoomMax);
212
+ return Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);
213
+ }
214
+ set zoomMax(value) {
215
+ this.reflect("zoom-max", value);
216
+ }
217
+ get zoomMin() {
218
+ const zoomMin = this.numberAttribute("zoom-min", defaultZoomMin);
219
+ return zoomMin > 0 ? zoomMin : defaultZoomMin;
220
+ }
221
+ set zoomMin(value) {
222
+ this.reflect("zoom-min", value);
223
+ }
192
224
  control = renderWaveform();
193
225
  canvas = requireChild(this.control, ".sonic-waveform-canvas", HTMLCanvasElement);
194
226
  colours = colours;
@@ -198,15 +230,18 @@ var SonicWaveform = class extends SonicWaveElement {
198
230
  #asked = /* @__PURE__ */ new WeakSet();
199
231
  #askedKey;
200
232
  #clock = createTrackingClock();
233
+ #connection;
201
234
  #drawn;
202
235
  #ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
203
236
  #grabbed;
204
237
  #isAsking = false;
238
+ #isPinchSpent = false;
205
239
  #kindColours = /* @__PURE__ */ new Map();
206
240
  #paintedKey = "";
207
241
  #peaks;
208
242
  #pending = [];
209
243
  #pendingListedMs = /* @__PURE__ */ new Map();
244
+ #pinch;
210
245
  #playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
211
246
  #readTime;
212
247
  #requestPeaks;
@@ -217,13 +252,27 @@ var SonicWaveform = class extends SonicWaveElement {
217
252
  insetProperty: "--_sonic-waveform-label-inset",
218
253
  parkProperty: "--_sonic-waveform-label-park"
219
254
  });
255
+ #touches = /* @__PURE__ */ new Map();
256
+ #wheel;
257
+ attributeChangedCallback(name) {
258
+ super.attributeChangedCallback(name);
259
+ if (name === "zoomable") this.#bindWheel();
260
+ }
220
261
  connectedCallback() {
221
- this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom");
262
+ this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
222
263
  super.connectedCallback();
223
264
  }
224
265
  connect(signal) {
225
266
  super.connect(signal);
267
+ this.#connection = signal;
268
+ signal.addEventListener("abort", () => {
269
+ this.#connection = void 0;
270
+ this.#bindWheel();
271
+ }, { once: true });
272
+ this.#bindWheel();
273
+ this.#bindPinch(signal);
226
274
  this.bindGestures(this.control, signal, () => this.#grab());
275
+ this.#bindZoomKeys(signal);
227
276
  if (!("fonts" in document)) return;
228
277
  document.fonts.addEventListener("loadingdone", () => {
229
278
  this.renderMarkers();
@@ -309,12 +358,70 @@ var SonicWaveform = class extends SonicWaveElement {
309
358
  this.#repaintAfter(promise);
310
359
  }
311
360
  }
361
+ #bindPinch(signal) {
362
+ const control = this.control;
363
+ this.#touches.clear();
364
+ this.#pinch = void 0;
365
+ this.#isPinchSpent = false;
366
+ control.addEventListener("pointerdown", (event) => {
367
+ if (event.pointerType !== "touch" || !this.zoomable || this.isDisabled()) return;
368
+ this.#touches.set(event.pointerId, event.clientX);
369
+ if (this.#pinch || this.#isPinchSpent) return;
370
+ const pointerIds = this.#touchPair();
371
+ if (!pointerIds) return;
372
+ this.abandonHold();
373
+ capturePointer(control, event.pointerId);
374
+ this.#pinch = {
375
+ pointerIds,
376
+ startSpanPx: this.#spanPx(pointerIds),
377
+ startZoom: this.zoom
378
+ };
379
+ }, { signal });
380
+ control.addEventListener("pointermove", (event) => {
381
+ if (!this.#touches.has(event.pointerId)) return;
382
+ this.#touches.set(event.pointerId, event.clientX);
383
+ const pinch = this.#pinch;
384
+ if (!pinch?.pointerIds.includes(event.pointerId)) return;
385
+ this.#zoomTo(pinch.startZoom * pinchFactor(pinch.startSpanPx, this.#spanPx(pinch.pointerIds)));
386
+ }, { signal });
387
+ for (const type of ["pointerup", "pointercancel"]) control.addEventListener(type, (event) => {
388
+ if (!this.#touches.delete(event.pointerId)) return;
389
+ if (this.#pinch?.pointerIds.includes(event.pointerId)) {
390
+ this.#pinch = void 0;
391
+ this.#isPinchSpent = true;
392
+ }
393
+ if (this.#touches.size === 0) this.#isPinchSpent = false;
394
+ }, { signal });
395
+ }
396
+ #bindWheel() {
397
+ this.#wheel?.abort();
398
+ this.#wheel = void 0;
399
+ if (!this.zoomable || !this.#connection) return;
400
+ this.#wheel = new AbortController();
401
+ this.control.addEventListener("wheel", (event) => {
402
+ if (!event.ctrlKey && !event.metaKey || this.isDisabled()) return;
403
+ event.preventDefault();
404
+ this.#zoomTo(this.zoom * wheelFactor(event.deltaY, event.deltaMode));
405
+ }, {
406
+ passive: false,
407
+ signal: this.#wheel.signal
408
+ });
409
+ }
410
+ #bindZoomKeys(signal) {
411
+ this.control.addEventListener("keydown", (event) => {
412
+ const direction = this.#zoomKey(event);
413
+ if (direction === void 0) return;
414
+ event.preventDefault();
415
+ this.#zoomTo(this.zoom * zoomKeyFactor ** direction);
416
+ }, { signal });
417
+ }
312
418
  #duePending() {
313
419
  const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
314
420
  if (due.length < this.#pending.length) this.surface()?.requestFrame();
315
421
  return due;
316
422
  }
317
423
  #grab() {
424
+ if (this.#pinch || this.#isPinchSpent) return void 0;
318
425
  const drawn = this.#drawn ?? {
319
426
  seconds: this.value,
320
427
  startSeconds: this.value
@@ -391,6 +498,25 @@ var SonicWaveform = class extends SonicWaveElement {
391
498
  #sourceSeconds() {
392
499
  return this.#readTime?.() ?? this.playback();
393
500
  }
501
+ #spanPx([first, second]) {
502
+ return (this.#touches.get(second) ?? 0) - (this.#touches.get(first) ?? 0);
503
+ }
504
+ #touchPair() {
505
+ const [first, second, third] = this.#touches.keys();
506
+ if (first === void 0 || second === void 0 || third !== void 0) return void 0;
507
+ return [first, second];
508
+ }
509
+ #zoomKey(event) {
510
+ if (!this.zoomable || this.isDisabled() || event.target !== this.control) return void 0;
511
+ if (event.ctrlKey || event.metaKey || event.altKey) return void 0;
512
+ return zoomKeys.get(event.key);
513
+ }
514
+ #zoomTo(target) {
515
+ const next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);
516
+ if (next === this.zoom) return;
517
+ this.zoom = next;
518
+ this.dispatchEvent(new Event("sonic-zoom", { bubbles: true }));
519
+ }
394
520
  };
395
521
 
396
522
  //#endregion
@@ -238,7 +238,13 @@ var SonicXy = class extends SonicFormElement {
238
238
  const xy = this.#xy;
239
239
  this.keepControl(xy, signal);
240
240
  this.render();
241
- this.checkStyles(xy, "xy.css");
241
+ this.checkStyles(xy, "xy.css", [
242
+ "cap",
243
+ "glass",
244
+ "pane",
245
+ "well",
246
+ "readout"
247
+ ]);
242
248
  this.#bindPointer(xy, signal);
243
249
  this.#bindKeys(xy, signal);
244
250
  }
@@ -0,0 +1,19 @@
1
+ import { clamp } from "./math.js";
2
+
3
+ //#region src/lib/zoom-gesture.ts
4
+ const wheelRate = .01;
5
+ const linePx = 16;
6
+ const pixelMode = 0;
7
+ const wheelCapPx = 25;
8
+ const minSpanPx = 16;
9
+ const zoomKeyFactor = Math.exp(wheelCapPx * wheelRate);
10
+ function wheelFactor(deltaY, deltaMode) {
11
+ const pixels = deltaMode === pixelMode ? deltaY : deltaY * linePx;
12
+ return Math.exp(-clamp(pixels, -25, wheelCapPx) * wheelRate);
13
+ }
14
+ function pinchFactor(startSpanPx, spanPx) {
15
+ return Math.max(minSpanPx, Math.abs(spanPx)) / Math.max(minSpanPx, Math.abs(startSpanPx));
16
+ }
17
+
18
+ //#endregion
19
+ export { pinchFactor, wheelFactor, zoomKeyFactor };
@@ -114,7 +114,11 @@ var SonicButton = class extends SonicFormElement {
114
114
  }
115
115
  }, signal);
116
116
  this.render();
117
- this.checkStyles(button, "button.css");
117
+ this.checkStyles(button, "button.css", [
118
+ "cap",
119
+ "well",
120
+ "keycap"
121
+ ]);
118
122
  button.addEventListener("click", (event) => {
119
123
  if (this.#isSoftDisabled()) {
120
124
  event.preventDefault();
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css');\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(button, 'button.css', ['cap', 'well', 'keycap']);\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -64,7 +64,13 @@ var SonicDial = class extends SonicValueElement {
64
64
  marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
65
65
  }, signal);
66
66
  this.render();
67
- this.checkStyles(dial, "dial.css");
67
+ this.checkStyles(dial, "dial.css", [
68
+ "cap",
69
+ "readout",
70
+ "value",
71
+ ["glass", ".sonic-dial-readout"],
72
+ ["arc", ".sonic-dial-ring"]
73
+ ]);
68
74
  this.bindGestures(dial, signal, () => ({
69
75
  position: (event) => -event.clientY,
70
76
  travelPx: this.travelPx(dial, "--_sonic-dial-travel")
@@ -1 +1 @@
1
- {"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,UAAU;EACjC,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
1
+ {"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t['glass', '.sonic-dial-readout'],\n\t\t\t['arc', '.sonic-dial-ring'],\n\t\t]);\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAGA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,YAAY;GAClC;GACA;GACA;GACA,CAAC,SAAS,qBAAqB;GAC/B,CAAC,OAAO,kBAAkB;EAC3B,CAAC;EACD,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
@@ -66,7 +66,11 @@ var SonicDisplayElement = class extends SonicElement {
66
66
  },
67
67
  signal
68
68
  });
69
- this.checkStyles(control, this.sheet);
69
+ this.checkStyles(control, this.sheet, [
70
+ "glass",
71
+ "pane",
72
+ "well"
73
+ ]);
70
74
  this.#syncFill = bindFill(control, this.sizeProperty, signal);
71
75
  this.#syncFill(this.fill);
72
76
  }