@xsynaptic/sonic-ui 0.21.0 → 0.23.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -0
  3. package/dist/default/elements/envelope.js +3 -6
  4. package/dist/default/elements/split.js +4 -7
  5. package/dist/default/elements/value-element.js +3 -0
  6. package/dist/default/elements/wave-element.js +1 -0
  7. package/dist/default/elements/waveform.js +48 -4
  8. package/dist/default/elements/wavestrip.js +1 -0
  9. package/dist/default/lib/waveform-bands.js +85 -0
  10. package/dist/default/lib/waveform-scene.js +52 -16
  11. package/dist/dev/elements/envelope.js +3 -6
  12. package/dist/dev/elements/envelope.js.map +1 -1
  13. package/dist/dev/elements/split.js +4 -7
  14. package/dist/dev/elements/split.js.map +1 -1
  15. package/dist/dev/elements/value-element.js +3 -0
  16. package/dist/dev/elements/value-element.js.map +1 -1
  17. package/dist/dev/elements/wave-element.d.ts +3 -2
  18. package/dist/dev/elements/wave-element.js +1 -0
  19. package/dist/dev/elements/wave-element.js.map +1 -1
  20. package/dist/dev/elements/waveform.d.ts +13 -4
  21. package/dist/dev/elements/waveform.js +48 -4
  22. package/dist/dev/elements/waveform.js.map +1 -1
  23. package/dist/dev/elements/wavestrip.d.ts +1 -0
  24. package/dist/dev/elements/wavestrip.js +1 -0
  25. package/dist/dev/elements/wavestrip.js.map +1 -1
  26. package/dist/dev/index.d.ts +2 -1
  27. package/dist/dev/lib/waveform-bands.d.ts +13 -0
  28. package/dist/dev/lib/waveform-bands.js +86 -0
  29. package/dist/dev/lib/waveform-bands.js.map +1 -0
  30. package/dist/dev/lib/waveform-scene.d.ts +6 -1
  31. package/dist/dev/lib/waveform-scene.js +52 -16
  32. package/dist/dev/lib/waveform-scene.js.map +1 -1
  33. package/dist/skins/amber.css +1 -0
  34. package/dist/skins/flat.css +0 -1
  35. package/dist/skins/ivory.css +1 -0
  36. package/dist/skins/lime.css +1 -0
  37. package/dist/skins/slate.css +1 -0
  38. package/dist/styles/material/readout.css +4 -2
  39. package/dist/styles/material/value.css +1 -1
  40. package/dist/styles/slider.css +29 -11
  41. package/dist/styles/waveform.css +29 -11
  42. package/package.json +1 -1
