@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
|
@@ -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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"range-element.js","names":[],"sources":["../../../src/elements/range-element.ts"],"sourcesContent":["import type { Taper } from '#lib/taper.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { RangeEntry } from '#elements/range-entry.ts';\nimport { requireChild } from '#lib/render.ts';\nimport { clampUnit, linearTaper, logTaper, skewTaper } from '#lib/taper.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// `position` grows with the value\nexport interface RangeAxis {\n\tposition: (event: PointerEvent) => number;\n\ttravelPx: number;\n}\n\ninterface RangeDrag extends RangeAxis {\n\tfromValue: number;\n\tisRevealed: boolean;\n\tlastPosition: number;\n\tpointerId: number;\n\t// Unstepped, or a slow drag rounds back to where it started\n\trawFraction: number;\n\trevealTimer: ReturnType<typeof setTimeout> | undefined;\n\tstartPosition: number;\n}\n\nconst fineScale = 0.1;\nconst keyRevealMs = 1000;\nconst pageSteps = 10;\nconst revealMs = 250;\nconst revealPx = 4;\n\n// happy-dom has no popover API\nconst canPopover = 'togglePopover' in HTMLElement.prototype;\n\n// A shared anchor name resolves to the last one on the page\nlet instanceCount = 0;\n\n// A Mac keyboard's Delete sends Backspace\nconst resetKeys = new Set(['Backspace', 'Delete']);\n\nconst keySteps = new Map([\n\t['ArrowDown', -1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', 1],\n\t['PageDown', -pageSteps],\n\t['PageUp', pageSteps],\n]);\n\n// A slider's children are presentational, so these come off while the entry is open\nconst sliderAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\n// As on `<input type=\"range\">`, the property never writes the `value` attribute back\nexport abstract class SonicRangeElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'step',\n\t\t'taper',\n\t\t'value',\n\t];\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\t// Named for the absent attribute, as `input.type` reads `text`\n\tget doublePress(): 'reset' | 'type' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'type';\n\t}\n\n\tset doublePress(gesture: 'reset' | 'type' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// Unclamped by `min`, so reading it back never pins the range\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\t// `undefined` while unset, so reading it back never pins it to `min`\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#value;\n\t}\n\n\t// A drag owns the value, so a timer's write can't pull the cap from under the pointer\n\tset value(next: number) {\n\t\tif (this.#drag) return;\n\n\t\tthis.#write(next);\n\t}\n\n\t#drag: RangeDrag | undefined;\n\n\t#entry: RangeEntry | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// Re-read from the attribute until the property is set, since `max` may arrive after `value`\n\t#isDirty = false;\n\n\t#keyRevealTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { bubble: HTMLElement; text: HTMLElement };\n\n\t#value = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') {\n\t\t\tif (this.#drag) return;\n\n\t\t\tthis.#isDirty = false;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) {\n\t\t\tthis.#entry?.close(false);\n\t\t\tthis.#endDrag();\n\t\t}\n\n\t\tthis.#value = this.#clamp(\n\t\t\tthis.#isDirty ? this.#value : this.numberAttribute('value', this.min),\n\t\t);\n\t\tthis.render();\n\t}\n\n\t// The range before `value`, or a value set with its `max` before upgrade clamps to the old one\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'formatValue',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => RangeAxis | undefined,\n\t): void {\n\t\tconst entry = this.#bindEntry(control, signal);\n\n\t\tcontrol.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || this.isDisabled() || entry.isOpen) return;\n\n\t\t\t\t// A click listener above makes WebKit send a tap's compatibility mousedown, which blurs the entry a double tap just opened\n\t\t\t\tif (event.pointerType === 'touch') event.preventDefault();\n\t\t\t\tcontrol.focus();\n\t\t\t\tif (event.metaKey || event.ctrlKey) {\n\t\t\t\t\tthis.#reset();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst fromValue = this.#value;\n\t\t\t\tconst axis = grab(event);\n\t\t\t\tif (!axis) return;\n\n\t\t\t\tconst startPosition = axis.position(event);\n\t\t\t\tconst drag: RangeDrag = {\n\t\t\t\t\tfromValue,\n\t\t\t\t\tisRevealed: false,\n\t\t\t\t\tlastPosition: startPosition,\n\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t\tposition: axis.position,\n\t\t\t\t\trawFraction: this.fraction(this.#value),\n\t\t\t\t\trevealTimer: undefined,\n\t\t\t\t\tstartPosition,\n\t\t\t\t\ttravelPx: axis.travelPx,\n\t\t\t\t};\n\n\t\t\t\tdrag.revealTimer = setTimeout(() => {\n\t\t\t\t\tthis.#reveal(drag);\n\t\t\t\t}, revealMs);\n\t\t\t\tthis.#drag = drag;\n\t\t\t\tcontrol.setPointerCapture(event.pointerId);\n\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tconst drag = this.#drag;\n\t\t\t\tif (event.pointerId !== drag?.pointerId) return;\n\n\t\t\t\tconst position = drag.position(event);\n\t\t\t\tconst scale = event.shiftKey ? fineScale : 1;\n\n\t\t\t\t// Per move, so pressing Shift mid-drag changes pace without a jump\n\t\t\t\tdrag.rawFraction = clampUnit(\n\t\t\t\t\tdrag.rawFraction + ((position - drag.lastPosition) / drag.travelPx) * scale,\n\t\t\t\t);\n\t\t\t\tdrag.lastPosition = position;\n\t\t\t\tif (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);\n\t\t\t\tthis.input(this.valueAt(drag.rawFraction));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\tconst endDrag = (event: PointerEvent): void => {\n\t\t\tif (event.pointerId === this.#drag?.pointerId) this.#endDrag();\n\t\t};\n\n\t\tcontrol.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t(event) => {\n\t\t\t\tendDrag(event);\n\t\t\t\tentry.forgetPress();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// Other code can release capture without a `pointerup`\n\t\tcontrol.addEventListener('lostpointercapture', endDrag, { signal });\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerId !== this.#drag?.pointerId) return;\n\n\t\t\t\tthis.#endDrag();\n\t\t\t\tif (!entry.press(event)) return;\n\n\t\t\t\tif (this.doublePress === 'reset') this.#reset();\n\t\t\t\telse entry.open();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindKeys(control, entry, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#endDrag();\n\t\t\t\tthis.#hideForKeys();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected clampRange(next: number): number {\n\t\treturn Math.min(this.#max(), Math.max(this.min, next));\n\t}\n\n\tprotected fraction(value: number): number {\n\t\treturn (this.#taper() ?? linearTaper(this.min, this.#max())).position(value);\n\t}\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#value;\n\n\t\tthis.#write(next);\n\t\tif (this.#value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected originFraction(): number {\n\t\treturn this.fraction(this.origin ?? this.min);\n\t}\n\n\tprotected positions(): number | undefined {\n\t\tconst positions = Math.round(this.range() / this.step) + 1;\n\n\t\tif (!this.notched || !Number.isFinite(positions) || positions < 2) return;\n\n\t\treturn positions;\n\t}\n\n\tprotected range(): number {\n\t\treturn this.#max() - this.min;\n\t}\n\n\t// Per render rather than per write, so a range with no `value` attribute submits its minimum too\n\tprotected renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#value), String(this.#value));\n\t\tif (this.#entry?.isOpen) {\n\t\t\tfor (const name of sliderAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.#renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tcontrol.setAttribute('role', 'slider');\n\t\tcontrol.setAttribute('aria-valuemin', String(this.min));\n\t\tcontrol.setAttribute('aria-valuemax', String(this.#max()));\n\t\tcontrol.setAttribute('aria-valuenow', String(this.#value));\n\t\tif (orientation) control.setAttribute('aria-orientation', orientation);\n\t\twriteAttribute(control, 'aria-valuetext', this.#formatValue?.(this.#value));\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected valueAt(fraction: number): number {\n\t\treturn (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);\n\t}\n\n\t#bindEntry(control: HTMLElement, signal: AbortSignal): RangeEntry {\n\t\tconst input = requireChild(control, 'input', HTMLInputElement);\n\t\tconst bubble = requireChild(control, '[popover]', HTMLElement);\n\n\t\tconst anchor = `--sonic-readout-${this.#instance}`;\n\n\t\t// Chrome's issues panel flags a form field with no id or name; a `name` would submit it\n\t\tinput.id = `sonic-entry-${this.#instance}`;\n\t\tthis.#readout = { bubble, text: requireChild(bubble, 'span', HTMLElement) };\n\t\tcontrol.style.setProperty('anchor-name', anchor);\n\t\tbubble.style.setProperty('position-anchor', anchor);\n\n\t\tconst entry = new RangeEntry(input, {\n\t\t\tcommit: (text) => {\n\t\t\t\t// eslint-disable-next-line unicorn/prefer-number-coercion -- `Number('')` is 0; an emptied field should leave the value alone\n\t\t\t\tthis.#commit((this.#parseValue ?? Number.parseFloat)(text));\n\t\t\t},\n\t\t\t// `Number.parseFloat` reads \"5 kHz\" as 5\n\t\t\ttext: () => (this.#parseValue ? this.#valueText() : String(this.#value)),\n\t\t\ttoggle: (isOpen) => {\n\t\t\t\tif (isOpen) this.forwardNaming(input, true);\n\t\t\t\tthis.toggleState('editing', isOpen);\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t});\n\n\t\tthis.#entry = entry;\n\t\tentry.bind(control, signal);\n\n\t\treturn entry;\n\t}\n\n\t#bindKeys(control: HTMLElement, entry: RangeEntry, signal: AbortSignal): void {\n\t\tcontrol.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented || event.target !== control) return;\n\n\t\t\t\tif (event.key === 'Enter') {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tentry.open();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst next = this.#keyTarget(event.key);\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#commit(next);\n\t\t\t\tthis.#revealForKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.#hideForKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#clamp(next: number): number {\n\t\tconst min = this.min;\n\t\tconst step = this.step;\n\t\tconst stepped = step > 0 ? min + Math.round((next - min) / step) * step : next;\n\n\t\t// A step like 0.01 leaves float residue\n\t\treturn this.clampRange(Number(stepped.toPrecision(12)));\n\t}\n\n\t#commit(next: number): void {\n\t\tif (this.input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#endDrag(): void {\n\t\tconst drag = this.#drag;\n\t\tif (!drag) return;\n\n\t\tclearTimeout(drag.revealTimer);\n\t\tthis.#drag = undefined;\n\t\tthis.toggleState('dragging', false);\n\t\tthis.#renderReadout();\n\t\tif (this.#value !== drag.fromValue) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#hideForKeys(): void {\n\t\tclearTimeout(this.#keyRevealTimer);\n\t\tif (this.#keyRevealTimer === undefined) return;\n\n\t\tthis.#keyRevealTimer = undefined;\n\t\tthis.#renderReadout();\n\t}\n\n\t#isValueShown(): boolean {\n\t\tconst isRevealed = this.#drag?.isRevealed === true || this.#keyRevealTimer !== undefined;\n\n\t\treturn isRevealed && this.readout;\n\t}\n\n\t#keyTarget(key: string): number | undefined {\n\t\tif (key === 'Home') return this.min;\n\t\tif (key === 'End') return this.#max();\n\t\tif (resetKeys.has(key)) return this.default;\n\n\t\tconst steps = keySteps.get(key);\n\t\tif (steps === undefined) return undefined;\n\n\t\tconst step = this.step > 0 ? this.step : this.range() / 100;\n\t\tconst taper = this.#taper();\n\t\tif (!taper) return this.#value + steps * step;\n\n\t\t// By travel, since steps at the wide end would take forever; at least one step, or the snap holds it in place\n\t\tconst next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));\n\n\t\treturn next === this.#value ? this.#value + Math.sign(steps) * step : next;\n\t}\n\n\t#max(): number {\n\t\treturn Math.max(this.min, this.max);\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tif (this.isDisabled()) {\n\t\t\tcontrol.setAttribute('aria-disabled', 'true');\n\t\t\tcontrol.removeAttribute('tabindex');\n\t\t\treturn;\n\t\t}\n\n\t\tcontrol.removeAttribute('aria-disabled');\n\t\tcontrol.setAttribute('tabindex', '0');\n\t}\n\n\t#renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout || !canPopover || !this.isConnected) return;\n\n\t\tconst isEditing = this.#entry?.isOpen === true;\n\n\t\treadout.text.hidden = isEditing;\n\t\tif (!isEditing) readout.text.textContent = this.#valueText();\n\t\treadout.bubble.togglePopover(isEditing || this.#isValueShown());\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit(this.default ?? NaN);\n\t}\n\n\t#reveal(drag: RangeDrag): void {\n\t\tif (drag.isRevealed || this.#drag !== drag) return;\n\n\t\tdrag.isRevealed = true;\n\t\tthis.#renderReadout();\n\t}\n\n\t#revealForKeys(): void {\n\t\tclearTimeout(this.#keyRevealTimer);\n\t\tthis.#keyRevealTimer = setTimeout(() => {\n\t\t\tthis.#hideForKeys();\n\t\t}, keyRevealMs);\n\t\tthis.#renderReadout();\n\t}\n\n\t// Notches are evenly spaced, so a notched control stays linear\n\t#taper(): Taper | undefined {\n\t\tif (this.notched) return undefined;\n\n\t\tconst min = this.min;\n\t\tconst max = this.#max();\n\t\tconst log = this.taper === 'log' ? logTaper(min, max) : undefined;\n\n\t\treturn log ?? skewTaper(min, max, this.midpoint ?? NaN);\n\t}\n\n\t#valueText(): string {\n\t\treturn this.#formatValue?.(this.#value) ?? String(this.#value);\n\t}\n\n\t#write(next: number): void {\n\t\tif (!Number.isFinite(next)) return;\n\n\t\tthis.#isDirty = true;\n\n\t\tconst clamped = this.#clamp(next);\n\t\tif (clamped === this.#value) return;\n\n\t\tthis.#value = clamped;\n\t\tthis.render();\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"range-element.js","names":[],"sources":["../../../src/elements/range-element.ts"],"sourcesContent":["import type { Taper } from '#lib/taper.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { RangeEntry } from '#elements/range-entry.ts';\nimport { requireChild } from '#lib/render.ts';\nimport { clampUnit, linearTaper, logTaper, skewTaper, wrapUnit } from '#lib/taper.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// `position` grows with the value\nexport interface RangeAxis {\n\tposition: (event: PointerEvent) => number;\n\ttravelPx: number;\n}\n\ninterface RangeDrag extends RangeAxis {\n\tfromValue: number;\n\tisRevealed: boolean;\n\tlastPosition: number;\n\tpointerId: number;\n\t// Unstepped, or a slow drag rounds back to where it started\n\trawFraction: number;\n\trevealTimer: ReturnType<typeof setTimeout> | undefined;\n\tstartPosition: number;\n}\n\nconst fineScale = 0.1;\nconst keyRevealMs = 1000;\nconst pageSteps = 10;\nconst revealMs = 250;\nconst revealPx = 4;\n\n// happy-dom has no popover API\nconst canPopover = 'togglePopover' in HTMLElement.prototype;\n\n// A shared anchor name resolves to the last one on the page\nlet instanceCount = 0;\n\n// A Mac keyboard's Delete sends Backspace\nconst resetKeys = new Set(['Backspace', 'Delete']);\n\nconst keySteps = new Map([\n\t['ArrowDown', -1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', 1],\n\t['PageDown', -pageSteps],\n\t['PageUp', pageSteps],\n]);\n\n// A slider's children are presentational, so these come off while the entry is open\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\n// As on `<input type=\"range\">`, the property never writes the `value` attribute back\nexport abstract class SonicRangeElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'step',\n\t\t'taper',\n\t\t'value',\n\t];\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\t// Named for the absent attribute, as `input.type` reads `text`\n\tget doublePress(): 'reset' | 'type' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'type';\n\t}\n\n\tset doublePress(gesture: 'reset' | 'type' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// Unclamped by `min`, so reading it back never pins the range\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\t// `undefined` while unset, so reading it back never pins it to `min`\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#value;\n\t}\n\n\t// A drag owns the value, so a timer's write can't pull the cap from under the pointer\n\tset value(next: number) {\n\t\tif (this.#drag) return;\n\n\t\tthis.#write(next);\n\t}\n\n\t#drag: RangeDrag | undefined;\n\n\t#entry: RangeEntry | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// Re-read from the attribute until the property is set, since `max` may arrive after `value`\n\t#isDirty = false;\n\n\t#keyRevealTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { bubble: HTMLElement; text: HTMLElement };\n\n\t#value = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') {\n\t\t\tif (this.#drag) return;\n\n\t\t\tthis.#isDirty = false;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) {\n\t\t\tthis.#entry?.close(false);\n\t\t\tthis.#endDrag();\n\t\t}\n\n\t\tthis.#value = this.#clamp(\n\t\t\tthis.#isDirty ? this.#value : this.numberAttribute('value', this.min),\n\t\t);\n\t\tthis.render();\n\t}\n\n\t// The range before `value`, or a value set with its `max` before upgrade clamps to the old one\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'formatValue',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => RangeAxis | undefined,\n\t): void {\n\t\tconst entry = this.#bindEntry(control, signal);\n\n\t\tcontrol.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || this.isDisabled() || entry.isOpen) return;\n\n\t\t\t\t// A click listener above makes WebKit send a tap's compatibility mousedown, which blurs the entry a double tap just opened\n\t\t\t\tif (event.pointerType === 'touch') event.preventDefault();\n\t\t\t\tcontrol.focus();\n\t\t\t\tif (event.metaKey || event.ctrlKey) {\n\t\t\t\t\tthis.#reset();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst fromValue = this.#value;\n\t\t\t\tconst axis = grab(event);\n\t\t\t\tif (!axis) return;\n\n\t\t\t\tconst startPosition = axis.position(event);\n\t\t\t\tconst drag: RangeDrag = {\n\t\t\t\t\tfromValue,\n\t\t\t\t\tisRevealed: false,\n\t\t\t\t\tlastPosition: startPosition,\n\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t\tposition: axis.position,\n\t\t\t\t\trawFraction: this.fraction(this.#value),\n\t\t\t\t\trevealTimer: undefined,\n\t\t\t\t\tstartPosition,\n\t\t\t\t\ttravelPx: axis.travelPx,\n\t\t\t\t};\n\n\t\t\t\tdrag.revealTimer = setTimeout(() => {\n\t\t\t\t\tthis.#reveal(drag);\n\t\t\t\t}, revealMs);\n\t\t\t\tthis.#drag = drag;\n\t\t\t\tcontrol.setPointerCapture(event.pointerId);\n\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tconst drag = this.#drag;\n\t\t\t\tif (event.pointerId !== drag?.pointerId) return;\n\n\t\t\t\tconst position = drag.position(event);\n\t\t\t\tconst scale = event.shiftKey ? fineScale : 1;\n\n\t\t\t\t// Per move, so pressing Shift mid-drag changes pace without a jump\n\t\t\t\tconst fraction =\n\t\t\t\t\tdrag.rawFraction + ((position - drag.lastPosition) / drag.travelPx) * scale;\n\n\t\t\t\tdrag.rawFraction = this.wraps() ? wrapUnit(fraction) : clampUnit(fraction);\n\t\t\t\tdrag.lastPosition = position;\n\t\t\t\tif (Math.abs(position - drag.startPosition) >= revealPx) this.#reveal(drag);\n\t\t\t\tthis.input(this.valueAt(drag.rawFraction));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\tconst endDrag = (event: PointerEvent): void => {\n\t\t\tif (event.pointerId === this.#drag?.pointerId) this.#endDrag();\n\t\t};\n\n\t\tcontrol.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t(event) => {\n\t\t\t\tendDrag(event);\n\t\t\t\tentry.forgetPress();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// Other code can release capture without a `pointerup`\n\t\tcontrol.addEventListener('lostpointercapture', endDrag, { signal });\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tif (event.pointerId !== this.#drag?.pointerId) return;\n\n\t\t\t\tthis.#endDrag();\n\t\t\t\tif (!entry.press(event)) return;\n\n\t\t\t\tif (this.doublePress === 'reset') this.#reset();\n\t\t\t\telse entry.open();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindKeys(control, entry, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#endDrag();\n\t\t\t\tthis.#hideForKeys();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected clampRange(next: number): number {\n\t\tconst min = this.min;\n\t\tif (!this.wraps()) return Math.min(this.#max(), Math.max(min, next));\n\n\t\tconst range = this.range();\n\t\tif (range <= 0) return min;\n\n\t\tconst offset = Number(((((next - min) % range) + range) % range).toPrecision(12));\n\n\t\treturn offset >= range ? min : Number((min + offset).toPrecision(12));\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected fraction(value: number): number {\n\t\treturn (this.#taper() ?? linearTaper(this.min, this.#max())).position(value);\n\t}\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#value;\n\n\t\tthis.#write(next);\n\t\tif (this.#value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected modulationFractions(amount: number): [number, number] {\n\t\tconst value = this.fraction(this.#value);\n\t\tconst modulated = this.fraction(this.#value + amount);\n\n\t\treturn [Math.min(value, modulated), Math.max(value, modulated)];\n\t}\n\n\tprotected originFraction(): number {\n\t\treturn this.fraction(this.origin ?? this.min);\n\t}\n\n\tprotected positions(): number | undefined {\n\t\tconst positions = Math.round(this.range() / this.step) + (this.wraps() ? 0 : 1);\n\n\t\tif (!this.notched || !Number.isFinite(positions) || positions < 2) return;\n\n\t\treturn positions;\n\t}\n\n\tprotected range(): number {\n\t\treturn this.#max() - this.min;\n\t}\n\n\t// Per render rather than per write, so a range with no `value` attribute submits its minimum too\n\tprotected renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#value), String(this.#value));\n\t\tif (this.#entry?.isOpen) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.#renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tcontrol.setAttribute('role', this.controlRole());\n\t\tcontrol.setAttribute('aria-valuemin', String(this.min));\n\t\tcontrol.setAttribute('aria-valuemax', String(this.#max()));\n\t\tcontrol.setAttribute('aria-valuenow', String(this.#value));\n\t\tif (orientation) control.setAttribute('aria-orientation', orientation);\n\t\twriteAttribute(control, 'aria-valuetext', this.#formatValue?.(this.#value));\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected valueAt(fraction: number): number {\n\t\treturn (this.#taper() ?? linearTaper(this.min, this.#max())).value(fraction);\n\t}\n\n\tprotected valueText(): string {\n\t\treturn this.#formatValue?.(this.#value) ?? String(this.#value);\n\t}\n\n\tprotected wraps(): boolean {\n\t\treturn false;\n\t}\n\n\t#bindEntry(control: HTMLElement, signal: AbortSignal): RangeEntry {\n\t\tconst input = requireChild(control, 'input', HTMLInputElement);\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\t// Chrome's issues panel flags a form field with no id or name; a `name` would submit it\n\t\tinput.id = `sonic-entry-${this.#instance}`;\n\t\tif (bubble) {\n\t\t\tconst anchor = `--sonic-readout-${this.#instance}`;\n\n\t\t\tthis.#readout = { bubble, text: requireChild(bubble, 'span', HTMLElement) };\n\t\t\tcontrol.style.setProperty('anchor-name', anchor);\n\t\t\tbubble.style.setProperty('position-anchor', anchor);\n\t\t}\n\n\t\tconst entry = new RangeEntry(input, {\n\t\t\tcommit: (text) => {\n\t\t\t\t// eslint-disable-next-line unicorn/prefer-number-coercion -- `Number('')` is 0; an emptied field should leave the value alone\n\t\t\t\tthis.#commit((this.#parseValue ?? Number.parseFloat)(text));\n\t\t\t},\n\t\t\t// `Number.parseFloat` reads \"5 kHz\" as 5\n\t\t\ttext: () => (this.#parseValue ? this.valueText() : String(this.#value)),\n\t\t\ttoggle: (isOpen) => {\n\t\t\t\tif (isOpen) this.forwardNaming(input, true);\n\t\t\t\tthis.toggleState('editing', isOpen);\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t});\n\n\t\tthis.#entry = entry;\n\t\tentry.bind(control, signal);\n\n\t\treturn entry;\n\t}\n\n\t#bindKeys(control: HTMLElement, entry: RangeEntry, signal: AbortSignal): void {\n\t\tcontrol.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented || event.target !== control) return;\n\n\t\t\t\tif (event.key === 'Enter') {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tentry.open();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst next = this.#keyTarget(event.key);\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#commit(next);\n\t\t\t\tthis.#revealForKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.#hideForKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#clamp(next: number): number {\n\t\tconst min = this.min;\n\t\tconst step = this.step;\n\t\tconst stepped = step > 0 ? min + Math.round((next - min) / step) * step : next;\n\n\t\t// A step like 0.01 leaves float residue\n\t\treturn this.clampRange(Number(stepped.toPrecision(12)));\n\t}\n\n\t#commit(next: number): void {\n\t\tif (this.input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#end(step: number): number {\n\t\tif (!this.wraps()) return this.#max();\n\n\t\treturn this.min + (Math.ceil(this.range() / step) - 1) * step;\n\t}\n\n\t#endDrag(): void {\n\t\tconst drag = this.#drag;\n\t\tif (!drag) return;\n\n\t\tclearTimeout(drag.revealTimer);\n\t\tthis.#drag = undefined;\n\t\tthis.toggleState('dragging', false);\n\t\tthis.#renderReadout();\n\t\tif (this.#value !== drag.fromValue) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#hideForKeys(): void {\n\t\tclearTimeout(this.#keyRevealTimer);\n\t\tif (this.#keyRevealTimer === undefined) return;\n\n\t\tthis.#keyRevealTimer = undefined;\n\t\tthis.#renderReadout();\n\t}\n\n\t#isValueShown(): boolean {\n\t\tconst isRevealed = this.#drag?.isRevealed === true || this.#keyRevealTimer !== undefined;\n\n\t\treturn isRevealed && this.readout;\n\t}\n\n\t#keyTarget(key: string): number | undefined {\n\t\tif (key === 'Home') return this.min;\n\t\tconst step = this.step > 0 ? this.step : this.range() / 100;\n\n\t\tif (key === 'End') return this.#end(step);\n\t\tif (resetKeys.has(key)) return this.default;\n\n\t\tconst steps = keySteps.get(key);\n\t\tif (steps === undefined) return undefined;\n\n\t\tconst taper = this.#taper();\n\t\tif (!taper) return this.#value + steps * step;\n\n\t\t// By travel, since steps at the wide end would take forever; at least one step, or the snap holds it in place\n\t\tconst next = this.#clamp(taper.value(taper.position(this.#value) + steps / 100));\n\n\t\treturn next === this.#value ? this.#value + Math.sign(steps) * step : next;\n\t}\n\n\t#max(): number {\n\t\treturn Math.max(this.min, this.max);\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tif (this.isDisabled()) {\n\t\t\tcontrol.setAttribute('aria-disabled', 'true');\n\t\t\tcontrol.removeAttribute('tabindex');\n\t\t\treturn;\n\t\t}\n\n\t\tcontrol.removeAttribute('aria-disabled');\n\t\tcontrol.setAttribute('tabindex', '0');\n\t}\n\n\t#renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout || !canPopover || !this.isConnected) return;\n\n\t\tconst isEditing = this.#entry?.isOpen === true;\n\n\t\treadout.text.hidden = isEditing;\n\t\tif (!isEditing) readout.text.textContent = this.valueText();\n\t\treadout.bubble.togglePopover(isEditing || this.#isValueShown());\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit(this.default ?? NaN);\n\t}\n\n\t#reveal(drag: RangeDrag): void {\n\t\tif (drag.isRevealed || this.#drag !== drag) return;\n\n\t\tdrag.isRevealed = true;\n\t\tthis.#renderReadout();\n\t}\n\n\t#revealForKeys(): void {\n\t\tclearTimeout(this.#keyRevealTimer);\n\t\tthis.#keyRevealTimer = setTimeout(() => {\n\t\t\tthis.#hideForKeys();\n\t\t}, keyRevealMs);\n\t\tthis.#renderReadout();\n\t}\n\n\t// Notches are evenly spaced, so a notched control stays linear\n\t#taper(): Taper | undefined {\n\t\tif (this.notched || this.wraps()) return undefined;\n\n\t\tconst min = this.min;\n\t\tconst max = this.#max();\n\t\tconst log = this.taper === 'log' ? logTaper(min, max) : undefined;\n\n\t\treturn log ?? skewTaper(min, max, this.midpoint ?? NaN);\n\t}\n\n\t#write(next: number): void {\n\t\tif (!Number.isFinite(next)) return;\n\n\t\tthis.#isDirty = true;\n\n\t\tconst clamped = this.#clamp(next);\n\t\tif (clamped === this.#value) return;\n\n\t\tthis.#value = clamped;\n\t\tthis.render();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -10,6 +10,8 @@ export declare class SonicSlider extends SonicRangeElement {
|
|
|
10
10
|
static readonly observedAttributes: string[];
|
|
11
11
|
get groovePress(): 'jump' | 'none';
|
|
12
12
|
set groovePress(gesture: 'jump' | 'none' | undefined);
|
|
13
|
+
get modulation(): number;
|
|
14
|
+
set modulation(amount: number | undefined);
|
|
13
15
|
get orientation(): 'horizontal' | 'vertical';
|
|
14
16
|
set orientation(direction: 'horizontal' | 'vertical' | undefined);
|
|
15
17
|
connectedCallback(): void;
|
|
@@ -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);
|
|
@@ -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.appendOnce(slider);\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\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\tif (positions === undefined) slider.style.removeProperty('--_sonic-slider-positions');\n\t\telse slider.style.setProperty('--_sonic-slider-positions', String(positions));\n\t\tthis.renderAria(slider, this.orientation);\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"],"mappings":";;;;AAgBA,MAAM,eAAe,SACT
|
|
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-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];\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\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('groovePress', 'modulation', '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.appendOnce(slider);\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.renderAria(slider, this.orientation);\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"],"mappings":";;;;AAgBA,MAAM,eAAe,SACT;;;;;;;;;;;;;;;;;;IAmBX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAGA,IAAI,cAA+B;EAClC,OAAO,KAAK,aAAa,cAAc,MAAM,SAAS,SAAS;CAChE;CAEA,IAAI,YAAY,SAAsC;EACrD,KAAK,QAAQ,gBAAgB,OAAO;CACrC;CAEA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;CAEA,IAAI,cAAyC;EAC5C,OAAO,KAAK,aAAa,aAAa,MAAM,aAAa,aAAa;CACvE;CAEA,IAAI,YAAY,WAAkD;EACjE,KAAK,QAAQ,eAAe,SAAS;CACtC;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,eAAe,cAAc,aAAa;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EACpB,MAAM,MAAM,aAAa,QAAQ,qBAAqB,cAAc;EAEpE,KAAK,WAAW,MAAM;EACtB,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,YAAY;EACrC,KAAK,aAAa,QAAQ,SAAS,UAAU;GAC5C,MAAM,OAAO,KAAK,MAAM,QAAQ,GAAG;GACnC,IAAI,MAAM,kBAAkB,QAAQ,IAAI,SAAS,MAAM,MAAM,GAAG,OAAO;GAEvE,IAAI,KAAK,gBAAgB,QAAQ;GAEjC,KAAK,MAAM,KAAK,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,QAAQ,CAAC;GAE9E,OAAO;EACR,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAU,SAAe;EACxB,MAAM,SAAS,KAAK;EACpB,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,CAAC,gBAAgB,gBAAgB,KAAK,oBAAoB,KAAK,UAAU;EAE/E,OAAO,MAAM,YAAY,yBAAyB,OAAO,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC;EACnF,OAAO,MAAM,YAAY,0BAA0B,OAAO,KAAK,eAAe,CAAC,CAAC;EAChF,OAAO,MAAM,YAAY,mCAAmC,OAAO,cAAc,CAAC;EAClF,OAAO,MAAM,YAAY,iCAAiC,OAAO,YAAY,CAAC;EAC9E,IAAI,cAAc,QAAW,OAAO,MAAM,eAAe,2BAA2B;OAC/E,OAAO,MAAM,YAAY,6BAA6B,OAAO,SAAS,CAAC;EAC5E,KAAK,WAAW,QAAQ,KAAK,WAAW;CACzC;CAEA,MAAM,QAAqB,KAA8B;EACxD,MAAM,QAAQ,OAAO,sBAAsB;EAC3C,MAAM,SAAS,IAAI,sBAAsB;EAEzC,IAAI,KAAK,gBAAgB,YACxB,OAAO;GACN,WAAW,UAAU,CAAC,MAAM;GAC5B,SAAS,OAAO,SAAS,IAAI,MAAM;GACnC,UAAU,KAAK,IAAI,GAAG,MAAM,SAAS,OAAO,MAAM;EACnD;EAGD,OAAO;GACN,WAAW,UAAU,MAAM;GAC3B,SAAS,MAAM,OAAO,OAAO,QAAQ;GACrC,UAAU,KAAK,IAAI,GAAG,MAAM,QAAQ,OAAO,KAAK;EACjD;CACD;AACD"}
|
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 };
|
|
@@ -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
|
@@ -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,5 +32,5 @@ 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 };
|
|
33
36
|
//# 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 clampUnit(fraction: number): number {\n\treturn Math.min(1, Math.max(0, fraction));\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"],"mappings":";AAMA,SAAgB,UAAU,UAA0B;CACnD,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AACzC;AAEA,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"}
|
|
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 clampUnit(fraction: number): number {\n\treturn Math.min(1, Math.max(0, fraction));\n}\n\nexport function wrapUnit(fraction: number): number {\n\treturn fraction - Math.floor(fraction);\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"],"mappings":";AAMA,SAAgB,UAAU,UAA0B;CACnD,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AACzC;AAEA,SAAgB,SAAS,UAA0B;CAClD,OAAO,WAAW,KAAK,MAAM,QAAQ;AACtC;AAEA,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"}
|
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);
|
|
@@ -28,13 +46,13 @@
|
|
|
28
46
|
|
|
29
47
|
/* Centred on six o'clock */
|
|
30
48
|
--_sonic-dial-sweep: var(--sonic-dial-sweep, 300deg);
|
|
49
|
+
--_sonic-dial-segment: var(--sonic-dial-segment, 30deg);
|
|
31
50
|
--_sonic-dial-ridge-pitch: var(--sonic-dial-ridge-pitch, 4px);
|
|
32
|
-
|
|
33
|
-
/* Read by the element as px */
|
|
34
51
|
--_sonic-dial-travel: var(--sonic-dial-travel, 160px);
|
|
35
52
|
--_sonic-unit: calc(var(--_sonic-dial-size) / 40);
|
|
36
53
|
--_sonic-dial-lit: var(--_sonic-lit);
|
|
37
54
|
--_sonic-dial-notch-band: 0px;
|
|
55
|
+
--_sonic-dial-notch-ends: 1;
|
|
38
56
|
--_sonic-dial-start: calc(180deg + (1turn - var(--_sonic-dial-sweep)) / 2);
|
|
39
57
|
--_sonic-dial-angle: calc(var(--_sonic-dial-value, 0) * var(--_sonic-dial-sweep));
|
|
40
58
|
--_sonic-dial-ring-width: calc(var(--_sonic-dial-size) * var(--_sonic-dial-ring-ratio));
|
|
@@ -72,6 +90,11 @@
|
|
|
72
90
|
--_sonic-cap: var(--_sonic-cap-hover);
|
|
73
91
|
}
|
|
74
92
|
|
|
93
|
+
[endless] > .sonic-dial {
|
|
94
|
+
--_sonic-dial-sweep: 1turn;
|
|
95
|
+
--_sonic-dial-notch-ends: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
75
98
|
[notched] > .sonic-dial {
|
|
76
99
|
--_sonic-dial-notch-band: calc(var(--_sonic-dial-size) * var(--_sonic-dial-notch-ratio));
|
|
77
100
|
}
|
|
@@ -90,7 +113,7 @@
|
|
|
90
113
|
.sonic-dial-notches {
|
|
91
114
|
--_sonic-dial-notch-width: atan2(var(--_sonic-unit), var(--_sonic-dial-size) / 2);
|
|
92
115
|
--_sonic-dial-notch-pitch: calc(
|
|
93
|
-
var(--_sonic-dial-sweep) / (var(--_sonic-dial-positions, 2) -
|
|
116
|
+
var(--_sonic-dial-sweep) / (var(--_sonic-dial-positions, 2) - var(--_sonic-dial-notch-ends))
|
|
94
117
|
);
|
|
95
118
|
|
|
96
119
|
position: absolute;
|
|
@@ -155,6 +178,17 @@
|
|
|
155
178
|
);
|
|
156
179
|
}
|
|
157
180
|
|
|
181
|
+
[endless] > .sonic-dial > .sonic-dial-ring {
|
|
182
|
+
background:
|
|
183
|
+
var(--_sonic-dial-ring-shade),
|
|
184
|
+
conic-gradient(
|
|
185
|
+
from
|
|
186
|
+
calc(var(--_sonic-dial-start) + var(--_sonic-dial-angle) - var(--_sonic-dial-segment) / 2),
|
|
187
|
+
var(--_sonic-dial-lit) var(--_sonic-dial-segment),
|
|
188
|
+
var(--_sonic-unlit) 0
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
158
192
|
.sonic-dial-modulation {
|
|
159
193
|
--_sonic-dial-modulation: var(
|
|
160
194
|
--sonic-modulation,
|
package/dist/styles/key.css
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
@property --_sonic-key-size {
|
|
2
|
+
syntax: '<length>';
|
|
3
|
+
inherits: true;
|
|
4
|
+
initial-value: 32px;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
@layer sonic {
|
|
2
8
|
:is(sonic-key, .sonic-key) {
|
|
3
9
|
--_sonic-key-size: var(--sonic-key-size, 2rem);
|
|
@@ -22,6 +28,8 @@
|
|
|
22
28
|
--_sonic-key-bevel-ratio: var(--sonic-key-bevel-ratio, 0.075);
|
|
23
29
|
--_sonic-key-face-radius-ratio: var(--sonic-key-face-radius-ratio, 0.15);
|
|
24
30
|
--_sonic-key-icon-ratio: var(--sonic-key-icon-ratio, 0.5);
|
|
31
|
+
--_sonic-key-led-ratio: var(--sonic-key-led-ratio, 0.2);
|
|
32
|
+
--_sonic-key-led-icon-ratio: var(--sonic-key-led-icon-ratio, 0.35);
|
|
25
33
|
--_sonic-key-depth-ratio: var(--sonic-key-depth-ratio, 0.03);
|
|
26
34
|
--_sonic-key-press-scale: var(--sonic-key-press-scale, 0.985);
|
|
27
35
|
--_sonic-unit: calc(var(--_sonic-key-size) / 32);
|
|
@@ -39,6 +47,7 @@
|
|
|
39
47
|
inline-size: var(--_sonic-key-size);
|
|
40
48
|
block-size: var(--_sonic-key-size);
|
|
41
49
|
padding: 0;
|
|
50
|
+
font: inherit;
|
|
42
51
|
color: var(--_sonic-ink);
|
|
43
52
|
cursor: pointer;
|
|
44
53
|
background: linear-gradient(
|
|
@@ -49,6 +58,7 @@
|
|
|
49
58
|
border: 0;
|
|
50
59
|
border-radius: var(--_sonic-key-corner);
|
|
51
60
|
box-shadow: var(--_sonic-well);
|
|
61
|
+
transition: color 120ms ease-out;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
/* A drifting finger would turn a held pad into a pan */
|
|
@@ -84,10 +94,25 @@
|
|
|
84
94
|
cursor: default;
|
|
85
95
|
}
|
|
86
96
|
|
|
97
|
+
.sonic-key:not([aria-pressed='true'], :disabled, :where([momentary]:state(pressed)) > *):is(
|
|
98
|
+
:focus-visible,
|
|
99
|
+
:active
|
|
100
|
+
) {
|
|
101
|
+
color: var(--_sonic-ink-lift);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (hover: hover) {
|
|
105
|
+
.sonic-key:not([aria-pressed='true'], :disabled, :where([momentary]:state(pressed)) > *):hover {
|
|
106
|
+
color: var(--_sonic-ink-lift);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
87
110
|
.sonic-key-cap {
|
|
88
111
|
position: absolute;
|
|
89
112
|
inset: var(--_sonic-key-gap);
|
|
90
113
|
display: grid;
|
|
114
|
+
gap: calc(var(--_sonic-key-size) * var(--_sonic-key-led-ratio) / 2);
|
|
115
|
+
place-content: center;
|
|
91
116
|
place-items: center;
|
|
92
117
|
background: var(--_sonic-skirt);
|
|
93
118
|
border-radius: max(0px, var(--_sonic-key-corner) - var(--_sonic-key-gap));
|
|
@@ -107,8 +132,18 @@
|
|
|
107
132
|
box-shadow: var(--_sonic-face-shadow);
|
|
108
133
|
}
|
|
109
134
|
|
|
110
|
-
& >
|
|
135
|
+
& > * {
|
|
111
136
|
position: relative;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
& > .sonic-led {
|
|
140
|
+
--_sonic-led-size: var(
|
|
141
|
+
--sonic-led-size,
|
|
142
|
+
calc(var(--_sonic-key-size) * var(--_sonic-key-led-ratio))
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
& > svg {
|
|
112
147
|
inline-size: calc(var(--_sonic-key-size) * var(--_sonic-key-icon-ratio));
|
|
113
148
|
block-size: calc(var(--_sonic-key-size) * var(--_sonic-key-icon-ratio));
|
|
114
149
|
filter: var(--_sonic-engraving);
|
|
@@ -117,6 +152,11 @@
|
|
|
117
152
|
}
|
|
118
153
|
}
|
|
119
154
|
|
|
155
|
+
.sonic-key-cap:has(> .sonic-led) > svg {
|
|
156
|
+
inline-size: calc(var(--_sonic-key-size) * var(--_sonic-key-led-icon-ratio));
|
|
157
|
+
block-size: calc(var(--_sonic-key-size) * var(--_sonic-key-led-icon-ratio));
|
|
158
|
+
}
|
|
159
|
+
|
|
120
160
|
/* Engraving under the glow keeps a lit icon's edge */
|
|
121
161
|
.sonic-key:is([aria-pressed='true'], :where([momentary]:state(pressed)) > *)
|
|
122
162
|
.sonic-key-cap
|