@xsynaptic/sonic-ui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +24 -10
- package/dist/default/elements/key.js +29 -4
- package/dist/default/elements/meter.js +63 -9
- package/dist/default/elements/number.js +46 -0
- package/dist/default/elements/range-element.js +172 -36
- package/dist/default/elements/slider.js +63 -3
- package/dist/default/index.js +2 -1
- package/dist/default/lib/detent.js +17 -0
- package/dist/default/lib/math.js +16 -0
- package/dist/default/lib/number-list.js +14 -0
- package/dist/default/lib/taper.js +17 -4
- 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 +24 -10
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/key.d.ts +4 -0
- package/dist/dev/elements/key.js +29 -4
- package/dist/dev/elements/key.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +6 -0
- package/dist/dev/elements/meter.js +63 -9
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.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 +12 -0
- package/dist/dev/elements/range-element.js +172 -36
- package/dist/dev/elements/range-element.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +8 -0
- package/dist/dev/elements/slider.js +63 -3
- 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/detent.js +18 -0
- package/dist/dev/lib/detent.js.map +1 -0
- package/dist/dev/lib/math.js +17 -0
- package/dist/dev/lib/math.js.map +1 -0
- package/dist/dev/lib/number-list.js +15 -0
- package/dist/dev/lib/number-list.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -1
- package/dist/dev/lib/taper.js +17 -4
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/styles/controls.css +2 -0
- package/dist/styles/dial.css +80 -6
- package/dist/styles/key.css +96 -11
- package/dist/styles/led.css +82 -0
- package/dist/styles/material.css +150 -20
- package/dist/styles/meter.css +120 -8
- package/dist/styles/number.css +81 -0
- package/dist/styles/screen.css +6 -39
- package/dist/styles/segmented.css +23 -7
- package/dist/styles/slider.css +143 -30
- package/package.json +4 -2
|
@@ -2,10 +2,21 @@ import { requireChild, template } from "../lib/render.js";
|
|
|
2
2
|
import { SonicRangeElement } from "./range-element.js";
|
|
3
3
|
|
|
4
4
|
//#region src/elements/slider.ts
|
|
5
|
+
function readRanges(ranges) {
|
|
6
|
+
if ("start" in ranges) return Array.from({ length: ranges.length }, (_entry, index) => [ranges.start(index), ranges.end(index)]);
|
|
7
|
+
return [...ranges].map(([start, end]) => [start, end]);
|
|
8
|
+
}
|
|
9
|
+
function grooveStop(fraction) {
|
|
10
|
+
if (fraction <= 0) return "0%";
|
|
11
|
+
if (fraction >= 1) return "100%";
|
|
12
|
+
return `calc(${String(fraction)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;
|
|
13
|
+
}
|
|
5
14
|
const renderSlider = template(`
|
|
6
15
|
<div class="sonic-slider" role="slider" tabindex="0">
|
|
16
|
+
<div class="sonic-slider-scale" aria-hidden="true"></div>
|
|
7
17
|
<div class="sonic-slider-marks"></div>
|
|
8
18
|
<div class="sonic-slider-groove"></div>
|
|
19
|
+
<div class="sonic-slider-modulation"></div>
|
|
9
20
|
<div class="sonic-slider-cap"></div>
|
|
10
21
|
<div class="sonic-slider-readout" popover="manual">
|
|
11
22
|
<span></span>
|
|
@@ -21,28 +32,53 @@ const renderSlider = template(`
|
|
|
21
32
|
</div>
|
|
22
33
|
`, HTMLDivElement);
|
|
23
34
|
var SonicSlider = class extends SonicRangeElement {
|
|
24
|
-
static observedAttributes = [
|
|
35
|
+
static observedAttributes = [
|
|
36
|
+
...SonicRangeElement.observedAttributes,
|
|
37
|
+
"modulation",
|
|
38
|
+
"orientation",
|
|
39
|
+
"spring"
|
|
40
|
+
];
|
|
41
|
+
get buffered() {
|
|
42
|
+
return this.#buffered.map(([start, end]) => [start, end]);
|
|
43
|
+
}
|
|
44
|
+
set buffered(ranges) {
|
|
45
|
+
this.#buffered = ranges ? readRanges(ranges).toSorted((first, second) => first[0] - second[0]) : [];
|
|
46
|
+
this.render();
|
|
47
|
+
}
|
|
25
48
|
get groovePress() {
|
|
26
49
|
return this.getAttribute("groove-press") === "none" ? "none" : "jump";
|
|
27
50
|
}
|
|
28
51
|
set groovePress(gesture) {
|
|
29
52
|
this.reflect("groove-press", gesture);
|
|
30
53
|
}
|
|
54
|
+
get modulation() {
|
|
55
|
+
return this.numberAttribute("modulation", 0);
|
|
56
|
+
}
|
|
57
|
+
set modulation(amount) {
|
|
58
|
+
this.reflect("modulation", amount);
|
|
59
|
+
}
|
|
31
60
|
get orientation() {
|
|
32
61
|
return this.getAttribute("orientation") === "vertical" ? "vertical" : "horizontal";
|
|
33
62
|
}
|
|
34
63
|
set orientation(direction) {
|
|
35
64
|
this.reflect("orientation", direction);
|
|
36
65
|
}
|
|
66
|
+
get spring() {
|
|
67
|
+
return this.hasAttribute("spring");
|
|
68
|
+
}
|
|
69
|
+
set spring(isSpring) {
|
|
70
|
+
this.reflect("spring", isSpring);
|
|
71
|
+
}
|
|
72
|
+
#buffered = [];
|
|
37
73
|
#slider = renderSlider();
|
|
38
74
|
connectedCallback() {
|
|
39
|
-
this.upgradeProperties("groovePress", "orientation");
|
|
75
|
+
this.upgradeProperties("buffered", "groovePress", "modulation", "orientation", "spring");
|
|
40
76
|
super.connectedCallback();
|
|
41
77
|
}
|
|
42
78
|
connect(signal) {
|
|
43
79
|
const slider = this.#slider;
|
|
44
80
|
const cap = requireChild(slider, ".sonic-slider-cap", HTMLDivElement);
|
|
45
|
-
this.
|
|
81
|
+
this.bindScale(slider, requireChild(slider, ".sonic-slider-scale", HTMLDivElement), signal);
|
|
46
82
|
this.render();
|
|
47
83
|
this.checkStyles(slider, "slider.css");
|
|
48
84
|
this.bindGestures(slider, signal, (event) => {
|
|
@@ -59,12 +95,20 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
59
95
|
render() {
|
|
60
96
|
const slider = this.#slider;
|
|
61
97
|
const positions = this.positions();
|
|
98
|
+
const [modulationFrom, modulationTo] = this.modulationFractions(this.modulation);
|
|
62
99
|
slider.style.setProperty("--_sonic-slider-value", String(this.fraction(this.value)));
|
|
63
100
|
slider.style.setProperty("--_sonic-slider-origin", String(this.originFraction()));
|
|
101
|
+
slider.style.setProperty("--_sonic-slider-modulation-from", String(modulationFrom));
|
|
102
|
+
slider.style.setProperty("--_sonic-slider-modulation-to", String(modulationTo));
|
|
64
103
|
if (positions === void 0) slider.style.removeProperty("--_sonic-slider-positions");
|
|
65
104
|
else slider.style.setProperty("--_sonic-slider-positions", String(positions));
|
|
105
|
+
this.#renderBuffered(slider);
|
|
106
|
+
this.renderScale();
|
|
66
107
|
this.renderAria(slider, this.orientation);
|
|
67
108
|
}
|
|
109
|
+
springTarget() {
|
|
110
|
+
return this.spring ? this.restValue() : void 0;
|
|
111
|
+
}
|
|
68
112
|
#axis(slider, cap) {
|
|
69
113
|
const track = slider.getBoundingClientRect();
|
|
70
114
|
const capBox = cap.getBoundingClientRect();
|
|
@@ -79,6 +123,22 @@ var SonicSlider = class extends SonicRangeElement {
|
|
|
79
123
|
travelPx: Math.max(1, track.width - capBox.width)
|
|
80
124
|
};
|
|
81
125
|
}
|
|
126
|
+
#renderBuffered(slider) {
|
|
127
|
+
if (this.#buffered.length === 0) {
|
|
128
|
+
slider.style.removeProperty("--_sonic-slider-buffered-ranges");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const stops = this.#buffered.flatMap(([start, end]) => {
|
|
132
|
+
const from = grooveStop(this.fraction(Math.min(start, end)));
|
|
133
|
+
const to = grooveStop(this.fraction(Math.max(start, end)));
|
|
134
|
+
return [
|
|
135
|
+
`transparent ${from}`,
|
|
136
|
+
`var(--_sonic-slider-buffered) ${from} ${to}`,
|
|
137
|
+
`transparent ${to}`
|
|
138
|
+
];
|
|
139
|
+
});
|
|
140
|
+
slider.style.setProperty("--_sonic-slider-buffered-ranges", `linear-gradient(var(--_sonic-slider-toward), ${stops.join(", ")})`);
|
|
141
|
+
}
|
|
82
142
|
};
|
|
83
143
|
|
|
84
144
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { RangeAxis } from '#elements/range-element.ts';\n\nimport { SonicRangeElement } from '#elements/range-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends RangeAxis {\n\t// The cap's centre at the minimum, on the same scale as `position`\n\tstartPx: number;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-marks\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-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-slider-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\nexport class SonicSlider extends SonicRangeElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRangeElement.observedAttributes,\n\t\t'orientation',\n\t];\n\n\t// Named for the absent attribute, as `input.type` reads `text`\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('groovePress', 'orientation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tthis.
|
|
1
|
+
{"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { RangeAxis } from '#elements/range-element.ts';\n\nimport { SonicRangeElement } from '#elements/range-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends RangeAxis {\n\t// The cap's centre at the minimum, on the same scale as `position`\n\tstartPx: number;\n}\n\nexport type BufferedRanges = Iterable<readonly [number, number]> | TimeRanges;\n\nfunction readRanges(ranges: BufferedRanges): Array<[number, number]> {\n\tif ('start' in ranges) {\n\t\treturn Array.from({ length: ranges.length }, (_entry, index) => [\n\t\t\tranges.start(index),\n\t\t\tranges.end(index),\n\t\t]);\n\t}\n\n\treturn [...ranges].map(([start, end]) => [start, end]);\n}\n\n// Along the groove, which runs past the travel by half its width at each end\nfunction grooveStop(fraction: number): string {\n\tif (fraction <= 0) return '0%';\n\tif (fraction >= 1) return '100%';\n\n\treturn `calc(${String(fraction)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-marks\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-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-slider-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\nexport class SonicSlider extends SonicRangeElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRangeElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'spring',\n\t];\n\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(ranges: BufferedRanges | undefined) {\n\t\tthis.#buffered = ranges\n\t\t\t? readRanges(ranges).toSorted((first, second) => first[0] - second[0])\n\t\t\t: [];\n\t\tthis.render();\n\t}\n\n\t// Named for the absent attribute, as `input.type` reads `text`\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\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\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('buffered', 'groovePress', 'modulation', 'orientation', 'spring');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tthis.bindScale(slider, requireChild(slider, '.sonic-slider-scale', HTMLDivElement), signal);\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected render(): void {\n\t\tconst slider = this.#slider;\n\t\tconst positions = this.positions();\n\t\tconst [modulationFrom, modulationTo] = this.modulationFractions(this.modulation);\n\n\t\tslider.style.setProperty('--_sonic-slider-value', String(this.fraction(this.value)));\n\t\tslider.style.setProperty('--_sonic-slider-origin', String(this.originFraction()));\n\t\tslider.style.setProperty('--_sonic-slider-modulation-from', String(modulationFrom));\n\t\tslider.style.setProperty('--_sonic-slider-modulation-to', String(modulationTo));\n\t\tif (positions === undefined) slider.style.removeProperty('--_sonic-slider-positions');\n\t\telse slider.style.setProperty('--_sonic-slider-positions', String(positions));\n\t\tthis.#renderBuffered(slider);\n\t\tthis.renderScale();\n\t\tthis.renderAria(slider, this.orientation);\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.restValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst track = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\tif (this.orientation === 'vertical') {\n\t\t\treturn {\n\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\tstartPx: capBox.height / 2 - track.bottom,\n\t\t\t\ttravelPx: Math.max(1, track.height - capBox.height),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tposition: (event) => event.clientX,\n\t\t\tstartPx: track.left + capBox.width / 2,\n\t\t\ttravelPx: Math.max(1, track.width - capBox.width),\n\t\t};\n\t}\n\n\t// Hard stops in one gradient, so any number of ranges costs one layer\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tif (this.#buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-ranges');\n\t\t\treturn;\n\t\t}\n\n\t\tconst stops = this.#buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(this.fraction(Math.min(start, end)));\n\t\t\tconst to = grooveStop(this.fraction(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-ranges',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
package/dist/dev/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
-
export { SonicDial, SonicKey, SonicMeter, SonicSegmented, SonicSlider, formatPercent, parsePercent };
|
|
8
|
+
export { SonicDial, SonicKey, SonicMeter, SonicNumber, SonicSegmented, SonicSlider, formatPercent, parsePercent };
|
package/dist/dev/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 };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/lib/detent.ts
|
|
2
|
+
function isAcross(from, to, place) {
|
|
3
|
+
return from !== place && (from - place) * (to - place) <= 0;
|
|
4
|
+
}
|
|
5
|
+
function passDetent(hold, from, to) {
|
|
6
|
+
if (hold.slack === void 0 && !isAcross(from, to, hold.place)) return to;
|
|
7
|
+
const slack = (hold.slack ?? 0) + to - hold.place;
|
|
8
|
+
if (Math.abs(slack) <= hold.zone) {
|
|
9
|
+
hold.slack = slack;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
hold.slack = void 0;
|
|
13
|
+
return hold.place + slack - Math.sign(slack) * hold.zone;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { passDetent };
|
|
18
|
+
//# sourceMappingURL=detent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detent.js","names":[],"sources":["../../../src/lib/detent.ts"],"sourcesContent":["// Places run 0 to 1 along the travel; `slack` is how far past the detent the pointer has pushed, set while held\nexport interface DetentHold {\n\tplace: number;\n\tslack: number | undefined;\n\tvalue: number;\n\tzone: number;\n}\n\nfunction isAcross(from: number, to: number, place: number): boolean {\n\treturn from !== place && (from - place) * (to - place) <= 0;\n}\n\n// The next place along the travel, or `undefined` while the detent holds\nexport function passDetent(hold: DetentHold, from: number, to: number): number | undefined {\n\tif (hold.slack === undefined && !isAcross(from, to, hold.place)) return to;\n\n\t// Held, `from` is the detent's place, so the move adds to the slack\n\tconst slack = (hold.slack ?? 0) + to - hold.place;\n\n\tif (Math.abs(slack) <= hold.zone) {\n\t\thold.slack = slack;\n\t\treturn undefined;\n\t}\n\n\thold.slack = undefined;\n\n\treturn hold.place + slack - Math.sign(slack) * hold.zone;\n}\n"],"mappings":";AAQA,SAAS,SAAS,MAAc,IAAY,OAAwB;CACnE,OAAO,SAAS,UAAU,OAAO,UAAU,KAAK,UAAU;AAC3D;AAGA,SAAgB,WAAW,MAAkB,MAAc,IAAgC;CAC1F,IAAI,KAAK,UAAU,UAAa,CAAC,SAAS,MAAM,IAAI,KAAK,KAAK,GAAG,OAAO;CAGxE,MAAM,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK;CAE5C,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM;EACjC,KAAK,QAAQ;EACb;CACD;CAEA,KAAK,QAAQ;CAEb,OAAO,KAAK,QAAQ,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK;AACrD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/lib/math.ts
|
|
2
|
+
function clamp(value, min, max) {
|
|
3
|
+
return Math.min(max, Math.max(min, value));
|
|
4
|
+
}
|
|
5
|
+
function clampUnit(fraction) {
|
|
6
|
+
return clamp(fraction, 0, 1);
|
|
7
|
+
}
|
|
8
|
+
function wrapUnit(fraction) {
|
|
9
|
+
return fraction - Math.floor(fraction);
|
|
10
|
+
}
|
|
11
|
+
function trimFloat(value) {
|
|
12
|
+
return Number(value.toPrecision(12));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { clamp, clampUnit, trimFloat, wrapUnit };
|
|
17
|
+
//# sourceMappingURL=math.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"math.js","names":[],"sources":["../../../src/lib/math.ts"],"sourcesContent":["export function clamp(value: number, min: number, max: number): number {\n\treturn Math.min(max, Math.max(min, value));\n}\n\nexport function clampUnit(fraction: number): number {\n\treturn clamp(fraction, 0, 1);\n}\n\nexport function wrapUnit(fraction: number): number {\n\treturn fraction - Math.floor(fraction);\n}\n\nexport function trimFloat(value: number): number {\n\treturn Number(value.toPrecision(12));\n}\n"],"mappings":";AAAA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACtE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC1C;AAEA,SAAgB,UAAU,UAA0B;CACnD,OAAO,MAAM,UAAU,GAAG,CAAC;AAC5B;AAEA,SAAgB,SAAS,UAA0B;CAClD,OAAO,WAAW,KAAK,MAAM,QAAQ;AACtC;AAEA,SAAgB,UAAU,OAAuB;CAChD,OAAO,OAAO,MAAM,YAAY,EAAE,CAAC;AACpC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/lib/number-list.ts
|
|
2
|
+
function parseNumberList(text) {
|
|
3
|
+
if (text === null) return void 0;
|
|
4
|
+
const entries = [...new Set(text.split(/[\s,]+/).filter(Boolean).map(Number).filter((entry) => Number.isFinite(entry)))].toSorted((first, second) => first - second);
|
|
5
|
+
return entries.length >= 2 ? entries : void 0;
|
|
6
|
+
}
|
|
7
|
+
function nearestEntry(entries, value) {
|
|
8
|
+
let nearest = entries[0] ?? value;
|
|
9
|
+
for (const entry of entries) if (Math.abs(entry - value) < Math.abs(nearest - value)) nearest = entry;
|
|
10
|
+
return nearest;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { nearestEntry, parseNumberList };
|
|
15
|
+
//# sourceMappingURL=number-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-list.js","names":[],"sources":["../../../src/lib/number-list.ts"],"sourcesContent":["export function parseNumberList(text: null | string): Array<number> | undefined {\n\tif (text === null) return undefined;\n\n\tconst entries = [\n\t\t...new Set(\n\t\t\ttext\n\t\t\t\t.split(/[\\s,]+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.map(Number)\n\t\t\t\t.filter((entry) => Number.isFinite(entry)),\n\t\t),\n\t].toSorted((first, second) => first - second);\n\n\treturn entries.length >= 2 ? entries : undefined;\n}\n\nexport function nearestEntry(entries: Array<number>, value: number): number {\n\tlet nearest = entries[0] ?? value;\n\n\tfor (const entry of entries) {\n\t\tif (Math.abs(entry - value) < Math.abs(nearest - value)) nearest = entry;\n\t}\n\n\treturn nearest;\n}\n"],"mappings":";AAAA,SAAgB,gBAAgB,MAAgD;CAC/E,IAAI,SAAS,MAAM,OAAO;CAE1B,MAAM,UAAU,CACf,GAAG,IAAI,IACN,KACE,MAAM,QAAQ,CAAC,CACf,OAAO,OAAO,CAAC,CACf,IAAI,MAAM,CAAC,CACX,QAAQ,UAAU,OAAO,SAAS,KAAK,CAAC,CAC3C,CACD,CAAC,CAAC,UAAU,OAAO,WAAW,QAAQ,MAAM;CAE5C,OAAO,QAAQ,UAAU,IAAI,UAAU;AACxC;AAEA,SAAgB,aAAa,SAAwB,OAAuB;CAC3E,IAAI,UAAU,QAAQ,MAAM;CAE5B,KAAK,MAAM,SAAS,SACnB,IAAI,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,IAAI,UAAU,KAAK,GAAG,UAAU;CAGpE,OAAO;AACR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-px-property.js","names":[],"sources":["../../../src/lib/read-px-property.ts"],"sourcesContent":["//
|
|
1
|
+
{"version":3,"file":"read-px-property.js","names":[],"sources":["../../../src/lib/read-px-property.ts"],"sourcesContent":["// Registered `<length>` properties only; their computed value is always px\nexport function readPxProperty(\n\tstyles: CSSStyleDeclaration,\n\tproperty: string,\n\tfallback: number,\n): number {\n\t// eslint-disable-next-line unicorn/prefer-number-coercion -- parsing has to stop at the unit\n\tconst parsed = Number.parseFloat(styles.getPropertyValue(property));\n\n\treturn Number.isFinite(parsed) ? parsed : fallback;\n}\n"],"mappings":";AACA,SAAgB,eACf,QACA,UACA,UACS;CAET,MAAM,SAAS,OAAO,WAAW,OAAO,iBAAiB,QAAQ,CAAC;CAElE,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC3C"}
|
package/dist/dev/lib/taper.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { clampUnit } from "./math.js";
|
|
2
|
+
|
|
1
3
|
//#region src/lib/taper.ts
|
|
2
|
-
function clampUnit(fraction) {
|
|
3
|
-
return Math.min(1, Math.max(0, fraction));
|
|
4
|
-
}
|
|
5
4
|
function linearTaper(min, max) {
|
|
6
5
|
const range = max - min;
|
|
7
6
|
return {
|
|
@@ -27,7 +26,21 @@ function skewTaper(min, max, midpoint) {
|
|
|
27
26
|
value: (position) => min + range * clampUnit(position) ** (1 / skew)
|
|
28
27
|
};
|
|
29
28
|
}
|
|
29
|
+
function listTaper(entries) {
|
|
30
|
+
const last = entries.length - 1;
|
|
31
|
+
return {
|
|
32
|
+
position: (value) => {
|
|
33
|
+
const above = entries.findIndex((entry) => entry >= value);
|
|
34
|
+
if (above === -1) return 1;
|
|
35
|
+
const high = entries[above];
|
|
36
|
+
const low = entries[above - 1];
|
|
37
|
+
if (high === void 0 || low === void 0) return 0;
|
|
38
|
+
return (above - 1 + (value - low) / (high - low)) / last;
|
|
39
|
+
},
|
|
40
|
+
value: (position) => entries[Math.round(clampUnit(position) * last)] ?? NaN
|
|
41
|
+
};
|
|
42
|
+
}
|
|
30
43
|
|
|
31
44
|
//#endregion
|
|
32
|
-
export {
|
|
45
|
+
export { linearTaper, listTaper, logTaper, skewTaper };
|
|
33
46
|
//# sourceMappingURL=taper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taper.js","names":[],"sources":["../../../src/lib/taper.ts"],"sourcesContent":["// Positions run 0 to 1 along the travel\nexport interface Taper {\n\tposition: (value: number) => number;\n\tvalue: (position: number) => number;\n}\n\nexport function
|
|
1
|
+
{"version":3,"file":"taper.js","names":[],"sources":["../../../src/lib/taper.ts"],"sourcesContent":["import { clampUnit } from '#lib/math.ts';\n\n// Positions run 0 to 1 along the travel\nexport interface Taper {\n\tposition: (value: number) => number;\n\tvalue: (position: number) => number;\n}\n\nexport function linearTaper(min: number, max: number): Taper {\n\tconst range = max - min;\n\n\treturn {\n\t\tposition: (value) => (range > 0 ? clampUnit((value - min) / range) : 0),\n\t\tvalue: (position) => min + clampUnit(position) * range,\n\t};\n}\n\nexport function logTaper(min: number, max: number): Taper | undefined {\n\tif (min <= 0 || max <= min) return undefined;\n\n\tconst span = Math.log(max / min);\n\n\treturn {\n\t\tposition: (value) => clampUnit(Math.log(Math.max(min, value) / min) / span),\n\t\tvalue: (position) => min * Math.exp(clampUnit(position) * span),\n\t};\n}\n\n// JUCE's skew: a power curve through `midpoint` at half the travel\nexport function skewTaper(min: number, max: number, midpoint: number): Taper | undefined {\n\tconst range = max - min;\n\tconst proportion = (midpoint - min) / range;\n\n\tif (proportion === 0.5 || !(proportion > 0 && proportion < 1)) return undefined;\n\n\tconst skew = Math.log(0.5) / Math.log(proportion);\n\n\treturn {\n\t\tposition: (value) => clampUnit((value - min) / range) ** skew,\n\t\tvalue: (position) => min + range * clampUnit(position) ** (1 / skew),\n\t};\n}\n\n// Entries sit evenly along the travel, however unevenly they are spaced\nexport function listTaper(entries: Array<number>): Taper {\n\tconst last = entries.length - 1;\n\n\treturn {\n\t\tposition: (value) => {\n\t\t\tconst above = entries.findIndex((entry) => entry >= value);\n\t\t\tif (above === -1) return 1;\n\n\t\t\tconst high = entries[above];\n\t\t\tconst low = entries[above - 1];\n\t\t\tif (high === undefined || low === undefined) return 0;\n\n\t\t\treturn (above - 1 + (value - low) / (high - low)) / last;\n\t\t},\n\t\tvalue: (position) => entries[Math.round(clampUnit(position) * last)] ?? NaN,\n\t};\n}\n"],"mappings":";;;AAQA,SAAgB,YAAY,KAAa,KAAoB;CAC5D,MAAM,QAAQ,MAAM;CAEpB,OAAO;EACN,WAAW,UAAW,QAAQ,IAAI,WAAW,QAAQ,OAAO,KAAK,IAAI;EACrE,QAAQ,aAAa,MAAM,UAAU,QAAQ,IAAI;CAClD;AACD;AAEA,SAAgB,SAAS,KAAa,KAAgC;CACrE,IAAI,OAAO,KAAK,OAAO,KAAK,OAAO;CAEnC,MAAM,OAAO,KAAK,IAAI,MAAM,GAAG;CAE/B,OAAO;EACN,WAAW,UAAU,UAAU,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,IAAI,IAAI;EAC1E,QAAQ,aAAa,MAAM,KAAK,IAAI,UAAU,QAAQ,IAAI,IAAI;CAC/D;AACD;AAGA,SAAgB,UAAU,KAAa,KAAa,UAAqC;CACxF,MAAM,QAAQ,MAAM;CACpB,MAAM,cAAc,WAAW,OAAO;CAEtC,IAAI,eAAe,MAAO,EAAE,aAAa,KAAK,aAAa,IAAI,OAAO;CAEtE,MAAM,OAAO,KAAK,IAAI,EAAG,IAAI,KAAK,IAAI,UAAU;CAEhD,OAAO;EACN,WAAW,UAAU,WAAW,QAAQ,OAAO,KAAK,KAAK;EACzD,QAAQ,aAAa,MAAM,QAAQ,UAAU,QAAQ,MAAM,IAAI;CAChE;AACD;AAGA,SAAgB,UAAU,SAA+B;CACxD,MAAM,OAAO,QAAQ,SAAS;CAE9B,OAAO;EACN,WAAW,UAAU;GACpB,MAAM,QAAQ,QAAQ,WAAW,UAAU,SAAS,KAAK;GACzD,IAAI,UAAU,IAAI,OAAO;GAEzB,MAAM,OAAO,QAAQ;GACrB,MAAM,MAAM,QAAQ,QAAQ;GAC5B,IAAI,SAAS,UAAa,QAAQ,QAAW,OAAO;GAEpD,QAAQ,QAAQ,KAAK,QAAQ,QAAQ,OAAO,QAAQ;EACrD;EACA,QAAQ,aAAa,QAAQ,KAAK,MAAM,UAAU,QAAQ,IAAI,IAAI,MAAM;CACzE;AACD"}
|
package/dist/styles/controls.css
CHANGED
package/dist/styles/dial.css
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
@property --_sonic-dial-size {
|
|
2
|
+
syntax: '<length>';
|
|
3
|
+
inherits: true;
|
|
4
|
+
initial-value: 40px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@property --_sonic-dial-ridge-pitch {
|
|
8
|
+
syntax: '<length>';
|
|
9
|
+
inherits: true;
|
|
10
|
+
initial-value: 4px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@property --_sonic-dial-travel {
|
|
14
|
+
syntax: '<length>';
|
|
15
|
+
inherits: false;
|
|
16
|
+
initial-value: 160px;
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
@layer sonic {
|
|
2
20
|
:is(sonic-dial, .sonic-dial) {
|
|
3
21
|
--_sonic-dial-size: var(--sonic-dial-size, 2.5rem);
|
|
@@ -9,9 +27,11 @@
|
|
|
9
27
|
|
|
10
28
|
sonic-dial:not(:defined, [hidden]) {
|
|
11
29
|
display: block;
|
|
30
|
+
visibility: hidden;
|
|
12
31
|
flex-shrink: 0;
|
|
13
32
|
inline-size: var(--_sonic-dial-size);
|
|
14
33
|
block-size: var(--_sonic-dial-size);
|
|
34
|
+
overflow: hidden;
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
.sonic-dial {
|
|
@@ -25,21 +45,24 @@
|
|
|
25
45
|
/* A fraction of the knob's angle */
|
|
26
46
|
--_sonic-dial-face-drift: var(--sonic-dial-face-drift, 0.1);
|
|
27
47
|
--_sonic-dial-notch-ratio: var(--sonic-dial-notch-ratio, 0.1);
|
|
48
|
+
--_sonic-dial-scale-ratio: var(--sonic-dial-scale-ratio, 0.2);
|
|
49
|
+
--_sonic-dial-scale-font-ratio: var(--sonic-dial-scale-font-ratio, 0.175);
|
|
28
50
|
|
|
29
51
|
/* Centred on six o'clock */
|
|
30
52
|
--_sonic-dial-sweep: var(--sonic-dial-sweep, 300deg);
|
|
53
|
+
--_sonic-dial-segment: var(--sonic-dial-segment, 30deg);
|
|
31
54
|
--_sonic-dial-ridge-pitch: var(--sonic-dial-ridge-pitch, 4px);
|
|
32
|
-
|
|
33
|
-
/* Read by the element as px */
|
|
34
55
|
--_sonic-dial-travel: var(--sonic-dial-travel, 160px);
|
|
35
56
|
--_sonic-unit: calc(var(--_sonic-dial-size) / 40);
|
|
36
57
|
--_sonic-dial-lit: var(--_sonic-lit);
|
|
37
58
|
--_sonic-dial-notch-band: 0px;
|
|
59
|
+
--_sonic-dial-scale-band: 0px;
|
|
60
|
+
--_sonic-dial-notch-ends: 1;
|
|
38
61
|
--_sonic-dial-start: calc(180deg + (1turn - var(--_sonic-dial-sweep)) / 2);
|
|
39
62
|
--_sonic-dial-angle: calc(var(--_sonic-dial-value, 0) * var(--_sonic-dial-sweep));
|
|
40
63
|
--_sonic-dial-ring-width: calc(var(--_sonic-dial-size) * var(--_sonic-dial-ring-ratio));
|
|
41
64
|
--_sonic-dial-cap-inset: calc(
|
|
42
|
-
var(--_sonic-dial-notch-band) + var(--_sonic-dial-size) *
|
|
65
|
+
var(--_sonic-dial-scale-band) + var(--_sonic-dial-notch-band) + var(--_sonic-dial-size) *
|
|
43
66
|
(var(--_sonic-dial-ring-ratio) + var(--_sonic-dial-gap-ratio))
|
|
44
67
|
);
|
|
45
68
|
|
|
@@ -72,10 +95,19 @@
|
|
|
72
95
|
--_sonic-cap: var(--_sonic-cap-hover);
|
|
73
96
|
}
|
|
74
97
|
|
|
98
|
+
[endless] > .sonic-dial {
|
|
99
|
+
--_sonic-dial-sweep: 1turn;
|
|
100
|
+
--_sonic-dial-notch-ends: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
75
103
|
[notched] > .sonic-dial {
|
|
76
104
|
--_sonic-dial-notch-band: calc(var(--_sonic-dial-size) * var(--_sonic-dial-notch-ratio));
|
|
77
105
|
}
|
|
78
106
|
|
|
107
|
+
:has(> [data-sonic-value]) > .sonic-dial {
|
|
108
|
+
--_sonic-dial-scale-band: calc(var(--_sonic-dial-size) * var(--_sonic-dial-scale-ratio));
|
|
109
|
+
}
|
|
110
|
+
|
|
79
111
|
/* Set on `--_sonic-dial-lit`, since `--_sonic-lit` cannot reference itself */
|
|
80
112
|
[dimmed] > .sonic-dial {
|
|
81
113
|
--_sonic-dial-lit: var(--_sonic-lit-off);
|
|
@@ -90,11 +122,11 @@
|
|
|
90
122
|
.sonic-dial-notches {
|
|
91
123
|
--_sonic-dial-notch-width: atan2(var(--_sonic-unit), var(--_sonic-dial-size) / 2);
|
|
92
124
|
--_sonic-dial-notch-pitch: calc(
|
|
93
|
-
var(--_sonic-dial-sweep) / (var(--_sonic-dial-positions, 2) -
|
|
125
|
+
var(--_sonic-dial-sweep) / (var(--_sonic-dial-positions, 2) - var(--_sonic-dial-notch-ends))
|
|
94
126
|
);
|
|
95
127
|
|
|
96
128
|
position: absolute;
|
|
97
|
-
inset:
|
|
129
|
+
inset: var(--_sonic-dial-scale-band);
|
|
98
130
|
background: repeating-conic-gradient(
|
|
99
131
|
from calc(var(--_sonic-dial-start) - var(--_sonic-dial-notch-width) / 2),
|
|
100
132
|
var(--_sonic-etch) 0 var(--_sonic-dial-notch-width),
|
|
@@ -116,9 +148,40 @@
|
|
|
116
148
|
mask-composite: intersect;
|
|
117
149
|
}
|
|
118
150
|
|
|
151
|
+
.sonic-dial-scale {
|
|
152
|
+
font-size: calc(var(--_sonic-dial-size) * var(--_sonic-dial-scale-font-ratio));
|
|
153
|
+
|
|
154
|
+
/* The near edge hugs the band's inner edge, and each mark grows outward along its angle */
|
|
155
|
+
& > * {
|
|
156
|
+
--_sonic-dial-scale-angle: calc(
|
|
157
|
+
var(--_sonic-dial-start) + var(--_sonic-scale-at, 0) * var(--_sonic-dial-sweep)
|
|
158
|
+
);
|
|
159
|
+
--_sonic-dial-scale-sin: sin(var(--_sonic-dial-scale-angle));
|
|
160
|
+
--_sonic-dial-scale-cos: cos(var(--_sonic-dial-scale-angle));
|
|
161
|
+
--_sonic-dial-scale-radius: calc(
|
|
162
|
+
var(--_sonic-dial-size) / 2 - var(--_sonic-dial-scale-band) + var(--_sonic-unit)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
inset: calc(50% - var(--_sonic-dial-scale-cos) * var(--_sonic-dial-scale-radius)) auto auto
|
|
166
|
+
calc(50% + var(--_sonic-dial-scale-sin) * var(--_sonic-dial-scale-radius));
|
|
167
|
+
translate: calc(-50% + var(--_sonic-dial-scale-sin) * 50%)
|
|
168
|
+
calc(-50% - var(--_sonic-dial-scale-cos) * 50%);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
& > .sonic-scale-tick {
|
|
172
|
+
--_sonic-dial-tick-length: calc(var(--_sonic-dial-scale-band) * 0.4);
|
|
173
|
+
|
|
174
|
+
inline-size: var(--_sonic-unit);
|
|
175
|
+
block-size: var(--_sonic-dial-tick-length);
|
|
176
|
+
rotate: var(--_sonic-dial-scale-angle);
|
|
177
|
+
translate: calc(-50% + var(--_sonic-dial-scale-sin) * var(--_sonic-dial-tick-length) / 2)
|
|
178
|
+
calc(-50% - var(--_sonic-dial-scale-cos) * var(--_sonic-dial-tick-length) / 2);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
119
182
|
:is(.sonic-dial-ring, .sonic-dial-modulation) {
|
|
120
183
|
position: absolute;
|
|
121
|
-
inset: var(--_sonic-dial-notch-band);
|
|
184
|
+
inset: calc(var(--_sonic-dial-scale-band) + var(--_sonic-dial-notch-band));
|
|
122
185
|
border-radius: 50%;
|
|
123
186
|
mask: radial-gradient(
|
|
124
187
|
closest-side,
|
|
@@ -155,6 +218,17 @@
|
|
|
155
218
|
);
|
|
156
219
|
}
|
|
157
220
|
|
|
221
|
+
[endless] > .sonic-dial > .sonic-dial-ring {
|
|
222
|
+
background:
|
|
223
|
+
var(--_sonic-dial-ring-shade),
|
|
224
|
+
conic-gradient(
|
|
225
|
+
from
|
|
226
|
+
calc(var(--_sonic-dial-start) + var(--_sonic-dial-angle) - var(--_sonic-dial-segment) / 2),
|
|
227
|
+
var(--_sonic-dial-lit) var(--_sonic-dial-segment),
|
|
228
|
+
var(--_sonic-unlit) 0
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
158
232
|
.sonic-dial-modulation {
|
|
159
233
|
--_sonic-dial-modulation: var(
|
|
160
234
|
--sonic-modulation,
|
package/dist/styles/key.css
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
@property --_sonic-key-size {
|
|
2
|
+
syntax: '<length>';
|
|
3
|
+
inherits: true;
|
|
4
|
+
initial-value: 32px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@property --_sonic-key-busy-angle {
|
|
8
|
+
syntax: '<angle>';
|
|
9
|
+
inherits: false;
|
|
10
|
+
initial-value: 0deg;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@keyframes sonic-key-busy {
|
|
14
|
+
to {
|
|
15
|
+
--_sonic-key-busy-angle: 1turn;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
@layer sonic {
|
|
2
20
|
:is(sonic-key, .sonic-key) {
|
|
3
21
|
--_sonic-key-size: var(--sonic-key-size, 2rem);
|
|
@@ -22,6 +40,8 @@
|
|
|
22
40
|
--_sonic-key-bevel-ratio: var(--sonic-key-bevel-ratio, 0.075);
|
|
23
41
|
--_sonic-key-face-radius-ratio: var(--sonic-key-face-radius-ratio, 0.15);
|
|
24
42
|
--_sonic-key-icon-ratio: var(--sonic-key-icon-ratio, 0.5);
|
|
43
|
+
--_sonic-key-led-ratio: var(--sonic-key-led-ratio, 0.2);
|
|
44
|
+
--_sonic-key-led-icon-ratio: var(--sonic-key-led-icon-ratio, 0.35);
|
|
25
45
|
--_sonic-key-depth-ratio: var(--sonic-key-depth-ratio, 0.03);
|
|
26
46
|
--_sonic-key-press-scale: var(--sonic-key-press-scale, 0.985);
|
|
27
47
|
--_sonic-unit: calc(var(--_sonic-key-size) / 32);
|
|
@@ -39,16 +59,14 @@
|
|
|
39
59
|
inline-size: var(--_sonic-key-size);
|
|
40
60
|
block-size: var(--_sonic-key-size);
|
|
41
61
|
padding: 0;
|
|
62
|
+
font: inherit;
|
|
42
63
|
color: var(--_sonic-ink);
|
|
43
64
|
cursor: pointer;
|
|
44
|
-
background:
|
|
45
|
-
var(--_sonic-light-fall),
|
|
46
|
-
color-mix(in oklab, var(--_sonic-unlit) 55%, #000),
|
|
47
|
-
color-mix(in oklab, var(--_sonic-unlit) 85%, #000)
|
|
48
|
-
);
|
|
65
|
+
background: var(--_sonic-well-floor);
|
|
49
66
|
border: 0;
|
|
50
67
|
border-radius: var(--_sonic-key-corner);
|
|
51
68
|
box-shadow: var(--_sonic-well);
|
|
69
|
+
transition: color 120ms ease-out;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
/* A drifting finger would turn a held pad into a pan */
|
|
@@ -56,12 +74,11 @@
|
|
|
56
74
|
touch-action: none;
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
.sonic-key:active:where(:not(:disabled)),
|
|
77
|
+
.sonic-key:active:where(:not(:disabled, [aria-disabled='true'])),
|
|
60
78
|
.sonic-key:is([aria-pressed='true'], :where([momentary]:state(pressed)) > *) {
|
|
61
79
|
--_sonic-key-shift: calc(var(--_sonic-key-depth) / 2);
|
|
62
80
|
--_sonic-key-scale: var(--_sonic-key-press-scale);
|
|
63
|
-
--_sonic-key-drop:
|
|
64
|
-
var(--_sonic-unit) var(--_sonic-drop-shade);
|
|
81
|
+
--_sonic-key-drop: var(--_sonic-press-drop);
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
.sonic-key:is([aria-pressed='true'], :where([momentary]:state(pressed)) > *) {
|
|
@@ -77,17 +94,69 @@
|
|
|
77
94
|
);
|
|
78
95
|
}
|
|
79
96
|
|
|
80
|
-
.sonic-key:disabled {
|
|
97
|
+
.sonic-key:is(:disabled, [aria-disabled='true']) {
|
|
81
98
|
--_sonic-lit: var(--_sonic-lit-disabled);
|
|
82
99
|
|
|
83
100
|
color: var(--_sonic-etch);
|
|
84
101
|
cursor: default;
|
|
85
102
|
}
|
|
86
103
|
|
|
104
|
+
.sonic-key:not(
|
|
105
|
+
[aria-pressed='true'],
|
|
106
|
+
:disabled,
|
|
107
|
+
[aria-disabled='true'],
|
|
108
|
+
:where([momentary]:state(pressed)) > *
|
|
109
|
+
):is(:focus-visible, :active) {
|
|
110
|
+
color: var(--_sonic-ink-lift);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@media (hover: hover) {
|
|
114
|
+
/* fallow-ignore-next-line code-duplication -- `:hover` sits under `(hover: hover)` and its `:focus-visible` partner outside it */
|
|
115
|
+
.sonic-key:not(
|
|
116
|
+
[aria-pressed='true'],
|
|
117
|
+
:disabled,
|
|
118
|
+
[aria-disabled='true'],
|
|
119
|
+
:where([momentary]:state(pressed)) > *
|
|
120
|
+
):hover {
|
|
121
|
+
color: var(--_sonic-ink-lift);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Turns with the arc, so evenly shaded */
|
|
126
|
+
[busy] > .sonic-key::after {
|
|
127
|
+
position: absolute;
|
|
128
|
+
inset: 0;
|
|
129
|
+
box-sizing: border-box;
|
|
130
|
+
padding: calc(var(--_sonic-key-size) * var(--_sonic-key-bevel-ratio));
|
|
131
|
+
pointer-events: none;
|
|
132
|
+
content: '';
|
|
133
|
+
background: conic-gradient(
|
|
134
|
+
from var(--_sonic-key-busy-angle),
|
|
135
|
+
transparent 0 55%,
|
|
136
|
+
var(--_sonic-lit)
|
|
137
|
+
);
|
|
138
|
+
border-radius: var(--_sonic-key-corner);
|
|
139
|
+
mask:
|
|
140
|
+
linear-gradient(#000 0 0) content-box exclude,
|
|
141
|
+
linear-gradient(#000 0 0);
|
|
142
|
+
animation: sonic-key-busy 1s linear infinite;
|
|
143
|
+
|
|
144
|
+
@media (prefers-reduced-motion: reduce) {
|
|
145
|
+
background: color-mix(in oklab, var(--_sonic-lit) 45%, transparent);
|
|
146
|
+
animation: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (forced-colors: active) {
|
|
150
|
+
forced-color-adjust: none;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
87
154
|
.sonic-key-cap {
|
|
88
155
|
position: absolute;
|
|
89
156
|
inset: var(--_sonic-key-gap);
|
|
90
157
|
display: grid;
|
|
158
|
+
gap: calc(var(--_sonic-key-size) * var(--_sonic-key-led-ratio) / 2);
|
|
159
|
+
place-content: center;
|
|
91
160
|
place-items: center;
|
|
92
161
|
background: var(--_sonic-skirt);
|
|
93
162
|
border-radius: max(0px, var(--_sonic-key-corner) - var(--_sonic-key-gap));
|
|
@@ -107,8 +176,18 @@
|
|
|
107
176
|
box-shadow: var(--_sonic-face-shadow);
|
|
108
177
|
}
|
|
109
178
|
|
|
110
|
-
& >
|
|
179
|
+
& > * {
|
|
111
180
|
position: relative;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
& > .sonic-led {
|
|
184
|
+
--_sonic-led-size: var(
|
|
185
|
+
--sonic-led-size,
|
|
186
|
+
calc(var(--_sonic-key-size) * var(--_sonic-key-led-ratio))
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
& > svg {
|
|
112
191
|
inline-size: calc(var(--_sonic-key-size) * var(--_sonic-key-icon-ratio));
|
|
113
192
|
block-size: calc(var(--_sonic-key-size) * var(--_sonic-key-icon-ratio));
|
|
114
193
|
filter: var(--_sonic-engraving);
|
|
@@ -117,6 +196,11 @@
|
|
|
117
196
|
}
|
|
118
197
|
}
|
|
119
198
|
|
|
199
|
+
.sonic-key-cap:has(> .sonic-led) > svg {
|
|
200
|
+
inline-size: calc(var(--_sonic-key-size) * var(--_sonic-key-led-icon-ratio));
|
|
201
|
+
block-size: calc(var(--_sonic-key-size) * var(--_sonic-key-led-icon-ratio));
|
|
202
|
+
}
|
|
203
|
+
|
|
120
204
|
/* Engraving under the glow keeps a lit icon's edge */
|
|
121
205
|
.sonic-key:is([aria-pressed='true'], :where([momentary]:state(pressed)) > *)
|
|
122
206
|
.sonic-key-cap
|
|
@@ -125,6 +209,7 @@
|
|
|
125
209
|
drop-shadow(0 0 var(--_sonic-unit) color-mix(in oklab, var(--_sonic-lit) 35%, transparent));
|
|
126
210
|
}
|
|
127
211
|
|
|
212
|
+
/* fallow-ignore-next-line code-duplication -- one pressed state, restated for each part of the cap it reaches */
|
|
128
213
|
.sonic-key:is([aria-pressed='true'], :where([momentary]:state(pressed)) > *)
|
|
129
214
|
.sonic-key-cap
|
|
130
215
|
> [data-sonic-when='off'],
|
|
@@ -145,7 +230,7 @@
|
|
|
145
230
|
--_sonic-face: var(--_sonic-lit);
|
|
146
231
|
--_sonic-ink: HighlightText;
|
|
147
232
|
|
|
148
|
-
&:disabled {
|
|
233
|
+
&:is(:disabled, [aria-disabled='true']) {
|
|
149
234
|
--_sonic-ink: ButtonFace;
|
|
150
235
|
}
|
|
151
236
|
|