@xsynaptic/sonic-ui 0.24.1 → 0.26.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 (137) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +6 -4
  3. package/dist/default/define/region.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/button.js +3 -0
  6. package/dist/default/elements/dial.js +3 -0
  7. package/dist/default/elements/envelope.js +14 -11
  8. package/dist/default/elements/form-element.js +10 -0
  9. package/dist/default/elements/meter.js +3 -0
  10. package/dist/default/elements/number.js +3 -0
  11. package/dist/default/elements/radio-group.js +4 -1
  12. package/dist/default/elements/readout.js +1 -1
  13. package/dist/default/elements/region-layer.js +253 -0
  14. package/dist/default/elements/region.js +161 -0
  15. package/dist/default/elements/slider.js +3 -0
  16. package/dist/default/elements/sonic-element.js +7 -9
  17. package/dist/default/elements/split.js +2 -2
  18. package/dist/default/elements/toggle.js +4 -4
  19. package/dist/default/elements/value-element.js +21 -3
  20. package/dist/default/elements/value-gestures.js +7 -5
  21. package/dist/default/elements/wave-element.js +15 -3
  22. package/dist/default/elements/waveform.js +38 -2
  23. package/dist/default/elements/wavestrip.js +90 -17
  24. package/dist/default/elements/xy.js +6 -11
  25. package/dist/default/index.js +2 -1
  26. package/dist/default/lib/canvas-surface.js +22 -1
  27. package/dist/default/lib/clock.js +1 -0
  28. package/dist/default/lib/drag-step.js +1 -1
  29. package/dist/default/lib/key-press.js +2 -2
  30. package/dist/default/lib/marker-band.js +40 -29
  31. package/dist/default/lib/region-drag.js +48 -0
  32. package/dist/dev/define/region.d.ts +2 -0
  33. package/dist/dev/define/region.js +8 -0
  34. package/dist/dev/define/region.js.map +1 -0
  35. package/dist/dev/define.d.ts +3 -1
  36. package/dist/dev/define.js +1 -0
  37. package/dist/dev/elements/button.d.ts +1 -0
  38. package/dist/dev/elements/button.js +3 -0
  39. package/dist/dev/elements/button.js.map +1 -1
  40. package/dist/dev/elements/dial.d.ts +1 -0
  41. package/dist/dev/elements/dial.js +3 -0
  42. package/dist/dev/elements/dial.js.map +1 -1
  43. package/dist/dev/elements/envelope.d.ts +1 -0
  44. package/dist/dev/elements/envelope.js +14 -11
  45. package/dist/dev/elements/envelope.js.map +1 -1
  46. package/dist/dev/elements/form-element.d.ts +1 -0
  47. package/dist/dev/elements/form-element.js +10 -0
  48. package/dist/dev/elements/form-element.js.map +1 -1
  49. package/dist/dev/elements/meter.d.ts +1 -0
  50. package/dist/dev/elements/meter.js +3 -0
  51. package/dist/dev/elements/meter.js.map +1 -1
  52. package/dist/dev/elements/number.d.ts +1 -0
  53. package/dist/dev/elements/number.js +3 -0
  54. package/dist/dev/elements/number.js.map +1 -1
  55. package/dist/dev/elements/radio-group.d.ts +1 -0
  56. package/dist/dev/elements/radio-group.js +4 -1
  57. package/dist/dev/elements/radio-group.js.map +1 -1
  58. package/dist/dev/elements/readout.js +1 -1
  59. package/dist/dev/elements/readout.js.map +1 -1
  60. package/dist/dev/elements/region-layer.js +254 -0
  61. package/dist/dev/elements/region-layer.js.map +1 -0
  62. package/dist/dev/elements/region.d.ts +30 -0
  63. package/dist/dev/elements/region.js +169 -0
  64. package/dist/dev/elements/region.js.map +1 -0
  65. package/dist/dev/elements/slider.d.ts +1 -0
  66. package/dist/dev/elements/slider.js +3 -0
  67. package/dist/dev/elements/slider.js.map +1 -1
  68. package/dist/dev/elements/sonic-element.d.ts +2 -0
  69. package/dist/dev/elements/sonic-element.js +8 -10
  70. package/dist/dev/elements/sonic-element.js.map +1 -1
  71. package/dist/dev/elements/split.js +2 -2
  72. package/dist/dev/elements/split.js.map +1 -1
  73. package/dist/dev/elements/toggle.js +4 -4
  74. package/dist/dev/elements/toggle.js.map +1 -1
  75. package/dist/dev/elements/value-element.d.ts +3 -1
  76. package/dist/dev/elements/value-element.js +21 -3
  77. package/dist/dev/elements/value-element.js.map +1 -1
  78. package/dist/dev/elements/value-gestures.js +7 -5
  79. package/dist/dev/elements/value-gestures.js.map +1 -1
  80. package/dist/dev/elements/wave-element.d.ts +8 -1
  81. package/dist/dev/elements/wave-element.js +15 -3
  82. package/dist/dev/elements/wave-element.js.map +1 -1
  83. package/dist/dev/elements/waveform.d.ts +7 -0
  84. package/dist/dev/elements/waveform.js +38 -2
  85. package/dist/dev/elements/waveform.js.map +1 -1
  86. package/dist/dev/elements/wavestrip.d.ts +17 -1
  87. package/dist/dev/elements/wavestrip.js +90 -17
  88. package/dist/dev/elements/wavestrip.js.map +1 -1
  89. package/dist/dev/elements/xy.d.ts +1 -0
  90. package/dist/dev/elements/xy.js +6 -11
  91. package/dist/dev/elements/xy.js.map +1 -1
  92. package/dist/dev/index.d.ts +3 -2
  93. package/dist/dev/index.js +2 -1
  94. package/dist/dev/lib/canvas-surface.js +22 -1
  95. package/dist/dev/lib/canvas-surface.js.map +1 -1
  96. package/dist/dev/lib/clock.js +1 -0
  97. package/dist/dev/lib/clock.js.map +1 -1
  98. package/dist/dev/lib/drag-step.js +1 -1
  99. package/dist/dev/lib/drag-step.js.map +1 -1
  100. package/dist/dev/lib/key-press.js +2 -2
  101. package/dist/dev/lib/key-press.js.map +1 -1
  102. package/dist/dev/lib/marker-band.d.ts +7 -0
  103. package/dist/dev/lib/marker-band.js +40 -29
  104. package/dist/dev/lib/marker-band.js.map +1 -1
  105. package/dist/dev/lib/region-drag.js +49 -0
  106. package/dist/dev/lib/region-drag.js.map +1 -0
  107. package/dist/styles/button.css +23 -13
  108. package/dist/styles/controls.css +1 -0
  109. package/dist/styles/dial.css +41 -24
  110. package/dist/styles/envelope.css +12 -5
  111. package/dist/styles/led.css +8 -8
  112. package/dist/styles/material/arc.css +2 -2
  113. package/dist/styles/material/cap.css +34 -13
  114. package/dist/styles/material/glass.css +1 -1
  115. package/dist/styles/material/groove.css +3 -3
  116. package/dist/styles/material/keycap.css +2 -2
  117. package/dist/styles/material/lens.css +1 -1
  118. package/dist/styles/material/pane.css +20 -6
  119. package/dist/styles/material/readout.css +14 -5
  120. package/dist/styles/material/{bracket.css → reticle.css} +15 -8
  121. package/dist/styles/material/well.css +2 -2
  122. package/dist/styles/material.css +1 -1
  123. package/dist/styles/meter.css +29 -16
  124. package/dist/styles/number.css +3 -2
  125. package/dist/styles/panel.css +3 -3
  126. package/dist/styles/region.css +94 -0
  127. package/dist/styles/ring.css +2 -1
  128. package/dist/styles/screen.css +7 -5
  129. package/dist/styles/segmented.css +5 -4
  130. package/dist/styles/slider.css +24 -15
  131. package/dist/styles/spectrum.css +9 -1
  132. package/dist/styles/switch.css +9 -2
  133. package/dist/styles/toggle.css +5 -4
  134. package/dist/styles/waveform.css +10 -3
  135. package/dist/styles/wavestrip.css +17 -8
  136. package/dist/styles/xy.css +24 -11
  137. package/package.json +3 -2
