@xsynaptic/sonic-ui 0.27.0 → 0.28.1
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 +19 -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/sonic-element.js +1 -1
- package/dist/default/elements/ticker.js +331 -0
- package/dist/default/elements/wavestrip.js +2 -5
- package/dist/default/index.js +2 -1
- package/dist/default/lib/cancel-zone.js +12 -0
- 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/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/meter.d.ts +1 -0
- package/dist/dev/elements/position-group.d.ts +1 -0
- package/dist/dev/elements/sonic-element.d.ts +1 -0
- package/dist/dev/elements/sonic-element.js +3 -2
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +1 -0
- package/dist/dev/elements/ticker.d.ts +25 -0
- package/dist/dev/elements/ticker.js +332 -0
- package/dist/dev/elements/ticker.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +1 -0
- package/dist/dev/elements/wavestrip.d.ts +1 -0
- package/dist/dev/elements/wavestrip.js +2 -5
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/index.d.ts +2 -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/check-styles.js +20 -12
- package/dist/dev/lib/check-styles.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +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/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 +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { BoxProbe } from '#lib/check-styles.ts';\n\nimport { checkStyles } from '#lib/check-styles.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst canReferElements = 'ariaLabelledByElements' in Element.prototype;\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\tstatic #labelCount = 0;\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\tdeclare protected readonly boxProbe: BoxProbe | undefined;\n\n\tdeclare protected readonly control: HTMLElement | undefined;\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\tdeclare protected readonly sheet: string | undefined;\n\n\t#connection: AbortController | undefined;\n\n\treadonly #states = new Set<string>();\n\n\tstatic #labelId(label: Element): string {\n\t\tif (label.id === '') {\n\t\t\tSonicElement.#labelCount += 1;\n\t\t\tlabel.id = `sonic-label-${String(SonicElement.#labelCount)}`;\n\t\t}\n\n\t\treturn label.id;\n\t}\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\n\t\tconst { boxProbe: box, control, sheet } = this;\n\n\t\tif (__DEV__ && control && sheet !== undefined) checkStyles(this, { box, control, sheet });\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 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) this.#writeLabels(target, labels);\n\t}\n\n\tprotected hasState(state: string): boolean {\n\t\treturn this.#states.has(state);\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(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\t\tif (!control) return;\n\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 && !this.usesChild(child)),\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 stateTarget(): Element | undefined {\n\t\treturn this.control;\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.#states.add(state);\n\t\telse this.#states.delete(state);\n\t\tthis.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);\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\tprotected usesChild(_child: Node): boolean {\n\t\treturn false;\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#writeLabels(target: Element, labels: Array<Element>): void {\n\t\tif (canReferElements) {\n\t\t\twriteLabelledBy(target, labels);\n\t\t\treturn;\n\t\t}\n\n\t\tconst ids = labels.map((label) => SonicElement.#labelId(label));\n\n\t\twriteAttribute(target, 'aria-labelledby', ids.join(' '));\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 type { BoxProbe } from '#lib/check-styles.ts';\n\nimport { checkStyles } from '#lib/check-styles.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst canReferElements = 'ariaLabelledByElements' in Element.prototype;\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\tstatic #labelCount = 0;\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\tdeclare protected readonly boxProbe: BoxProbe | undefined;\n\n\tdeclare protected readonly control: HTMLElement | undefined;\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\tdeclare protected readonly sheet: string | undefined;\n\n\tdeclare protected readonly sheetToken: `--_sonic-${string}` | undefined;\n\n\t#connection: AbortController | undefined;\n\n\treadonly #states = new Set<string>();\n\n\tstatic #labelId(label: Element): string {\n\t\tif (label.id === '') {\n\t\t\tSonicElement.#labelCount += 1;\n\t\t\tlabel.id = `sonic-label-${String(SonicElement.#labelCount)}`;\n\t\t}\n\n\t\treturn label.id;\n\t}\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\n\t\tconst { boxProbe: box, control, sheet, sheetToken: token } = this;\n\n\t\tif (__DEV__ && control && sheet !== undefined)\n\t\t\tcheckStyles(this, { box, control, sheet, token });\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 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) this.#writeLabels(target, labels);\n\t}\n\n\tprotected hasState(state: string): boolean {\n\t\treturn this.#states.has(state);\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(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\t\tif (!control) return;\n\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 && !this.usesChild(child)),\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 stateTarget(): Element | undefined {\n\t\treturn this.control;\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.#states.add(state);\n\t\telse this.#states.delete(state);\n\t\tthis.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);\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\tprotected usesChild(_child: Node): boolean {\n\t\treturn false;\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#writeLabels(target: Element, labels: Array<Element>): void {\n\t\tif (canReferElements) {\n\t\t\twriteLabelledBy(target, labels);\n\t\t\treturn;\n\t\t}\n\n\t\tconst ids = labels.map((label) => SonicElement.#labelId(label));\n\n\t\twriteAttribute(target, 'aria-labelledby', ids.join(' '));\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SonicElement } from "./sonic-element.js";
|
|
2
|
+
//#region src/elements/ticker.d.ts
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'sonic-ticker': SonicTicker;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare class SonicTicker extends SonicElement {
|
|
9
|
+
#private;
|
|
10
|
+
static readonly observedAttributes: string[];
|
|
11
|
+
get cancellable(): boolean;
|
|
12
|
+
set cancellable(isCancellable: boolean);
|
|
13
|
+
get paused(): boolean;
|
|
14
|
+
set paused(isPaused: boolean);
|
|
15
|
+
get text(): string;
|
|
16
|
+
set text(text: string | undefined);
|
|
17
|
+
protected readonly control: HTMLSpanElement;
|
|
18
|
+
protected readonly sheet = "ticker.css";
|
|
19
|
+
protected readonly sheetToken = "--_sonic-ticker-fade";
|
|
20
|
+
attributeChangedCallback(name: string): void;
|
|
21
|
+
protected connect(signal: AbortSignal): void;
|
|
22
|
+
protected render(): void;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//# sourceMappingURL=ticker.d.ts.map
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { clamp } from "../lib/math.js";
|
|
2
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
|
+
import { SonicElement } from "./sonic-element.js";
|
|
4
|
+
import { copyNode } from "../lib/copy-node.js";
|
|
5
|
+
import { placeChildren, requireChild, template } from "../lib/render.js";
|
|
6
|
+
import { mirrorChildren } from "../lib/mirror-children.js";
|
|
7
|
+
import { bindDrag, capturePointer } from "../lib/pointer-drag.js";
|
|
8
|
+
import { readMsProperty } from "../lib/read-ms-property.js";
|
|
9
|
+
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
10
|
+
import { readPxProperty } from "../lib/read-px-property.js";
|
|
11
|
+
import { outsideCancelZone } from "../lib/cancel-zone.js";
|
|
12
|
+
import { clippedEnds, fadeWidth, legDeparture, legFades, legPosition, tickerLeg } from "../lib/ticker-leg.js";
|
|
13
|
+
|
|
14
|
+
//#region src/elements/ticker.ts
|
|
15
|
+
const renderTicker = template(`<span class="sonic-ticker"><span class="sonic-ticker-tape"></span></span>`, HTMLSpanElement);
|
|
16
|
+
const fadePx = 12;
|
|
17
|
+
const holdMs = 2e3;
|
|
18
|
+
const ratePxPerSecond = 40;
|
|
19
|
+
var SonicTicker = class extends SonicElement {
|
|
20
|
+
static observedAttributes = ["disabled", "paused"];
|
|
21
|
+
get cancellable() {
|
|
22
|
+
return this.hasAttribute("cancellable");
|
|
23
|
+
}
|
|
24
|
+
set cancellable(isCancellable) {
|
|
25
|
+
this.reflect("cancellable", isCancellable);
|
|
26
|
+
}
|
|
27
|
+
get paused() {
|
|
28
|
+
return this.hasAttribute("paused");
|
|
29
|
+
}
|
|
30
|
+
set paused(isPaused) {
|
|
31
|
+
this.reflect("paused", isPaused);
|
|
32
|
+
}
|
|
33
|
+
get text() {
|
|
34
|
+
return this.#originals().map((original) => original.textContent).join("");
|
|
35
|
+
}
|
|
36
|
+
set text(text) {
|
|
37
|
+
const next = text ?? "";
|
|
38
|
+
if (this.#holds(next)) return;
|
|
39
|
+
for (const original of this.#originals()) original.remove();
|
|
40
|
+
if (next !== "") this.prepend(next);
|
|
41
|
+
}
|
|
42
|
+
control = renderTicker();
|
|
43
|
+
sheet = "ticker.css";
|
|
44
|
+
sheetToken = "--_sonic-ticker-fade";
|
|
45
|
+
#drag;
|
|
46
|
+
#fade = fadePx;
|
|
47
|
+
#heading = 1;
|
|
48
|
+
#isClickSwallowed = false;
|
|
49
|
+
#isFocusInside = false;
|
|
50
|
+
#isHovered = false;
|
|
51
|
+
#isOffScreen = false;
|
|
52
|
+
#isPressed = false;
|
|
53
|
+
#mark;
|
|
54
|
+
#measured = "";
|
|
55
|
+
#position = 0;
|
|
56
|
+
#reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
|
57
|
+
#running;
|
|
58
|
+
#tape = requireChild(this.control, ".sonic-ticker-tape", HTMLSpanElement);
|
|
59
|
+
#travel = {
|
|
60
|
+
distance: 0,
|
|
61
|
+
hold: holdMs,
|
|
62
|
+
rate: ratePxPerSecond
|
|
63
|
+
};
|
|
64
|
+
attributeChangedCallback(name) {
|
|
65
|
+
if (name === "disabled" && this.isBound()) {
|
|
66
|
+
this.#drag?.end();
|
|
67
|
+
this.#run(this.#tape, {
|
|
68
|
+
from: 0,
|
|
69
|
+
heading: 1
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
this.render();
|
|
73
|
+
}
|
|
74
|
+
connect(signal) {
|
|
75
|
+
const control = this.control;
|
|
76
|
+
const tape = this.#tape;
|
|
77
|
+
const measure = () => {
|
|
78
|
+
this.#measure(tape);
|
|
79
|
+
};
|
|
80
|
+
const observer = new ResizeObserver(measure);
|
|
81
|
+
const drag = this.#bindDrag(tape, signal);
|
|
82
|
+
this.#drag = drag;
|
|
83
|
+
this.upgradeProperties("cancellable", "paused", "text");
|
|
84
|
+
mirrorChildren(this, {
|
|
85
|
+
canCopyLinks: true,
|
|
86
|
+
control,
|
|
87
|
+
copy: copyNode,
|
|
88
|
+
place: (copies) => {
|
|
89
|
+
placeChildren(tape, copies);
|
|
90
|
+
drag.end();
|
|
91
|
+
this.#measure(tape, {
|
|
92
|
+
from: 0,
|
|
93
|
+
heading: 1
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}, signal);
|
|
97
|
+
this.render();
|
|
98
|
+
observer.observe(control);
|
|
99
|
+
observer.observe(tape);
|
|
100
|
+
if ("fonts" in document) document.fonts.addEventListener("loadingdone", measure, { signal });
|
|
101
|
+
this.#bindFocus(tape, signal);
|
|
102
|
+
this.#bindPauses(signal);
|
|
103
|
+
this.#reducedMotion.addEventListener("change", () => {
|
|
104
|
+
this.#run(tape);
|
|
105
|
+
}, { signal });
|
|
106
|
+
signal.addEventListener("abort", () => {
|
|
107
|
+
observer.disconnect();
|
|
108
|
+
this.#stop();
|
|
109
|
+
}, { once: true });
|
|
110
|
+
}
|
|
111
|
+
render() {
|
|
112
|
+
if (!this.isBound()) return;
|
|
113
|
+
this.toggleState("paused", this.paused);
|
|
114
|
+
this.#syncPause();
|
|
115
|
+
}
|
|
116
|
+
#bindDrag(tape, signal) {
|
|
117
|
+
const control = this.control;
|
|
118
|
+
control.addEventListener("click", (event) => {
|
|
119
|
+
if (!this.#isClickSwallowed) return;
|
|
120
|
+
this.#isClickSwallowed = false;
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
event.stopPropagation();
|
|
123
|
+
}, {
|
|
124
|
+
capture: true,
|
|
125
|
+
signal
|
|
126
|
+
});
|
|
127
|
+
this.getRootNode().addEventListener("dragstart", (event) => {
|
|
128
|
+
if (this.#isPressed) event.preventDefault();
|
|
129
|
+
}, { signal });
|
|
130
|
+
return bindDrag(control, {
|
|
131
|
+
grab: (event) => {
|
|
132
|
+
if (this.#travel.distance <= 0 || this.isDisabled()) return;
|
|
133
|
+
return {
|
|
134
|
+
from: 0,
|
|
135
|
+
isDragging: false,
|
|
136
|
+
outside: void 0,
|
|
137
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
138
|
+
x: event.clientX
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
isCaptureLate: true,
|
|
142
|
+
move: (held, event) => {
|
|
143
|
+
if (!held.isDragging) {
|
|
144
|
+
if (Math.abs(event.clientX - held.x) < held.thresholdPx) return;
|
|
145
|
+
const { from, heading } = this.#where();
|
|
146
|
+
held.isDragging = true;
|
|
147
|
+
held.from = from;
|
|
148
|
+
held.outside = this.cancellable ? outsideCancelZone(control, "--_sonic-ticker-cancel-zone") : void 0;
|
|
149
|
+
held.x = event.clientX;
|
|
150
|
+
capturePointer(control, event.pointerId);
|
|
151
|
+
this.toggleState("dragging", true);
|
|
152
|
+
this.#stop();
|
|
153
|
+
this.#heading = heading;
|
|
154
|
+
}
|
|
155
|
+
const isOutside = held.outside?.(event) === true;
|
|
156
|
+
this.toggleState("cancelling", isOutside);
|
|
157
|
+
this.#place(tape, isOutside ? held.from : held.from - (event.clientX - held.x));
|
|
158
|
+
},
|
|
159
|
+
release: (held) => {
|
|
160
|
+
if (!held.isDragging) return;
|
|
161
|
+
this.#isClickSwallowed = true;
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
this.#isClickSwallowed = false;
|
|
164
|
+
}, 0);
|
|
165
|
+
this.toggleState("dragging", false);
|
|
166
|
+
this.toggleState("cancelling", false);
|
|
167
|
+
this.#run(tape);
|
|
168
|
+
},
|
|
169
|
+
toggle: (isPressed) => {
|
|
170
|
+
this.#isPressed = isPressed;
|
|
171
|
+
this.#syncPause();
|
|
172
|
+
}
|
|
173
|
+
}, signal);
|
|
174
|
+
}
|
|
175
|
+
#bindFocus(tape, signal) {
|
|
176
|
+
const control = this.control;
|
|
177
|
+
control.addEventListener("focusout", (event) => {
|
|
178
|
+
if (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;
|
|
179
|
+
control.scrollLeft = 0;
|
|
180
|
+
this.#isFocusInside = false;
|
|
181
|
+
this.#run(tape);
|
|
182
|
+
}, { signal });
|
|
183
|
+
control.addEventListener("focusin", (event) => {
|
|
184
|
+
this.#isFocusInside = true;
|
|
185
|
+
this.#run(tape);
|
|
186
|
+
if (event.target instanceof Element) event.target.scrollIntoView({ block: "nearest" });
|
|
187
|
+
}, { signal });
|
|
188
|
+
}
|
|
189
|
+
#bindPauses(signal) {
|
|
190
|
+
const control = this.control;
|
|
191
|
+
for (const type of ["pointerenter", "pointerleave"]) control.addEventListener(type, (event) => {
|
|
192
|
+
if (event.pointerType !== "mouse") return;
|
|
193
|
+
this.#isHovered = type === "pointerenter";
|
|
194
|
+
this.#syncPause();
|
|
195
|
+
}, { signal });
|
|
196
|
+
for (const type of ["focusin", "focusout"]) this.getRootNode().addEventListener(type, () => {
|
|
197
|
+
this.#syncPause();
|
|
198
|
+
}, { signal });
|
|
199
|
+
const view = new IntersectionObserver((entries) => {
|
|
200
|
+
this.#isOffScreen = entries.at(-1)?.isIntersecting === false;
|
|
201
|
+
this.#syncPause();
|
|
202
|
+
});
|
|
203
|
+
view.observe(control);
|
|
204
|
+
signal.addEventListener("abort", () => {
|
|
205
|
+
view.disconnect();
|
|
206
|
+
}, { once: true });
|
|
207
|
+
}
|
|
208
|
+
#holds(text) {
|
|
209
|
+
const [only, ...rest] = this.#originals();
|
|
210
|
+
if (only === void 0) return text === "";
|
|
211
|
+
if (rest.length > 0 || !(only instanceof Text)) return false;
|
|
212
|
+
return only.data === text;
|
|
213
|
+
}
|
|
214
|
+
#isPaused() {
|
|
215
|
+
return this.paused || this.#isHovered || this.#isPressed || this.#isOffScreen || this.control.closest(":focus-visible") !== null;
|
|
216
|
+
}
|
|
217
|
+
#measure(tape, departure) {
|
|
218
|
+
const control = this.control;
|
|
219
|
+
const styles = getComputedStyle(control);
|
|
220
|
+
const box = control.clientWidth - readPxProperty(styles, "padding-left", 0) - readPxProperty(styles, "padding-right", 0);
|
|
221
|
+
const travel = {
|
|
222
|
+
distance: Math.max(0, Math.round(tape.offsetWidth - box)),
|
|
223
|
+
hold: readMsProperty(styles, "--_sonic-ticker-hold", holdMs),
|
|
224
|
+
rate: readPxProperty(styles, "--_sonic-ticker-rate", ratePxPerSecond)
|
|
225
|
+
};
|
|
226
|
+
const fade = readPxProperty(styles, "--_sonic-ticker-fade", fadePx);
|
|
227
|
+
const measured = [...Object.values(travel), fade].join(" ");
|
|
228
|
+
if (!departure && measured === this.#measured) return;
|
|
229
|
+
const from = departure ?? this.#where();
|
|
230
|
+
this.#fade = fade;
|
|
231
|
+
this.#measured = measured;
|
|
232
|
+
this.#travel = travel;
|
|
233
|
+
this.toggleState("overflowing", travel.distance > 0);
|
|
234
|
+
this.#run(tape, from);
|
|
235
|
+
}
|
|
236
|
+
#originals() {
|
|
237
|
+
return [...this.childNodes].filter((child) => child !== this.control);
|
|
238
|
+
}
|
|
239
|
+
#place(tape, position) {
|
|
240
|
+
const { distance } = this.#travel;
|
|
241
|
+
this.#position = clamp(position, 0, distance);
|
|
242
|
+
tape.style.translate = this.#position > 0 ? `${String(-this.#position)}px` : "";
|
|
243
|
+
writeAttribute(this.control, "data-sonic-clipped", clippedEnds(this.#position, distance));
|
|
244
|
+
this.#writeFades(this.#position);
|
|
245
|
+
}
|
|
246
|
+
#run(tape, departure = this.#where()) {
|
|
247
|
+
const leg = !this.#isFocusInside && !this.#reducedMotion.matches && !this.hasState("dragging") && !this.isDisabled() ? tickerLeg(this.#travel, departure) : void 0;
|
|
248
|
+
this.#stop();
|
|
249
|
+
this.#heading = departure.heading;
|
|
250
|
+
if (leg) this.#start(tape, leg);
|
|
251
|
+
else this.#place(tape, this.#isFocusInside ? 0 : departure.from);
|
|
252
|
+
}
|
|
253
|
+
#start(tape, leg) {
|
|
254
|
+
const animation = tape.animate({ translate: [`${String(-leg.from)}px`, `${String(-leg.to)}px`] }, {
|
|
255
|
+
delay: leg.hold,
|
|
256
|
+
duration: leg.duration,
|
|
257
|
+
fill: "both"
|
|
258
|
+
});
|
|
259
|
+
animation.addEventListener("finish", () => {
|
|
260
|
+
this.#run(tape, {
|
|
261
|
+
from: leg.to,
|
|
262
|
+
heading: leg.heading
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
this.#running = {
|
|
266
|
+
animation,
|
|
267
|
+
fades: [],
|
|
268
|
+
leg
|
|
269
|
+
};
|
|
270
|
+
tape.style.translate = "";
|
|
271
|
+
writeAttribute(this.control, "data-sonic-clipped", clippedEnds(leg.from, this.#travel.distance));
|
|
272
|
+
this.#syncPause();
|
|
273
|
+
}
|
|
274
|
+
#startFades(leg, elapsed) {
|
|
275
|
+
const fades = legFades(leg, this.#travel.distance, this.#fade);
|
|
276
|
+
return ["start", "end"].flatMap((end) => {
|
|
277
|
+
const { delay, duration, widths } = fades[end];
|
|
278
|
+
if (duration <= 0 || delay + duration <= elapsed) return [];
|
|
279
|
+
return this.control.animate({ [`--_sonic-ticker-fade-${end}`]: widths.map((width) => `${String(width)}px`) }, {
|
|
280
|
+
delay: delay - elapsed,
|
|
281
|
+
duration,
|
|
282
|
+
fill: "both"
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
#stop() {
|
|
287
|
+
const running = this.#running;
|
|
288
|
+
clearTimeout(this.#mark);
|
|
289
|
+
if (!running) return;
|
|
290
|
+
for (const fade of running.fades) fade.cancel();
|
|
291
|
+
running.animation.cancel();
|
|
292
|
+
this.#running = void 0;
|
|
293
|
+
}
|
|
294
|
+
#syncPause() {
|
|
295
|
+
const running = this.#running;
|
|
296
|
+
if (!running) return;
|
|
297
|
+
const { animation, leg } = running;
|
|
298
|
+
const elapsed = Number(animation.currentTime);
|
|
299
|
+
clearTimeout(this.#mark);
|
|
300
|
+
for (const fade of running.fades) fade.cancel();
|
|
301
|
+
running.fades = [];
|
|
302
|
+
this.#writeFades(legPosition(leg, elapsed));
|
|
303
|
+
if (elapsed >= leg.hold) writeAttribute(this.control, "data-sonic-clipped", "start end");
|
|
304
|
+
if (this.#isPaused()) {
|
|
305
|
+
animation.pause();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (animation.playState === "paused") animation.play();
|
|
309
|
+
running.fades = this.#startFades(leg, elapsed);
|
|
310
|
+
if (elapsed >= leg.hold) return;
|
|
311
|
+
this.#mark = setTimeout(() => {
|
|
312
|
+
writeAttribute(this.control, "data-sonic-clipped", "start end");
|
|
313
|
+
}, leg.hold - elapsed);
|
|
314
|
+
}
|
|
315
|
+
#where() {
|
|
316
|
+
const running = this.#running;
|
|
317
|
+
if (!running) return {
|
|
318
|
+
from: this.#position,
|
|
319
|
+
heading: this.#heading
|
|
320
|
+
};
|
|
321
|
+
return legDeparture(running.leg, Number(running.animation.currentTime));
|
|
322
|
+
}
|
|
323
|
+
#writeFades(position) {
|
|
324
|
+
const { style } = this.control;
|
|
325
|
+
style.setProperty("--_sonic-ticker-fade-start", `${String(fadeWidth(position, this.#fade))}px`);
|
|
326
|
+
style.setProperty("--_sonic-ticker-fade-end", `${String(fadeWidth(this.#travel.distance - position, this.#fade))}px`);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
//#endregion
|
|
331
|
+
export { SonicTicker };
|
|
332
|
+
//# sourceMappingURL=ticker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticker.js","names":[],"sources":["../../../src/elements/ticker.ts"],"sourcesContent":["import type { TickerDeparture, TickerHeading, TickerLeg, TickerTravel } from '#lib/ticker-leg.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { outsideCancelZone } from '#lib/cancel-zone.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { clamp } from '#lib/math.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag, capturePointer } from '#lib/pointer-drag.ts';\nimport { readMsProperty } from '#lib/read-ms-property.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport {\n\tclippedEnds,\n\tfadeWidth,\n\tlegDeparture,\n\tlegFades,\n\tlegPosition,\n\ttickerLeg,\n} from '#lib/ticker-leg.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-ticker': SonicTicker;\n\t}\n}\n\nconst renderTicker = template(\n\t/* HTML */ `<span class=\"sonic-ticker\"><span class=\"sonic-ticker-tape\"></span></span>`,\n\tHTMLSpanElement,\n);\n\nconst fadePx = 12;\n\nconst holdMs = 2000;\n\nconst ratePxPerSecond = 40;\n\ninterface Running {\n\tanimation: Animation;\n\tfades: Array<Animation>;\n\tleg: TickerLeg;\n}\n\ninterface TapeDrag {\n\tfrom: number;\n\tisDragging: boolean;\n\toutside: ((event: PointerEvent) => boolean) | undefined;\n\tthresholdPx: number;\n\tx: number;\n}\n\nexport class SonicTicker extends SonicElement {\n\tstatic override readonly observedAttributes = ['disabled', 'paused'];\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.hasAttribute('paused');\n\t}\n\n\tset paused(isPaused: boolean) {\n\t\tthis.reflect('paused', isPaused);\n\t}\n\n\tget text(): string {\n\t\treturn this.#originals()\n\t\t\t.map((original) => original.textContent)\n\t\t\t.join('');\n\t}\n\n\tset text(text: string | undefined) {\n\t\tconst next = text ?? '';\n\n\t\t// A host that re-renders the same title would otherwise restart the tape\n\t\tif (this.#holds(next)) return;\n\n\t\tfor (const original of this.#originals()) original.remove();\n\t\tif (next !== '') this.prepend(next);\n\t}\n\n\tprotected override readonly control = renderTicker();\n\n\tprotected override readonly sheet = 'ticker.css';\n\n\tprotected override readonly sheetToken = '--_sonic-ticker-fade';\n\n\t#drag: undefined | { end: () => void };\n\n\t#fade = fadePx;\n\n\t#heading: TickerHeading = 1;\n\n\t#isClickSwallowed = false;\n\n\t#isFocusInside = false;\n\n\t#isHovered = false;\n\n\t#isOffScreen = false;\n\n\t#isPressed = false;\n\n\t#mark: ReturnType<typeof setTimeout> | undefined;\n\n\t#measured = '';\n\n\t#position = 0;\n\n\treadonly #reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\n\n\t#running: Running | undefined;\n\n\treadonly #tape = requireChild(this.control, '.sonic-ticker-tape', HTMLSpanElement);\n\n\t#travel: TickerTravel = { distance: 0, hold: holdMs, rate: ratePxPerSecond };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'disabled' && this.isBound()) {\n\t\t\tthis.#drag?.end();\n\t\t\tthis.#run(this.#tape, { from: 0, heading: 1 });\n\t\t}\n\n\t\tthis.render();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\t\tconst tape = this.#tape;\n\t\tconst measure = (): void => {\n\t\t\tthis.#measure(tape);\n\t\t};\n\t\tconst observer = new ResizeObserver(measure);\n\t\tconst drag = this.#bindDrag(tape, signal);\n\n\t\tthis.#drag = drag;\n\n\t\tthis.upgradeProperties('cancellable', 'paused', 'text');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcanCopyLinks: true,\n\t\t\t\tcontrol,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(tape, copies);\n\t\t\t\t\tdrag.end();\n\t\t\t\t\tthis.#measure(tape, { from: 0, heading: 1 });\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\n\t\tobserver.observe(control);\n\t\tobserver.observe(tape);\n\t\tif ('fonts' in document) document.fonts.addEventListener('loadingdone', measure, { signal });\n\t\tthis.#bindFocus(tape, signal);\n\t\tthis.#bindPauses(signal);\n\t\tthis.#reducedMotion.addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.#run(tape);\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\tobserver.disconnect();\n\t\t\t\tthis.#stop();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tthis.toggleState('paused', this.paused);\n\t\tthis.#syncPause();\n\t}\n\n\t#bindDrag(tape: HTMLElement, signal: AbortSignal): { end: () => void } {\n\t\tconst control = this.control;\n\n\t\tcontrol.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (!this.#isClickSwallowed) return;\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tevent.preventDefault();\n\t\t\t\tevent.stopPropagation();\n\t\t\t},\n\t\t\t{ capture: true, signal },\n\t\t);\n\t\t// In WebKit, dragging a link that wraps the ticker fires `dragstart` on the link, outside the control\n\t\tthis.getRootNode().addEventListener(\n\t\t\t'dragstart',\n\t\t\t(event) => {\n\t\t\t\tif (this.#isPressed) event.preventDefault();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\n\t\treturn bindDrag<TapeDrag>(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (this.#travel.distance <= 0 || this.isDisabled()) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfrom: 0,\n\t\t\t\t\t\tisDragging: false,\n\t\t\t\t\t\toutside: undefined,\n\t\t\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\t\t\tx: event.clientX,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tisCaptureLate: true,\n\t\t\t\tmove: (held, event) => {\n\t\t\t\t\tif (!held.isDragging) {\n\t\t\t\t\t\tif (Math.abs(event.clientX - held.x) < held.thresholdPx) return;\n\n\t\t\t\t\t\tconst { from, heading } = this.#where();\n\n\t\t\t\t\t\theld.isDragging = true;\n\t\t\t\t\t\theld.from = from;\n\t\t\t\t\t\theld.outside = this.cancellable\n\t\t\t\t\t\t\t? outsideCancelZone(control, '--_sonic-ticker-cancel-zone')\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\theld.x = event.clientX;\n\t\t\t\t\t\tcapturePointer(control, event.pointerId);\n\t\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\t\tthis.#stop();\n\t\t\t\t\t\tthis.#heading = heading;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isOutside = held.outside?.(event) === true;\n\n\t\t\t\t\tthis.toggleState('cancelling', isOutside);\n\t\t\t\t\tthis.#place(tape, isOutside ? held.from : held.from - (event.clientX - held.x));\n\t\t\t\t},\n\t\t\t\trelease: (held) => {\n\t\t\t\t\tif (!held.isDragging) return;\n\n\t\t\t\t\t// Safari sends no click after a drag, so the flag is cleared on a timer or it would cancel the next real click\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\t\t}, 0);\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.toggleState('cancelling', false);\n\t\t\t\t\tthis.#run(tape);\n\t\t\t\t},\n\t\t\t\ttoggle: (isPressed) => {\n\t\t\t\t\tthis.#isPressed = isPressed;\n\t\t\t\t\tthis.#syncPause();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#bindFocus(tape: HTMLElement, signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tcontrol.addEventListener(\n\t\t\t'focusout',\n\t\t\t(event) => {\n\t\t\t\tif (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;\n\n\t\t\t\tcontrol.scrollLeft = 0;\n\t\t\t\tthis.#isFocusInside = false;\n\t\t\t\tthis.#run(tape);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'focusin',\n\t\t\t(event) => {\n\t\t\t\tthis.#isFocusInside = true;\n\t\t\t\tthis.#run(tape);\n\t\t\t\tif (event.target instanceof Element) event.target.scrollIntoView({ block: 'nearest' });\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#bindPauses(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tfor (const type of ['pointerenter', 'pointerleave'] as const) {\n\t\t\tcontrol.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tif (event.pointerType !== 'mouse') return;\n\n\t\t\t\t\tthis.#isHovered = type === 'pointerenter';\n\t\t\t\t\tthis.#syncPause();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\t\tfor (const type of ['focusin', 'focusout'] as const) {\n\t\t\tthis.getRootNode().addEventListener(\n\t\t\t\ttype,\n\t\t\t\t() => {\n\t\t\t\t\tthis.#syncPause();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t}\n\n\t\tconst view = new IntersectionObserver((entries) => {\n\t\t\tthis.#isOffScreen = entries.at(-1)?.isIntersecting === false;\n\t\t\tthis.#syncPause();\n\t\t});\n\n\t\tview.observe(control);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tview.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#holds(text: string): boolean {\n\t\tconst [only, ...rest] = this.#originals();\n\t\tif (only === undefined) return text === '';\n\t\tif (rest.length > 0 || !(only instanceof Text)) return false;\n\n\t\treturn only.data === text;\n\t}\n\n\t#isPaused(): boolean {\n\t\treturn (\n\t\t\tthis.paused ||\n\t\t\tthis.#isHovered ||\n\t\t\tthis.#isPressed ||\n\t\t\tthis.#isOffScreen ||\n\t\t\tthis.control.closest(':focus-visible') !== null\n\t\t);\n\t}\n\n\t#measure(tape: HTMLElement, departure?: TickerDeparture): void {\n\t\tconst control = this.control;\n\t\tconst styles = getComputedStyle(control);\n\t\tconst box =\n\t\t\tcontrol.clientWidth -\n\t\t\treadPxProperty(styles, 'padding-left', 0) -\n\t\t\treadPxProperty(styles, 'padding-right', 0);\n\t\tconst travel = {\n\t\t\tdistance: Math.max(0, Math.round(tape.offsetWidth - box)),\n\t\t\thold: readMsProperty(styles, '--_sonic-ticker-hold', holdMs),\n\t\t\trate: readPxProperty(styles, '--_sonic-ticker-rate', ratePxPerSecond),\n\t\t};\n\t\tconst fade = readPxProperty(styles, '--_sonic-ticker-fade', fadePx);\n\t\tconst measured = [...Object.values(travel), fade].join(' ');\n\t\tif (!departure && measured === this.#measured) return;\n\n\t\tconst from = departure ?? this.#where();\n\n\t\tthis.#fade = fade;\n\t\tthis.#measured = measured;\n\t\tthis.#travel = travel;\n\t\tthis.toggleState('overflowing', travel.distance > 0);\n\t\tthis.#run(tape, from);\n\t}\n\n\t#originals(): Array<ChildNode> {\n\t\treturn [...this.childNodes].filter((child) => child !== this.control);\n\t}\n\n\t#place(tape: HTMLElement, position: number): void {\n\t\tconst { distance } = this.#travel;\n\n\t\tthis.#position = clamp(position, 0, distance);\n\t\ttape.style.translate = this.#position > 0 ? `${String(-this.#position)}px` : '';\n\t\twriteAttribute(this.control, 'data-sonic-clipped', clippedEnds(this.#position, distance));\n\t\tthis.#writeFades(this.#position);\n\t}\n\n\t#run(tape: HTMLElement, departure: TickerDeparture = this.#where()): void {\n\t\tconst canTravel =\n\t\t\t!this.#isFocusInside &&\n\t\t\t!this.#reducedMotion.matches &&\n\t\t\t!this.hasState('dragging') &&\n\t\t\t!this.isDisabled();\n\t\tconst leg = canTravel ? tickerLeg(this.#travel, departure) : undefined;\n\n\t\tthis.#stop();\n\t\tthis.#heading = departure.heading;\n\t\tif (leg) this.#start(tape, leg);\n\t\telse this.#place(tape, this.#isFocusInside ? 0 : departure.from);\n\t}\n\n\t#start(tape: HTMLElement, leg: TickerLeg): void {\n\t\tconst animation = tape.animate(\n\t\t\t{ translate: [`${String(-leg.from)}px`, `${String(-leg.to)}px`] },\n\t\t\t{ delay: leg.hold, duration: leg.duration, fill: 'both' },\n\t\t);\n\n\t\tanimation.addEventListener('finish', () => {\n\t\t\tthis.#run(tape, { from: leg.to, heading: leg.heading });\n\t\t});\n\t\tthis.#running = { animation, fades: [], leg };\n\t\ttape.style.translate = '';\n\t\twriteAttribute(\n\t\t\tthis.control,\n\t\t\t'data-sonic-clipped',\n\t\t\tclippedEnds(leg.from, this.#travel.distance),\n\t\t);\n\t\tthis.#syncPause();\n\t}\n\n\t#startFades(leg: TickerLeg, elapsed: number): Array<Animation> {\n\t\tconst fades = legFades(leg, this.#travel.distance, this.#fade);\n\n\t\treturn (['start', 'end'] as const).flatMap((end) => {\n\t\t\tconst { delay, duration, widths } = fades[end];\n\t\t\tif (duration <= 0 || delay + duration <= elapsed) return [];\n\n\t\t\treturn this.control.animate(\n\t\t\t\t{ [`--_sonic-ticker-fade-${end}`]: widths.map((width) => `${String(width)}px`) },\n\t\t\t\t{ delay: delay - elapsed, duration, fill: 'both' },\n\t\t\t);\n\t\t});\n\t}\n\n\t#stop(): void {\n\t\tconst running = this.#running;\n\n\t\tclearTimeout(this.#mark);\n\t\tif (!running) return;\n\n\t\tfor (const fade of running.fades) fade.cancel();\n\t\trunning.animation.cancel();\n\t\tthis.#running = undefined;\n\t}\n\n\t// Calling `play()` on a finished animation restarts it, so the fades are recreated on resume, not paused\n\t#syncPause(): void {\n\t\tconst running = this.#running;\n\t\tif (!running) return;\n\n\t\tconst { animation, leg } = running;\n\t\tconst elapsed = Number(animation.currentTime);\n\n\t\tclearTimeout(this.#mark);\n\t\tfor (const fade of running.fades) fade.cancel();\n\t\trunning.fades = [];\n\t\tthis.#writeFades(legPosition(leg, elapsed));\n\t\tif (elapsed >= leg.hold) writeAttribute(this.control, 'data-sonic-clipped', 'start end');\n\t\tif (this.#isPaused()) {\n\t\t\tanimation.pause();\n\t\t\treturn;\n\t\t}\n\t\tif (animation.playState === 'paused') animation.play();\n\t\trunning.fades = this.#startFades(leg, elapsed);\n\t\tif (elapsed >= leg.hold) return;\n\n\t\tthis.#mark = setTimeout(() => {\n\t\t\twriteAttribute(this.control, 'data-sonic-clipped', 'start end');\n\t\t}, leg.hold - elapsed);\n\t}\n\n\t#where(): TickerDeparture {\n\t\tconst running = this.#running;\n\t\tif (!running) return { from: this.#position, heading: this.#heading };\n\n\t\treturn legDeparture(running.leg, Number(running.animation.currentTime));\n\t}\n\n\t#writeFades(position: number): void {\n\t\tconst { style } = this.control;\n\n\t\tstyle.setProperty('--_sonic-ticker-fade-start', `${String(fadeWidth(position, this.#fade))}px`);\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-ticker-fade-end',\n\t\t\t`${String(fadeWidth(this.#travel.distance - position, this.#fade))}px`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { clamp, clampProportion } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
|
-
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
3
|
import { scrubRegions } from "../lib/scrub.js";
|
|
5
4
|
import { sortedRegions } from "../lib/time-regions.js";
|
|
5
|
+
import { outsideCancelZone } from "../lib/cancel-zone.js";
|
|
6
6
|
import { SonicWaveElement } from "./wave-element.js";
|
|
7
7
|
import { createMarkerBand } from "../lib/marker-band.js";
|
|
8
8
|
import { RegionLayer } from "./region-layer.js";
|
|
@@ -22,7 +22,6 @@ const lengths = {
|
|
|
22
22
|
pitch: "--_sonic-wavestrip-bar-pitch",
|
|
23
23
|
radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
|
|
24
24
|
};
|
|
25
|
-
const cancelZonePx = 48;
|
|
26
25
|
const renderWavestrip = template(`
|
|
27
26
|
<div class="sonic-wavestrip">
|
|
28
27
|
<canvas class="sonic-wavestrip-canvas" role="slider" tabindex="0"></canvas>
|
|
@@ -256,9 +255,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
256
255
|
}
|
|
257
256
|
#outside() {
|
|
258
257
|
if (!this.cancellable) return void 0;
|
|
259
|
-
|
|
260
|
-
const zone = readPxProperty(getComputedStyle(this.control), "--_sonic-cancel-zone", cancelZonePx);
|
|
261
|
-
return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
|
|
258
|
+
return outsideCancelZone(this.control, "--_sonic-cancel-zone");
|
|
262
259
|
}
|
|
263
260
|
#placeReadout() {
|
|
264
261
|
const at = this.mapping().proportionOf(this.readoutValue());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { RegionLayer } from '#elements/region-layer.ts';\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { createMarkerBand } from '#lib/marker-band.ts';\nimport { clamp, clampProportion } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { stripBars, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nexport type MarkerRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\tgrooveSize: '--_sonic-wavestrip-groove-size',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst cancelZonePx = 48;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" role=\"slider\" tabindex=\"0\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-regions\"></div>\n\t\t\t<div class=\"sonic-wavestrip-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-wavestrip-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\t/** Loaded spans, in seconds */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\t/** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */\n\tget formatSpokenRegion(): ((start: Seconds, end: Seconds) => string) | undefined {\n\t\treturn this.#formatSpokenRegion;\n\t}\n\n\tset formatSpokenRegion(format: ((start: Seconds, end: Seconds) => string) | undefined) {\n\t\tthis.#formatSpokenRegion = format;\n\t\tthis.#regions?.render();\n\t}\n\n\t/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\t/** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */\n\tget preview(): Seconds | undefined {\n\t\treturn this.#preview;\n\t}\n\n\tset preview(seconds: null | Seconds | undefined) {\n\t\tthis.#preview = Number.isFinite(seconds) ? (seconds ?? undefined) : undefined;\n\t\tthis.render();\n\t}\n\n\t/** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */\n\tget renderMarker(): MarkerRender | undefined {\n\t\treturn this.#renderMarker;\n\t}\n\n\tset renderMarker(render: MarkerRender | undefined) {\n\t\tthis.#renderMarker = render;\n\t\tthis.#drawMarker =\n\t\t\trender &&\n\t\t\t((marker, element) => {\n\t\t\t\trender({ ...marker }, element);\n\t\t\t});\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tprotected override readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly numbers = {};\n\n\tprotected override readonly sheet = 'wavestrip.css';\n\n\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\t#drawMarker: MarkerRender | undefined;\n\n\t#formatSpokenRegion: ((start: Seconds, end: Seconds) => string) | undefined;\n\n\treadonly #markerBand = createMarkerBand<WaveMarker>(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\t#preview: Seconds | undefined;\n\n\t#regions: RegionLayer | undefined;\n\n\t#renderMarker: MarkerRender | undefined;\n\n\t/** Viewport x of a time, clamped to the strip */\n\tclientXOf(value: Seconds): number {\n\t\tconst { startPx, travelPx } = this.#axis();\n\n\t\treturn startPx + this.mapping().proportionOf(value) * travelPx;\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'cancellable',\n\t\t\t'formatSpokenRegion',\n\t\t\t'peaks',\n\t\t\t'preview',\n\t\t\t'renderMarker',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { startPx, travelPx } = this.#axis();\n\t\tconst mapping = this.mapping();\n\n\t\treturn clamp(\n\t\t\tmapping.valueAt(clampProportion((clientX - startPx) / travelPx)),\n\t\t\t...mapping.bounds,\n\t\t);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst { control } = this;\n\n\t\tthis.#regions = new RegionLayer(\n\t\t\t{\n\t\t\t\taxis: () => this.#axis(),\n\t\t\t\tcontrol,\n\t\t\t\telement: this,\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tlayer: requireChild(control, '.sonic-wavestrip-regions', HTMLDivElement),\n\t\t\t\tmapping: () => this.mapping(),\n\t\t\t\toutside: () => this.#outside(),\n\t\t\t\tseek: (event) => {\n\t\t\t\t\tif (this.input(this.valueFromPoint(event.clientX, event.clientY))) {\n\t\t\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tspeak: ({ end, start }) =>\n\t\t\t\t\tthis.#formatSpokenRegion?.(start, end) ??\n\t\t\t\t\tthis.spokenText(start) ??\n\t\t\t\t\tthis.notation.format(start),\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsuper.connect(signal);\n\t\tthis.bindHover(control, signal, {\n\t\t\tchanged: () => {\n\t\t\t\tif (this.#preview !== undefined) this.render();\n\t\t\t},\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\tvalueAt: (event) =>\n\t\t\t\tthis.#markerStartAt(event, this.#axis()) ??\n\t\t\t\tthis.mapping().snap(this.valueFromPoint(event.clientX, event.clientY)),\n\t\t});\n\t\tthis.bindGestures(signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tthis.#regions?.follow();\n\t\tthis.showCurrentMarker(this.scrubState().played);\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.canvas;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\t/** The first point marker is the one a press there snaps to */\n\tprotected markersInReach(point: Point, widthPx: number): Array<number> {\n\t\tconst regions = this.markerProportions().flatMap(([from, to], index) => {\n\t\t\tconst isRegion = this.markerList()[index]?.end !== undefined;\n\n\t\t\treturn isRegion && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];\n\t\t});\n\n\t\treturn [...this.#markerBand.inReach(point, widthPx), ...regions];\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override usesChild(child: Node): boolean {\n\t\treturn child instanceof Element && child.localName === 'sonic-region';\n\t}\n\n\t#axis(): ValueAxis & { startPx: number; topPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttopPx: box.top,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst peaks = this.#peaks;\n\t\tconst groove = stripGroove(size, look.lengths);\n\t\tconst drawn = groove ? [groove] : [];\n\t\tconst bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;\n\n\t\tthis.#bars = undefined;\n\t\tif (bars.length === 0) return undefined;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of bars) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#grab(event: PointerEvent): undefined | ValueAxis {\n\t\tconst axis = this.#axis();\n\t\tconst marker = this.#markerStartAt(event, axis);\n\t\tconst regions = this.#regions;\n\n\t\tif (regions?.isHeld() === true) {\n\t\t\tregions.abandon();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (marker === undefined && regions?.take(event) === true) return undefined;\n\n\t\tconst at = (event.clientX - axis.startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = {\n\t\t\tfromProportion: clampProportion(at),\n\t\t\tposition: axis.position,\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\n\t\tthis.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#markerStartAt(\n\t\t{ clientX, clientY }: PointerEvent,\n\t\t{ startPx, topPx, travelPx }: { startPx: number; topPx: number; travelPx: number },\n\t): number | undefined {\n\t\tconst [index] = this.#markerBand.inReach(\n\t\t\t{ x: clientX - startPx, y: clientY - topPx },\n\t\t\ttravelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : this.markerList()[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\tconst box = this.control.getBoundingClientRect();\n\t\tconst zone = readPxProperty(\n\t\t\tgetComputedStyle(this.control),\n\t\t\t'--_sonic-cancel-zone',\n\t\t\tcancelZonePx,\n\t\t);\n\n\t\treturn (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst mapping = this.mapping();\n\t\tconst { proportionOf } = mapping;\n\t\tconst state = this.scrubState();\n\t\tconst isOwn = state.from !== undefined || this.hoverValue !== undefined;\n\t\tconst preview = isOwn || this.#preview === undefined ? undefined : proportionOf(this.#preview);\n\t\tconst { played, scrub = preview } = scrubRegions(state, this.value, mapping);\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed,\n\t\t\t\t...(scrub === undefined ? {} : { scrub }),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"wavestrip.js","names":[],"sources":["../../../src/elements/wavestrip.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { StripRegions } from '#lib/strip-scene.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { RegionLayer } from '#elements/region-layer.ts';\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { outsideCancelZone } from '#lib/cancel-zone.ts';\nimport { createMarkerBand } from '#lib/marker-band.ts';\nimport { clamp, clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { stripBars, stripGroove, stripRegions } from '#lib/strip-scene.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-wavestrip': SonicWavestrip;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Length = keyof typeof lengths;\n\nexport type MarkerRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst colours = {\n\tbuffered: '--_sonic-wavestrip-buffered',\n\tplayed: '--_sonic-wavestrip-played',\n\tscrub: '--_sonic-wavestrip-scrub',\n\twave: '--_sonic-wavestrip-wave',\n} as const;\n\nconst lengths = {\n\tgapRatio: '--_sonic-wavestrip-bar-gap-ratio',\n\tgrooveRatio: '--_sonic-wavestrip-groove-ratio',\n\tgrooveSize: '--_sonic-wavestrip-groove-size',\n\tpitch: '--_sonic-wavestrip-bar-pitch',\n\tradiusRatio: '--_sonic-wavestrip-bar-radius-ratio',\n} as const;\n\nconst renderWavestrip = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-wavestrip\">\n\t\t\t<canvas class=\"sonic-wavestrip-canvas\" role=\"slider\" tabindex=\"0\"></canvas>\n\t\t\t<div class=\"sonic-wavestrip-markers\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-wavestrip-regions\"></div>\n\t\t\t<div class=\"sonic-wavestrip-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-wavestrip-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction fillRegion(\n\tcontext: CanvasRenderingContext2D,\n\tpath: Path2D,\n\t[from, to, style]: [number, number, string],\n): void {\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, context.canvas.height);\n\tcontext.clip();\n\tcontext.fillStyle = style;\n\tcontext.fill(path);\n\tcontext.restore();\n}\n\nexport class SonicWavestrip extends SonicWaveElement<Colour, Length> {\n\t/** Loaded spans, in seconds */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget cancellable(): boolean {\n\t\treturn this.hasAttribute('cancellable');\n\t}\n\n\tset cancellable(isCancellable: boolean) {\n\t\tthis.reflect('cancellable', isCancellable);\n\t}\n\n\t/** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */\n\tget formatSpokenRegion(): ((start: Seconds, end: Seconds) => string) | undefined {\n\t\treturn this.#formatSpokenRegion;\n\t}\n\n\tset formatSpokenRegion(format: ((start: Seconds, end: Seconds) => string) | undefined) {\n\t\tthis.#formatSpokenRegion = format;\n\t\tthis.#regions?.render();\n\t}\n\n\t/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */\n\tget peaks(): ArrayLike<number> | undefined {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: ArrayLike<number> | undefined) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.surface()?.invalidate();\n\t}\n\n\t/** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */\n\tget preview(): Seconds | undefined {\n\t\treturn this.#preview;\n\t}\n\n\tset preview(seconds: null | Seconds | undefined) {\n\t\tthis.#preview = Number.isFinite(seconds) ? (seconds ?? undefined) : undefined;\n\t\tthis.render();\n\t}\n\n\t/** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */\n\tget renderMarker(): MarkerRender | undefined {\n\t\treturn this.#renderMarker;\n\t}\n\n\tset renderMarker(render: MarkerRender | undefined) {\n\t\tthis.#renderMarker = render;\n\t\tthis.#drawMarker =\n\t\t\trender &&\n\t\t\t((marker, element) => {\n\t\t\t\trender({ ...marker }, element);\n\t\t\t});\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tprotected override readonly control = renderWavestrip();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-wavestrip-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly lengths = lengths;\n\n\tprotected readonly numbers = {};\n\n\tprotected override readonly sheet = 'wavestrip.css';\n\n\tprotected readonly sizeProperty = '--_sonic-wavestrip-size';\n\n\t#bars: Path2D | undefined;\n\n\t#buffered: Array<[number, number]> = [];\n\n\t#drawMarker: MarkerRender | undefined;\n\n\t#formatSpokenRegion: ((start: Seconds, end: Seconds) => string) | undefined;\n\n\treadonly #markerBand = createMarkerBand<WaveMarker>(\n\t\trequireChild(this.control, '.sonic-wavestrip-markers', HTMLDivElement),\n\t);\n\n\t#paintedKey = '';\n\n\t#peaks: ArrayLike<number> | undefined;\n\n\t#preview: Seconds | undefined;\n\n\t#regions: RegionLayer | undefined;\n\n\t#renderMarker: MarkerRender | undefined;\n\n\t/** Viewport x of a time, clamped to the strip */\n\tclientXOf(value: Seconds): number {\n\t\tconst { startPx, travelPx } = this.#axis();\n\n\t\treturn startPx + this.mapping().proportionOf(value) * travelPx;\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'cancellable',\n\t\t\t'formatSpokenRegion',\n\t\t\t'peaks',\n\t\t\t'preview',\n\t\t\t'renderMarker',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { startPx, travelPx } = this.#axis();\n\t\tconst mapping = this.mapping();\n\n\t\treturn clamp(\n\t\t\tmapping.valueAt(clampProportion((clientX - startPx) / travelPx)),\n\t\t\t...mapping.bounds,\n\t\t);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst { control } = this;\n\n\t\tthis.#regions = new RegionLayer(\n\t\t\t{\n\t\t\t\taxis: () => this.#axis(),\n\t\t\t\tcontrol,\n\t\t\t\telement: this,\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tlayer: requireChild(control, '.sonic-wavestrip-regions', HTMLDivElement),\n\t\t\t\tmapping: () => this.mapping(),\n\t\t\t\toutside: () => this.#outside(),\n\t\t\t\tseek: (event) => {\n\t\t\t\t\tif (this.input(this.valueFromPoint(event.clientX, event.clientY))) {\n\t\t\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tspeak: ({ end, start }) =>\n\t\t\t\t\tthis.#formatSpokenRegion?.(start, end) ??\n\t\t\t\t\tthis.spokenText(start) ??\n\t\t\t\t\tthis.notation.format(start),\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsuper.connect(signal);\n\t\tthis.bindHover(control, signal, {\n\t\t\tchanged: () => {\n\t\t\t\tif (this.#preview !== undefined) this.render();\n\t\t\t},\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout();\n\t\t\t},\n\t\t\tvalueAt: (event) =>\n\t\t\t\tthis.#markerStartAt(event, this.#axis()) ??\n\t\t\t\tthis.mapping().snap(this.valueFromPoint(event.clientX, event.clientY)),\n\t\t});\n\t\tthis.bindGestures(signal, (event) => this.#grab(event));\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tthis.#placeReadout();\n\t\tthis.#regions?.follow();\n\t\tthis.showCurrentMarker(this.scrubState().played);\n\t\tif (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) {\n\t\t\tsurface.requestFrame();\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.canvas;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.length === 0;\n\t}\n\n\t/** The first point marker is the one a press there snaps to */\n\tprotected markersInReach(point: Point, widthPx: number): Array<number> {\n\t\tconst regions = this.markerProportions().flatMap(([from, to], index) => {\n\t\t\tconst isRegion = this.markerList()[index]?.end !== undefined;\n\n\t\t\treturn isRegion && point.x >= from * widthPx && point.x <= to * widthPx ? [index] : [];\n\t\t});\n\n\t\treturn [...this.#markerBand.inReach(point, widthPx), ...regions];\n\t}\n\n\tprotected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void {\n\t\tconst { look, size } = frame;\n\t\tconst strip = this.#stripRegions(size.width);\n\t\tconst bars = this.#barsFor(frame);\n\n\t\tthis.#paintedKey = strip.key;\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (!bars) return;\n\n\t\tcontext.fillStyle = look.colours.wave;\n\t\tcontext.fill(bars);\n\t\tfor (const { from, kind, to } of strip.regions) {\n\t\t\tfillRegion(context, bars, [from, to, look.colours[kind]]);\n\t\t}\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tthis.#markerBand.render(this.markerList(), this.markerProportions(), this.#drawMarker);\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override usesChild(child: Node): boolean {\n\t\treturn child instanceof Element && child.localName === 'sonic-region';\n\t}\n\n\t#axis(): ValueAxis & { startPx: number; topPx: number } {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\n\t\treturn {\n\t\t\tposition: (event: PointerEvent) => event.clientX,\n\t\t\tstartPx: box.left,\n\t\t\ttopPx: box.top,\n\t\t\ttravelPx: Math.max(1, box.width),\n\t\t};\n\t}\n\n\t#barsFor({ isDirty, look, size }: SurfaceFrame<Colour, Length>): Path2D | undefined {\n\t\tif (!isDirty && this.#bars) return this.#bars;\n\n\t\tconst peaks = this.#peaks;\n\t\tconst groove = stripGroove(size, look.lengths);\n\t\tconst drawn = groove ? [groove] : [];\n\t\tconst bars = peaks && peaks.length > 0 ? stripBars(peaks, size, look.lengths) : drawn;\n\n\t\tthis.#bars = undefined;\n\t\tif (bars.length === 0) return undefined;\n\n\t\tconst path = new Path2D();\n\n\t\tfor (const { height, radius, width, x, y } of bars) {\n\t\t\tpath.roundRect(x, y, width, height, radius);\n\t\t}\n\t\tthis.#bars = path;\n\n\t\treturn path;\n\t}\n\n\t#grab(event: PointerEvent): undefined | ValueAxis {\n\t\tconst axis = this.#axis();\n\t\tconst marker = this.#markerStartAt(event, axis);\n\t\tconst regions = this.#regions;\n\n\t\tif (regions?.isHeld() === true) {\n\t\t\tregions.abandon();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (marker === undefined && regions?.take(event) === true) return undefined;\n\n\t\tconst at = (event.clientX - axis.startPx) / axis.travelPx;\n\t\tconst outside = this.#outside();\n\t\tconst grabbed = {\n\t\t\tfromProportion: clampProportion(at),\n\t\t\tposition: axis.position,\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\n\t\tthis.input(marker ?? this.valueFromPoint(event.clientX, event.clientY));\n\n\t\treturn outside ? { ...grabbed, outside } : grabbed;\n\t}\n\n\t#markerStartAt(\n\t\t{ clientX, clientY }: PointerEvent,\n\t\t{ startPx, topPx, travelPx }: { startPx: number; topPx: number; travelPx: number },\n\t): number | undefined {\n\t\tconst [index] = this.#markerBand.inReach(\n\t\t\t{ x: clientX - startPx, y: clientY - topPx },\n\t\t\ttravelPx,\n\t\t);\n\n\t\treturn index === undefined ? undefined : this.markerList()[index]?.start;\n\t}\n\n\t#outside(): ((event: PointerEvent) => boolean) | undefined {\n\t\tif (!this.cancellable) return undefined;\n\n\t\treturn outsideCancelZone(this.control, '--_sonic-cancel-zone');\n\t}\n\n\t#placeReadout(): void {\n\t\tconst at = this.mapping().proportionOf(this.readoutValue());\n\n\t\tthis.control.style.setProperty('--_sonic-wavestrip-readout-at', String(at));\n\t}\n\n\t#stripRegions(width: number): StripRegions {\n\t\tconst mapping = this.mapping();\n\t\tconst { proportionOf } = mapping;\n\t\tconst state = this.scrubState();\n\t\tconst isOwn = state.from !== undefined || this.hoverValue !== undefined;\n\t\tconst preview = isOwn || this.#preview === undefined ? undefined : proportionOf(this.#preview);\n\t\tconst { played, scrub = preview } = scrubRegions(state, this.value, mapping);\n\n\t\treturn stripRegions(\n\t\t\t{\n\t\t\t\tbuffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),\n\t\t\t\tplayed,\n\t\t\t\t...(scrub === undefined ? {} : { scrub }),\n\t\t\t},\n\t\t\twidth,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
package/dist/dev/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { SonicSpectrum } from "./elements/spectrum.js";
|
|
|
11
11
|
import { SplitMode } from "./lib/distribute.js";
|
|
12
12
|
import { SonicSplit } from "./elements/split.js";
|
|
13
13
|
import { SonicSwitch } from "./elements/switch.js";
|
|
14
|
+
import { SonicTicker } from "./elements/ticker.js";
|
|
14
15
|
import { SonicToggle } from "./elements/toggle.js";
|
|
15
16
|
import { WaveMarker } from "./elements/wave-element.js";
|
|
16
17
|
import { PeaksRequest } from "./lib/peaks-request.js";
|
|
@@ -24,4 +25,4 @@ import { formatClock, parseClock } from "./lib/clock.js";
|
|
|
24
25
|
import { crossfadeGains } from "./lib/crossfade.js";
|
|
25
26
|
import { envelopeCurve } from "./lib/envelope-curve.js";
|
|
26
27
|
import { formatPercent, parsePercent } from "./lib/percent.js";
|
|
27
|
-
export { type FieldAxis, type LabelRender, type MarkerRender, type PeaksRequest, SonicButton, SonicDial, SonicEnvelope, SonicMeter, SonicNumber, SonicRegion, SonicSegmented, SonicSlider, SonicSpectrum, SonicSplit, SonicSwitch, SonicToggle, SonicWaveform, SonicWavestrip, SonicXy, type SplitMode, 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 };
|