@xsynaptic/sonic-ui 0.2.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 +2 -2
- package/dist/default/define/number.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/dial.js +19 -7
- package/dist/default/elements/number.js +46 -0
- package/dist/default/elements/range-element.js +46 -23
- package/dist/default/elements/slider.js +16 -2
- package/dist/default/index.js +2 -1
- package/dist/default/lib/taper.js +4 -1
- 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.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/dial.d.ts +3 -0
- package/dist/dev/elements/dial.js +19 -7
- package/dist/dev/elements/dial.js.map +1 -1
- 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 +4 -0
- package/dist/dev/elements/range-element.js +46 -23
- package/dist/dev/elements/range-element.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +2 -0
- package/dist/dev/elements/slider.js +16 -2
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/index.d.ts +2 -1
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/read-px-property.js.map +1 -1
- package/dist/dev/lib/taper.js +4 -1
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/styles/controls.css +2 -0
- package/dist/styles/dial.css +37 -3
- package/dist/styles/key.css +41 -1
- package/dist/styles/led.css +75 -0
- package/dist/styles/material.css +100 -19
- package/dist/styles/meter.css +19 -3
- package/dist/styles/number.css +81 -0
- package/dist/styles/screen.css +6 -39
- package/dist/styles/segmented.css +21 -0
- package/dist/styles/slider.css +52 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
-
Native custom elements for audio interfaces
|
|
3
|
+
Native custom elements for audio interfaces. Each control renders in the light DOM, drawn in CSS, skinned with `--sonic-*` custom properties. No framework, no runtime dependencies.
|
|
4
4
|
|
|
5
5
|
Early and unstable: any 0.x release may break the API.
|
|
6
6
|
|
|
@@ -34,7 +34,7 @@ The styles sit in `@layer sonic`, so list it after your resets. A skin (`slate`,
|
|
|
34
34
|
- **Attributes and properties** reflect each other: `dial.max = 200` writes `max="200"`. `value` and the key's `pressed` are the live state and never write their attribute, as on native inputs.
|
|
35
35
|
- **Events:** `change` when a control settles, and `input` while a dial or slider moves; both bubble.
|
|
36
36
|
- **Formatting:** `formatValue` sets the text the readout and assistive technology read, and `parseValue` reads typed entry back. `formatPercent` and `parsePercent` from the package root show 0 to 1 as a percentage.
|
|
37
|
-
- **Forms:**
|
|
37
|
+
- **Forms:** controls that hold a value submit under their `name`, reset, take a `<label>`, and follow a disabled `<fieldset>`, as native inputs do.
|
|
38
38
|
|
|
39
39
|
## Browsers
|
|
40
40
|
|
package/dist/default/define.js
CHANGED
|
@@ -25,7 +25,17 @@ const renderDial = template(`
|
|
|
25
25
|
`, HTMLDivElement);
|
|
26
26
|
const fallbackTravelPx = 160;
|
|
27
27
|
var SonicDial = class extends SonicRangeElement {
|
|
28
|
-
static observedAttributes = [
|
|
28
|
+
static observedAttributes = [
|
|
29
|
+
...SonicRangeElement.observedAttributes,
|
|
30
|
+
"endless",
|
|
31
|
+
"modulation"
|
|
32
|
+
];
|
|
33
|
+
get endless() {
|
|
34
|
+
return this.hasAttribute("endless");
|
|
35
|
+
}
|
|
36
|
+
set endless(isEndless) {
|
|
37
|
+
this.reflect("endless", isEndless);
|
|
38
|
+
}
|
|
29
39
|
get modulation() {
|
|
30
40
|
return this.numberAttribute("modulation", 0);
|
|
31
41
|
}
|
|
@@ -34,7 +44,7 @@ var SonicDial = class extends SonicRangeElement {
|
|
|
34
44
|
}
|
|
35
45
|
#dial = renderDial();
|
|
36
46
|
connectedCallback() {
|
|
37
|
-
this.upgradeProperties("modulation");
|
|
47
|
+
this.upgradeProperties("endless", "modulation");
|
|
38
48
|
super.connectedCallback();
|
|
39
49
|
}
|
|
40
50
|
connect(signal) {
|
|
@@ -53,16 +63,18 @@ var SonicDial = class extends SonicRangeElement {
|
|
|
53
63
|
render() {
|
|
54
64
|
const dial = this.#dial;
|
|
55
65
|
const positions = this.positions();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
dial.style.setProperty("--_sonic-dial-value", String(value));
|
|
66
|
+
const [modulationFrom, modulationTo] = this.modulationFractions(this.endless ? 0 : this.modulation);
|
|
67
|
+
dial.style.setProperty("--_sonic-dial-value", String(this.fraction(this.value)));
|
|
59
68
|
dial.style.setProperty("--_sonic-dial-origin", String(this.originFraction()));
|
|
60
|
-
dial.style.setProperty("--_sonic-dial-modulation-from", String(
|
|
61
|
-
dial.style.setProperty("--_sonic-dial-modulation-to", String(
|
|
69
|
+
dial.style.setProperty("--_sonic-dial-modulation-from", String(modulationFrom));
|
|
70
|
+
dial.style.setProperty("--_sonic-dial-modulation-to", String(modulationTo));
|
|
62
71
|
if (positions === void 0) dial.style.removeProperty("--_sonic-dial-positions");
|
|
63
72
|
else dial.style.setProperty("--_sonic-dial-positions", String(positions));
|
|
64
73
|
this.renderAria(dial);
|
|
65
74
|
}
|
|
75
|
+
wraps() {
|
|
76
|
+
return this.endless;
|
|
77
|
+
}
|
|
66
78
|
};
|
|
67
79
|
|
|
68
80
|
//#endregion
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
3
|
+
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
|
|
5
|
+
//#region src/elements/number.ts
|
|
6
|
+
const renderNumber = template(`
|
|
7
|
+
<div class="sonic-number" role="spinbutton" tabindex="0">
|
|
8
|
+
<span aria-hidden="true" class="sonic-number-value"></span>
|
|
9
|
+
<input
|
|
10
|
+
autocomplete="off"
|
|
11
|
+
class="sonic-number-entry"
|
|
12
|
+
enterkeyhint="done"
|
|
13
|
+
hidden
|
|
14
|
+
spellcheck="false"
|
|
15
|
+
type="text"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
`, HTMLDivElement);
|
|
19
|
+
const fallbackTravelPx = 160;
|
|
20
|
+
var SonicNumber = class extends SonicRangeElement {
|
|
21
|
+
#number = renderNumber();
|
|
22
|
+
#digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
|
|
23
|
+
connect(signal) {
|
|
24
|
+
const number = this.#number;
|
|
25
|
+
this.appendOnce(number);
|
|
26
|
+
this.render();
|
|
27
|
+
this.checkStyles(number, "number.css");
|
|
28
|
+
this.bindGestures(number, signal, () => ({
|
|
29
|
+
position: (event) => -event.clientY,
|
|
30
|
+
travelPx: readPxProperty(getComputedStyle(number), "--_sonic-number-travel", fallbackTravelPx)
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
controlRole() {
|
|
34
|
+
return "spinbutton";
|
|
35
|
+
}
|
|
36
|
+
focusTarget() {
|
|
37
|
+
return this.#number;
|
|
38
|
+
}
|
|
39
|
+
render() {
|
|
40
|
+
this.#digits.textContent = this.valueText();
|
|
41
|
+
this.renderAria(this.#number);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { SonicNumber };
|
|
@@ -2,7 +2,7 @@ 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 { clampUnit, linearTaper, logTaper, skewTaper } from "../lib/taper.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;
|
|
@@ -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",
|
|
@@ -192,7 +192,8 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
192
192
|
if (event.pointerId !== drag?.pointerId) return;
|
|
193
193
|
const position = drag.position(event);
|
|
194
194
|
const scale = event.shiftKey ? fineScale : 1;
|
|
195
|
-
|
|
195
|
+
const fraction = drag.rawFraction + (position - drag.lastPosition) / drag.travelPx * scale;
|
|
196
|
+
drag.rawFraction = this.wraps() ? wrapUnit(fraction) : clampUnit(fraction);
|
|
196
197
|
drag.lastPosition = position;
|
|
197
198
|
if (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);
|
|
198
199
|
this.input(this.valueAt(drag.rawFraction));
|
|
@@ -219,7 +220,15 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
219
220
|
}, { once: true });
|
|
220
221
|
}
|
|
221
222
|
clampRange(next) {
|
|
222
|
-
|
|
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";
|
|
223
232
|
}
|
|
224
233
|
fraction(value) {
|
|
225
234
|
return (this.#taper() ?? linearTaper(this.min, this.#max())).position(value);
|
|
@@ -234,11 +243,16 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
234
243
|
}));
|
|
235
244
|
return true;
|
|
236
245
|
}
|
|
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)];
|
|
250
|
+
}
|
|
237
251
|
originFraction() {
|
|
238
252
|
return this.fraction(this.origin ?? this.min);
|
|
239
253
|
}
|
|
240
254
|
positions() {
|
|
241
|
-
const positions = Math.round(this.range() / this.step) + 1;
|
|
255
|
+
const positions = Math.round(this.range() / this.step) + (this.wraps() ? 0 : 1);
|
|
242
256
|
if (!this.notched || !Number.isFinite(positions) || positions < 2) return;
|
|
243
257
|
return positions;
|
|
244
258
|
}
|
|
@@ -248,12 +262,12 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
248
262
|
renderAria(control, orientation) {
|
|
249
263
|
this.writeFormValue(String(this.#value), String(this.#value));
|
|
250
264
|
if (this.#entry?.isOpen) {
|
|
251
|
-
for (const name of
|
|
265
|
+
for (const name of roleAttributes) control.removeAttribute(name);
|
|
252
266
|
this.forwardNaming(control, false);
|
|
253
267
|
this.#renderReadout();
|
|
254
268
|
return;
|
|
255
269
|
}
|
|
256
|
-
control.setAttribute("role",
|
|
270
|
+
control.setAttribute("role", this.controlRole());
|
|
257
271
|
control.setAttribute("aria-valuemin", String(this.min));
|
|
258
272
|
control.setAttribute("aria-valuemax", String(this.#max()));
|
|
259
273
|
control.setAttribute("aria-valuenow", String(this.#value));
|
|
@@ -269,22 +283,30 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
269
283
|
valueAt(fraction) {
|
|
270
284
|
return (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);
|
|
271
285
|
}
|
|
286
|
+
valueText() {
|
|
287
|
+
return this.#formatValue?.(this.#value) ?? String(this.#value);
|
|
288
|
+
}
|
|
289
|
+
wraps() {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
272
292
|
#bindEntry(control, signal) {
|
|
273
293
|
const input = requireChild(control, "input", HTMLInputElement);
|
|
274
|
-
const bubble =
|
|
275
|
-
const anchor = `--sonic-readout-${this.#instance}`;
|
|
294
|
+
const bubble = control.querySelector("[popover]");
|
|
276
295
|
input.id = `sonic-entry-${this.#instance}`;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
}
|
|
283
305
|
const entry = new RangeEntry(input, {
|
|
284
306
|
commit: (text) => {
|
|
285
307
|
this.#commit((this.#parseValue ?? Number.parseFloat)(text));
|
|
286
308
|
},
|
|
287
|
-
text: () => this.#parseValue ? this
|
|
309
|
+
text: () => this.#parseValue ? this.valueText() : String(this.#value),
|
|
288
310
|
toggle: (isOpen) => {
|
|
289
311
|
if (isOpen) this.forwardNaming(input, true);
|
|
290
312
|
this.toggleState("editing", isOpen);
|
|
@@ -322,6 +344,10 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
322
344
|
#commit(next) {
|
|
323
345
|
if (this.input(next)) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
324
346
|
}
|
|
347
|
+
#end(step) {
|
|
348
|
+
if (!this.wraps()) return this.#max();
|
|
349
|
+
return this.min + (Math.ceil(this.range() / step) - 1) * step;
|
|
350
|
+
}
|
|
325
351
|
#endDrag() {
|
|
326
352
|
const drag = this.#drag;
|
|
327
353
|
if (!drag) return;
|
|
@@ -342,11 +368,11 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
342
368
|
}
|
|
343
369
|
#keyTarget(key) {
|
|
344
370
|
if (key === "Home") return this.min;
|
|
345
|
-
|
|
371
|
+
const step = this.step > 0 ? this.step : this.range() / 100;
|
|
372
|
+
if (key === "End") return this.#end(step);
|
|
346
373
|
if (resetKeys.has(key)) return this.default;
|
|
347
374
|
const steps = keySteps.get(key);
|
|
348
375
|
if (steps === void 0) return void 0;
|
|
349
|
-
const step = this.step > 0 ? this.step : this.range() / 100;
|
|
350
376
|
const taper = this.#taper();
|
|
351
377
|
if (!taper) return this.#value + steps * step;
|
|
352
378
|
const next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));
|
|
@@ -369,7 +395,7 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
369
395
|
if (!readout || !canPopover || !this.isConnected) return;
|
|
370
396
|
const isEditing = this.#entry?.isOpen === true;
|
|
371
397
|
readout.text.hidden = isEditing;
|
|
372
|
-
if (!isEditing) readout.text.textContent = this
|
|
398
|
+
if (!isEditing) readout.text.textContent = this.valueText();
|
|
373
399
|
readout.bubble.togglePopover(isEditing || this.#isValueShown());
|
|
374
400
|
}
|
|
375
401
|
#reset() {
|
|
@@ -388,14 +414,11 @@ var SonicRangeElement = class extends SonicFormElement {
|
|
|
388
414
|
this.#renderReadout();
|
|
389
415
|
}
|
|
390
416
|
#taper() {
|
|
391
|
-
if (this.notched) return void 0;
|
|
417
|
+
if (this.notched || this.wraps()) return void 0;
|
|
392
418
|
const min = this.min;
|
|
393
419
|
const max = this.#max();
|
|
394
420
|
return (this.taper === "log" ? logTaper(min, max) : void 0) ?? skewTaper(min, max, this.midpoint ?? NaN);
|
|
395
421
|
}
|
|
396
|
-
#valueText() {
|
|
397
|
-
return this.#formatValue?.(this.#value) ?? String(this.#value);
|
|
398
|
-
}
|
|
399
422
|
#write(next) {
|
|
400
423
|
if (!Number.isFinite(next)) return;
|
|
401
424
|
this.#isDirty = true;
|
|
@@ -6,6 +6,7 @@ const renderSlider = template(`
|
|
|
6
6
|
<div class="sonic-slider" role="slider" tabindex="0">
|
|
7
7
|
<div class="sonic-slider-marks"></div>
|
|
8
8
|
<div class="sonic-slider-groove"></div>
|
|
9
|
+
<div class="sonic-slider-modulation"></div>
|
|
9
10
|
<div class="sonic-slider-cap"></div>
|
|
10
11
|
<div class="sonic-slider-readout" popover="manual">
|
|
11
12
|
<span></span>
|
|
@@ -21,13 +22,23 @@ const renderSlider = template(`
|
|
|
21
22
|
</div>
|
|
22
23
|
`, HTMLDivElement);
|
|
23
24
|
var SonicSlider = class extends SonicRangeElement {
|
|
24
|
-
static observedAttributes = [
|
|
25
|
+
static observedAttributes = [
|
|
26
|
+
...SonicRangeElement.observedAttributes,
|
|
27
|
+
"modulation",
|
|
28
|
+
"orientation"
|
|
29
|
+
];
|
|
25
30
|
get groovePress() {
|
|
26
31
|
return this.getAttribute("groove-press") === "none" ? "none" : "jump";
|
|
27
32
|
}
|
|
28
33
|
set groovePress(gesture) {
|
|
29
34
|
this.reflect("groove-press", gesture);
|
|
30
35
|
}
|
|
36
|
+
get modulation() {
|
|
37
|
+
return this.numberAttribute("modulation", 0);
|
|
38
|
+
}
|
|
39
|
+
set modulation(amount) {
|
|
40
|
+
this.reflect("modulation", amount);
|
|
41
|
+
}
|
|
31
42
|
get orientation() {
|
|
32
43
|
return this.getAttribute("orientation") === "vertical" ? "vertical" : "horizontal";
|
|
33
44
|
}
|
|
@@ -36,7 +47,7 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
36
47
|
}
|
|
37
48
|
#slider = renderSlider();
|
|
38
49
|
connectedCallback() {
|
|
39
|
-
this.upgradeProperties("groovePress", "orientation");
|
|
50
|
+
this.upgradeProperties("groovePress", "modulation", "orientation");
|
|
40
51
|
super.connectedCallback();
|
|
41
52
|
}
|
|
42
53
|
connect(signal) {
|
|
@@ -59,8 +70,11 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
59
70
|
render() {
|
|
60
71
|
const slider = this.#slider;
|
|
61
72
|
const positions = this.positions();
|
|
73
|
+
const [modulationFrom, modulationTo] = this.modulationFractions(this.modulation);
|
|
62
74
|
slider.style.setProperty("--_sonic-slider-value", String(this.fraction(this.value)));
|
|
63
75
|
slider.style.setProperty("--_sonic-slider-origin", String(this.originFraction()));
|
|
76
|
+
slider.style.setProperty("--_sonic-slider-modulation-from", String(modulationFrom));
|
|
77
|
+
slider.style.setProperty("--_sonic-slider-modulation-to", String(modulationTo));
|
|
64
78
|
if (positions === void 0) slider.style.removeProperty("--_sonic-slider-positions");
|
|
65
79
|
else slider.style.setProperty("--_sonic-slider-positions", String(positions));
|
|
66
80
|
this.renderAria(slider, this.orientation);
|
package/dist/default/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SonicDial } from "./elements/dial.js";
|
|
2
2
|
import { SonicKey } from "./elements/key.js";
|
|
3
3
|
import { SonicMeter } from "./elements/meter.js";
|
|
4
|
+
import { SonicNumber } from "./elements/number.js";
|
|
4
5
|
import { SonicSegmented } from "./elements/segmented.js";
|
|
5
6
|
import { SonicSlider } from "./elements/slider.js";
|
|
6
7
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
7
8
|
|
|
8
|
-
export { SonicDial, SonicKey, SonicMeter, SonicSegmented, SonicSlider, formatPercent, parsePercent };
|
|
9
|
+
export { SonicDial, SonicKey, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, formatPercent, parsePercent };
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
function clampUnit(fraction) {
|
|
3
3
|
return Math.min(1, Math.max(0, fraction));
|
|
4
4
|
}
|
|
5
|
+
function wrapUnit(fraction) {
|
|
6
|
+
return fraction - Math.floor(fraction);
|
|
7
|
+
}
|
|
5
8
|
function linearTaper(min, max) {
|
|
6
9
|
const range = max - min;
|
|
7
10
|
return {
|
|
@@ -29,4 +32,4 @@ function skewTaper(min, max, midpoint) {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
//#endregion
|
|
32
|
-
export { clampUnit, linearTaper, logTaper, skewTaper };
|
|
35
|
+
export { clampUnit, linearTaper, logTaper, skewTaper, wrapUnit };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../src/define/number.ts"],"sourcesContent":["import { defineOnce } from '#elements/define-once.ts';\nimport { SonicNumber } from '#elements/number.ts';\n\ndefineOnce('sonic-number', SonicNumber);\n\n// Type-only, so the tag map augmentation ships without a runtime export\n/** @public */\nexport type { SonicNumber } from '#elements/number.ts';\n"],"mappings":";;;;AAGA,WAAW,gBAAgB,WAAW"}
|
package/dist/dev/define.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { SonicKey } from "./elements/key.js";
|
|
|
4
4
|
import "./define/key.js";
|
|
5
5
|
import { SonicMeter } from "./elements/meter.js";
|
|
6
6
|
import "./define/meter.js";
|
|
7
|
+
import { SonicNumber } from "./elements/number.js";
|
|
8
|
+
import "./define/number.js";
|
|
7
9
|
import { SonicSegmented } from "./elements/segmented.js";
|
|
8
10
|
import "./define/segmented.js";
|
|
9
11
|
import { SonicSlider } from "./elements/slider.js";
|
|
10
12
|
import "./define/slider.js";
|
|
11
|
-
export type { SonicDial, SonicKey, SonicMeter, SonicSegmented, SonicSlider };
|
|
13
|
+
export type { SonicDial, SonicKey, SonicMeter, SonicNumber, SonicSegmented, SonicSlider };
|
package/dist/dev/define.js
CHANGED
|
@@ -8,12 +8,15 @@ declare global {
|
|
|
8
8
|
export declare class SonicDial extends SonicRangeElement {
|
|
9
9
|
#private;
|
|
10
10
|
static readonly observedAttributes: string[];
|
|
11
|
+
get endless(): boolean;
|
|
12
|
+
set endless(isEndless: boolean);
|
|
11
13
|
get modulation(): number;
|
|
12
14
|
set modulation(amount: number | undefined);
|
|
13
15
|
connectedCallback(): void;
|
|
14
16
|
protected connect(signal: AbortSignal): void;
|
|
15
17
|
protected focusTarget(): HTMLElement;
|
|
16
18
|
protected render(): void;
|
|
19
|
+
protected wraps(): boolean;
|
|
17
20
|
}
|
|
18
21
|
//#endregion
|
|
19
22
|
//# sourceMappingURL=dial.d.ts.map
|
|
@@ -25,7 +25,17 @@ const renderDial = template(`
|
|
|
25
25
|
`, HTMLDivElement);
|
|
26
26
|
const fallbackTravelPx = 160;
|
|
27
27
|
var SonicDial = class extends SonicRangeElement {
|
|
28
|
-
static observedAttributes = [
|
|
28
|
+
static observedAttributes = [
|
|
29
|
+
...SonicRangeElement.observedAttributes,
|
|
30
|
+
"endless",
|
|
31
|
+
"modulation"
|
|
32
|
+
];
|
|
33
|
+
get endless() {
|
|
34
|
+
return this.hasAttribute("endless");
|
|
35
|
+
}
|
|
36
|
+
set endless(isEndless) {
|
|
37
|
+
this.reflect("endless", isEndless);
|
|
38
|
+
}
|
|
29
39
|
get modulation() {
|
|
30
40
|
return this.numberAttribute("modulation", 0);
|
|
31
41
|
}
|
|
@@ -34,7 +44,7 @@ var SonicDial = class extends SonicRangeElement {
|
|
|
34
44
|
}
|
|
35
45
|
#dial = renderDial();
|
|
36
46
|
connectedCallback() {
|
|
37
|
-
this.upgradeProperties("modulation");
|
|
47
|
+
this.upgradeProperties("endless", "modulation");
|
|
38
48
|
super.connectedCallback();
|
|
39
49
|
}
|
|
40
50
|
connect(signal) {
|
|
@@ -53,16 +63,18 @@ var SonicDial = class extends SonicRangeElement {
|
|
|
53
63
|
render() {
|
|
54
64
|
const dial = this.#dial;
|
|
55
65
|
const positions = this.positions();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
dial.style.setProperty("--_sonic-dial-value", String(value));
|
|
66
|
+
const [modulationFrom, modulationTo] = this.modulationFractions(this.endless ? 0 : this.modulation);
|
|
67
|
+
dial.style.setProperty("--_sonic-dial-value", String(this.fraction(this.value)));
|
|
59
68
|
dial.style.setProperty("--_sonic-dial-origin", String(this.originFraction()));
|
|
60
|
-
dial.style.setProperty("--_sonic-dial-modulation-from", String(
|
|
61
|
-
dial.style.setProperty("--_sonic-dial-modulation-to", String(
|
|
69
|
+
dial.style.setProperty("--_sonic-dial-modulation-from", String(modulationFrom));
|
|
70
|
+
dial.style.setProperty("--_sonic-dial-modulation-to", String(modulationTo));
|
|
62
71
|
if (positions === void 0) dial.style.removeProperty("--_sonic-dial-positions");
|
|
63
72
|
else dial.style.setProperty("--_sonic-dial-positions", String(positions));
|
|
64
73
|
this.renderAria(dial);
|
|
65
74
|
}
|
|
75
|
+
wraps() {
|
|
76
|
+
return this.endless;
|
|
77
|
+
}
|
|
66
78
|
};
|
|
67
79
|
|
|
68
80
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { SonicRangeElement } from '#elements/range-element.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-pointer\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst fallbackTravelPx = 160;\n\nexport class SonicDial extends SonicRangeElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRangeElement.observedAttributes,\n\t\t'modulation',\n\t];\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\treadonly #dial = renderDial();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tthis.appendOnce(dial);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\t// Once per gesture; a computed style read per move forces a style recalc\n\t\t\ttravelPx: readPxProperty(getComputedStyle(dial), '--_sonic-dial-travel', fallbackTravelPx),\n\t\t}));\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected render(): void {\n\t\tconst dial = this.#dial;\n\t\tconst positions = this.positions();\n\t\tconst
|
|
1
|
+
{"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { SonicRangeElement } from '#elements/range-element.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-pointer\"></div>\n\t\t\t<div class=\"sonic-dial-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-dial-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst fallbackTravelPx = 160;\n\nexport class SonicDial extends SonicRangeElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRangeElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\treadonly #dial = renderDial();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tthis.appendOnce(dial);\n\t\tthis.render();\n\t\tthis.checkStyles(dial, 'dial.css');\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\t// Once per gesture; a computed style read per move forces a style recalc\n\t\t\ttravelPx: readPxProperty(getComputedStyle(dial), '--_sonic-dial-travel', fallbackTravelPx),\n\t\t}));\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected render(): void {\n\t\tconst dial = this.#dial;\n\t\tconst positions = this.positions();\n\t\tconst [modulationFrom, modulationTo] = this.modulationFractions(\n\t\t\tthis.endless ? 0 : this.modulation,\n\t\t);\n\n\t\tdial.style.setProperty('--_sonic-dial-value', String(this.fraction(this.value)));\n\t\tdial.style.setProperty('--_sonic-dial-origin', String(this.originFraction()));\n\t\tdial.style.setProperty('--_sonic-dial-modulation-from', String(modulationFrom));\n\t\tdial.style.setProperty('--_sonic-dial-modulation-to', String(modulationTo));\n\t\tif (positions === undefined) dial.style.removeProperty('--_sonic-dial-positions');\n\t\telse dial.style.setProperty('--_sonic-dial-positions', String(positions));\n\t\tthis.renderAria(dial);\n\t}\n\n\tprotected override wraps(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;AAUA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;IAoBX,cACD;AAEA,MAAM,mBAAmB;AAEzB,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;CAEA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,YAAY;EAC9C,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,KAAK,WAAW,IAAI;EACpB,KAAK,OAAO;EACZ,KAAK,YAAY,MAAM,UAAU;EACjC,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAE5B,UAAU,eAAe,iBAAiB,IAAI,GAAG,wBAAwB,gBAAgB;EAC1F,EAAE;CACH;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAU,SAAe;EACxB,MAAM,OAAO,KAAK;EAClB,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,CAAC,gBAAgB,gBAAgB,KAAK,oBAC3C,KAAK,UAAU,IAAI,KAAK,UACzB;EAEA,KAAK,MAAM,YAAY,uBAAuB,OAAO,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC;EAC/E,KAAK,MAAM,YAAY,wBAAwB,OAAO,KAAK,eAAe,CAAC,CAAC;EAC5E,KAAK,MAAM,YAAY,iCAAiC,OAAO,cAAc,CAAC;EAC9E,KAAK,MAAM,YAAY,+BAA+B,OAAO,YAAY,CAAC;EAC1E,IAAI,cAAc,QAAW,KAAK,MAAM,eAAe,yBAAyB;OAC3E,KAAK,MAAM,YAAY,2BAA2B,OAAO,SAAS,CAAC;EACxE,KAAK,WAAW,IAAI;CACrB;CAEA,AAAmB,QAAiB;EACnC,OAAO,KAAK;CACb;AACD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
2
|
+
//#region src/elements/number.d.ts
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'sonic-number': SonicNumber;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare class SonicNumber extends SonicRangeElement {
|
|
9
|
+
#private;
|
|
10
|
+
protected connect(signal: AbortSignal): void;
|
|
11
|
+
protected controlRole(): 'spinbutton';
|
|
12
|
+
protected focusTarget(): HTMLElement;
|
|
13
|
+
protected render(): void;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//# sourceMappingURL=number.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { SonicRangeElement } from "./range-element.js";
|
|
3
|
+
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
|
|
5
|
+
//#region src/elements/number.ts
|
|
6
|
+
const renderNumber = template(`
|
|
7
|
+
<div class="sonic-number" role="spinbutton" tabindex="0">
|
|
8
|
+
<span aria-hidden="true" class="sonic-number-value"></span>
|
|
9
|
+
<input
|
|
10
|
+
autocomplete="off"
|
|
11
|
+
class="sonic-number-entry"
|
|
12
|
+
enterkeyhint="done"
|
|
13
|
+
hidden
|
|
14
|
+
spellcheck="false"
|
|
15
|
+
type="text"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
`, HTMLDivElement);
|
|
19
|
+
const fallbackTravelPx = 160;
|
|
20
|
+
var SonicNumber = class extends SonicRangeElement {
|
|
21
|
+
#number = renderNumber();
|
|
22
|
+
#digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
|
|
23
|
+
connect(signal) {
|
|
24
|
+
const number = this.#number;
|
|
25
|
+
this.appendOnce(number);
|
|
26
|
+
this.render();
|
|
27
|
+
this.checkStyles(number, "number.css");
|
|
28
|
+
this.bindGestures(number, signal, () => ({
|
|
29
|
+
position: (event) => -event.clientY,
|
|
30
|
+
travelPx: readPxProperty(getComputedStyle(number), "--_sonic-number-travel", fallbackTravelPx)
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
controlRole() {
|
|
34
|
+
return "spinbutton";
|
|
35
|
+
}
|
|
36
|
+
focusTarget() {
|
|
37
|
+
return this.#number;
|
|
38
|
+
}
|
|
39
|
+
render() {
|
|
40
|
+
this.#digits.textContent = this.valueText();
|
|
41
|
+
this.renderAria(this.#number);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { SonicNumber };
|
|
47
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicRangeElement } from '#elements/range-element.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst fallbackTravelPx = 160;\n\nexport class SonicNumber extends SonicRangeElement {\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.appendOnce(number);\n\t\tthis.render();\n\t\tthis.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: readPxProperty(\n\t\t\t\tgetComputedStyle(number),\n\t\t\t\t'--_sonic-number-travel',\n\t\t\t\tfallbackTravelPx,\n\t\t\t),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected render(): void {\n\t\tthis.#digits.textContent = this.valueText();\n\t\tthis.renderAria(this.#number);\n\t}\n}\n"],"mappings":";;;;;AAUA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,MAAM,mBAAmB;AAEzB,IAAa,cAAb,cAAiC,kBAAkB;CAClD,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,WAAW,MAAM;EACtB,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,YAAY;EACrC,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,eACT,iBAAiB,MAAM,GACvB,0BACA,gBACD;EACD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAU,SAAe;EACxB,KAAK,QAAQ,cAAc,KAAK,UAAU;EAC1C,KAAK,WAAW,KAAK,OAAO;CAC7B;AACD"}
|
|
@@ -39,14 +39,18 @@ export declare abstract class SonicRangeElement extends SonicFormElement {
|
|
|
39
39
|
connectedCallback(): void;
|
|
40
40
|
protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => RangeAxis | undefined): void;
|
|
41
41
|
protected clampRange(next: number): number;
|
|
42
|
+
protected controlRole(): 'slider' | 'spinbutton';
|
|
42
43
|
protected fraction(value: number): number;
|
|
43
44
|
protected input(next: number): boolean;
|
|
45
|
+
protected modulationFractions(amount: number): [number, number];
|
|
44
46
|
protected originFraction(): number;
|
|
45
47
|
protected positions(): number | undefined;
|
|
46
48
|
protected range(): number;
|
|
47
49
|
protected renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void;
|
|
48
50
|
protected restoreState(state: string): void;
|
|
49
51
|
protected valueAt(fraction: number): number;
|
|
52
|
+
protected valueText(): string;
|
|
53
|
+
protected wraps(): boolean;
|
|
50
54
|
}
|
|
51
55
|
//#endregion
|
|
52
56
|
//# sourceMappingURL=range-element.d.ts.map
|