@@ -1,10 +1,11 @@
1
- import { clampProportion } from "../lib/math.js";
1
+ import { clamp, clampProportion } from "../lib/math.js";
2
2
  import { requireChild, template } from "../lib/render.js";
3
3
  import { readPxProperty } from "../lib/read-px-property.js";
4
4
  import { scrubRegions } from "../lib/scrub.js";
5
5
  import { sortedRegions } from "../lib/time-regions.js";
6
6
  import { SonicWaveElement } from "./wave-element.js";
7
7
  import { createMarkerBand } from "../lib/marker-band.js";
8
+ import { RegionLayer } from "./region-layer.js";
8
9
  import { stripBars, stripGroove, stripRegions } from "../lib/strip-scene.js";
9
10
 
10
11
  //#region src/elements/wavestrip.ts
@@ -23,9 +24,10 @@ const lengths = {
23
24
  };
24
25
  const cancelZonePx = 48;
25
26
  const renderWavestrip = template(`
26
- <div class="sonic-wavestrip" role="slider" tabindex="0">
27
- <canvas class="sonic-wavestrip-canvas" aria-hidden="true"></canvas>
27
+ <div class="sonic-wavestrip">
28
+ <canvas class="sonic-wavestrip-canvas" role="slider" tabindex="0"></canvas>
28
29
  <div class="sonic-wavestrip-markers" aria-hidden="true"></div>
30
+ <div class="sonic-wavestrip-regions"></div>
29
31
  <div class="sonic-wavestrip-readout" popover="manual">
30
32
  <span></span>
31
33
  <input
@@ -63,6 +65,14 @@ var SonicWavestrip = class extends SonicWaveElement {
63
65
  set cancellable(isCancellable) {
64
66
  this.reflect("cancellable", isCancellable);
65
67
  }
68
+ /** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */
69
+ get formatSpokenRegion() {
70
+ return this.#formatSpokenRegion;
71
+ }
72
+ set formatSpokenRegion(format) {
73
+ this.#formatSpokenRegion = format;
74
+ this.#regions?.render();
75
+ }
66
76
  /** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */
67
77
  get peaks() {
68
78
  return this.#peaks;
@@ -72,6 +82,25 @@ var SonicWavestrip = class extends SonicWaveElement {
72
82
  this.renderEmpty();
73
83
  this.surface()?.invalidate();
74
84
  }
85
+ /** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */
86
+ get preview() {
87
+ return this.#preview;
88
+ }
89
+ set preview(seconds) {
90
+ this.#preview = Number.isFinite(seconds) ? seconds ?? void 0 : void 0;
91
+ this.render();
92
+ }
93
+ /** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */
94
+ get renderMarker() {
95
+ return this.#renderMarker;
96
+ }
97
+ set renderMarker(render) {
98
+ this.#renderMarker = render;
99
+ this.#drawMarker = render && ((marker, element) => {
100
+ render({ ...marker }, element);
101
+ });
102
+ if (this.isBound()) this.renderMarkers();
103
+ }
75
104
  control = renderWavestrip();
76
105
  canvas = requireChild(this.control, ".sonic-wavestrip-canvas", HTMLCanvasElement);
77
106
  colours = colours;
@@ -81,43 +110,75 @@ var SonicWavestrip = class extends SonicWaveElement {
81
110
  sizeProperty = "--_sonic-wavestrip-size";
82
111
  #bars;
83
112
  #buffered = [];
113
+ #drawMarker;
114
+ #formatSpokenRegion;
84
115
  #markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
85
116
  #paintedKey = "";
86
117
  #peaks;
118
+ #preview;
119
+ #regions;
120
+ #renderMarker;
87
121
  /** Viewport x of a time, clamped to the strip */
88
122
  clientXOf(value) {
89
123
  const { startPx, travelPx } = this.#axis();
90
124
  return startPx + this.mapping().proportionOf(value) * travelPx;
91
125
  }
92
126
  connectedCallback() {
93
- this.upgradeProperties("buffered", "cancellable", "peaks");
127
+ this.upgradeProperties("buffered", "cancellable", "formatSpokenRegion", "peaks", "preview", "renderMarker");
94
128
  super.connectedCallback();
95
129
  }
130
+ valueFromPoint(clientX, _clientY) {
131
+ const { startPx, travelPx } = this.#axis();
132
+ const mapping = this.mapping();
133
+ return clamp(mapping.valueAt(clampProportion((clientX - startPx) / travelPx)), ...mapping.bounds);
134
+ }
96
135
  connect(signal) {
97
136
  const { control } = this;
137
+ this.#regions = new RegionLayer({
138
+ axis: () => this.#axis(),
139
+ control,
140
+ element: this,
141
+ isDisabled: () => this.isDisabled(),
142
+ layer: requireChild(control, ".sonic-wavestrip-regions", HTMLDivElement),
143
+ mapping: () => this.mapping(),
144
+ outside: () => this.#outside(),
145
+ seek: (event) => {
146
+ if (this.input(this.valueFromPoint(event.clientX, event.clientY))) this.dispatchEvent(new Event("change", { bubbles: true }));
147
+ },
148
+ speak: ({ end, start }) => this.#formatSpokenRegion?.(start, end) ?? this.spokenText(start) ?? this.notation.format(start)
149
+ }, signal);
98
150
  super.connect(signal);
99
151
  this.bindHover(control, signal, {
152
+ changed: () => {
153
+ if (this.#preview !== void 0) this.render();
154
+ },
100
155
  place: () => {
101
156
  this.#placeReadout();
102
157
  },
103
- valueAt: (event) => {
104
- const axis = this.#axis();
105
- const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
106
- const mapping = this.mapping();
107
- return this.#markerAt(event, axis) ?? mapping.snap(mapping.valueAt(at));
108
- }
158
+ valueAt: (event) => this.#markerStartAt(event, this.#axis()) ?? this.mapping().snap(this.valueFromPoint(event.clientX, event.clientY))
109
159
  });
110
160
  this.bindGestures(control, signal, (event) => this.#grab(event));
111
161
  }
112
162
  draw() {
113
163
  const surface = this.surface();
114
164
  this.#placeReadout();
165
+ this.#regions?.follow();
115
166
  this.showCurrentMarker(this.scrubState().played);
116
167
  if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
117
168
  }
169
+ focusTarget() {
170
+ return this.canvas;
171
+ }
118
172
  isEmpty() {
119
173
  return !this.#peaks || this.#peaks.length === 0;
120
174
  }
175
+ /** The first point marker is the one a press there snaps to */
176
+ markersInReach(point, widthPx) {
177
+ const regions = this.markerProportions().flatMap(([from, to], index) => {
178
+ return this.markerList()[index]?.end !== void 0 && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];
179
+ });
180
+ return [...this.#markerBand.inReach(point, widthPx), ...regions];
181
+ }
121
182
  paint(context, frame) {
122
183
  const { look, size } = frame;
123
184
  const strip = this.#stripRegions(size.width);
@@ -134,11 +195,14 @@ var SonicWavestrip = class extends SonicWaveElement {
134
195
  ]);
135
196
  }
136
197
  renderMarkers() {
137
- this.#markerBand.render(this.markerList(), this.markerProportions());
198
+ this.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);
138
199
  }
139
200
  scrubsKeyRepeat() {
140
201
  return true;
141
202
  }
203
+ usesChild(child) {
204
+ return child instanceof Element && child.localName === "sonic-region";
205
+ }
142
206
  #axis() {
143
207
  const box = this.canvas.getBoundingClientRect();
144
208
  return {
@@ -163,6 +227,13 @@ var SonicWavestrip = class extends SonicWaveElement {
163
227
  }
164
228
  #grab(event) {
165
229
  const axis = this.#axis();
230
+ const marker = this.#markerStartAt(event, axis);
231
+ const regions = this.#regions;
232
+ if (regions?.isHeld() === true) {
233
+ regions.abandon();
234
+ return;
235
+ }
236
+ if (marker === void 0 && regions?.take(event) === true) return void 0;
166
237
  const at = (event.clientX - axis.startPx) / axis.travelPx;
167
238
  const outside = this.#outside();
168
239
  const grabbed = {
@@ -170,16 +241,16 @@ var SonicWavestrip = class extends SonicWaveElement {
170
241
  position: axis.position,
171
242
  travelPx: axis.travelPx
172
243
  };
173
- this.input(this.#markerAt(event, axis) ?? this.mapping().valueAt(at));
244
+ this.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));
174
245
  return outside ? {
175
246
  ...grabbed,
176
247
  outside
177
248
  } : grabbed;
178
249
  }
179
- #markerAt(event, { startPx, topPx, travelPx }) {
180
- const index = this.#markerBand.at({
181
- x: event.clientX - startPx,
182
- y: event.clientY - topPx
250
+ #markerStartAt({ clientX, clientY }, { startPx, topPx, travelPx }) {
251
+ const [index] = this.#markerBand.inReach({
252
+ x: clientX - startPx,
253
+ y: clientY - topPx
183
254
  }, travelPx);
184
255
  return index === void 0 ? void 0 : this.markerList()[index]?.start;
185
256
  }
@@ -196,7 +267,9 @@ var SonicWavestrip = class extends SonicWaveElement {
196
267
  #stripRegions(width) {
197
268
  const mapping = this.mapping();
198
269
  const { proportionOf } = mapping;
199
- const { played, scrub } = scrubRegions(this.scrubState(), this.value, mapping);
270
+ const state = this.scrubState();
271
+ const preview = state.from !== void 0 || this.hoverValue !== void 0 || this.#preview === void 0 ? void 0 : proportionOf(this.#preview);
272
+ const { played, scrub = preview } = scrubRegions(state, this.value, mapping);
200
273
  return stripRegions({
201
274
  buffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
202
275
  played,
@@ -30,7 +30,7 @@ const orientations = {
30
30
  };
31
31
  const renderXy = template(`
32
32
  <div class="sonic-xy">
33
- <div class="sonic-xy-bracket" aria-hidden="true"></div>
33
+ <div class="sonic-xy-reticle" aria-hidden="true"></div>
34
34
  <div class="sonic-xy-field" aria-hidden="true"></div>
35
35
  <div class="sonic-xy-puck">
36
36
  <div class="sonic-xy-axis" data-sonic-axis="x" role="slider" tabindex="0"></div>
@@ -265,6 +265,9 @@ var SonicXy = class extends SonicFormElement {
265
265
  this.x = x;
266
266
  this.y = y;
267
267
  }
268
+ stateTarget() {
269
+ return this.#xy;
270
+ }
268
271
  #axisText(axis) {
269
272
  const { value } = this.#models[axis];
270
273
  return `${this.getAttribute(`${axis}-label`) ?? axis.toUpperCase()} ${this.#formatValue?.(value, axis) ?? String(value)}`;
@@ -404,16 +407,8 @@ var SonicXy = class extends SonicFormElement {
404
407
  }
405
408
  }
