@xsynaptic/sonic-ui 0.1.0 → 0.3.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/README.md +8 -1
- package/dist/default/define/dial.js +7 -0
- package/dist/default/define/key.js +7 -0
- package/dist/default/define/meter.js +7 -0
- package/dist/default/define/number.js +7 -0
- package/dist/default/define/segmented.js +7 -0
- package/dist/default/define/slider.js +7 -0
- package/dist/{define.js → default/define.js} +1 -0
- package/dist/default/elements/define-once.js +7 -0
- package/dist/default/elements/dial.js +81 -0
- package/dist/default/elements/form-element.js +56 -0
- package/dist/default/elements/key.js +144 -0
- package/dist/{elements → default/elements}/meter.js +27 -16
- package/dist/default/elements/number.js +46 -0
- package/dist/default/elements/range-element.js +433 -0
- package/dist/{elements → default/elements}/range-entry.js +5 -4
- package/dist/default/elements/segmented.js +173 -0
- package/dist/default/elements/slider.js +99 -0
- package/dist/default/elements/sonic-element.js +153 -0
- package/dist/{index.js → default/index.js} +3 -1
- package/dist/default/lib/copy-node.js +30 -0
- package/dist/default/lib/percent.js +12 -0
- package/dist/default/lib/read-px-property.js +8 -0
- package/dist/{lib → default/lib}/render.js +9 -2
- package/dist/{lib → default/lib}/taper.js +4 -2
- package/dist/default/lib/write-attribute.js +11 -0
- package/dist/dev/define/dial.js.map +1 -0
- package/dist/dev/define/key.js.map +1 -0
- package/dist/dev/define/meter.js.map +1 -0
- package/dist/dev/define/number.d.ts +2 -0
- package/dist/dev/define/number.js +8 -0
- package/dist/dev/define/number.js.map +1 -0
- package/dist/dev/define/segmented.js.map +1 -0
- package/dist/dev/define/slider.js.map +1 -0
- package/dist/{define.d.ts → dev/define.d.ts} +3 -1
- package/dist/dev/define.js +6 -0
- package/dist/dev/elements/define-once.js.map +1 -0
- package/dist/{elements → dev/elements}/dial.d.ts +7 -0
- package/dist/{elements → dev/elements}/dial.js +31 -6
- package/dist/dev/elements/dial.js.map +1 -0
- package/dist/dev/elements/form-element.d.ts +22 -0
- package/dist/dev/elements/form-element.js +57 -0
- package/dist/dev/elements/form-element.js.map +1 -0
- package/dist/dev/elements/key.d.ts +30 -0
- package/dist/{elements → dev/elements}/key.js +71 -25
- package/dist/dev/elements/key.js.map +1 -0
- package/dist/{elements → dev/elements}/meter.d.ts +6 -0
- package/dist/dev/elements/meter.js +131 -0
- package/dist/dev/elements/meter.js.map +1 -0
- package/dist/dev/elements/number.d.ts +16 -0
- package/dist/dev/elements/number.js +47 -0
- package/dist/dev/elements/number.js.map +1 -0
- package/dist/dev/elements/range-element.d.ts +56 -0
- package/dist/{elements → dev/elements}/range-element.js +153 -67
- package/dist/dev/elements/range-element.js.map +1 -0
- package/dist/dev/elements/range-entry.js +75 -0
- package/dist/dev/elements/range-entry.js.map +1 -0
- package/dist/{elements → dev/elements}/segmented.d.ts +5 -2
- package/dist/dev/elements/segmented.js +174 -0
- package/dist/dev/elements/segmented.js.map +1 -0
- package/dist/{elements → dev/elements}/slider.d.ts +8 -0
- package/dist/{elements → dev/elements}/slider.js +37 -7
- package/dist/dev/elements/slider.js.map +1 -0
- package/dist/dev/elements/sonic-element.d.ts +33 -0
- package/dist/dev/elements/sonic-element.js +169 -0
- package/dist/dev/elements/sonic-element.js.map +1 -0
- package/dist/{index.d.ts → dev/index.d.ts} +3 -1
- package/dist/dev/index.js +9 -0
- package/dist/dev/lib/copy-node.js +31 -0
- package/dist/dev/lib/copy-node.js.map +1 -0
- package/dist/dev/lib/percent.d.ts +5 -0
- package/dist/dev/lib/percent.js +13 -0
- package/dist/dev/lib/percent.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -0
- package/dist/dev/lib/render.js +27 -0
- package/dist/dev/lib/render.js.map +1 -0
- package/dist/dev/lib/taper.js +36 -0
- package/dist/dev/lib/taper.js.map +1 -0
- package/dist/dev/lib/write-attribute.js.map +1 -0
- package/dist/skins/ivory.css +3 -3
- package/dist/skins/slate.css +4 -4
- package/dist/styles/controls.css +2 -1
- package/dist/styles/dial.css +70 -16
- package/dist/styles/key.css +71 -11
- package/dist/styles/led.css +75 -0
- package/dist/styles/material.css +154 -25
- package/dist/styles/meter.css +44 -8
- package/dist/styles/number.css +81 -0
- package/dist/styles/screen.css +6 -33
- package/dist/styles/segmented.css +45 -7
- package/dist/styles/slider.css +68 -4
- package/package.json +16 -13
- package/dist/define/dial.js.map +0 -1
- package/dist/define/key.js.map +0 -1
- package/dist/define/meter.js.map +0 -1
- package/dist/define/segmented.js.map +0 -1
- package/dist/define/slider.js.map +0 -1
- package/dist/elements/define-once.js.map +0 -1
- package/dist/elements/dial.js.map +0 -1
- package/dist/elements/key.d.ts +0 -17
- package/dist/elements/key.js.map +0 -1
- package/dist/elements/meter.js.map +0 -1
- package/dist/elements/range-element.d.ts +0 -32
- package/dist/elements/range-element.js.map +0 -1
- package/dist/elements/range-entry.js.map +0 -1
- package/dist/elements/segmented.js +0 -104
- package/dist/elements/segmented.js.map +0 -1
- package/dist/elements/slider.js.map +0 -1
- package/dist/elements/sonic-element.d.ts +0 -20
- package/dist/elements/sonic-element.js +0 -94
- package/dist/elements/sonic-element.js.map +0 -1
- package/dist/lib/read-px-property.js.map +0 -1
- package/dist/lib/render.js.map +0 -1
- package/dist/lib/taper.js.map +0 -1
- package/dist/lib/write-attribute.js.map +0 -1
- /package/dist/{define → dev/define}/dial.d.ts +0 -0
- /package/dist/{define → dev/define}/dial.js +0 -0
- /package/dist/{define → dev/define}/key.d.ts +0 -0
- /package/dist/{define → dev/define}/key.js +0 -0
- /package/dist/{define → dev/define}/meter.d.ts +0 -0
- /package/dist/{define → dev/define}/meter.js +0 -0
- /package/dist/{define → dev/define}/segmented.d.ts +0 -0
- /package/dist/{define → dev/define}/segmented.js +0 -0
- /package/dist/{define → dev/define}/slider.d.ts +0 -0
- /package/dist/{define → dev/define}/slider.js +0 -0
- /package/dist/{elements → dev/elements}/define-once.js +0 -0
- /package/dist/{lib → dev/lib}/read-px-property.js +0 -0
- /package/dist/{lib → dev/lib}/write-attribute.js +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { RangeEntry } from "./range-entry.js";
|
|
2
|
-
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
|
-
import { SonicElement } from "./sonic-element.js";
|
|
4
1
|
import { requireChild } from "../lib/render.js";
|
|
5
|
-
import {
|
|
2
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
|
+
import { SonicFormElement } from "./form-element.js";
|
|
4
|
+
import { RangeEntry } from "./range-entry.js";
|
|
5
|
+
import { clampUnit, linearTaper, logTaper, skewTaper, wrapUnit } from "../lib/taper.js";
|
|
6
6
|
|
|
7
7
|
//#region src/elements/range-element.ts
|
|
8
8
|
const fineScale = .1;
|
|
@@ -11,7 +11,7 @@ const pageSteps = 10;
|
|
|
11
11
|
const revealMs = 250;
|
|
12
12
|
const revealPx = 4;
|
|
13
13
|
const canPopover = "togglePopover" in HTMLElement.prototype;
|
|
14
|
-
let
|
|
14
|
+
let instanceCount = 0;
|
|
15
15
|
const resetKeys = /* @__PURE__ */ new Set(["Backspace", "Delete"]);
|
|
16
16
|
const keySteps = /* @__PURE__ */ new Map([
|
|
17
17
|
["ArrowDown", -1],
|
|
@@ -21,7 +21,7 @@ const keySteps = /* @__PURE__ */ new Map([
|
|
|
21
21
|
["PageDown", -10],
|
|
22
22
|
["PageUp", pageSteps]
|
|
23
23
|
]);
|
|
24
|
-
const
|
|
24
|
+
const roleAttributes = [
|
|
25
25
|
"aria-disabled",
|
|
26
26
|
"aria-orientation",
|
|
27
27
|
"aria-valuemax",
|
|
@@ -31,9 +31,9 @@ const sliderAttributes = [
|
|
|
31
31
|
"role",
|
|
32
32
|
"tabindex"
|
|
33
33
|
];
|
|
34
|
-
var SonicRangeElement = class extends
|
|
34
|
+
var SonicRangeElement = class extends SonicFormElement {
|
|
35
35
|
static observedAttributes = [
|
|
36
|
-
...
|
|
36
|
+
...SonicFormElement.observedAttributes,
|
|
37
37
|
"max",
|
|
38
38
|
"midpoint",
|
|
39
39
|
"min",
|
|
@@ -44,6 +44,24 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
44
44
|
"taper",
|
|
45
45
|
"value"
|
|
46
46
|
];
|
|
47
|
+
get default() {
|
|
48
|
+
return this.optionalNumberAttribute("default");
|
|
49
|
+
}
|
|
50
|
+
set default(value) {
|
|
51
|
+
this.reflect("default", value);
|
|
52
|
+
}
|
|
53
|
+
get dimmed() {
|
|
54
|
+
return this.hasAttribute("dimmed");
|
|
55
|
+
}
|
|
56
|
+
set dimmed(isDimmed) {
|
|
57
|
+
this.reflect("dimmed", isDimmed);
|
|
58
|
+
}
|
|
59
|
+
get doublePress() {
|
|
60
|
+
return this.getAttribute("double-press") === "reset" ? "reset" : "type";
|
|
61
|
+
}
|
|
62
|
+
set doublePress(gesture) {
|
|
63
|
+
this.reflect("double-press", gesture);
|
|
64
|
+
}
|
|
47
65
|
get formatValue() {
|
|
48
66
|
return this.#formatValue;
|
|
49
67
|
}
|
|
@@ -51,12 +69,60 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
51
69
|
this.#formatValue = format;
|
|
52
70
|
this.render();
|
|
53
71
|
}
|
|
72
|
+
get max() {
|
|
73
|
+
return this.numberAttribute("max", 100);
|
|
74
|
+
}
|
|
75
|
+
set max(value) {
|
|
76
|
+
this.reflect("max", value);
|
|
77
|
+
}
|
|
78
|
+
get midpoint() {
|
|
79
|
+
return this.optionalNumberAttribute("midpoint");
|
|
80
|
+
}
|
|
81
|
+
set midpoint(value) {
|
|
82
|
+
this.reflect("midpoint", value);
|
|
83
|
+
}
|
|
84
|
+
get min() {
|
|
85
|
+
return this.numberAttribute("min", 0);
|
|
86
|
+
}
|
|
87
|
+
set min(value) {
|
|
88
|
+
this.reflect("min", value);
|
|
89
|
+
}
|
|
90
|
+
get notched() {
|
|
91
|
+
return this.hasAttribute("notched");
|
|
92
|
+
}
|
|
93
|
+
set notched(isNotched) {
|
|
94
|
+
this.reflect("notched", isNotched);
|
|
95
|
+
}
|
|
96
|
+
get origin() {
|
|
97
|
+
return this.optionalNumberAttribute("origin");
|
|
98
|
+
}
|
|
99
|
+
set origin(value) {
|
|
100
|
+
this.reflect("origin", value);
|
|
101
|
+
}
|
|
54
102
|
get parseValue() {
|
|
55
103
|
return this.#parseValue;
|
|
56
104
|
}
|
|
57
105
|
set parseValue(parse) {
|
|
58
106
|
this.#parseValue = parse;
|
|
59
107
|
}
|
|
108
|
+
get readout() {
|
|
109
|
+
return this.hasAttribute("readout");
|
|
110
|
+
}
|
|
111
|
+
set readout(isShown) {
|
|
112
|
+
this.reflect("readout", isShown);
|
|
113
|
+
}
|
|
114
|
+
get step() {
|
|
115
|
+
return this.numberAttribute("step", 1);
|
|
116
|
+
}
|
|
117
|
+
set step(value) {
|
|
118
|
+
this.reflect("step", value);
|
|
119
|
+
}
|
|
120
|
+
get taper() {
|
|
121
|
+
return this.getAttribute("taper") === "log" ? "log" : "linear";
|
|
122
|
+
}
|
|
123
|
+
set taper(curve) {
|
|
124
|
+
this.reflect("taper", curve);
|
|
125
|
+
}
|
|
60
126
|
get value() {
|
|
61
127
|
return this.#value;
|
|
62
128
|
}
|
|
@@ -64,10 +130,10 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
64
130
|
if (this.#drag) return;
|
|
65
131
|
this.#write(next);
|
|
66
132
|
}
|
|
67
|
-
#anchor = `--sonic-readout-${String(anchorCount += 1)}`;
|
|
68
133
|
#drag;
|
|
69
134
|
#entry;
|
|
70
135
|
#formatValue;
|
|
136
|
+
#instance = String(instanceCount += 1);
|
|
71
137
|
#isDirty = false;
|
|
72
138
|
#keyRevealTimer;
|
|
73
139
|
#parseValue;
|
|
@@ -78,23 +144,22 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
78
144
|
if (this.#drag) return;
|
|
79
145
|
this.#isDirty = false;
|
|
80
146
|
}
|
|
81
|
-
if (name === "disabled" && this.
|
|
147
|
+
if (name === "disabled" && this.isDisabled()) {
|
|
82
148
|
this.#entry?.close(false);
|
|
83
149
|
this.#endDrag();
|
|
84
150
|
}
|
|
85
|
-
this.#value = this.#clamp(this.#isDirty ? this.#value : this.numberAttribute("value", this.min
|
|
151
|
+
this.#value = this.#clamp(this.#isDirty ? this.#value : this.numberAttribute("value", this.min));
|
|
86
152
|
this.render();
|
|
87
153
|
}
|
|
88
154
|
connectedCallback() {
|
|
89
|
-
this.
|
|
90
|
-
this.upgradeProperty("parseValue");
|
|
91
|
-
this.upgradeProperty("value");
|
|
155
|
+
this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "step", "taper", "formatValue", "parseValue", "value");
|
|
92
156
|
super.connectedCallback();
|
|
93
157
|
}
|
|
94
158
|
bindGestures(control, signal, grab) {
|
|
95
159
|
const entry = this.#bindEntry(control, signal);
|
|
96
160
|
control.addEventListener("pointerdown", (event) => {
|
|
97
|
-
if (event.button !== 0 || this.
|
|
161
|
+
if (event.button !== 0 || this.isDisabled() || entry.isOpen) return;
|
|
162
|
+
if (event.pointerType === "touch") event.preventDefault();
|
|
98
163
|
control.focus();
|
|
99
164
|
if (event.metaKey || event.ctrlKey) {
|
|
100
165
|
this.#reset();
|
|
@@ -127,7 +192,8 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
127
192
|
if (event.pointerId !== drag?.pointerId) return;
|
|
128
193
|
const position = drag.position(event);
|
|
129
194
|
const scale = event.shiftKey ? fineScale : 1;
|
|
130
|
-
|
|
195
|
+
const fraction = drag.rawFraction + (position - drag.lastPosition) / drag.travelPx * scale;
|
|
196
|
+
drag.rawFraction = this.wraps() ? wrapUnit(fraction) : clampUnit(fraction);
|
|
131
197
|
drag.lastPosition = position;
|
|
132
198
|
if (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);
|
|
133
199
|
this.input(this.valueAt(drag.rawFraction));
|
|
@@ -144,7 +210,7 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
144
210
|
if (event.pointerId !== this.#drag?.pointerId) return;
|
|
145
211
|
this.#endDrag();
|
|
146
212
|
if (!entry.press(event)) return;
|
|
147
|
-
if (this.
|
|
213
|
+
if (this.doublePress === "reset") this.#reset();
|
|
148
214
|
else entry.open();
|
|
149
215
|
}, { signal });
|
|
150
216
|
this.#bindKeys(control, entry, signal);
|
|
@@ -154,10 +220,18 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
154
220
|
}, { once: true });
|
|
155
221
|
}
|
|
156
222
|
clampRange(next) {
|
|
157
|
-
|
|
223
|
+
const min = this.min;
|
|
224
|
+
if (!this.wraps()) return Math.min(this.#max(), Math.max(min, next));
|
|
225
|
+
const range = this.range();
|
|
226
|
+
if (range <= 0) return min;
|
|
227
|
+
const offset = Number((((next - min) % range + range) % range).toPrecision(12));
|
|
228
|
+
return offset >= range ? min : Number((min + offset).toPrecision(12));
|
|
229
|
+
}
|
|
230
|
+
controlRole() {
|
|
231
|
+
return "slider";
|
|
158
232
|
}
|
|
159
233
|
fraction(value) {
|
|
160
|
-
return (this.#taper() ?? linearTaper(this.min
|
|
234
|
+
return (this.#taper() ?? linearTaper(this.min, this.#max())).position(value);
|
|
161
235
|
}
|
|
162
236
|
input(next) {
|
|
163
237
|
const previous = this.#value;
|
|
@@ -169,33 +243,33 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
169
243
|
}));
|
|
170
244
|
return true;
|
|
171
245
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return this.numberAttribute("min", 0);
|
|
246
|
+
modulationFractions(amount) {
|
|
247
|
+
const value = this.fraction(this.#value);
|
|
248
|
+
const modulated = this.fraction(this.#value + amount);
|
|
249
|
+
return [Math.min(value, modulated), Math.max(value, modulated)];
|
|
177
250
|
}
|
|
178
251
|
originFraction() {
|
|
179
|
-
return this.fraction(this.
|
|
252
|
+
return this.fraction(this.origin ?? this.min);
|
|
180
253
|
}
|
|
181
254
|
positions() {
|
|
182
|
-
const positions = Math.round(this.range() / this
|
|
183
|
-
if (!this.
|
|
255
|
+
const positions = Math.round(this.range() / this.step) + (this.wraps() ? 0 : 1);
|
|
256
|
+
if (!this.notched || !Number.isFinite(positions) || positions < 2) return;
|
|
184
257
|
return positions;
|
|
185
258
|
}
|
|
186
259
|
range() {
|
|
187
|
-
return this
|
|
260
|
+
return this.#max() - this.min;
|
|
188
261
|
}
|
|
189
262
|
renderAria(control, orientation) {
|
|
263
|
+
this.writeFormValue(String(this.#value), String(this.#value));
|
|
190
264
|
if (this.#entry?.isOpen) {
|
|
191
|
-
for (const name of
|
|
265
|
+
for (const name of roleAttributes) control.removeAttribute(name);
|
|
192
266
|
this.forwardNaming(control, false);
|
|
193
267
|
this.#renderReadout();
|
|
194
268
|
return;
|
|
195
269
|
}
|
|
196
|
-
control.setAttribute("role",
|
|
197
|
-
control.setAttribute("aria-valuemin", String(this.min
|
|
198
|
-
control.setAttribute("aria-valuemax", String(this
|
|
270
|
+
control.setAttribute("role", this.controlRole());
|
|
271
|
+
control.setAttribute("aria-valuemin", String(this.min));
|
|
272
|
+
control.setAttribute("aria-valuemax", String(this.#max()));
|
|
199
273
|
control.setAttribute("aria-valuenow", String(this.#value));
|
|
200
274
|
if (orientation) control.setAttribute("aria-orientation", orientation);
|
|
201
275
|
writeAttribute(control, "aria-valuetext", this.#formatValue?.(this.#value));
|
|
@@ -203,23 +277,36 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
203
277
|
this.#renderDisabled(control);
|
|
204
278
|
this.#renderReadout();
|
|
205
279
|
}
|
|
280
|
+
restoreState(state) {
|
|
281
|
+
this.value = Number(state);
|
|
282
|
+
}
|
|
206
283
|
valueAt(fraction) {
|
|
207
|
-
return (this.#taper() ?? linearTaper(this.min
|
|
284
|
+
return (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);
|
|
285
|
+
}
|
|
286
|
+
valueText() {
|
|
287
|
+
return this.#formatValue?.(this.#value) ?? String(this.#value);
|
|
288
|
+
}
|
|
289
|
+
wraps() {
|
|
290
|
+
return false;
|
|
208
291
|
}
|
|
209
292
|
#bindEntry(control, signal) {
|
|
210
293
|
const input = requireChild(control, "input", HTMLInputElement);
|
|
211
|
-
const bubble =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
294
|
+
const bubble = control.querySelector("[popover]");
|
|
295
|
+
input.id = `sonic-entry-${this.#instance}`;
|
|
296
|
+
if (bubble) {
|
|
297
|
+
const anchor = `--sonic-readout-${this.#instance}`;
|
|
298
|
+
this.#readout = {
|
|
299
|
+
bubble,
|
|
300
|
+
text: requireChild(bubble, "span", HTMLElement)
|
|
301
|
+
};
|
|
302
|
+
control.style.setProperty("anchor-name", anchor);
|
|
303
|
+
bubble.style.setProperty("position-anchor", anchor);
|
|
304
|
+
}
|
|
218
305
|
const entry = new RangeEntry(input, {
|
|
219
306
|
commit: (text) => {
|
|
220
307
|
this.#commit((this.#parseValue ?? Number.parseFloat)(text));
|
|
221
308
|
},
|
|
222
|
-
text: () => this.#parseValue ? this
|
|
309
|
+
text: () => this.#parseValue ? this.valueText() : String(this.#value),
|
|
223
310
|
toggle: (isOpen) => {
|
|
224
311
|
if (isOpen) this.forwardNaming(input, true);
|
|
225
312
|
this.toggleState("editing", isOpen);
|
|
@@ -232,7 +319,7 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
232
319
|
}
|
|
233
320
|
#bindKeys(control, entry, signal) {
|
|
234
321
|
control.addEventListener("keydown", (event) => {
|
|
235
|
-
if (this.
|
|
322
|
+
if (this.isDisabled() || event.defaultPrevented || event.target !== control) return;
|
|
236
323
|
if (event.key === "Enter") {
|
|
237
324
|
event.preventDefault();
|
|
238
325
|
entry.open();
|
|
@@ -249,14 +336,18 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
249
336
|
}, { signal });
|
|
250
337
|
}
|
|
251
338
|
#clamp(next) {
|
|
252
|
-
const min = this.min
|
|
253
|
-
const step = this
|
|
339
|
+
const min = this.min;
|
|
340
|
+
const step = this.step;
|
|
254
341
|
const stepped = step > 0 ? min + Math.round((next - min) / step) * step : next;
|
|
255
342
|
return this.clampRange(Number(stepped.toPrecision(12)));
|
|
256
343
|
}
|
|
257
344
|
#commit(next) {
|
|
258
345
|
if (this.input(next)) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
259
346
|
}
|
|
347
|
+
#end(step) {
|
|
348
|
+
if (!this.wraps()) return this.#max();
|
|
349
|
+
return this.min + (Math.ceil(this.range() / step) - 1) * step;
|
|
350
|
+
}
|
|
260
351
|
#endDrag() {
|
|
261
352
|
const drag = this.#drag;
|
|
262
353
|
if (!drag) return;
|
|
@@ -273,22 +364,25 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
273
364
|
this.#renderReadout();
|
|
274
365
|
}
|
|
275
366
|
#isValueShown() {
|
|
276
|
-
return (this.#drag?.isRevealed === true || this.#keyRevealTimer !== void 0) && this.
|
|
367
|
+
return (this.#drag?.isRevealed === true || this.#keyRevealTimer !== void 0) && this.readout;
|
|
277
368
|
}
|
|
278
369
|
#keyTarget(key) {
|
|
279
|
-
if (key === "Home") return this.min
|
|
280
|
-
|
|
281
|
-
if (
|
|
370
|
+
if (key === "Home") return this.min;
|
|
371
|
+
const step = this.step > 0 ? this.step : this.range() / 100;
|
|
372
|
+
if (key === "End") return this.#end(step);
|
|
373
|
+
if (resetKeys.has(key)) return this.default;
|
|
282
374
|
const steps = keySteps.get(key);
|
|
283
375
|
if (steps === void 0) return void 0;
|
|
284
|
-
const step = this.#step() > 0 ? this.#step() : this.range() / 100;
|
|
285
376
|
const taper = this.#taper();
|
|
286
377
|
if (!taper) return this.#value + steps * step;
|
|
287
378
|
const next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));
|
|
288
379
|
return next === this.#value ? this.#value + Math.sign(steps) * step : next;
|
|
289
380
|
}
|
|
381
|
+
#max() {
|
|
382
|
+
return Math.max(this.min, this.max);
|
|
383
|
+
}
|
|
290
384
|
#renderDisabled(control) {
|
|
291
|
-
if (this.
|
|
385
|
+
if (this.isDisabled()) {
|
|
292
386
|
control.setAttribute("aria-disabled", "true");
|
|
293
387
|
control.removeAttribute("tabindex");
|
|
294
388
|
return;
|
|
@@ -301,15 +395,11 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
301
395
|
if (!readout || !canPopover || !this.isConnected) return;
|
|
302
396
|
const isEditing = this.#entry?.isOpen === true;
|
|
303
397
|
readout.text.hidden = isEditing;
|
|
304
|
-
if (!isEditing) readout.text.textContent = this
|
|
398
|
+
if (!isEditing) readout.text.textContent = this.valueText();
|
|
305
399
|
readout.bubble.togglePopover(isEditing || this.#isValueShown());
|
|
306
400
|
}
|
|
307
401
|
#reset() {
|
|
308
|
-
this.#commit(this.
|
|
309
|
-
}
|
|
310
|
-
#resetValue() {
|
|
311
|
-
const value = this.numberAttribute("default", NaN);
|
|
312
|
-
return Number.isFinite(value) ? value : void 0;
|
|
402
|
+
this.#commit(this.default ?? NaN);
|
|
313
403
|
}
|
|
314
404
|
#reveal(drag) {
|
|
315
405
|
if (drag.isRevealed || this.#drag !== drag) return;
|
|
@@ -323,22 +413,18 @@ var SonicRangeElement = class extends SonicElement {
|
|
|
323
413
|
}, keyRevealMs);
|
|
324
414
|
this.#renderReadout();
|
|
325
415
|
}
|
|
326
|
-
#step() {
|
|
327
|
-
return this.numberAttribute("step", 1);
|
|
328
|
-
}
|
|
329
416
|
#taper() {
|
|
330
|
-
if (this.
|
|
331
|
-
const min = this.min
|
|
332
|
-
const max = this
|
|
333
|
-
return (this.
|
|
334
|
-
}
|
|
335
|
-
#valueText() {
|
|
336
|
-
return this.#formatValue?.(this.#value) ?? String(this.#value);
|
|
417
|
+
if (this.notched || this.wraps()) return void 0;
|
|
418
|
+
const min = this.min;
|
|
419
|
+
const max = this.#max();
|
|
420
|
+
return (this.taper === "log" ? logTaper(min, max) : void 0) ?? skewTaper(min, max, this.midpoint ?? NaN);
|
|
337
421
|
}
|
|
338
422
|
#write(next) {
|
|
339
423
|
if (!Number.isFinite(next)) return;
|
|
340
424
|
this.#isDirty = true;
|
|
341
|
-
|
|
425
|
+
const clamped = this.#clamp(next);
|
|
426
|
+
if (clamped === this.#value) return;
|
|
427
|
+
this.#value = clamped;
|
|
342
428
|
this.render();
|
|
343
429
|
}
|
|
344
430
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"range-element.js","names":[],"sources":["../../../src/elements/range-element.ts"],"sourcesContent":["import type { Taper } from '#lib/taper.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { RangeEntry } from '#elements/range-entry.ts';\nimport { requireChild } from '#lib/render.ts';\nimport { clampUnit, linearTaper, logTaper, skewTaper, wrapUnit } from '#lib/taper.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// `position` grows with the value\nexport interface RangeAxis {\n\tposition: (event: PointerEvent) => number;\n\ttravelPx: number;\n}\n\ninterface RangeDrag extends RangeAxis {\n\tfromValue: number;\n\tisRevealed: boolean;\n\tlastPosition: number;\n\tpointerId: number;\n\t// Unstepped, or a slow drag rounds back to where it started\n\trawFraction: number;\n\trevealTimer: ReturnType<typeof setTimeout> | undefined;\n\tstartPosition: number;\n}\n\nconst fineScale = 0.1;\nconst keyRevealMs = 1000;\nconst pageSteps = 10;\nconst revealMs = 250;\nconst revealPx = 4;\n\n// happy-dom has no popover API\nconst canPopover = 'togglePopover' in HTMLElement.prototype;\n\n// A shared anchor name resolves to the last one on the page\nlet instanceCount = 0;\n\n// A Mac keyboard's Delete sends Backspace\nconst resetKeys = new Set(['Backspace', 'Delete']);\n\nconst keySteps = new Map([\n\t['ArrowDown', -1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', 1],\n\t['PageDown', -pageSteps],\n\t['PageUp', pageSteps],\n]);\n\n// A slider's children are presentational, so these come off while the entry is open\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\n// As on `<input type=\"range\">`, the property never writes the `value` attribute back\nexport abstract class SonicRangeElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'step',\n\t\t'taper',\n\t\t'value',\n\t];\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\t// Named for the absent attribute, as `input.type` reads `text`\n\tget doublePress(): 'reset' | 'type' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'type';\n\t}\n\n\tset doublePress(gesture: 'reset' | 'type' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// Unclamped by `min`, so reading it back never pins the range\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\t// `undefined` while unset, so reading it back never pins it to `min`\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#value;\n\t}\n\n\t// A drag owns the value, so a timer's write can't pull the cap from under the pointer\n\tset value(next: number) {\n\t\tif (this.#drag) return;\n\n\t\tthis.#write(next);\n\t}\n\n\t#drag: RangeDrag | undefined;\n\n\t#entry: RangeEntry | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// Re-read from the attribute until the property is set, since `max` may arrive after `value`\n\t#isDirty = false;\n\n\t#keyRevealTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { bubble: HTMLElement; text: HTMLElement };\n\n\t#value = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') {\n\t\t\tif (this.#drag) return;\n\n\t\t\tthis.#isDirty = false;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) {\n\t\t\tthis.#entry?.close(false);\n\t\t\tthis.#endDrag();\n\t\t}\n\n\t\tthis.#value = this.#clamp(\n\t\t\tthis.#isDirty ? this.#value : this.numberAttribute('value', this.min),\n\t\t);\n\t\tthis.render();\n\t}\n\n\t// The range before `value`, or a value set with its `max` before upgrade clamps to the old one\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'formatValue',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => RangeAxis | undefined,\n\t): void {\n\t\tconst entry = this.#bindEntry(control, signal);\n\n\t\tcontrol.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || this.isDisabled() || entry.isOpen) return;\n\n\t\t\t\t// A click listener above makes WebKit send a tap's compatibility mousedown, which blurs the entry a double tap just opened\n\t\t\t\tif (event.pointerType === 'touch') event.preventDefault();\n\t\t\t\tcontrol.focus();\n\t\t\t\tif (event.metaKey || event.ctrlKey) {\n\t\t\t\t\tthis.#reset();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst fromValue = this.#value;\n\t\t\t\tconst axis = grab(event);\n\t\t\t\tif (!axis) return;\n\n\t\t\t\tconst startPosition = axis.position(event);\n\t\t\t\tconst drag: RangeDrag = {\n\t\t\t\t\tfromValue,\n\t\t\t\t\tisRevealed: false,\n\t\t\t\t\tlastPosition: startPosition,\n\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t\tposition: axis.position,\n\t\t\t\t\trawFraction: this.fraction(this.#value),\n\t\t\t\t\trevealTimer: undefined,\n\t\t\t\t\tstartPosition,\n\t\t\t\t\ttravelPx: axis.travelPx,\n\t\t\t\t};\n\n\t\t\t\tdrag.revealTimer = setTimeout(() => {\n\t\t\t\t\tthis.#reveal(drag);\n\t\t\t\t}, revealMs);\n\t\t\t\tthis.#drag = drag;\n\t\t\t\tcontrol.setPointerCapture(event.pointerId);\n\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tconst drag = this.#drag;\n\t\t\t\tif (event.pointerId !== drag?.pointerId) return;\n\n\t\t\t\tconst position = drag.position(event);\n\t\t\t\tconst scale = event.shiftKey ? fineScale : 1;\n\n\t\t\t\t// Per move, so pressing Shift mid-drag changes pace without a jump\n\t\t\t\tconst fraction =\n\t\t\t\t\tdrag.rawFraction + ((position - drag.lastPosition) / drag.travelPx) * scale;\n\n\t\t\t\tdrag.rawFraction = this.wraps() ? wrapUnit(fraction) : clampUnit(fraction);\n\t\t\t\tdrag.lastPosition = position;\n\t\t\t\tif (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);\n\t\t\t\tthis.input(this.valueAt(drag.rawFraction));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\tconst endDrag = (event: PointerEvent): void => {\n\t\t\tif (event.pointerId === this.#drag?.pointerId) this.#endDrag();\n\t\t};\n\n\t\tcontrol.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t(event) => {\n\t\t\t\tendDrag(event);\n\t\t\t\tentry.forgetPress();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// Other code can release capture without a `pointerup`\n\t\tcontrol.addEventListener('lostpointercapture', endDrag, { signal });\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerId !== this.#drag?.pointerId) return;\n\n\t\t\t\tthis.#endDrag();\n\t\t\t\tif (!entry.press(event)) return;\n\n\t\t\t\tif (this.doublePress === 'reset') this.#reset();\n\t\t\t\telse entry.open();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindKeys(control, entry, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#endDrag();\n\t\t\t\tthis.#hideForKeys();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected clampRange(next: number): number {\n\t\tconst min = this.min;\n\t\tif (!this.wraps()) return Math.min(this.#max(), Math.max(min, next));\n\n\t\tconst range = this.range();\n\t\tif (range <= 0) return min;\n\n\t\tconst offset = Number(((((next - min) % range) + range) % range).toPrecision(12));\n\n\t\treturn offset >= range ? min : Number((min + offset).toPrecision(12));\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected fraction(value: number): number {\n\t\treturn (this.#taper() ?? linearTaper(this.min, this.#max())).position(value);\n\t}\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#value;\n\n\t\tthis.#write(next);\n\t\tif (this.#value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected modulationFractions(amount: number): [number, number] {\n\t\tconst value = this.fraction(this.#value);\n\t\tconst modulated = this.fraction(this.#value + amount);\n\n\t\treturn [Math.min(value, modulated), Math.max(value, modulated)];\n\t}\n\n\tprotected originFraction(): number {\n\t\treturn this.fraction(this.origin ?? this.min);\n\t}\n\n\tprotected positions(): number | undefined {\n\t\tconst positions = Math.round(this.range() / this.step) + (this.wraps() ? 0 : 1);\n\n\t\tif (!this.notched || !Number.isFinite(positions) || positions < 2) return;\n\n\t\treturn positions;\n\t}\n\n\tprotected range(): number {\n\t\treturn this.#max() - this.min;\n\t}\n\n\t// Per render rather than per write, so a range with no `value` attribute submits its minimum too\n\tprotected renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#value), String(this.#value));\n\t\tif (this.#entry?.isOpen) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.#renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tcontrol.setAttribute('role', this.controlRole());\n\t\tcontrol.setAttribute('aria-valuemin', String(this.min));\n\t\tcontrol.setAttribute('aria-valuemax', String(this.#max()));\n\t\tcontrol.setAttribute('aria-valuenow', String(this.#value));\n\t\tif (orientation) control.setAttribute('aria-orientation', orientation);\n\t\twriteAttribute(control, 'aria-valuetext', this.#formatValue?.(this.#value));\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected valueAt(fraction: number): number {\n\t\treturn (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);\n\t}\n\n\tprotected valueText(): string {\n\t\treturn this.#formatValue?.(this.#value) ?? String(this.#value);\n\t}\n\n\tprotected wraps(): boolean {\n\t\treturn false;\n\t}\n\n\t#bindEntry(control: HTMLElement, signal: AbortSignal): RangeEntry {\n\t\tconst input = requireChild(control, 'input', HTMLInputElement);\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\t// Chrome's issues panel flags a form field with no id or name; a `name` would submit it\n\t\tinput.id = `sonic-entry-${this.#instance}`;\n\t\tif (bubble) {\n\t\t\tconst anchor = `--sonic-readout-${this.#instance}`;\n\n\t\t\tthis.#readout = { bubble, text: requireChild(bubble, 'span', HTMLElement) };\n\t\t\tcontrol.style.setProperty('anchor-name', anchor);\n\t\t\tbubble.style.setProperty('position-anchor', anchor);\n\t\t}\n\n\t\tconst entry = new RangeEntry(input, {\n\t\t\tcommit: (text) => {\n\t\t\t\t// eslint-disable-next-line unicorn/prefer-number-coercion -- `Number('')` is 0; an emptied field should leave the value alone\n\t\t\t\tthis.#commit((this.#parseValue ?? Number.parseFloat)(text));\n\t\t\t},\n\t\t\t// `Number.parseFloat` reads \"5 kHz\" as 5\n\t\t\ttext: () => (this.#parseValue ? this.valueText() : String(this.#value)),\n\t\t\ttoggle: (isOpen) => {\n\t\t\t\tif (isOpen) this.forwardNaming(input, true);\n\t\t\t\tthis.toggleState('editing', isOpen);\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t});\n\n\t\tthis.#entry = entry;\n\t\tentry.bind(control, signal);\n\n\t\treturn entry;\n\t}\n\n\t#bindKeys(control: HTMLElement, entry: RangeEntry, signal: AbortSignal): void {\n\t\tcontrol.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented || event.target !== control) return;\n\n\t\t\t\tif (event.key === 'Enter') {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tentry.open();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst next = this.#keyTarget(event.key);\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#commit(next);\n\t\t\t\tthis.#revealForKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.#hideForKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#clamp(next: number): number {\n\t\tconst min = this.min;\n\t\tconst step = this.step;\n\t\tconst stepped = step > 0 ? min + Math.round((next - min) / step) * step : next;\n\n\t\t// A step like 0.01 leaves float residue\n\t\treturn this.clampRange(Number(stepped.toPrecision(12)));\n\t}\n\n\t#commit(next: number): void {\n\t\tif (this.input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#end(step: number): number {\n\t\tif (!this.wraps()) return this.#max();\n\n\t\treturn this.min + (Math.ceil(this.range() / step) - 1) * step;\n\t}\n\n\t#endDrag(): void {\n\t\tconst drag = this.#drag;\n\t\tif (!drag) return;\n\n\t\tclearTimeout(drag.revealTimer);\n\t\tthis.#drag = undefined;\n\t\tthis.toggleState('dragging', false);\n\t\tthis.#renderReadout();\n\t\tif (this.#value !== drag.fromValue) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#hideForKeys(): void {\n\t\tclearTimeout(this.#keyRevealTimer);\n\t\tif (this.#keyRevealTimer === undefined) return;\n\n\t\tthis.#keyRevealTimer = undefined;\n\t\tthis.#renderReadout();\n\t}\n\n\t#isValueShown(): boolean {\n\t\tconst isRevealed = this.#drag?.isRevealed === true || this.#keyRevealTimer !== undefined;\n\n\t\treturn isRevealed && this.readout;\n\t}\n\n\t#keyTarget(key: string): number | undefined {\n\t\tif (key === 'Home') return this.min;\n\t\tconst step = this.step > 0 ? this.step : this.range() / 100;\n\n\t\tif (key === 'End') return this.#end(step);\n\t\tif (resetKeys.has(key)) return this.default;\n\n\t\tconst steps = keySteps.get(key);\n\t\tif (steps === undefined) return undefined;\n\n\t\tconst taper = this.#taper();\n\t\tif (!taper) return this.#value + steps * step;\n\n\t\t// By travel, since steps at the wide end would take forever; at least one step, or the snap holds it in place\n\t\tconst next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));\n\n\t\treturn next === this.#value ? this.#value + Math.sign(steps) * step : next;\n\t}\n\n\t#max(): number {\n\t\treturn Math.max(this.min, this.max);\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tif (this.isDisabled()) {\n\t\t\tcontrol.setAttribute('aria-disabled', 'true');\n\t\t\tcontrol.removeAttribute('tabindex');\n\t\t\treturn;\n\t\t}\n\n\t\tcontrol.removeAttribute('aria-disabled');\n\t\tcontrol.setAttribute('tabindex', '0');\n\t}\n\n\t#renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout || !canPopover || !this.isConnected) return;\n\n\t\tconst isEditing = this.#entry?.isOpen === true;\n\n\t\treadout.text.hidden = isEditing;\n\t\tif (!isEditing) readout.text.textContent = this.valueText();\n\t\treadout.bubble.togglePopover(isEditing || this.#isValueShown());\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit(this.default ?? NaN);\n\t}\n\n\t#reveal(drag: RangeDrag): void {\n\t\tif (drag.isRevealed || this.#drag !== drag) return;\n\n\t\tdrag.isRevealed = true;\n\t\tthis.#renderReadout();\n\t}\n\n\t#revealForKeys(): void {\n\t\tclearTimeout(this.#keyRevealTimer);\n\t\tthis.#keyRevealTimer = setTimeout(() => {\n\t\t\tthis.#hideForKeys();\n\t\t}, keyRevealMs);\n\t\tthis.#renderReadout();\n\t}\n\n\t// Notches are evenly spaced, so a notched control stays linear\n\t#taper(): Taper | undefined {\n\t\tif (this.notched || this.wraps()) return undefined;\n\n\t\tconst min = this.min;\n\t\tconst max = this.#max();\n\t\tconst log = this.taper === 'log' ? logTaper(min, max) : undefined;\n\n\t\treturn log ?? skewTaper(min, max, this.midpoint ?? NaN);\n\t}\n\n\t#write(next: number): void {\n\t\tif (!Number.isFinite(next)) return;\n\n\t\tthis.#isDirty = true;\n\n\t\tconst clamped = this.#clamp(next);\n\t\tif (clamped === this.#value) return;\n\n\t\tthis.#value = clamped;\n\t\tthis.render();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/elements/range-entry.ts
|
|
2
|
+
const doublePressMs = 500;
|
|
3
|
+
const doublePressPx = 4;
|
|
4
|
+
function isDoublePress(previous, next) {
|
|
5
|
+
if (!previous) return false;
|
|
6
|
+
return next.time - previous.time < doublePressMs && Math.hypot(next.x - previous.x, next.y - previous.y) < doublePressPx;
|
|
7
|
+
}
|
|
8
|
+
var RangeEntry = class {
|
|
9
|
+
get isOpen() {
|
|
10
|
+
return this.#isOpen;
|
|
11
|
+
}
|
|
12
|
+
#input;
|
|
13
|
+
#isOpen = false;
|
|
14
|
+
#lastPress;
|
|
15
|
+
#openedText = "";
|
|
16
|
+
#options;
|
|
17
|
+
constructor(input, options) {
|
|
18
|
+
this.#input = input;
|
|
19
|
+
this.#options = options;
|
|
20
|
+
}
|
|
21
|
+
bind(control, signal) {
|
|
22
|
+
const input = this.#input;
|
|
23
|
+
input.addEventListener("keydown", (event) => {
|
|
24
|
+
if (event.isComposing || event.key !== "Enter" && event.key !== "Escape") return;
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
this.close(event.key === "Enter");
|
|
27
|
+
control.focus();
|
|
28
|
+
}, { signal });
|
|
29
|
+
input.addEventListener("blur", () => {
|
|
30
|
+
this.close(true);
|
|
31
|
+
}, { signal });
|
|
32
|
+
for (const type of ["change", "input"]) input.addEventListener(type, (event) => {
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
}, { signal });
|
|
35
|
+
signal.addEventListener("abort", () => {
|
|
36
|
+
this.close(false);
|
|
37
|
+
}, { once: true });
|
|
38
|
+
}
|
|
39
|
+
close(isCommitting) {
|
|
40
|
+
if (!this.#isOpen) return;
|
|
41
|
+
this.#isOpen = false;
|
|
42
|
+
this.#input.hidden = true;
|
|
43
|
+
this.#options.toggle(false);
|
|
44
|
+
if (isCommitting && this.#input.value !== this.#openedText) this.#options.commit(this.#input.value);
|
|
45
|
+
}
|
|
46
|
+
forgetPress() {
|
|
47
|
+
this.#lastPress = void 0;
|
|
48
|
+
}
|
|
49
|
+
open() {
|
|
50
|
+
const input = this.#input;
|
|
51
|
+
this.#isOpen = true;
|
|
52
|
+
this.#openedText = this.#options.text();
|
|
53
|
+
input.value = this.#openedText;
|
|
54
|
+
input.hidden = false;
|
|
55
|
+
this.#options.toggle(true);
|
|
56
|
+
input.focus();
|
|
57
|
+
input.select();
|
|
58
|
+
}
|
|
59
|
+
press(event) {
|
|
60
|
+
const press = {
|
|
61
|
+
time: event.timeStamp,
|
|
62
|
+
x: event.clientX,
|
|
63
|
+
y: event.clientY
|
|
64
|
+
};
|
|
65
|
+
const previous = this.#lastPress;
|
|
66
|
+
this.#lastPress = press;
|
|
67
|
+
if (!isDoublePress(previous, press)) return false;
|
|
68
|
+
this.#lastPress = void 0;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { RangeEntry };
|
|
75
|
+
//# sourceMappingURL=range-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"range-entry.js","names":[],"sources":["../../../src/elements/range-entry.ts"],"sourcesContent":["interface Press {\n\ttime: number;\n\tx: number;\n\ty: number;\n}\n\ninterface RangeEntryOptions {\n\tcommit: (text: string) => void;\n\ttext: () => string;\n\ttoggle: (isOpen: boolean) => void;\n}\n\n// Not `dblclick`, which iOS may not deliver under `touch-action: none`\nconst doublePressMs = 500;\nconst doublePressPx = 4;\n\nfunction isDoublePress(previous: Press | undefined, next: Press): boolean {\n\tif (!previous) return false;\n\n\treturn (\n\t\tnext.time - previous.time < doublePressMs &&\n\t\tMath.hypot(next.x - previous.x, next.y - previous.y) < doublePressPx\n\t);\n}\n\nexport class RangeEntry {\n\tget isOpen(): boolean {\n\t\treturn this.#isOpen;\n\t}\n\n\treadonly #input: HTMLInputElement;\n\n\t#isOpen = false;\n\n\t#lastPress: Press | undefined;\n\n\t#openedText = '';\n\n\treadonly #options: RangeEntryOptions;\n\n\tconstructor(input: HTMLInputElement, options: RangeEntryOptions) {\n\t\tthis.#input = input;\n\t\tthis.#options = options;\n\t}\n\n\tbind(control: HTMLElement, signal: AbortSignal): void {\n\t\tconst input = this.#input;\n\n\t\tinput.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.isComposing || (event.key !== 'Enter' && event.key !== 'Escape')) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.close(event.key === 'Enter');\n\t\t\t\tcontrol.focus();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tinput.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.close(true);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\t// The field's own events would reach the host's listeners as if the value had moved\n\t\tfor (const type of ['change', 'input']) {\n\t\t\tinput.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.close(false);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t// Closes first, so the committed value renders with the control's role back in place\n\tclose(isCommitting: boolean): void {\n\t\tif (!this.#isOpen) return;\n\n\t\tthis.#isOpen = false;\n\t\tthis.#input.hidden = true;\n\t\tthis.#options.toggle(false);\n\t\t// A format that rounds would otherwise write its rounding back\n\t\tif (isCommitting && this.#input.value !== this.#openedText)\n\t\t\tthis.#options.commit(this.#input.value);\n\t}\n\n\tforgetPress(): void {\n\t\tthis.#lastPress = undefined;\n\t}\n\n\topen(): void {\n\t\tconst input = this.#input;\n\n\t\tthis.#isOpen = true;\n\t\tthis.#openedText = this.#options.text();\n\t\tinput.value = this.#openedText;\n\t\tinput.hidden = false;\n\t\tthis.#options.toggle(true);\n\t\tinput.focus();\n\t\tinput.select();\n\t}\n\n\tpress(event: PointerEvent): boolean {\n\t\tconst press = { time: event.timeStamp, x: event.clientX, y: event.clientY };\n\t\tconst previous = this.#lastPress;\n\n\t\tthis.#lastPress = press;\n\t\tif (!isDoublePress(previous, press)) return false;\n\n\t\tthis.#lastPress = undefined;\n\n\t\treturn true;\n\t}\n}\n"],"mappings":";AAaA,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AAEtB,SAAS,cAAc,UAA6B,MAAsB;CACzE,IAAI,CAAC,UAAU,OAAO;CAEtB,OACC,KAAK,OAAO,SAAS,OAAO,iBAC5B,KAAK,MAAM,KAAK,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,CAAC,IAAI;AAEzD;AAEA,IAAa,aAAb,MAAwB;CACvB,IAAI,SAAkB;EACrB,OAAO,KAAK;CACb;CAEA,AAAS;CAET,UAAU;CAEV;CAEA,cAAc;CAEd,AAAS;CAET,YAAY,OAAyB,SAA4B;EAChE,KAAK,SAAS;EACd,KAAK,WAAW;CACjB;CAEA,KAAK,SAAsB,QAA2B;EACrD,MAAM,QAAQ,KAAK;EAEnB,MAAM,iBACL,YACC,UAAU;GACV,IAAI,MAAM,eAAgB,MAAM,QAAQ,WAAW,MAAM,QAAQ,UAAW;GAE5E,MAAM,eAAe;GACrB,KAAK,MAAM,MAAM,QAAQ,OAAO;GAChC,QAAQ,MAAM;EACf,GACA,EAAE,OAAO,CACV;EACA,MAAM,iBACL,cACM;GACL,KAAK,MAAM,IAAI;EAChB,GACA,EAAE,OAAO,CACV;EAGA,KAAK,MAAM,QAAQ,CAAC,UAAU,OAAO,GACpC,MAAM,iBACL,OACC,UAAU;GACV,MAAM,gBAAgB;EACvB,GACA,EAAE,OAAO,CACV;EAED,OAAO,iBACN,eACM;GACL,KAAK,MAAM,KAAK;EACjB,GACA,EAAE,MAAM,KAAK,CACd;CACD;CAGA,MAAM,cAA6B;EAClC,IAAI,CAAC,KAAK,SAAS;EAEnB,KAAK,UAAU;EACf,KAAK,OAAO,SAAS;EACrB,KAAK,SAAS,OAAO,KAAK;EAE1B,IAAI,gBAAgB,KAAK,OAAO,UAAU,KAAK,aAC9C,KAAK,SAAS,OAAO,KAAK,OAAO,KAAK;CACxC;CAEA,cAAoB;EACnB,KAAK,aAAa;CACnB;CAEA,OAAa;EACZ,MAAM,QAAQ,KAAK;EAEnB,KAAK,UAAU;EACf,KAAK,cAAc,KAAK,SAAS,KAAK;EACtC,MAAM,QAAQ,KAAK;EACnB,MAAM,SAAS;EACf,KAAK,SAAS,OAAO,IAAI;EACzB,MAAM,MAAM;EACZ,MAAM,OAAO;CACd;CAEA,MAAM,OAA8B;EACnC,MAAM,QAAQ;GAAE,MAAM,MAAM;GAAW,GAAG,MAAM;GAAS,GAAG,MAAM;EAAQ;EAC1E,MAAM,WAAW,KAAK;EAEtB,KAAK,aAAa;EAClB,IAAI,CAAC,cAAc,UAAU,KAAK,GAAG,OAAO;EAE5C,KAAK,aAAa;EAElB,OAAO;CACR;AACD"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
2
2
|
//#region src/elements/segmented.d.ts
|
|
3
3
|
declare global {
|
|
4
4
|
interface HTMLElementTagNameMap {
|
|
5
5
|
'sonic-segmented': SonicSegmented;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
-
export declare class SonicSegmented extends
|
|
8
|
+
export declare class SonicSegmented extends SonicFormElement {
|
|
9
9
|
#private;
|
|
10
10
|
static readonly observedAttributes: string[];
|
|
11
11
|
get value(): string;
|
|
12
12
|
set value(next: string);
|
|
13
13
|
attributeChangedCallback(name: string): void;
|
|
14
14
|
protected connect(signal: AbortSignal): void;
|
|
15
|
+
protected focusTarget(): HTMLElement | undefined;
|
|
16
|
+
protected render(): void;
|
|
17
|
+
protected restoreState(state: string): void;
|
|
15
18
|
}
|
|
16
19
|
//#endregion
|
|
17
20
|
//# sourceMappingURL=segmented.d.ts.map
|