@xsynaptic/sonic-ui 0.25.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
  4. package/dist/default/elements/button.js +12 -6
  5. package/dist/default/elements/dial.js +9 -10
  6. package/dist/default/elements/envelope.js +26 -20
  7. package/dist/default/elements/field-gesture.js +49 -8
  8. package/dist/default/elements/meter.js +6 -5
  9. package/dist/default/elements/number.js +5 -11
  10. package/dist/default/elements/position-group.js +4 -4
  11. package/dist/default/elements/radio-group.js +7 -7
  12. package/dist/default/elements/region-layer.js +3 -1
  13. package/dist/default/elements/region.js +5 -1
  14. package/dist/default/elements/segmented.js +7 -4
  15. package/dist/default/elements/slider.js +12 -10
  16. package/dist/default/elements/sonic-element.js +10 -13
  17. package/dist/default/elements/spectrum.js +3 -3
  18. package/dist/default/elements/switch.js +11 -6
  19. package/dist/default/elements/toggle.js +16 -10
  20. package/dist/default/elements/value-element.js +26 -4
  21. package/dist/default/elements/wave-element.js +11 -9
  22. package/dist/default/elements/waveform.js +1 -4
  23. package/dist/default/elements/wavestrip.js +1 -1
  24. package/dist/default/elements/xy.js +34 -44
  25. package/dist/default/lib/canvas-surface.js +35 -2
  26. package/dist/default/lib/key-press.js +2 -2
  27. package/dist/default/lib/render.js +2 -2
  28. package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
  29. package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
  30. package/dist/dev/elements/analyser-element.js.map +1 -0
  31. package/dist/dev/elements/button.d.ts +4 -0
  32. package/dist/dev/elements/button.js +12 -11
  33. package/dist/dev/elements/button.js.map +1 -1
  34. package/dist/dev/elements/dial.d.ts +4 -1
  35. package/dist/dev/elements/dial.js +9 -14
  36. package/dist/dev/elements/dial.js.map +1 -1
  37. package/dist/dev/elements/envelope.d.ts +5 -0
  38. package/dist/dev/elements/envelope.js +26 -24
  39. package/dist/dev/elements/envelope.js.map +1 -1
  40. package/dist/dev/elements/field-gesture.js +49 -8
  41. package/dist/dev/elements/field-gesture.js.map +1 -1
  42. package/dist/dev/elements/meter.d.ts +2 -0
  43. package/dist/dev/elements/meter.js +6 -6
  44. package/dist/dev/elements/meter.js.map +1 -1
  45. package/dist/dev/elements/number.d.ts +2 -1
  46. package/dist/dev/elements/number.js +5 -12
  47. package/dist/dev/elements/number.js.map +1 -1
  48. package/dist/dev/elements/position-group.js +4 -4
  49. package/dist/dev/elements/position-group.js.map +1 -1
  50. package/dist/dev/elements/radio-group.d.ts +1 -1
  51. package/dist/dev/elements/radio-group.js +7 -7
  52. package/dist/dev/elements/radio-group.js.map +1 -1
  53. package/dist/dev/elements/region-layer.js +3 -1
  54. package/dist/dev/elements/region-layer.js.map +1 -1
  55. package/dist/dev/elements/region.d.ts +1 -0
  56. package/dist/dev/elements/region.js +5 -1
  57. package/dist/dev/elements/region.js.map +1 -1
  58. package/dist/dev/elements/segmented.d.ts +4 -2
  59. package/dist/dev/elements/segmented.js +7 -9
  60. package/dist/dev/elements/segmented.js.map +1 -1
  61. package/dist/dev/elements/slider.d.ts +4 -1
  62. package/dist/dev/elements/slider.js +12 -14
  63. package/dist/dev/elements/slider.js.map +1 -1
  64. package/dist/dev/elements/sonic-element.d.ts +6 -7
  65. package/dist/dev/elements/sonic-element.js +16 -91
  66. package/dist/dev/elements/sonic-element.js.map +1 -1
  67. package/dist/dev/elements/spectrum.d.ts +2 -2
  68. package/dist/dev/elements/spectrum.js +3 -3
  69. package/dist/dev/elements/spectrum.js.map +1 -1
  70. package/dist/dev/elements/switch.d.ts +4 -1
  71. package/dist/dev/elements/switch.js +11 -10
  72. package/dist/dev/elements/switch.js.map +1 -1
  73. package/dist/dev/elements/toggle.d.ts +4 -1
  74. package/dist/dev/elements/toggle.js +16 -15
  75. package/dist/dev/elements/toggle.js.map +1 -1
  76. package/dist/dev/elements/value-element.d.ts +4 -3
  77. package/dist/dev/elements/value-element.js +26 -4
  78. package/dist/dev/elements/value-element.js.map +1 -1
  79. package/dist/dev/elements/wave-element.d.ts +2 -1
  80. package/dist/dev/elements/wave-element.js +11 -13
  81. package/dist/dev/elements/wave-element.js.map +1 -1
  82. package/dist/dev/elements/waveform.d.ts +0 -1
  83. package/dist/dev/elements/waveform.js +1 -4
  84. package/dist/dev/elements/waveform.js.map +1 -1
  85. package/dist/dev/elements/wavestrip.js +1 -1
  86. package/dist/dev/elements/wavestrip.js.map +1 -1
  87. package/dist/dev/elements/xy.d.ts +4 -0
  88. package/dist/dev/elements/xy.js +34 -48
  89. package/dist/dev/elements/xy.js.map +1 -1
  90. package/dist/dev/index.d.ts +3 -1
  91. package/dist/dev/lib/canvas-surface.js +35 -2
  92. package/dist/dev/lib/canvas-surface.js.map +1 -1
  93. package/dist/dev/lib/check-styles.d.ts +8 -0
  94. package/dist/dev/lib/check-styles.js +87 -0
  95. package/dist/dev/lib/check-styles.js.map +1 -0
  96. package/dist/dev/lib/key-press.js +2 -2
  97. package/dist/dev/lib/key-press.js.map +1 -1
  98. package/dist/dev/lib/render.js +2 -2
  99. package/dist/dev/lib/render.js.map +1 -1
  100. package/dist/styles/button.css +11 -9
  101. package/dist/styles/dial.css +6 -6
  102. package/dist/styles/envelope.css +6 -0
  103. package/dist/styles/led.css +8 -8
  104. package/dist/styles/material/cap.css +13 -2
  105. package/dist/styles/material/pane.css +7 -1
  106. package/dist/styles/material/readout.css +10 -2
  107. package/dist/styles/material/reticle.css +2 -2
  108. package/dist/styles/meter.css +11 -11
  109. package/dist/styles/number.css +1 -1
  110. package/dist/styles/region.css +6 -0
  111. package/dist/styles/segmented.css +3 -3
  112. package/dist/styles/slider.css +12 -12
  113. package/dist/styles/switch.css +6 -0
  114. package/dist/styles/toggle.css +3 -3
  115. package/dist/styles/waveform.css +7 -1
  116. package/dist/styles/wavestrip.css +6 -0
  117. package/dist/styles/xy.css +15 -3
  118. package/package.json +1 -1
  119. package/dist/dev/elements/display-element.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @xsynaptic/sonic-ui