406
409
  #renderForm() {
407
- const { name, x, y } = this;
408
- const state = `${String(x)},${String(y)}`;
409
- if (name === "") {
410
- this.writeFormValue(null, state);
411
- return;
412
- }
413
- const fields = new FormData();
414
- fields.set(`${name}.x`, String(x));
415
- fields.set(`${name}.y`, String(y));
416
- this.writeFormValue(fields, state);
410
+ const { x, y } = this;
411
+ this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
417
412
  }
418
413
  #renderReadout() {
419
414
  const { isRevealed } = this.#claim;
@@ -4,6 +4,7 @@ import { envelopeCurve } from "./lib/envelope-curve.js";
4
4
  import { SonicEnvelope } from "./elements/envelope.js";
5
5
  import { SonicMeter } from "./elements/meter.js";
6
6
  import { SonicNumber } from "./elements/number.js";
7
+ import { SonicRegion } from "./elements/region.js";
7
8
  import { SonicSegmented } from "./elements/segmented.js";
8
9
  import { SonicSlider } from "./elements/slider.js";
9
10
  import { SonicSpectrum } from "./elements/spectrum.js";
@@ -17,4 +18,4 @@ import { SonicXy } from "./elements/xy.js";
17
18
  import { crossfadeGains } from "./lib/crossfade.js";
