@xsynaptic/sonic-ui 0.18.0 → 0.20.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 (101) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +20 -9
  3. package/dist/default/elements/button.js +0 -5
  4. package/dist/default/elements/dial.js +0 -7
  5. package/dist/default/elements/display-element.js +0 -5
  6. package/dist/default/elements/envelope.js +0 -6
  7. package/dist/default/elements/meter.js +0 -1
  8. package/dist/default/elements/number.js +0 -7
  9. package/dist/default/elements/segmented.js +0 -5
  10. package/dist/default/elements/slider.js +0 -7
  11. package/dist/default/elements/sonic-element.js +3 -1
  12. package/dist/default/elements/switch.js +0 -1
  13. package/dist/default/elements/toggle.js +0 -5
  14. package/dist/default/elements/value-element.js +8 -4
  15. package/dist/default/elements/wave-element.js +7 -8
  16. package/dist/default/elements/xy.js +0 -7
  17. package/dist/default/index.js +2 -1
  18. package/dist/default/lib/canvas-surface.js +5 -1
  19. package/dist/default/lib/clock.js +23 -0
  20. package/dist/default/lib/spoken-duration.js +30 -0
  21. package/dist/default/lib/waveform-buckets.js +7 -1
  22. package/dist/dev/elements/button.js +5 -5
  23. package/dist/dev/elements/button.js.map +1 -1
  24. package/dist/dev/elements/dial.js +4 -7
  25. package/dist/dev/elements/dial.js.map +1 -1
  26. package/dist/dev/elements/display-element.js +1 -5
  27. package/dist/dev/elements/display-element.js.map +1 -1
  28. package/dist/dev/elements/envelope.js +4 -6
  29. package/dist/dev/elements/envelope.js.map +1 -1
  30. package/dist/dev/elements/meter.js +1 -1
  31. package/dist/dev/elements/meter.js.map +1 -1
  32. package/dist/dev/elements/number.js +4 -7
  33. package/dist/dev/elements/number.js.map +1 -1
  34. package/dist/dev/elements/segmented.js +5 -5
  35. package/dist/dev/elements/segmented.js.map +1 -1
  36. package/dist/dev/elements/slider.js +4 -7
  37. package/dist/dev/elements/slider.js.map +1 -1
  38. package/dist/dev/elements/sonic-element.d.ts +6 -16
  39. package/dist/dev/elements/sonic-element.js +54 -29
  40. package/dist/dev/elements/sonic-element.js.map +1 -1
  41. package/dist/dev/elements/split.js.map +1 -1
  42. package/dist/dev/elements/switch.js +4 -1
  43. package/dist/dev/elements/switch.js.map +1 -1
  44. package/dist/dev/elements/toggle.js +5 -5
  45. package/dist/dev/elements/toggle.js.map +1 -1
  46. package/dist/dev/elements/value-element.d.ts +6 -0
  47. package/dist/dev/elements/value-element.js +8 -4
  48. package/dist/dev/elements/value-element.js.map +1 -1
  49. package/dist/dev/elements/wave-element.d.ts +2 -1
  50. package/dist/dev/elements/wave-element.js +11 -8
  51. package/dist/dev/elements/wave-element.js.map +1 -1
  52. package/dist/dev/elements/waveform.d.ts +1 -1
  53. package/dist/dev/elements/xy.js +4 -7
  54. package/dist/dev/elements/xy.js.map +1 -1
  55. package/dist/dev/index.d.ts +3 -2
  56. package/dist/dev/index.js +2 -1
  57. package/dist/dev/lib/canvas-surface.js +5 -1
  58. package/dist/dev/lib/canvas-surface.js.map +1 -1
  59. package/dist/dev/lib/clock.d.ts +5 -0
  60. package/dist/dev/lib/clock.js +24 -0
  61. package/dist/dev/lib/clock.js.map +1 -0
  62. package/dist/dev/lib/mirror-children.js.map +1 -1
  63. package/dist/dev/lib/owned-control.js.map +1 -1
  64. package/dist/dev/lib/spoken-duration.js +31 -0
  65. package/dist/dev/lib/spoken-duration.js.map +1 -0
  66. package/dist/dev/lib/waveform-buckets.d.ts +2 -2
  67. package/dist/dev/lib/waveform-buckets.js +7 -1
  68. package/dist/dev/lib/waveform-buckets.js.map +1 -1
  69. package/dist/styles/button.css +29 -5
  70. package/dist/styles/dial.css +10 -9
  71. package/dist/styles/envelope.css +17 -10
  72. package/dist/styles/led.css +5 -1
  73. package/dist/styles/material/arc.css +3 -2
  74. package/dist/styles/material/bracket.css +3 -3
  75. package/dist/styles/material/cap.css +38 -13
  76. package/dist/styles/material/core.css +4 -0
  77. package/dist/styles/material/glass.css +1 -6
  78. package/dist/styles/material/groove.css +4 -3
  79. package/dist/styles/material/keycap.css +3 -2
  80. package/dist/styles/material/lens.css +1 -1
  81. package/dist/styles/material/pane.css +7 -13
  82. package/dist/styles/material/readout.css +6 -6
  83. package/dist/styles/material/well.css +3 -14
  84. package/dist/styles/meter.css +2 -7
  85. package/dist/styles/number.css +5 -1
  86. package/dist/styles/panel.css +4 -3
  87. package/dist/styles/ring.css +1 -1
  88. package/dist/styles/screen.css +6 -4
  89. package/dist/styles/segmented.css +17 -4
  90. package/dist/styles/slider.css +9 -3
  91. package/dist/styles/spectrum.css +8 -9
  92. package/dist/styles/switch.css +35 -10
  93. package/dist/styles/toggle.css +14 -7
  94. package/dist/styles/waveform.css +17 -18
  95. package/dist/styles/wavestrip.css +12 -25
  96. package/dist/styles/xy.css +46 -25
  97. package/package.json +1 -6
  98. package/dist/default/dat.js +0 -52
  99. package/dist/dev/dat.d.ts +0 -20
  100. package/dist/dev/dat.js +0 -53
  101. package/dist/dev/dat.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @xsynaptic/sonic-ui
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - - Breaking: the wave strip, waveform, spectrum, envelope and XY pad draw no glass. Each is plain, in the text colour around it, with no fill, edge or corner. For the encased look wrap one in `<div class="sonic-screen">`, whose padding is the inset.
8
+ - Breaking: `.sonic-screen` is sized like a `div`. It fills a block, hugs in a flex row and is never shorter than `--sonic-screen-size`; `--sonic-screen-aspect-ratio` is removed, so a text readout sets its own width. A control with `fill` needs the screen given a height.
9
+ - Removed tokens: `--sonic-wavestrip-inset-ratio`, `--sonic-waveform-inset-ratio`, `--sonic-spectrum-inset-ratio`, `--sonic-xy-inset-ratio`, and the `-radius-ratio` of the wave strip, waveform, spectrum, envelope and XY pad. `--sonic-radius` no longer reaches those five, and `--sonic-glass` with its depth, texture, edge, text and font reaches only the screen, the number box and the readouts. The four relief skins drop their wave strip inset.
10
+ - New token: `--sonic-wavestrip-marker-edge` colours the ring round a marker, which is clear when unset. A held puck and the gap in its focus ring are clear too, and the pad's lines stop at the puck. A held or disabled envelope handle is filled in its ring's colour.
11
+ - A plain control takes its colour from `color`, where it took `--sonic-glass-text`, and its canvas repaints when `color` changes. It ignores `--sonic-ink`, as before.
12
+ - With no token set: the XY pad and the spectrum lose their default insets (0.04 and 0.06), a wave strip's regions stop at the wave's height, and a screen's well takes the full depth where the waveform's took 0.6.
13
+ - `spectrum.css` needs only `material/core.css`; the envelope, waveform, wave strip and XY pad sheets no longer need `pane` or `well`. Importing sheets one by one, the glass comes back with `screen.css` and `material/glass.css`, `pane.css` and `well.css`.
14
+
15
+ ### Patch Changes
16
+
17
+ - - Development warnings name a control's own sheet and `material/core.css` when missing, and no longer any other material sheet; the README table lists what each sheet needs.
18
+
19
+ ## 0.19.0
20
+
21
+ ### Minor Changes
22
+
23
+ - - Shared tokens, each unset or at today's value: `--sonic-press-duration` (60ms) and `--sonic-fade-duration` (120ms) time a cap's press and every fade; `--sonic-shade` (black) and `--sonic-shine` (white) are the poles the light mixes a surface towards.
24
+ - `--sonic-legend-font-size` sets the text of a button, a segmented option, a switch and a toggle. `--sonic-radius` sets the outer corner of a button, a segmented control, a number box, a screen, a slider's cap, a wave strip, a waveform, a spectrum, an envelope and an XY pad. A control's own `-font-ratio` or `-radius-ratio` still wins, and under the shared radius a button's face follows its corner.
25
+ - Waveform and wave strip: the inset ratio now defaults to 0, so the wave fills its glass; set `--sonic-waveform-inset-ratio: 0.06` or `--sonic-wavestrip-inset-ratio: 0.1` for the inset each had. The slate, amber, ivory and lime skins keep the wave strip's 0.1, which holds its markers clear of the well's shadow.
26
+ - Spectrum and wave strip: the canvas is rounded to the glass, so a large radius no longer leaves corners poking out.
27
+ - A cast shadow or highlight now computes to `color(srgb …)` where it was `rgba()`; nothing drawn changes.
28
+ - In development a control warns about a box property on its host, a padding zeroed by an unlayered rule, and a missing `material/scale.css` or `material/bracket.css`. The README has the recipe for layering a reset.
29
+ - - A waveform or a wave strip now shows, reads and speaks a clock when no formatter is set: `1:23` in the readout and the typed entry, and a duration in the nearest `lang` for assistive technology. Setting `formatValue`, `parseValue` or `formatSpokenValue` replaces that part.
30
+ - `formatClock` and `parseClock` are exported, so your own time text can match. A negative time is written with `−` (U+2212), not the ASCII hyphen; `parseClock` reads either.
31
+ - `fullScale` is optional on `peaks` and follows the array when unset.
32
+ - `./dat` is removed. Pass `peaks` as `{ pairsPerSecond, samples }`, where the first is a header's `sampleRate / samplesPerPixel`.
33
+
3
34
  ## 0.18.0
