@xsynaptic/sonic-ui 0.3.0 → 0.4.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/dist/default/elements/dial.js +5 -3
- package/dist/default/elements/key.js +29 -4
- package/dist/default/elements/meter.js +63 -9
- package/dist/default/elements/number.js +1 -1
- package/dist/default/elements/range-element.js +134 -21
- package/dist/default/elements/slider.js +49 -3
- package/dist/default/lib/detent.js +17 -0
- package/dist/default/lib/math.js +16 -0
- package/dist/default/lib/number-list.js +14 -0
- package/dist/default/lib/taper.js +17 -7
- package/dist/dev/elements/dial.js +5 -3
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/key.d.ts +4 -0
- package/dist/dev/elements/key.js +29 -4
- package/dist/dev/elements/key.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +6 -0
- package/dist/dev/elements/meter.js +63 -9
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +1 -1
- package/dist/dev/elements/range-element.d.ts +8 -0
- package/dist/dev/elements/range-element.js +134 -21
- package/dist/dev/elements/range-element.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +6 -0
- package/dist/dev/elements/slider.js +49 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/lib/detent.js +18 -0
- package/dist/dev/lib/detent.js.map +1 -0
- package/dist/dev/lib/math.js +17 -0
- package/dist/dev/lib/math.js.map +1 -0
- package/dist/dev/lib/number-list.js +15 -0
- package/dist/dev/lib/number-list.js.map +1 -0
- package/dist/dev/lib/taper.js +17 -7
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/styles/dial.css +43 -3
- package/dist/styles/key.css +60 -15
- package/dist/styles/led.css +8 -1
- package/dist/styles/material.css +50 -1
- package/dist/styles/meter.css +101 -5
- package/dist/styles/segmented.css +2 -7
- package/dist/styles/slider.css +94 -32
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { template } from "../lib/render.js";
|
|
2
|
-
import { SonicRangeElement } from "./range-element.js";
|
|
1
|
+
import { requireChild, template } from "../lib/render.js";
|
|
3
2
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
3
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
4
4
|
|
|
5
5
|
//#region src/elements/dial.ts
|
|
6
6
|
const renderDial = template(`
|
|
7
7
|
<div class="sonic-dial" role="slider" tabindex="0">
|
|
8
|
+
<div class="sonic-dial-scale" aria-hidden="true"></div>
|
|
8
9
|
<div class="sonic-dial-notches"></div>
|
|
9
10
|
<div class="sonic-dial-ring"></div>
|
|
10
11
|
<div class="sonic-dial-modulation"></div>
|
|
@@ -49,7 +50,7 @@ var SonicDial = class extends SonicRangeElement {
|
|
|
49
50
|
}
|
|
50
51
|
connect(signal) {
|
|
51
52
|
const dial = this.#dial;
|
|
52
|
-
this.
|
|
53
|
+
this.bindScale(dial, requireChild(dial, ".sonic-dial-scale", HTMLDivElement), signal);
|
|
53
54
|
this.render();
|
|
54
55
|
this.checkStyles(dial, "dial.css");
|
|
55
56
|
this.bindGestures(dial, signal, () => ({
|
|
@@ -70,6 +71,7 @@ var SonicDial = class extends SonicRangeElement {
|
|
|
70
71
|
dial.style.setProperty("--_sonic-dial-modulation-to", String(modulationTo));
|
|
71
72
|
if (positions === void 0) dial.style.removeProperty("--_sonic-dial-positions");
|
|
72
73
|
else dial.style.setProperty("--_sonic-dial-positions", String(positions));
|
|
74
|
+
this.renderScale();
|
|
73
75
|
this.renderAria(dial);
|
|
74
76
|
}
|
|
75
77
|
wraps() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
2
3
|
import { SonicFormElement } from "./form-element.js";
|
|
3
4
|
import { copyNode } from "../lib/copy-node.js";
|
|
4
5
|
|
|
@@ -10,11 +11,19 @@ const holdKeys = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
|
10
11
|
var SonicKey = class extends SonicFormElement {
|
|
11
12
|
static observedAttributes = [
|
|
12
13
|
...SonicFormElement.observedAttributes,
|
|
14
|
+
"busy",
|
|
13
15
|
"momentary",
|
|
14
16
|
"pressed",
|
|
17
|
+
"soft-disabled",
|
|
15
18
|
"toggle",
|
|
16
19
|
"value"
|
|
17
20
|
];
|
|
21
|
+
get busy() {
|
|
22
|
+
return this.hasAttribute("busy");
|
|
23
|
+
}
|
|
24
|
+
set busy(isBusy) {
|
|
25
|
+
this.reflect("busy", isBusy);
|
|
26
|
+
}
|
|
18
27
|
get defaultPressed() {
|
|
19
28
|
return this.hasAttribute("pressed");
|
|
20
29
|
}
|
|
@@ -35,6 +44,12 @@ var SonicKey = class extends SonicFormElement {
|
|
|
35
44
|
this.#pressed = isPressed === true;
|
|
36
45
|
this.render();
|
|
37
46
|
}
|
|
47
|
+
get softDisabled() {
|
|
48
|
+
return this.hasAttribute("soft-disabled");
|
|
49
|
+
}
|
|
50
|
+
set softDisabled(isSoftDisabled) {
|
|
51
|
+
this.reflect("soft-disabled", isSoftDisabled);
|
|
52
|
+
}
|
|
38
53
|
get toggle() {
|
|
39
54
|
return this.hasAttribute("toggle");
|
|
40
55
|
}
|
|
@@ -53,7 +68,7 @@ var SonicKey = class extends SonicFormElement {
|
|
|
53
68
|
#pressed = false;
|
|
54
69
|
attributeChangedCallback(name) {
|
|
55
70
|
if (name === "pressed") this.#isDirty = false;
|
|
56
|
-
if (
|
|
71
|
+
if (this.isDisabled() || this.#isSoftDisabled()) this.#release();
|
|
57
72
|
this.render();
|
|
58
73
|
}
|
|
59
74
|
formResetCallback() {
|
|
@@ -65,7 +80,7 @@ var SonicKey = class extends SonicFormElement {
|
|
|
65
80
|
connect(signal) {
|
|
66
81
|
const key = this.#key;
|
|
67
82
|
const cap = requireChild(key, ".sonic-key-cap", HTMLSpanElement);
|
|
68
|
-
this.upgradeProperties("defaultPressed", "momentary", "toggle", "pressed", "value");
|
|
83
|
+
this.upgradeProperties("busy", "defaultPressed", "momentary", "softDisabled", "toggle", "pressed", "value");
|
|
69
84
|
this.mirrorChildren({
|
|
70
85
|
control: key,
|
|
71
86
|
copy: (originals) => {
|
|
@@ -77,7 +92,12 @@ var SonicKey = class extends SonicFormElement {
|
|
|
77
92
|
signal.addEventListener("abort", () => {
|
|
78
93
|
this.#release();
|
|
79
94
|
}, { once: true });
|
|
80
|
-
key.addEventListener("click", () => {
|
|
95
|
+
key.addEventListener("click", (event) => {
|
|
96
|
+
if (this.#isSoftDisabled()) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
event.stopImmediatePropagation();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
81
101
|
if (!this.toggle) return;
|
|
82
102
|
this.pressed = !this.pressed;
|
|
83
103
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
@@ -90,6 +110,8 @@ var SonicKey = class extends SonicFormElement {
|
|
|
90
110
|
render() {
|
|
91
111
|
const key = this.#key;
|
|
92
112
|
key.disabled = this.isDisabled();
|
|
113
|
+
writeAttribute(key, "aria-disabled", this.#isSoftDisabled() ? "true" : void 0);
|
|
114
|
+
writeAttribute(key, "aria-busy", this.busy ? "true" : void 0);
|
|
93
115
|
key.removeAttribute("aria-pressed");
|
|
94
116
|
if (this.toggle) key.setAttribute("aria-pressed", String(this.pressed));
|
|
95
117
|
this.toggleState("pressed", this.pressed);
|
|
@@ -124,7 +146,7 @@ var SonicKey = class extends SonicFormElement {
|
|
|
124
146
|
}, { signal });
|
|
125
147
|
}
|
|
126
148
|
#hold(holder) {
|
|
127
|
-
if (this.isDisabled() || this.#holder !== void 0) return;
|
|
149
|
+
if (this.isDisabled() || this.#isSoftDisabled() || this.#holder !== void 0) return;
|
|
128
150
|
this.#holder = holder;
|
|
129
151
|
this.pressed = true;
|
|
130
152
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
@@ -132,6 +154,9 @@ var SonicKey = class extends SonicFormElement {
|
|
|
132
154
|
#isMomentary() {
|
|
133
155
|
return this.momentary && !this.toggle;
|
|
134
156
|
}
|
|
157
|
+
#isSoftDisabled() {
|
|
158
|
+
return this.softDisabled && !this.isDisabled();
|
|
159
|
+
}
|
|
135
160
|
#release() {
|
|
136
161
|
if (this.#holder === void 0) return;
|
|
137
162
|
this.#holder = void 0;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { template } from "../lib/render.js";
|
|
2
2
|
import { SonicElement } from "./sonic-element.js";
|
|
3
|
+
import { parseNumberList } from "../lib/number-list.js";
|
|
4
|
+
import { linearTaper } from "../lib/taper.js";
|
|
3
5
|
|
|
4
6
|
//#region src/elements/meter.ts
|
|
5
7
|
const renderMeter = template(`
|
|
@@ -14,20 +16,30 @@ const holdMs = 1500;
|
|
|
14
16
|
function toDecibels(amplitude) {
|
|
15
17
|
return 20 * Math.log10(Math.abs(amplitude));
|
|
16
18
|
}
|
|
19
|
+
function lightsBelow(lights, zone) {
|
|
20
|
+
return `calc(${lights.map((threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`).join(" + ")})`;
|
|
21
|
+
}
|
|
17
22
|
var SonicMeter = class extends SonicElement {
|
|
18
23
|
static observedAttributes = [
|
|
19
24
|
"disabled",
|
|
25
|
+
"lights",
|
|
20
26
|
"max",
|
|
21
|
-
"min"
|
|
27
|
+
"min",
|
|
28
|
+
"origin",
|
|
29
|
+
"scale"
|
|
22
30
|
];
|
|
23
31
|
get level() {
|
|
24
32
|
return this.#level;
|
|
25
33
|
}
|
|
26
34
|
set level(amplitude) {
|
|
27
35
|
if (!Number.isFinite(amplitude)) return;
|
|
28
|
-
const decibels = toDecibels(amplitude);
|
|
29
36
|
const now = performance.now();
|
|
30
37
|
this.#level = amplitude;
|
|
38
|
+
if (this.scale === "linear") {
|
|
39
|
+
this.#render(now);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const decibels = toDecibels(amplitude);
|
|
31
43
|
this.#target = decibels;
|
|
32
44
|
this.#bar = Math.max(this.#bar, decibels);
|
|
33
45
|
if (decibels >= this.#peak) {
|
|
@@ -38,6 +50,12 @@ var SonicMeter = class extends SonicElement {
|
|
|
38
50
|
this.#render(now);
|
|
39
51
|
this.#schedule(now);
|
|
40
52
|
}
|
|
53
|
+
get lights() {
|
|
54
|
+
return parseNumberList(this.getAttribute("lights"));
|
|
55
|
+
}
|
|
56
|
+
set lights(thresholds) {
|
|
57
|
+
this.reflect("lights", thresholds?.join(" "));
|
|
58
|
+
}
|
|
41
59
|
get max() {
|
|
42
60
|
return this.numberAttribute("max", 0);
|
|
43
61
|
}
|
|
@@ -56,24 +74,39 @@ var SonicMeter = class extends SonicElement {
|
|
|
56
74
|
set orientation(direction) {
|
|
57
75
|
this.reflect("orientation", direction);
|
|
58
76
|
}
|
|
77
|
+
get origin() {
|
|
78
|
+
return this.optionalNumberAttribute("origin");
|
|
79
|
+
}
|
|
80
|
+
set origin(value) {
|
|
81
|
+
this.reflect("origin", value);
|
|
82
|
+
}
|
|
83
|
+
get scale() {
|
|
84
|
+
return this.getAttribute("scale") === "linear" ? "linear" : "db";
|
|
85
|
+
}
|
|
86
|
+
set scale(scale) {
|
|
87
|
+
this.reflect("scale", scale);
|
|
88
|
+
}
|
|
59
89
|
#bar = -Infinity;
|
|
60
90
|
#clipAt = -Infinity;
|
|
61
91
|
#frame;
|
|
62
92
|
#lastFrame;
|
|
63
93
|
#level = 0;
|
|
94
|
+
#lights;
|
|
64
95
|
#meter = renderMeter();
|
|
65
96
|
#peak = -Infinity;
|
|
66
97
|
#peakAt = -Infinity;
|
|
67
98
|
#target = -Infinity;
|
|
68
99
|
attributeChangedCallback() {
|
|
69
100
|
const now = performance.now();
|
|
101
|
+
this.#renderLights();
|
|
70
102
|
this.#render(now);
|
|
71
103
|
this.#schedule(now);
|
|
72
104
|
}
|
|
73
105
|
connect(signal) {
|
|
74
106
|
const now = performance.now();
|
|
75
|
-
this.upgradeProperties("max", "min", "orientation", "level");
|
|
107
|
+
this.upgradeProperties("lights", "max", "min", "orientation", "origin", "scale", "level");
|
|
76
108
|
this.appendOnce(this.#meter);
|
|
109
|
+
this.#renderLights();
|
|
77
110
|
this.#render(now);
|
|
78
111
|
this.#schedule(now);
|
|
79
112
|
this.checkStyles(this.#meter, "meter.css");
|
|
@@ -83,21 +116,25 @@ var SonicMeter = class extends SonicElement {
|
|
|
83
116
|
this.#lastFrame = void 0;
|
|
84
117
|
}, { once: true });
|
|
85
118
|
}
|
|
86
|
-
#fraction(
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
return
|
|
119
|
+
#fraction(level) {
|
|
120
|
+
const lights = this.#lights;
|
|
121
|
+
if (lights) return lights.filter((threshold) => threshold <= level).length / lights.length;
|
|
122
|
+
return linearTaper(this.min, this.max).position(level);
|
|
90
123
|
}
|
|
91
124
|
#isSettled(now) {
|
|
125
|
+
if (this.scale === "linear") return true;
|
|
92
126
|
const floor = this.min;
|
|
93
127
|
return this.#bar <= Math.max(this.#target, floor) && this.#peak <= Math.max(this.#bar, floor) && now - this.#clipAt >= holdMs;
|
|
94
128
|
}
|
|
95
129
|
#render(now) {
|
|
96
130
|
const meter = this.#meter;
|
|
131
|
+
const origin = this.origin === void 0 ? 0 : this.#fraction(this.origin);
|
|
97
132
|
meter.style.setProperty("--_sonic-meter-min", String(this.min));
|
|
98
133
|
meter.style.setProperty("--_sonic-meter-max", String(this.max));
|
|
99
|
-
|
|
100
|
-
|
|
134
|
+
meter.style.setProperty("--_sonic-meter-origin", String(origin));
|
|
135
|
+
if (this.disabled || this.scale === "linear") {
|
|
136
|
+
const level = this.disabled ? origin : this.#fraction(this.#level);
|
|
137
|
+
meter.style.setProperty("--_sonic-meter-level", String(level));
|
|
101
138
|
meter.style.setProperty("--_sonic-meter-peak", "0");
|
|
102
139
|
meter.style.setProperty("--_sonic-meter-clipped", "0");
|
|
103
140
|
return;
|
|
@@ -106,6 +143,23 @@ var SonicMeter = class extends SonicElement {
|
|
|
106
143
|
meter.style.setProperty("--_sonic-meter-peak", String(this.#fraction(this.#peak)));
|
|
107
144
|
meter.style.setProperty("--_sonic-meter-clipped", now - this.#clipAt < holdMs ? "1" : "0");
|
|
108
145
|
}
|
|
146
|
+
#renderLights() {
|
|
147
|
+
const meter = this.#meter;
|
|
148
|
+
const lights = this.lights;
|
|
149
|
+
this.#lights = lights;
|
|
150
|
+
this.toggleState("ladder", lights !== void 0);
|
|
151
|
+
if (!lights) {
|
|
152
|
+
for (const name of [
|
|
153
|
+
"count",
|
|
154
|
+
"hot-lights",
|
|
155
|
+
"clip-lights"
|
|
156
|
+
]) meter.style.removeProperty(`--_sonic-meter-${name}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
meter.style.setProperty("--_sonic-meter-count", String(lights.length));
|
|
160
|
+
meter.style.setProperty("--_sonic-meter-hot-lights", lightsBelow(lights, "hot"));
|
|
161
|
+
meter.style.setProperty("--_sonic-meter-clip-lights", lightsBelow(lights, "clip"));
|
|
162
|
+
}
|
|
109
163
|
#schedule(now) {
|
|
110
164
|
if (this.#frame !== void 0 || !this.isConnected || this.#isSettled(now)) return;
|
|
111
165
|
if (this.#lastFrame === void 0) this.#lastFrame = now;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
|
-
import { SonicRangeElement } from "./range-element.js";
|
|
3
2
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
3
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
4
4
|
|
|
5
5
|
//#region src/elements/number.ts
|
|
6
6
|
const renderNumber = template(`
|
|
@@ -2,9 +2,15 @@ import { requireChild } from "../lib/render.js";
|
|
|
2
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
4
|
import { RangeEntry } from "./range-entry.js";
|
|
5
|
-
import {
|
|
5
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
6
|
+
import { passDetent } from "../lib/detent.js";
|
|
7
|
+
import { clamp, clampUnit, trimFloat, wrapUnit } from "../lib/math.js";
|
|
8
|
+
import { nearestEntry, parseNumberList } from "../lib/number-list.js";
|
|
9
|
+
import { readPxProperty } from "../lib/read-px-property.js";
|
|
10
|
+
import { linearTaper, listTaper, logTaper, skewTaper } from "../lib/taper.js";
|
|
6
11
|
|
|
7
12
|
//#region src/elements/range-element.ts
|
|
13
|
+
const detentZonePx = 8;
|
|
8
14
|
const fineScale = .1;
|
|
9
15
|
const keyRevealMs = 1e3;
|
|
10
16
|
const pageSteps = 10;
|
|
@@ -13,6 +19,7 @@ const revealPx = 4;
|
|
|
13
19
|
const canPopover = "togglePopover" in HTMLElement.prototype;
|
|
14
20
|
let instanceCount = 0;
|
|
15
21
|
const resetKeys = /* @__PURE__ */ new Set(["Backspace", "Delete"]);
|
|
22
|
+
const endKeys = /* @__PURE__ */ new Set(["End", "Home"]);
|
|
16
23
|
const keySteps = /* @__PURE__ */ new Map([
|
|
17
24
|
["ArrowDown", -1],
|
|
18
25
|
["ArrowLeft", -1],
|
|
@@ -31,9 +38,22 @@ const roleAttributes = [
|
|
|
31
38
|
"role",
|
|
32
39
|
"tabindex"
|
|
33
40
|
];
|
|
41
|
+
function scaleValue(mark) {
|
|
42
|
+
const text = mark.dataset.sonicValue?.trim();
|
|
43
|
+
return text ? Number(text) : NaN;
|
|
44
|
+
}
|
|
45
|
+
function scaleMark(original) {
|
|
46
|
+
const mark = copyNode(original);
|
|
47
|
+
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) return [];
|
|
48
|
+
if (!Number.isFinite(scaleValue(mark))) return [];
|
|
49
|
+
const isTick = mark.childElementCount === 0 && mark.textContent.trim() === "";
|
|
50
|
+
mark.classList.add(isTick ? "sonic-scale-tick" : "sonic-scale-label");
|
|
51
|
+
return [mark];
|
|
52
|
+
}
|
|
34
53
|
var SonicRangeElement = class extends SonicFormElement {
|
|
35
54
|
static observedAttributes = [
|
|
36
55
|
...SonicFormElement.observedAttributes,
|
|
56
|
+
"detent",
|
|
37
57
|
"max",
|
|
38
58
|
"midpoint",
|
|
39
59
|
"min",
|
|
@@ -42,7 +62,8 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
42
62
|
"readout",
|
|
43
63
|
"step",
|
|
44
64
|
"taper",
|
|
45
|
-
"value"
|
|
65
|
+
"value",
|
|
66
|
+
"values"
|
|
46
67
|
];
|
|
47
68
|
get default() {
|
|
48
69
|
return this.optionalNumberAttribute("default");
|
|
@@ -50,6 +71,12 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
50
71
|
set default(value) {
|
|
51
72
|
this.reflect("default", value);
|
|
52
73
|
}
|
|
74
|
+
get detent() {
|
|
75
|
+
return this.optionalNumberAttribute("detent");
|
|
76
|
+
}
|
|
77
|
+
set detent(value) {
|
|
78
|
+
this.reflect("detent", value);
|
|
79
|
+
}
|
|
53
80
|
get dimmed() {
|
|
54
81
|
return this.hasAttribute("dimmed");
|
|
55
82
|
}
|
|
@@ -130,7 +157,14 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
130
157
|
if (this.#drag) return;
|
|
131
158
|
this.#write(next);
|
|
132
159
|
}
|
|
160
|
+
get values() {
|
|
161
|
+
return parseNumberList(this.getAttribute("values"));
|
|
162
|
+
}
|
|
163
|
+
set values(entries) {
|
|
164
|
+
this.reflect("values", entries?.join(" "));
|
|
165
|
+
}
|
|
133
166
|
#drag;
|
|
167
|
+
#entries;
|
|
134
168
|
#entry;
|
|
135
169
|
#formatValue;
|
|
136
170
|
#instance = String(instanceCount += 1);
|
|
@@ -138,8 +172,10 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
138
172
|
#keyRevealTimer;
|
|
139
173
|
#parseValue;
|
|
140
174
|
#readout;
|
|
175
|
+
#scale;
|
|
141
176
|
#value = 0;
|
|
142
177
|
attributeChangedCallback(name) {
|
|
178
|
+
this.#entries = parseNumberList(this.getAttribute("values"));
|
|
143
179
|
if (name === "value") {
|
|
144
180
|
if (this.#drag) return;
|
|
145
181
|
this.#isDirty = false;
|
|
@@ -152,7 +188,7 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
152
188
|
this.render();
|
|
153
189
|
}
|
|
154
190
|
connectedCallback() {
|
|
155
|
-
this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "step", "taper", "formatValue", "parseValue", "value");
|
|
191
|
+
this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "step", "taper", "values", "detent", "formatValue", "parseValue", "value");
|
|
156
192
|
super.connectedCallback();
|
|
157
193
|
}
|
|
158
194
|
bindGestures(control, signal, grab) {
|
|
@@ -170,6 +206,7 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
170
206
|
if (!axis) return;
|
|
171
207
|
const startPosition = axis.position(event);
|
|
172
208
|
const drag = {
|
|
209
|
+
detent: this.#detentHold(control, axis.travelPx),
|
|
173
210
|
fromValue,
|
|
174
211
|
isRevealed: false,
|
|
175
212
|
lastPosition: startPosition,
|
|
@@ -192,11 +229,10 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
192
229
|
if (event.pointerId !== drag?.pointerId) return;
|
|
193
230
|
const position = drag.position(event);
|
|
194
231
|
const scale = event.shiftKey ? fineScale : 1;
|
|
195
|
-
const
|
|
196
|
-
drag.rawFraction = this.wraps() ? wrapUnit(fraction) : clampUnit(fraction);
|
|
232
|
+
const next = this.#move(drag, (position - drag.lastPosition) / drag.travelPx * scale);
|
|
197
233
|
drag.lastPosition = position;
|
|
198
234
|
if (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);
|
|
199
|
-
this.input(
|
|
235
|
+
this.input(next);
|
|
200
236
|
}, { signal });
|
|
201
237
|
const endDrag = (event) => {
|
|
202
238
|
if (event.pointerId === this.#drag?.pointerId) this.#endDrag();
|
|
@@ -209,7 +245,7 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
209
245
|
control.addEventListener("pointerup", (event) => {
|
|
210
246
|
if (event.pointerId !== this.#drag?.pointerId) return;
|
|
211
247
|
this.#endDrag();
|
|
212
|
-
if (!entry.press(event)) return;
|
|
248
|
+
if (!entry.press(event) || this.springTarget() !== void 0) return;
|
|
213
249
|
if (this.doublePress === "reset") this.#reset();
|
|
214
250
|
else entry.open();
|
|
215
251
|
}, { signal });
|
|
@@ -219,13 +255,24 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
219
255
|
this.#hideForKeys();
|
|
220
256
|
}, { once: true });
|
|
221
257
|
}
|
|
258
|
+
bindScale(control, scale, signal) {
|
|
259
|
+
this.#scale = scale;
|
|
260
|
+
this.mirrorChildren({
|
|
261
|
+
control,
|
|
262
|
+
copy: (originals) => {
|
|
263
|
+
scale.replaceChildren(...originals.flatMap((original) => scaleMark(original)));
|
|
264
|
+
this.renderScale();
|
|
265
|
+
},
|
|
266
|
+
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]")
|
|
267
|
+
}, signal);
|
|
268
|
+
}
|
|
222
269
|
clampRange(next) {
|
|
223
270
|
const min = this.min;
|
|
224
|
-
if (!this.wraps()) return
|
|
271
|
+
if (!this.wraps()) return clamp(next, min, this.#max());
|
|
225
272
|
const range = this.range();
|
|
226
273
|
if (range <= 0) return min;
|
|
227
|
-
const offset =
|
|
228
|
-
return offset >= range ? min :
|
|
274
|
+
const offset = trimFloat(((next - min) % range + range) % range);
|
|
275
|
+
return offset >= range ? min : trimFloat(min + offset);
|
|
229
276
|
}
|
|
230
277
|
controlRole() {
|
|
231
278
|
return "slider";
|
|
@@ -249,10 +296,10 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
249
296
|
return [Math.min(value, modulated), Math.max(value, modulated)];
|
|
250
297
|
}
|
|
251
298
|
originFraction() {
|
|
252
|
-
return this.fraction(this.
|
|
299
|
+
return this.fraction(this.restValue());
|
|
253
300
|
}
|
|
254
301
|
positions() {
|
|
255
|
-
const positions = Math.round(this.range() / this.step) + (this.wraps() ? 0 : 1);
|
|
302
|
+
const positions = this.#entries?.length ?? Math.round(this.range() / this.step) + (this.wraps() ? 0 : 1);
|
|
256
303
|
if (!this.notched || !Number.isFinite(positions) || positions < 2) return;
|
|
257
304
|
return positions;
|
|
258
305
|
}
|
|
@@ -267,9 +314,10 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
267
314
|
this.#renderReadout();
|
|
268
315
|
return;
|
|
269
316
|
}
|
|
317
|
+
const [low, high] = this.#bounds();
|
|
270
318
|
control.setAttribute("role", this.controlRole());
|
|
271
|
-
control.setAttribute("aria-valuemin", String(
|
|
272
|
-
control.setAttribute("aria-valuemax", String(
|
|
319
|
+
control.setAttribute("aria-valuemin", String(low));
|
|
320
|
+
control.setAttribute("aria-valuemax", String(high));
|
|
273
321
|
control.setAttribute("aria-valuenow", String(this.#value));
|
|
274
322
|
if (orientation) control.setAttribute("aria-orientation", orientation);
|
|
275
323
|
writeAttribute(control, "aria-valuetext", this.#formatValue?.(this.#value));
|
|
@@ -277,9 +325,21 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
277
325
|
this.#renderDisabled(control);
|
|
278
326
|
this.#renderReadout();
|
|
279
327
|
}
|
|
328
|
+
renderScale() {
|
|
329
|
+
const marks = this.#scale?.children ?? [];
|
|
330
|
+
for (const mark of marks) {
|
|
331
|
+
if (!(mark instanceof HTMLElement || mark instanceof SVGElement)) continue;
|
|
332
|
+
const at = String(this.fraction(this.clampRange(scaleValue(mark))));
|
|
333
|
+
if (mark.style.getPropertyValue("--_sonic-scale-at") !== at) mark.style.setProperty("--_sonic-scale-at", at);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
280
336
|
restoreState(state) {
|
|
281
337
|
this.value = Number(state);
|
|
282
338
|
}
|
|
339
|
+
restValue() {
|
|
340
|
+
return this.origin ?? this.#bounds()[0];
|
|
341
|
+
}
|
|
342
|
+
springTarget() {}
|
|
283
343
|
valueAt(fraction) {
|
|
284
344
|
return (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);
|
|
285
345
|
}
|
|
@@ -321,6 +381,7 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
321
381
|
control.addEventListener("keydown", (event) => {
|
|
322
382
|
if (this.isDisabled() || event.defaultPrevented || event.target !== control) return;
|
|
323
383
|
if (event.key === "Enter") {
|
|
384
|
+
if (this.springTarget() !== void 0) return;
|
|
324
385
|
event.preventDefault();
|
|
325
386
|
entry.open();
|
|
326
387
|
return;
|
|
@@ -331,21 +392,43 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
331
392
|
this.#commit(next);
|
|
332
393
|
this.#revealForKeys();
|
|
333
394
|
}, { signal });
|
|
395
|
+
control.addEventListener("keyup", (event) => {
|
|
396
|
+
if (keySteps.has(event.key) || endKeys.has(event.key)) this.#springBack();
|
|
397
|
+
}, { signal });
|
|
334
398
|
control.addEventListener("blur", () => {
|
|
335
399
|
this.#hideForKeys();
|
|
400
|
+
this.#springBack();
|
|
336
401
|
}, { signal });
|
|
337
402
|
}
|
|
403
|
+
#bounds() {
|
|
404
|
+
const entries = this.#entries;
|
|
405
|
+
return [entries?.[0] ?? this.min, entries?.at(-1) ?? this.#max()];
|
|
406
|
+
}
|
|
338
407
|
#clamp(next) {
|
|
408
|
+
const entries = this.#entries;
|
|
409
|
+
if (entries) return nearestEntry(entries, next);
|
|
339
410
|
const min = this.min;
|
|
340
411
|
const step = this.step;
|
|
341
412
|
const stepped = step > 0 ? min + Math.round((next - min) / step) * step : next;
|
|
342
|
-
return this.clampRange(
|
|
413
|
+
return this.clampRange(trimFloat(stepped));
|
|
343
414
|
}
|
|
344
415
|
#commit(next) {
|
|
345
416
|
if (this.input(next)) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
346
417
|
}
|
|
418
|
+
#detentHold(control, travelPx) {
|
|
419
|
+
const detent = this.detent;
|
|
420
|
+
if (detent === void 0) return void 0;
|
|
421
|
+
const value = this.#clamp(detent);
|
|
422
|
+
const zonePx = readPxProperty(getComputedStyle(control), "--_sonic-detent-zone", detentZonePx);
|
|
423
|
+
return {
|
|
424
|
+
place: this.fraction(value),
|
|
425
|
+
slack: value === this.#value ? 0 : void 0,
|
|
426
|
+
value,
|
|
427
|
+
zone: zonePx / travelPx
|
|
428
|
+
};
|
|
429
|
+
}
|
|
347
430
|
#end(step) {
|
|
348
|
-
if (!this.wraps()) return this.#
|
|
431
|
+
if (!this.wraps()) return this.#bounds()[1];
|
|
349
432
|
return this.min + (Math.ceil(this.range() / step) - 1) * step;
|
|
350
433
|
}
|
|
351
434
|
#endDrag() {
|
|
@@ -356,6 +439,7 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
356
439
|
this.toggleState("dragging", false);
|
|
357
440
|
this.#renderReadout();
|
|
358
441
|
if (this.#value !== drag.fromValue) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
442
|
+
this.#springBack();
|
|
359
443
|
}
|
|
360
444
|
#hideForKeys() {
|
|
361
445
|
clearTimeout(this.#keyRevealTimer);
|
|
@@ -367,20 +451,29 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
367
451
|
return (this.#drag?.isRevealed === true || this.#keyRevealTimer !== void 0) && this.readout;
|
|
368
452
|
}
|
|
369
453
|
#keyTarget(key) {
|
|
370
|
-
if (key === "Home") return this
|
|
454
|
+
if (key === "Home") return this.#bounds()[0];
|
|
371
455
|
const step = this.step > 0 ? this.step : this.range() / 100;
|
|
372
456
|
if (key === "End") return this.#end(step);
|
|
373
457
|
if (resetKeys.has(key)) return this.default;
|
|
374
458
|
const steps = keySteps.get(key);
|
|
375
459
|
if (steps === void 0) return void 0;
|
|
376
|
-
|
|
377
|
-
if (!taper) return this.#value + steps * step;
|
|
378
|
-
const next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));
|
|
379
|
-
return next === this.#value ? this.#value + Math.sign(steps) * step : next;
|
|
460
|
+
return this.#stopAtDetent(this.#stepBy(steps, step));
|
|
380
461
|
}
|
|
381
462
|
#max() {
|
|
382
463
|
return Math.max(this.min, this.max);
|
|
383
464
|
}
|
|
465
|
+
#move(drag, delta) {
|
|
466
|
+
const { detent } = drag;
|
|
467
|
+
const to = drag.rawFraction + delta;
|
|
468
|
+
const next = detent ? passDetent(detent, drag.rawFraction, to) : to;
|
|
469
|
+
if (detent && next === void 0) {
|
|
470
|
+
drag.rawFraction = detent.place;
|
|
471
|
+
return detent.value;
|
|
472
|
+
}
|
|
473
|
+
const place = next ?? to;
|
|
474
|
+
drag.rawFraction = this.wraps() ? wrapUnit(place) : clampUnit(place);
|
|
475
|
+
return this.valueAt(drag.rawFraction);
|
|
476
|
+
}
|
|
384
477
|
#renderDisabled(control) {
|
|
385
478
|
if (this.isDisabled()) {
|
|
386
479
|
control.setAttribute("aria-disabled", "true");
|
|
@@ -413,7 +506,27 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
413
506
|
}, keyRevealMs);
|
|
414
507
|
this.#renderReadout();
|
|
415
508
|
}
|
|
509
|
+
#springBack() {
|
|
510
|
+
const target = this.springTarget();
|
|
511
|
+
if (target !== void 0) this.#commit(target);
|
|
512
|
+
}
|
|
513
|
+
#stepBy(steps, step) {
|
|
514
|
+
const entries = this.#entries;
|
|
515
|
+
if (entries) return entries[clamp(entries.indexOf(this.#value) + steps, 0, entries.length - 1)] ?? this.#value;
|
|
516
|
+
const taper = this.#taper();
|
|
517
|
+
if (!taper) return this.#value + steps * step;
|
|
518
|
+
const next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));
|
|
519
|
+
return next === this.#value ? this.#value + Math.sign(steps) * step : next;
|
|
520
|
+
}
|
|
521
|
+
#stopAtDetent(next) {
|
|
522
|
+
const detent = this.detent;
|
|
523
|
+
if (detent === void 0) return next;
|
|
524
|
+
const value = this.#clamp(detent);
|
|
525
|
+
return (this.#value - value) * (next - value) < 0 ? value : next;
|
|
526
|
+
}
|
|
416
527
|
#taper() {
|
|
528
|
+
const entries = this.#entries;
|
|
529
|
+
if (entries) return listTaper(entries);
|
|
417
530
|
if (this.notched || this.wraps()) return void 0;
|
|
418
531
|
const min = this.min;
|
|
419
532
|
const max = this.#max();
|