18
19
  import { formatPercent, parsePercent } from "./lib/percent.js";
19
20
 
20
- export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
21
+ export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
@@ -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) => {
@@ -1,6 +1,7 @@
1
1
  //#region src/lib/clock.ts
2
2
  const sign = /^[-−]/;
3
3
  function formatClock(seconds) {
4
+ if (!Number.isFinite(seconds)) return "";
4
5
  const total = Math.floor(Math.abs(seconds));
5
6
  const hours = Math.floor(total / 3600);
6
7
  const minutes = Math.floor(total / 60) % 60;
@@ -96,4 +96,4 @@ function stepDrag(mapping, state, move) {
96
96
  }
97
97
 
98
98
  //#endregion
99
- export { dragThresholdPx, startDrag, stepDrag };
99
+ export { dragThresholdPx, fineFactor, startDrag, stepDrag };
@@ -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();
@@ -2,7 +2,7 @@ import { readPxProperty } from "./read-px-property.js";
2
2
 
3
3
  //#region src/lib/marker-band.ts
4
4
  const kindPattern = /^[a-z][a-z0-9-]*$/;
5
- const snapSlopPx = 4;
5
+ const snapMarginPx = 4;
6
6
  function isKind(kind) {
7
7
  return kind !== void 0 && kindPattern.test(kind);
8
8
  }
@@ -13,45 +13,61 @@ function writeKind(part, kind, fallback) {
13
13
  }
14
14
  part.style.setProperty("--_sonic-marker", `var(--sonic-marker-${kind}, var(${fallback}))`);
15
15
  }
16
- function renderMarker(marker, [from, to], lane) {
16
+ function createPart(marker) {
17
17
  const part = document.createElement("div");
18
- part.className = lane === void 0 ? "sonic-wavestrip-region" : "sonic-wavestrip-marker";
19
- if (lane !== void 0) part.style.setProperty("--_sonic-marker-lane", String(lane));
20
- part.style.setProperty("--_sonic-marker-from", String(from));
21
- part.style.setProperty("--_sonic-marker-to", String(to));
18
+ part.className = marker.end === void 0 ? "sonic-wavestrip-marker" : "sonic-wavestrip-region";
22
19
  writeKind(part, marker.kind, "--_sonic-marker-default");
23
20
  if (marker.dimmed === true) part.dataset.sonicDimmed = "";
24
21
  return part;
25
22
  }
26
23
  function createMarkerBand(band) {
27
- let dots = [];
24
+ let built;
25
+ let placed = [];
28
26
  const px = (property) => readPxProperty(getComputedStyle(band), property, 0);
27
+ function build(markers, draw) {
28
+ const parts = markers.map((marker) => {
29
+ const part = createPart(marker);
30
+ if (draw && marker.end === void 0) {
31
+ part.style.setProperty("--_sonic-marker-paint", "transparent");
32
+ draw(marker, part);
33
+ }
34
+ return part;
35
+ });
36
+ band.replaceChildren(...parts);
37
+ built = {
38
+ draw,
39
+ markers,
40
+ parts
41
+ };
42
+ return parts;
43
+ }
29
44
  return {
30
- at: ({ x, y }, widthPx) => {
45
+ inReach: (point, widthPx) => {
31
46
  const size = px("--_sonic-marker-size");
32
47
  const step = px("--_sonic-marker-step");
33
- const nearest = nearestDot({
34
- x,
35
- y
36
- }, dots.map(({ at, lane }) => ({
48
+ return centersInReach(point, placed.map(({ at, lane }) => ({
37
49
  x: at * widthPx,
38
50
  y: size / 2 + lane * step
39
- })), size / 2 + snapSlopPx);
40
- return nearest === void 0 ? void 0 : dots[nearest]?.index;
51
+ })), size / 2 + snapMarginPx).flatMap((order) => placed[order]?.index ?? []);
41
52
  },
42
- render: (markers, proportions) => {
53
+ render: (markers, proportions, draw) => {
43
54
  const widthPx = band.getBoundingClientRect().width;
44
55
  const points = markers.flatMap((marker, index) => marker.end === void 0 ? [{
45
56
  at: proportions[index]?.[0] ?? 0,
46
57
  index
47
58
  }] : []);
48
59
  const lanes = markerLanes(points.map(({ at }) => at * widthPx), px("--_sonic-marker-step"));
49
- dots = points.map((point, order) => ({
60
+ const parts = built?.markers === markers && built.draw === draw ? built.parts : build(markers, draw);
61
+ placed = points.map((point, order) => ({
50
62
  ...point,
51
63
  lane: lanes[order] ?? 0
52
64
  }));
53
- const laneOf = new Map(dots.map(({ index, lane }) => [index, lane]));
54
- band.replaceChildren(...markers.map((marker, index) => renderMarker(marker, proportions[index] ?? [0, 0], laneOf.get(index))));
65
+ for (const [index, part] of parts.entries()) {
66
+ const [from, to] = proportions[index] ?? [0, 0];
67
+ part.style.setProperty("--_sonic-marker-from", String(from));
68
+ part.style.setProperty("--_sonic-marker-to", String(to));
69
+ }
70
+ for (const { index, lane } of placed) parts[index]?.style.setProperty("--_sonic-marker-lane", String(lane));
55
71
  }
56
72
  };
57
73
  }
@@ -64,17 +80,12 @@ function markerLanes(positions, minDistance) {
64
80
  return lane;
65
81
  });
66
82
  }
67
- function nearestDot(point, dots, reach) {
68
- let nearest;
69
- let nearestDistance = reach * reach;
70
- for (const [index, dot] of dots.entries()) {
71
- const distance = (dot.x - point.x) ** 2 + (dot.y - point.y) ** 2;
72
- if (distance > nearestDistance) continue;
73
- nearest = index;
74
- nearestDistance = distance;
75
- }
76
- return nearest;
83
+ function centersInReach(point, centers, reach) {
84
+ return centers.map((center, index) => ({
85
+ distance: (center.x - point.x) ** 2 + (center.y - point.y) ** 2,
86
+ index
87
+ })).filter(({ distance }) => distance <= reach * reach).toSorted((first, second) => first.distance - second.distance || second.index - first.index).map(({ index }) => index);
77
88
  }
78
89
 
79
90
  //#endregion
80
- export { createMarkerBand, isKind, markerLanes, nearestDot, writeKind };
91
+ export { centersInReach, createMarkerBand, isKind, markerLanes, writeKind };
@@ -0,0 +1,48 @@
1
+ import { clamp, clampProportion, trimFloat } from "./math.js";
2
+ import { fineFactor } from "./drag-step.js";
3
+
4
+ //#region src/lib/region-drag.ts
5
+ function regionAt(bodies, x) {
6
+ const index = bodies.findLastIndex((body) => body !== void 0 && x >= body[0] && x <= body[1]);
7
+ return index === -1 ? void 0 : index;
8
+ }
9
+ function regionTravel({ end, start }, [low, high]) {
10
+ return [low, Math.max(low, trimFloat(high - (end - start)))];
11
+ }
12
+ function moveRegionTo(mapping, span, start) {
13
+ const moved = clamp(mapping.snap(start), ...regionTravel(span, mapping.bounds));
14
+ return {
15
+ end: trimFloat(moved + (span.end - span.start)),
16
+ start: moved
17
+ };
18
+ }
19
+ function startRegionDrag({ position, ...fixed }) {
20
+ return {
21
+ ...fixed,
22
+ isEngaged: false,
23
+ lastPosition: position,
24
+ startPosition: position,
25
+ travelPx: 0
26
+ };
27
+ }
28
+ function stepRegionDrag(mapping, state, move) {
29
+ if (!(state.isEngaged || Math.abs(move.position - state.startPosition) >= state.thresholdPx)) return {
30
+ span: void 0,
31
+ state
32
+ };
33
+ const pace = move.isFine ? fineFactor : 1;
34
+ const travelPx = state.travelPx + (move.position - state.lastPosition) * pace;
35
+ const at = mapping.proportionOf(state.from.start) + travelPx / state.widthPx;
36
+ return {
37
+ span: moveRegionTo(mapping, state.from, mapping.valueAt(clampProportion(at))),
38
+ state: {
39
+ ...state,
40
+ isEngaged: true,
41
+ lastPosition: move.position,
42
+ travelPx
43
+ }
44
+ };
45
+ }
46
+
47
+ //#endregion
48
+ export { moveRegionTo, regionAt, regionTravel, startRegionDrag, stepRegionDrag };
@@ -0,0 +1,2 @@
1
+ import { SonicRegion } from "../elements/region.js";
2
+ export type { SonicRegion };
@@ -0,0 +1,8 @@
1
+ import { defineOnce } from "../elements/define-once.js";
2
+ import { SonicRegion } from "../elements/region.js";
3
+
4
+ //#region src/define/region.ts
5
+ defineOnce("sonic-region", SonicRegion);
6
+
7
+ //#endregion
8
+ //# sourceMappingURL=region.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"region.js","names":[],"sources":["../../../src/define/region.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicRegion } from '#elements/region.ts';\n\ndefineOnce('sonic-region', SonicRegion);\n\nexport type { SonicRegion } from '#elements/region.ts';\n"],"mappings":";;;;AAGA,WAAW,gBAAgB,WAAW"}
@@ -8,6 +8,8 @@ import { SonicMeter } from "./elements/meter.js";
8
8
  import "./define/meter.js";
9
9
  import { SonicNumber } from "./elements/number.js";
10
10
  import "./define/number.js";
11
+ import { SonicRegion } from "./elements/region.js";
12
+ import "./define/region.js";
11
13
  import { SonicSegmented } from "./elements/segmented.js";
12
14
  import "./define/segmented.js";
13
15
  import { SonicSlider } from "./elements/slider.js";
@@ -26,4 +28,4 @@ import { SonicWavestrip } from "./elements/wavestrip.js";
26
28
  import "./define/wavestrip.js";
27
29
  import { SonicXy } from "./elements/xy.js";
28
30
  import "./define/xy.js";
29
- export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy };
31
+ export type { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy };
@@ -3,6 +3,7 @@ import "./define/dial.js";
3
3
  import "./define/envelope.js";
4
4
  import "./define/meter.js";
5
5
  import "./define/number.js";
6
+ import "./define/region.js";
6
7
  import "./define/segmented.js";
7
8
  import "./define/slider.js";
8
9
  import "./define/spectrum.js";
@@ -35,6 +35,7 @@ export declare class SonicButton extends SonicFormElement {
35
35
  protected focusTarget(): HTMLElement;
36
36
  protected render(): void;
37
37
  protected restoreState(state: string): void;
38
+ protected stateTarget(): HTMLElement;
38
39
  }
39
40
  //#endregion
40
41
  //# sourceMappingURL=button.d.ts.map
@@ -154,6 +154,9 @@ var SonicButton = class extends SonicFormElement {
154
154
  restoreState(state) {
155
155
  if (this.latching) this.pressed = state === "true";
156
156
  }
157
+ stateTarget() {
158
+ return this.#button;
159
+ }
157
160
  #bindMomentary(button, signal) {
158
161
  const holding = bindHold(button, {
159
162
  canHold: () => !this.isDisabled() && !this.#isSoftDisabled(),
@@ -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 const __DEV__: boolean;\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\t/** Shows the legend child whose `data-sonic-when` has this name */\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\t/** Submitted with the form while a latching button is pressed */\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\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t});\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 || event.defaultPrevented) return;\n\t\t\t\tif (!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 const __DEV__: boolean;\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\t/** Shows the legend child whose `data-sonic-when` has this name */\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\t/** Submitted with the form while a latching button is pressed */\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\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t});\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\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.#button;\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 || event.defaultPrevented) return;\n\t\t\t\tif (!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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"}
@@ -21,6 +21,7 @@ export declare class SonicDial extends SonicValueElement {
21
21
  protected draw(): void;
22
22
  protected focusTarget(): HTMLElement;
23
23
  protected isWrapping(): boolean;
24
+ protected stateTarget(): HTMLElement;
24
25
  }
25
26
  //#endregion
26
27
  //# sourceMappingURL=dial.d.ts.map
@@ -89,6 +89,9 @@ var SonicDial = class extends SonicValueElement {
89
89
  isWrapping() {
90
90
  return this.endless;
91
91
  }
92
+ stateTarget() {
93
+ return this.#dial;
94
+ }
92
95
  };
93
96
 
94
97
  //#endregion