@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.
- package/CHANGELOG.md +42 -2
- package/README.md +6 -4
- package/dist/default/define/region.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +3 -0
- package/dist/default/elements/dial.js +3 -0
- package/dist/default/elements/envelope.js +14 -11
- package/dist/default/elements/form-element.js +10 -0
- package/dist/default/elements/meter.js +3 -0
- package/dist/default/elements/number.js +3 -0
- package/dist/default/elements/radio-group.js +4 -1
- package/dist/default/elements/readout.js +1 -1
- package/dist/default/elements/region-layer.js +253 -0
- package/dist/default/elements/region.js +161 -0
- package/dist/default/elements/slider.js +3 -0
- package/dist/default/elements/sonic-element.js +7 -9
- package/dist/default/elements/split.js +2 -2
- package/dist/default/elements/toggle.js +4 -4
- package/dist/default/elements/value-element.js +21 -3
- package/dist/default/elements/value-gestures.js +7 -5
- package/dist/default/elements/wave-element.js +15 -3
- package/dist/default/elements/waveform.js +38 -2
- package/dist/default/elements/wavestrip.js +90 -17
- package/dist/default/elements/xy.js +6 -11
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +22 -1
- package/dist/default/lib/clock.js +1 -0
- package/dist/default/lib/drag-step.js +1 -1
- package/dist/default/lib/key-press.js +2 -2
- package/dist/default/lib/marker-band.js +40 -29
- package/dist/default/lib/region-drag.js +48 -0
- package/dist/dev/define/region.d.ts +2 -0
- package/dist/dev/define/region.js +8 -0
- package/dist/dev/define/region.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.d.ts +1 -0
- package/dist/dev/elements/button.js +3 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +1 -0
- package/dist/dev/elements/dial.js +3 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +1 -0
- package/dist/dev/elements/envelope.js +14 -11
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/form-element.d.ts +1 -0
- package/dist/dev/elements/form-element.js +10 -0
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +1 -0
- package/dist/dev/elements/meter.js +3 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +1 -0
- package/dist/dev/elements/number.js +3 -0
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +1 -0
- package/dist/dev/elements/radio-group.js +4 -1
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout.js +1 -1
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/region-layer.js +254 -0
- package/dist/dev/elements/region-layer.js.map +1 -0
- package/dist/dev/elements/region.d.ts +30 -0
- package/dist/dev/elements/region.js +169 -0
- package/dist/dev/elements/region.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +1 -0
- package/dist/dev/elements/slider.js +3 -0
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +2 -0
- package/dist/dev/elements/sonic-element.js +8 -10
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js +2 -2
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/toggle.js +4 -4
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +3 -1
- package/dist/dev/elements/value-element.js +21 -3
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +7 -5
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +8 -1
- package/dist/dev/elements/wave-element.js +15 -3
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +7 -0
- package/dist/dev/elements/waveform.js +38 -2
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +17 -1
- package/dist/dev/elements/wavestrip.js +90 -17
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +1 -0
- package/dist/dev/elements/xy.js +6 -11
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/canvas-surface.js +22 -1
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/clock.js +1 -0
- package/dist/dev/lib/clock.js.map +1 -1
- package/dist/dev/lib/drag-step.js +1 -1
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/key-press.js +2 -2
- package/dist/dev/lib/key-press.js.map +1 -1
- package/dist/dev/lib/marker-band.d.ts +7 -0
- package/dist/dev/lib/marker-band.js +40 -29
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/region-drag.js +49 -0
- package/dist/dev/lib/region-drag.js.map +1 -0
- package/dist/styles/button.css +23 -13
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +41 -24
- package/dist/styles/envelope.css +12 -5
- package/dist/styles/led.css +8 -8
- package/dist/styles/material/arc.css +2 -2
- package/dist/styles/material/cap.css +34 -13
- package/dist/styles/material/glass.css +1 -1
- package/dist/styles/material/groove.css +3 -3
- package/dist/styles/material/keycap.css +2 -2
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +20 -6
- package/dist/styles/material/readout.css +14 -5
- package/dist/styles/material/{bracket.css → reticle.css} +15 -8
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/material.css +1 -1
- package/dist/styles/meter.css +29 -16
- package/dist/styles/number.css +3 -2
- package/dist/styles/panel.css +3 -3
- package/dist/styles/region.css +94 -0
- package/dist/styles/ring.css +2 -1
- package/dist/styles/screen.css +7 -5
- package/dist/styles/segmented.css +5 -4
- package/dist/styles/slider.css +24 -15
- package/dist/styles/spectrum.css +9 -1
- package/dist/styles/switch.css +9 -2
- package/dist/styles/toggle.css +5 -4
- package/dist/styles/waveform.css +10 -3
- package/dist/styles/wavestrip.css +17 -8
- package/dist/styles/xy.css +24 -11
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.26.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- - 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- Region: `tabindex="-1"` on `<sonic-region>` takes its part out of the tab order, as on a value control.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- - 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.
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
## 0.25.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- `formatClock` writes a time that is not finite (a duration not yet known, a live stream's) as an empty string, where it wrote `NaN:NaN`.
|
|
22
|
+
|
|
23
|
+
## 0.25.0
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- - `<sonic-region>`: an editable region, a child of a wave strip, drawn as a bracket and registered by `./define/region` with `region.css`. A drag of its body or a key moves it and keeps its length. It has `start`, `end`, `kind`, `name` (submitting `name.start` and `name.end`) and `disabled`, speaks its start, carries `:state(dragging)`, and fires `input` and `change`, which bubble through the strip.
|
|
28
|
+
- Region surface: tokens `--sonic-region-bracket`, `--sonic-region-fill`, `--sonic-region-bracket-width` and `--sonic-region-bracket-radius`; hook classes `.sonic-wavestrip-regions`, `.sonic-region` and `.sonic-region-bracket`; `data-sonic-clipped` on a region written past a bound.
|
|
29
|
+
- Wave strip: `formatSpokenRegion(start, end)` words what a region speaks, and `preview` draws a time as a scrub's edge without scrubbing to it.
|
|
30
|
+
- Wave strip and waveform: `valueFromPoint(clientX, clientY)` is the inverse of `clientXOf`.
|
|
31
|
+
- Wave strip, breaking: the slider role, the tab stop and the focus glow are on `.sonic-wavestrip-canvas`, so `.sonic-wavestrip:focus-visible` no longer matches (`:focus-within` does).
|
|
32
|
+
- - XY pad and envelope: the arcs round a held puck or handle are the reticle. Breaking: `.sonic-xy-bracket` is `.sonic-xy-reticle`, `.sonic-envelope-bracket` is `.sonic-envelope-reticle`, and `material/bracket.css` is `material/reticle.css`.
|
|
33
|
+
- - Waveform: `clientXOf(seconds)` is the viewport x where a time was last drawn, as on the wave strip, so a layer over the wave follows a paged window and a drag. The x is not clamped to the window.
|
|
34
|
+
- Wave strip and waveform: `markersFromPoint(clientX, clientY)` lists the markers at a point, nearest first.
|
|
35
|
+
- Wave strip: `renderMarker(marker, element)` draws a point marker; the element lasts until the next `markers` or `renderMarker` write.
|
|
36
|
+
- Waveform: a marker outside the bounds paints its line at the bound.
|
|
37
|
+
- Wave strip: `--sonic-wave` is renamed `--sonic-unplayed`, beside `--sonic-buffered` and `--sonic-scrub`. Breaking for a skin or host that sets it.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Older browsers, down to Chrome 111, Safari 16.4 and Firefox 128: the built sheets carry vendor prefixes (`-webkit-mask`, `-webkit-user-select`), shading no longer needs relative colour, a dial's scale and the XY pad's lines no longer need the `cap` unit or `sign()`, and typed entry stays shut without the popover API. With no skin, glass follows `Canvas`: an off-white in light, the engine's dark canvas in dark.
|
|
42
|
+
|
|
3
43
|
## 0.24.1
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
|
@@ -29,8 +69,8 @@
|
|
|
29
69
|
|
|
30
70
|
- - `--sonic-entry-selection` colours the selected text of a typed entry, which opens with its value selected. Unset, it stays the control's lit colour.
|
|
31
71
|
- `--sonic-slider-groove-hover-delay` is how long a slider's groove waits under a pointer before it thickens to `--sonic-slider-groove-hover-ratio`. A press or a keyboard focus thickens it at once and leaving thins it at once. Unset, there is no wait.
|
|
32
|
-
- `--sonic-readout-depth` scales a readout's cast shadow, inner shine and lighter
|
|
33
|
-
- Behaviour change: a readout no longer takes `--sonic-glass-depth` from a screen around it. Inside a screen flattened with `--sonic-glass-depth: 0` its
|
|
72
|
+
- `--sonic-readout-depth` scales a readout's cast shadow, inner shine and lighter center from 0 to 1. Unset, it follows the relief.
|
|
73
|
+
- Behaviour change: a readout no longer takes `--sonic-glass-depth` from a screen around it. Inside a screen flattened with `--sonic-glass-depth: 0` its center follows the relief again, as its shadow always did; set `--sonic-readout-depth: 0` to flatten it.
|
|
34
74
|
|
|
35
75
|
## 0.21.0
|
|
36
76
|
|
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
43
43
|
- The split is the one host with a box: `<sonic-split>` is inline until given a display, and laying it out is yours.
|
|
44
44
|
- Register the classes under their stock tag names (`sonic-dial`, `sonic-split`). The sheets and the split key on them, so a split under another tag finds no members.
|
|
45
45
|
- Name every control with `aria-label` or `aria-labelledby` on the host, or with a `<label>` for a form-associated one.
|
|
46
|
+
- Every control that holds a value the user sets is form-associated, as a native input is, and that is deliberate: given a `name` it submits, a form reset returns it to its attributes, a disabled `<fieldset>` disables it, and a `<label>` names it. With no `name` it submits nothing. A value control submits its number and a segmented control its value; a latching button, and a switch or toggle with two positions, submit their `value` only while on, as a checkbox does; an XY pad and a region submit two fields (`name.x` and `name.y`, `name.start` and `name.end`). A momentary button, the displays, the envelope and the split submit nothing.
|
|
46
47
|
- A boolean attribute is on when present, whatever its value, so `pressed="false"` latches a button. From a framework pass `undefined`, never `false`.
|
|
47
48
|
- A control uses only the children it documents and adds one child of its own. Children it draws are copied, so a copy has none of your listeners; keep them static. Under a hydrating framework, register the definitions once hydration is done (in React, from an effect).
|
|
48
49
|
- Events bubble and carry no detail: read the property when one fires. A control knows nothing about playback, so wire its events to your own state and write the property back.
|
|
@@ -50,7 +51,7 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
50
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.
|
|
51
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.
|
|
52
53
|
- A display takes its signal as data, and nothing in the library fetches, decodes or opens an `AudioContext`.
|
|
53
|
-
-
|
|
54
|
+
- A state a control sets itself is an attribute on its drawn part, `data-sonic-<state>`, selected as `.sonic-slider[data-sonic-dragging]`.
|
|
54
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.
|
|
55
56
|
|
|
56
57
|
## Skinning
|
|
@@ -75,11 +76,12 @@ A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to st
|
|
|
75
76
|
| --------------- | ----------------------------------------------- |
|
|
76
77
|
| `button.css` | core, cap, well, keycap |
|
|
77
78
|
| `dial.css` | core, cap, glass, readout, value, arc, scale |
|
|
78
|
-
| `envelope.css` | core, glass, readout,
|
|
79
|
+
| `envelope.css` | core, glass, readout, reticle |
|
|
79
80
|
| `led.css` | core, lens |
|
|
80
81
|
| `meter.css` | core, lens, groove |
|
|
81
82
|
| `number.css` | core, cap, value |
|
|
82
83
|
| `panel.css` | core |
|
|
84
|
+
| `region.css` | core, cap |
|
|
83
85
|
| `ring.css` | core, arc |
|
|
84
86
|
| `screen.css` | core, glass, pane, well |
|
|
85
87
|
| `segmented.css` | core, cap, well, keycap |
|
|
@@ -89,8 +91,8 @@ A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to st
|
|
|
89
91
|
| `toggle.css` | core, cap, well, keycap |
|
|
90
92
|
| `waveform.css` | core, cap, glass, readout, value |
|
|
91
93
|
| `wavestrip.css` | core, cap, glass, readout, value |
|
|
92
|
-
| `xy.css` | core, cap, glass, readout,
|
|
94
|
+
| `xy.css` | core, cap, glass, readout, reticle |
|
|
93
95
|
|
|
94
96
|
An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`. Only a missing `core.css` or control sheet is warned about, so check a control that draws wrong against this table.
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
Tested in current Chrome, Firefox and Safari. Known to work from Chrome 111, Safari 16.4 and Firefox 128; older versions are best effort. No control mirrors for right-to-left. MIT licensed.
|
package/dist/default/define.js
CHANGED
|
@@ -149,6 +149,9 @@ var SonicButton = class extends SonicFormElement {
|
|
|
149
149
|
restoreState(state) {
|
|
150
150
|
if (this.latching) this.pressed = state === "true";
|
|
151
151
|
}
|
|
152
|
+
stateTarget() {
|
|
153
|
+
return this.#button;
|
|
154
|
+
}
|
|
152
155
|
#bindMomentary(button, signal) {
|
|
153
156
|
const holding = bindHold(button, {
|
|
154
157
|
canHold: () => !this.isDisabled() && !this.#isSoftDisabled(),
|
|
@@ -36,7 +36,7 @@ function stageParts(envelope, hook, stages) {
|
|
|
36
36
|
}
|
|
37
37
|
const renderEnvelope = template(`
|
|
38
38
|
<div class="sonic-envelope" aria-hidden="true">
|
|
39
|
-
<div class="sonic-envelope-
|
|
39
|
+
<div class="sonic-envelope-reticle"></div>
|
|
40
40
|
<svg class="sonic-envelope-graph" preserveAspectRatio="none" viewBox="0 0 1 1">
|
|
41
41
|
<path class="sonic-envelope-fill" />
|
|
42
42
|
<rect class="sonic-envelope-fill sonic-envelope-floor" y="1" />
|
|
@@ -123,11 +123,10 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
123
123
|
this.reflect("sustain", id);
|
|
124
124
|
}
|
|
125
125
|
#bindings = /* @__PURE__ */ new Map();
|
|
126
|
-
#envelope = renderEnvelope();
|
|
127
|
-
#bracket = requireChild(this.#envelope, ".sonic-envelope-bracket", HTMLDivElement);
|
|
128
126
|
#claim = new ReadoutClaim(() => {
|
|
129
127
|
this.#renderReadout();
|
|
130
128
|
});
|
|
129
|
+
#envelope = renderEnvelope();
|
|
131
130
|
#curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
|
|
132
131
|
#fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
|
|
133
132
|
#floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
|
|
@@ -138,6 +137,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
138
137
|
#isDriving = false;
|
|
139
138
|
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
140
139
|
#readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
|
|
140
|
+
#reticle = requireChild(this.#envelope, ".sonic-envelope-reticle", HTMLDivElement);
|
|
141
141
|
#shape = adsrShape({});
|
|
142
142
|
#waiting = /* @__PURE__ */ new Set();
|
|
143
143
|
#warned = /* @__PURE__ */ new Set();
|
|
@@ -164,6 +164,9 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
164
164
|
this.#unbind();
|
|
165
165
|
}, { once: true });
|
|
166
166
|
}
|
|
167
|
+
stateTarget() {
|
|
168
|
+
return this.#envelope;
|
|
169
|
+
}
|
|
167
170
|
#bind() {
|
|
168
171
|
this.#gesture?.end();
|
|
169
172
|
this.#unbind();
|
|
@@ -229,15 +232,9 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
229
232
|
at: shape.curveHandles.find((point) => point.stage === stage),
|
|
230
233
|
drives: this.#curveDrives(stage)
|
|
231
234
|
});
|
|
232
|
-
this.#
|
|
235
|
+
this.#drawReticle();
|
|
233
236
|
this.#renderReadout();
|
|
234
237
|
}
|
|
235
|
-
#drawBracket() {
|
|
236
|
-
const part = this.#held;
|
|
237
|
-
if (!part) return;
|
|
238
|
-
for (const name of ["--_sonic-envelope-x", "--_sonic-envelope-y"]) this.#bracket.style.setProperty(name, part.style.getPropertyValue(name));
|
|
239
|
-
writeAttribute(this.#bracket, "data-sonic-part", part.classList.contains("sonic-envelope-curve") ? "curve" : void 0);
|
|
240
|
-
}
|
|
241
238
|
#drawPoint(part, point) {
|
|
242
239
|
const { at, drives } = point;
|
|
243
240
|
writeAttribute(part, "hidden", at ? void 0 : "");
|
|
@@ -248,6 +245,12 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
248
245
|
writeAttribute(part, "data-sonic-axes", drives.map(({ axis }) => axis).join(""));
|
|
249
246
|
writeAttribute(part, "data-sonic-disabled", isStill ? "" : void 0);
|
|
250
247
|
}
|
|
248
|
+
#drawReticle() {
|
|
249
|
+
const part = this.#held;
|
|
250
|
+
if (!part) return;
|
|
251
|
+
for (const name of ["--_sonic-envelope-x", "--_sonic-envelope-y"]) this.#reticle.style.setProperty(name, part.style.getPropertyValue(name));
|
|
252
|
+
writeAttribute(this.#reticle, "data-sonic-part", part.classList.contains("sonic-envelope-curve") ? "curve" : void 0);
|
|
253
|
+
}
|
|
251
254
|
#drives(stage) {
|
|
252
255
|
const x = this.#bindings.get(stage);
|
|
253
256
|
const y = stage === "decay" ? this.#bindings.get("sustain") : void 0;
|
|
@@ -263,7 +266,7 @@ var SonicEnvelope = class extends SonicElement {
|
|
|
263
266
|
const pressed = this.#pressed(event);
|
|
264
267
|
if (!pressed || pressed.drives.length === 0) return void 0;
|
|
265
268
|
this.#held = pressed.part;
|
|
266
|
-
this.#
|
|
269
|
+
this.#drawReticle();
|
|
267
270
|
const box = this.#graph.getBoundingClientRect();
|
|
268
271
|
const travel = {
|
|
269
272
|
x: Math.max(1, box.width * this.#shape.share),
|
|
@@ -47,6 +47,16 @@ var SonicFormElement = class extends SonicElement {
|
|
|
47
47
|
if (!internals || this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby")) return [];
|
|
48
48
|
return [...internals.labels].filter((label) => label instanceof Element);
|
|
49
49
|
}
|
|
50
|
+
writeFormFields(fields, state) {
|
|
51
|
+
const { name } = this;
|
|
52
|
+
if (name === "") {
|
|
53
|
+
this.writeFormValue(null, state);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const data = new FormData();
|
|
57
|
+
for (const [field, value] of fields) data.set(`${name}.${field}`, String(value));
|
|
58
|
+
this.writeFormValue(data, state);
|
|
59
|
+
}
|
|
50
60
|
writeFormValue(value, state) {
|
|
51
61
|
this.internals?.setFormValue(value, `${this.localName}:${state}`);
|
|
52
62
|
}
|
|
@@ -148,6 +148,9 @@ var SonicMeter = class extends SonicElement {
|
|
|
148
148
|
this.#lastFrame = void 0;
|
|
149
149
|
}, { once: true });
|
|
150
150
|
}
|
|
151
|
+
stateTarget() {
|
|
152
|
+
return this.#meter;
|
|
153
|
+
}
|
|
151
154
|
#isSettled(now) {
|
|
152
155
|
if (this.#isValueShown) return true;
|
|
153
156
|
if (now - this.#clipAt < holdMs) return false;
|
|
@@ -139,6 +139,9 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
139
139
|
this.value = next;
|
|
140
140
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
141
141
|
}
|
|
142
|
+
stateTarget() {
|
|
143
|
+
return this.group;
|
|
144
|
+
}
|
|
142
145
|
stepFrom(from, step) {
|
|
143
146
|
return stepFrom(this.positions(), from, {
|
|
144
147
|
isWrapping: this.isWrapping(),
|
|
@@ -192,7 +195,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
|
|
|
192
195
|
if (focused) this.#refocus(focused, focusedValue);
|
|
193
196
|
}
|
|
194
197
|
#markPressed(pressed) {
|
|
195
|
-
for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-
|
|
198
|
+
for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-active", index === pressed);
|
|
196
199
|
}
|
|
197
200
|
#optionBoxes() {
|
|
198
201
|
const positions = this.positions();
|
|
@@ -2,7 +2,7 @@ import { requireChild } from "../lib/render.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/elements/readout.ts
|
|
4
4
|
const canPopover = "togglePopover" in HTMLElement.prototype;
|
|
5
|
-
const isUnanchored =
|
|
5
|
+
const isUnanchored = !CSS.supports("anchor-name", "--a");
|
|
6
6
|
let readoutCount = 0;
|
|
7
7
|
var Readout = class {
|
|
8
8
|
#anchor;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
2
|
+
import { placeChildren, template } from "../lib/render.js";
|
|
3
|
+
import { capturePointer } from "../lib/pointer-drag.js";
|
|
4
|
+
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
5
|
+
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
6
|
+
import { SonicRegion, linkRegion, ownRegions } from "./region.js";
|
|
7
|
+
import { writeKind } from "../lib/marker-band.js";
|
|
8
|
+
import { moveRegionTo, regionAt, regionTravel, startRegionDrag, stepRegionDrag } from "../lib/region-drag.js";
|
|
9
|
+
|
|
10
|
+
//#region src/elements/region-layer.ts
|
|
11
|
+
const renderPart = template(`
|
|
12
|
+
<div class="sonic-region" role="slider" aria-orientation="horizontal">
|
|
13
|
+
<div class="sonic-region-bracket"></div>
|
|
14
|
+
</div>
|
|
15
|
+
`, HTMLDivElement);
|
|
16
|
+
function hasMoved(from, to) {
|
|
17
|
+
return from.start !== to.start || from.end !== to.end;
|
|
18
|
+
}
|
|
19
|
+
function clippedEnds(region, [low, high]) {
|
|
20
|
+
const ends = [...Math.min(region.start, region.end) < low ? ["start"] : [], ...Math.max(region.start, region.end) > high ? ["end"] : []];
|
|
21
|
+
return ends.length > 0 ? ends.join(" ") : void 0;
|
|
22
|
+
}
|
|
23
|
+
function writeProperty(part, name, value) {
|
|
24
|
+
if (part.style.getPropertyValue(name) !== value) part.style.setProperty(name, value);
|
|
25
|
+
}
|
|
26
|
+
var RegionLayer = class {
|
|
27
|
+
#drawn = [];
|
|
28
|
+
#held;
|
|
29
|
+
#host;
|
|
30
|
+
#key;
|
|
31
|
+
#keyScrub;
|
|
32
|
+
constructor(host, signal) {
|
|
33
|
+
const render = () => {
|
|
34
|
+
this.render();
|
|
35
|
+
};
|
|
36
|
+
const observer = new MutationObserver(render);
|
|
37
|
+
this.#host = host;
|
|
38
|
+
this.#bindPointer(signal);
|
|
39
|
+
this.#bindKeys(signal);
|
|
40
|
+
observer.observe(host.element, { childList: true });
|
|
41
|
+
ownRegions(host.element, (region) => {
|
|
42
|
+
this.#redraw(region);
|
|
43
|
+
}, signal);
|
|
44
|
+
signal.addEventListener("abort", () => {
|
|
45
|
+
observer.disconnect();
|
|
46
|
+
this.#release();
|
|
47
|
+
this.#endKeyScrub();
|
|
48
|
+
}, { once: true });
|
|
49
|
+
}
|
|
50
|
+
abandon() {
|
|
51
|
+
this.#release(true);
|
|
52
|
+
}
|
|
53
|
+
follow() {
|
|
54
|
+
if (this.#drawn.length === 0) return;
|
|
55
|
+
if (this.#stripKey() !== this.#key) this.render();
|
|
56
|
+
}
|
|
57
|
+
isHeld() {
|
|
58
|
+
return this.#held !== void 0;
|
|
59
|
+
}
|
|
60
|
+
render() {
|
|
61
|
+
const host = this.#host;
|
|
62
|
+
const drawn = [...host.element.children].filter((child) => child instanceof SonicRegion).map((region) => this.#draw(region));
|
|
63
|
+
this.#drawn = drawn;
|
|
64
|
+
this.#key = this.#stripKey();
|
|
65
|
+
placeChildren(host.layer, drawn.map(({ part }) => part));
|
|
66
|
+
this.#dropGone();
|
|
67
|
+
}
|
|
68
|
+
take(event) {
|
|
69
|
+
const host = this.#host;
|
|
70
|
+
const { startPx, travelPx } = host.axis();
|
|
71
|
+
const mapping = host.mapping();
|
|
72
|
+
const index = regionAt(this.#drawn.map((entry) => {
|
|
73
|
+
if (!this.#isEnabled(entry)) return;
|
|
74
|
+
const { end, start } = entry.link.span();
|
|
75
|
+
return [mapping.proportionOf(start) * travelPx, mapping.proportionOf(end) * travelPx];
|
|
76
|
+
}), event.clientX - startPx);
|
|
77
|
+
const pressed = index === void 0 ? void 0 : this.#drawn[index];
|
|
78
|
+
if (!pressed) return false;
|
|
79
|
+
this.#endKeyScrub();
|
|
80
|
+
const from = pressed.link.span();
|
|
81
|
+
this.#held = {
|
|
82
|
+
drawn: pressed,
|
|
83
|
+
from,
|
|
84
|
+
outside: host.outside(),
|
|
85
|
+
pointerId: event.pointerId,
|
|
86
|
+
press: event,
|
|
87
|
+
state: startRegionDrag({
|
|
88
|
+
from,
|
|
89
|
+
position: event.clientX,
|
|
90
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
91
|
+
widthPx: travelPx
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
pressed.link.hold("pointer");
|
|
95
|
+
capturePointer(host.control, event.pointerId);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
#bindKeys(signal) {
|
|
99
|
+
const { layer } = this.#host;
|
|
100
|
+
layer.addEventListener("keydown", (event) => {
|
|
101
|
+
const entry = this.#drawn.find(({ part }) => part === event.target);
|
|
102
|
+
if (!entry || event.defaultPrevented || this.#held) return;
|
|
103
|
+
if (this.#isEnabled(entry)) this.#keyTo(entry, event);
|
|
104
|
+
}, { signal });
|
|
105
|
+
layer.addEventListener("keyup", (event) => {
|
|
106
|
+
if (event.key === "Meta" || event.key === this.#keyScrub?.key) this.#endKeyScrub();
|
|
107
|
+
}, { signal });
|
|
108
|
+
layer.addEventListener("focusin", () => {
|
|
109
|
+
this.render();
|
|
110
|
+
}, { signal });
|
|
111
|
+
layer.addEventListener("focusout", () => {
|
|
112
|
+
this.#endKeyScrub();
|
|
113
|
+
}, { signal });
|
|
114
|
+
}
|
|
115
|
+
#bindPointer(signal) {
|
|
116
|
+
const whileHeld = (type, handle) => {
|
|
117
|
+
this.#host.control.addEventListener(type, (event) => {
|
|
118
|
+
const held = this.#held;
|
|
119
|
+
if (held && event instanceof PointerEvent && held.pointerId === event.pointerId) handle(held, event);
|
|
120
|
+
}, { signal });
|
|
121
|
+
};
|
|
122
|
+
whileHeld("pointermove", (hold, event) => {
|
|
123
|
+
this.#dragTo(hold, event);
|
|
124
|
+
});
|
|
125
|
+
whileHeld("pointerup", (hold) => {
|
|
126
|
+
const isPress = !hold.state.isEngaged;
|
|
127
|
+
this.#release();
|
|
128
|
+
if (isPress) this.#host.seek(hold.press);
|
|
129
|
+
});
|
|
130
|
+
whileHeld("pointercancel", () => {
|
|
131
|
+
this.#release(true);
|
|
132
|
+
});
|
|
133
|
+
whileHeld("lostpointercapture", () => {
|
|
134
|
+
this.#release();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
#dragTo(hold, event) {
|
|
138
|
+
const wasEngaged = hold.state.isEngaged;
|
|
139
|
+
const { span, state } = stepRegionDrag(this.#host.mapping(), hold.state, {
|
|
140
|
+
isFine: event.shiftKey,
|
|
141
|
+
position: event.clientX
|
|
142
|
+
});
|
|
143
|
+
hold.state = state;
|
|
144
|
+
if (!span) return;
|
|
145
|
+
if (!wasEngaged) focusByPointer(hold.drawn.part);
|
|
146
|
+
hold.drawn.link.input(hold.outside?.(event) === true ? hold.from : span);
|
|
147
|
+
}
|
|
148
|
+
#draw(region) {
|
|
149
|
+
const host = this.#host;
|
|
150
|
+
const known = this.#drawn.find((entry) => entry.region === region);
|
|
151
|
+
const entry = known ?? {
|
|
152
|
+
kind: void 0,
|
|
153
|
+
link: linkRegion(region),
|
|
154
|
+
part: renderPart(),
|
|
155
|
+
region
|
|
156
|
+
};
|
|
157
|
+
const { link, part } = entry;
|
|
158
|
+
const mapping = host.mapping();
|
|
159
|
+
const span = link.span();
|
|
160
|
+
const [low, high] = regionTravel(span, mapping.bounds);
|
|
161
|
+
const canMove = this.#isEnabled(entry);
|
|
162
|
+
const stop = region.getAttribute("tabindex") === "-1" ? "-1" : "0";
|
|
163
|
+
writeProperty(part, "--_sonic-region-from", String(mapping.proportionOf(span.start)));
|
|
164
|
+
writeProperty(part, "--_sonic-region-to", String(mapping.proportionOf(span.end)));
|
|
165
|
+
if (!known || entry.kind !== region.kind) {
|
|
166
|
+
entry.kind = region.kind;
|
|
167
|
+
writeKind(part, region.kind, "--_sonic-ink");
|
|
168
|
+
}
|
|
169
|
+
writeAttribute(part, "data-sonic-clipped", clippedEnds(region, mapping.bounds));
|
|
170
|
+
writeAttribute(part, "aria-valuemin", String(low));
|
|
171
|
+
writeAttribute(part, "aria-valuemax", String(high));
|
|
172
|
+
writeAttribute(part, "aria-valuenow", String(span.start));
|
|
173
|
+
writeAttribute(part, "aria-valuetext", host.speak(span));
|
|
174
|
+
writeAttribute(part, "aria-disabled", canMove ? void 0 : "true");
|
|
175
|
+
writeAttribute(part, "tabindex", canMove ? stop : void 0);
|
|
176
|
+
link.draw(part);
|
|
177
|
+
return entry;
|
|
178
|
+
}
|
|
179
|
+
#dropGone() {
|
|
180
|
+
const isGone = (entry) => !this.#drawn.includes(entry) || !this.#isEnabled(entry);
|
|
181
|
+
if (this.#held && isGone(this.#held.drawn)) this.#release();
|
|
182
|
+
if (this.#keyScrub && isGone(this.#keyScrub.drawn)) this.#endKeyScrub();
|
|
183
|
+
}
|
|
184
|
+
#endKeyScrub() {
|
|
185
|
+
const scrub = this.#keyScrub;
|
|
186
|
+
if (!scrub) return;
|
|
187
|
+
const { link } = scrub.drawn;
|
|
188
|
+
this.#keyScrub = void 0;
|
|
189
|
+
link.hold(void 0);
|
|
190
|
+
if (hasMoved(scrub.from, link.span())) link.change();
|
|
191
|
+
}
|
|
192
|
+
#isEnabled({ link }) {
|
|
193
|
+
return !this.#host.isDisabled() && !link.isDisabled();
|
|
194
|
+
}
|
|
195
|
+
#keyTo(entry, event) {
|
|
196
|
+
const mapping = this.#host.mapping();
|
|
197
|
+
const from = entry.link.span();
|
|
198
|
+
const start = mapping.keyTarget(event.key, from.start);
|
|
199
|
+
if (start === void 0) return;
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
const next = moveRegionTo(mapping, from, start);
|
|
202
|
+
if (!event.repeat) {
|
|
203
|
+
this.#endKeyScrub();
|
|
204
|
+
if (entry.link.input(next)) entry.link.change();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (this.#keyScrub?.drawn !== entry || this.#keyScrub.key !== event.key) {
|
|
208
|
+
this.#endKeyScrub();
|
|
209
|
+
this.#keyScrub = {
|
|
210
|
+
drawn: entry,
|
|
211
|
+
from,
|
|
212
|
+
key: event.key
|
|
213
|
+
};
|
|
214
|
+
entry.link.hold("keys");
|
|
215
|
+
}
|
|
216
|
+
entry.link.input(next);
|
|
217
|
+
}
|
|
218
|
+
#redraw(region) {
|
|
219
|
+
if (this.#drawn.every((entry) => entry.region !== region)) {
|
|
220
|
+
this.render();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.#draw(region);
|
|
224
|
+
this.#dropGone();
|
|
225
|
+
}
|
|
226
|
+
#release(isRestored = false) {
|
|
227
|
+
const hold = this.#held;
|
|
228
|
+
if (!hold) return;
|
|
229
|
+
const { link } = hold.drawn;
|
|
230
|
+
this.#held = void 0;
|
|
231
|
+
if (isRestored) link.input(hold.from);
|
|
232
|
+
link.hold(void 0);
|
|
233
|
+
if (hasMoved(hold.from, link.span())) link.change();
|
|
234
|
+
}
|
|
235
|
+
#stripKey() {
|
|
236
|
+
const host = this.#host;
|
|
237
|
+
const mapping = host.mapping();
|
|
238
|
+
const [low, high] = mapping.bounds;
|
|
239
|
+
return [
|
|
240
|
+
low,
|
|
241
|
+
high,
|
|
242
|
+
mapping.proportionOf((low + high) / 2),
|
|
243
|
+
host.isDisabled(),
|
|
244
|
+
host.speak({
|
|
245
|
+
end: high,
|
|
246
|
+
start: high
|
|
247
|
+
})
|
|
248
|
+
].join("|");
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
export { RegionLayer };
|