@xsynaptic/sonic-ui 0.1.0 → 0.2.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/segmented.js +7 -0
- package/dist/default/define/slider.js +7 -0
- package/dist/default/elements/define-once.js +7 -0
- package/dist/{elements → default/elements}/dial.js +15 -3
- 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/range-element.js +410 -0
- package/dist/{elements → default/elements}/range-entry.js +5 -4
- package/dist/default/elements/segmented.js +173 -0
- package/dist/{elements → default/elements}/slider.js +23 -8
- package/dist/default/elements/sonic-element.js +153 -0
- package/dist/{index.js → default/index.js} +2 -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/default/lib/taper.js +32 -0
- 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/segmented.js.map +1 -0
- package/dist/dev/define/slider.js.map +1 -0
- package/dist/dev/define.js +5 -0
- package/dist/dev/elements/define-once.js.map +1 -0
- package/dist/{elements → dev/elements}/dial.d.ts +4 -0
- package/dist/dev/elements/dial.js +70 -0
- 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/{elements → dev/elements}/range-element.d.ts +25 -5
- package/dist/{elements → dev/elements}/range-element.js +116 -53
- 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 +6 -0
- package/dist/dev/elements/slider.js +86 -0
- 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} +2 -1
- package/dist/dev/index.js +8 -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.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 +0 -1
- package/dist/styles/dial.css +33 -13
- package/dist/styles/key.css +30 -10
- package/dist/styles/material.css +58 -10
- package/dist/styles/meter.css +25 -5
- package/dist/styles/screen.css +7 -1
- package/dist/styles/segmented.css +24 -7
- package/dist/styles/slider.css +17 -4
- package/package.json +13 -12
- 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.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.js → default/define.js} +0 -0
- /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/{define.d.ts → dev/define.d.ts} +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}/taper.js +0 -0
- /package/dist/{lib → dev/lib}/write-attribute.js +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { requireChild } from "../lib/render.js";
|
|
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 } from "../lib/taper.js";
|
|
6
|
+
|
|
7
|
+
//#region src/elements/range-element.ts
|
|
8
|
+
const fineScale = .1;
|
|
9
|
+
const keyRevealMs = 1e3;
|
|
10
|
+
const pageSteps = 10;
|
|
11
|
+
const revealMs = 250;
|
|
12
|
+
const revealPx = 4;
|
|
13
|
+
const canPopover = "togglePopover" in HTMLElement.prototype;
|
|
14
|
+
let instanceCount = 0;
|
|
15
|
+
const resetKeys = /* @__PURE__ */ new Set(["Backspace", "Delete"]);
|
|
16
|
+
const keySteps = /* @__PURE__ */ new Map([
|
|
17
|
+
["ArrowDown", -1],
|
|
18
|
+
["ArrowLeft", -1],
|
|
19
|
+
["ArrowRight", 1],
|
|
20
|
+
["ArrowUp", 1],
|
|
21
|
+
["PageDown", -10],
|
|
22
|
+
["PageUp", pageSteps]
|
|
23
|
+
]);
|
|
24
|
+
const sliderAttributes = [
|
|
25
|
+
"aria-disabled",
|
|
26
|
+
"aria-orientation",
|
|
27
|
+
"aria-valuemax",
|
|
28
|
+
"aria-valuemin",
|
|
29
|
+
"aria-valuenow",
|
|
30
|
+
"aria-valuetext",
|
|
31
|
+
"role",
|
|
32
|
+
"tabindex"
|
|
33
|
+
];
|
|
34
|
+
var SonicRangeElement = class extends SonicFormElement {
|
|
35
|
+
static observedAttributes = [
|
|
36
|
+
...SonicFormElement.observedAttributes,
|
|
37
|
+
"max",
|
|
38
|
+
"midpoint",
|
|
39
|
+
"min",
|
|
40
|
+
"notched",
|
|
41
|
+
"origin",
|
|
42
|
+
"readout",
|
|
43
|
+
"step",
|
|
44
|
+
"taper",
|
|
45
|
+
"value"
|
|
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
|
+
}
|
|
65
|
+
get formatValue() {
|
|
66
|
+
return this.#formatValue;
|
|
67
|
+
}
|
|
68
|
+
set formatValue(format) {
|
|
69
|
+
this.#formatValue = format;
|
|
70
|
+
this.render();
|
|
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
|
+
}
|
|
102
|
+
get parseValue() {
|
|
103
|
+
return this.#parseValue;
|
|
104
|
+
}
|
|
105
|
+
set parseValue(parse) {
|
|
106
|
+
this.#parseValue = parse;
|
|
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
|
+
}
|
|
126
|
+
get value() {
|
|
127
|
+
return this.#value;
|
|
128
|
+
}
|
|
129
|
+
set value(next) {
|
|
130
|
+
if (this.#drag) return;
|
|
131
|
+
this.#write(next);
|
|
132
|
+
}
|
|
133
|
+
#drag;
|
|
134
|
+
#entry;
|
|
135
|
+
#formatValue;
|
|
136
|
+
#instance = String(instanceCount += 1);
|
|
137
|
+
#isDirty = false;
|
|
138
|
+
#keyRevealTimer;
|
|
139
|
+
#parseValue;
|
|
140
|
+
#readout;
|
|
141
|
+
#value = 0;
|
|
142
|
+
attributeChangedCallback(name) {
|
|
143
|
+
if (name === "value") {
|
|
144
|
+
if (this.#drag) return;
|
|
145
|
+
this.#isDirty = false;
|
|
146
|
+
}
|
|
147
|
+
if (name === "disabled" && this.isDisabled()) {
|
|
148
|
+
this.#entry?.close(false);
|
|
149
|
+
this.#endDrag();
|
|
150
|
+
}
|
|
151
|
+
this.#value = this.#clamp(this.#isDirty ? this.#value : this.numberAttribute("value", this.min));
|
|
152
|
+
this.render();
|
|
153
|
+
}
|
|
154
|
+
connectedCallback() {
|
|
155
|
+
this.upgradeProperties("default", "dimmed", "doublePress", "max", "midpoint", "min", "notched", "origin", "readout", "step", "taper", "formatValue", "parseValue", "value");
|
|
156
|
+
super.connectedCallback();
|
|
157
|
+
}
|
|
158
|
+
bindGestures(control, signal, grab) {
|
|
159
|
+
const entry = this.#bindEntry(control, signal);
|
|
160
|
+
control.addEventListener("pointerdown", (event) => {
|
|
161
|
+
if (event.button !== 0 || this.isDisabled() || entry.isOpen) return;
|
|
162
|
+
if (event.pointerType === "touch") event.preventDefault();
|
|
163
|
+
control.focus();
|
|
164
|
+
if (event.metaKey || event.ctrlKey) {
|
|
165
|
+
this.#reset();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const fromValue = this.#value;
|
|
169
|
+
const axis = grab(event);
|
|
170
|
+
if (!axis) return;
|
|
171
|
+
const startPosition = axis.position(event);
|
|
172
|
+
const drag = {
|
|
173
|
+
fromValue,
|
|
174
|
+
isRevealed: false,
|
|
175
|
+
lastPosition: startPosition,
|
|
176
|
+
pointerId: event.pointerId,
|
|
177
|
+
position: axis.position,
|
|
178
|
+
rawFraction: this.fraction(this.#value),
|
|
179
|
+
revealTimer: void 0,
|
|
180
|
+
startPosition,
|
|
181
|
+
travelPx: axis.travelPx
|
|
182
|
+
};
|
|
183
|
+
drag.revealTimer = setTimeout(() => {
|
|
184
|
+
this.#reveal(drag);
|
|
185
|
+
}, revealMs);
|
|
186
|
+
this.#drag = drag;
|
|
187
|
+
control.setPointerCapture(event.pointerId);
|
|
188
|
+
this.toggleState("dragging", true);
|
|
189
|
+
}, { signal });
|
|
190
|
+
control.addEventListener("pointermove", (event) => {
|
|
191
|
+
const drag = this.#drag;
|
|
192
|
+
if (event.pointerId !== drag?.pointerId) return;
|
|
193
|
+
const position = drag.position(event);
|
|
194
|
+
const scale = event.shiftKey ? fineScale : 1;
|
|
195
|
+
drag.rawFraction = clampUnit(drag.rawFraction + (position - drag.lastPosition) / drag.travelPx * scale);
|
|
196
|
+
drag.lastPosition = position;
|
|
197
|
+
if (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);
|
|
198
|
+
this.input(this.valueAt(drag.rawFraction));
|
|
199
|
+
}, { signal });
|
|
200
|
+
const endDrag = (event) => {
|
|
201
|
+
if (event.pointerId === this.#drag?.pointerId) this.#endDrag();
|
|
202
|
+
};
|
|
203
|
+
control.addEventListener("pointercancel", (event) => {
|
|
204
|
+
endDrag(event);
|
|
205
|
+
entry.forgetPress();
|
|
206
|
+
}, { signal });
|
|
207
|
+
control.addEventListener("lostpointercapture", endDrag, { signal });
|
|
208
|
+
control.addEventListener("pointerup", (event) => {
|
|
209
|
+
if (event.pointerId !== this.#drag?.pointerId) return;
|
|
210
|
+
this.#endDrag();
|
|
211
|
+
if (!entry.press(event)) return;
|
|
212
|
+
if (this.doublePress === "reset") this.#reset();
|
|
213
|
+
else entry.open();
|
|
214
|
+
}, { signal });
|
|
215
|
+
this.#bindKeys(control, entry, signal);
|
|
216
|
+
signal.addEventListener("abort", () => {
|
|
217
|
+
this.#endDrag();
|
|
218
|
+
this.#hideForKeys();
|
|
219
|
+
}, { once: true });
|
|
220
|
+
}
|
|
221
|
+
clampRange(next) {
|
|
222
|
+
return Math.min(this.#max(), Math.max(this.min, next));
|
|
223
|
+
}
|
|
224
|
+
fraction(value) {
|
|
225
|
+
return (this.#taper() ?? linearTaper(this.min, this.#max())).position(value);
|
|
226
|
+
}
|
|
227
|
+
input(next) {
|
|
228
|
+
const previous = this.#value;
|
|
229
|
+
this.#write(next);
|
|
230
|
+
if (this.#value === previous) return false;
|
|
231
|
+
this.dispatchEvent(new Event("input", {
|
|
232
|
+
bubbles: true,
|
|
233
|
+
composed: true
|
|
234
|
+
}));
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
originFraction() {
|
|
238
|
+
return this.fraction(this.origin ?? this.min);
|
|
239
|
+
}
|
|
240
|
+
positions() {
|
|
241
|
+
const positions = Math.round(this.range() / this.step) + 1;
|
|
242
|
+
if (!this.notched || !Number.isFinite(positions) || positions < 2) return;
|
|
243
|
+
return positions;
|
|
244
|
+
}
|
|
245
|
+
range() {
|
|
246
|
+
return this.#max() - this.min;
|
|
247
|
+
}
|
|
248
|
+
renderAria(control, orientation) {
|
|
249
|
+
this.writeFormValue(String(this.#value), String(this.#value));
|
|
250
|
+
if (this.#entry?.isOpen) {
|
|
251
|
+
for (const name of sliderAttributes) control.removeAttribute(name);
|
|
252
|
+
this.forwardNaming(control, false);
|
|
253
|
+
this.#renderReadout();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
control.setAttribute("role", "slider");
|
|
257
|
+
control.setAttribute("aria-valuemin", String(this.min));
|
|
258
|
+
control.setAttribute("aria-valuemax", String(this.#max()));
|
|
259
|
+
control.setAttribute("aria-valuenow", String(this.#value));
|
|
260
|
+
if (orientation) control.setAttribute("aria-orientation", orientation);
|
|
261
|
+
writeAttribute(control, "aria-valuetext", this.#formatValue?.(this.#value));
|
|
262
|
+
this.forwardNaming(control, true);
|
|
263
|
+
this.#renderDisabled(control);
|
|
264
|
+
this.#renderReadout();
|
|
265
|
+
}
|
|
266
|
+
restoreState(state) {
|
|
267
|
+
this.value = Number(state);
|
|
268
|
+
}
|
|
269
|
+
valueAt(fraction) {
|
|
270
|
+
return (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);
|
|
271
|
+
}
|
|
272
|
+
#bindEntry(control, signal) {
|
|
273
|
+
const input = requireChild(control, "input", HTMLInputElement);
|
|
274
|
+
const bubble = requireChild(control, "[popover]", HTMLElement);
|
|
275
|
+
const anchor = `--sonic-readout-${this.#instance}`;
|
|
276
|
+
input.id = `sonic-entry-${this.#instance}`;
|
|
277
|
+
this.#readout = {
|
|
278
|
+
bubble,
|
|
279
|
+
text: requireChild(bubble, "span", HTMLElement)
|
|
280
|
+
};
|
|
281
|
+
control.style.setProperty("anchor-name", anchor);
|
|
282
|
+
bubble.style.setProperty("position-anchor", anchor);
|
|
283
|
+
const entry = new RangeEntry(input, {
|
|
284
|
+
commit: (text) => {
|
|
285
|
+
this.#commit((this.#parseValue ?? Number.parseFloat)(text));
|
|
286
|
+
},
|
|
287
|
+
text: () => this.#parseValue ? this.#valueText() : String(this.#value),
|
|
288
|
+
toggle: (isOpen) => {
|
|
289
|
+
if (isOpen) this.forwardNaming(input, true);
|
|
290
|
+
this.toggleState("editing", isOpen);
|
|
291
|
+
this.render();
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
this.#entry = entry;
|
|
295
|
+
entry.bind(control, signal);
|
|
296
|
+
return entry;
|
|
297
|
+
}
|
|
298
|
+
#bindKeys(control, entry, signal) {
|
|
299
|
+
control.addEventListener("keydown", (event) => {
|
|
300
|
+
if (this.isDisabled() || event.defaultPrevented || event.target !== control) return;
|
|
301
|
+
if (event.key === "Enter") {
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
entry.open();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const next = this.#keyTarget(event.key);
|
|
307
|
+
if (next === void 0) return;
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
this.#commit(next);
|
|
310
|
+
this.#revealForKeys();
|
|
311
|
+
}, { signal });
|
|
312
|
+
control.addEventListener("blur", () => {
|
|
313
|
+
this.#hideForKeys();
|
|
314
|
+
}, { signal });
|
|
315
|
+
}
|
|
316
|
+
#clamp(next) {
|
|
317
|
+
const min = this.min;
|
|
318
|
+
const step = this.step;
|
|
319
|
+
const stepped = step > 0 ? min + Math.round((next - min) / step) * step : next;
|
|
320
|
+
return this.clampRange(Number(stepped.toPrecision(12)));
|
|
321
|
+
}
|
|
322
|
+
#commit(next) {
|
|
323
|
+
if (this.input(next)) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
324
|
+
}
|
|
325
|
+
#endDrag() {
|
|
326
|
+
const drag = this.#drag;
|
|
327
|
+
if (!drag) return;
|
|
328
|
+
clearTimeout(drag.revealTimer);
|
|
329
|
+
this.#drag = void 0;
|
|
330
|
+
this.toggleState("dragging", false);
|
|
331
|
+
this.#renderReadout();
|
|
332
|
+
if (this.#value !== drag.fromValue) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
333
|
+
}
|
|
334
|
+
#hideForKeys() {
|
|
335
|
+
clearTimeout(this.#keyRevealTimer);
|
|
336
|
+
if (this.#keyRevealTimer === void 0) return;
|
|
337
|
+
this.#keyRevealTimer = void 0;
|
|
338
|
+
this.#renderReadout();
|
|
339
|
+
}
|
|
340
|
+
#isValueShown() {
|
|
341
|
+
return (this.#drag?.isRevealed === true || this.#keyRevealTimer !== void 0) && this.readout;
|
|
342
|
+
}
|
|
343
|
+
#keyTarget(key) {
|
|
344
|
+
if (key === "Home") return this.min;
|
|
345
|
+
if (key === "End") return this.#max();
|
|
346
|
+
if (resetKeys.has(key)) return this.default;
|
|
347
|
+
const steps = keySteps.get(key);
|
|
348
|
+
if (steps === void 0) return void 0;
|
|
349
|
+
const step = this.step > 0 ? this.step : this.range() / 100;
|
|
350
|
+
const taper = this.#taper();
|
|
351
|
+
if (!taper) return this.#value + steps * step;
|
|
352
|
+
const next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));
|
|
353
|
+
return next === this.#value ? this.#value + Math.sign(steps) * step : next;
|
|
354
|
+
}
|
|
355
|
+
#max() {
|
|
356
|
+
return Math.max(this.min, this.max);
|
|
357
|
+
}
|
|
358
|
+
#renderDisabled(control) {
|
|
359
|
+
if (this.isDisabled()) {
|
|
360
|
+
control.setAttribute("aria-disabled", "true");
|
|
361
|
+
control.removeAttribute("tabindex");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
control.removeAttribute("aria-disabled");
|
|
365
|
+
control.setAttribute("tabindex", "0");
|
|
366
|
+
}
|
|
367
|
+
#renderReadout() {
|
|
368
|
+
const readout = this.#readout;
|
|
369
|
+
if (!readout || !canPopover || !this.isConnected) return;
|
|
370
|
+
const isEditing = this.#entry?.isOpen === true;
|
|
371
|
+
readout.text.hidden = isEditing;
|
|
372
|
+
if (!isEditing) readout.text.textContent = this.#valueText();
|
|
373
|
+
readout.bubble.togglePopover(isEditing || this.#isValueShown());
|
|
374
|
+
}
|
|
375
|
+
#reset() {
|
|
376
|
+
this.#commit(this.default ?? NaN);
|
|
377
|
+
}
|
|
378
|
+
#reveal(drag) {
|
|
379
|
+
if (drag.isRevealed || this.#drag !== drag) return;
|
|
380
|
+
drag.isRevealed = true;
|
|
381
|
+
this.#renderReadout();
|
|
382
|
+
}
|
|
383
|
+
#revealForKeys() {
|
|
384
|
+
clearTimeout(this.#keyRevealTimer);
|
|
385
|
+
this.#keyRevealTimer = setTimeout(() => {
|
|
386
|
+
this.#hideForKeys();
|
|
387
|
+
}, keyRevealMs);
|
|
388
|
+
this.#renderReadout();
|
|
389
|
+
}
|
|
390
|
+
#taper() {
|
|
391
|
+
if (this.notched) return void 0;
|
|
392
|
+
const min = this.min;
|
|
393
|
+
const max = this.#max();
|
|
394
|
+
return (this.taper === "log" ? logTaper(min, max) : void 0) ?? skewTaper(min, max, this.midpoint ?? NaN);
|
|
395
|
+
}
|
|
396
|
+
#valueText() {
|
|
397
|
+
return this.#formatValue?.(this.#value) ?? String(this.#value);
|
|
398
|
+
}
|
|
399
|
+
#write(next) {
|
|
400
|
+
if (!Number.isFinite(next)) return;
|
|
401
|
+
this.#isDirty = true;
|
|
402
|
+
const clamped = this.#clamp(next);
|
|
403
|
+
if (clamped === this.#value) return;
|
|
404
|
+
this.#value = clamped;
|
|
405
|
+
this.render();
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
//#endregion
|
|
410
|
+
export { SonicRangeElement };
|
|
@@ -12,6 +12,7 @@ var RangeEntry = class {
|
|
|
12
12
|
#input;
|
|
13
13
|
#isOpen = false;
|
|
14
14
|
#lastPress;
|
|
15
|
+
#openedText = "";
|
|
15
16
|
#options;
|
|
16
17
|
constructor(input, options) {
|
|
17
18
|
this.#input = input;
|
|
@@ -40,7 +41,7 @@ var RangeEntry = class {
|
|
|
40
41
|
this.#isOpen = false;
|
|
41
42
|
this.#input.hidden = true;
|
|
42
43
|
this.#options.toggle(false);
|
|
43
|
-
if (isCommitting) this.#options.commit(this.#input.value);
|
|
44
|
+
if (isCommitting && this.#input.value !== this.#openedText) this.#options.commit(this.#input.value);
|
|
44
45
|
}
|
|
45
46
|
forgetPress() {
|
|
46
47
|
this.#lastPress = void 0;
|
|
@@ -48,7 +49,8 @@ var RangeEntry = class {
|
|
|
48
49
|
open() {
|
|
49
50
|
const input = this.#input;
|
|
50
51
|
this.#isOpen = true;
|
|
51
|
-
|
|
52
|
+
this.#openedText = this.#options.text();
|
|
53
|
+
input.value = this.#openedText;
|
|
52
54
|
input.hidden = false;
|
|
53
55
|
this.#options.toggle(true);
|
|
54
56
|
input.focus();
|
|
@@ -69,5 +71,4 @@ var RangeEntry = class {
|
|
|
69
71
|
};
|
|
70
72
|
|
|
71
73
|
//#endregion
|
|
72
|
-
export { RangeEntry };
|
|
73
|
-
//# sourceMappingURL=range-entry.js.map
|
|
74
|
+
export { RangeEntry };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { template } from "../lib/render.js";
|
|
2
|
+
import { SonicFormElement } from "./form-element.js";
|
|
3
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
4
|
+
|
|
5
|
+
//#region src/elements/segmented.ts
|
|
6
|
+
const renderSegmented = template(`<div class="sonic-segmented" role="radiogroup"></div>`, HTMLDivElement);
|
|
7
|
+
const renderSegment = template(`
|
|
8
|
+
<button class="sonic-segmented-segment" role="radio" type="button">
|
|
9
|
+
<span class="sonic-segmented-cap"></span>
|
|
10
|
+
</button>
|
|
11
|
+
`, HTMLButtonElement);
|
|
12
|
+
const keySteps = /* @__PURE__ */ new Map([
|
|
13
|
+
["ArrowDown", 1],
|
|
14
|
+
["ArrowLeft", -1],
|
|
15
|
+
["ArrowRight", 1],
|
|
16
|
+
["ArrowUp", -1]
|
|
17
|
+
]);
|
|
18
|
+
function isUnder(segment, x, y) {
|
|
19
|
+
const box = segment.getBoundingClientRect();
|
|
20
|
+
return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
|
|
21
|
+
}
|
|
22
|
+
function segmentValue(segment) {
|
|
23
|
+
return segment.querySelector("[data-sonic-value]")?.dataset.sonicValue;
|
|
24
|
+
}
|
|
25
|
+
var SonicSegmented = class extends SonicFormElement {
|
|
26
|
+
static observedAttributes = [...SonicFormElement.observedAttributes, "value"];
|
|
27
|
+
get value() {
|
|
28
|
+
return this.#value;
|
|
29
|
+
}
|
|
30
|
+
set value(next) {
|
|
31
|
+
if (next === this.#value) return;
|
|
32
|
+
this.#value = next;
|
|
33
|
+
this.render();
|
|
34
|
+
}
|
|
35
|
+
#press;
|
|
36
|
+
#segmented = renderSegmented();
|
|
37
|
+
#value = "";
|
|
38
|
+
attributeChangedCallback(name) {
|
|
39
|
+
if (name === "value") this.#value = this.getAttribute("value") ?? "";
|
|
40
|
+
this.render();
|
|
41
|
+
}
|
|
42
|
+
connect(signal) {
|
|
43
|
+
const segmented = this.#segmented;
|
|
44
|
+
this.upgradeProperties("value");
|
|
45
|
+
this.mirrorChildren({
|
|
46
|
+
control: segmented,
|
|
47
|
+
copy: (options) => {
|
|
48
|
+
this.#copyOptions(options);
|
|
49
|
+
},
|
|
50
|
+
isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]")
|
|
51
|
+
}, signal);
|
|
52
|
+
this.checkStyles(segmented, "segmented.css");
|
|
53
|
+
segmented.addEventListener("click", (event) => {
|
|
54
|
+
if (event instanceof PointerEvent && event.pointerType !== "") return;
|
|
55
|
+
const segment = this.#segmentOf(event.target);
|
|
56
|
+
if (segment) this.#select(segment);
|
|
57
|
+
}, { signal });
|
|
58
|
+
this.#bindPress(segmented, signal);
|
|
59
|
+
segmented.addEventListener("keydown", (event) => {
|
|
60
|
+
if (event.defaultPrevented) return;
|
|
61
|
+
const segment = this.#segmentOf(event.target);
|
|
62
|
+
const next = segment && this.#keyTarget(segment, event.key);
|
|
63
|
+
if (!next) return;
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
this.#select(next);
|
|
66
|
+
next.focus();
|
|
67
|
+
}, { signal });
|
|
68
|
+
}
|
|
69
|
+
focusTarget() {
|
|
70
|
+
return this.#segments().find((segment) => segment.tabIndex === 0);
|
|
71
|
+
}
|
|
72
|
+
render() {
|
|
73
|
+
const segmented = this.#segmented;
|
|
74
|
+
const segments = this.#segments();
|
|
75
|
+
const checked = segments.find((segment) => segmentValue(segment) === this.#value);
|
|
76
|
+
const focusable = checked ?? segments[0];
|
|
77
|
+
for (const segment of segments) {
|
|
78
|
+
segment.disabled = this.isDisabled();
|
|
79
|
+
segment.setAttribute("aria-checked", String(segment === checked));
|
|
80
|
+
segment.tabIndex = segment === focusable ? 0 : -1;
|
|
81
|
+
}
|
|
82
|
+
this.forwardNaming(segmented, true);
|
|
83
|
+
this.writeFormValue(checked ? this.#value : null, this.#value);
|
|
84
|
+
}
|
|
85
|
+
restoreState(state) {
|
|
86
|
+
this.value = state;
|
|
87
|
+
}
|
|
88
|
+
#bindPress(segmented, signal) {
|
|
89
|
+
const isOwn = (event) => event.pointerId === this.#press?.pointerId;
|
|
90
|
+
const release = (event) => {
|
|
91
|
+
if (isOwn(event)) this.#hold(void 0);
|
|
92
|
+
};
|
|
93
|
+
segmented.addEventListener("pointerdown", (event) => {
|
|
94
|
+
const segment = this.#segmentOf(event.target);
|
|
95
|
+
if (!segment || event.button !== 0 || this.isDisabled()) return;
|
|
96
|
+
segmented.setPointerCapture(event.pointerId);
|
|
97
|
+
this.#hold({
|
|
98
|
+
pointerId: event.pointerId,
|
|
99
|
+
segment
|
|
100
|
+
});
|
|
101
|
+
}, { signal });
|
|
102
|
+
segmented.addEventListener("pointermove", (event) => {
|
|
103
|
+
if (!isOwn(event)) return;
|
|
104
|
+
this.#hold({
|
|
105
|
+
pointerId: event.pointerId,
|
|
106
|
+
segment: this.#segments().find((segment) => isUnder(segment, event.clientX, event.clientY))
|
|
107
|
+
});
|
|
108
|
+
}, { signal });
|
|
109
|
+
segmented.addEventListener("pointerup", (event) => {
|
|
110
|
+
const segment = isOwn(event) ? this.#press?.segment : void 0;
|
|
111
|
+
release(event);
|
|
112
|
+
if (!segment || this.isDisabled()) return;
|
|
113
|
+
this.#select(segment);
|
|
114
|
+
if (segmented.matches(":focus-within")) segment.focus();
|
|
115
|
+
}, { signal });
|
|
116
|
+
segmented.addEventListener("pointercancel", release, { signal });
|
|
117
|
+
segmented.addEventListener("lostpointercapture", release, { signal });
|
|
118
|
+
signal.addEventListener("abort", () => {
|
|
119
|
+
this.#hold(void 0);
|
|
120
|
+
}, { once: true });
|
|
121
|
+
}
|
|
122
|
+
#copyOptions(options) {
|
|
123
|
+
const segmented = this.#segmented;
|
|
124
|
+
const segments = this.#segments();
|
|
125
|
+
const focused = this.#segmentOf(segmented.querySelector(":scope > :focus"));
|
|
126
|
+
const focusedValue = focused && segmentValue(focused);
|
|
127
|
+
for (const [index, option] of options.entries()) {
|
|
128
|
+
let segment = segments[index];
|
|
129
|
+
if (!segment) {
|
|
130
|
+
segment = renderSegment();
|
|
131
|
+
segmented.append(segment);
|
|
132
|
+
}
|
|
133
|
+
segment.firstElementChild?.replaceChildren(copyNode(option));
|
|
134
|
+
}
|
|
135
|
+
for (const segment of segments.slice(options.length)) segment.remove();
|
|
136
|
+
this.render();
|
|
137
|
+
if (focused) this.#refocus(focused, focusedValue);
|
|
138
|
+
}
|
|
139
|
+
#hold(press) {
|
|
140
|
+
this.#press = press;
|
|
141
|
+
for (const segment of this.#segments()) segment.toggleAttribute("data-sonic-pressed", segment === press?.segment);
|
|
142
|
+
}
|
|
143
|
+
#keyTarget(segment, key) {
|
|
144
|
+
const segments = this.#segments();
|
|
145
|
+
if (key === "Home") return segments[0];
|
|
146
|
+
if (key === "End") return segments.at(-1);
|
|
147
|
+
const step = keySteps.get(key);
|
|
148
|
+
if (step === void 0) return void 0;
|
|
149
|
+
return segments[(segments.indexOf(segment) + step + segments.length) % segments.length];
|
|
150
|
+
}
|
|
151
|
+
#refocus(focused, value) {
|
|
152
|
+
const segments = this.#segments();
|
|
153
|
+
const next = segments.find((segment) => segmentValue(segment) === value) ?? segments.find((segment) => segment.tabIndex === 0);
|
|
154
|
+
if (next !== focused) next?.focus();
|
|
155
|
+
}
|
|
156
|
+
#segmentOf(target) {
|
|
157
|
+
if (!(target instanceof Element)) return void 0;
|
|
158
|
+
const segment = target.closest(".sonic-segmented-segment");
|
|
159
|
+
return segment instanceof HTMLButtonElement && segment.parentElement === this.#segmented ? segment : void 0;
|
|
160
|
+
}
|
|
161
|
+
#segments() {
|
|
162
|
+
return [...this.#segmented.children].filter((child) => child instanceof HTMLButtonElement);
|
|
163
|
+
}
|
|
164
|
+
#select(segment) {
|
|
165
|
+
const next = segmentValue(segment);
|
|
166
|
+
if (next === void 0 || next === this.#value) return;
|
|
167
|
+
this.value = next;
|
|
168
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
export { SonicSegmented };
|
|
@@ -22,7 +22,23 @@ const renderSlider = template(`
|
|
|
22
22
|
`, HTMLDivElement);
|
|
23
23
|
var SonicSlider = class extends SonicRangeElement {
|
|
24
24
|
static observedAttributes = [...SonicRangeElement.observedAttributes, "orientation"];
|
|
25
|
+
get groovePress() {
|
|
26
|
+
return this.getAttribute("groove-press") === "none" ? "none" : "jump";
|
|
27
|
+
}
|
|
28
|
+
set groovePress(gesture) {
|
|
29
|
+
this.reflect("groove-press", gesture);
|
|
30
|
+
}
|
|
31
|
+
get orientation() {
|
|
32
|
+
return this.getAttribute("orientation") === "vertical" ? "vertical" : "horizontal";
|
|
33
|
+
}
|
|
34
|
+
set orientation(direction) {
|
|
35
|
+
this.reflect("orientation", direction);
|
|
36
|
+
}
|
|
25
37
|
#slider = renderSlider();
|
|
38
|
+
connectedCallback() {
|
|
39
|
+
this.upgradeProperties("groovePress", "orientation");
|
|
40
|
+
super.connectedCallback();
|
|
41
|
+
}
|
|
26
42
|
connect(signal) {
|
|
27
43
|
const slider = this.#slider;
|
|
28
44
|
const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
|
|
@@ -32,11 +48,14 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
32
48
|
this.bindGestures(slider, signal, (event) => {
|
|
33
49
|
const axis = this.#axis(slider, cap);
|
|
34
50
|
if (event.target instanceof Node && cap.contains(event.target)) return axis;
|
|
35
|
-
if (this.
|
|
51
|
+
if (this.groovePress === "none") return;
|
|
36
52
|
this.input(this.valueAt((axis.position(event) - axis.startPx) / axis.travelPx));
|
|
37
53
|
return axis;
|
|
38
54
|
});
|
|
39
55
|
}
|
|
56
|
+
focusTarget() {
|
|
57
|
+
return this.#slider;
|
|
58
|
+
}
|
|
40
59
|
render() {
|
|
41
60
|
const slider = this.#slider;
|
|
42
61
|
const positions = this.positions();
|
|
@@ -44,12 +63,12 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
44
63
|
slider.style.setProperty("--_sonic-slider-origin", String(this.originFraction()));
|
|
45
64
|
if (positions === void 0) slider.style.removeProperty("--_sonic-slider-positions");
|
|
46
65
|
else slider.style.setProperty("--_sonic-slider-positions", String(positions));
|
|
47
|
-
this.renderAria(slider, this
|
|
66
|
+
this.renderAria(slider, this.orientation);
|
|
48
67
|
}
|
|
49
68
|
#axis(slider, cap) {
|
|
50
69
|
const track = slider.getBoundingClientRect();
|
|
51
70
|
const capBox = cap.getBoundingClientRect();
|
|
52
|
-
if (this
|
|
71
|
+
if (this.orientation === "vertical") return {
|
|
53
72
|
position: (event) => -event.clientY,
|
|
54
73
|
startPx: capBox.height / 2 - track.bottom,
|
|
55
74
|
travelPx: Math.max(1, track.height - capBox.height)
|
|
@@ -60,11 +79,7 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
60
79
|
travelPx: Math.max(1, track.width - capBox.width)
|
|
61
80
|
};
|
|
62
81
|
}
|
|
63
|
-
#isVertical() {
|
|
64
|
-
return this.getAttribute("orientation") === "vertical";
|
|
65
|
-
}
|
|
66
82
|
};
|
|
67
83
|
|
|
68
84
|
//#endregion
|
|
69
|
-
export { SonicSlider };
|
|
70
|
-
//# sourceMappingURL=slider.js.map
|
|
85
|
+
export { SonicSlider };
|