@xsynaptic/sonic-ui 0.17.0 → 0.18.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/CHANGELOG.md +13 -0
- package/README.md +112 -19
- package/dist/default/dat.js +1 -0
- package/dist/default/elements/button.js +2 -0
- package/dist/default/elements/dial.js +2 -0
- package/dist/default/elements/display-element.js +9 -7
- package/dist/default/elements/envelope.js +31 -72
- package/dist/default/elements/field-gesture.js +81 -0
- package/dist/default/elements/meter.js +4 -0
- package/dist/default/elements/position-group.js +42 -26
- package/dist/default/elements/radio-group.js +59 -56
- package/dist/default/elements/scale.js +3 -3
- package/dist/default/elements/slider.js +11 -13
- package/dist/default/elements/split.js +14 -13
- package/dist/default/elements/switch.js +31 -46
- package/dist/default/elements/toggle.js +20 -44
- package/dist/default/elements/value-element.js +65 -91
- package/dist/default/elements/value-gestures.js +22 -22
- package/dist/default/elements/wave-element.js +9 -12
- package/dist/default/elements/waveform.js +42 -241
- package/dist/default/elements/wavestrip.js +10 -5
- package/dist/default/elements/xy.js +67 -112
- package/dist/default/lib/canvas-surface.js +25 -0
- package/dist/default/lib/crossfade.js +1 -0
- package/dist/default/lib/envelope-curve.js +1 -0
- package/dist/default/lib/peaks-request.js +44 -0
- package/dist/default/lib/positions.js +58 -0
- package/dist/default/lib/scrub.js +12 -0
- package/dist/default/lib/value-model.js +62 -0
- package/dist/default/lib/waveform-scene.js +108 -0
- package/dist/default/lib/zoom-gesture.js +97 -1
- package/dist/dev/dat.d.ts +1 -0
- package/dist/dev/dat.js +1 -0
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +2 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +2 -0
- package/dist/dev/elements/dial.js +2 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +5 -3
- package/dist/dev/elements/display-element.js +9 -7
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +31 -72
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +82 -0
- package/dist/dev/elements/field-gesture.js.map +1 -0
- package/dist/dev/elements/meter.d.ts +10 -5
- package/dist/dev/elements/meter.js +4 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/position-group.d.ts +12 -6
- package/dist/dev/elements/position-group.js +42 -26
- package/dist/dev/elements/position-group.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +11 -7
- package/dist/dev/elements/radio-group.js +59 -56
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/scale.js +3 -3
- package/dist/dev/elements/scale.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +4 -2
- package/dist/dev/elements/slider.js +11 -13
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/split.d.ts +1 -0
- package/dist/dev/elements/split.js +14 -13
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +2 -2
- package/dist/dev/elements/switch.js +31 -46
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +20 -44
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +5 -5
- package/dist/dev/elements/value-element.js +65 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +22 -22
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +6 -4
- package/dist/dev/elements/wave-element.js +9 -12
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +19 -21
- package/dist/dev/elements/waveform.js +42 -241
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +5 -1
- package/dist/dev/elements/wavestrip.js +10 -5
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +67 -112
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +2 -1
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +25 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +1 -0
- package/dist/dev/lib/crossfade.js +1 -0
- package/dist/dev/lib/crossfade.js.map +1 -1
- package/dist/dev/lib/envelope-curve.d.ts +1 -0
- package/dist/dev/lib/envelope-curve.js +1 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -1
- package/dist/dev/lib/peaks-request.d.ts +5 -0
- package/dist/dev/lib/peaks-request.js +45 -0
- package/dist/dev/lib/peaks-request.js.map +1 -0
- package/dist/dev/lib/positions.d.ts +7 -0
- package/dist/dev/lib/positions.js +59 -0
- package/dist/dev/lib/positions.js.map +1 -0
- package/dist/dev/lib/scrub.d.ts +8 -0
- package/dist/dev/lib/scrub.js +13 -0
- package/dist/dev/lib/scrub.js.map +1 -0
- package/dist/dev/lib/time-regions.d.ts +1 -0
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/dev/lib/units.d.ts +8 -0
- package/dist/dev/lib/value-model.js +63 -0
- package/dist/dev/lib/value-model.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +1 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -1
- package/dist/dev/lib/waveform-scene.d.ts +11 -0
- package/dist/dev/lib/waveform-scene.js +109 -0
- package/dist/dev/lib/waveform-scene.js.map +1 -0
- package/dist/dev/lib/zoom-gesture.js +97 -1
- package/dist/dev/lib/zoom-gesture.js.map +1 -1
- package/dist/styles/button.css +16 -6
- package/dist/styles/waveform.css +2 -1
- package/package.json +1 -1
- package/dist/default/lib/asked-value.js +0 -21
- package/dist/default/lib/fill-watch.js +0 -29
- package/dist/dev/lib/asked-value.js +0 -22
- package/dist/dev/lib/asked-value.js.map +0 -1
- package/dist/dev/lib/fill-watch.js +0 -30
- package/dist/dev/lib/fill-watch.js.map +0 -1
|
@@ -2,15 +2,14 @@ import { clampProportion, toNumber } from "../lib/math.js";
|
|
|
2
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
4
|
import { requireChild, template } from "../lib/render.js";
|
|
5
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
6
5
|
import { Readout } from "./readout.js";
|
|
7
|
-
import { ReadoutClaim
|
|
8
|
-
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
6
|
+
import { ReadoutClaim } from "./readout-claim.js";
|
|
9
7
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
10
8
|
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
11
|
-
import { resetKeys
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { resetKeys } from "../lib/value-mapping.js";
|
|
10
|
+
import { createValueModel } from "../lib/value-model.js";
|
|
11
|
+
import { pointerPosition } from "../lib/field.js";
|
|
12
|
+
import { bindFieldGesture } from "./field-gesture.js";
|
|
14
13
|
|
|
15
14
|
//#region src/elements/xy.ts
|
|
16
15
|
const axes = ["x", "y"];
|
|
@@ -75,7 +74,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
75
74
|
this.render();
|
|
76
75
|
}
|
|
77
76
|
get pointerType() {
|
|
78
|
-
return this.#pointerType;
|
|
77
|
+
return this.#gesture?.pointerType();
|
|
79
78
|
}
|
|
80
79
|
get readout() {
|
|
81
80
|
return this.hasAttribute("readout");
|
|
@@ -84,7 +83,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
84
83
|
this.reflect("readout", isEnabled);
|
|
85
84
|
}
|
|
86
85
|
get x() {
|
|
87
|
-
return this.#
|
|
86
|
+
return this.#models.x.value;
|
|
88
87
|
}
|
|
89
88
|
set x(next) {
|
|
90
89
|
this.#write("x", next);
|
|
@@ -138,7 +137,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
138
137
|
this.reflect("x-taper", curve);
|
|
139
138
|
}
|
|
140
139
|
get y() {
|
|
141
|
-
return this.#
|
|
140
|
+
return this.#models.y.value;
|
|
142
141
|
}
|
|
143
142
|
set y(next) {
|
|
144
143
|
this.#write("y", next);
|
|
@@ -198,41 +197,28 @@ var SonicXy = class extends SonicFormElement {
|
|
|
198
197
|
#xy = renderXy();
|
|
199
198
|
#field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
|
|
200
199
|
#formatValue;
|
|
201
|
-
#
|
|
200
|
+
#gesture;
|
|
201
|
+
#models = {
|
|
202
|
+
x: createValueModel(() => this.#spec("x")),
|
|
203
|
+
y: createValueModel(() => this.#spec("y"))
|
|
204
|
+
};
|
|
202
205
|
#parts = {
|
|
203
206
|
x: requireChild(this.#xy, "[data-sonic-axis=\"x\"]", HTMLDivElement),
|
|
204
207
|
y: requireChild(this.#xy, "[data-sonic-axis=\"y\"]", HTMLDivElement)
|
|
205
208
|
};
|
|
206
|
-
#pointerDrag;
|
|
207
|
-
#pointerType;
|
|
208
209
|
#puck = requireChild(this.#xy, ".sonic-xy-puck", HTMLDivElement);
|
|
209
210
|
#readout = new Readout(requireChild(this.#xy, ".sonic-xy-readout", HTMLDivElement));
|
|
210
|
-
#values = {
|
|
211
|
-
x: {
|
|
212
|
-
asked: void 0,
|
|
213
|
-
value: 0
|
|
214
|
-
},
|
|
215
|
-
y: {
|
|
216
|
-
asked: void 0,
|
|
217
|
-
value: 0
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
211
|
attributeChangedCallback(name) {
|
|
221
|
-
this.#
|
|
222
|
-
if (name === "
|
|
223
|
-
|
|
224
|
-
this.#values[name].asked = void 0;
|
|
225
|
-
}
|
|
226
|
-
if (name === "disabled" && this.isDisabled()) this.#pointerDrag?.end();
|
|
227
|
-
this.#refresh();
|
|
212
|
+
if ((name === "x" || name === "y") && this.#gesture?.current()) return;
|
|
213
|
+
if (name === "disabled" && this.isDisabled()) this.#gesture?.end();
|
|
214
|
+
this.#respec(name);
|
|
228
215
|
}
|
|
229
216
|
connectedCallback() {
|
|
230
217
|
this.upgradeProperties("readout", "xDefault", "xLabel", "xMax", "xMidpoint", "xMin", "xOrigin", "xStep", "xTaper", "yDefault", "yLabel", "yMax", "yMidpoint", "yMin", "yOrigin", "yStep", "yTaper", "formatValue", "x", "y");
|
|
231
218
|
super.connectedCallback();
|
|
232
219
|
}
|
|
233
220
|
formResetCallback() {
|
|
234
|
-
|
|
235
|
-
this.#refresh();
|
|
221
|
+
this.#respec(...axes);
|
|
236
222
|
}
|
|
237
223
|
connect(signal) {
|
|
238
224
|
const xy = this.#xy;
|
|
@@ -254,11 +240,11 @@ var SonicXy = class extends SonicFormElement {
|
|
|
254
240
|
render() {
|
|
255
241
|
if (!this.isBound()) return;
|
|
256
242
|
const { style } = this.#xy;
|
|
257
|
-
const mappings = this.#mappings();
|
|
258
243
|
for (const axis of axes) {
|
|
259
|
-
const
|
|
244
|
+
const model = this.#models[axis];
|
|
245
|
+
const mapping = model.mapping();
|
|
260
246
|
const origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];
|
|
261
|
-
style.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(
|
|
247
|
+
style.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));
|
|
262
248
|
style.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));
|
|
263
249
|
}
|
|
264
250
|
this.#renderAria();
|
|
@@ -271,7 +257,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
271
257
|
this.y = y;
|
|
272
258
|
}
|
|
273
259
|
#axisText(axis) {
|
|
274
|
-
const { value } = this.#
|
|
260
|
+
const { value } = this.#models[axis];
|
|
275
261
|
return `${this.getAttribute(`${axis}-label`) ?? axis.toUpperCase()} ${this.#formatValue?.(value, axis) ?? String(value)}`;
|
|
276
262
|
}
|
|
277
263
|
#bindKeys(xy, signal) {
|
|
@@ -297,32 +283,22 @@ var SonicXy = class extends SonicFormElement {
|
|
|
297
283
|
xy.addEventListener("mousedown", (event) => {
|
|
298
284
|
event.preventDefault();
|
|
299
285
|
}, { signal });
|
|
300
|
-
this.#
|
|
301
|
-
|
|
302
|
-
this.#input(drag.from);
|
|
303
|
-
},
|
|
286
|
+
this.#gesture = bindFieldGesture(xy, {
|
|
287
|
+
claim: this.#claim,
|
|
304
288
|
grab: (event) => {
|
|
305
289
|
if (this.isDisabled() || isMenuPress(event)) return;
|
|
306
290
|
focusByPointer(this.focusTarget());
|
|
307
|
-
this.#pointerType = event.pointerType;
|
|
308
291
|
if (!isResetPress(event)) {
|
|
309
292
|
this.toggleState("dragging", true);
|
|
310
293
|
return this.#grab(event);
|
|
311
294
|
}
|
|
312
295
|
this.#reset();
|
|
313
|
-
this.#pointerType = void 0;
|
|
314
296
|
},
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
drag.state = step.state;
|
|
318
|
-
if (step.state.isEngaged) this.#claim.reveal("drag");
|
|
319
|
-
if (!this.#input(step)) this.#renderReadout();
|
|
297
|
+
input: (_hold, next) => {
|
|
298
|
+
if (!this.#input(next)) this.#renderReadout();
|
|
320
299
|
},
|
|
321
|
-
release: (
|
|
322
|
-
|
|
323
|
-
this.#claim.press(false);
|
|
324
|
-
if (hasMoved) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
325
|
-
this.#pointerType = void 0;
|
|
300
|
+
release: (_hold, moved) => {
|
|
301
|
+
if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
326
302
|
},
|
|
327
303
|
toggle: (isDragging) => {
|
|
328
304
|
this.toggleState("dragging", isDragging);
|
|
@@ -333,13 +309,13 @@ var SonicXy = class extends SonicFormElement {
|
|
|
333
309
|
if (this.#input(next)) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
334
310
|
}
|
|
335
311
|
#grab(event) {
|
|
336
|
-
const from = {
|
|
337
|
-
x: this.x,
|
|
338
|
-
y: this.y
|
|
339
|
-
};
|
|
340
312
|
const field = this.#field.getBoundingClientRect();
|
|
341
313
|
const puck = this.#puck.getBoundingClientRect();
|
|
342
|
-
const
|
|
314
|
+
const { x, y } = this.#models;
|
|
315
|
+
const from = {
|
|
316
|
+
x: x.value,
|
|
317
|
+
y: y.value
|
|
318
|
+
};
|
|
343
319
|
const travel = {
|
|
344
320
|
x: Math.max(1, field.width - puck.width),
|
|
345
321
|
y: Math.max(1, field.height - puck.height)
|
|
@@ -351,32 +327,26 @@ var SonicXy = class extends SonicFormElement {
|
|
|
351
327
|
};
|
|
352
328
|
const isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);
|
|
353
329
|
if (!isOnPuck) this.#input({
|
|
354
|
-
x:
|
|
355
|
-
y:
|
|
330
|
+
x: x.mapping().valueAt(pressed.x),
|
|
331
|
+
y: y.mapping().valueAt(pressed.y)
|
|
356
332
|
});
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
proportion: isOnPuck ? mappings[axis].proportionOf(this.#values[axis].value) : clampProportion(pressed[axis]),
|
|
333
|
+
const hold = (axis) => ({
|
|
334
|
+
from: from[axis],
|
|
335
|
+
model: this.#models[axis],
|
|
336
|
+
...isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) },
|
|
362
337
|
travelPx: travel[axis]
|
|
363
338
|
});
|
|
364
|
-
return {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
thresholdPx: dragThresholdPx(event.pointerType),
|
|
369
|
-
x: start("x"),
|
|
370
|
-
y: start("y")
|
|
371
|
-
})
|
|
372
|
-
};
|
|
339
|
+
return { axes: {
|
|
340
|
+
x: hold("x"),
|
|
341
|
+
y: hold("y")
|
|
342
|
+
} };
|
|
373
343
|
}
|
|
374
344
|
#hasDefault() {
|
|
375
345
|
return this.xDefault !== void 0 || this.yDefault !== void 0;
|
|
376
346
|
}
|
|
377
347
|
#input(next) {
|
|
378
348
|
let hasMoved = false;
|
|
379
|
-
for (const axis of axes) if (
|
|
349
|
+
for (const axis of axes) if (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;
|
|
380
350
|
if (!hasMoved) return false;
|
|
381
351
|
this.render();
|
|
382
352
|
this.dispatchEvent(new Event("input", {
|
|
@@ -386,9 +356,8 @@ var SonicXy = class extends SonicFormElement {
|
|
|
386
356
|
return true;
|
|
387
357
|
}
|
|
388
358
|
#keyTo(axis, key) {
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
const hasMoved = moveTo(this.#values[axis], mapping, next ?? NaN);
|
|
359
|
+
const model = this.#models[axis];
|
|
360
|
+
const hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);
|
|
392
361
|
this.#currentAxis = axis;
|
|
393
362
|
this.render();
|
|
394
363
|
this.#parts[axis].focus();
|
|
@@ -399,46 +368,18 @@ var SonicXy = class extends SonicFormElement {
|
|
|
399
368
|
}));
|
|
400
369
|
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
401
370
|
}
|
|
402
|
-
#mapping(axis) {
|
|
403
|
-
const midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);
|
|
404
|
-
return valueMapping({
|
|
405
|
-
max: this.numberAttribute(`${axis}-max`, 100),
|
|
406
|
-
...midpoint === void 0 ? {} : { midpoint },
|
|
407
|
-
isNotched: false,
|
|
408
|
-
isWrapping: false,
|
|
409
|
-
min: this.numberAttribute(`${axis}-min`, 0),
|
|
410
|
-
step: this.numberAttribute(`${axis}-step`, 1),
|
|
411
|
-
taper: this.#taper(axis)
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
#mappings() {
|
|
415
|
-
if (this.#mapped) return this.#mapped;
|
|
416
|
-
this.#mapped = {
|
|
417
|
-
x: this.#mapping("x"),
|
|
418
|
-
y: this.#mapping("y")
|
|
419
|
-
};
|
|
420
|
-
return this.#mapped;
|
|
421
|
-
}
|
|
422
|
-
#refresh() {
|
|
423
|
-
const mappings = this.#mappings();
|
|
424
|
-
for (const axis of axes) {
|
|
425
|
-
const mapping = mappings[axis];
|
|
426
|
-
resnap(this.#values[axis], mapping, this.numberAttribute(axis, mapping.bounds[0]));
|
|
427
|
-
}
|
|
428
|
-
this.render();
|
|
429
|
-
}
|
|
430
371
|
#renderAria() {
|
|
431
|
-
const mappings = this.#mappings();
|
|
432
372
|
const isDisabled = this.isDisabled();
|
|
433
373
|
for (const axis of axes) {
|
|
434
374
|
const part = this.#parts[axis];
|
|
435
|
-
const
|
|
375
|
+
const model = this.#models[axis];
|
|
376
|
+
const [low, high] = model.mapping().bounds;
|
|
436
377
|
const isCurrent = axis === this.#currentAxis;
|
|
437
378
|
const stop = isCurrent ? "0" : "-1";
|
|
438
379
|
writeAttribute(part, "aria-orientation", orientations[axis]);
|
|
439
380
|
writeAttribute(part, "aria-valuemin", String(low));
|
|
440
381
|
writeAttribute(part, "aria-valuemax", String(high));
|
|
441
|
-
writeAttribute(part, "aria-valuenow", String(
|
|
382
|
+
writeAttribute(part, "aria-valuenow", String(model.value));
|
|
442
383
|
writeAttribute(part, "aria-valuetext", this.#valueText(axis));
|
|
443
384
|
this.forwardNaming(part, true);
|
|
444
385
|
writeAttribute(part, "aria-hidden", isCurrent ? void 0 : "true");
|
|
@@ -471,6 +412,22 @@ var SonicXy = class extends SonicFormElement {
|
|
|
471
412
|
y: this.yDefault
|
|
472
413
|
});
|
|
473
414
|
}
|
|
415
|
+
#respec(...forgotten) {
|
|
416
|
+
for (const axis of axes) this.#models[axis].respec(this.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)), { forgetAsk: forgotten.includes(axis) });
|
|
417
|
+
this.render();
|
|
418
|
+
}
|
|
419
|
+
#spec(axis) {
|
|
420
|
+
const midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);
|
|
421
|
+
return {
|
|
422
|
+
max: this.numberAttribute(`${axis}-max`, 100),
|
|
423
|
+
...midpoint === void 0 ? {} : { midpoint },
|
|
424
|
+
isNotched: false,
|
|
425
|
+
isWrapping: false,
|
|
426
|
+
min: this.numberAttribute(`${axis}-min`, 0),
|
|
427
|
+
step: this.numberAttribute(`${axis}-step`, 1),
|
|
428
|
+
taper: this.#taper(axis)
|
|
429
|
+
};
|
|
430
|
+
}
|
|
474
431
|
#taper(axis) {
|
|
475
432
|
return this.getAttribute(`${axis}-taper`) === "log" ? "log" : "linear";
|
|
476
433
|
}
|
|
@@ -478,14 +435,12 @@ var SonicXy = class extends SonicFormElement {
|
|
|
478
435
|
return `${this.#axisText(axis)}, ${this.#axisText(axis === "x" ? "y" : "x")}`;
|
|
479
436
|
}
|
|
480
437
|
#write(axis, next) {
|
|
481
|
-
if (this.#
|
|
482
|
-
const axisValue = this.#values[axis];
|
|
438
|
+
if (this.#gesture?.current()) return;
|
|
483
439
|
if (next === void 0 || next === null) {
|
|
484
|
-
|
|
485
|
-
this.#refresh();
|
|
440
|
+
this.#respec(axis);
|
|
486
441
|
return;
|
|
487
442
|
}
|
|
488
|
-
if (
|
|
443
|
+
if (this.#models[axis].write(toNumber(next))) this.render();
|
|
489
444
|
}
|
|
490
445
|
};
|
|
491
446
|
|
|
@@ -22,6 +22,7 @@ var CanvasSurface = class {
|
|
|
22
22
|
}
|
|
23
23
|
#context;
|
|
24
24
|
#cssBox;
|
|
25
|
+
#fillWatch;
|
|
25
26
|
#frame;
|
|
26
27
|
#isDirty = true;
|
|
27
28
|
#isRebuildWanted = true;
|
|
@@ -51,6 +52,7 @@ var CanvasSurface = class {
|
|
|
51
52
|
signal.addEventListener("abort", () => {
|
|
52
53
|
if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
|
|
53
54
|
this.#frame = void 0;
|
|
55
|
+
this.#stopFill();
|
|
54
56
|
}, { once: true });
|
|
55
57
|
this.#schedule();
|
|
56
58
|
}
|
|
@@ -65,6 +67,25 @@ var CanvasSurface = class {
|
|
|
65
67
|
requestFrame() {
|
|
66
68
|
this.#schedule();
|
|
67
69
|
}
|
|
70
|
+
setFill(isFilling) {
|
|
71
|
+
const { fill, signal } = this.#options;
|
|
72
|
+
if (!fill || signal.aborted || isFilling === (this.#fillWatch !== void 0)) return;
|
|
73
|
+
const { style } = fill.control;
|
|
74
|
+
this.#stopFill();
|
|
75
|
+
if (!isFilling) {
|
|
76
|
+
style.removeProperty(fill.sizeProperty);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const started = new ResizeObserver((entries) => {
|
|
80
|
+
const box = entries.at(-1)?.borderBoxSize[0];
|
|
81
|
+
if (!box) return;
|
|
82
|
+
requestAnimationFrame(() => {
|
|
83
|
+
if (this.#fillWatch === started) style.setProperty(fill.sizeProperty, `${String(box.blockSize)}px`);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
this.#fillWatch = started;
|
|
87
|
+
started.observe(fill.control);
|
|
88
|
+
}
|
|
68
89
|
#armTransitions() {
|
|
69
90
|
const { canvas, colours, numbers = {} } = this.#options;
|
|
70
91
|
const { style } = canvas;
|
|
@@ -190,6 +211,10 @@ var CanvasSurface = class {
|
|
|
190
211
|
this.#draw(frameMs);
|
|
191
212
|
});
|
|
192
213
|
}
|
|
214
|
+
#stopFill() {
|
|
215
|
+
this.#fillWatch?.disconnect();
|
|
216
|
+
this.#fillWatch = void 0;
|
|
217
|
+
}
|
|
193
218
|
#watchResolution() {
|
|
194
219
|
const { signal } = this.#options;
|
|
195
220
|
matchMedia(`(resolution: ${String(deviceRatio())}dppx)`).addEventListener("change", () => {
|
|
@@ -5,6 +5,7 @@ function progress(side, span) {
|
|
|
5
5
|
if (span === 0) return side > 0 ? 1 : 0;
|
|
6
6
|
return Math.min(1, side / span);
|
|
7
7
|
}
|
|
8
|
+
/** `position` runs from 0, all a, to 1, all b; `sharpness` from 0 to 1 shortens the overlap */
|
|
8
9
|
function crossfadeGains(position, options = {}) {
|
|
9
10
|
const at = clampProportion(position);
|
|
10
11
|
const span = 1 - clampProportion(options.sharpness ?? 0);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//#region src/lib/envelope-curve.ts
|
|
2
2
|
const flatCurve = .001;
|
|
3
|
+
/** `position` is 0 to 1 along a stage; a `curve` of 0 is a straight line */
|
|
3
4
|
function envelopeCurve(position, curve) {
|
|
4
5
|
if (Math.abs(curve) < flatCurve) return position;
|
|
5
6
|
return Math.expm1(curve * position) / Math.expm1(curve);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/lib/peaks-request.ts
|
|
2
|
+
function createPeaksRequest(repaint) {
|
|
3
|
+
const awaited = /* @__PURE__ */ new WeakSet();
|
|
4
|
+
let askedKey;
|
|
5
|
+
let isAsking = false;
|
|
6
|
+
const changed = () => {
|
|
7
|
+
if (!isAsking) askedKey = void 0;
|
|
8
|
+
repaint();
|
|
9
|
+
};
|
|
10
|
+
const changedAfter = async (asked) => {
|
|
11
|
+
await Promise.allSettled([asked]);
|
|
12
|
+
changed();
|
|
13
|
+
};
|
|
14
|
+
const call = (request, wanted) => {
|
|
15
|
+
isAsking = true;
|
|
16
|
+
try {
|
|
17
|
+
return request?.(...wanted);
|
|
18
|
+
} finally {
|
|
19
|
+
isAsking = false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
ask: (request, wanted) => {
|
|
24
|
+
const key = wanted?.join(":");
|
|
25
|
+
if (!wanted || key === askedKey) return;
|
|
26
|
+
askedKey = key;
|
|
27
|
+
const asked = call(request, wanted);
|
|
28
|
+
const promises = asked instanceof Promise ? [asked] : asked ?? [];
|
|
29
|
+
for (const promise of promises) {
|
|
30
|
+
if (awaited.has(promise)) continue;
|
|
31
|
+
awaited.add(promise);
|
|
32
|
+
changedAfter(promise);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
changed,
|
|
36
|
+
reset: () => {
|
|
37
|
+
askedKey = void 0;
|
|
38
|
+
repaint();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { createPeaksRequest };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/positions.ts
|
|
4
|
+
const keySteps = /* @__PURE__ */ new Map([
|
|
5
|
+
["ArrowDown", 1],
|
|
6
|
+
["ArrowLeft", -1],
|
|
7
|
+
["ArrowRight", 1],
|
|
8
|
+
["ArrowUp", -1]
|
|
9
|
+
]);
|
|
10
|
+
function indexOrUndefined(index) {
|
|
11
|
+
return index < 0 ? void 0 : index;
|
|
12
|
+
}
|
|
13
|
+
function isEnabled(position) {
|
|
14
|
+
return !position.isDisabled;
|
|
15
|
+
}
|
|
16
|
+
function enabledEnds(positions) {
|
|
17
|
+
return [positions.findIndex(isEnabled), positions.findLastIndex(isEnabled)];
|
|
18
|
+
}
|
|
19
|
+
function stepFrom(positions, from, { isWrapping, step }) {
|
|
20
|
+
const count = positions.length;
|
|
21
|
+
let index = from;
|
|
22
|
+
for (const _ of positions) {
|
|
23
|
+
const next = index + step;
|
|
24
|
+
index = isWrapping ? (next + count) % count : clamp(next, 0, count - 1);
|
|
25
|
+
if (positions[index]?.isDisabled === false) return index;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function keyTarget(positions, from, { isWrapping, key }) {
|
|
29
|
+
const [first, last] = enabledEnds(positions);
|
|
30
|
+
if (key === "Home") return indexOrUndefined(first);
|
|
31
|
+
if (key === "End") return indexOrUndefined(last);
|
|
32
|
+
const step = keySteps.get(key);
|
|
33
|
+
if (step === void 0) return void 0;
|
|
34
|
+
return stepFrom(positions, from, {
|
|
35
|
+
isWrapping,
|
|
36
|
+
step
|
|
37
|
+
}) ?? from;
|
|
38
|
+
}
|
|
39
|
+
function isMomentaryAt(positions, index) {
|
|
40
|
+
return (index === 0 || index === positions.length - 1) && positions.length > 1 && positions[index]?.isMomentary === true;
|
|
41
|
+
}
|
|
42
|
+
function pressTarget(positions, checked, pressed) {
|
|
43
|
+
return positions.length === 2 && checked >= 0 ? 1 - checked : pressed;
|
|
44
|
+
}
|
|
45
|
+
function soleStep(positions, from) {
|
|
46
|
+
const ways = [-1, 1].map((step) => stepFrom(positions, from, {
|
|
47
|
+
isWrapping: false,
|
|
48
|
+
step
|
|
49
|
+
})).filter((to) => to !== void 0 && to !== from);
|
|
50
|
+
return ways.length === 1 ? ways[0] : void 0;
|
|
51
|
+
}
|
|
52
|
+
function restOf(positions, held) {
|
|
53
|
+
if (!isMomentaryAt(positions, held)) return void 0;
|
|
54
|
+
return held === 0 ? 1 : held - 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { enabledEnds, isMomentaryAt, keyTarget, pressTarget, restOf, soleStep, stepFrom };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/lib/scrub.ts
|
|
2
|
+
function scrubRegions(state, value, mapping) {
|
|
3
|
+
const played = mapping.proportionOf(state.played);
|
|
4
|
+
const reached = mapping.proportionOf(value);
|
|
5
|
+
return {
|
|
6
|
+
played,
|
|
7
|
+
scrub: state.from !== void 0 && state.isRevealed && reached !== played ? reached : void 0
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { scrubRegions };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
2
|
+
import { valueMapping } from "./value-mapping.js";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/value-model.ts
|
|
5
|
+
function createValueModel(readSpec) {
|
|
6
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7
|
+
let asked;
|
|
8
|
+
let bounds;
|
|
9
|
+
let landing;
|
|
10
|
+
let mapped;
|
|
11
|
+
let value = 0;
|
|
12
|
+
function mapping() {
|
|
13
|
+
if (mapped) return mapped;
|
|
14
|
+
mapped = valueMapping(readSpec());
|
|
15
|
+
return mapped;
|
|
16
|
+
}
|
|
17
|
+
function moveTo(next) {
|
|
18
|
+
if (!Number.isFinite(next)) return false;
|
|
19
|
+
const snapped = mapping().snap(next);
|
|
20
|
+
asked = snapped;
|
|
21
|
+
if (snapped === value) return false;
|
|
22
|
+
value = snapped;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
input: (next) => moveTo(bounds ? clamp(next, ...bounds) : next),
|
|
27
|
+
land: (target, direction) => landing?.(target, direction) ?? target,
|
|
28
|
+
limit: () => bounds,
|
|
29
|
+
mapping,
|
|
30
|
+
notify: () => {
|
|
31
|
+
for (const listener of listeners) listener();
|
|
32
|
+
},
|
|
33
|
+
respec: (fallback, options) => {
|
|
34
|
+
mapped = void 0;
|
|
35
|
+
if (options?.forgetAsk === true) asked = void 0;
|
|
36
|
+
value = mapping().snap(asked ?? fallback);
|
|
37
|
+
},
|
|
38
|
+
setLanding: (resolve) => {
|
|
39
|
+
landing = resolve;
|
|
40
|
+
},
|
|
41
|
+
setLimit: (next) => {
|
|
42
|
+
bounds = next;
|
|
43
|
+
},
|
|
44
|
+
get value() {
|
|
45
|
+
return value;
|
|
46
|
+
},
|
|
47
|
+
watch: (listener) => {
|
|
48
|
+
listeners.add(listener);
|
|
49
|
+
return () => {
|
|
50
|
+
listeners.delete(listener);
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
write: (next) => {
|
|
54
|
+
const hasMoved = moveTo(next);
|
|
55
|
+
if (Number.isFinite(next)) asked = next;
|
|
56
|
+
return hasMoved;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { createValueModel };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
2
|
+
import { isKind } from "./marker-band.js";
|
|
3
|
+
import { waveformBuckets } from "./waveform-buckets.js";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/waveform-scene.ts
|
|
6
|
+
const waveformColours = {
|
|
7
|
+
ends: "--_sonic-waveform-ends",
|
|
8
|
+
grid: "--_sonic-waveform-grid",
|
|
9
|
+
marker: "--_sonic-waveform-marker",
|
|
10
|
+
placeholder: "--_sonic-waveform-placeholder",
|
|
11
|
+
wave: "--_sonic-waveform-wave",
|
|
12
|
+
waveEdge: "--_sonic-waveform-wave-edge"
|
|
13
|
+
};
|
|
14
|
+
const amplitudeMargin = .94;
|
|
15
|
+
const hatchPitchPx = 8;
|
|
16
|
+
const placeholderWavelengthPx = 48;
|
|
17
|
+
const placeholderAmplitude = .2;
|
|
18
|
+
function paintHatch(context, scene, [from, to]) {
|
|
19
|
+
if (to <= from) return;
|
|
20
|
+
const { dpr, height } = scene;
|
|
21
|
+
const pitch = Math.max(2, Math.round(hatchPitchPx * dpr));
|
|
22
|
+
const offset = scene.startSeconds * scene.pixelsPerSecond;
|
|
23
|
+
context.save();
|
|
24
|
+
context.beginPath();
|
|
25
|
+
context.rect(from, 0, to - from, height);
|
|
26
|
+
context.clip();
|
|
27
|
+
context.beginPath();
|
|
28
|
+
for (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {
|
|
29
|
+
context.moveTo(x, height);
|
|
30
|
+
context.lineTo(x + height, 0);
|
|
31
|
+
}
|
|
32
|
+
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
33
|
+
context.strokeStyle = scene.colours.ends;
|
|
34
|
+
context.stroke();
|
|
35
|
+
context.restore();
|
|
36
|
+
}
|
|
37
|
+
function paintPlaceholders(context, scene) {
|
|
38
|
+
if (scene.pending.length === 0) return;
|
|
39
|
+
const { dpr, height, pixelsPerSecond, startSeconds } = scene;
|
|
40
|
+
const wavelength = placeholderWavelengthPx * dpr;
|
|
41
|
+
const center = height / 2;
|
|
42
|
+
const origin = startSeconds * pixelsPerSecond;
|
|
43
|
+
const waveY = (x) => center - Math.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) * center * placeholderAmplitude;
|
|
44
|
+
context.beginPath();
|
|
45
|
+
for (const [from, to] of scene.pending) {
|
|
46
|
+
const opening = (from - startSeconds) * pixelsPerSecond;
|
|
47
|
+
const closing = (to - startSeconds) * pixelsPerSecond;
|
|
48
|
+
context.moveTo(opening, waveY(opening));
|
|
49
|
+
for (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));
|
|
50
|
+
context.lineTo(closing, waveY(closing));
|
|
51
|
+
}
|
|
52
|
+
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
53
|
+
context.strokeStyle = scene.colours.placeholder;
|
|
54
|
+
context.stroke();
|
|
55
|
+
}
|
|
56
|
+
function waveFill(context, scene) {
|
|
57
|
+
const { wave, waveEdge } = scene.colours;
|
|
58
|
+
const gradient = context.createLinearGradient(0, 0, 0, scene.height);
|
|
59
|
+
gradient.addColorStop(0, waveEdge);
|
|
60
|
+
gradient.addColorStop(.5, wave);
|
|
61
|
+
gradient.addColorStop(1, waveEdge);
|
|
62
|
+
return gradient;
|
|
63
|
+
}
|
|
64
|
+
function paintWave(context, scene, peaks) {
|
|
65
|
+
const buckets = waveformBuckets(peaks, scene);
|
|
66
|
+
const [first] = buckets;
|
|
67
|
+
if (!first) return;
|
|
68
|
+
const center = scene.height / 2;
|
|
69
|
+
const amplitudePx = center * amplitudeMargin;
|
|
70
|
+
context.fillStyle = waveFill(context, scene);
|
|
71
|
+
context.beginPath();
|
|
72
|
+
context.moveTo(first.x, center);
|
|
73
|
+
for (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);
|
|
74
|
+
for (const bucket of buckets.toReversed()) context.lineTo(bucket.x, center - bucket.low * amplitudePx);
|
|
75
|
+
context.closePath();
|
|
76
|
+
context.fill();
|
|
77
|
+
}
|
|
78
|
+
function paintMarkers(context, scene, drawn) {
|
|
79
|
+
const line = Math.max(1, Math.round(scene.dpr));
|
|
80
|
+
for (const { dimmed, kind, start } of drawn.markers) {
|
|
81
|
+
const x = (start - scene.startSeconds) * scene.pixelsPerSecond;
|
|
82
|
+
context.fillStyle = (isKind(kind) ? drawn.kindColours.get(kind) : void 0) ?? scene.colours.marker;
|
|
83
|
+
context.globalAlpha = dimmed === true ? .5 : 1;
|
|
84
|
+
context.fillRect(x - line / 2, 0, line, scene.height);
|
|
85
|
+
}
|
|
86
|
+
context.globalAlpha = 1;
|
|
87
|
+
}
|
|
88
|
+
function paintWaveform(context, scene, drawn) {
|
|
89
|
+
const { dpr, height, pixelsPerSecond, startSeconds, width } = scene;
|
|
90
|
+
const x = (seconds) => (seconds - startSeconds) * pixelsPerSecond;
|
|
91
|
+
const { ends, grid } = scene.colours;
|
|
92
|
+
const line = Math.max(1, Math.round(dpr));
|
|
93
|
+
const [opening, closing] = [x(drawn.range[0]), x(drawn.range[1])];
|
|
94
|
+
const [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];
|
|
95
|
+
context.clearRect(0, 0, width, height);
|
|
96
|
+
paintHatch(context, scene, [0, from]);
|
|
97
|
+
paintHatch(context, scene, [to, width]);
|
|
98
|
+
context.fillStyle = grid;
|
|
99
|
+
if (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);
|
|
100
|
+
paintPlaceholders(context, scene);
|
|
101
|
+
if (drawn.peaks) paintWave(context, scene, drawn.peaks);
|
|
102
|
+
context.fillStyle = ends;
|
|
103
|
+
for (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);
|
|
104
|
+
paintMarkers(context, scene, drawn);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
export { paintWaveform, waveformColours };
|