@@ -58,13 +58,12 @@ var SonicSplit = class extends SonicElement {
58
58
  #isWarned = false;
59
59
  #isWriting = false;
60
60
  #members = [];
61
- #signal;
62
61
  #snapshot;
63
62
  #total = 0;
64
63
  #waiting = /* @__PURE__ */ new Set();
65
64
  attributeChangedCallback(name) {
66
- if ((name === "mode" || name === "total") && this.#signal) this.#bind();
67
- if (name === "disabled" && this.#signal) this.#limit();
65
+ if ((name === "mode" || name === "total") && this.isBound()) this.#bind();
66
+ if (name === "disabled" && this.isBound()) this.#limit();
68
67
  }
69
68
  connectedCallback() {
70
69
  this.upgradeProperties("mode", "total");
@@ -74,7 +73,6 @@ var SonicSplit = class extends SonicElement {
74
73
  const observer = new MutationObserver((records) => {
75
74
  if (records.some((record) => record.attributeName !== null && lockAttributes.includes(record.attributeName)) || !this.#isSameMembers(this.#find())) this.#bind();
76
75
  });
77
- this.#signal = signal;
78
76
  if (this.internals) this.internals.role = "group";
79
77
  observer.observe(this, {
80
78
  attributeFilter: lockAttributes,
@@ -87,7 +85,6 @@ var SonicSplit = class extends SonicElement {
87
85
  }, { signal });
88
86
  signal.addEventListener("abort", () => {
89
87
  observer.disconnect();
90
- this.#signal = void 0;
91
88
  this.#isTotalKept = false;
92
89
  this.#unbind();
93
90
  }, { once: true });
@@ -194,7 +191,7 @@ var SonicSplit = class extends SonicElement {
194
191
  queueMicrotask(() => {
195
192
  this.#isWriting = true;
196
193
  try {
197
- for (const { element } of changed) element.dispatchEvent(new Event("change", { bubbles: true }));
194
+ for (const { link } of changed) link.change();
198
195
  } finally {
199
196
  this.#isWriting = false;
200
197
  }
@@ -228,7 +225,7 @@ var SonicSplit = class extends SonicElement {
228
225
  this.#waiting.add(tag);
229
226
  customElements.whenDefined(tag).then(() => {
230
227
  this.#waiting.delete(tag);
231
- if (this.#signal) this.#bind();
228
+ if (this.isBound()) this.#bind();
232
229
  });
233
230
  }
234
231
  #warnReach() {
@@ -1 +1 @@
1
- {"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridSlop = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridSlop ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridSlop ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.model.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\t/** What the members add up to; unset, it is their sum when they connect */\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#signal: AbortSignal | undefined;\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.#signal) this.#bind();\n\t\tif (name === 'disabled' && this.#signal) this.#limit();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tthis.#signal = signal;\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\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.#signal = undefined;\n\t\t\t\tthis.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.model.value !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.model.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.model.setLanding((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.model.value, unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst isFrozen = this.isDisabled();\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.model.setLimit(\n\t\t\t\tisFrozen || isLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.model.value },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.model.value !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { element } of changed) {\n\t\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.model.setLanding(undefined);\n\t\t\tlink.model.setLimit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.#signal) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.model.value) continue;\n\n\t\t\t\tmember.link.model.setLimit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.model.value;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridSlop = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridSlop ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridSlop ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.model.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\t/** What the members add up to; unset, it is their sum when they connect */\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.isBound()) this.#bind();\n\t\tif (name === 'disabled' && this.isBound()) this.#limit();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\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.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.model.value !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.model.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.model.setLanding((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.model.value, unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst isFrozen = this.isDisabled();\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.model.setLimit(\n\t\t\t\tisFrozen || isLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.model.value },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.model.value !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { link } of changed) link.change();\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.model.setLanding(undefined);\n\t\t\tlink.model.setLimit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.isBound()) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.model.value) continue;\n\n\t\t\t\tmember.link.model.setLimit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.model.value;\n\t}\n}\n"],"mappings":"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"}
@@ -52,6 +52,9 @@ var SonicValueElement = class extends SonicFormElement {
52
52
  ];
53
53
  static {
54
54
  link = (element) => ({
55
+ change: () => {
56
+ element.dispatchEvent(new Event("change", { bubbles: true }));
57
+ },
55
58
  input: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),
56
59
  isDisabled: () => element.isDisabled(),
57
60
  isHeld: () => element.#isHeld(),
@@ -1 +1 @@
1
- {"version":3,"file":"value-element.js","names":[],"sources":["../../../src/elements/value-element.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { HoverPreview } from '#lib/hover-preview.ts';\nimport type { ScrubState } from '#lib/scrub.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { ValueGestures } from '#elements/value-gestures.ts';\nimport { bindHoverPreview } from '#lib/hover-preview.ts';\nimport { toNumber, trimFloat } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-hover': Event;\n\t\t'sonic-reveal': Event;\n\t}\n}\n\ninterface HoverBinding {\n\tcanShow?: () => boolean;\n\tplace: () => void;\n\tvalueAt: (event: PointerEvent) => number | undefined;\n}\n\nexport interface ValueNotation {\n\tformat: (value: number) => string;\n\tparse: (text: string) => number;\n\tspeak?: (value: number) => string;\n}\n\nconst fallbackTravelPx = 160;\n\nlet instanceCount = 0;\n\nconst proportionAttributes = new Set(['max', 'midpoint', 'min', 'notched', 'positions', 'taper']);\n\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\nexport interface ValueLink {\n\tinput: (next: number, isMover?: boolean) => boolean;\n\tisDisabled: () => boolean;\n\tisHeld: () => boolean;\n\tmodel: ValueModel;\n}\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicValueElement) => ValueLink;\n\nexport function linkValue(element: SonicValueElement): ValueLink {\n\treturn link(element);\n}\n\nexport abstract class SonicValueElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'detent',\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'spoken-step',\n\t\t'step',\n\t\t'tabindex',\n\t\t'taper',\n\t\t'value',\n\t\t'positions',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tinput: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tisHeld: () => element.#isHeld(),\n\t\t\tmodel: element.#model,\n\t\t});\n\t}\n\n\tget cancelling(): boolean {\n\t\treturn this.hasState('cancelling');\n\t}\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget detent(): number | undefined {\n\t\treturn this.optionalNumberAttribute('detent');\n\t}\n\n\tset detent(value: number | undefined) {\n\t\tthis.reflect('detent', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\tget doublePress(): 'entry' | 'none' | 'reset' {\n\t\tconst gesture = this.getAttribute('double-press');\n\n\t\treturn gesture === 'none' || gesture === 'reset' ? gesture : 'entry';\n\t}\n\n\tset doublePress(gesture: 'entry' | 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget entry(): 'none' | undefined {\n\t\treturn this.getAttribute('entry') === 'none' ? 'none' : undefined;\n\t}\n\n\tset entry(gesture: 'none' | undefined) {\n\t\tthis.reflect('entry', gesture);\n\t}\n\n\tget formatEntry(): ((value: number) => string) | undefined {\n\t\treturn this.#formatEntry;\n\t}\n\n\tset formatEntry(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatEntry = format;\n\t}\n\n\tget formatSpokenValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatSpokenValue;\n\t}\n\n\tset formatSpokenValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatSpokenValue = format;\n\t\tthis.render();\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\tget hoverValue(): number | undefined {\n\t\treturn this.#hover?.value();\n\t}\n\n\tget keyStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('key-step');\n\t}\n\n\tset keyStep(value: number | undefined) {\n\t\tthis.reflect('key-step', value);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget pointerType(): string | undefined {\n\t\treturn this.#gestures?.pointerType();\n\t}\n\n\t/** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */\n\tget positions(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('positions'));\n\t}\n\n\tset positions(positions: Array<number> | undefined) {\n\t\tthis.reflect('positions', positions?.join(' '));\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\t/** Rounds the value assistive technology hears, in value units; the value itself is untouched */\n\tget spokenStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('spoken-step');\n\t}\n\n\tset spokenStep(value: number | undefined) {\n\t\tthis.reflect('spoken-step', value);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#model.value;\n\t}\n\n\tset value(next: null | number | undefined) {\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.formResetCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = toNumber(next);\n\n\t\tif (this.#isHeld()) {\n\t\t\tthis.#writeHeld(value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#model.write(value)) this.render();\n\t}\n\n\tget valueText(): string {\n\t\treturn this.#textFor(this.#model.value);\n\t}\n\n\t// `Number.parseFloat`, as `Number('')` is 0; an emptied field should leave the value alone\n\tprotected readonly notation: ValueNotation = { format: String, parse: Number.parseFloat };\n\n\t#formatEntry: ((value: number) => string) | undefined;\n\n\t#formatSpokenValue: ((value: number) => string) | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\t#gestures: undefined | ValueGestures;\n\n\t#heldAt: number | undefined;\n\n\t#hover: HoverPreview | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// A subclass adding an input to `isWrapping()` has to observe it\n\treadonly #model = createValueModel(() => this.#spec());\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { anchor: HTMLElement; bubble: Readout };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value' && this.#isHeld()) {\n\t\t\tthis.#writeHeld(this.numberAttribute('value', this.min));\n\t\t\treturn;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gestures?.end();\n\n\t\tthis.#model.respec(this.numberAttribute('value', this.min), { forgetAsk: name === 'value' });\n\t\tif (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'entry',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'spokenStep',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'positions',\n\t\t\t'detent',\n\t\t\t'formatEntry',\n\t\t\t'formatValue',\n\t\t\t'formatSpokenValue',\n\t\t\t'keyStep',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected abandonHold(): void {\n\t\tthis.#gestures?.abandon();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => undefined | ValueAxis,\n\t): void {\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\tif (bubble) this.#readout = { anchor: control, bubble: new Readout(bubble) };\n\t\tthis.#gestures = new ValueGestures(\n\t\t\t{\n\t\t\t\tdefault: () => this.default,\n\t\t\t\tdetent: () => this.detent,\n\t\t\t\tdispatch: (type) => {\n\t\t\t\t\tthis.dispatchEvent(new Event(type, { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdoublePress: () => this.doublePress,\n\t\t\t\tentryId: `sonic-entry-${this.#instance}`,\n\t\t\t\tentryText: () => this.#entryText(),\n\t\t\t\tforwardNaming: (target, isNamed) => {\n\t\t\t\t\tthis.forwardNaming(target, isNamed);\n\t\t\t\t},\n\t\t\t\thasEntry: () => this.entry !== 'none',\n\t\t\t\tinput: (next) => this.input(next),\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tkeyStep: () => this.keyStep,\n\t\t\t\tmodel: this.#model,\n\t\t\t\tparse: (text) => (this.#parseValue ?? this.notation.parse)(text),\n\t\t\t\trender: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tscrubChanged: () => {\n\t\t\t\t\tthis.#renderHold();\n\t\t\t\t},\n\t\t\t\tscrubsKeyRepeat: () => this.scrubsKeyRepeat(),\n\t\t\t\tspringTarget: () => this.springTarget(),\n\t\t\t\ttapTarget: () => this.tapTarget(),\n\t\t\t\ttoggleState: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontrol,\n\t\t);\n\t\tthis.#gestures.bind(signal, grab);\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tthis.#hover?.lift(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected bindHover(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\t{ canShow = () => true, place, valueAt }: HoverBinding,\n\t): void {\n\t\tthis.#hover = bindHoverPreview(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcanShow,\n\t\t\t\tchanged: () => {\n\t\t\t\t\tthis.dispatchEvent(new Event('sonic-hover', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdismiss: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tisTaken: () => this.#isEditing() || this.#isRevealed(),\n\t\t\t\tshow: () => {\n\t\t\t\t\tif (!this.readout) return;\n\n\t\t\t\t\tthis.renderReadout();\n\t\t\t\t\tplace();\n\t\t\t\t},\n\t\t\t\tvalueAt,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected controlOrientation(): 'horizontal' | 'vertical' | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected abstract draw(): void;\n\n\tprotected abstract override focusTarget(): HTMLElement;\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#model.value;\n\n\t\tif (this.#model.input(next)) this.render();\n\t\tif (this.#model.value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected mapping(): ValueMapping {\n\t\treturn this.#model.mapping();\n\t}\n\n\tprotected originValue(): number {\n\t\treturn this.origin ?? this.mapping().bounds[0];\n\t}\n\n\tprotected proportionsChanged(): void {\n\t\t// Runs when every proportion moves, never per input\n\t}\n\n\tprotected readoutValue(): number {\n\t\treturn this.#shown().value ?? this.#model.value;\n\t}\n\n\tprotected render(): void {\n\t\tif (this.isBound()) this.#renderControl();\n\t\tthis.#model.notify();\n\t}\n\n\tprotected renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout) return;\n\n\t\tconst { isOpen, value } = this.#shown();\n\t\tconst text = value === undefined ? undefined : this.#textFor(value);\n\n\t\treadout.bubble.show({ anchor: readout.anchor, isOpen: isOpen && text !== '', text });\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected scrubChanged(): void {\n\t\t// Only a subclass draws a scrub\n\t}\n\n\tprotected scrubsKeyRepeat(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected scrubState(): ScrubState {\n\t\tconst from = this.#gestures?.scrubFrom();\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tisRevealed: this.#isRevealed(),\n\t\t\tplayed: from === undefined ? this.#model.value : (this.#heldAt ?? from),\n\t\t};\n\t}\n\n\tprotected springTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected tapTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number {\n\t\treturn Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));\n\t}\n\n\t#entryText(): string {\n\t\tconst value = this.#model.value;\n\n\t\tif (this.#formatEntry) return this.#formatEntry(value);\n\n\t\t// The notation's own parser may not read a consumer's `formatValue`\n\t\tif (!this.#parseValue) return this.notation.format(value);\n\n\t\tconst text = this.valueText;\n\n\t\tif (__DEV__ && !Number.isFinite(this.#parseValue(text))) {\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> opens its entry with \"${text}\", which parseValue cannot read; set formatEntry`,\n\t\t\t);\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t#isEditing(): boolean {\n\t\treturn this.#gestures?.isEditing() === true;\n\t}\n\n\t#isHeld(): boolean {\n\t\treturn this.#gestures?.isHeld() === true;\n\t}\n\n\t#isRevealed(): boolean {\n\t\treturn this.#gestures?.isRevealed() === true;\n\t}\n\n\t#renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#model.value), String(this.#model.value));\n\t\tif (this.#isEditing()) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tconst [low, high] = this.mapping().bounds;\n\n\t\twriteAttribute(control, 'role', this.controlRole());\n\t\twriteAttribute(control, 'aria-valuemin', String(low));\n\t\twriteAttribute(control, 'aria-valuemax', String(high));\n\t\twriteAttribute(control, 'aria-valuenow', String(this.#spokenNow()));\n\t\tif (orientation) writeAttribute(control, 'aria-orientation', orientation);\n\t\twriteAttribute(\n\t\t\tcontrol,\n\t\t\t'aria-valuetext',\n\t\t\t(this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(this.#spokenNow()),\n\t\t);\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.renderReadout();\n\t}\n\n\t#renderControl(): void {\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.#isEditing() || this.#isRevealed()) this.#hover?.clear();\n\t\tthis.draw();\n\t\tthis.toggleState(\n\t\t\t'at-origin',\n\t\t\t!this.isWrapping() && this.#model.value === this.mapping().snap(this.originValue()),\n\t\t);\n\t\tthis.#renderAria(this.focusTarget(), this.controlOrientation());\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\twriteAttribute(control, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t// The host is `display: contents`, so its tab stop is the control's\n\t\tconst stop = this.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteAttribute(control, 'tabindex', isDisabled ? undefined : stop);\n\t}\n\n\t#renderHold(): void {\n\t\tif (this.#gestures?.scrubFrom() === undefined) this.#heldAt = undefined;\n\t\tthis.#hover?.restore();\n\t\tthis.renderReadout();\n\t\tthis.scrubChanged();\n\t}\n\n\t#shown(): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing()) return { isOpen: true, value: undefined };\n\n\t\tconst isRevealed = this.#isRevealed();\n\t\tconst preview = isRevealed ? undefined : this.#hover?.value();\n\n\t\treturn {\n\t\t\tisOpen: (isRevealed || preview !== undefined) && this.readout,\n\t\t\tvalue: preview ?? this.#model.value,\n\t\t};\n\t}\n\n\t#spec(): ValueSpec {\n\t\tconst { detent, midpoint, positions } = this;\n\n\t\treturn {\n\t\t\t...(detent === undefined ? {} : { detent }),\n\t\t\t...(positions ? { positions } : {}),\n\t\t\tmax: this.max,\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: this.notched,\n\t\t\tisWrapping: this.isWrapping(),\n\t\t\tmin: this.min,\n\t\t\tstep: this.step,\n\t\t\ttaper: this.taper,\n\t\t};\n\t}\n\n\t#spokenNow(): number {\n\t\tconst step = this.spokenStep;\n\n\t\treturn step !== undefined && step > 0\n\t\t\t? trimFloat(Math.round(this.#model.value / step) * step)\n\t\t\t: this.#model.value;\n\t}\n\n\t#textFor(value: number): string {\n\t\treturn (this.#formatValue ?? this.notation.format)(value);\n\t}\n\n\t#writeHeld(next: number): void {\n\t\tthis.#heldAt = next;\n\t\tthis.scrubChanged();\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"value-element.js","names":[],"sources":["../../../src/elements/value-element.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { HoverPreview } from '#lib/hover-preview.ts';\nimport type { ScrubState } from '#lib/scrub.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { ValueGestures } from '#elements/value-gestures.ts';\nimport { bindHoverPreview } from '#lib/hover-preview.ts';\nimport { toNumber, trimFloat } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-hover': Event;\n\t\t'sonic-reveal': Event;\n\t}\n}\n\ninterface HoverBinding {\n\tcanShow?: () => boolean;\n\tplace: () => void;\n\tvalueAt: (event: PointerEvent) => number | undefined;\n}\n\nexport interface ValueNotation {\n\tformat: (value: number) => string;\n\tparse: (text: string) => number;\n\tspeak?: (value: number) => string;\n}\n\nconst fallbackTravelPx = 160;\n\nlet instanceCount = 0;\n\nconst proportionAttributes = new Set(['max', 'midpoint', 'min', 'notched', 'positions', 'taper']);\n\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\nexport interface ValueLink {\n\tchange: () => void;\n\tinput: (next: number, isMover?: boolean) => boolean;\n\tisDisabled: () => boolean;\n\tisHeld: () => boolean;\n\tmodel: ValueModel;\n}\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicValueElement) => ValueLink;\n\nexport function linkValue(element: SonicValueElement): ValueLink {\n\treturn link(element);\n}\n\nexport abstract class SonicValueElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'detent',\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'spoken-step',\n\t\t'step',\n\t\t'tabindex',\n\t\t'taper',\n\t\t'value',\n\t\t'positions',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\tinput: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tisHeld: () => element.#isHeld(),\n\t\t\tmodel: element.#model,\n\t\t});\n\t}\n\n\tget cancelling(): boolean {\n\t\treturn this.hasState('cancelling');\n\t}\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget detent(): number | undefined {\n\t\treturn this.optionalNumberAttribute('detent');\n\t}\n\n\tset detent(value: number | undefined) {\n\t\tthis.reflect('detent', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\tget doublePress(): 'entry' | 'none' | 'reset' {\n\t\tconst gesture = this.getAttribute('double-press');\n\n\t\treturn gesture === 'none' || gesture === 'reset' ? gesture : 'entry';\n\t}\n\n\tset doublePress(gesture: 'entry' | 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget entry(): 'none' | undefined {\n\t\treturn this.getAttribute('entry') === 'none' ? 'none' : undefined;\n\t}\n\n\tset entry(gesture: 'none' | undefined) {\n\t\tthis.reflect('entry', gesture);\n\t}\n\n\tget formatEntry(): ((value: number) => string) | undefined {\n\t\treturn this.#formatEntry;\n\t}\n\n\tset formatEntry(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatEntry = format;\n\t}\n\n\tget formatSpokenValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatSpokenValue;\n\t}\n\n\tset formatSpokenValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatSpokenValue = format;\n\t\tthis.render();\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\tget hoverValue(): number | undefined {\n\t\treturn this.#hover?.value();\n\t}\n\n\tget keyStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('key-step');\n\t}\n\n\tset keyStep(value: number | undefined) {\n\t\tthis.reflect('key-step', value);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget pointerType(): string | undefined {\n\t\treturn this.#gestures?.pointerType();\n\t}\n\n\t/** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */\n\tget positions(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('positions'));\n\t}\n\n\tset positions(positions: Array<number> | undefined) {\n\t\tthis.reflect('positions', positions?.join(' '));\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\t/** Rounds the value assistive technology hears, in value units; the value itself is untouched */\n\tget spokenStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('spoken-step');\n\t}\n\n\tset spokenStep(value: number | undefined) {\n\t\tthis.reflect('spoken-step', value);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#model.value;\n\t}\n\n\tset value(next: null | number | undefined) {\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.formResetCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = toNumber(next);\n\n\t\tif (this.#isHeld()) {\n\t\t\tthis.#writeHeld(value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#model.write(value)) this.render();\n\t}\n\n\tget valueText(): string {\n\t\treturn this.#textFor(this.#model.value);\n\t}\n\n\t// `Number.parseFloat`, as `Number('')` is 0; an emptied field should leave the value alone\n\tprotected readonly notation: ValueNotation = { format: String, parse: Number.parseFloat };\n\n\t#formatEntry: ((value: number) => string) | undefined;\n\n\t#formatSpokenValue: ((value: number) => string) | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\t#gestures: undefined | ValueGestures;\n\n\t#heldAt: number | undefined;\n\n\t#hover: HoverPreview | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// A subclass adding an input to `isWrapping()` has to observe it\n\treadonly #model = createValueModel(() => this.#spec());\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { anchor: HTMLElement; bubble: Readout };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value' && this.#isHeld()) {\n\t\t\tthis.#writeHeld(this.numberAttribute('value', this.min));\n\t\t\treturn;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gestures?.end();\n\n\t\tthis.#model.respec(this.numberAttribute('value', this.min), { forgetAsk: name === 'value' });\n\t\tif (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'entry',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'spokenStep',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'positions',\n\t\t\t'detent',\n\t\t\t'formatEntry',\n\t\t\t'formatValue',\n\t\t\t'formatSpokenValue',\n\t\t\t'keyStep',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected abandonHold(): void {\n\t\tthis.#gestures?.abandon();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => undefined | ValueAxis,\n\t): void {\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\tif (bubble) this.#readout = { anchor: control, bubble: new Readout(bubble) };\n\t\tthis.#gestures = new ValueGestures(\n\t\t\t{\n\t\t\t\tdefault: () => this.default,\n\t\t\t\tdetent: () => this.detent,\n\t\t\t\tdispatch: (type) => {\n\t\t\t\t\tthis.dispatchEvent(new Event(type, { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdoublePress: () => this.doublePress,\n\t\t\t\tentryId: `sonic-entry-${this.#instance}`,\n\t\t\t\tentryText: () => this.#entryText(),\n\t\t\t\tforwardNaming: (target, isNamed) => {\n\t\t\t\t\tthis.forwardNaming(target, isNamed);\n\t\t\t\t},\n\t\t\t\thasEntry: () => this.entry !== 'none',\n\t\t\t\tinput: (next) => this.input(next),\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tkeyStep: () => this.keyStep,\n\t\t\t\tmodel: this.#model,\n\t\t\t\tparse: (text) => (this.#parseValue ?? this.notation.parse)(text),\n\t\t\t\trender: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tscrubChanged: () => {\n\t\t\t\t\tthis.#renderHold();\n\t\t\t\t},\n\t\t\t\tscrubsKeyRepeat: () => this.scrubsKeyRepeat(),\n\t\t\t\tspringTarget: () => this.springTarget(),\n\t\t\t\ttapTarget: () => this.tapTarget(),\n\t\t\t\ttoggleState: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontrol,\n\t\t);\n\t\tthis.#gestures.bind(signal, grab);\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tthis.#hover?.lift(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected bindHover(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\t{ canShow = () => true, place, valueAt }: HoverBinding,\n\t): void {\n\t\tthis.#hover = bindHoverPreview(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcanShow,\n\t\t\t\tchanged: () => {\n\t\t\t\t\tthis.dispatchEvent(new Event('sonic-hover', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdismiss: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tisTaken: () => this.#isEditing() || this.#isRevealed(),\n\t\t\t\tshow: () => {\n\t\t\t\t\tif (!this.readout) return;\n\n\t\t\t\t\tthis.renderReadout();\n\t\t\t\t\tplace();\n\t\t\t\t},\n\t\t\t\tvalueAt,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected controlOrientation(): 'horizontal' | 'vertical' | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected abstract draw(): void;\n\n\tprotected abstract override focusTarget(): HTMLElement;\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#model.value;\n\n\t\tif (this.#model.input(next)) this.render();\n\t\tif (this.#model.value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected mapping(): ValueMapping {\n\t\treturn this.#model.mapping();\n\t}\n\n\tprotected originValue(): number {\n\t\treturn this.origin ?? this.mapping().bounds[0];\n\t}\n\n\tprotected proportionsChanged(): void {\n\t\t// Runs when every proportion moves, never per input\n\t}\n\n\tprotected readoutValue(): number {\n\t\treturn this.#shown().value ?? this.#model.value;\n\t}\n\n\tprotected render(): void {\n\t\tif (this.isBound()) this.#renderControl();\n\t\tthis.#model.notify();\n\t}\n\n\tprotected renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout) return;\n\n\t\tconst { isOpen, value } = this.#shown();\n\t\tconst text = value === undefined ? undefined : this.#textFor(value);\n\n\t\treadout.bubble.show({ anchor: readout.anchor, isOpen: isOpen && text !== '', text });\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected scrubChanged(): void {\n\t\t// Only a subclass draws a scrub\n\t}\n\n\tprotected scrubsKeyRepeat(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected scrubState(): ScrubState {\n\t\tconst from = this.#gestures?.scrubFrom();\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tisRevealed: this.#isRevealed(),\n\t\t\tplayed: from === undefined ? this.#model.value : (this.#heldAt ?? from),\n\t\t};\n\t}\n\n\tprotected springTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected tapTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number {\n\t\treturn Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));\n\t}\n\n\t#entryText(): string {\n\t\tconst value = this.#model.value;\n\n\t\tif (this.#formatEntry) return this.#formatEntry(value);\n\n\t\t// The notation's own parser may not read a consumer's `formatValue`\n\t\tif (!this.#parseValue) return this.notation.format(value);\n\n\t\tconst text = this.valueText;\n\n\t\tif (__DEV__ && !Number.isFinite(this.#parseValue(text))) {\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> opens its entry with \"${text}\", which parseValue cannot read; set formatEntry`,\n\t\t\t);\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t#isEditing(): boolean {\n\t\treturn this.#gestures?.isEditing() === true;\n\t}\n\n\t#isHeld(): boolean {\n\t\treturn this.#gestures?.isHeld() === true;\n\t}\n\n\t#isRevealed(): boolean {\n\t\treturn this.#gestures?.isRevealed() === true;\n\t}\n\n\t#renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#model.value), String(this.#model.value));\n\t\tif (this.#isEditing()) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tconst [low, high] = this.mapping().bounds;\n\n\t\twriteAttribute(control, 'role', this.controlRole());\n\t\twriteAttribute(control, 'aria-valuemin', String(low));\n\t\twriteAttribute(control, 'aria-valuemax', String(high));\n\t\twriteAttribute(control, 'aria-valuenow', String(this.#spokenNow()));\n\t\tif (orientation) writeAttribute(control, 'aria-orientation', orientation);\n\t\twriteAttribute(\n\t\t\tcontrol,\n\t\t\t'aria-valuetext',\n\t\t\t(this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(this.#spokenNow()),\n\t\t);\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.renderReadout();\n\t}\n\n\t#renderControl(): void {\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.#isEditing() || this.#isRevealed()) this.#hover?.clear();\n\t\tthis.draw();\n\t\tthis.toggleState(\n\t\t\t'at-origin',\n\t\t\t!this.isWrapping() && this.#model.value === this.mapping().snap(this.originValue()),\n\t\t);\n\t\tthis.#renderAria(this.focusTarget(), this.controlOrientation());\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\twriteAttribute(control, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t// The host is `display: contents`, so its tab stop is the control's\n\t\tconst stop = this.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteAttribute(control, 'tabindex', isDisabled ? undefined : stop);\n\t}\n\n\t#renderHold(): void {\n\t\tif (this.#gestures?.scrubFrom() === undefined) this.#heldAt = undefined;\n\t\tthis.#hover?.restore();\n\t\tthis.renderReadout();\n\t\tthis.scrubChanged();\n\t}\n\n\t#shown(): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing()) return { isOpen: true, value: undefined };\n\n\t\tconst isRevealed = this.#isRevealed();\n\t\tconst preview = isRevealed ? undefined : this.#hover?.value();\n\n\t\treturn {\n\t\t\tisOpen: (isRevealed || preview !== undefined) && this.readout,\n\t\t\tvalue: preview ?? this.#model.value,\n\t\t};\n\t}\n\n\t#spec(): ValueSpec {\n\t\tconst { detent, midpoint, positions } = this;\n\n\t\treturn {\n\t\t\t...(detent === undefined ? {} : { detent }),\n\t\t\t...(positions ? { positions } : {}),\n\t\t\tmax: this.max,\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: this.notched,\n\t\t\tisWrapping: this.isWrapping(),\n\t\t\tmin: this.min,\n\t\t\tstep: this.step,\n\t\t\ttaper: this.taper,\n\t\t};\n\t}\n\n\t#spokenNow(): number {\n\t\tconst step = this.spokenStep;\n\n\t\treturn step !== undefined && step > 0\n\t\t\t? trimFloat(Math.round(this.#model.value / step) * step)\n\t\t\t: this.#model.value;\n\t}\n\n\t#textFor(value: number): string {\n\t\treturn (this.#formatValue ?? this.notation.format)(value);\n\t}\n\n\t#writeHeld(next: number): void {\n\t\tthis.#heldAt = next;\n\t\tthis.scrubChanged();\n\t}\n}\n"],"mappings":"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"}
@@ -17,7 +17,7 @@ export interface WaveMarker {
17
17
  label?: string;
18
18
  start: Seconds;
19
19
  }
20
- export declare abstract class SonicWaveElement<Colour extends string, Length extends string = never> extends SonicValueElement {
20
+ export declare abstract class SonicWaveElement<Colour extends string, Length extends string = never, Numeric extends string = never> extends SonicValueElement {
21
21
  #private;
22
22
  static readonly observedAttributes: string[];
23
23
  get currentMarker(): undefined | WaveMarker;
@@ -30,6 +30,7 @@ export declare abstract class SonicWaveElement<Colour extends string, Length ext
30
30
  protected abstract readonly control: HTMLElement;
31
31
  protected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;
32
32
  protected readonly notation: ValueNotation;
33
+ protected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;
33
34
  protected abstract readonly sheet: string;
34
35
  protected abstract readonly sizeProperty: `--_sonic-${string}`;
35
36
  attributeChangedCallback(name: string): void;
@@ -40,7 +41,7 @@ export declare abstract class SonicWaveElement<Colour extends string, Length ext
40
41
  protected abstract isEmpty(): boolean;
41
42
  protected markerList(): ReadonlyArray<WaveMarker>;
42
43
  protected markerProportions(): Array<[number, number]>;
43
- protected abstract paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void;
44
+ protected abstract paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length, Numeric>): void;
44
45
  protected proportionsChanged(): void;
45
46
  protected renderEmpty(): void;
46
47
  protected abstract renderMarkers(): void;
@@ -63,6 +63,7 @@ var SonicWaveElement = class extends SonicValueElement {
63
63
  sizeProperty: this.sizeProperty
64
64
  },
65
65
  lengths: this.lengths,
66
+ numbers: this.numbers,
66
67
  paint: (context, frame) => {
67
68
  this.paint(context, frame);
68
69
  },
@@ -1 +1 @@
1
- {"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n> extends SonicValueElement {\n\tstatic override readonly observedAttributes = [...SonicValueElement.observedAttributes, 'fill'];\n\n\tget currentMarker(): undefined | WaveMarker {\n\t\treturn this.#current ? { ...this.#current } : undefined;\n\t}\n\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\tget markers(): Array<WaveMarker> {\n\t\treturn this.#markers.map((marker) => ({ ...marker }));\n\t}\n\n\tset markers(list: Iterable<WaveMarker> | undefined) {\n\t\tthis.#markers = [...(list ?? [])]\n\t\t\t.filter((marker) => Number.isFinite(marker.start))\n\t\t\t.map((marker) => ({ ...marker }))\n\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\tif (!this.isBound()) return;\n\n\t\tthis.renderMarkers();\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.renderEmpty();\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tfill: { control, sizeProperty: this.sizeProperty },\n\t\t\tlengths: this.lengths,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(control, this.sheet, {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected abstract isEmpty(): boolean;\n\n\tprotected markerList(): ReadonlyArray<WaveMarker> {\n\t\treturn this.#markers;\n\t}\n\n\tprotected markerProportions(): Array<[number, number]> {\n\t\tconst mapping = this.mapping();\n\n\t\treturn this.#markers.map(({ end, start }) => {\n\t\t\tconst at = mapping.proportionOf(start);\n\t\t\tconst to = end === undefined ? at : mapping.proportionOf(end);\n\n\t\t\treturn [Math.min(at, to), Math.max(at, to)];\n\t\t});\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length>,\n\t): void;\n\n\tprotected override proportionsChanged(): void {\n\t\tthis.renderMarkers();\n\t}\n\n\tprotected renderEmpty(): void {\n\t\tthis.toggleState('empty', this.isEmpty());\n\t}\n\n\tprotected abstract renderMarkers(): void;\n\n\tprotected override scrubChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> extends SonicValueElement {\n\tstatic override readonly observedAttributes = [...SonicValueElement.observedAttributes, 'fill'];\n\n\tget currentMarker(): undefined | WaveMarker {\n\t\treturn this.#current ? { ...this.#current } : undefined;\n\t}\n\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\tget markers(): Array<WaveMarker> {\n\t\treturn this.#markers.map((marker) => ({ ...marker }));\n\t}\n\n\tset markers(list: Iterable<WaveMarker> | undefined) {\n\t\tthis.#markers = [...(list ?? [])]\n\t\t\t.filter((marker) => Number.isFinite(marker.start))\n\t\t\t.map((marker) => ({ ...marker }))\n\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\tif (!this.isBound()) return;\n\n\t\tthis.renderMarkers();\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.renderEmpty();\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tfill: { control, sizeProperty: this.sizeProperty },\n\t\t\tlengths: this.lengths,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(control, this.sheet, {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected abstract isEmpty(): boolean;\n\n\tprotected markerList(): ReadonlyArray<WaveMarker> {\n\t\treturn this.#markers;\n\t}\n\n\tprotected markerProportions(): Array<[number, number]> {\n\t\tconst mapping = this.mapping();\n\n\t\treturn this.#markers.map(({ end, start }) => {\n\t\t\tconst at = mapping.proportionOf(start);\n\t\t\tconst to = end === undefined ? at : mapping.proportionOf(end);\n\n\t\t\treturn [Math.min(at, to), Math.max(at, to)];\n\t\t});\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length, Numeric>,\n\t): void;\n\n\tprotected override proportionsChanged(): void {\n\t\tthis.renderMarkers();\n\t}\n\n\tprotected renderEmpty(): void {\n\t\tthis.toggleState('empty', this.isEmpty());\n\t}\n\n\tprotected abstract renderMarkers(): void;\n\n\tprotected override scrubChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":"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"}
@@ -3,8 +3,9 @@ import { TimeRegions } from "../lib/time-regions.js";
3
3
  import { SurfaceFrame } from "../lib/canvas-surface.js";
4
4
  import { SonicWaveElement, WaveMarker } from "./wave-element.js";
5
5
  import { PeaksRequest } from "../lib/peaks-request.js";
6
+ import { WaveformBands } from "../lib/waveform-bands.js";
6
7
  import { WaveformPeaks } from "../lib/waveform-buckets.js";
7
- import { waveformColours } from "../lib/waveform-scene.js";
8
+ import { waveformColours, waveformNumbers } from "../lib/waveform-scene.js";
8
9
  //#region src/elements/waveform.d.ts
9
10
  declare global {
10
11
  interface HTMLElementEventMap {
@@ -15,10 +16,13 @@ declare global {
15
16
  }
16
17
  }
17
18
  type Colour = keyof typeof waveformColours;
19
+ type Numeric = keyof typeof waveformNumbers;
18
20
  export type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;
19
- export declare class SonicWaveform extends SonicWaveElement<Colour> {
21
+ export declare class SonicWaveform extends SonicWaveElement<Colour, never, Numeric> {
20
22
  #private;
21
23
  static readonly observedAttributes: string[];
24
+ get bands(): undefined | WaveformBands;
25
+ set bands(bands: undefined | WaveformBands);
22
26
  get peaks(): undefined | WaveformPeaks;
23
27
  set peaks(peaks: undefined | WaveformPeaks);
24
28
  /** Spans being fetched; drawn as placeholders until cleared */
@@ -55,10 +59,15 @@ export declare class SonicWaveform extends SonicWaveElement<Colour> {
55
59
  readonly grid: "--_sonic-waveform-grid";
56
60
  readonly marker: "--_sonic-waveform-marker";
57
61
  readonly placeholder: "--_sonic-waveform-placeholder";
62
+ readonly shade: "--_sonic-waveform-shade";
63
+ readonly shadeClear: "--_sonic-waveform-shade-clear";
58
64
  readonly wave: "--_sonic-waveform-wave";
59
- readonly waveEdge: "--_sonic-waveform-wave-edge";
60
65
  };
61
66
  protected readonly lengths: {};
67
+ protected readonly numbers: {
68
+ readonly bandOpacity: "--_sonic-waveform-band-opacity";
69
+ readonly edgeShade: "--_sonic-waveform-edge-shade";
70
+ };
62
71
  protected readonly sheet = "waveform.css";
63
72
  protected readonly sizeProperty = "--_sonic-waveform-size";
64
73
  attributeChangedCallback(name: string): void;
@@ -66,7 +75,7 @@ export declare class SonicWaveform extends SonicWaveElement<Colour> {
66
75
  protected connect(signal: AbortSignal): void;
67
76
  protected draw(): void;
68
77
  protected isEmpty(): boolean;
69
- protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour>): void;
78
+ protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>): void;
70
79
  protected renderMarkers(): void;
71
80
  protected scrubChanged(): void;
72
81
  protected scrubsKeyRepeat(): boolean;
@@ -7,7 +7,8 @@ import { isKind, writeKind } from "../lib/marker-band.js";
7
7
  import { createLabelRider } from "../lib/marker-rider.js";
8
8
  import { createPeaksRequest } from "../lib/peaks-request.js";
9
9
  import { createTrackingClock } from "../lib/tracking-clock.js";
10
- import { paintWaveform, waveformColours } from "../lib/waveform-scene.js";
10
+ import { bandTable } from "../lib/waveform-bands.js";
11
+ import { bandChannels, paintWaveform, waveformColours, waveformNumbers } from "../lib/waveform-scene.js";
11
12
  import { bindZoom, bindZoomKeys } from "../lib/zoom-gesture.js";
12
13
 
13
14
  //#region src/elements/waveform.ts
@@ -54,6 +55,14 @@ var SonicWaveform = class extends SonicWaveElement {
54
55
  "zoom-min",
55
56
  "zoomable"
56
57
  ];
58
+ get bands() {
59
+ return this.#bands;
60
+ }
61
+ set bands(bands) {
62
+ this.#bands = bands;
63
+ this.#bandTable = bandTable(bands);
64
+ this.#peaksRequest.changed();
65
+ }
57
66
  get peaks() {
58
67
  return this.#peaks;
59
68
  }
@@ -157,8 +166,12 @@ var SonicWaveform = class extends SonicWaveElement {
157
166
  canvas = requireChild(this.control, ".sonic-waveform-canvas", HTMLCanvasElement);
158
167
  colours = waveformColours;
159
168
  lengths = {};
169
+ numbers = waveformNumbers;
160
170
  sheet = "waveform.css";
161
171
  sizeProperty = "--_sonic-waveform-size";
172
+ #bandColours = [];
173
+ #bands;
174
+ #bandTable;
162
175
  #clock = createTrackingClock();
163
176
  #drawn;
164
177
  #ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
@@ -182,13 +195,14 @@ var SonicWaveform = class extends SonicWaveElement {
182
195
  insetProperty: "--_sonic-waveform-label-inset",
183
196
  parkProperty: "--_sonic-waveform-label-park"
184
197
  });
198
+ #strip;
185
199
  #zoomGesture;
186
200
  attributeChangedCallback(name) {
187
201
  super.attributeChangedCallback(name);
188
202
  if (name === "zoomable") this.#zoomGesture?.sync();
189
203
  }
190
204
  connectedCallback() {
191
- this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
205
+ this.upgradeProperties("bands", "peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
192
206
  super.connectedCallback();
193
207
  }
194
208
  connect(signal) {
@@ -262,8 +276,10 @@ var SonicWaveform = class extends SonicWaveElement {
262
276
  if (isRebuilt) this.#kindColours = this.#readKindColours();
263
277
  paintWaveform(context, {
264
278
  ...view,
265
- colours: look.colours
279
+ colours: look.colours,
280
+ numbers: look.numbers
266
281
  }, {
282
+ bands: this.#tint(isRebuilt),
267
283
  kindColours: this.#kindColours,
268
284
  markers: this.markerList(),
269
285
  peaks: this.#peaks,
@@ -328,6 +344,14 @@ var SonicWaveform = class extends SonicWaveElement {
328
344
  startSeconds: this.value
329
345
  };
330
346
  }
347
+ #readBandColours(strip, bandCount) {
348
+ const probes = Array.from({ length: bandCount }, (_probe, band) => {
349
+ const probe = document.createElement("div");
350
+ probe.style.setProperty("color", `var(--sonic-waveform-band-${String(band + 1)}, transparent)`);
351
+ return probe;
352
+ });
353
+ return this.#readProbes(probes).map((colour) => bandChannels(strip, colour));
354
+ }
331
355
  #readKindColours() {
332
356
  const kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];
333
357
  const probes = kinds.map((kind) => {
@@ -336,14 +360,34 @@ var SonicWaveform = class extends SonicWaveElement {
336
360
  writeKind(probe, kind, "--_sonic-waveform-marker");
337
361
  return probe;
338
362
  });
363
+ const read = this.#readProbes(probes);
364
+ return new Map(kinds.map((kind, index) => [kind, read[index] ?? ""]));
365
+ }
366
+ #readProbes(probes) {
339
367
  this.control.append(...probes);
340
368
  const read = probes.map((probe) => getComputedStyle(probe).color);
341
369
  for (const probe of probes) probe.remove();
342
- return new Map(kinds.map((kind, index) => [kind, read[index] ?? ""]));
370
+ return read;
343
371
  }
344
372
  #sourceSeconds() {
345
373
  return this.#readTime?.() ?? this.scrubState().played;
346
374
  }
375
+ #tint(isRebuilt) {
376
+ const table = this.#bandTable;
377
+ if (!table) return void 0;
378
+ const strip = this.#strip ?? document.createElement("canvas").getContext("2d", { willReadFrequently: true }) ?? void 0;
379
+ if (!strip) return void 0;
380
+ this.#strip = strip;
381
+ if (isRebuilt || this.#bandColours.length !== table.bandCount) {
382
+ strip.canvas.height = 1;
383
+ this.#bandColours = this.#readBandColours(strip, table.bandCount);
384
+ }
385
+ return {
386
+ colours: this.#bandColours,
387
+ strip,
388
+ table
389
+ };
390
+ }
347
391
  #zoomTo(target) {
348
392
  const next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);
349
393
  if (next === this.zoom) return;