2
2
 
3
+ ## 0.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - - Envelope: a handle drag reports its reveal as the other controls with a readout do, with `data-sonic-revealed` on `.sonic-envelope`, a bubbling `sonic-reveal` event and a read-only `revealed` property.
8
+ - The `FieldAxis` and `SplitMode` types are exported.
9
+
10
+ ## 0.26.0
11
+
12
+ ### Minor Changes
13
+
14
+ - - Breaking: a state is an attribute on the control's drawn part, `data-sonic-<state>`, and no longer a custom state on the host. `sonic-slider:state(dragging) > .sonic-slider` becomes `.sonic-slider[data-sonic-dragging]`; a region's is on its `.sonic-region`. Every look that follows a state now draws at the browser floor, a meter's ladder included.
15
+ - Breaking: the mark on a button, an option or a toggle position held down by a key or a pointer is `data-sonic-active`, renamed from `data-sonic-pressed`, which on `.sonic-button` is now the `pressed` state.
16
+ - Wave controls and the spectrum: a canvas that parses no `color-mix()` is handed a colour it does take, so in an older Chrome the wave strip's unplayed bars no longer paint black and the waveform's edge shade no longer throws.
17
+ - Region: `tabindex="-1"` on `<sonic-region>` takes its part out of the tab order, as on a value control.
18
+
19
+ ### Patch Changes
20
+
21
+ - - Value controls: the spoken formatter runs only when the spoken value or the formatter changes, and a wave strip with no `<sonic-region>` no longer calls it twice per write. A changed `lang` is heard at the next spoken value.
22
+ - Older browsers: a closed readout stays out of the layout without the popover API, snapped edges fall back to a hairline without `round()`, and three rules no longer drop where `:state()` is missing.
23
+
3
24
  ## 0.25.1
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -51,7 +51,7 @@ import '@xsynaptic/sonic-ui/define';
51
51
  - `formatValue` and `parseValue` are properties holding functions. Once `parseValue` is set it has to read what `formatValue` writes, or typed entry opens on text it cannot take.
52
52
  - To show a control without taking input, set `inert` on the host. It leaves the tab order and the accessibility tree and ignores presses, and it keeps its colours and keeps drawing; `disabled` dims it and is announced as disabled.