4
35
 
5
36
  ### Minor Changes
package/README.md CHANGED
@@ -34,9 +34,17 @@ import '@xsynaptic/sonic-ui/define';
34
34
  ```
35
35
 
36
36
  - Every sheet sits in the `sonic` layer, so declare it below the layer your own rules live in.
37
+ - A rule outside any layer beats every layered one, so an unlayered reset (`* { margin: 0; padding: 0 }`, a zeroed `border`, a bare `button`) strips padding, borders, type sizes and colours from the controls. Import a reset you do not own into a layer declared before `sonic`:
38
+
39
+ ```css
40
+ @layer base, sonic, components;
41
+
42
+ @import 'reset.css' layer(base);
43
+ ```
44
+
37
45
  - A skin sheet does nothing until its class, `sonic-skin-<name>`, is on an ancestor of the controls. The skins are `slate`, `amber`, `ivory`, `lime` and `flat`; a wrapper with another skin's class reskins what is inside it. With no skin a control is bare: no cap and no well, drawn in the text colour around it.
38
46
  - `./define` registers every control. `./define/<control>` registers one, and `.` exports the classes and registers nothing.
39
- - Under a bundler that sets the `development` condition, as Vite's dev server does, the controls warn in the console about a missing sheet, a child they do not use and a binding that found nothing. Read the console before debugging.
47
+ - Under a bundler that sets the `development` condition, as Vite's dev server does, the controls warn in the console about a control's own sheet or `material/core.css` missing, a padding lost to an unlayered rule, a box property set on a host, a child they do not use and a binding that found nothing. Read the console before debugging.
40
48
 
41
49
  ## What every control shares
42
50
 
@@ -92,7 +100,8 @@ The LED, the panel, the ring and the screen have no behaviour, so they are class
92
100
 
93
101
  - `<span class="sonic-led">` is unlit until it carries `data-sonic-lit`, whose value picks the colour: empty, `idle`, `ok`, `warning` or `danger`. Inside a latching button or a toggle's cap it lights with the control.
94
102
  - `class="sonic-ring"` draws a lit arc around its one child, from `--sonic-ring-from` to `--sonic-ring-to`, each 0 to 1. Give it your own `role="progressbar"`.
95
- - `class="sonic-screen"` is a pane of glass for your own text or canvas, and `class="sonic-panel"` groups controls under a `sonic-panel-title`.
103
+ - `class="sonic-screen"` is a box of glass that holds your own text, a canvas or a control, and is sized like a `div`: it fills a block, hugs in a flex row, and is never shorter than `--sonic-screen-size`. A wave strip, waveform, spectrum, envelope or XY pad draws no glass of its own, so wrap one in a screen for the encased look; its padding is the inset, and it centres what it holds, so a control that should span it is given its length (`--sonic-wavestrip-length: 100%`). A control with `fill` needs the screen given a height.
104
+ - `class="sonic-panel"` groups controls under a `sonic-panel-title`.
96
105
 
97
106
  ## Displays
98
107
 
@@ -110,7 +119,9 @@ A display takes its signal as data and never opens an `AudioContext`.
110
119
 
111
120
  ## Wave controls
112
121
 
113
- `<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. `./dat` reads an audiowaveform `.dat` file into `peaks`, and fetches nothing itself.
122
+ `<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. `peaks` is `{ pairsPerSecond, samples }`, and the full scale follows the array unless `fullScale` is set.
123
+
124
+ A wave control shows and reads a clock (`1:23`, typed or drawn) and speaks a duration in the language around it, the nearest `lang`. Setting `formatValue`, `parseValue` or `formatSpokenValue` replaces that part; `formatClock` and `parseClock` are the same pair, exported so your own time text matches.
114
125
 
115
126
  Their events bubble and carry no detail, so read the property when one fires: `revealed` on `sonic-reveal`, `currentMarker` on `sonic-marker`, and the strip's `hoverValue` on `sonic-hover`, which `clientXOf(value)` turns into a viewport x.
116
127
 
@@ -149,7 +160,7 @@ A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to st
149
160
  | --------------- | ----------------------------------------------- |
150
161
  | `button.css` | core, cap, well, keycap |
151
162
  | `dial.css` | core, cap, glass, readout, value, arc, scale |
152
- | `envelope.css` | core, glass, pane, well, readout, bracket |
163
+ | `envelope.css` | core, glass, readout, bracket |
153
164
  | `led.css` | core, lens |
154
165
  | `meter.css` | core, lens, groove |
155
166
  | `number.css` | core, cap, glass, pane, well, value |
@@ -158,13 +169,13 @@ A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to st
158
169
  | `screen.css` | core, glass, pane, well |
159
170
  | `segmented.css` | core, cap, well, keycap |
160
171
  | `slider.css` | core, cap, glass, readout, value, groove, scale |
161
- | `spectrum.css` | core, glass, pane, well |
172
+ | `spectrum.css` | core |
162
173
  | `switch.css` | core, cap |
163
174
  | `toggle.css` | core, cap, well, keycap |
164
- | `waveform.css` | core, cap, glass, pane, well, readout, value |
165
- | `wavestrip.css` | core, cap, glass, pane, well, readout, value |
166
- | `xy.css` | core, cap, glass, pane, well, readout, bracket |
175
+ | `waveform.css` | core, cap, glass, readout, value |
176
+ | `wavestrip.css` | core, cap, glass, readout, value |
177
+ | `xy.css` | core, cap, glass, readout, bracket |
167
178
 
168
- An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`.
179
+ An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`. No warning names a missing material sheet other than `core.css`, so check a control that draws wrong against this table.
169
180
 
170
181
  Targets current Chrome and Edge, Safari 26 and Firefox 147. No control mirrors for right-to-left. MIT licensed.
@@ -116,11 +116,6 @@ var SonicButton = class extends SonicFormElement {
116
116
  }
117
117
  }, signal);
118
118
  this.render();
119
- this.checkStyles(button, "button.css", [
120
- "cap",
121
- "well",
122
- "keycap"
123
- ]);
124
119
  button.addEventListener("click", (event) => {
125
120
  if (this.#isSoftDisabled()) {
126
121
  event.preventDefault();
@@ -66,13 +66,6 @@ var SonicDial = class extends SonicValueElement {
66
66
  marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
67
67
  }, signal);
68
68
  this.render();
69
- this.checkStyles(dial, "dial.css", [
70
- "cap",
71
- "readout",
72
- "value",
73
- ["glass", ".sonic-dial-readout"],
74
- ["arc", ".sonic-dial-ring"]
75
- ]);
76
69
  this.bindGestures(dial, signal, () => ({
77
70
  position: (event) => -event.clientY,
78
71
  travelPx: this.travelPx(dial, "--_sonic-dial-travel")
@@ -69,11 +69,6 @@ var SonicDisplayElement = class extends SonicElement {
69
69
  },
70
70
  signal
71
71
  });
72
- this.checkStyles(control, this.sheet, [
73
- "glass",
74
- "pane",
75
- "well"
76
- ]);
77
72
  this.#surface.setFill(this.fill);
78
73
  }
79
74
  surface() {
@@ -161,12 +161,6 @@ var SonicEnvelope = class extends SonicElement {
161
161
  this.#signal = signal;
162
162
  this.keepControl(envelope, signal);
163
163
  this.#bind();
164
- this.checkStyles(envelope, "envelope.css", [
165
- "glass",
166
- "pane",
167
- "well",
168
- "readout"
169
- ]);
170
164
  this.#bindPointer(envelope, signal);
171
165
  signal.addEventListener("abort", () => {
172
166
  this.#signal = void 0;
@@ -142,7 +142,6 @@ var SonicMeter = class extends SonicElement {
142
142
  this.#renderSegments();
143
143
  this.#render(now);
144
144
  this.#schedule(now);
145
- this.checkStyles(this.#meter, "meter.css", ["lens", "groove"]);
146
145
  signal.addEventListener("abort", () => {
147
146
  if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
148
147
  this.#frame = void 0;
@@ -32,13 +32,6 @@ 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", [
36
- "cap",
37
- "glass",
38
- "pane",
39
- "well",
40
- "value"
41
- ]);
42
35
  this.bindGestures(number, signal, () => ({
43
36
  position: (event) => -event.clientY,
44
37
  travelPx: this.travelPx(number, "--_sonic-number-travel")
@@ -12,11 +12,6 @@ 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", [
16
- "well",
17
- "keycap",
18
- ["cap", ".sonic-segmented-option"]
19
- ]);
20
15
  }
21
16
  renderOption() {
22
17
  return renderOption();
@@ -118,13 +118,6 @@ var SonicSlider = class extends SonicValueElement {
118
118
  }
119
119
  });
120
120
  this.render();
121
- this.checkStyles(slider, "slider.css", [
122
- "cap",
123
- "readout",
124
- "value",
125
- "groove",
126
- ["glass", ".sonic-slider-readout"]
127
- ]);
128
121
  this.bindGestures(slider, signal, (event) => {
129
122
  const axis = this.#axis(slider, cap);
130
123
  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, material = []) {}
61
+ checkStyles(control, sheet, box) {}
62
62
  focusTarget() {}
63
63
  forwardNaming(target, isNamed) {
64
64
  const labels = isNamed ? this.namingLabels() : [];
@@ -124,6 +124,8 @@ var SonicElement = class extends HTMLElement {
124
124
  this[name] = value;
125
125
  }
126
126
  }
127
+ #checkBox(control, checked, box) {}
128
+ #checkHost(checked) {}
127
129
  #focused() {
128
130
  const root = this.getRootNode();
129
131
  const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
@@ -35,7 +35,6 @@ var SonicSwitch = class extends SonicPositionGroupElement {
35
35
  connect(signal) {
36
36
  const group = this.group;
37
37
  super.connect(signal);
38
- this.checkStyles(group, "switch.css", ["cap"]);
39
38
  this.bare.addEventListener("click", (event) => {
40
39
  const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
41
40
  this.#isClickSwallowed = false;
@@ -32,11 +32,6 @@ var SonicToggle = class extends SonicPositionGroupElement {
32
32
  }
33
33
  connect(signal) {
34
34
  super.connect(signal);
35
- this.checkStyles(this.group, "toggle.css", [
36
- "cap",
37
- "well",
38
- "keycap"
39
- ]);
40
35
  this.bare.addEventListener("click", (event) => {
41
36
  if (event instanceof PointerEvent && event.pointerType !== "") return;
42
37
  this.setChecked(!this.checked);
@@ -216,6 +216,10 @@ var SonicValueElement = class extends SonicFormElement {
216
216
  get valueText() {
217
217
  return this.#textFor(this.#model.value);
218
218
  }
219
+ notation = {
220
+ format: String,
221
+ parse: Number.parseFloat
222
+ };
219
223
  #formatEntry;
220
224
  #formatSpokenValue;
221
225
  #formatValue;
@@ -266,7 +270,7 @@ var SonicValueElement = class extends SonicFormElement {
266
270
  isDisabled: () => this.isDisabled(),
267
271
  keyStep: () => this.keyStep,
268
272
  model: this.#model,
269
- parse: (text) => (this.#parseValue ?? Number.parseFloat)(text),
273
+ parse: (text) => (this.#parseValue ?? this.notation.parse)(text),
270
274
  render: () => {
271
275
  this.render();
272
276
  },
@@ -368,7 +372,7 @@ var SonicValueElement = class extends SonicFormElement {
368
372
  #entryText() {
369
373
  const value = this.#model.value;
370
374
  if (this.#formatEntry) return this.#formatEntry(value);
371
- if (!this.#parseValue) return String(value);
375
+ if (!this.#parseValue) return this.notation.format(value);
372
376
  return this.valueText;
373
377
  }
374
378
  #isEditing() {
@@ -394,7 +398,7 @@ var SonicValueElement = class extends SonicFormElement {
394
398
  writeAttribute(control, "aria-valuemax", String(high));
395
399
  writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
396
400
  if (orientation) writeAttribute(control, "aria-orientation", orientation);
397
- writeAttribute(control, "aria-valuetext", (this.#formatSpokenValue ?? this.#formatValue)?.(this.#spokenNow()));
401
+ writeAttribute(control, "aria-valuetext", (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(this.#spokenNow()));
398
402
  this.forwardNaming(control, true);
399
403
  this.#renderDisabled(control);
400
404
  this.renderReadout();
@@ -448,7 +452,7 @@ var SonicValueElement = class extends SonicFormElement {
448
452
  return step !== void 0 && step > 0 ? trimFloat(Math.round(this.#model.value / step) * step) : this.#model.value;
449
453
  }
450
454
  #textFor(value) {
451
- return this.#formatValue?.(value) ?? String(value);
455
+ return (this.#formatValue ?? this.notation.format)(value);
452
456
  }
453
457
  #writeHeld(next) {
454
458
  this.#heldAt = next;
@@ -1,5 +1,7 @@
1
1
  import { SonicValueElement } from "./value-element.js";
2
2
  import { bindSurface } from "../lib/canvas-surface.js";
3
+ import { formatClock, parseClock } from "../lib/clock.js";
4
+ import { spokenDuration } from "../lib/spoken-duration.js";
3
5
 
4
6
  //#region src/elements/wave-element.ts
5
7
  function markerKey(marker) {
@@ -30,6 +32,11 @@ var SonicWaveElement = class extends SonicValueElement {
30
32
  this.renderMarkers();
31
33
  this.render();
32
34
  }
35
+ notation = {
36
+ format: formatClock,
37
+ parse: parseClock,
38
+ speak: (seconds) => spokenDuration(seconds, this.closest("[lang]")?.lang)
39
+ };
33
40
  #current;
34
41
  #markers = [];
35
42
  #surface;
@@ -65,14 +72,6 @@ var SonicWaveElement = class extends SonicValueElement {
65
72
  signal
66
73
  });
67
74
  this.render();
68
- this.checkStyles(control, this.sheet, [
69
- "cap",
70
- "glass",
71
- "pane",
72
- "well",
73
- "readout",
74
- "value"
75
- ]);
76
75
  this.#surface.setFill(this.fill);
77
76
  }
78
77
  focusTarget() {
@@ -224,13 +224,6 @@ var SonicXy = class extends SonicFormElement {
224
224
  const xy = this.#xy;
225
225
  this.keepControl(xy, signal);
226
226
  this.render();
227
- this.checkStyles(xy, "xy.css", [
228
- "cap",
229
- "glass",
230
- "pane",
231
- "well",
232
- "readout"
233
- ]);
234
227
  this.#bindPointer(xy, signal);
235
228
  this.#bindKeys(xy, signal);
236
229
  }
@@ -10,10 +10,11 @@ import { SonicSpectrum } from "./elements/spectrum.js";
10
10
  import { SonicSplit } from "./elements/split.js";
11
11
  import { SonicSwitch } from "./elements/switch.js";
12
12
  import { SonicToggle } from "./elements/toggle.js";
13
+ import { formatClock, parseClock } from "./lib/clock.js";
13
14
  import { SonicWaveform } from "./elements/waveform.js";
14
15
  import { SonicWavestrip } from "./elements/wavestrip.js";
15
16
  import { SonicXy } from "./elements/xy.js";
16
17
  import { crossfadeGains } from "./lib/crossfade.js";
17
18
  import { formatPercent, parsePercent } from "./lib/percent.js";
18
19
 
19
- export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatPercent, parsePercent };
20
+ export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
@@ -89,7 +89,11 @@ var CanvasSurface = class {
89
89
  #armTransitions() {
90
90
  const { canvas, colours, numbers = {} } = this.#options;
91
91
  const { style } = canvas;
92
- const watched = [...Object.values(colours), ...Object.values(numbers)];
92
+ const watched = [
93
+ "color",
94
+ ...Object.values(colours),
95
+ ...Object.values(numbers)
96
+ ];
93
97
  style.setProperty("transition-property", watched.join(", "));
94
98
  style.setProperty("transition-duration", "0.001ms");
95
99
  style.setProperty("transition-timing-function", "step-start");
@@ -0,0 +1,23 @@
1
+ //#region src/lib/clock.ts
2
+ const sign = /^[-−]/;
3
+ function formatClock(seconds) {
4
+ const total = Math.floor(Math.abs(seconds));
5
+ const hours = Math.floor(total / 3600);
6
+ const minutes = Math.floor(total / 60) % 60;
7
+ const rest = String(total % 60).padStart(2, "0");
8
+ const prefix = seconds < 0 ? "−" : "";
9
+ if (hours === 0) return `${prefix}${String(minutes)}:${rest}`;
10
+ return `${prefix}${String(hours)}:${String(minutes).padStart(2, "0")}:${rest}`;
11
+ }
12
+ function parseClock(text) {
13
+ const clock = text.trim();
14
+ let seconds = 0;
15
+ for (const part of clock.replace(sign, "").split(":")) {
16
+ if (part.trim() === "") return NaN;
17
+ seconds = seconds * 60 + Number(part);
18
+ }
19
+ return sign.test(clock) ? -seconds : seconds;
20
+ }
21
+
22
+ //#endregion
23
+ export { formatClock, parseClock };
@@ -0,0 +1,30 @@
1
+ //#region src/lib/spoken-duration.ts
2
+ const formatters = /* @__PURE__ */ new Map();
3
+ function formatterFor(language, isZero) {
4
+ const key = `${language}|${String(isZero)}`;
5
+ const cached = formatters.get(key);
6
+ if (cached) return cached;
7
+ const options = {
8
+ secondsDisplay: isZero ? "always" : "auto",
9
+ style: "long"
10
+ };
11
+ let formatter;
12
+ try {
13
+ formatter = new Intl.DurationFormat(language || void 0, options);
14
+ } catch {
15
+ formatter = new Intl.DurationFormat(void 0, options);
16
+ }
17
+ formatters.set(key, formatter);
18
+ return formatter;
19
+ }
20
+ function spokenDuration(seconds, language = "") {
21
+ const total = Math.trunc(seconds);
22
+ return formatterFor(language, total === 0).format({
23
+ hours: Math.trunc(total / 3600),
24
+ minutes: Math.trunc(total / 60) % 60,
25
+ seconds: total % 60
26
+ });
27
+ }
28
+
29
+ //#endregion
30
+ export { spokenDuration };
@@ -1,9 +1,15 @@
1
1
  //#region src/lib/waveform-buckets.ts
2
+ function fullScaleOf({ fullScale, samples }) {
3
+ if (fullScale !== void 0) return fullScale;
4
+ if (samples instanceof Int8Array) return 128;
5
+ return samples instanceof Int16Array ? 32768 : 1;
6
+ }
2
7
  function foldPairs(peaks, fromPair, toPair) {
3
8
  const { samples } = peaks;
4
9
  const channels = Math.max(1, peaks.channels ?? 1);
5
10
  const stride = channels * 2;
6
11
  const pairsTotal = Math.floor(samples.length / stride);
12
+ const fullScale = fullScaleOf(peaks);
7
13
  let low = 0;
8
14
  let high = 0;
9
15
  for (let pair = Math.max(0, fromPair); pair < Math.min(pairsTotal, toPair); pair += 1) for (let channel = 0; channel < channels; channel += 1) {
@@ -11,7 +17,7 @@ function foldPairs(peaks, fromPair, toPair) {
11
17
  low = Math.min(low, samples[at] ?? 0);
12
18
  high = Math.max(high, samples[at + 1] ?? 0);
13
19
  }
14
- return [low / peaks.fullScale, high / peaks.fullScale];
20
+ return [low / fullScale, high / fullScale];
15
21
  }
16
22
  function waveformBuckets(peaks, view) {
17
23
  const { pairsPerSecond } = peaks;
@@ -116,11 +116,11 @@ var SonicButton = class extends SonicFormElement {
116
116
  }
117
117
  }, signal);
118
118
  this.render();
119
- this.checkStyles(button, "button.css", [
120
- "cap",
121
- "well",
122
- "keycap"
123
- ]);
119
+ this.checkStyles(button, "button.css", {
120
+ property: "padding-inline-start",
121
+ ratio: "--_sonic-button-padding-ratio",
122
+ selector: ".sonic-button-cap > :not(svg, .sonic-led)"
123
+ });
124
124
  button.addEventListener("click", (event) => {
125
125
  if (this.#isSoftDisabled()) {
126
126
  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\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\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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"}
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 || !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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"}
@@ -66,13 +66,10 @@ var SonicDial = class extends SonicValueElement {
66
66
  marks: requireChild(dial, ".sonic-dial-scale", HTMLDivElement)
67
67
  }, signal);
68
68
  this.render();
69
- this.checkStyles(dial, "dial.css", [
70
- "cap",
71
- "readout",
72
- "value",
73
- ["glass", ".sonic-dial-readout"],
74
- ["arc", ".sonic-dial-ring"]
75
- ]);
69
+ this.checkStyles(dial, "dial.css", {
70
+ property: "margin-bottom",
71
+ selector: "[popover]"
72
+ });
76
73
  this.bindGestures(dial, signal, () => ({
77
74
  position: (event) => -event.clientY,
78
75
  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/** How far a modulation source can push the value, in value units; signed */\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/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\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;;CAIA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAIA,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"}
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 const __DEV__: boolean;\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/** How far a modulation source can push the value, in value units; signed */\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/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\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\tif (__DEV__)\n\t\t\tthis.checkStyles(dial, 'dial.css', { property: 'margin-bottom', selector: '[popover]' });\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":";;;;;;AAaA,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;;CAIA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAIA,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;EAEX,KAAK,YAAY,MAAM,YAAY;GAAE,UAAU;GAAiB,UAAU;EAAY,CAAC;EACxF,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"}
@@ -69,11 +69,7 @@ var SonicDisplayElement = class extends SonicElement {
69
69
  },
70
70
  signal
71
71
  });
72
- this.checkStyles(control, this.sheet, [
73
- "glass",
74
- "pane",
75
- "well"
76
- ]);
72
+ this.checkStyles(control, this.sheet);
77
73
  this.#surface.setFill(this.fill);
78
74
  }
79
75
  surface() {