馃悕馃悕馃悕
1
2$css(`
3.number {
4 display: block;
5 line-height: 1.5rem;
6 border-left: 1px solid var(--main-faded);
7 padding-left: 0.5rem;
8}
9
10.number:has(input:focus) {
11 border-left: 3px solid var(--main-solid);
12 padding-left: calc(0.5rem - 2px);
13}
14
15.number label {
16 color: var(--main-solid);
17 min-height: 1em;
18 line-height: 1em;
19 cursor: text;
20}
21
22.number .copyable-value {
23 width: 0;
24 display: inline-block;
25}
26
27.number input[type=number] {
28 background: var(--main-background);
29 color: var(--main-solid);
30 font-family: var(--main-font);
31 transition: border-color 0.2s ease;
32 border-bottom: 1px solid var(--main-faded);
33 min-height: 1rem;
34 line-height: 1rem;
35 width: 5rem;
36}
37
38.number input[type=number] {
39 -webkit-appearance: textfield;
40 -moz-appearance: textfield;
41 appearance: textfield;
42}
43
44.number input[type=number]::-webkit-inner-spin-button,
45.number input[type=number]::-webkit-outer-spin-button {
46 -webkit-appearnce: none;
47}
48
49.number input[type=number]:focus {
50 border-color: var(--main-transparent);
51}
52
53.number input[type=range] {
54 -webkit-appearance: none;
55 appearance: none;
56 width: 100%;
57 outline: none;
58 cursor: pointer;
59 overflow: visible;
60 margin-top: 0.25rem;
61 border: none;
62 border-radius: 2px;
63}
64
65.number input[type=range]:focus {
66 background: none;
67}
68
69/* -webkit: Chromium, Safari, Opera */
70.number input[type=range]::-webkit-slider-runnable-track {
71 background: var(--main-faded);
72 height: 2px;
73}
74
75.number input[type=range]:focus::-webkit-slider-runnable-track {
76 background: var(--main-solid);
77}
78
79/* -moz: Firefox */
80.number input[type=range]::-moz-range-track {
81 background: var(--main-faded);
82 height: 2px;
83}
84
85.number input[type=range]:focus::-moz-range-track {
86 background: var(--main-solid);
87}
88
89.number input[type=range]::-webkit-slider-thumb {
90 -webkit-appearance: none;
91 appearance: none;
92 width: 0.5rem;
93 height: 1rem;
94 border-radius: 2px;
95 background: var(--main-solid);
96 margin-top: calc(1px - 1rem); /* center the thumb on the track */
97
98}
99
100.number input[type=range]::-moz-range-thumb {
101 width: 0.5rem;
102 height: 1rem;
103 border-radius: 2px;
104 border: none; /* cancel default style */
105 background: var(--main-solid);
106}
107
108`);
109
110const defaults = {
111 label: "x",
112 min: -1.0,
113 max: 1.0,
114 limitField: false,
115 step: 0.01,
116 value: 0,
117 onUpdate: null
118};
119
120export async function main(target, spec) {
121 spec = { ...defaults, ...spec };
122
123 const control = document.createElement("div");
124 control.className = "control number";
125
126 // TODO ensure uniqueness more rigorously
127 const name = spec.label.toLowerCase().replace(/\s+/g, "-");
128
129 const label = document.createElement("label");
130 label.innerText = spec.label;
131 label.id = `${name}-label`;
132
133 const label_eq = document.createElement("span");
134 label_eq.innerText = " = ";
135
136 const copyable_value = document.createElement("span");
137 copyable_value.innerText = `${spec.value};\n`;
138 copyable_value.classList = "copyable-value";
139 label_eq.appendChild(copyable_value);
140
141 label_eq.setAttribute("aria-hidden", true);
142
143 const slider = document.createElement("input");
144 slider.type = "range";
145 slider.setAttribute("aria-labelledby", label.id);
146 slider.min = spec.min;
147 slider.max = spec.max;
148 slider.step = spec.step;
149 slider.value = spec.value;
150
151 const field = document.createElement("input");
152 field.type = "number";
153 field.setAttribute("aria-labelledby", label.id);
154 if (spec.limitField) {
155 field.min = spec.min;
156 field.max = spec.max;
157 }
158 field.step = spec.step;
159 field.value = spec.value;
160
161 const set = (value) => {
162 slider.value = value;
163 field.value = value;
164 }
165
166 slider.addEventListener("input", () => {
167 field.value = slider.value;
168 copyable_value.innerText = slider.value;
169 spec.onUpdate?.(slider.value, set);
170 });
171
172 field.addEventListener("input", () => {
173 slider.value = field.value;
174 copyable_value.innerText = field.value;
175 spec.onUpdate?.(field.value, set);
176 });
177
178 control.appendChild(label);
179 control.appendChild(label_eq);
180 control.appendChild(field);
181 control.appendChild(slider);
182 target.appendChild(control);
183}
184