53
53
  - A display takes its signal as data, and nothing in the library fetches, decodes or opens an `AudioContext`.
54
- - States a control sets itself are custom states on the host, selected with `:state()`.
54
+ - A state a control sets itself is an attribute on its drawn part, `data-sonic-<state>`, selected as `.sonic-slider[data-sonic-dragging]`.
55
55
  - The LED, the panel, the ring and the screen have no behaviour, so they are classes on your own elements (`sonic-led`, `sonic-panel`, `sonic-ring`, `sonic-screen`). A control draws no glass of its own: wrap one in a screen for the encased look.
56
56
 
57
57
  ## Skinning
@@ -1,8 +1,8 @@
1
1
  import { SonicElement } from "./sonic-element.js";
2
- import { bindSurface } from "../lib/canvas-surface.js";
2
+ import { bindControlSurface } from "../lib/canvas-surface.js";
3
3
 
4
- //#region src/elements/display-element.ts
5
- var SonicDisplayElement = class extends SonicElement {
4
+ //#region src/elements/analyser-element.ts
5
+ var SonicAnalyserElement = class extends SonicElement {
6
6
  static observedAttributes = [
7
7
  ...SonicElement.observedAttributes,
8
8
  "fill",
@@ -55,21 +55,19 @@ var SonicDisplayElement = class extends SonicElement {
55
55
  }
56
56
  connect(signal) {
57
57
  const control = this.control;
58
- this.keepControl(control, signal);
59
- this.#surface = bindSurface({
58
+ this.keepControl(signal);
59
+ this.#surface = bindControlSurface({
60
60
  canvas: this.canvas,
61
61
  colours: this.colours,
62
- fill: {
63
- control,
64
- sizeProperty: this.sizeProperty
65
- },
62
+ control,
63
+ fill: this.fill,
66
64
  numbers: this.numbers,
67
65
  paint: (context, frame) => {
68
66
  this.#paint(context, frame);
69
67
  },
70
- signal
68
+ signal,
69
+ sizeProperty: this.sizeProperty
71
70
  });
72
- this.#surface.setFill(this.fill);
73
71
  }
74
72
  surface() {
75
73
  return this.#surface;
@@ -87,4 +85,4 @@ var SonicDisplayElement = class extends SonicElement {
87
85
  };
88
86
 
89
87
  //#endregion
90
- export { SonicDisplayElement };
88
+ export { SonicAnalyserElement };
@@ -88,7 +88,13 @@ var SonicButton = class extends SonicFormElement {
88
88
  set value(value) {
89
89
  this.reflect("value", value);
90
90
  }
91
- #button = renderButton();
91
+ boxProbe = {
92
+ property: "padding-inline-start",
93
+ ratio: "--_sonic-button-padding-ratio",
94
+ selector: ".sonic-button-cap > :not(svg, .sonic-led)"
95
+ };
96
+ control = renderButton();
97
+ sheet = "button.css";
92
98
  #holding;
93
99
  #isDirty = false;
94
100
  #pressed = false;
@@ -101,10 +107,10 @@ var SonicButton = class extends SonicFormElement {
101
107
  this.attributeChangedCallback("pressed");
102
108
  }
103
109
  activate() {
104
- this.#button.click();
110
+ this.control.click();
105
111
  }
106
112
  connect(signal) {
107
- const button = this.#button;
113
+ const button = this.control;
108
114
  const cap = requireChild(button, ".sonic-button-cap", HTMLSpanElement);
109
115
  this.upgradeProperties("armed", "busy", "defaultPressed", "legend", "momentary", "softDisabled", "latching", "pressed", "value");
110
116
  mirrorChildren(this, {
@@ -130,11 +136,11 @@ var SonicButton = class extends SonicFormElement {
130
136
  bindKeyPress(button, signal);
131
137
  }
132
138
  focusTarget() {
133
- return this.#button;
139
+ return this.control;
134
140
  }
135
141
  render() {
136
142
  if (!this.isBound()) return;
137
- const button = this.#button;
143
+ const button = this.control;
138
144
  writeAttribute(button, "disabled", this.isDisabled() ? "" : void 0);
139
145
  writeAttribute(button, "aria-disabled", this.#isSoftDisabled() ? "true" : void 0);
140
146
  writeAttribute(button, "aria-busy", this.busy ? "true" : void 0);
@@ -187,7 +193,7 @@ var SonicButton = class extends SonicFormElement {
187
193
  }
188
194
  #showLegend() {
189
195
  const legend = this.legend;
190
- for (const part of this.#button.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
196
+ for (const part of this.control.querySelectorAll(":scope > .sonic-button-cap > [data-sonic-when]")) {
191
197
  const when = part.dataset.sonicWhen ?? "";
192
198
  part.toggleAttribute("data-sonic-hidden", !pressWhens.has(when) && when !== legend);
193
199
  }
@@ -51,8 +51,13 @@ var SonicDial = class extends SonicValueElement {
51
51
  set modulationValue(value) {
52
52
  this.#modulation.write(value, this.isBound() ? this.mapping() : void 0);
53
53
  }
54
- #dial = renderDial();
55
- #modulation = createModulation(this.#dial, "dial", (isModulated) => {
54
+ boxProbe = {
55
+ property: "margin-bottom",
56
+ selector: "[popover]"
57
+ };
58
+ control = renderDial();
59
+ sheet = "dial.css";
60
+ #modulation = createModulation(this.control, "dial", (isModulated) => {
56
61
  this.toggleState("modulated", isModulated);
57
62
  });
58
63
  connectedCallback() {
@@ -60,16 +65,13 @@ var SonicDial = class extends SonicValueElement {
60
65
  super.connectedCallback();
61
66
  }
62
67
  connect(signal) {
63
- const dial = this.#dial;
68
+ const dial = this.control;
64
69
  bindScale(this, {
65
70
  control: dial,
66
71
  marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
67
72
  }, signal);
68
73
  this.render();
69
- this.bindGestures(dial, signal, () => ({
70
- position: (event) => -event.clientY,
71
- travelPx: this.travelPx(dial, "--_sonic-dial-travel")
72
- }));
74
+ this.bindGestures(signal, () => this.upwardAxis("--_sonic-dial-travel"));
73
75
  }
74
76
  draw() {
75
77
  this.#modulation.draw({
@@ -79,9 +81,6 @@ var SonicDial = class extends SonicValueElement {
79
81
  value: this.value
80
82
  });
81
83
  }
82
- focusTarget() {
83
- return this.#dial;
84
- }
85
84
  isWrapping() {
86
85
  return this.endless;
87
86
  }
@@ -2,7 +2,6 @@ import { writeAttribute } from "../lib/write-attribute.js";
2
2
  import { SonicElement } from "./sonic-element.js";
3
3
  import { requireChild, template } from "../lib/render.js";
4
4
  import { Readout } from "./readout.js";
5
- import { ReadoutClaim } from "./readout-claim.js";
6
5
  import { SonicValueElement, linkValue } from "./value-element.js";
7
6
  import { bindFieldGesture } from "./field-gesture.js";
8
7
  import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
@@ -116,28 +115,33 @@ var SonicEnvelope = class extends SonicElement {
116
115
  set releaseCurve(id) {
117
116
  this.reflect("release-curve", id);
118
117
  }
118
+ get revealed() {
119
+ return this.hasState("revealed");
120
+ }
119
121
  get sustain() {
120
122
  return this.getAttribute("sustain") ?? "";
121
123
  }
122
124
  set sustain(id) {
123
125
  this.reflect("sustain", id);
124
126
  }
127
+ boxProbe = {
128
+ property: "margin-bottom",
129
+ selector: "[popover]"
130
+ };
131
+ control = renderEnvelope();
132
+ sheet = "envelope.css";
125
133
  #bindings = /* @__PURE__ */ new Map();
126
- #claim = new ReadoutClaim(() => {
127
- this.#renderReadout();
128
- });
129
- #envelope = renderEnvelope();
130
- #curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
131
- #fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
132
- #floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
134
+ #curveHandles = stageParts(this.control, "sonic-envelope-curve", curveStages);
135
+ #fill = requireChild(this.control, ".sonic-envelope-fill", SVGElement);
136
+ #floor = requireChild(this.control, ".sonic-envelope-floor", SVGElement);
133
137
  #gesture;
134
- #graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
135
- #handles = stageParts(this.#envelope, "sonic-envelope-handle", timeStages);
138
+ #graph = requireChild(this.control, ".sonic-envelope-graph", SVGElement);
139
+ #handles = stageParts(this.control, "sonic-envelope-handle", timeStages);
136
140
  #held;
137
141
  #isDriving = false;
138
- #line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
139
- #readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
140
- #reticle = requireChild(this.#envelope, ".sonic-envelope-reticle", HTMLDivElement);
142
+ #line = requireChild(this.control, ".sonic-envelope-line", SVGElement);
143
+ #readout = new Readout(requireChild(this.control, ".sonic-envelope-readout", HTMLDivElement));
144
+ #reticle = requireChild(this.control, ".sonic-envelope-reticle", HTMLDivElement);
141
145
  #shape = adsrShape({});
142
146
  #waiting = /* @__PURE__ */ new Set();
143
147
  #warned = /* @__PURE__ */ new Set();
@@ -156,10 +160,9 @@ var SonicEnvelope = class extends SonicElement {
156
160
  super.connectedCallback();
157
161
  }
158
162
  connect(signal) {
159
- const envelope = this.#envelope;
160
- this.keepControl(envelope, signal);
163
+ this.keepControl(signal);
161
164
  this.#bind();
162
- this.#bindPointer(envelope, signal);
165
+ this.#bindPointer(this.control, signal);
163
166
  signal.addEventListener("abort", () => {
164
167
  this.#unbind();
165
168
  }, { once: true });
@@ -184,7 +187,7 @@ var SonicEnvelope = class extends SonicElement {
184
187
  }
185
188
  #bindPointer(envelope, signal) {
186
189
  this.#gesture = bindFieldGesture(envelope, {
187
- claim: this.#claim,
190
+ element: this,
188
191
  grab: (event) => this.#grab(event),
189
192
  input: ({ drives }, next) => {
190
193
  this.#isDriving = true;
@@ -201,8 +204,11 @@ var SonicEnvelope = class extends SonicElement {
201
204
  binding.link.change();
202
205
  }
203
206
  },
204
- toggle: (isDragging) => {
205
- this.toggleState("dragging", isDragging);
207
+ renderReadout: () => {
208
+ this.#renderReadout();
209
+ },
210
+ toggle: (state, isOn) => {
211
+ this.toggleState(state, isOn);
206
212
  }
207
213
  }, signal);
208
214
  }
@@ -316,7 +322,7 @@ var SonicEnvelope = class extends SonicElement {
316
322
  }
317
323
  this.#readout.show({
318
324
  anchor: drag.part,
319
- isOpen: this.#claim.isRevealed && this.readout,
325
+ isOpen: this.#gesture?.isRevealed() === true && this.readout,
320
326
  text: drag.drives.map(({ binding }) => binding.element.valueText).join(", ")
321
327
  });
322
328
  }
@@ -1,6 +1,8 @@
1
1
  import { bindDrag } from "../lib/pointer-drag.js";
2
- import { revealDelay } from "./readout-claim.js";
2
+ import { ReadoutClaim, revealDelay } from "./readout-claim.js";
3
+ import { createDoublePress } from "../lib/double-press.js";
3
4
  import { dragThresholdPx } from "../lib/drag-step.js";
5
+ import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
4
6
  import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
5
7
 
6
8
  //#region src/elements/field-gesture.ts
@@ -23,16 +25,39 @@ function startAxis(hold) {
23
25
  travelPx
24
26
  };
25
27
  }
28
+ function createReveal({ element, renderReadout, toggle }) {
29
+ let isRevealed = false;
30
+ const show = () => {
31
+ if (claim.isRevealed !== isRevealed) {
32
+ isRevealed = claim.isRevealed;
33
+ toggle("revealed", isRevealed);
34
+ element.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
35
+ }
36
+ renderReadout();
37
+ };
38
+ const claim = new ReadoutClaim(show);
39
+ return {
40
+ claim,
41
+ show
42
+ };
43
+ }
26
44
  function bindFieldGesture(target, options, signal) {
27
- const { claim } = options;
45
+ const { reset, resetsOnDoublePress } = options;
46
+ const { claim, show } = createReveal(options);
47
+ const doublePress = reset && resetsOnDoublePress ? createDoublePress() : void 0;
28
48
  let pointerType;
49
+ const grab = (event) => {
50
+ if (isMenuPress(event)) return void 0;
51
+ if (!reset || !isResetPress(event)) return options.grab(event);
52
+ reset();
53
+ };
29
54
  const drag = bindDrag(target, {
30
55
  cancel: ({ from, hold }) => {
31
56
  options.input(hold, from);
32
57
  },
33
58
  grab: (event) => {
34
59
  pointerType = event.pointerType;
35
- const hold = options.grab(event);
60
+ const hold = grab(event);
36
61
  if (!hold) {
37
62
  pointerType = void 0;
38
63
  return;
@@ -53,8 +78,8 @@ function bindFieldGesture(target, options, signal) {
53
78
  })
54
79
  };
55
80
  },
56
- lift: ({ hold }, event) => {
57
- options.lift?.(hold, event);
81
+ lift: (_held, event) => {
82
+ if (doublePress?.press(event) === true && resetsOnDoublePress?.() === true) reset?.();
58
83
  },
59
84
  move: (held, event) => {
60
85
  const { axes } = held.hold;
@@ -63,7 +88,10 @@ function bindFieldGesture(target, options, signal) {
63
88
  y: axes.y?.model.mapping()
64
89
  }, held.state, flip(pointerMove(event), held.hold));
65
90
  held.state = step.state;
66
- if (step.state.isEngaged) claim.reveal("drag");
91
+ if (step.state.isEngaged) {
92
+ claim.reveal("drag");
93
+ show();
94
+ }
67
95
  options.input(held.hold, step);
68
96
  },
69
97
  release: ({ from, hold }) => {
@@ -71,12 +99,25 @@ function bindFieldGesture(target, options, signal) {
71
99
  options.release(hold, fieldAxes.filter((axis) => hold.axes[axis]?.model.value !== from[axis]));
72
100
  pointerType = void 0;
73
101
  },
74
- toggle: options.toggle
102
+ toggle: (isDragging) => {
103
+ options.toggle("dragging", isDragging);
104
+ }
75
105
  }, signal);
106
+ target.addEventListener("pointercancel", () => {
107
+ doublePress?.forget();
108
+ }, { signal });
76
109
  return {
110
+ concealKeys: () => {
111
+ if (claim.conceal("keys")) show();
112
+ },
77
113
  current: () => drag.current()?.hold,
78
114
  end: drag.end,
79
- pointerType: () => pointerType
115
+ isRevealed: () => claim.isRevealed,
116
+ pointerType: () => pointerType,
117
+ revealKeys: () => {
118
+ claim.reveal("keys");
119
+ show();
120
+ }
80
121
  };
81
122
  }
82
123
 
@@ -109,6 +109,8 @@ var SonicMeter = class extends SonicElement {
109
109
  set value(value) {
110
110
  this.reflect("value", value);
111
111
  }
112
+ control = renderMeter();
113
+ sheet = "meter.css";
112
114
  #bar = -Infinity;
113
115
  #carryMs = 0;
114
116
  #clipAt = -Infinity;
@@ -116,7 +118,6 @@ var SonicMeter = class extends SonicElement {
116
118
  #isValueShown = false;
117
119
  #lastFrame;
118
120
  #level = 0;
119
- #meter = renderMeter();
120
121
  #peak = -Infinity;
121
122
  #peakHold = -Infinity;
122
123
  #peakHoldAt = -Infinity;
@@ -138,7 +139,7 @@ var SonicMeter = class extends SonicElement {
138
139
  connect(signal) {
139
140
  const now = performance.now();
140
141
  this.upgradeProperties("ballistics", "max", "min", "orientation", "origin", "segments", "value", "level");
141
- this.keepControl(this.#meter, signal);
142
+ this.keepControl(signal);
142
143
  this.#renderSegments();
143
144
  this.#render(now);
144
145
  this.#schedule(now);
@@ -172,7 +173,7 @@ var SonicMeter = class extends SonicElement {
172
173
  }
173
174
  #render(now) {
174
175
  if (!this.isBound()) return;
175
- const meter = this.#meter;
176
+ const meter = this.control;
176
177
  const origin = this.origin === void 0 ? 0 : this.#proportionOf(this.origin);
177
178
  meter.style.setProperty("--_sonic-meter-min", String(this.min));
178
179
  meter.style.setProperty("--_sonic-meter-max", String(this.max));
@@ -193,7 +194,7 @@ var SonicMeter = class extends SonicElement {
193
194
  }
194
195
  #renderSegments() {
195
196
  if (!this.isBound()) return;
196
- const meter = this.#meter;
197
+ const meter = this.control;
197
198
  const segments = this.#segments;
198
199
  this.toggleState("ladder", segments !== void 0);
199
200
  if (!segments) {
@@ -209,7 +210,7 @@ var SonicMeter = class extends SonicElement {
209
210
  meter.style.setProperty("--_sonic-meter-clip-segments", segmentsBelow(segments, "clip"));
210
211
  }
211
212
  #renderStill(bar) {
212
- const { style } = this.#meter;
213
+ const { style } = this.control;
213
214
  style.setProperty("--_sonic-meter-bar", String(bar));
214
215
  style.setProperty("--_sonic-meter-peak-hold", "0");
215
216
  style.setProperty("--_sonic-meter-clipped", "0");
@@ -22,20 +22,17 @@ var SonicNumber = class extends SonicValueElement {
22
22
  set press(gesture) {
23
23
  this.reflect("press", gesture);
24
24
  }
25
- #number = renderNumber();
26
- #digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
25
+ control = renderNumber();
26
+ sheet = "number.css";
27
+ #digits = requireChild(this.control, ".sonic-number-value", HTMLSpanElement);
27
28
  connectedCallback() {
28
29
  this.upgradeProperties("press");
29
30
  super.connectedCallback();
30
31
  }
31
32
  connect(signal) {
32
- const number = this.#number;
33
- this.keepControl(number, signal);
33
+ this.keepControl(signal);
34
34
  this.render();
35
- this.bindGestures(number, signal, () => ({
36
- position: (event) => -event.clientY,
37
- travelPx: this.travelPx(number, "--_sonic-number-travel")
38
- }));
35
+ this.bindGestures(signal, () => this.upwardAxis("--_sonic-number-travel"));
39
36
  }
40
37
  controlRole() {
41
38
  return "spinbutton";
@@ -43,9 +40,6 @@ var SonicNumber = class extends SonicValueElement {
43
40
  draw() {
44
41
  this.#digits.textContent = this.valueText;
45
42
  }
46
- focusTarget() {
47
- return this.#number;
48
- }
49
43
  tapTarget() {
50
44
  if (this.press !== "step") return void 0;
51
45
  const mapping = this.mapping();
@@ -81,7 +81,7 @@ var SonicPositionGroupElement = class extends SonicRadioGroupElement {
81
81
  connect(signal) {
82
82
  this.upgradeProperties("defaultChecked", "checked", "orientation");
83
83
  super.connect(signal);
84
- this.#holding = bindHold(this.group, {
84
+ this.#holding = bindHold(this.control, {
85
85
  canHold: () => !this.isDisabled(),
86
86
  leave: "focusout",
87
87
  onHold: () => {
@@ -129,7 +129,7 @@ var SonicPositionGroupElement = class extends SonicRadioGroupElement {
129
129
  }
130
130
  render() {
131
131
  if (!this.isBound()) return;
132
- const group = this.group;
132
+ const group = this.control;
133
133
  const count = this.options().length;
134
134
  if (count === 0) {
135
135
  this.#renderBare();
@@ -156,12 +156,12 @@ var SonicPositionGroupElement = class extends SonicRadioGroupElement {
156
156
  springBack() {
157
157
  const rest = restOf(this.positions(), this.checkedIndex());
158
158
  if (rest === void 0) return;
159
- const isFocused = this.group.contains(this.ownerDocument.activeElement);
159
+ const isFocused = this.control.contains(this.ownerDocument.activeElement);
160
160
  this.select(rest);
161
161
  if (isFocused) this.options()[rest]?.focus();
162
162
  }
163
163
  #renderBare() {
164
- const group = this.group;
164
+ const group = this.control;
165
165
  const bare = this.bare;
166
166
  const isChecked = this.checked;
167
167
  writeAttribute(group, "role", void 0);
@@ -43,7 +43,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
43
43
  return false;
44
44
  }
45
45
  connect(signal) {
46
- const group = this.group;
46
+ const group = this.control;
47
47
  this.upgradeProperties("value");
48
48
  mirrorChildren(this, {
49
49
  control: group,
@@ -91,10 +91,10 @@ var SonicRadioGroupElement = class extends SonicFormElement {
91
91
  optionOf(target) {
92
92
  if (!(target instanceof Element)) return void 0;
93
93
  const option = target.closest("[role=\"radio\"]");
94
- return option instanceof HTMLButtonElement && option.parentElement === this.group ? option : void 0;
94
+ return option instanceof HTMLButtonElement && option.parentElement === this.control ? option : void 0;
95
95
  }
96
96
  options() {
97
- return [...this.group.children].filter((child) => child instanceof HTMLButtonElement && child.getAttribute("role") === "radio");
97
+ return [...this.control.children].filter((child) => child instanceof HTMLButtonElement && child.getAttribute("role") === "radio");
98
98
  }
99
99
  placeCopies(copies) {
100
100
  this.#copyOptions(copies);
@@ -109,7 +109,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
109
109
  });
110
110
  }
111
111
  refocusAt(index) {
112
- if (this.group.matches(":focus-within")) this.options()[index]?.focus();
112
+ if (this.control.matches(":focus-within")) this.options()[index]?.focus();
113
113
  }
114
114
  releaseTarget(index) {
115
115
  return index;
@@ -126,7 +126,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
126
126
  writeAttribute(option, "aria-checked", String(option === checked));
127
127
  writeAttribute(option, "tabindex", option === focusable ? "0" : "-1");
128
128
  }
129
- this.forwardNaming(this.group, true);
129
+ this.forwardNaming(this.control, true);
130
130
  this.writeFormValue(checked ? this.#value : null, this.#value);
131
131
  }
132
132
  restoreState(state) {
@@ -175,7 +175,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
175
175
  }, signal);
176
176
  }
177
177
  #copyOptions(copies) {
178
- const group = this.group;
178
+ const group = this.control;
179
179
  const options = this.options();
180
180
  const focused = this.optionOf(group.querySelector(":scope > :focus"));
181
181
  const focusedValue = focused && optionValue(focused);
@@ -192,7 +192,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
192
192
  if (focused) this.#refocus(focused, focusedValue);
193
193
  }
194
194
  #markPressed(pressed) {
195
- for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-pressed", index === pressed);
195
+ for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-active", index === pressed);
196
196
  }
197
197
  #optionBoxes() {
198
198
  const positions = this.positions();
@@ -51,6 +51,7 @@ var RegionLayer = class {
51
51
  this.#release(true);
52
52
  }
53
53
  follow() {
54
+ if (this.#drawn.length === 0) return;
54
55
  if (this.#stripKey() !== this.#key) this.render();
55
56
  }
56
57
  isHeld() {
@@ -158,6 +159,7 @@ var RegionLayer = class {
158
159
  const span = link.span();
159
160
  const [low, high] = regionTravel(span, mapping.bounds);
160
161
  const canMove = this.#isEnabled(entry);
162
+ const stop = region.getAttribute("tabindex") === "-1" ? "-1" : "0";
161
163
  writeProperty(part, "--_sonic-region-from", String(mapping.proportionOf(span.start)));
162
164
  writeProperty(part, "--_sonic-region-to", String(mapping.proportionOf(span.end)));
163
165
  if (!known || entry.kind !== region.kind) {
@@ -170,7 +172,7 @@ var RegionLayer = class {
170
172
  writeAttribute(part, "aria-valuenow", String(span.start));
171
173
  writeAttribute(part, "aria-valuetext", host.speak(span));
172
174
  writeAttribute(part, "aria-disabled", canMove ? void 0 : "true");
173
- writeAttribute(part, "tabindex", canMove ? "0" : void 0);
175
+ writeAttribute(part, "tabindex", canMove ? stop : void 0);
174
176
  link.draw(part);
175
177
  return entry;
176
178
  }
@@ -18,7 +18,8 @@ var SonicRegion = class extends SonicFormElement {
18
18
  "end",
19
19
  "kind",
20
20
  "name",
21
- "start"
21
+ "start",
22
+ "tabindex"
22
23
  ];
23
24
  static {
24
25
  link = (element) => ({
@@ -108,6 +109,9 @@ var SonicRegion = class extends SonicFormElement {
108
109
  };
109
110
  this.render();
110
111
  }
112
+ stateTarget() {
113
+ return this.#part;
114
+ }
111
115
  #draw(part) {
112
116
  this.#part = part;
113
117
  this.forwardNaming(part, true);
@@ -9,10 +9,13 @@ const renderOption = template(`
9
9
  </button>
10
10
  `, HTMLButtonElement);
11
11
  var SonicSegmented = class extends SonicRadioGroupElement {
12
- group = renderSegmented();
13
- connect(signal) {
14
- super.connect(signal);
15
- }
12
+ boxProbe = {
13
+ property: "padding-inline-start",
14
+ ratio: "--_sonic-segmented-padding-ratio",
15
+ selector: ".sonic-segmented-cap"
16
+ };
17
+ control = renderSegmented();
18
+ sheet = "segmented.css";
16
19
  renderOption() {
17
20
  return renderOption();
18
21
  }
@@ -86,9 +86,14 @@ var SonicSlider = class extends SonicValueElement {
86
86
  set spring(isSpring) {
87
87
  this.reflect("spring", isSpring);
88
88
  }
89
+ boxProbe = {
90
+ property: "margin-bottom",
91
+ selector: "[popover]"
92
+ };
93
+ control = renderSlider();
94
+ sheet = "slider.css";
89
95
  #buffered = [];
90
- #slider = renderSlider();
91
- #modulation = createModulation(this.#slider, "slider", (isModulated) => {
96
+ #modulation = createModulation(this.control, "slider", (isModulated) => {
92
97
  this.toggleState("modulated", isModulated);
93
98
  });
94
99
  connectedCallback() {
@@ -96,7 +101,7 @@ var SonicSlider = class extends SonicValueElement {
96
101
  super.connectedCallback();
97
102
  }
98
103
  connect(signal) {
99
- const slider = this.#slider;
104
+ const slider = this.control;
100
105
  const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
101
106
  bindScale(this, {
102
107
  control: slider,
@@ -118,7 +123,7 @@ var SonicSlider = class extends SonicValueElement {
118
123
  }
119
124
  });
120
125
  this.render();
121
- this.bindGestures(slider, signal, (event) => {
126
+ this.bindGestures(signal, (event) => {
122
127
  const axis = this.#axis(slider, cap);
123
128
  if (event.target instanceof Node && cap.contains(event.target)) return axis;
124
129
  if (this.groovePress === "none") return;
@@ -136,12 +141,9 @@ var SonicSlider = class extends SonicValueElement {
136
141
  origin: this.originValue(),
137
142
  value: this.value
138
143
  });
139
- this.#renderBuffered(this.#slider);
140
- this.#renderScrub(this.#slider);
141
- this.#placeReadout(this.#slider);
142
- }
143
- focusTarget() {
144
- return this.#slider;
144
+ this.#renderBuffered(this.control);
145
+ this.#renderScrub(this.control);
146
+ this.#placeReadout(this.control);
145
147
  }
146
148
  scrubChanged() {
147
149
  if (this.scrub) this.render();