@xsynaptic/sonic-ui 0.26.0 → 0.28.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 +20 -0
- package/README.md +1 -0
- package/dist/default/define/ticker.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
- package/dist/default/elements/button.js +12 -9
- package/dist/default/elements/dial.js +9 -13
- package/dist/default/elements/envelope.js +26 -23
- package/dist/default/elements/field-gesture.js +49 -8
- package/dist/default/elements/meter.js +6 -8
- package/dist/default/elements/number.js +5 -14
- package/dist/default/elements/position-group.js +4 -4
- package/dist/default/elements/radio-group.js +6 -9
- package/dist/default/elements/segmented.js +7 -4
- package/dist/default/elements/slider.js +12 -13
- package/dist/default/elements/sonic-element.js +7 -5
- package/dist/default/elements/spectrum.js +3 -3
- package/dist/default/elements/switch.js +11 -6
- package/dist/default/elements/ticker.js +313 -0
- package/dist/default/elements/toggle.js +14 -8
- package/dist/default/elements/value-element.js +11 -3
- package/dist/default/elements/wave-element.js +11 -12
- package/dist/default/elements/waveform.js +1 -4
- package/dist/default/elements/wavestrip.js +3 -6
- package/dist/default/elements/xy.js +34 -47
- package/dist/default/index.js +2 -1
- package/dist/default/lib/cancel-zone.js +12 -0
- package/dist/default/lib/canvas-surface.js +13 -1
- package/dist/default/lib/distribute.js +2 -3
- package/dist/default/lib/mirror-children.js +1 -1
- package/dist/default/lib/pointer-drag.js +8 -2
- package/dist/default/lib/render.js +2 -2
- package/dist/default/lib/ticker-leg.js +54 -0
- package/dist/dev/define/ticker.d.ts +2 -0
- package/dist/dev/define/ticker.js +8 -0
- package/dist/dev/define/ticker.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
- package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
- package/dist/dev/elements/analyser-element.js.map +1 -0
- package/dist/dev/elements/button.d.ts +4 -1
- package/dist/dev/elements/button.js +12 -14
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +4 -2
- package/dist/dev/elements/dial.js +9 -17
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +5 -1
- package/dist/dev/elements/envelope.js +26 -27
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +49 -8
- package/dist/dev/elements/field-gesture.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +2 -1
- package/dist/dev/elements/meter.js +6 -9
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +2 -2
- package/dist/dev/elements/number.js +5 -15
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/position-group.js +4 -4
- package/dist/dev/elements/position-group.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +1 -2
- package/dist/dev/elements/radio-group.js +6 -9
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/segmented.d.ts +4 -2
- package/dist/dev/elements/segmented.js +7 -9
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +4 -2
- package/dist/dev/elements/slider.js +12 -17
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +6 -7
- package/dist/dev/elements/sonic-element.js +14 -83
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +2 -2
- package/dist/dev/elements/spectrum.js +3 -3
- package/dist/dev/elements/spectrum.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +4 -1
- package/dist/dev/elements/switch.js +11 -10
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/ticker.d.ts +25 -0
- package/dist/dev/elements/ticker.js +314 -0
- package/dist/dev/elements/ticker.js.map +1 -0
- package/dist/dev/elements/toggle.d.ts +4 -1
- package/dist/dev/elements/toggle.js +14 -13
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +4 -3
- package/dist/dev/elements/value-element.js +11 -3
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +2 -2
- package/dist/dev/elements/wave-element.js +11 -16
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +0 -1
- package/dist/dev/elements/waveform.js +1 -4
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.js +3 -6
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +4 -1
- package/dist/dev/elements/xy.js +34 -51
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +4 -1
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/cancel-zone.js +13 -0
- package/dist/dev/lib/cancel-zone.js.map +1 -0
- package/dist/dev/lib/canvas-surface.js +13 -1
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/check-styles.d.ts +8 -0
- package/dist/dev/lib/check-styles.js +95 -0
- package/dist/dev/lib/check-styles.js.map +1 -0
- package/dist/dev/lib/distribute.js +2 -3
- package/dist/dev/lib/distribute.js.map +1 -1
- package/dist/dev/lib/mirror-children.js +9 -7
- package/dist/dev/lib/mirror-children.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +8 -2
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/render.js +2 -2
- package/dist/dev/lib/render.js.map +1 -1
- package/dist/dev/lib/ticker-leg.js +55 -0
- package/dist/dev/lib/ticker-leg.js.map +1 -0
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +28 -5
- package/dist/styles/material/cap.css +16 -5
- package/dist/styles/material/core.css +1 -0
- package/dist/styles/slider.css +2 -1
- package/dist/styles/switch.css +3 -1
- package/dist/styles/ticker.css +115 -0
- package/dist/styles/toggle.css +7 -5
- package/package.json +2 -1
- package/dist/dev/elements/display-element.js.map +0 -1
package/dist/dev/elements/xy.js
CHANGED
|
@@ -3,10 +3,7 @@ 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
5
|
import { Readout } from "./readout.js";
|
|
6
|
-
import { ReadoutClaim } from "./readout-claim.js";
|
|
7
|
-
import { createDoublePress } from "../lib/double-press.js";
|
|
8
6
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
9
|
-
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
10
7
|
import { resetKeys } from "../lib/value-mapping.js";
|
|
11
8
|
import { createValueModel } from "../lib/value-model.js";
|
|
12
9
|
import { pointerPosition } from "../lib/field.js";
|
|
@@ -203,26 +200,26 @@ var SonicXy = class extends SonicFormElement {
|
|
|
203
200
|
set yTaper(curve) {
|
|
204
201
|
this.reflect("y-taper", curve);
|
|
205
202
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
boxProbe = {
|
|
204
|
+
property: "margin-bottom",
|
|
205
|
+
selector: "[popover]"
|
|
206
|
+
};
|
|
207
|
+
control = renderXy();
|
|
208
|
+
sheet = "xy.css";
|
|
209
209
|
#currentAxis = "x";
|
|
210
|
-
#
|
|
211
|
-
#xy = renderXy();
|
|
212
|
-
#field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
|
|
210
|
+
#field = requireChild(this.control, ".sonic-xy-field", HTMLDivElement);
|
|
213
211
|
#formatValue;
|
|
214
212
|
#gesture;
|
|
215
|
-
#isRevealed = false;
|
|
216
213
|
#models = {
|
|
217
214
|
x: createValueModel(() => this.#spec("x")),
|
|
218
215
|
y: createValueModel(() => this.#spec("y"))
|
|
219
216
|
};
|
|
220
217
|
#parts = {
|
|
221
|
-
x: requireChild(this
|
|
222
|
-
y: requireChild(this
|
|
218
|
+
x: requireChild(this.control, "[data-sonic-axis=\"x\"]", HTMLDivElement),
|
|
219
|
+
y: requireChild(this.control, "[data-sonic-axis=\"y\"]", HTMLDivElement)
|
|
223
220
|
};
|
|
224
|
-
#puck = requireChild(this
|
|
225
|
-
#readout = new Readout(requireChild(this
|
|
221
|
+
#puck = requireChild(this.control, ".sonic-xy-puck", HTMLDivElement);
|
|
222
|
+
#readout = new Readout(requireChild(this.control, ".sonic-xy-readout", HTMLDivElement));
|
|
226
223
|
attributeChangedCallback(name) {
|
|
227
224
|
if ((name === "x" || name === "y") && this.#gesture?.current()) return;
|
|
228
225
|
if (name === "disabled" && this.isDisabled()) this.#gesture?.end();
|
|
@@ -237,13 +234,9 @@ var SonicXy = class extends SonicFormElement {
|
|
|
237
234
|
this.#respec(...axes);
|
|
238
235
|
}
|
|
239
236
|
connect(signal) {
|
|
240
|
-
const xy = this
|
|
241
|
-
this.keepControl(
|
|
237
|
+
const xy = this.control;
|
|
238
|
+
this.keepControl(signal);
|
|
242
239
|
this.render();
|
|
243
|
-
this.checkStyles(xy, "xy.css", {
|
|
244
|
-
property: "margin-bottom",
|
|
245
|
-
selector: "[popover]"
|
|
246
|
-
});
|
|
247
240
|
this.#bindPointer(xy, signal);
|
|
248
241
|
this.#bindKeys(xy, signal);
|
|
249
242
|
}
|
|
@@ -252,7 +245,7 @@ var SonicXy = class extends SonicFormElement {
|
|
|
252
245
|
}
|
|
253
246
|
render() {
|
|
254
247
|
if (!this.isBound()) return;
|
|
255
|
-
const { style } = this
|
|
248
|
+
const { style } = this.control;
|
|
256
249
|
for (const axis of axes) {
|
|
257
250
|
const model = this.#models[axis];
|
|
258
251
|
const mapping = model.mapping();
|
|
@@ -269,9 +262,6 @@ var SonicXy = class extends SonicFormElement {
|
|
|
269
262
|
this.x = x;
|
|
270
263
|
this.y = y;
|
|
271
264
|
}
|
|
272
|
-
stateTarget() {
|
|
273
|
-
return this.#xy;
|
|
274
|
-
}
|
|
275
265
|
#axisText(axis) {
|
|
276
266
|
const { value } = this.#models[axis];
|
|
277
267
|
return `${this.getAttribute(`${axis}-label`) ?? axis.toUpperCase()} ${this.#formatValue?.(value, axis) ?? String(value)}`;
|
|
@@ -282,49 +272,47 @@ var SonicXy = class extends SonicFormElement {
|
|
|
282
272
|
const move = keyMoves.get(event.key);
|
|
283
273
|
if (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;
|
|
284
274
|
event.preventDefault();
|
|
285
|
-
this.#
|
|
286
|
-
this.#renderReadout();
|
|
275
|
+
this.#gesture?.revealKeys();
|
|
287
276
|
if (move) this.#keyTo(...move);
|
|
288
277
|
else this.#reset();
|
|
289
278
|
}, { signal });
|
|
290
279
|
xy.addEventListener("focusout", (event) => {
|
|
291
280
|
if (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;
|
|
292
|
-
|
|
281
|
+
this.#gesture?.concealKeys();
|
|
293
282
|
}, { signal });
|
|
294
283
|
signal.addEventListener("abort", () => {
|
|
295
|
-
|
|
284
|
+
this.#gesture?.concealKeys();
|
|
296
285
|
}, { once: true });
|
|
297
286
|
}
|
|
298
287
|
#bindPointer(xy, signal) {
|
|
299
288
|
xy.addEventListener("mousedown", (event) => {
|
|
300
289
|
event.preventDefault();
|
|
301
290
|
}, { signal });
|
|
302
|
-
xy.addEventListener("pointercancel", () => {
|
|
303
|
-
this.#doublePress.forget();
|
|
304
|
-
}, { signal });
|
|
305
291
|
this.#gesture = bindFieldGesture(xy, {
|
|
306
|
-
|
|
292
|
+
element: this,
|
|
307
293
|
grab: (event) => {
|
|
308
|
-
if (this.isDisabled()
|
|
294
|
+
if (this.isDisabled()) return;
|
|
309
295
|
focusByPointer(this.focusTarget());
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return this.#grab(event);
|
|
313
|
-
}
|
|
314
|
-
this.#reset();
|
|
296
|
+
this.toggleState("dragging", true);
|
|
297
|
+
return this.#grab(event);
|
|
315
298
|
},
|
|
316
299
|
input: (_hold, next) => {
|
|
317
|
-
|
|
318
|
-
},
|
|
319
|
-
lift: (_hold, event) => {
|
|
320
|
-
if (!this.#doublePress.press(event) || this.doublePress !== "reset") return;
|
|
321
|
-
this.#reset();
|
|
300
|
+
this.#input(next);
|
|
322
301
|
},
|
|
323
302
|
release: (_hold, moved) => {
|
|
324
303
|
if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
325
304
|
},
|
|
326
|
-
|
|
327
|
-
this
|
|
305
|
+
renderReadout: () => {
|
|
306
|
+
this.#renderReadout();
|
|
307
|
+
},
|
|
308
|
+
reset: () => {
|
|
309
|
+
if (this.isDisabled()) return;
|
|
310
|
+
focusByPointer(this.focusTarget());
|
|
311
|
+
this.#reset();
|
|
312
|
+
},
|
|
313
|
+
resetsOnDoublePress: () => this.doublePress === "reset",
|
|
314
|
+
toggle: (state, isOn) => {
|
|
315
|
+
this.toggleState(state, isOn);
|
|
328
316
|
}
|
|
329
317
|
}, signal);
|
|
330
318
|
}
|
|
@@ -415,16 +403,11 @@ var SonicXy = class extends SonicFormElement {
|
|
|
415
403
|
this.writeFormFields([["x", x], ["y", y]], `${String(x)},${String(y)}`);
|
|
416
404
|
}
|
|
417
405
|
#renderReadout() {
|
|
418
|
-
const { isRevealed } = this.#claim;
|
|
419
406
|
this.#readout.show({
|
|
420
407
|
anchor: this.#puck,
|
|
421
|
-
isOpen: isRevealed && this.readout,
|
|
408
|
+
isOpen: this.#gesture?.isRevealed() === true && this.readout,
|
|
422
409
|
text: this.#valueText("x")
|
|
423
410
|
});
|
|
424
|
-
if (isRevealed === this.#isRevealed) return;
|
|
425
|
-
this.#isRevealed = isRevealed;
|
|
426
|
-
this.toggleState("revealed", isRevealed);
|
|
427
|
-
this.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
|
|
428
411
|
}
|
|
429
412
|
#reset() {
|
|
430
413
|
this.#commit({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldGesture, FieldHold, FieldInput } from '#elements/field-gesture.ts';\nimport type { FieldAxis } from '#lib/field.ts';\nimport type { ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { createDoublePress } from '#lib/double-press.ts';\nimport { pointerPosition } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion, toNumber } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\nconst axes = ['x', 'y'] as const;\n\nconst keyMoves = new Map<string, [FieldAxis, string]>([\n\t['ArrowDown', ['y', 'ArrowDown']],\n\t['ArrowLeft', ['x', 'ArrowLeft']],\n\t['ArrowRight', ['x', 'ArrowRight']],\n\t['ArrowUp', ['y', 'ArrowUp']],\n\t['End', ['x', 'PageUp']],\n\t['Home', ['x', 'PageDown']],\n\t['PageDown', ['y', 'PageDown']],\n\t['PageUp', ['y', 'PageUp']],\n]);\n\nconst orientations = { x: 'horizontal', y: 'vertical' } as const;\n\nconst renderXy = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-xy\">\n\t\t\t<div class=\"sonic-xy-reticle\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-field\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-puck\">\n\t\t\t\t<div class=\"sonic-xy-axis\" data-sonic-axis=\"x\" role=\"slider\" tabindex=\"0\"></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"sonic-xy-axis\"\n\t\t\t\t\tdata-sonic-axis=\"y\"\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t\t<div class=\"sonic-xy-readout\" aria-hidden=\"true\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicXy extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'name',\n\t\t'readout',\n\t\t'x',\n\t\t'x-label',\n\t\t'x-max',\n\t\t'x-midpoint',\n\t\t'x-min',\n\t\t'x-origin',\n\t\t'x-step',\n\t\t'x-taper',\n\t\t'y',\n\t\t'y-label',\n\t\t'y-max',\n\t\t'y-midpoint',\n\t\t'y-min',\n\t\t'y-origin',\n\t\t'y-step',\n\t\t'y-taper',\n\t];\n\n\tget doublePress(): 'none' | 'reset' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'none';\n\t}\n\n\tset doublePress(gesture: 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget formatValue(): ((value: number, axis: FieldAxis) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isEnabled: boolean) {\n\t\tthis.reflect('readout', isEnabled);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\tget x(): number {\n\t\treturn this.#models.x.value;\n\t}\n\n\tset x(next: null | number | undefined) {\n\t\tthis.#write('x', next);\n\t}\n\n\tget xDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-default');\n\t}\n\n\tset xDefault(value: number | undefined) {\n\t\tthis.reflect('x-default', value);\n\t}\n\n\tget xLabel(): string | undefined {\n\t\treturn this.getAttribute('x-label') ?? undefined;\n\t}\n\n\tset xLabel(label: string | undefined) {\n\t\tthis.reflect('x-label', label);\n\t}\n\n\tget xMax(): number {\n\t\treturn this.numberAttribute('x-max', 100);\n\t}\n\n\tset xMax(value: number | undefined) {\n\t\tthis.reflect('x-max', value);\n\t}\n\n\tget xMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-midpoint');\n\t}\n\n\tset xMidpoint(value: number | undefined) {\n\t\tthis.reflect('x-midpoint', value);\n\t}\n\n\tget xMin(): number {\n\t\treturn this.numberAttribute('x-min', 0);\n\t}\n\n\tset xMin(value: number | undefined) {\n\t\tthis.reflect('x-min', value);\n\t}\n\n\tget xOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-origin');\n\t}\n\n\tset xOrigin(value: number | undefined) {\n\t\tthis.reflect('x-origin', value);\n\t}\n\n\tget xStep(): number {\n\t\treturn this.numberAttribute('x-step', 1);\n\t}\n\n\tset xStep(value: number | undefined) {\n\t\tthis.reflect('x-step', value);\n\t}\n\n\tget xTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('x');\n\t}\n\n\tset xTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('x-taper', curve);\n\t}\n\n\tget y(): number {\n\t\treturn this.#models.y.value;\n\t}\n\n\tset y(next: null | number | undefined) {\n\t\tthis.#write('y', next);\n\t}\n\n\tget yDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-default');\n\t}\n\n\tset yDefault(value: number | undefined) {\n\t\tthis.reflect('y-default', value);\n\t}\n\n\tget yLabel(): string | undefined {\n\t\treturn this.getAttribute('y-label') ?? undefined;\n\t}\n\n\tset yLabel(label: string | undefined) {\n\t\tthis.reflect('y-label', label);\n\t}\n\n\tget yMax(): number {\n\t\treturn this.numberAttribute('y-max', 100);\n\t}\n\n\tset yMax(value: number | undefined) {\n\t\tthis.reflect('y-max', value);\n\t}\n\n\tget yMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-midpoint');\n\t}\n\n\tset yMidpoint(value: number | undefined) {\n\t\tthis.reflect('y-midpoint', value);\n\t}\n\n\tget yMin(): number {\n\t\treturn this.numberAttribute('y-min', 0);\n\t}\n\n\tset yMin(value: number | undefined) {\n\t\tthis.reflect('y-min', value);\n\t}\n\n\tget yOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-origin');\n\t}\n\n\tset yOrigin(value: number | undefined) {\n\t\tthis.reflect('y-origin', value);\n\t}\n\n\tget yStep(): number {\n\t\treturn this.numberAttribute('y-step', 1);\n\t}\n\n\tset yStep(value: number | undefined) {\n\t\tthis.reflect('y-step', value);\n\t}\n\n\tget yTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('y');\n\t}\n\n\tset yTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('y-taper', curve);\n\t}\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\t#currentAxis: FieldAxis = 'x';\n\n\treadonly #doublePress = createDoublePress();\n\n\treadonly #xy = renderXy();\n\n\treadonly #field = requireChild(this.#xy, '.sonic-xy-field', HTMLDivElement);\n\n\t#formatValue: ((value: number, axis: FieldAxis) => string) | undefined;\n\n\t#gesture: FieldGesture<FieldHold> | undefined;\n\n\t#isRevealed = false;\n\n\treadonly #models: Record<FieldAxis, ValueModel> = {\n\t\tx: createValueModel(() => this.#spec('x')),\n\t\ty: createValueModel(() => this.#spec('y')),\n\t};\n\n\treadonly #parts: Record<FieldAxis, HTMLDivElement> = {\n\t\tx: requireChild(this.#xy, '[data-sonic-axis=\"x\"]', HTMLDivElement),\n\t\ty: requireChild(this.#xy, '[data-sonic-axis=\"y\"]', HTMLDivElement),\n\t};\n\n\treadonly #puck = requireChild(this.#xy, '.sonic-xy-puck', HTMLDivElement);\n\n\treadonly #readout = new Readout(requireChild(this.#xy, '.sonic-xy-readout', HTMLDivElement));\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'x' || name === 'y') && this.#gesture?.current()) return;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gesture?.end();\n\n\t\tthis.#respec(name);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'doublePress',\n\t\t\t'readout',\n\t\t\t'xDefault',\n\t\t\t'xLabel',\n\t\t\t'xMax',\n\t\t\t'xMidpoint',\n\t\t\t'xMin',\n\t\t\t'xOrigin',\n\t\t\t'xStep',\n\t\t\t'xTaper',\n\t\t\t'yDefault',\n\t\t\t'yLabel',\n\t\t\t'yMax',\n\t\t\t'yMidpoint',\n\t\t\t'yMin',\n\t\t\t'yOrigin',\n\t\t\t'yStep',\n\t\t\t'yTaper',\n\t\t\t'formatValue',\n\t\t\t'x',\n\t\t\t'y',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\tthis.#respec(...axes);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.#xy;\n\n\t\tthis.keepControl(xy, signal);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(xy, 'xy.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.#bindPointer(xy, signal);\n\t\tthis.#bindKeys(xy, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#parts[this.#currentAxis];\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst { style } = this.#xy;\n\n\t\tfor (const axis of axes) {\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst mapping = model.mapping();\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));\n\t\t}\n\t\tthis.#renderAria();\n\t\tthis.#renderForm();\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [x = NaN, y = NaN] = state.split(',').map(Number);\n\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.#xy;\n\t}\n\n\t#axisText(axis: FieldAxis): string {\n\t\tconst { value } = this.#models[axis];\n\t\tconst label = this.getAttribute(`${axis}-label`) ?? axis.toUpperCase();\n\n\t\treturn `${label} ${this.#formatValue?.(value, axis) ?? String(value)}`;\n\t}\n\n\t#bindKeys(xy: HTMLElement, signal: AbortSignal): void {\n\t\txy.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented) return;\n\n\t\t\t\tconst move = keyMoves.get(event.key);\n\t\t\t\tif (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#renderReadout();\n\t\t\t\tif (move) this.#keyTo(...move);\n\t\t\t\telse this.#reset();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\t// A key on the other axis moves focus between the two parts\n\t\t\t\tif (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#claim.conceal('keys')) this.#renderReadout();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bindPointer(xy: HTMLElement, signal: AbortSignal): void {\n\t\t// A press on the field would take focus off the axis part and select the page's text\n\t\txy.addEventListener(\n\t\t\t'mousedown',\n\t\t\t(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t() => {\n\t\t\t\tthis.#doublePress.forget();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\txy,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.isDisabled() || isMenuPress(event)) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tif (!isResetPress(event)) {\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\n\t\t\t\t\t\treturn this.#grab(event);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#reset();\n\n\t\t\t\t\treturn;\n\t\t\t\t},\n\t\t\t\tinput: (_hold, next) => {\n\t\t\t\t\tif (!this.#input(next)) this.#renderReadout();\n\t\t\t\t},\n\t\t\t\tlift: (_hold, event) => {\n\t\t\t\t\tif (!this.#doublePress.press(event) || this.doublePress !== 'reset') return;\n\n\t\t\t\t\tthis.#reset();\n\t\t\t\t},\n\t\t\t\trelease: (_hold, moved) => {\n\t\t\t\t\tif (moved.length > 0) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#commit(next: FieldInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): FieldHold {\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst { x, y } = this.#models;\n\t\tconst from = { x: x.value, y: y.value };\n\t\tconst travel = {\n\t\t\tx: Math.max(1, field.width - puck.width),\n\t\t\ty: Math.max(1, field.height - puck.height),\n\t\t};\n\t\tconst position = pointerPosition(event);\n\t\tconst pressed = {\n\t\t\tx: (position.x - field.left - puck.width / 2) / travel.x,\n\t\t\ty: (position.y + field.bottom - puck.height / 2) / travel.y,\n\t\t};\n\t\tconst isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);\n\n\t\tif (!isOnPuck) {\n\t\t\tthis.#input({ x: x.mapping().valueAt(pressed.x), y: y.mapping().valueAt(pressed.y) });\n\t\t}\n\n\t\tconst hold = (axis: FieldAxis) => ({\n\t\t\tfrom: from[axis],\n\t\t\tmodel: this.#models[axis],\n\t\t\t...(isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) }),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn { axes: { x: hold('x'), y: hold('y') } };\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: FieldInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;\n\t\t}\n\t\tif (!hasMoved) return false;\n\n\t\tthis.render();\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\t#keyTo(axis: FieldAxis, key: string): void {\n\t\tconst model = this.#models[axis];\n\t\tconst hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);\n\n\t\tthis.#currentAxis = axis;\n\t\tthis.render();\n\t\tthis.#parts[axis].focus();\n\t\tif (!hasMoved) return;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#renderAria(): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst part = this.#parts[axis];\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst [low, high] = model.mapping().bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\twriteAttribute(part, 'aria-orientation', orientations[axis]);\n\t\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\t\twriteAttribute(part, 'aria-valuenow', String(model.value));\n\t\t\twriteAttribute(part, 'aria-valuetext', this.#valueText(axis));\n\t\t\tthis.forwardNaming(part, true);\n\t\t\twriteAttribute(part, 'aria-hidden', isCurrent ? undefined : 'true');\n\t\t\twriteAttribute(part, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t\twriteAttribute(part, 'tabindex', isDisabled ? undefined : stop);\n\t\t}\n\t}\n\n\t#renderForm(): void {\n\t\tconst { x, y } = this;\n\n\t\tthis.writeFormFields(\n\t\t\t[\n\t\t\t\t['x', x],\n\t\t\t\t['y', y],\n\t\t\t],\n\t\t\t`${String(x)},${String(y)}`,\n\t\t);\n\t}\n\n\t#renderReadout(): void {\n\t\tconst { isRevealed } = this.#claim;\n\n\t\tthis.#readout.show({\n\t\t\tanchor: this.#puck,\n\t\t\tisOpen: isRevealed && this.readout,\n\t\t\ttext: this.#valueText('x'),\n\t\t});\n\t\tif (isRevealed === this.#isRevealed) return;\n\n\t\tthis.#isRevealed = isRevealed;\n\t\tthis.toggleState('revealed', isRevealed);\n\t\tthis.dispatchEvent(new Event('sonic-reveal', { bubbles: true }));\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit({ x: this.xDefault, y: this.yDefault });\n\t}\n\n\t#respec(...forgotten: Array<string>): void {\n\t\tfor (const axis of axes) {\n\t\t\tthis.#models[axis].respec(\n\t\t\t\tthis.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)),\n\t\t\t\t{ forgetAsk: forgotten.includes(axis) },\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#spec(axis: FieldAxis): ValueSpec {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn {\n\t\t\tmax: this.numberAttribute(`${axis}-max`, 100),\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: false,\n\t\t\tisWrapping: false,\n\t\t\tmin: this.numberAttribute(`${axis}-min`, 0),\n\t\t\tstep: this.numberAttribute(`${axis}-step`, 1),\n\t\t\ttaper: this.#taper(axis),\n\t\t};\n\t}\n\n\t#taper(axis: FieldAxis): ValueSpec['taper'] {\n\t\treturn this.getAttribute(`${axis}-taper`) === 'log' ? 'log' : 'linear';\n\t}\n\n\t#valueText(axis: FieldAxis): string {\n\t\treturn `${this.#axisText(axis)}, ${this.#axisText(axis === 'x' ? 'y' : 'x')}`;\n\t}\n\n\t#write(axis: FieldAxis, next: null | number | undefined): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.#respec(axis);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#models[axis].write(toNumber(next))) this.render();\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"xy.js","names":[],"sources":["../../../src/elements/xy.ts"],"sourcesContent":["import type { FieldGesture, FieldHold, FieldInput } from '#elements/field-gesture.ts';\nimport type { BoxProbe } from '#lib/check-styles.ts';\nimport type { FieldAxis } from '#lib/field.ts';\nimport type { ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { pointerPosition } from '#lib/field.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clampProportion, toNumber } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-xy': SonicXy;\n\t}\n}\n\nconst axes = ['x', 'y'] as const;\n\nconst keyMoves = new Map<string, [FieldAxis, string]>([\n\t['ArrowDown', ['y', 'ArrowDown']],\n\t['ArrowLeft', ['x', 'ArrowLeft']],\n\t['ArrowRight', ['x', 'ArrowRight']],\n\t['ArrowUp', ['y', 'ArrowUp']],\n\t['End', ['x', 'PageUp']],\n\t['Home', ['x', 'PageDown']],\n\t['PageDown', ['y', 'PageDown']],\n\t['PageUp', ['y', 'PageUp']],\n]);\n\nconst orientations = { x: 'horizontal', y: 'vertical' } as const;\n\nconst renderXy = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-xy\">\n\t\t\t<div class=\"sonic-xy-reticle\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-field\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-xy-puck\">\n\t\t\t\t<div class=\"sonic-xy-axis\" data-sonic-axis=\"x\" role=\"slider\" tabindex=\"0\"></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"sonic-xy-axis\"\n\t\t\t\t\tdata-sonic-axis=\"y\"\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t\t<div class=\"sonic-xy-readout\" aria-hidden=\"true\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicXy extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'name',\n\t\t'readout',\n\t\t'x',\n\t\t'x-label',\n\t\t'x-max',\n\t\t'x-midpoint',\n\t\t'x-min',\n\t\t'x-origin',\n\t\t'x-step',\n\t\t'x-taper',\n\t\t'y',\n\t\t'y-label',\n\t\t'y-max',\n\t\t'y-midpoint',\n\t\t'y-min',\n\t\t'y-origin',\n\t\t'y-step',\n\t\t'y-taper',\n\t];\n\n\tget doublePress(): 'none' | 'reset' {\n\t\treturn this.getAttribute('double-press') === 'reset' ? 'reset' : 'none';\n\t}\n\n\tset doublePress(gesture: 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget formatValue(): ((value: number, axis: FieldAxis) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number, axis: FieldAxis) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isEnabled: boolean) {\n\t\tthis.reflect('readout', isEnabled);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\tget x(): number {\n\t\treturn this.#models.x.value;\n\t}\n\n\tset x(next: null | number | undefined) {\n\t\tthis.#write('x', next);\n\t}\n\n\tget xDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-default');\n\t}\n\n\tset xDefault(value: number | undefined) {\n\t\tthis.reflect('x-default', value);\n\t}\n\n\tget xLabel(): string | undefined {\n\t\treturn this.getAttribute('x-label') ?? undefined;\n\t}\n\n\tset xLabel(label: string | undefined) {\n\t\tthis.reflect('x-label', label);\n\t}\n\n\tget xMax(): number {\n\t\treturn this.numberAttribute('x-max', 100);\n\t}\n\n\tset xMax(value: number | undefined) {\n\t\tthis.reflect('x-max', value);\n\t}\n\n\tget xMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-midpoint');\n\t}\n\n\tset xMidpoint(value: number | undefined) {\n\t\tthis.reflect('x-midpoint', value);\n\t}\n\n\tget xMin(): number {\n\t\treturn this.numberAttribute('x-min', 0);\n\t}\n\n\tset xMin(value: number | undefined) {\n\t\tthis.reflect('x-min', value);\n\t}\n\n\tget xOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('x-origin');\n\t}\n\n\tset xOrigin(value: number | undefined) {\n\t\tthis.reflect('x-origin', value);\n\t}\n\n\tget xStep(): number {\n\t\treturn this.numberAttribute('x-step', 1);\n\t}\n\n\tset xStep(value: number | undefined) {\n\t\tthis.reflect('x-step', value);\n\t}\n\n\tget xTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('x');\n\t}\n\n\tset xTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('x-taper', curve);\n\t}\n\n\tget y(): number {\n\t\treturn this.#models.y.value;\n\t}\n\n\tset y(next: null | number | undefined) {\n\t\tthis.#write('y', next);\n\t}\n\n\tget yDefault(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-default');\n\t}\n\n\tset yDefault(value: number | undefined) {\n\t\tthis.reflect('y-default', value);\n\t}\n\n\tget yLabel(): string | undefined {\n\t\treturn this.getAttribute('y-label') ?? undefined;\n\t}\n\n\tset yLabel(label: string | undefined) {\n\t\tthis.reflect('y-label', label);\n\t}\n\n\tget yMax(): number {\n\t\treturn this.numberAttribute('y-max', 100);\n\t}\n\n\tset yMax(value: number | undefined) {\n\t\tthis.reflect('y-max', value);\n\t}\n\n\tget yMidpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-midpoint');\n\t}\n\n\tset yMidpoint(value: number | undefined) {\n\t\tthis.reflect('y-midpoint', value);\n\t}\n\n\tget yMin(): number {\n\t\treturn this.numberAttribute('y-min', 0);\n\t}\n\n\tset yMin(value: number | undefined) {\n\t\tthis.reflect('y-min', value);\n\t}\n\n\tget yOrigin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('y-origin');\n\t}\n\n\tset yOrigin(value: number | undefined) {\n\t\tthis.reflect('y-origin', value);\n\t}\n\n\tget yStep(): number {\n\t\treturn this.numberAttribute('y-step', 1);\n\t}\n\n\tset yStep(value: number | undefined) {\n\t\tthis.reflect('y-step', value);\n\t}\n\n\tget yTaper(): ValueSpec['taper'] {\n\t\treturn this.#taper('y');\n\t}\n\n\tset yTaper(curve: undefined | ValueSpec['taper']) {\n\t\tthis.reflect('y-taper', curve);\n\t}\n\n\tprotected override readonly boxProbe: BoxProbe = {\n\t\tproperty: 'margin-bottom',\n\t\tselector: '[popover]',\n\t};\n\n\tprotected override readonly control = renderXy();\n\n\tprotected override readonly sheet = 'xy.css';\n\n\t#currentAxis: FieldAxis = 'x';\n\n\treadonly #field = requireChild(this.control, '.sonic-xy-field', HTMLDivElement);\n\n\t#formatValue: ((value: number, axis: FieldAxis) => string) | undefined;\n\n\t#gesture: FieldGesture<FieldHold> | undefined;\n\n\treadonly #models: Record<FieldAxis, ValueModel> = {\n\t\tx: createValueModel(() => this.#spec('x')),\n\t\ty: createValueModel(() => this.#spec('y')),\n\t};\n\n\treadonly #parts: Record<FieldAxis, HTMLDivElement> = {\n\t\tx: requireChild(this.control, '[data-sonic-axis=\"x\"]', HTMLDivElement),\n\t\ty: requireChild(this.control, '[data-sonic-axis=\"y\"]', HTMLDivElement),\n\t};\n\n\treadonly #puck = requireChild(this.control, '.sonic-xy-puck', HTMLDivElement);\n\n\treadonly #readout = new Readout(requireChild(this.control, '.sonic-xy-readout', HTMLDivElement));\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'x' || name === 'y') && this.#gesture?.current()) return;\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gesture?.end();\n\n\t\tthis.#respec(name);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'doublePress',\n\t\t\t'readout',\n\t\t\t'xDefault',\n\t\t\t'xLabel',\n\t\t\t'xMax',\n\t\t\t'xMidpoint',\n\t\t\t'xMin',\n\t\t\t'xOrigin',\n\t\t\t'xStep',\n\t\t\t'xTaper',\n\t\t\t'yDefault',\n\t\t\t'yLabel',\n\t\t\t'yMax',\n\t\t\t'yMidpoint',\n\t\t\t'yMin',\n\t\t\t'yOrigin',\n\t\t\t'yStep',\n\t\t\t'yTaper',\n\t\t\t'formatValue',\n\t\t\t'x',\n\t\t\t'y',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\tthis.#respec(...axes);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst xy = this.control;\n\n\t\tthis.keepControl(signal);\n\t\tthis.render();\n\t\tthis.#bindPointer(xy, signal);\n\t\tthis.#bindKeys(xy, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#parts[this.#currentAxis];\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst { style } = this.control;\n\n\t\tfor (const axis of axes) {\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst mapping = model.mapping();\n\t\t\tconst origin = this.optionalNumberAttribute(`${axis}-origin`) ?? mapping.bounds[0];\n\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}`, String(mapping.proportionOf(model.value)));\n\t\t\tstyle.setProperty(`--_sonic-xy-${axis}-origin`, String(mapping.proportionOf(origin)));\n\t\t}\n\t\tthis.#renderAria();\n\t\tthis.#renderForm();\n\t\tthis.#renderReadout();\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [x = NaN, y = NaN] = state.split(',').map(Number);\n\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\t#axisText(axis: FieldAxis): string {\n\t\tconst { value } = this.#models[axis];\n\t\tconst label = this.getAttribute(`${axis}-label`) ?? axis.toUpperCase();\n\n\t\treturn `${label} ${this.#formatValue?.(value, axis) ?? String(value)}`;\n\t}\n\n\t#bindKeys(xy: HTMLElement, signal: AbortSignal): void {\n\t\txy.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (this.isDisabled() || event.defaultPrevented) return;\n\n\t\t\t\tconst move = keyMoves.get(event.key);\n\t\t\t\tif (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#gesture?.revealKeys();\n\t\t\t\tif (move) this.#keyTo(...move);\n\t\t\t\telse this.#reset();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\txy.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\t// A key on the other axis moves focus between the two parts\n\t\t\t\tif (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;\n\t\t\t\tthis.#gesture?.concealKeys();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#gesture?.concealKeys();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bindPointer(xy: HTMLElement, signal: AbortSignal): void {\n\t\t// A press on the field would take focus off the axis part and select the page's text\n\t\txy.addEventListener(\n\t\t\t'mousedown',\n\t\t\t(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\txy,\n\t\t\t{\n\t\t\t\telement: this,\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tthis.toggleState('dragging', true);\n\n\t\t\t\t\treturn this.#grab(event);\n\t\t\t\t},\n\t\t\t\tinput: (_hold, next) => {\n\t\t\t\t\tthis.#input(next);\n\t\t\t\t},\n\t\t\t\trelease: (_hold, moved) => {\n\t\t\t\t\tif (moved.length > 0) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\trenderReadout: () => {\n\t\t\t\t\tthis.#renderReadout();\n\t\t\t\t},\n\t\t\t\treset: () => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tfocusByPointer(this.focusTarget());\n\t\t\t\t\tthis.#reset();\n\t\t\t\t},\n\t\t\t\tresetsOnDoublePress: () => this.doublePress === 'reset',\n\t\t\t\ttoggle: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#commit(next: FieldInput): void {\n\t\tif (this.#input(next)) this.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#grab(event: PointerEvent): FieldHold {\n\t\tconst field = this.#field.getBoundingClientRect();\n\t\tconst puck = this.#puck.getBoundingClientRect();\n\t\tconst { x, y } = this.#models;\n\t\tconst from = { x: x.value, y: y.value };\n\t\tconst travel = {\n\t\t\tx: Math.max(1, field.width - puck.width),\n\t\t\ty: Math.max(1, field.height - puck.height),\n\t\t};\n\t\tconst position = pointerPosition(event);\n\t\tconst pressed = {\n\t\t\tx: (position.x - field.left - puck.width / 2) / travel.x,\n\t\t\ty: (position.y + field.bottom - puck.height / 2) / travel.y,\n\t\t};\n\t\tconst isOnPuck = event.target instanceof Node && this.#puck.contains(event.target);\n\n\t\tif (!isOnPuck) {\n\t\t\tthis.#input({ x: x.mapping().valueAt(pressed.x), y: y.mapping().valueAt(pressed.y) });\n\t\t}\n\n\t\tconst hold = (axis: FieldAxis) => ({\n\t\t\tfrom: from[axis],\n\t\t\tmodel: this.#models[axis],\n\t\t\t...(isOnPuck ? {} : { proportion: clampProportion(pressed[axis]) }),\n\t\t\ttravelPx: travel[axis],\n\t\t});\n\n\t\treturn { axes: { x: hold('x'), y: hold('y') } };\n\t}\n\n\t#hasDefault(): boolean {\n\t\treturn this.xDefault !== undefined || this.yDefault !== undefined;\n\t}\n\n\t#input(next: FieldInput): boolean {\n\t\tlet hasMoved = false;\n\n\t\tfor (const axis of axes) {\n\t\t\tif (this.#models[axis].input(next[axis] ?? NaN)) hasMoved = true;\n\t\t}\n\t\tif (!hasMoved) return false;\n\n\t\tthis.render();\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\t#keyTo(axis: FieldAxis, key: string): void {\n\t\tconst model = this.#models[axis];\n\t\tconst hasMoved = model.input(model.mapping().keyTarget(key, model.value) ?? NaN);\n\n\t\tthis.#currentAxis = axis;\n\t\tthis.render();\n\t\tthis.#parts[axis].focus();\n\t\tif (!hasMoved) return;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#renderAria(): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const axis of axes) {\n\t\t\tconst part = this.#parts[axis];\n\t\t\tconst model = this.#models[axis];\n\t\t\tconst [low, high] = model.mapping().bounds;\n\t\t\tconst isCurrent = axis === this.#currentAxis;\n\t\t\tconst stop = isCurrent ? '0' : '-1';\n\n\t\t\twriteAttribute(part, 'aria-orientation', orientations[axis]);\n\t\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\t\twriteAttribute(part, 'aria-valuenow', String(model.value));\n\t\t\twriteAttribute(part, 'aria-valuetext', this.#valueText(axis));\n\t\t\tthis.forwardNaming(part, true);\n\t\t\twriteAttribute(part, 'aria-hidden', isCurrent ? undefined : 'true');\n\t\t\twriteAttribute(part, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t\twriteAttribute(part, 'tabindex', isDisabled ? undefined : stop);\n\t\t}\n\t}\n\n\t#renderForm(): void {\n\t\tconst { x, y } = this;\n\n\t\tthis.writeFormFields(\n\t\t\t[\n\t\t\t\t['x', x],\n\t\t\t\t['y', y],\n\t\t\t],\n\t\t\t`${String(x)},${String(y)}`,\n\t\t);\n\t}\n\n\t#renderReadout(): void {\n\t\tthis.#readout.show({\n\t\t\tanchor: this.#puck,\n\t\t\tisOpen: this.#gesture?.isRevealed() === true && this.readout,\n\t\t\ttext: this.#valueText('x'),\n\t\t});\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit({ x: this.xDefault, y: this.yDefault });\n\t}\n\n\t#respec(...forgotten: Array<string>): void {\n\t\tfor (const axis of axes) {\n\t\t\tthis.#models[axis].respec(\n\t\t\t\tthis.numberAttribute(axis, this.numberAttribute(`${axis}-min`, 0)),\n\t\t\t\t{ forgetAsk: forgotten.includes(axis) },\n\t\t\t);\n\t\t}\n\t\tthis.render();\n\t}\n\n\t#spec(axis: FieldAxis): ValueSpec {\n\t\tconst midpoint = this.optionalNumberAttribute(`${axis}-midpoint`);\n\n\t\treturn {\n\t\t\tmax: this.numberAttribute(`${axis}-max`, 100),\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: false,\n\t\t\tisWrapping: false,\n\t\t\tmin: this.numberAttribute(`${axis}-min`, 0),\n\t\t\tstep: this.numberAttribute(`${axis}-step`, 1),\n\t\t\ttaper: this.#taper(axis),\n\t\t};\n\t}\n\n\t#taper(axis: FieldAxis): ValueSpec['taper'] {\n\t\treturn this.getAttribute(`${axis}-taper`) === 'log' ? 'log' : 'linear';\n\t}\n\n\t#valueText(axis: FieldAxis): string {\n\t\treturn `${this.#axisText(axis)}, ${this.#axisText(axis === 'x' ? 'y' : 'x')}`;\n\t}\n\n\t#write(axis: FieldAxis, next: null | number | undefined): void {\n\t\tif (this.#gesture?.current()) return;\n\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.#respec(axis);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#models[axis].write(toNumber(next))) this.render();\n\t}\n}\n"],"mappings":"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"}
|
package/dist/dev/index.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { SonicSegmented } from "./elements/segmented.js";
|
|
|
8
8
|
import { TimeRegions } from "./lib/time-regions.js";
|
|
9
9
|
import { SonicSlider } from "./elements/slider.js";
|
|
10
10
|
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
11
|
+
import { SplitMode } from "./lib/distribute.js";
|
|
11
12
|
import { SonicSplit } from "./elements/split.js";
|
|
12
13
|
import { SonicSwitch } from "./elements/switch.js";
|
|
14
|
+
import { SonicTicker } from "./elements/ticker.js";
|
|
13
15
|
import { SonicToggle } from "./elements/toggle.js";
|
|
14
16
|
import { WaveMarker } from "./elements/wave-element.js";
|
|
15
17
|
import { PeaksRequest } from "./lib/peaks-request.js";
|
|
@@ -17,9 +19,10 @@ import { WaveformBands } from "./lib/waveform-bands.js";
|
|
|
17
19
|
import { WaveformPeaks } from "./lib/waveform-buckets.js";
|
|
18
20
|
import { LabelRender, SonicWaveform } from "./elements/waveform.js";
|
|
19
21
|
import { MarkerRender, SonicWavestrip } from "./elements/wavestrip.js";
|
|
22
|
+
import { FieldAxis } from "./lib/field.js";
|
|
20
23
|
import { SonicXy } from "./elements/xy.js";
|
|
21
24
|
import { formatClock, parseClock } from "./lib/clock.js";
|
|
22
25
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
23
26
|
import { envelopeCurve } from "./lib/envelope-curve.js";
|
|
24
27
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
25
|
-
export { type LabelRender, type MarkerRender, type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type TimeRegions, type WaveMarker, type WaveformBands, type WaveformPeaks, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
28
|
+
export { type FieldAxis, type LabelRender, type MarkerRender, type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicTicker, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type SplitMode, type TimeRegions, type WaveMarker, type WaveformBands, type WaveformPeaks, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
package/dist/dev/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { SonicSlider } from "./elements/slider.js";
|
|
|
10
10
|
import { SonicSpectrum } from "./elements/spectrum.js";
|
|
11
11
|
import { SonicSplit } from "./elements/split.js";
|
|
12
12
|
import { SonicSwitch } from "./elements/switch.js";
|
|
13
|
+
import { SonicTicker } from "./elements/ticker.js";
|
|
13
14
|
import { SonicToggle } from "./elements/toggle.js";
|
|
14
15
|
import { formatClock, parseClock } from "./lib/clock.js";
|
|
15
16
|
import { SonicWaveform } from "./elements/waveform.js";
|
|
@@ -18,4 +19,4 @@ import { SonicXy } from "./elements/xy.js";
|
|
|
18
19
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
19
20
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
20
21
|
|
|
21
|
-
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
22
|
+
export { SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicTicker, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, crossfadeGains, envelopeCurve, formatClock, formatPercent, parseClock, parsePercent };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readPxProperty } from "./read-px-property.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/cancel-zone.ts
|
|
4
|
+
const cancelZonePx = 48;
|
|
5
|
+
function outsideCancelZone(control, property) {
|
|
6
|
+
const box = control.getBoundingClientRect();
|
|
7
|
+
const zone = readPxProperty(getComputedStyle(control), property, cancelZonePx);
|
|
8
|
+
return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { outsideCancelZone };
|
|
13
|
+
//# sourceMappingURL=cancel-zone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancel-zone.js","names":[],"sources":["../../../src/lib/cancel-zone.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\nconst cancelZonePx = 48;\n\nexport function outsideCancelZone(\n\tcontrol: HTMLElement,\n\tproperty: string,\n): (event: PointerEvent) => boolean {\n\tconst box = control.getBoundingClientRect();\n\tconst zone = readPxProperty(getComputedStyle(control), property, cancelZonePx);\n\n\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n}\n"],"mappings":";;;AAEA,MAAM,eAAe;AAErB,SAAgB,kBACf,SACA,UACmC;CACnC,MAAM,MAAM,QAAQ,sBAAsB;CAC1C,MAAM,OAAO,eAAe,iBAAiB,OAAO,GAAG,UAAU,YAAY;CAE7E,QAAQ,UAAU,MAAM,UAAU,IAAI,MAAM,QAAQ,MAAM,UAAU,IAAI,SAAS;AAClF"}
|
|
@@ -254,7 +254,19 @@ var CanvasSurface = class {
|
|
|
254
254
|
function bindSurface(options) {
|
|
255
255
|
return new CanvasSurface(options);
|
|
256
256
|
}
|
|
257
|
+
function bindControlSurface(options) {
|
|
258
|
+
const { control, fill, sizeProperty, ...surfaceOptions } = options;
|
|
259
|
+
const surface = bindSurface({
|
|
260
|
+
...surfaceOptions,
|
|
261
|
+
fill: {
|
|
262
|
+
control,
|
|
263
|
+
sizeProperty
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
surface.setFill(fill);
|
|
267
|
+
return surface;
|
|
268
|
+
}
|
|
257
269
|
|
|
258
270
|
//#endregion
|
|
259
|
-
export { bindSurface };
|
|
271
|
+
export { bindControlSurface, bindSurface };
|
|
260
272
|
//# sourceMappingURL=canvas-surface.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-surface.js","names":["named"],"sources":["../../../src/lib/canvas-surface.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ntype PrivateProperty = `--_sonic-${string}`;\n\nexport interface SurfaceSize {\n\tdpr: number;\n\theight: number;\n\twidth: number;\n}\n\nexport interface SurfaceLook<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tcolours: Record<Colour, string>;\n\tisReducedMotion: boolean;\n\tlengths: Record<Length, number>;\n\tnumbers: Record<Numeric, number>;\n}\n\nexport interface SurfaceFrame<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tframeMs: number;\n\tisDirty: boolean;\n\tisRebuilt: boolean;\n\tlook: SurfaceLook<Colour, Length, Numeric>;\n\tsize: SurfaceSize;\n}\n\nexport interface Surface {\n\tinvalidate: () => void;\n\treadonly isVisible: boolean;\n\trebuild: () => void;\n\trequestFrame: () => void;\n\tsetFill: (isFilling: boolean) => void;\n\treadonly size: SurfaceSize;\n}\n\ninterface SurfaceOptions<Colour extends string, Length extends string, Numeric extends string> {\n\tcanvas: HTMLCanvasElement;\n\tcolours: Record<Colour, PrivateProperty>;\n\tfill?: { control: HTMLElement; sizeProperty: PrivateProperty };\n\tlengths?: Record<Length, PrivateProperty>;\n\tnumbers?: Record<Numeric, PrivateProperty>;\n\tpaint: (context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length, Numeric>) => void;\n\tresize?: (size: SurfaceSize) => void;\n\tsignal: AbortSignal;\n}\n\nfunction deviceRatio(): number {\n\treturn globalThis.devicePixelRatio || 1;\n}\n\n// WebKit reports none, and an emulated device scale reports it in CSS px, so it counts only where it agrees\nfunction deviceBox(\n\tentry: ResizeObserverEntry,\n\tcss: ResizeObserverSize,\n): ResizeObserverSize | undefined {\n\tconst device =\n\t\t'devicePixelContentBoxSize' in entry ? entry.devicePixelContentBoxSize[0] : undefined;\n\tconst dpr = deviceRatio();\n\tif (!device || Math.abs(device.inlineSize - css.inlineSize * dpr) > 1) return undefined;\n\n\treturn Math.abs(device.blockSize - css.blockSize * dpr) > 1 ? undefined : device;\n}\n\nfunction canTakeColour(context: CanvasRenderingContext2D, colour: string): boolean {\n\tconst kept = context.fillStyle;\n\tconst isKept = (sentinel: string): boolean => {\n\t\tcontext.fillStyle = sentinel;\n\t\tcontext.fillStyle = colour;\n\n\t\treturn context.fillStyle === sentinel;\n\t};\n\tconst isRefused = isKept('#000000') && isKept('#ffffff');\n\n\tcontext.fillStyle = kept;\n\n\treturn !isRefused;\n}\n\n// `Object.fromEntries` widens the keys to `string`\nfunction readEach<Name extends string, Value>(\n\tproperties: Partial<Record<Name, PrivateProperty>>,\n\tread: (property: PrivateProperty) => Value,\n): Record<Name, Value> {\n\tconst named: Readonly<Record<string, PrivateProperty | undefined>> = properties;\n\n\treturn Object.fromEntries(\n\t\tObject.entries(named).flatMap(([name, property]) =>\n\t\t\tproperty === undefined ? [] : [[name, read(property)]],\n\t\t),\n\t) as Record<Name, Value>;\n}\n\nclass CanvasSurface<\n\tColour extends string,\n\tLength extends string,\n\tNumeric extends string,\n> implements Surface {\n\tget isVisible(): boolean {\n\t\treturn this.#isVisible;\n\t}\n\n\tget size(): SurfaceSize {\n\t\treturn this.#size;\n\t}\n\n\t// happy-dom has no canvas context\n\treadonly #context: CanvasRenderingContext2D | undefined;\n\n\t#cssBox: undefined | { height: number; width: number };\n\n\t#fillWatch: ResizeObserver | undefined;\n\n\t#frame: number | undefined;\n\n\t#isDirty = true;\n\n\t#isRebuildWanted = true;\n\n\t#isVisible = true;\n\n\t#look: SurfaceLook<Colour, Length, Numeric> | undefined;\n\n\treadonly #options: SurfaceOptions<Colour, Length, Numeric>;\n\n\treadonly #reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\n\n\t#size: SurfaceSize = { dpr: deviceRatio(), height: 0, width: 0 };\n\n\tconstructor(options: SurfaceOptions<Colour, Length, Numeric>) {\n\t\tconst { canvas, signal } = options;\n\n\t\tthis.#options = options;\n\t\tthis.#context = canvas.getContext('2d') ?? undefined;\n\t\tthis.#armTransitions();\n\t\tthis.#observeSize();\n\t\tthis.#observeView();\n\t\tthis.#watchResolution();\n\t\tthis.#reducedMotion.addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// `transitionstart` drops events when a change replaces a pending transition in WebKit\n\t\tcanvas.addEventListener(\n\t\t\t'transitionrun',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#stopFill();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#schedule();\n\t}\n\n\tinvalidate(): void {\n\t\tthis.#isDirty = true;\n\t\tthis.#schedule();\n\t}\n\n\trebuild(): void {\n\t\tthis.#isRebuildWanted = true;\n\t\tthis.invalidate();\n\t}\n\n\trequestFrame(): void {\n\t\tthis.#schedule();\n\t}\n\n\tsetFill(isFilling: boolean): void {\n\t\tconst { fill, signal } = this.#options;\n\t\tif (!fill || signal.aborted || isFilling === (this.#fillWatch !== undefined)) return;\n\n\t\tconst { style } = fill.control;\n\n\t\tthis.#stopFill();\n\t\tif (!isFilling) {\n\t\t\tstyle.removeProperty(fill.sizeProperty);\n\t\t\treturn;\n\t\t}\n\n\t\tconst started = new ResizeObserver((entries) => {\n\t\t\tconst box = entries.at(-1)?.borderBoxSize[0];\n\t\t\tif (!box) return;\n\n\t\t\t// Written a frame on, or the canvas resizes inside this delivery and WebKit reports a loop\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (this.#fillWatch === started) {\n\t\t\t\t\tstyle.setProperty(fill.sizeProperty, `${String(box.blockSize)}px`);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\tthis.#fillWatch = started;\n\t\tstarted.observe(fill.control);\n\t}\n\n\t#armTransitions(): void {\n\t\tconst { canvas, colours, numbers = {} } = this.#options;\n\t\tconst { style } = canvas;\n\t\t// A colour mixed from `currentcolor` computes to the same text whatever `color` is\n\t\tconst watched = ['color', ...Object.values(colours), ...Object.values(numbers)];\n\n\t\tstyle.setProperty('transition-property', watched.join(', '));\n\t\tstyle.setProperty('transition-duration', '0.001ms');\n\t\tstyle.setProperty('transition-timing-function', 'step-start');\n\t\tstyle.setProperty('transition-behavior', 'allow-discrete');\n\t}\n\n\t#current(): { isRebuilt: boolean; look: SurfaceLook<Colour, Length, Numeric> } {\n\t\tif (this.#look && !this.#isRebuildWanted) return { isRebuilt: false, look: this.#look };\n\n\t\treturn { isRebuilt: true, look: this.#read() };\n\t}\n\n\t#draw(frameMs: number): void {\n\t\tthis.#frame = undefined;\n\n\t\tconst size = this.#size;\n\t\tif (size.width === 0 || size.height === 0) return;\n\n\t\tconst { canvas, paint } = this.#options;\n\t\tconst { isRebuilt, look } = this.#current();\n\t\tconst isDirty = this.#isDirty;\n\n\t\tthis.#look = look;\n\t\tthis.#isDirty = false;\n\t\tthis.#isRebuildWanted = false;\n\t\t// Assigning either clears the canvas and its state\n\t\tif (canvas.width !== size.width) canvas.width = size.width;\n\t\tif (canvas.height !== size.height) canvas.height = size.height;\n\t\tif (this.#context) paint(this.#context, { frameMs, isDirty, isRebuilt, look, size });\n\t}\n\n\t#measure(entry: ResizeObserverEntry): void {\n\t\tconst [box] = entry.contentBoxSize;\n\t\tif (!box) return;\n\n\t\tconst device = deviceBox(entry, box);\n\n\t\tif (device) {\n\t\t\tthis.#cssBox = undefined;\n\t\t\tthis.#resize(device.inlineSize, device.blockSize);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#cssBox = { height: box.blockSize, width: box.inlineSize };\n\t\tthis.#rescale();\n\t}\n\n\t#observeSize(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\n\t\t\tif (entry) this.#measure(entry);\n\t\t});\n\n\t\ttry {\n\t\t\tobserver.observe(canvas, { box: 'device-pixel-content-box' });\n\t\t} catch {\n\t\t\t// WebKit throws a TypeError on the option\n\t\t\tobserver.observe(canvas, { box: 'content-box' });\n\t\t}\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#observeView(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new IntersectionObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\t\t\tif (!entry) return;\n\n\t\t\tconst wasVisible = this.#isVisible;\n\n\t\t\tthis.#isVisible = entry.isIntersecting;\n\t\t\tif (!entry.isIntersecting && this.#frame !== undefined) {\n\t\t\t\tcancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t}\n\n\t\t\t// Chromium fires no transition inside a skipped `content-visibility` subtree\n\t\t\tif (!wasVisible && entry.isIntersecting) this.rebuild();\n\t\t});\n\n\t\tobserver.observe(canvas);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#read(): SurfaceLook<Colour, Length, Numeric> {\n\t\tconst { canvas, colours, lengths = {}, numbers = {} } = this.#options;\n\t\tconst styles = getComputedStyle(canvas);\n\n\t\treturn {\n\t\t\tcolours: readEach(colours, (property) =>\n\t\t\t\tthis.#resolve(\n\t\t\t\t\tstyles\n\t\t\t\t\t\t.getPropertyValue(property)\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.replaceAll('currentcolor', () => styles.color),\n\t\t\t\t),\n\t\t\t),\n\t\t\tisReducedMotion: this.#reducedMotion.matches,\n\t\t\tlengths: readEach<Length, number>(lengths, (property) => readPxProperty(styles, property, 0)),\n\t\t\tnumbers: readEach<Numeric, number>(numbers, (property) =>\n\t\t\t\treadPxProperty(styles, property, 0),\n\t\t\t),\n\t\t};\n\t}\n\n\t#rescale(): void {\n\t\tconst box = this.#cssBox;\n\t\tif (!box) {\n\t\t\tthis.#resize(this.#size.width, this.#size.height);\n\t\t\treturn;\n\t\t}\n\n\t\tconst dpr = deviceRatio();\n\n\t\tthis.#resize(Math.round(box.width * dpr), Math.round(box.height * dpr));\n\t}\n\n\t#resize(width: number, height: number): void {\n\t\tconst previous = this.#size;\n\t\tconst dpr = deviceRatio();\n\t\tif (previous.width === width && previous.height === height && previous.dpr === dpr) return;\n\n\t\tthis.#size = { dpr, height, width };\n\t\tthis.#options.resize?.(this.#size);\n\t\tif (previous.width === 0 || previous.height === 0) this.rebuild();\n\t\telse this.invalidate();\n\t}\n\n\t#resolve(colour: string): string {\n\t\tconst context = this.#context;\n\t\tif (!context || canTakeColour(context, colour)) return colour;\n\n\t\tconst probe = document.createElement('span');\n\n\t\tprobe.style.color = colour;\n\t\tthis.#options.canvas.append(probe);\n\n\t\tconst resolved = getComputedStyle(probe).color;\n\n\t\tprobe.remove();\n\n\t\treturn resolved;\n\t}\n\n\t#schedule(): void {\n\t\tif (this.#options.signal.aborted || this.#frame !== undefined || !this.#isVisible) return;\n\n\t\tthis.#frame = requestAnimationFrame((frameMs) => {\n\t\t\tthis.#draw(frameMs);\n\t\t});\n\t}\n\n\t#stopFill(): void {\n\t\tthis.#fillWatch?.disconnect();\n\t\tthis.#fillWatch = undefined;\n\t}\n\n\t#watchResolution(): void {\n\t\tconst { signal } = this.#options;\n\n\t\tmatchMedia(`(resolution: ${String(deviceRatio())}dppx)`).addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.#watchResolution();\n\t\t\t\tthis.#rescale();\n\t\t\t},\n\t\t\t{ once: true, signal },\n\t\t);\n\t}\n}\n\nexport function bindSurface<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n>(options: SurfaceOptions<Colour, Length, Numeric>): Surface {\n\treturn new CanvasSurface(options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"canvas-surface.js","names":["named"],"sources":["../../../src/lib/canvas-surface.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ntype PrivateProperty = `--_sonic-${string}`;\n\nexport interface SurfaceSize {\n\tdpr: number;\n\theight: number;\n\twidth: number;\n}\n\nexport interface SurfaceLook<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tcolours: Record<Colour, string>;\n\tisReducedMotion: boolean;\n\tlengths: Record<Length, number>;\n\tnumbers: Record<Numeric, number>;\n}\n\nexport interface SurfaceFrame<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tframeMs: number;\n\tisDirty: boolean;\n\tisRebuilt: boolean;\n\tlook: SurfaceLook<Colour, Length, Numeric>;\n\tsize: SurfaceSize;\n}\n\nexport interface Surface {\n\tinvalidate: () => void;\n\treadonly isVisible: boolean;\n\trebuild: () => void;\n\trequestFrame: () => void;\n\tsetFill: (isFilling: boolean) => void;\n\treadonly size: SurfaceSize;\n}\n\ninterface SurfaceOptions<Colour extends string, Length extends string, Numeric extends string> {\n\tcanvas: HTMLCanvasElement;\n\tcolours: Record<Colour, PrivateProperty>;\n\tfill?: { control: HTMLElement; sizeProperty: PrivateProperty };\n\tlengths?: Record<Length, PrivateProperty>;\n\tnumbers?: Record<Numeric, PrivateProperty>;\n\tpaint: (context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length, Numeric>) => void;\n\tresize?: (size: SurfaceSize) => void;\n\tsignal: AbortSignal;\n}\n\ninterface ControlSurfaceOptions<\n\tColour extends string,\n\tLength extends string,\n\tNumeric extends string,\n> extends Omit<SurfaceOptions<Colour, Length, Numeric>, 'fill'> {\n\tcontrol: HTMLElement;\n\tfill: boolean;\n\tsizeProperty: PrivateProperty;\n}\n\nfunction deviceRatio(): number {\n\treturn globalThis.devicePixelRatio || 1;\n}\n\n// WebKit reports none, and an emulated device scale reports it in CSS px, so it counts only where it agrees\nfunction deviceBox(\n\tentry: ResizeObserverEntry,\n\tcss: ResizeObserverSize,\n): ResizeObserverSize | undefined {\n\tconst device =\n\t\t'devicePixelContentBoxSize' in entry ? entry.devicePixelContentBoxSize[0] : undefined;\n\tconst dpr = deviceRatio();\n\tif (!device || Math.abs(device.inlineSize - css.inlineSize * dpr) > 1) return undefined;\n\n\treturn Math.abs(device.blockSize - css.blockSize * dpr) > 1 ? undefined : device;\n}\n\nfunction canTakeColour(context: CanvasRenderingContext2D, colour: string): boolean {\n\tconst kept = context.fillStyle;\n\tconst isKept = (sentinel: string): boolean => {\n\t\tcontext.fillStyle = sentinel;\n\t\tcontext.fillStyle = colour;\n\n\t\treturn context.fillStyle === sentinel;\n\t};\n\tconst isRefused = isKept('#000000') && isKept('#ffffff');\n\n\tcontext.fillStyle = kept;\n\n\treturn !isRefused;\n}\n\n// `Object.fromEntries` widens the keys to `string`\nfunction readEach<Name extends string, Value>(\n\tproperties: Partial<Record<Name, PrivateProperty>>,\n\tread: (property: PrivateProperty) => Value,\n): Record<Name, Value> {\n\tconst named: Readonly<Record<string, PrivateProperty | undefined>> = properties;\n\n\treturn Object.fromEntries(\n\t\tObject.entries(named).flatMap(([name, property]) =>\n\t\t\tproperty === undefined ? [] : [[name, read(property)]],\n\t\t),\n\t) as Record<Name, Value>;\n}\n\nclass CanvasSurface<\n\tColour extends string,\n\tLength extends string,\n\tNumeric extends string,\n> implements Surface {\n\tget isVisible(): boolean {\n\t\treturn this.#isVisible;\n\t}\n\n\tget size(): SurfaceSize {\n\t\treturn this.#size;\n\t}\n\n\t// happy-dom has no canvas context\n\treadonly #context: CanvasRenderingContext2D | undefined;\n\n\t#cssBox: undefined | { height: number; width: number };\n\n\t#fillWatch: ResizeObserver | undefined;\n\n\t#frame: number | undefined;\n\n\t#isDirty = true;\n\n\t#isRebuildWanted = true;\n\n\t#isVisible = true;\n\n\t#look: SurfaceLook<Colour, Length, Numeric> | undefined;\n\n\treadonly #options: SurfaceOptions<Colour, Length, Numeric>;\n\n\treadonly #reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\n\n\t#size: SurfaceSize = { dpr: deviceRatio(), height: 0, width: 0 };\n\n\tconstructor(options: SurfaceOptions<Colour, Length, Numeric>) {\n\t\tconst { canvas, signal } = options;\n\n\t\tthis.#options = options;\n\t\tthis.#context = canvas.getContext('2d') ?? undefined;\n\t\tthis.#armTransitions();\n\t\tthis.#observeSize();\n\t\tthis.#observeView();\n\t\tthis.#watchResolution();\n\t\tthis.#reducedMotion.addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// `transitionstart` drops events when a change replaces a pending transition in WebKit\n\t\tcanvas.addEventListener(\n\t\t\t'transitionrun',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#stopFill();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#schedule();\n\t}\n\n\tinvalidate(): void {\n\t\tthis.#isDirty = true;\n\t\tthis.#schedule();\n\t}\n\n\trebuild(): void {\n\t\tthis.#isRebuildWanted = true;\n\t\tthis.invalidate();\n\t}\n\n\trequestFrame(): void {\n\t\tthis.#schedule();\n\t}\n\n\tsetFill(isFilling: boolean): void {\n\t\tconst { fill, signal } = this.#options;\n\t\tif (!fill || signal.aborted || isFilling === (this.#fillWatch !== undefined)) return;\n\n\t\tconst { style } = fill.control;\n\n\t\tthis.#stopFill();\n\t\tif (!isFilling) {\n\t\t\tstyle.removeProperty(fill.sizeProperty);\n\t\t\treturn;\n\t\t}\n\n\t\tconst started = new ResizeObserver((entries) => {\n\t\t\tconst box = entries.at(-1)?.borderBoxSize[0];\n\t\t\tif (!box) return;\n\n\t\t\t// Written a frame on, or the canvas resizes inside this delivery and WebKit reports a loop\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (this.#fillWatch === started) {\n\t\t\t\t\tstyle.setProperty(fill.sizeProperty, `${String(box.blockSize)}px`);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\tthis.#fillWatch = started;\n\t\tstarted.observe(fill.control);\n\t}\n\n\t#armTransitions(): void {\n\t\tconst { canvas, colours, numbers = {} } = this.#options;\n\t\tconst { style } = canvas;\n\t\t// A colour mixed from `currentcolor` computes to the same text whatever `color` is\n\t\tconst watched = ['color', ...Object.values(colours), ...Object.values(numbers)];\n\n\t\tstyle.setProperty('transition-property', watched.join(', '));\n\t\tstyle.setProperty('transition-duration', '0.001ms');\n\t\tstyle.setProperty('transition-timing-function', 'step-start');\n\t\tstyle.setProperty('transition-behavior', 'allow-discrete');\n\t}\n\n\t#current(): { isRebuilt: boolean; look: SurfaceLook<Colour, Length, Numeric> } {\n\t\tif (this.#look && !this.#isRebuildWanted) return { isRebuilt: false, look: this.#look };\n\n\t\treturn { isRebuilt: true, look: this.#read() };\n\t}\n\n\t#draw(frameMs: number): void {\n\t\tthis.#frame = undefined;\n\n\t\tconst size = this.#size;\n\t\tif (size.width === 0 || size.height === 0) return;\n\n\t\tconst { canvas, paint } = this.#options;\n\t\tconst { isRebuilt, look } = this.#current();\n\t\tconst isDirty = this.#isDirty;\n\n\t\tthis.#look = look;\n\t\tthis.#isDirty = false;\n\t\tthis.#isRebuildWanted = false;\n\t\t// Assigning either clears the canvas and its state\n\t\tif (canvas.width !== size.width) canvas.width = size.width;\n\t\tif (canvas.height !== size.height) canvas.height = size.height;\n\t\tif (this.#context) paint(this.#context, { frameMs, isDirty, isRebuilt, look, size });\n\t}\n\n\t#measure(entry: ResizeObserverEntry): void {\n\t\tconst [box] = entry.contentBoxSize;\n\t\tif (!box) return;\n\n\t\tconst device = deviceBox(entry, box);\n\n\t\tif (device) {\n\t\t\tthis.#cssBox = undefined;\n\t\t\tthis.#resize(device.inlineSize, device.blockSize);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#cssBox = { height: box.blockSize, width: box.inlineSize };\n\t\tthis.#rescale();\n\t}\n\n\t#observeSize(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\n\t\t\tif (entry) this.#measure(entry);\n\t\t});\n\n\t\ttry {\n\t\t\tobserver.observe(canvas, { box: 'device-pixel-content-box' });\n\t\t} catch {\n\t\t\t// WebKit throws a TypeError on the option\n\t\t\tobserver.observe(canvas, { box: 'content-box' });\n\t\t}\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#observeView(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new IntersectionObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\t\t\tif (!entry) return;\n\n\t\t\tconst wasVisible = this.#isVisible;\n\n\t\t\tthis.#isVisible = entry.isIntersecting;\n\t\t\tif (!entry.isIntersecting && this.#frame !== undefined) {\n\t\t\t\tcancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t}\n\n\t\t\t// Chromium fires no transition inside a skipped `content-visibility` subtree\n\t\t\tif (!wasVisible && entry.isIntersecting) this.rebuild();\n\t\t});\n\n\t\tobserver.observe(canvas);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#read(): SurfaceLook<Colour, Length, Numeric> {\n\t\tconst { canvas, colours, lengths = {}, numbers = {} } = this.#options;\n\t\tconst styles = getComputedStyle(canvas);\n\n\t\treturn {\n\t\t\tcolours: readEach(colours, (property) =>\n\t\t\t\tthis.#resolve(\n\t\t\t\t\tstyles\n\t\t\t\t\t\t.getPropertyValue(property)\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.replaceAll('currentcolor', () => styles.color),\n\t\t\t\t),\n\t\t\t),\n\t\t\tisReducedMotion: this.#reducedMotion.matches,\n\t\t\tlengths: readEach<Length, number>(lengths, (property) => readPxProperty(styles, property, 0)),\n\t\t\tnumbers: readEach<Numeric, number>(numbers, (property) =>\n\t\t\t\treadPxProperty(styles, property, 0),\n\t\t\t),\n\t\t};\n\t}\n\n\t#rescale(): void {\n\t\tconst box = this.#cssBox;\n\t\tif (!box) {\n\t\t\tthis.#resize(this.#size.width, this.#size.height);\n\t\t\treturn;\n\t\t}\n\n\t\tconst dpr = deviceRatio();\n\n\t\tthis.#resize(Math.round(box.width * dpr), Math.round(box.height * dpr));\n\t}\n\n\t#resize(width: number, height: number): void {\n\t\tconst previous = this.#size;\n\t\tconst dpr = deviceRatio();\n\t\tif (previous.width === width && previous.height === height && previous.dpr === dpr) return;\n\n\t\tthis.#size = { dpr, height, width };\n\t\tthis.#options.resize?.(this.#size);\n\t\tif (previous.width === 0 || previous.height === 0) this.rebuild();\n\t\telse this.invalidate();\n\t}\n\n\t#resolve(colour: string): string {\n\t\tconst context = this.#context;\n\t\tif (!context || canTakeColour(context, colour)) return colour;\n\n\t\tconst probe = document.createElement('span');\n\n\t\tprobe.style.color = colour;\n\t\tthis.#options.canvas.append(probe);\n\n\t\tconst resolved = getComputedStyle(probe).color;\n\n\t\tprobe.remove();\n\n\t\treturn resolved;\n\t}\n\n\t#schedule(): void {\n\t\tif (this.#options.signal.aborted || this.#frame !== undefined || !this.#isVisible) return;\n\n\t\tthis.#frame = requestAnimationFrame((frameMs) => {\n\t\t\tthis.#draw(frameMs);\n\t\t});\n\t}\n\n\t#stopFill(): void {\n\t\tthis.#fillWatch?.disconnect();\n\t\tthis.#fillWatch = undefined;\n\t}\n\n\t#watchResolution(): void {\n\t\tconst { signal } = this.#options;\n\n\t\tmatchMedia(`(resolution: ${String(deviceRatio())}dppx)`).addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.#watchResolution();\n\t\t\t\tthis.#rescale();\n\t\t\t},\n\t\t\t{ once: true, signal },\n\t\t);\n\t}\n}\n\nexport function bindSurface<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n>(options: SurfaceOptions<Colour, Length, Numeric>): Surface {\n\treturn new CanvasSurface(options);\n}\n\nexport function bindControlSurface<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n>(options: ControlSurfaceOptions<Colour, Length, Numeric>): Surface {\n\tconst { control, fill, sizeProperty, ...surfaceOptions } = options;\n\tconst surface = bindSurface({ ...surfaceOptions, fill: { control, sizeProperty } });\n\n\tsurface.setFill(fill);\n\n\treturn surface;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region src/lib/check-styles.ts
|
|
2
|
+
const checkedSheets = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function isLost(drawn, { token }) {
|
|
4
|
+
return getComputedStyle(drawn).getPropertyValue(token) === "";
|
|
5
|
+
}
|
|
6
|
+
const hostBoxProperties = [
|
|
7
|
+
"margin-top",
|
|
8
|
+
"margin-right",
|
|
9
|
+
"margin-bottom",
|
|
10
|
+
"margin-left",
|
|
11
|
+
"padding-top",
|
|
12
|
+
"padding-right",
|
|
13
|
+
"padding-bottom",
|
|
14
|
+
"padding-left",
|
|
15
|
+
"width",
|
|
16
|
+
"height"
|
|
17
|
+
];
|
|
18
|
+
function readHostBox(host) {
|
|
19
|
+
const style = getComputedStyle(host);
|
|
20
|
+
if (style.display === "none") return void 0;
|
|
21
|
+
if (style.display !== "contents") return "display";
|
|
22
|
+
return hostBoxProperties.find((property) => ![
|
|
23
|
+
"",
|
|
24
|
+
"0px",
|
|
25
|
+
"auto"
|
|
26
|
+
].includes(style.getPropertyValue(property)));
|
|
27
|
+
}
|
|
28
|
+
function isZeroed(control, { property, ratio, selector }) {
|
|
29
|
+
const part = selector === void 0 ? control : control.querySelector(selector);
|
|
30
|
+
if (!part || getComputedStyle(part).getPropertyValue(property) !== "0px") return false;
|
|
31
|
+
return ratio === void 0 || Number(getComputedStyle(control).getPropertyValue(ratio)) !== 0;
|
|
32
|
+
}
|
|
33
|
+
function checkHost(host, checked) {
|
|
34
|
+
const hostBox = readHostBox(host);
|
|
35
|
+
if (hostBox === void 0) return;
|
|
36
|
+
checked.add("host");
|
|
37
|
+
console.warn(hostBox === "display" ? `<${host.localName}> is given a display, but it has to stay display: contents; lay it out through a parent or a wrapper` : `<${host.localName}> is display: contents and has no box, so its ${hostBox} does nothing; set it on a parent or a wrapper`);
|
|
38
|
+
}
|
|
39
|
+
function checkReset(host, { box, control }, checked) {
|
|
40
|
+
if (!control.isConnected) return;
|
|
41
|
+
if (!checked.has("host")) checkHost(host, checked);
|
|
42
|
+
if (!box || checked.has("reset") || !isZeroed(control, box)) return;
|
|
43
|
+
checked.add("reset");
|
|
44
|
+
console.warn(`<${host.localName}> has lost its ${box.property} to a rule outside a layer, which beats every sonic rule; import that stylesheet into a layer declared before sonic`);
|
|
45
|
+
}
|
|
46
|
+
function sheetProbes(sheet, token) {
|
|
47
|
+
const led = {
|
|
48
|
+
selector: ".sonic-led",
|
|
49
|
+
sheet: "led.css",
|
|
50
|
+
token: "--_sonic-led-lens-ratio"
|
|
51
|
+
};
|
|
52
|
+
if (token !== void 0) return [{
|
|
53
|
+
sheet,
|
|
54
|
+
token
|
|
55
|
+
}, led];
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
sheet: "material/core.css",
|
|
59
|
+
token: "--_sonic-unlit"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
sheet,
|
|
63
|
+
token: "--_sonic-unit"
|
|
64
|
+
},
|
|
65
|
+
led
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
function checkStyles(host, check) {
|
|
69
|
+
const { box, control, sheet, token } = check;
|
|
70
|
+
const elementClass = host.constructor;
|
|
71
|
+
const checked = checkedSheets.get(elementClass) ?? /* @__PURE__ */ new Set();
|
|
72
|
+
const part = ({ selector }) => selector === void 0 ? control : host.querySelector(selector) ?? void 0;
|
|
73
|
+
const probes = sheetProbes(sheet, token).filter((probe) => !checked.has(probe.sheet) && part(probe) !== void 0);
|
|
74
|
+
if ((!box || checked.has("reset")) && checked.has("host") && probes.length === 0) return;
|
|
75
|
+
checkedSheets.set(elementClass, checked);
|
|
76
|
+
for (const probe of probes) checked.add(probe.sheet);
|
|
77
|
+
requestAnimationFrame(() => {
|
|
78
|
+
const missing = [];
|
|
79
|
+
for (const probe of probes) {
|
|
80
|
+
const drawn = control.isConnected ? part(probe) : void 0;
|
|
81
|
+
if (!drawn) checked.delete(probe.sheet);
|
|
82
|
+
else if (isLost(drawn, probe)) missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);
|
|
83
|
+
}
|
|
84
|
+
if (missing.length === 0) {
|
|
85
|
+
checkReset(host, check, checked);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
checked.add("host").add("reset");
|
|
89
|
+
console.warn(`<${host.localName}> draws blank without ${missing.join(" and ")} (or controls.css)`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { checkStyles };
|
|
95
|
+
//# sourceMappingURL=check-styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-styles.js","names":[],"sources":["../../../src/lib/check-styles.ts"],"sourcesContent":["declare const __DEV__: boolean;\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\n// A padding or margin the sheets leave at zero only where `ratio` is zero\nexport interface BoxProbe {\n\tproperty: string;\n\tratio?: `--_sonic-${string}`;\n\tselector?: string;\n}\n\nfunction isLost(drawn: Element, { token }: StyleProbe): boolean {\n\treturn getComputedStyle(drawn).getPropertyValue(token) === '';\n}\n\nconst hostBoxProperties = [\n\t'margin-top',\n\t'margin-right',\n\t'margin-bottom',\n\t'margin-left',\n\t'padding-top',\n\t'padding-right',\n\t'padding-bottom',\n\t'padding-left',\n\t'width',\n\t'height',\n];\n\n// An environment with no computed styles answers '', which is no box property\nfunction readHostBox(host: Element): string | undefined {\n\tconst style = getComputedStyle(host);\n\tif (style.display === 'none') return undefined;\n\tif (style.display !== 'contents') return 'display';\n\n\treturn hostBoxProperties.find(\n\t\t(property) => !['', '0px', 'auto'].includes(style.getPropertyValue(property)),\n\t);\n}\n\nfunction isZeroed(control: Element, { property, ratio, selector }: BoxProbe): boolean {\n\tconst part = selector === undefined ? control : control.querySelector(selector);\n\tif (!part || getComputedStyle(part).getPropertyValue(property) !== '0px') return false;\n\n\treturn ratio === undefined || Number(getComputedStyle(control).getPropertyValue(ratio)) !== 0;\n}\n\nfunction checkHost(host: Element, checked: Set<string>): void {\n\tconst hostBox = readHostBox(host);\n\tif (hostBox === undefined) return;\n\n\tchecked.add('host');\n\tconsole.warn(\n\t\thostBox === 'display'\n\t\t\t? `<${host.localName}> is given a display, but it has to stay display: contents; lay it out through a parent or a wrapper`\n\t\t\t: `<${host.localName}> is display: contents and has no box, so its ${hostBox} does nothing; set it on a parent or a wrapper`,\n\t);\n}\n\nexport interface StyleCheck {\n\tbox?: BoxProbe | undefined;\n\tcontrol: HTMLElement;\n\tsheet: string;\n\ttoken?: string | undefined;\n}\n\n// Once a class; a sheet that is missing has already said why the control looks wrong\nfunction checkReset(host: Element, { box, control }: StyleCheck, checked: Set<string>): void {\n\tif (!control.isConnected) return;\n\n\tif (!checked.has('host')) checkHost(host, checked);\n\tif (!box || checked.has('reset') || !isZeroed(control, box)) return;\n\n\tchecked.add('reset');\n\tconsole.warn(\n\t\t`<${host.localName}> has lost its ${box.property} to a rule outside a layer, which beats every sonic rule; import that stylesheet into a layer declared before sonic`,\n\t);\n}\n\nfunction sheetProbes(sheet: string, token: string | undefined): Array<StyleProbe> {\n\tconst led = { selector: '.sonic-led', sheet: 'led.css', token: '--_sonic-led-lens-ratio' };\n\tif (token !== undefined) return [{ sheet, token }, led];\n\n\treturn [\n\t\t{ sheet: 'material/core.css', token: '--_sonic-unlit' },\n\t\t{ sheet, token: '--_sonic-unit' },\n\t\tled,\n\t];\n}\n\nexport function checkStyles(host: Element, check: StyleCheck): void {\n\tif (!__DEV__) return;\n\n\tconst { box, control, sheet, token } = check;\n\tconst elementClass = host.constructor;\n\tconst checked = checkedSheets.get(elementClass) ?? new Set<string>();\n\tconst part = ({ selector }: StyleProbe): Element | undefined =>\n\t\tselector === undefined ? control : (host.querySelector(selector) ?? undefined);\n\tconst probes = sheetProbes(sheet, token).filter(\n\t\t(probe) => !checked.has(probe.sheet) && part(probe) !== undefined,\n\t);\n\tconst isResetChecked = !box || checked.has('reset');\n\tconst isBoxChecked = isResetChecked && checked.has('host');\n\tif (isBoxChecked && probes.length === 0) return;\n\n\tcheckedSheets.set(elementClass, checked);\n\tfor (const probe of probes) checked.add(probe.sheet);\n\trequestAnimationFrame(() => {\n\t\tconst missing: Array<string> = [];\n\n\t\tfor (const probe of probes) {\n\t\t\tconst drawn = control.isConnected ? part(probe) : undefined;\n\n\t\t\tif (!drawn) checked.delete(probe.sheet);\n\t\t\telse if (isLost(drawn, probe)) missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t}\n\t\tif (missing.length === 0) {\n\t\t\tcheckReset(host, check, checked);\n\t\t\treturn;\n\t\t}\n\n\t\tchecked.add('host').add('reset');\n\t\tconsole.warn(\n\t\t\t`<${host.localName}> draws blank without ${missing.join(' and ')} (or controls.css)`,\n\t\t);\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -187,8 +187,7 @@ function close(members, rule, held) {
|
|
|
187
187
|
const { landing, move, mover } = held;
|
|
188
188
|
const { index } = move;
|
|
189
189
|
const landed = landing.values[index] ?? mover.value;
|
|
190
|
-
|
|
191
|
-
return nearest(targets.flatMap((target) => {
|
|
190
|
+
return nearest(moverTargets(members, landing, index).flatMap((target) => {
|
|
192
191
|
const attempt = spread(members, rule, {
|
|
193
192
|
index,
|
|
194
193
|
target
|
|
@@ -197,7 +196,7 @@ function close(members, rule, held) {
|
|
|
197
196
|
}), move, {
|
|
198
197
|
landed,
|
|
199
198
|
start: mover.value
|
|
200
|
-
}) ??
|
|
199
|
+
}) ?? members.map(({ value }) => value);
|
|
201
200
|
}
|
|
202
201
|
function distribute(members, rule, move) {
|
|
203
202
|
const landing = spread(members, rule, move);
|