@xsynaptic/sonic-ui 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/default/define/spectrum.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +33 -4
- package/dist/default/elements/dial.js +5 -1
- package/dist/default/elements/display-element.js +77 -0
- package/dist/default/elements/envelope.js +15 -3
- package/dist/default/elements/meter.js +1 -1
- package/dist/default/elements/radio-group.js +3 -2
- package/dist/default/elements/readout-claim.js +0 -29
- package/dist/default/elements/scale.js +43 -0
- package/dist/default/elements/slider.js +82 -3
- package/dist/default/elements/sonic-element.js +2 -67
- package/dist/default/elements/spectrum.js +230 -0
- package/dist/default/elements/switch.js +1 -1
- package/dist/default/elements/value-element.js +81 -91
- package/dist/default/elements/wave-element.js +59 -9
- package/dist/default/elements/waveform.js +56 -10
- package/dist/default/elements/wavestrip.js +45 -21
- package/dist/default/elements/xy.js +3 -2
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +7 -4
- package/dist/default/lib/frame-timeline.js +2 -2
- package/dist/default/lib/hover-preview.js +46 -0
- package/dist/default/lib/marker-rider.js +7 -4
- package/dist/default/lib/mirror-children.js +64 -0
- package/dist/default/lib/owned-control.js +10 -0
- package/dist/default/lib/pointer-drag.js +7 -2
- package/dist/default/lib/spectrum-columns.js +74 -0
- package/dist/default/lib/spectrum-levels.js +55 -0
- package/dist/default/lib/strip-scene.js +12 -1
- package/dist/default/lib/time-regions.js +8 -1
- package/dist/dev/define/spectrum.d.ts +2 -0
- package/dist/dev/define/spectrum.js +8 -0
- package/dist/dev/define/spectrum.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +33 -4
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +5 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +29 -0
- package/dist/dev/elements/display-element.js +78 -0
- package/dist/dev/elements/display-element.js.map +1 -0
- package/dist/dev/elements/envelope.js +15 -3
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/radio-group.js +3 -2
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout-claim.js +0 -29
- package/dist/dev/elements/readout-claim.js.map +1 -1
- package/dist/dev/elements/scale.js +44 -0
- package/dist/dev/elements/scale.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +9 -0
- package/dist/dev/elements/slider.js +82 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +0 -8
- package/dist/dev/elements/sonic-element.js +3 -90
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +59 -0
- package/dist/dev/elements/spectrum.js +231 -0
- package/dist/dev/elements/spectrum.js.map +1 -0
- package/dist/dev/elements/switch.js +1 -1
- package/dist/dev/elements/value-element.d.ts +8 -6
- package/dist/dev/elements/value-element.js +81 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +8 -2
- package/dist/dev/elements/wave-element.js +59 -9
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +12 -4
- package/dist/dev/elements/waveform.js +56 -10
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +7 -0
- package/dist/dev/elements/wavestrip.js +45 -21
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +3 -2
- 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/canvas-surface.d.ts +4 -3
- package/dist/dev/lib/canvas-surface.js +7 -4
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -2
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/hover-preview.js +47 -0
- package/dist/dev/lib/hover-preview.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +7 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/mirror-children.js +80 -0
- package/dist/dev/lib/mirror-children.js.map +1 -0
- package/dist/dev/lib/owned-control.js +23 -0
- package/dist/dev/lib/owned-control.js.map +1 -0
- package/dist/dev/lib/pointer-drag.js +7 -2
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/spectrum-columns.js +75 -0
- package/dist/dev/lib/spectrum-columns.js.map +1 -0
- package/dist/dev/lib/spectrum-levels.js +56 -0
- package/dist/dev/lib/spectrum-levels.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +12 -1
- package/dist/dev/lib/strip-scene.js.map +1 -1
- package/dist/dev/lib/time-regions.js +8 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/skins/flat.css +2 -1
- package/dist/styles/button.css +28 -1
- package/dist/styles/controls.css +1 -0
- package/dist/styles/envelope.css +30 -30
- package/dist/styles/led.css +18 -2
- package/dist/styles/material.css +90 -29
- package/dist/styles/meter.css +22 -18
- package/dist/styles/panel.css +2 -1
- package/dist/styles/slider.css +42 -5
- package/dist/styles/spectrum.css +105 -0
- package/dist/styles/switch.css +10 -7
- package/dist/styles/waveform.css +38 -16
- package/dist/styles/wavestrip.css +22 -4
- package/dist/styles/xy.css +14 -30
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toNumber } from "../lib/math.js";
|
|
2
|
-
import {
|
|
2
|
+
import { appendOnce, checkChildren } from "../lib/owned-control.js";
|
|
3
3
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
4
|
|
|
5
5
|
//#region src/elements/sonic-element.ts
|
|
@@ -9,22 +9,11 @@ const namingAttributes = [
|
|
|
9
9
|
"aria-labelledby"
|
|
10
10
|
];
|
|
11
11
|
const checkedSheets = /* @__PURE__ */ new WeakMap();
|
|
12
|
-
const checkedMirrors = /* @__PURE__ */ new WeakSet();
|
|
13
|
-
const checkedChildren = /* @__PURE__ */ new WeakSet();
|
|
14
|
-
const interactive = "a[href], button, input, select, textarea, [tabindex]";
|
|
15
12
|
function writeLabelledBy(target, labels) {
|
|
16
13
|
const current = target.ariaLabelledByElements;
|
|
17
14
|
if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
|
|
18
15
|
target.ariaLabelledByElements = labels;
|
|
19
16
|
}
|
|
20
|
-
function unmirrorable(original) {
|
|
21
|
-
if (!(original instanceof Element)) return void 0;
|
|
22
|
-
return [original, ...original.querySelectorAll("*")].find((element) => element.matches(interactive) || element.localName.includes("-"));
|
|
23
|
-
}
|
|
24
|
-
function isContent(child) {
|
|
25
|
-
if (child instanceof Element) return true;
|
|
26
|
-
return child instanceof Text && child.data.trim() !== "";
|
|
27
|
-
}
|
|
28
17
|
var SonicElement = class extends HTMLElement {
|
|
29
18
|
static observedAttributes = [...namingAttributes, "disabled"];
|
|
30
19
|
get disabled() {
|
|
@@ -35,7 +24,6 @@ var SonicElement = class extends HTMLElement {
|
|
|
35
24
|
}
|
|
36
25
|
internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
|
|
37
26
|
#connection;
|
|
38
|
-
#slots;
|
|
39
27
|
blur() {
|
|
40
28
|
this.#focused()?.blur();
|
|
41
29
|
}
|
|
@@ -121,8 +109,8 @@ var SonicElement = class extends HTMLElement {
|
|
|
121
109
|
}
|
|
122
110
|
keepControl(control, signal) {
|
|
123
111
|
const keep = () => {
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
appendOnce(this, control);
|
|
113
|
+
checkChildren(this, [...this.childNodes].filter((child) => child !== control));
|
|
126
114
|
};
|
|
127
115
|
const observer = new MutationObserver(keep);
|
|
128
116
|
keep();
|
|
@@ -131,50 +119,6 @@ var SonicElement = class extends HTMLElement {
|
|
|
131
119
|
observer.disconnect();
|
|
132
120
|
}, { once: true });
|
|
133
121
|
}
|
|
134
|
-
mirrorChildren({ control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
|
|
135
|
-
const slots = this.#slots ?? attachSlots(this);
|
|
136
|
-
let copies = /* @__PURE__ */ new Map();
|
|
137
|
-
this.#slots = slots;
|
|
138
|
-
const mirror = (touched) => {
|
|
139
|
-
this.#appendOnce(control);
|
|
140
|
-
const children = [...this.childNodes].filter((child) => child !== control);
|
|
141
|
-
const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
|
|
142
|
-
const kept = copies;
|
|
143
|
-
const originals = children.filter((child) => isCopied(child));
|
|
144
|
-
this.#checkMirrored(originals);
|
|
145
|
-
this.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));
|
|
146
|
-
copies = new Map(originals.map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
|
|
147
|
-
place([...copies.values()].filter((made) => made !== void 0));
|
|
148
|
-
slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
|
|
149
|
-
slots.kept.assign(...slotted.filter((child) => isCopied(child)));
|
|
150
|
-
};
|
|
151
|
-
const watch = () => {
|
|
152
|
-
observer.disconnect();
|
|
153
|
-
observer.observe(this, { childList: true });
|
|
154
|
-
for (const child of this.childNodes) {
|
|
155
|
-
if (child === control) continue;
|
|
156
|
-
observer.observe(child, {
|
|
157
|
-
attributes: true,
|
|
158
|
-
characterData: true,
|
|
159
|
-
childList: true,
|
|
160
|
-
subtree: true
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
const observer = new MutationObserver((records) => {
|
|
165
|
-
const changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));
|
|
166
|
-
if (changes.length === 0) return;
|
|
167
|
-
if (changes.some((record) => record.target === this)) {
|
|
168
|
-
mirror();
|
|
169
|
-
watch();
|
|
170
|
-
} else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));
|
|
171
|
-
});
|
|
172
|
-
mirror();
|
|
173
|
-
watch();
|
|
174
|
-
signal.addEventListener("abort", () => {
|
|
175
|
-
observer.disconnect();
|
|
176
|
-
}, { once: true });
|
|
177
|
-
}
|
|
178
122
|
namingLabels() {
|
|
179
123
|
return [];
|
|
180
124
|
}
|
|
@@ -205,42 +149,11 @@ var SonicElement = class extends HTMLElement {
|
|
|
205
149
|
this[name] = value;
|
|
206
150
|
}
|
|
207
151
|
}
|
|
208
|
-
#appendOnce(control) {
|
|
209
|
-
const [hook] = control.classList;
|
|
210
|
-
const stale = [...this.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
|
|
211
|
-
for (const child of stale) child.remove();
|
|
212
|
-
if (control.parentNode !== this) this.append(control);
|
|
213
|
-
}
|
|
214
|
-
#checkChildren(undocumented) {
|
|
215
|
-
if (checkedChildren.has(this.constructor)) return;
|
|
216
|
-
const found = undocumented.find((child) => isContent(child));
|
|
217
|
-
if (!found) return;
|
|
218
|
-
checkedChildren.add(this.constructor);
|
|
219
|
-
console.warn(`<${this.localName}> uses only the children it documents, so the ${found instanceof Element ? `<${found.localName}>` : "text"} inside it is unsupported`);
|
|
220
|
-
}
|
|
221
|
-
#checkMirrored(originals) {
|
|
222
|
-
if (checkedMirrors.has(this.constructor)) return;
|
|
223
|
-
const [found] = originals.flatMap((original) => unmirrorable(original) ?? []);
|
|
224
|
-
if (!found) return;
|
|
225
|
-
checkedMirrors.add(this.constructor);
|
|
226
|
-
console.warn(`<${this.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`);
|
|
227
|
-
}
|
|
228
|
-
#childHolding(node) {
|
|
229
|
-
let current = node;
|
|
230
|
-
while (current && current.parentNode !== this) current = current.parentNode;
|
|
231
|
-
return current ?? void 0;
|
|
232
|
-
}
|
|
233
152
|
#focused() {
|
|
234
153
|
const root = this.getRootNode();
|
|
235
154
|
const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;
|
|
236
155
|
return active instanceof HTMLElement && this.contains(active) ? active : void 0;
|
|
237
156
|
}
|
|
238
|
-
#isCopiedChange(record, control, isCopied) {
|
|
239
|
-
if (record.target === this) return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
|
|
240
|
-
const child = this.#childHolding(record.target);
|
|
241
|
-
if (child === void 0) return false;
|
|
242
|
-
return isCopied(child) || record.type === "attributes" && record.target === child;
|
|
243
|
-
}
|
|
244
157
|
};
|
|
245
158
|
|
|
246
159
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { MirrorSlots } from '#lib/render.ts';\n\nimport { toNumber } from '#lib/math.ts';\nimport { attachSlots } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\nconst checkedMirrors = new WeakSet<object>();\n\nconst checkedChildren = new WeakSet<object>();\n\nconst interactive = 'a[href], button, input, select, textarea, [tabindex]';\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\nfunction unmirrorable(original: Node): Element | undefined {\n\tif (!(original instanceof Element)) return undefined;\n\n\treturn [original, ...original.querySelectorAll('*')].find(\n\t\t(element) => element.matches(interactive) || element.localName.includes('-'),\n\t);\n}\n\nfunction isContent(child: Node): boolean {\n\tif (child instanceof Element) return true;\n\n\treturn child instanceof Text && child.data.trim() !== '';\n}\n\ninterface Mirror {\n\tcontrol: Element;\n\tcopy: (original: ChildNode) => Node | undefined;\n\tisCopied?: (child: Node) => boolean;\n\tisPassed?: (child: Node) => boolean;\n\tplace: (copies: Array<Node>) => void;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\t#connection: AbortController | undefined;\n\n\t#slots: MirrorSlots | undefined;\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\tthis.#connection = connection;\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected checkStyles(control: HTMLElement, sheet: string): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst elementClass = this.constructor;\n\t\tconst checked = checkedSheets.get(elementClass) ?? new Set<string>();\n\t\tconst part = ({ selector }: StyleProbe): Element | undefined =>\n\t\t\tselector === undefined ? control : (this.querySelector(selector) ?? undefined);\n\t\t// An LED inherits `--_sonic-unit` from its control, so a token of its own probes it\n\t\tconst probes: Array<StyleProbe> = [\n\t\t\t{ sheet: 'material.css', token: '--_sonic-unlit' },\n\t\t\t{ sheet, token: '--_sonic-unit' },\n\t\t\t{ selector: '.sonic-led', sheet: 'led.css', token: '--_sonic-led-lens-ratio' },\n\t\t].filter((probe) => !checked.has(probe.sheet) && part(probe) !== undefined);\n\t\tif (probes.length === 0) return;\n\n\t\tcheckedSheets.set(elementClass, checked);\n\t\tfor (const probe of probes) checked.add(probe.sheet);\n\t\trequestAnimationFrame(() => {\n\t\t\tconst missing: Array<string> = [];\n\n\t\t\tfor (const probe of probes) {\n\t\t\t\tconst drawn = control.isConnected ? part(probe) : undefined;\n\n\t\t\t\tif (!drawn) checked.delete(probe.sheet);\n\t\t\t\telse if (getComputedStyle(drawn).getPropertyValue(probe.token) === '') {\n\t\t\t\t\tmissing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (missing.length === 0) return;\n\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> draws blank without ${missing.join(' and ')} (or controls.css)`,\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) writeLabelledBy(target, labels);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isBound(): boolean {\n\t\treturn this.#connection !== undefined;\n\t}\n\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A morph against server HTML, or a stray `innerHTML`, deletes the control\n\tprotected keepControl(control: Element, signal: AbortSignal): void {\n\t\tconst keep = (): void => {\n\t\t\tthis.#appendOnce(control);\n\t\t\tthis.#checkChildren([...this.childNodes].filter((child) => child !== control));\n\t\t};\n\t\tconst observer = new MutationObserver(keep);\n\n\t\tkeep();\n\t\tobserver.observe(this, { childList: true });\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\tprotected mirrorChildren(\n\t\t{ control, copy, isCopied = () => true, isPassed = () => false, place }: Mirror,\n\t\tsignal: AbortSignal,\n\t): void {\n\t\tconst slots = this.#slots ?? attachSlots(this);\n\t\tlet copies = new Map<ChildNode, Node | undefined>();\n\n\t\tthis.#slots = slots;\n\t\tconst mirror = (touched?: Set<Node | undefined>): void => {\n\t\t\tthis.#appendOnce(control);\n\n\t\t\tconst children = [...this.childNodes].filter((child) => child !== control);\n\t\t\tconst slotted = children.filter(\n\t\t\t\t(child): child is Element | Text => child instanceof Element || child instanceof Text,\n\t\t\t);\n\t\t\tconst kept = copies;\n\t\t\tconst originals = children.filter((child) => isCopied(child));\n\n\t\t\tthis.#checkMirrored(originals);\n\t\t\tthis.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));\n\t\t\tcopies = new Map(\n\t\t\t\toriginals.map((original) => [\n\t\t\t\t\toriginal,\n\t\t\t\t\ttouched && !touched.has(original) && kept.has(original)\n\t\t\t\t\t\t? kept.get(original)\n\t\t\t\t\t\t: copy(original),\n\t\t\t\t]),\n\t\t\t);\n\t\t\tplace([...copies.values()].filter((made) => made !== undefined));\n\t\t\tslots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);\n\t\t\tslots.kept.assign(...slotted.filter((child) => isCopied(child)));\n\t\t};\n\t\t// Each child is watched, never the control, so a value write wakes nothing\n\t\tconst watch = (): void => {\n\t\t\tobserver.disconnect();\n\t\t\tobserver.observe(this, { childList: true });\n\t\t\tfor (const child of this.childNodes) {\n\t\t\t\tif (child === control) continue;\n\n\t\t\t\tobserver.observe(child, {\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tcharacterData: true,\n\t\t\t\t\tchildList: true,\n\t\t\t\t\tsubtree: true,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));\n\t\t\tif (changes.length === 0) return;\n\n\t\t\tif (changes.some((record) => record.target === this)) {\n\t\t\t\tmirror();\n\t\t\t\twatch();\n\t\t\t} else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));\n\t\t});\n\n\t\tmirror();\n\t\twatch();\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\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst parsed = toNumber(this.getAttribute(name));\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// Never compared; a host attribute set again resets the value a gesture left dirty\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (typeof value !== 'number' && typeof value !== 'string' && value !== true) {\n\t\t\tthis.removeAttribute(name);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAttribute(name, value === true ? '' : value.toString());\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.internals?.states.add(state);\n\t\telse this.internals?.states.delete(state);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\t#appendOnce(control: Element): void {\n\t\tconst [hook] = control.classList;\n\t\tconst stale = [...this.children].filter(\n\t\t\t(child) => child !== control && hook !== undefined && child.classList.contains(hook),\n\t\t);\n\n\t\tfor (const child of stale) child.remove();\n\t\tif (control.parentNode !== this) this.append(control);\n\t}\n\n\t#checkChildren(undocumented: Array<Node>): void {\n\t\tif (!__DEV__ || checkedChildren.has(this.constructor)) return;\n\n\t\tconst found = undocumented.find((child) => isContent(child));\n\t\tif (!found) return;\n\n\t\tcheckedChildren.add(this.constructor);\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> uses only the children it documents, so the ${\n\t\t\t\tfound instanceof Element ? `<${found.localName}>` : 'text'\n\t\t\t} inside it is unsupported`,\n\t\t);\n\t}\n\n\t#checkMirrored(originals: Array<Node>): void {\n\t\tif (!__DEV__ || checkedMirrors.has(this.constructor)) return;\n\n\t\tconst [found] = originals.flatMap((original) => unmirrorable(original) ?? []);\n\t\tif (!found) return;\n\n\t\tcheckedMirrors.add(this.constructor);\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`,\n\t\t);\n\t}\n\n\t#childHolding(node: Node): Node | undefined {\n\t\tlet current: Node | null = node;\n\n\t\twhile (current && current.parentNode !== this) current = current.parentNode;\n\n\t\treturn current ?? undefined;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#isCopiedChange(\n\t\trecord: MutationRecord,\n\t\tcontrol: Element,\n\t\tisCopied: (child: Node) => boolean,\n\t): boolean {\n\t\tif (record.target === this) {\n\t\t\treturn (\n\t\t\t\trecord.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control)\n\t\t\t);\n\t\t}\n\n\t\tconst child = this.#childHolding(record.target);\n\n\t\tif (child === undefined) return false;\n\n\t\treturn isCopied(child) || (record.type === 'attributes' && record.target === child);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\t#connection: AbortController | undefined;\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\tthis.#connection = connection;\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected checkStyles(control: HTMLElement, sheet: string): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst elementClass = this.constructor;\n\t\tconst checked = checkedSheets.get(elementClass) ?? new Set<string>();\n\t\tconst part = ({ selector }: StyleProbe): Element | undefined =>\n\t\t\tselector === undefined ? control : (this.querySelector(selector) ?? undefined);\n\t\t// An LED inherits `--_sonic-unit` from its control, so a token of its own probes it\n\t\tconst probes: Array<StyleProbe> = [\n\t\t\t{ sheet: 'material.css', token: '--_sonic-unlit' },\n\t\t\t{ sheet, token: '--_sonic-unit' },\n\t\t\t{ selector: '.sonic-led', sheet: 'led.css', token: '--_sonic-led-lens-ratio' },\n\t\t].filter((probe) => !checked.has(probe.sheet) && part(probe) !== undefined);\n\t\tif (probes.length === 0) return;\n\n\t\tcheckedSheets.set(elementClass, checked);\n\t\tfor (const probe of probes) checked.add(probe.sheet);\n\t\trequestAnimationFrame(() => {\n\t\t\tconst missing: Array<string> = [];\n\n\t\t\tfor (const probe of probes) {\n\t\t\t\tconst drawn = control.isConnected ? part(probe) : undefined;\n\n\t\t\t\tif (!drawn) checked.delete(probe.sheet);\n\t\t\t\telse if (getComputedStyle(drawn).getPropertyValue(probe.token) === '') {\n\t\t\t\t\tmissing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (missing.length === 0) return;\n\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> draws blank without ${missing.join(' and ')} (or controls.css)`,\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) writeLabelledBy(target, labels);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isBound(): boolean {\n\t\treturn this.#connection !== undefined;\n\t}\n\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A morph against server HTML, or a stray `innerHTML`, deletes the control\n\tprotected keepControl(control: Element, signal: AbortSignal): void {\n\t\tconst keep = (): void => {\n\t\t\tappendOnce(this, control);\n\t\t\tif (__DEV__)\n\t\t\t\tcheckChildren(\n\t\t\t\t\tthis,\n\t\t\t\t\t[...this.childNodes].filter((child) => child !== control),\n\t\t\t\t);\n\t\t};\n\t\tconst observer = new MutationObserver(keep);\n\n\t\tkeep();\n\t\tobserver.observe(this, { childList: true });\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\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst parsed = toNumber(this.getAttribute(name));\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// Never compared; a host attribute set again resets the value a gesture left dirty\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (typeof value !== 'number' && typeof value !== 'string' && value !== true) {\n\t\t\tthis.removeAttribute(name);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAttribute(name, value === true ? '' : value.toString());\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.internals?.states.add(state);\n\t\telse this.internals?.states.delete(state);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { SurfaceFrame } from "../lib/canvas-surface.js";
|
|
2
|
+
import { SonicDisplayElement } from "./display-element.js";
|
|
3
|
+
//#region src/elements/spectrum.d.ts
|
|
4
|
+
declare global {
|
|
5
|
+
interface HTMLElementTagNameMap {
|
|
6
|
+
'sonic-spectrum': SonicSpectrum;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
type Colour = keyof typeof colours;
|
|
10
|
+
type Numeric = keyof typeof numbers;
|
|
11
|
+
interface SpectrumPeak {
|
|
12
|
+
decibels: number;
|
|
13
|
+
frequency: number;
|
|
14
|
+
}
|
|
15
|
+
declare const colours: {
|
|
16
|
+
readonly clip: "--_sonic-spectrum-clip";
|
|
17
|
+
readonly grid: "--_sonic-spectrum-grid";
|
|
18
|
+
readonly hot: "--_sonic-spectrum-hot";
|
|
19
|
+
readonly lit: "--_sonic-spectrum-lit";
|
|
20
|
+
};
|
|
21
|
+
declare const numbers: {
|
|
22
|
+
readonly clipFrom: "--_sonic-spectrum-clip-from";
|
|
23
|
+
readonly hotFrom: "--_sonic-spectrum-hot-from";
|
|
24
|
+
};
|
|
25
|
+
export declare class SonicSpectrum extends SonicDisplayElement<Colour, Numeric> {
|
|
26
|
+
#private;
|
|
27
|
+
static readonly observedAttributes: string[];
|
|
28
|
+
get frequencyMax(): number;
|
|
29
|
+
set frequencyMax(hertz: number | undefined);
|
|
30
|
+
get frequencyMin(): number;
|
|
31
|
+
set frequencyMin(hertz: number | undefined);
|
|
32
|
+
get grid(): 'none' | 'on';
|
|
33
|
+
set grid(grid: 'none' | 'on' | undefined);
|
|
34
|
+
get peak(): SpectrumPeak | undefined;
|
|
35
|
+
get peakHold(): boolean;
|
|
36
|
+
set peakHold(isHeld: boolean);
|
|
37
|
+
get sampleRate(): number;
|
|
38
|
+
set sampleRate(hertz: number | undefined);
|
|
39
|
+
protected readonly control: HTMLDivElement;
|
|
40
|
+
protected readonly canvas: HTMLCanvasElement;
|
|
41
|
+
protected readonly colours: {
|
|
42
|
+
readonly clip: "--_sonic-spectrum-clip";
|
|
43
|
+
readonly grid: "--_sonic-spectrum-grid";
|
|
44
|
+
readonly hot: "--_sonic-spectrum-hot";
|
|
45
|
+
readonly lit: "--_sonic-spectrum-lit";
|
|
46
|
+
};
|
|
47
|
+
protected readonly numbers: {
|
|
48
|
+
readonly clipFrom: "--_sonic-spectrum-clip-from";
|
|
49
|
+
readonly hotFrom: "--_sonic-spectrum-hot-from";
|
|
50
|
+
};
|
|
51
|
+
protected readonly sheet = "spectrum.css";
|
|
52
|
+
connectedCallback(): void;
|
|
53
|
+
resetPeak(): void;
|
|
54
|
+
protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>): void;
|
|
55
|
+
protected pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;
|
|
56
|
+
protected receive(frame: Float32Array): void;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//# sourceMappingURL=spectrum.d.ts.map
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { SonicDisplayElement } from "./display-element.js";
|
|
3
|
+
import { columnEdges, columnLevels, gridColumns, gridRows, levelRows, loudestBin, zoneRows } from "../lib/spectrum-columns.js";
|
|
4
|
+
import { areSettled, createLevels, receiveLevels, stepLevels } from "../lib/spectrum-levels.js";
|
|
5
|
+
|
|
6
|
+
//#region src/elements/spectrum.ts
|
|
7
|
+
const colours = {
|
|
8
|
+
clip: "--_sonic-spectrum-clip",
|
|
9
|
+
grid: "--_sonic-spectrum-grid",
|
|
10
|
+
hot: "--_sonic-spectrum-hot",
|
|
11
|
+
lit: "--_sonic-spectrum-lit"
|
|
12
|
+
};
|
|
13
|
+
const numbers = {
|
|
14
|
+
clipFrom: "--_sonic-spectrum-clip-from",
|
|
15
|
+
hotFrom: "--_sonic-spectrum-hot-from"
|
|
16
|
+
};
|
|
17
|
+
const defaultFrequencies = [20, 2e4];
|
|
18
|
+
const defaultSampleRate = 48e3;
|
|
19
|
+
const renderSpectrum = template(`
|
|
20
|
+
<div aria-hidden="true" class="sonic-spectrum">
|
|
21
|
+
<canvas class="sonic-spectrum-canvas"></canvas>
|
|
22
|
+
</div>
|
|
23
|
+
`, HTMLDivElement);
|
|
24
|
+
function paintGrid(context, { dpr, height, look, range, rowOf, width }, [frequencyMin, frequencyMax]) {
|
|
25
|
+
const line = Math.max(1, Math.round(dpr));
|
|
26
|
+
const columns = gridColumns({
|
|
27
|
+
frequencyMax,
|
|
28
|
+
frequencyMin
|
|
29
|
+
}, width);
|
|
30
|
+
const rows = gridRows(range, rowOf);
|
|
31
|
+
context.beginPath();
|
|
32
|
+
for (const column of columns) context.rect(column, 0, line, height);
|
|
33
|
+
for (const row of rows) context.rect(0, row, width, line);
|
|
34
|
+
context.fillStyle = look.colours.grid;
|
|
35
|
+
context.fill();
|
|
36
|
+
}
|
|
37
|
+
function zoneFill(context, { height, look, rowOf }) {
|
|
38
|
+
const gradient = context.createLinearGradient(0, 0, 0, height);
|
|
39
|
+
const { clip, hot } = zoneRows(look.numbers, rowOf);
|
|
40
|
+
gradient.addColorStop(clip / height, look.colours.clip);
|
|
41
|
+
gradient.addColorStop(clip / height, look.colours.hot);
|
|
42
|
+
gradient.addColorStop(hot / height, look.colours.hot);
|
|
43
|
+
gradient.addColorStop(hot / height, look.colours.lit);
|
|
44
|
+
return gradient;
|
|
45
|
+
}
|
|
46
|
+
function paintBars(context, scene, bars) {
|
|
47
|
+
const { height, rowOf } = scene;
|
|
48
|
+
let previous = height;
|
|
49
|
+
context.beginPath();
|
|
50
|
+
context.moveTo(0, height);
|
|
51
|
+
for (const [column, level] of bars.entries()) {
|
|
52
|
+
const row = rowOf(level);
|
|
53
|
+
bars[column] = row;
|
|
54
|
+
if (row === previous) continue;
|
|
55
|
+
context.lineTo(column, previous);
|
|
56
|
+
context.lineTo(column, row);
|
|
57
|
+
previous = row;
|
|
58
|
+
}
|
|
59
|
+
context.lineTo(bars.length, previous);
|
|
60
|
+
context.lineTo(bars.length, height);
|
|
61
|
+
context.closePath();
|
|
62
|
+
context.fillStyle = zoneFill(context, scene);
|
|
63
|
+
context.fill();
|
|
64
|
+
}
|
|
65
|
+
function paintHolds(context, { look, rowOf }, { bars, holds }) {
|
|
66
|
+
context.beginPath();
|
|
67
|
+
for (const [column, level] of holds.entries()) {
|
|
68
|
+
const row = rowOf(level);
|
|
69
|
+
if (row < (bars[column] ?? 0)) context.rect(column, row, 1, 1);
|
|
70
|
+
}
|
|
71
|
+
context.fillStyle = look.colours.lit;
|
|
72
|
+
context.fill();
|
|
73
|
+
}
|
|
74
|
+
var SonicSpectrum = class extends SonicDisplayElement {
|
|
75
|
+
static observedAttributes = [
|
|
76
|
+
...SonicDisplayElement.observedAttributes,
|
|
77
|
+
"frequency-max",
|
|
78
|
+
"frequency-min",
|
|
79
|
+
"grid",
|
|
80
|
+
"peak-hold",
|
|
81
|
+
"sample-rate"
|
|
82
|
+
];
|
|
83
|
+
get frequencyMax() {
|
|
84
|
+
return this.numberAttribute("frequency-max", defaultFrequencies[1]);
|
|
85
|
+
}
|
|
86
|
+
set frequencyMax(hertz) {
|
|
87
|
+
this.reflect("frequency-max", hertz);
|
|
88
|
+
}
|
|
89
|
+
get frequencyMin() {
|
|
90
|
+
return this.numberAttribute("frequency-min", defaultFrequencies[0]);
|
|
91
|
+
}
|
|
92
|
+
set frequencyMin(hertz) {
|
|
93
|
+
this.reflect("frequency-min", hertz);
|
|
94
|
+
}
|
|
95
|
+
get grid() {
|
|
96
|
+
return this.getAttribute("grid") === "none" ? "none" : "on";
|
|
97
|
+
}
|
|
98
|
+
set grid(grid) {
|
|
99
|
+
this.reflect("grid", grid);
|
|
100
|
+
}
|
|
101
|
+
get peak() {
|
|
102
|
+
return this.#peak && { ...this.#peak };
|
|
103
|
+
}
|
|
104
|
+
get peakHold() {
|
|
105
|
+
return this.hasAttribute("peak-hold");
|
|
106
|
+
}
|
|
107
|
+
set peakHold(isHeld) {
|
|
108
|
+
this.reflect("peak-hold", isHeld);
|
|
109
|
+
}
|
|
110
|
+
get sampleRate() {
|
|
111
|
+
const sampleRate = this.numberAttribute("sample-rate", defaultSampleRate);
|
|
112
|
+
return sampleRate > 0 ? sampleRate : defaultSampleRate;
|
|
113
|
+
}
|
|
114
|
+
set sampleRate(hertz) {
|
|
115
|
+
this.reflect("sample-rate", hertz);
|
|
116
|
+
}
|
|
117
|
+
control = renderSpectrum();
|
|
118
|
+
canvas = requireChild(this.control, ".sonic-spectrum-canvas", HTMLCanvasElement);
|
|
119
|
+
colours = colours;
|
|
120
|
+
numbers = numbers;
|
|
121
|
+
sheet = "spectrum.css";
|
|
122
|
+
#columns;
|
|
123
|
+
#isSettled = true;
|
|
124
|
+
#lastFrameMs;
|
|
125
|
+
#levels = createLevels(0);
|
|
126
|
+
#peak;
|
|
127
|
+
#receivedRate;
|
|
128
|
+
connectedCallback() {
|
|
129
|
+
this.upgradeProperties("frequencyMax", "frequencyMin", "grid", "peakHold", "sampleRate");
|
|
130
|
+
super.connectedCallback();
|
|
131
|
+
}
|
|
132
|
+
resetPeak() {
|
|
133
|
+
this.#peak = void 0;
|
|
134
|
+
}
|
|
135
|
+
paint(context, { frameMs, isDirty, look, size }) {
|
|
136
|
+
if (this.isDisabled()) {
|
|
137
|
+
if (isDirty) this.#draw(context, look, size);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const wasSettled = this.#isSettled;
|
|
141
|
+
this.#step(frameMs, look.isReducedMotion);
|
|
142
|
+
if (isDirty || !wasSettled || !this.#isSettled) this.#draw(context, look, size);
|
|
143
|
+
if (!this.#isSettled) this.surface()?.requestFrame();
|
|
144
|
+
}
|
|
145
|
+
pull(analyser, buffer) {
|
|
146
|
+
analyser.getFloatFrequencyData(buffer);
|
|
147
|
+
}
|
|
148
|
+
receive(frame) {
|
|
149
|
+
if (frame.length !== this.#levels.bars.length) this.#levels = createLevels(frame.length);
|
|
150
|
+
this.#receivedRate = this.analyser?.context.sampleRate ?? this.sampleRate;
|
|
151
|
+
receiveLevels(this.#levels, frame, this.min);
|
|
152
|
+
const loudest = loudestBin(frame, this.#axis());
|
|
153
|
+
if (loudest && loudest.decibels > (this.#peak?.decibels ?? -Infinity)) this.#peak = loudest;
|
|
154
|
+
}
|
|
155
|
+
#axis() {
|
|
156
|
+
const [frequencyMin, frequencyMax] = this.#frequencies();
|
|
157
|
+
return {
|
|
158
|
+
binCount: this.#levels.bars.length,
|
|
159
|
+
frequencyMax,
|
|
160
|
+
frequencyMin,
|
|
161
|
+
sampleRate: this.#receivedRate ?? this.sampleRate
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
#columnsFor(width) {
|
|
165
|
+
const axis = this.#axis();
|
|
166
|
+
const key = [
|
|
167
|
+
width,
|
|
168
|
+
axis.binCount,
|
|
169
|
+
axis.sampleRate,
|
|
170
|
+
axis.frequencyMin,
|
|
171
|
+
axis.frequencyMax
|
|
172
|
+
].join(" ");
|
|
173
|
+
if (this.#columns?.key !== key) this.#columns = {
|
|
174
|
+
bars: new Float64Array(width),
|
|
175
|
+
edges: columnEdges(axis, width),
|
|
176
|
+
holds: new Float64Array(width),
|
|
177
|
+
key
|
|
178
|
+
};
|
|
179
|
+
return this.#columns;
|
|
180
|
+
}
|
|
181
|
+
#draw(context, look, size) {
|
|
182
|
+
const scene = {
|
|
183
|
+
...size,
|
|
184
|
+
look,
|
|
185
|
+
range: {
|
|
186
|
+
max: this.max,
|
|
187
|
+
min: this.min
|
|
188
|
+
},
|
|
189
|
+
rowOf: levelRows({
|
|
190
|
+
max: this.max,
|
|
191
|
+
min: this.min
|
|
192
|
+
}, size.height)
|
|
193
|
+
};
|
|
194
|
+
context.clearRect(0, 0, size.width, size.height);
|
|
195
|
+
if (this.grid === "on") paintGrid(context, scene, this.#frequencies());
|
|
196
|
+
if (this.isDisabled()) return;
|
|
197
|
+
const { bars, edges, holds } = this.#columnsFor(size.width);
|
|
198
|
+
const spread = {
|
|
199
|
+
edges,
|
|
200
|
+
floor: scene.range.min
|
|
201
|
+
};
|
|
202
|
+
columnLevels(bars, this.#levels.bars, spread);
|
|
203
|
+
paintBars(context, scene, bars);
|
|
204
|
+
if (!this.peakHold) return;
|
|
205
|
+
columnLevels(holds, this.#levels.holds, spread);
|
|
206
|
+
paintHolds(context, scene, {
|
|
207
|
+
bars,
|
|
208
|
+
holds
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
#frequencies() {
|
|
212
|
+
const { frequencyMax, frequencyMin } = this;
|
|
213
|
+
return frequencyMin > 0 && frequencyMax > frequencyMin ? [frequencyMin, frequencyMax] : defaultFrequencies;
|
|
214
|
+
}
|
|
215
|
+
#step(frameMs, isReducedMotion) {
|
|
216
|
+
const floor = this.min;
|
|
217
|
+
stepLevels(this.#levels, {
|
|
218
|
+
elapsedMs: Math.max(0, frameMs - (this.#lastFrameMs ?? frameMs)),
|
|
219
|
+
floor,
|
|
220
|
+
isHeld: this.peakHold,
|
|
221
|
+
isHoldStepped: isReducedMotion,
|
|
222
|
+
nowMs: frameMs
|
|
223
|
+
});
|
|
224
|
+
this.#isSettled = areSettled(this.#levels, floor);
|
|
225
|
+
this.#lastFrameMs = this.#isSettled ? void 0 : frameMs;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
export { SonicSpectrum };
|
|
231
|
+
//# sourceMappingURL=spectrum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spectrum.js","names":[],"sources":["../../../src/elements/spectrum.ts"],"sourcesContent":["import type { SurfaceFrame, SurfaceLook, SurfaceSize } from '#lib/canvas-surface.ts';\nimport type { FrequencyAxis } from '#lib/spectrum-columns.ts';\n\nimport { SonicDisplayElement } from '#elements/display-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport {\n\tcolumnEdges,\n\tcolumnLevels,\n\tgridColumns,\n\tgridRows,\n\tlevelRows,\n\tloudestBin,\n\tzoneRows,\n} from '#lib/spectrum-columns.ts';\nimport { areSettled, createLevels, receiveLevels, stepLevels } from '#lib/spectrum-levels.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-spectrum': SonicSpectrum;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Numeric = keyof typeof numbers;\n\ntype Look = SurfaceLook<Colour, never, Numeric>;\n\ninterface SpectrumPeak {\n\tdecibels: number;\n\tfrequency: number;\n}\n\ninterface Scene extends SurfaceSize {\n\tlook: Look;\n\trange: { max: number; min: number };\n\trowOf: (level: number) => number;\n}\n\ninterface Columns {\n\tbars: Float64Array;\n\tedges: Float64Array;\n\tholds: Float64Array;\n\tkey: string;\n}\n\nconst colours = {\n\tclip: '--_sonic-spectrum-clip',\n\tgrid: '--_sonic-spectrum-grid',\n\thot: '--_sonic-spectrum-hot',\n\tlit: '--_sonic-spectrum-lit',\n} as const;\n\nconst numbers = {\n\tclipFrom: '--_sonic-spectrum-clip-from',\n\thotFrom: '--_sonic-spectrum-hot-from',\n} as const;\n\nconst defaultFrequencies: [number, number] = [20, 20_000];\nconst defaultSampleRate = 48_000;\n\nconst renderSpectrum = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-spectrum\">\n\t\t\t<canvas class=\"sonic-spectrum-canvas\"></canvas>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\n// One path, since a crossing filled twice would show brighter than its lines\nfunction paintGrid(\n\tcontext: CanvasRenderingContext2D,\n\t{ dpr, height, look, range, rowOf, width }: Scene,\n\t[frequencyMin, frequencyMax]: [number, number],\n): void {\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst columns = gridColumns({ frequencyMax, frequencyMin }, width);\n\tconst rows = gridRows(range, rowOf);\n\n\tcontext.beginPath();\n\tfor (const column of columns) context.rect(column, 0, line, height);\n\tfor (const row of rows) context.rect(0, row, width, line);\n\tcontext.fillStyle = look.colours.grid;\n\tcontext.fill();\n}\n\nfunction zoneFill(\n\tcontext: CanvasRenderingContext2D,\n\t{ height, look, rowOf }: Scene,\n): CanvasGradient {\n\tconst gradient = context.createLinearGradient(0, 0, 0, height);\n\tconst { clip, hot } = zoneRows(look.numbers, rowOf);\n\n\tgradient.addColorStop(clip / height, look.colours.clip);\n\tgradient.addColorStop(clip / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.lit);\n\n\treturn gradient;\n}\n\n// Whole device pixels, so nothing is antialiased\nfunction paintBars(context: CanvasRenderingContext2D, scene: Scene, bars: Float64Array): void {\n\tconst { height, rowOf } = scene;\n\tlet previous = height;\n\n\tcontext.beginPath();\n\tcontext.moveTo(0, height);\n\tfor (const [column, level] of bars.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tbars[column] = row;\n\t\tif (row === previous) continue;\n\n\t\tcontext.lineTo(column, previous);\n\t\tcontext.lineTo(column, row);\n\t\tprevious = row;\n\t}\n\tcontext.lineTo(bars.length, previous);\n\tcontext.lineTo(bars.length, height);\n\tcontext.closePath();\n\tcontext.fillStyle = zoneFill(context, scene);\n\tcontext.fill();\n}\n\nfunction paintHolds(\n\tcontext: CanvasRenderingContext2D,\n\t{ look, rowOf }: Scene,\n\t{ bars, holds }: { bars: Float64Array; holds: Float64Array },\n): void {\n\tcontext.beginPath();\n\tfor (const [column, level] of holds.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tif (row < (bars[column] ?? 0)) context.rect(column, row, 1, 1);\n\t}\n\tcontext.fillStyle = look.colours.lit;\n\tcontext.fill();\n}\n\nexport class SonicSpectrum extends SonicDisplayElement<Colour, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicDisplayElement.observedAttributes,\n\t\t'frequency-max',\n\t\t'frequency-min',\n\t\t'grid',\n\t\t'peak-hold',\n\t\t'sample-rate',\n\t];\n\n\tget frequencyMax(): number {\n\t\treturn this.numberAttribute('frequency-max', defaultFrequencies[1]);\n\t}\n\n\tset frequencyMax(hertz: number | undefined) {\n\t\tthis.reflect('frequency-max', hertz);\n\t}\n\n\tget frequencyMin(): number {\n\t\treturn this.numberAttribute('frequency-min', defaultFrequencies[0]);\n\t}\n\n\tset frequencyMin(hertz: number | undefined) {\n\t\tthis.reflect('frequency-min', hertz);\n\t}\n\n\tget grid(): 'none' | 'on' {\n\t\treturn this.getAttribute('grid') === 'none' ? 'none' : 'on';\n\t}\n\n\tset grid(grid: 'none' | 'on' | undefined) {\n\t\tthis.reflect('grid', grid);\n\t}\n\n\tget peak(): SpectrumPeak | undefined {\n\t\treturn this.#peak && { ...this.#peak };\n\t}\n\n\tget peakHold(): boolean {\n\t\treturn this.hasAttribute('peak-hold');\n\t}\n\n\tset peakHold(isHeld: boolean) {\n\t\tthis.reflect('peak-hold', isHeld);\n\t}\n\n\tget sampleRate(): number {\n\t\tconst sampleRate = this.numberAttribute('sample-rate', defaultSampleRate);\n\n\t\treturn sampleRate > 0 ? sampleRate : defaultSampleRate;\n\t}\n\n\tset sampleRate(hertz: number | undefined) {\n\t\tthis.reflect('sample-rate', hertz);\n\t}\n\n\tprotected readonly control = renderSpectrum();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-spectrum-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly numbers = numbers;\n\n\tprotected readonly sheet = 'spectrum.css';\n\n\t#columns: Columns | undefined;\n\n\t#isSettled = true;\n\n\t#lastFrameMs: number | undefined;\n\n\t#levels = createLevels(0);\n\n\t#peak: SpectrumPeak | undefined;\n\n\t#receivedRate: number | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('frequencyMax', 'frequencyMin', 'grid', 'peakHold', 'sampleRate');\n\t\tsuper.connectedCallback();\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = undefined;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tif (this.isDisabled()) {\n\t\t\tif (isDirty) this.#draw(context, look, size);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wasSettled = this.#isSettled;\n\n\t\tthis.#step(frameMs, look.isReducedMotion);\n\t\tif (isDirty || !wasSettled || !this.#isSettled) this.#draw(context, look, size);\n\t\tif (!this.#isSettled) this.surface()?.requestFrame();\n\t}\n\n\tprotected pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void {\n\t\tanalyser.getFloatFrequencyData(buffer);\n\t}\n\n\tprotected receive(frame: Float32Array): void {\n\t\tif (frame.length !== this.#levels.bars.length) this.#levels = createLevels(frame.length);\n\t\tthis.#receivedRate = this.analyser?.context.sampleRate ?? this.sampleRate;\n\t\treceiveLevels(this.#levels, frame, this.min);\n\n\t\tconst loudest = loudestBin(frame, this.#axis());\n\n\t\tif (loudest && loudest.decibels > (this.#peak?.decibels ?? -Infinity)) this.#peak = loudest;\n\t}\n\n\t#axis(): FrequencyAxis {\n\t\tconst [frequencyMin, frequencyMax] = this.#frequencies();\n\n\t\treturn {\n\t\t\tbinCount: this.#levels.bars.length,\n\t\t\tfrequencyMax,\n\t\t\tfrequencyMin,\n\t\t\t// The bars on show keep the rate they arrived at once the analyser is unset\n\t\t\tsampleRate: this.#receivedRate ?? this.sampleRate,\n\t\t};\n\t}\n\n\t#columnsFor(width: number): Columns {\n\t\tconst axis = this.#axis();\n\t\tconst key = [width, axis.binCount, axis.sampleRate, axis.frequencyMin, axis.frequencyMax].join(\n\t\t\t' ',\n\t\t);\n\n\t\tif (this.#columns?.key !== key) {\n\t\t\tthis.#columns = {\n\t\t\t\tbars: new Float64Array(width),\n\t\t\t\tedges: columnEdges(axis, width),\n\t\t\t\tholds: new Float64Array(width),\n\t\t\t\tkey,\n\t\t\t};\n\t\t}\n\n\t\treturn this.#columns;\n\t}\n\n\t#draw(context: CanvasRenderingContext2D, look: Look, size: SurfaceSize): void {\n\t\tconst scene = {\n\t\t\t...size,\n\t\t\tlook,\n\t\t\trange: { max: this.max, min: this.min },\n\t\t\trowOf: levelRows({ max: this.max, min: this.min }, size.height),\n\t\t};\n\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (this.grid === 'on') paintGrid(context, scene, this.#frequencies());\n\t\tif (this.isDisabled()) return;\n\n\t\tconst { bars, edges, holds } = this.#columnsFor(size.width);\n\t\tconst spread = { edges, floor: scene.range.min };\n\n\t\tcolumnLevels(bars, this.#levels.bars, spread);\n\t\tpaintBars(context, scene, bars);\n\t\tif (!this.peakHold) return;\n\n\t\tcolumnLevels(holds, this.#levels.holds, spread);\n\t\tpaintHolds(context, scene, { bars, holds });\n\t}\n\n\t#frequencies(): [number, number] {\n\t\tconst { frequencyMax, frequencyMin } = this;\n\n\t\treturn frequencyMin > 0 && frequencyMax > frequencyMin\n\t\t\t? [frequencyMin, frequencyMax]\n\t\t\t: defaultFrequencies;\n\t}\n\n\t#step(frameMs: number, isReducedMotion: boolean): void {\n\t\tconst floor = this.min;\n\n\t\tstepLevels(this.#levels, {\n\t\t\telapsedMs: Math.max(0, frameMs - (this.#lastFrameMs ?? frameMs)),\n\t\t\tfloor,\n\t\t\tisHeld: this.peakHold,\n\t\t\tisHoldStepped: isReducedMotion,\n\t\t\tnowMs: frameMs,\n\t\t});\n\t\tthis.#isSettled = areSettled(this.#levels, floor);\n\t\tthis.#lastFrameMs = this.#isSettled ? undefined : frameMs;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { clamp } from "../lib/math.js";
|
|
2
|
-
import { requireChild, template } from "../lib/render.js";
|
|
3
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
4
3
|
import { bindHold } from "../lib/hold.js";
|
|
4
|
+
import { requireChild, template } from "../lib/render.js";
|
|
5
5
|
import { bindDrag } from "../lib/pointer-drag.js";
|
|
6
6
|
import { SonicRadioGroupElement, isUnder, optionValue } from "./radio-group.js";
|
|
7
7
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SonicFormElement } from "./form-element.js";
|
|
2
|
-
import { TimeRegions } from "../lib/time-regions.js";
|
|
3
2
|
import { ValueMapping } from "../lib/value-mapping.js";
|
|
4
3
|
//#region src/elements/value-element.d.ts
|
|
5
4
|
export interface ValueAxis {
|
|
@@ -12,8 +11,6 @@ export interface ValueAxis {
|
|
|
12
11
|
export declare abstract class SonicValueElement extends SonicFormElement {
|
|
13
12
|
#private;
|
|
14
13
|
static readonly observedAttributes: string[];
|
|
15
|
-
get buffered(): Array<[number, number]>;
|
|
16
|
-
set buffered(regions: TimeRegions | undefined);
|
|
17
14
|
get default(): number | undefined;
|
|
18
15
|
set default(value: number | undefined);
|
|
19
16
|
get detent(): number | undefined;
|
|
@@ -40,10 +37,13 @@ export declare abstract class SonicValueElement extends SonicFormElement {
|
|
|
40
37
|
set origin(value: number | undefined);
|
|
41
38
|
get parseValue(): ((text: string) => number) | undefined;
|
|
42
39
|
set parseValue(parse: ((text: string) => number) | undefined);
|
|
40
|
+
get pointerType(): string | undefined;
|
|
43
41
|
get positions(): Array<number> | undefined;
|
|
44
42
|
set positions(positions: Array<number> | undefined);
|
|
45
43
|
get readout(): boolean;
|
|
46
44
|
set readout(isShown: boolean);
|
|
45
|
+
get spokenStep(): number | undefined;
|
|
46
|
+
set spokenStep(value: number | undefined);
|
|
47
47
|
get step(): number;
|
|
48
48
|
set step(value: number | undefined);
|
|
49
49
|
get taper(): 'linear' | 'log';
|
|
@@ -54,24 +54,26 @@ export declare abstract class SonicValueElement extends SonicFormElement {
|
|
|
54
54
|
attributeChangedCallback(name: string): void;
|
|
55
55
|
connectedCallback(): void;
|
|
56
56
|
protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void;
|
|
57
|
-
protected bindScale(control: HTMLElement, marks: HTMLElement, signal: AbortSignal): void;
|
|
58
57
|
protected controlOrientation(): 'horizontal' | 'vertical' | undefined;
|
|
59
58
|
protected controlRole(): 'slider' | 'spinbutton';
|
|
60
59
|
protected abstract draw(): void;
|
|
61
60
|
protected abstract focusTarget(): HTMLElement;
|
|
62
61
|
protected heldFrom(): number | undefined;
|
|
63
|
-
protected heldWrite(
|
|
62
|
+
protected heldWrite(next: number): void;
|
|
64
63
|
protected holdChanged(): void;
|
|
65
|
-
protected hoverReadout(value: number | undefined): void;
|
|
66
64
|
protected input(next: number): boolean;
|
|
65
|
+
protected isEditing(): boolean;
|
|
67
66
|
protected isRevealed(): boolean;
|
|
68
67
|
protected isWrapping(): boolean;
|
|
69
68
|
protected mapping(): ValueMapping;
|
|
70
69
|
protected originValue(): number;
|
|
70
|
+
protected playback(): number;
|
|
71
|
+
protected previewValue(): number | undefined;
|
|
71
72
|
protected proportionsChanged(): void;
|
|
72
73
|
protected readModulationValue(): number | undefined;
|
|
73
74
|
protected readoutValue(): number;
|
|
74
75
|
protected render(): void;
|
|
76
|
+
protected renderReadout(): void;
|
|
75
77
|
protected restoreState(state: string): void;
|
|
76
78
|
protected scrubsKeyRepeat(): boolean;
|
|
77
79
|
protected springTarget(): number | undefined;
|