@xsynaptic/sonic-ui 0.19.0 → 0.21.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 (79) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +27 -112
  3. package/dist/default/elements/button.js +2 -1
  4. package/dist/default/elements/field-gesture.js +3 -0
  5. package/dist/default/elements/sonic-element.js +1 -1
  6. package/dist/default/elements/split.js +3 -1
  7. package/dist/default/elements/switch.js +5 -3
  8. package/dist/default/elements/toggle.js +10 -5
  9. package/dist/default/elements/value-entry.js +5 -18
  10. package/dist/default/elements/waveform.js +15 -4
  11. package/dist/default/elements/xy.js +33 -5
  12. package/dist/default/lib/canvas-surface.js +5 -1
  13. package/dist/default/lib/double-press.js +30 -0
  14. package/dist/dev/elements/button.js +5 -11
  15. package/dist/dev/elements/button.js.map +1 -1
  16. package/dist/dev/elements/dial.js +2 -12
  17. package/dist/dev/elements/dial.js.map +1 -1
  18. package/dist/dev/elements/display-element.js +1 -5
  19. package/dist/dev/elements/display-element.js.map +1 -1
  20. package/dist/dev/elements/envelope.js +2 -15
  21. package/dist/dev/elements/envelope.js.map +1 -1
  22. package/dist/dev/elements/field-gesture.js +3 -0
  23. package/dist/dev/elements/field-gesture.js.map +1 -1
  24. package/dist/dev/elements/meter.js +1 -1
  25. package/dist/dev/elements/meter.js.map +1 -1
  26. package/dist/dev/elements/number.js +1 -13
  27. package/dist/dev/elements/number.js.map +1 -1
  28. package/dist/dev/elements/segmented.js +3 -10
  29. package/dist/dev/elements/segmented.js.map +1 -1
  30. package/dist/dev/elements/slider.js +2 -12
  31. package/dist/dev/elements/slider.js.map +1 -1
  32. package/dist/dev/elements/sonic-element.d.ts +1 -22
  33. package/dist/dev/elements/sonic-element.js +7 -34
  34. package/dist/dev/elements/sonic-element.js.map +1 -1
  35. package/dist/dev/elements/split.js +3 -1
  36. package/dist/dev/elements/split.js.map +1 -1
  37. package/dist/dev/elements/switch.js +7 -8
  38. package/dist/dev/elements/switch.js.map +1 -1
  39. package/dist/dev/elements/toggle.js +13 -15
  40. package/dist/dev/elements/toggle.js.map +1 -1
  41. package/dist/dev/elements/value-entry.js +5 -18
  42. package/dist/dev/elements/value-entry.js.map +1 -1
  43. package/dist/dev/elements/wave-element.js +2 -12
  44. package/dist/dev/elements/wave-element.js.map +1 -1
  45. package/dist/dev/elements/waveform.d.ts +2 -0
  46. package/dist/dev/elements/waveform.js +15 -4
  47. package/dist/dev/elements/waveform.js.map +1 -1
  48. package/dist/dev/elements/xy.d.ts +4 -0
  49. package/dist/dev/elements/xy.js +35 -17
  50. package/dist/dev/elements/xy.js.map +1 -1
  51. package/dist/dev/lib/canvas-surface.js +5 -1
  52. package/dist/dev/lib/canvas-surface.js.map +1 -1
  53. package/dist/dev/lib/double-press.js +31 -0
  54. package/dist/dev/lib/double-press.js.map +1 -0
  55. package/dist/skins/amber.css +0 -1
  56. package/dist/skins/ivory.css +0 -1
  57. package/dist/skins/lime.css +0 -1
  58. package/dist/skins/slate.css +0 -1
  59. package/dist/styles/button.css +1 -0
  60. package/dist/styles/dial.css +1 -1
  61. package/dist/styles/envelope.css +16 -14
  62. package/dist/styles/led.css +7 -19
  63. package/dist/styles/material/bracket.css +1 -1
  64. package/dist/styles/material/cap.css +27 -4
  65. package/dist/styles/material/core.css +9 -1
  66. package/dist/styles/material/glass.css +1 -7
  67. package/dist/styles/material/lens.css +18 -1
  68. package/dist/styles/material/pane.css +2 -12
  69. package/dist/styles/material/readout.css +7 -1
  70. package/dist/styles/material/well.css +8 -12
  71. package/dist/styles/meter.css +6 -4
  72. package/dist/styles/number.css +8 -9
  73. package/dist/styles/screen.css +1 -3
  74. package/dist/styles/spectrum.css +8 -22
  75. package/dist/styles/toggle.css +4 -0
  76. package/dist/styles/waveform.css +12 -24
  77. package/dist/styles/wavestrip.css +12 -33
  78. package/dist/styles/xy.css +30 -28
  79. package/package.json +1 -1
@@ -117,16 +117,9 @@ var SonicButton = class extends SonicFormElement {
117
117
  }, signal);
118
118
  this.render();
119
119
  this.checkStyles(button, "button.css", {
120
- box: {
121
- property: "padding-inline-start",
122
- ratio: "--_sonic-button-padding-ratio",
123
- selector: ".sonic-button-cap > :not(svg, .sonic-led)"
124
- },
125
- material: [
126
- "cap",
127
- "well",
128
- "keycap"
129
- ]
120
+ property: "padding-inline-start",
121
+ ratio: "--_sonic-button-padding-ratio",
122
+ selector: ".sonic-button-cap > :not(svg, .sonic-led)"
130
123
  });
131
124
  button.addEventListener("click", (event) => {
132
125
  if (this.#isSoftDisabled()) {
@@ -179,7 +172,8 @@ var SonicButton = class extends SonicFormElement {
179
172
  holding.hold(event.pointerId);
180
173
  }, { signal });
181
174
  button.addEventListener("keydown", (event) => {
182
- if (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;
175
+ if (event.repeat || event.defaultPrevented) return;
176
+ if (!holdKeys.has(event.key) || !this.#isMomentary()) return;
183
177
  holding.hold(event.key);
184
178
  }, { signal });
185
179
  }
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\t/** Shows the legend child whose `data-sonic-when` has this name */\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\t/** Submitted with the form while a latching button is pressed */\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tbox: {\n\t\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t\t},\n\t\t\t\tmaterial: ['cap', 'well', 'keycap'],\n\t\t\t});\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || !holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"button.js","names":[],"sources":["../../../src/elements/button.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-button': SonicButton;\n\t}\n}\n\nconst renderButton = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-button\" type=\"button\"><span class=\"sonic-button-cap\"></span></button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst holdKeys = new Set([' ', 'Enter']);\n\nconst popupAttributes = {\n\tcontrols: 'aria-controls',\n\texpanded: 'aria-expanded',\n\tpopup: 'aria-haspopup',\n} as const;\n\nconst pressWhens = new Set(['pressed', 'released']);\n\nexport class SonicButton extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t...Object.keys(popupAttributes),\n\t\t'busy',\n\t\t'legend',\n\t\t'momentary',\n\t\t'pressed',\n\t\t'soft-disabled',\n\t\t'latching',\n\t\t'value',\n\t];\n\n\tget armed(): boolean {\n\t\treturn this.hasAttribute('armed');\n\t}\n\n\tset armed(isArmed: boolean) {\n\t\tthis.reflect('armed', isArmed);\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.hasAttribute('busy');\n\t}\n\n\tset busy(isBusy: boolean) {\n\t\tthis.reflect('busy', isBusy);\n\t}\n\n\tget defaultPressed(): boolean {\n\t\treturn this.hasAttribute('pressed');\n\t}\n\n\tset defaultPressed(isPressed: boolean) {\n\t\tthis.reflect('pressed', isPressed);\n\t}\n\n\tget latching(): boolean {\n\t\treturn this.hasAttribute('latching');\n\t}\n\n\tset latching(isLatching: boolean) {\n\t\tthis.reflect('latching', isLatching);\n\t}\n\n\t/** Shows the legend child whose `data-sonic-when` has this name */\n\tget legend(): string | undefined {\n\t\treturn this.getAttribute('legend') ?? undefined;\n\t}\n\n\tset legend(name: string | undefined) {\n\t\tthis.reflect('legend', name);\n\t}\n\n\tget momentary(): boolean {\n\t\treturn this.hasAttribute('momentary');\n\t}\n\n\tset momentary(isMomentary: boolean) {\n\t\tthis.reflect('momentary', isMomentary);\n\t}\n\n\tget pressed(): boolean {\n\t\treturn this.#isDirty ? this.#pressed : this.defaultPressed;\n\t}\n\n\tset pressed(isPressed: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#pressed = isPressed === true;\n\t\tthis.render();\n\t}\n\n\tget softDisabled(): boolean {\n\t\treturn this.hasAttribute('soft-disabled');\n\t}\n\n\tset softDisabled(isSoftDisabled: boolean) {\n\t\tthis.reflect('soft-disabled', isSoftDisabled);\n\t}\n\n\t/** Submitted with the form while a latching button is pressed */\n\tget value(): string {\n\t\treturn this.getAttribute('value') ?? 'on';\n\t}\n\n\tset value(value: string | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\treadonly #button = renderButton();\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\t#pressed = false;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'pressed') this.#isDirty = false;\n\t\tif (this.isDisabled() || this.#isSoftDisabled()) this.#holding?.release();\n\t\tthis.render();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.attributeChangedCallback('pressed');\n\t}\n\n\tprotected override activate(): void {\n\t\tthis.#button.click();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst button = this.#button;\n\t\tconst cap = requireChild(button, '.sonic-button-cap', HTMLSpanElement);\n\n\t\tthis.upgradeProperties(\n\t\t\t'armed',\n\t\t\t'busy',\n\t\t\t'defaultPressed',\n\t\t\t'legend',\n\t\t\t'momentary',\n\t\t\t'softDisabled',\n\t\t\t'latching',\n\t\t\t'pressed',\n\t\t\t'value',\n\t\t);\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: button,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tplaceChildren(cap, copies);\n\t\t\t\t\tthis.#showLegend();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(button, 'button.css', {\n\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\tratio: '--_sonic-button-padding-ratio',\n\t\t\t\tselector: '.sonic-button-cap > :not(svg, .sonic-led)',\n\t\t\t});\n\n\t\tbutton.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A native disabled button fires no click at all\n\t\t\t\tif (this.#isSoftDisabled()) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!this.latching) return;\n\n\t\t\t\tthis.pressed = !this.pressed;\n\t\t\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindMomentary(button, signal);\n\t\tbindKeyPress(button, signal);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#button;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst button = this.#button;\n\n\t\twriteAttribute(button, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(button, 'aria-disabled', this.#isSoftDisabled() ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-busy', this.busy ? 'true' : undefined);\n\t\twriteAttribute(button, 'aria-pressed', this.latching ? String(this.pressed) : undefined);\n\t\tthis.toggleState('pressed', this.pressed);\n\t\tthis.forwardNaming(button, true);\n\t\tthis.#forwardPopup(button);\n\t\tthis.#showLegend();\n\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tconst submitted = this.latching && this.pressed ? this.value : null;\n\n\t\tthis.writeFormValue(submitted, String(this.pressed));\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tif (this.latching) this.pressed = state === 'true';\n\t}\n\n\t#bindMomentary(button: HTMLButtonElement, signal: AbortSignal): void {\n\t\tconst holding = bindHold(\n\t\t\tbutton,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled() && !this.#isSoftDisabled(),\n\t\t\t\tleave: 'blur',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.#press(true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.#press(false);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tthis.#holding = holding;\n\t\tbutton.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tif (event.button !== 0 || !this.#isMomentary()) return;\n\n\t\t\t\tcapturePointer(button, event.pointerId);\n\t\t\t\tholding.hold(event.pointerId);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tbutton.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.repeat || event.defaultPrevented) return;\n\t\t\t\tif (!holdKeys.has(event.key) || !this.#isMomentary()) return;\n\n\t\t\t\tholding.hold(event.key);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#forwardPopup(button: HTMLButtonElement): void {\n\t\tfor (const [name, aria] of Object.entries(popupAttributes)) {\n\t\t\twriteAttribute(button, aria, this.getAttribute(name) ?? undefined);\n\t\t}\n\t}\n\n\t#isMomentary(): boolean {\n\t\treturn this.momentary && !this.latching;\n\t}\n\n\t#isSoftDisabled(): boolean {\n\t\treturn this.softDisabled && !this.isDisabled();\n\t}\n\n\t#press(isPressed: boolean): void {\n\t\tthis.pressed = isPressed;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#showLegend(): void {\n\t\tconst legend = this.legend;\n\n\t\tfor (const part of this.#button.querySelectorAll<HTMLElement | SVGElement>(\n\t\t\t':scope > .sonic-button-cap > [data-sonic-when]',\n\t\t)) {\n\t\t\tconst when = part.dataset.sonicWhen ?? '';\n\n\t\t\tpart.toggleAttribute('data-sonic-hidden', !pressWhens.has(when) && when !== legend);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -67,18 +67,8 @@ var SonicDial = class extends SonicValueElement {
67
67
  }, signal);
68
68
  this.render();
69
69
  this.checkStyles(dial, "dial.css", {
70
- box: {
71
- property: "margin-bottom",
72
- selector: "[popover]"
73
- },
74
- material: [
75
- "cap",
76
- "readout",
77
- "value",
78
- ["glass", ".sonic-dial-readout"],
79
- ["arc", ".sonic-dial-ring"],
80
- ["scale", ".sonic-dial-scale"]
81
- ]
70
+ property: "margin-bottom",
71
+ selector: "[popover]"
82
72
  });
83
73
  this.bindGestures(dial, signal, () => ({
84
74
  position: (event) => -event.clientY,
@@ -1 +1 @@
1
- {"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-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-dial-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\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(dial, 'dial.css', {\n\t\t\t\tbox: { property: 'margin-bottom', selector: '[popover]' },\n\t\t\t\tmaterial: [\n\t\t\t\t\t'cap',\n\t\t\t\t\t'readout',\n\t\t\t\t\t'value',\n\t\t\t\t\t['glass', '.sonic-dial-readout'],\n\t\t\t\t\t['arc', '.sonic-dial-ring'],\n\t\t\t\t\t['scale', '.sonic-dial-scale'],\n\t\t\t\t],\n\t\t\t});\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAaA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;;CAIA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAIA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EAEX,KAAK,YAAY,MAAM,YAAY;GAClC,KAAK;IAAE,UAAU;IAAiB,UAAU;GAAY;GACxD,UAAU;IACT;IACA;IACA;IACA,CAAC,SAAS,qBAAqB;IAC/B,CAAC,OAAO,kBAAkB;IAC1B,CAAC,SAAS,mBAAmB;GAC9B;EACD,CAAC;EACF,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
1
+ {"version":3,"file":"dial.js","names":[],"sources":["../../../src/elements/dial.ts"],"sourcesContent":["import { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-dial': SonicDial;\n\t}\n}\n\nconst renderDial = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-dial\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-dial-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-dial-notches\"></div>\n\t\t\t<div class=\"sonic-dial-ring\"></div>\n\t\t\t<div class=\"sonic-dial-modulation\"></div>\n\t\t\t<div class=\"sonic-dial-cap\"></div>\n\t\t\t<div class=\"sonic-dial-indicator\"></div>\n\t\t\t<div class=\"sonic-dial-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-dial-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\nexport class SonicDial extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'endless',\n\t\t'modulation',\n\t];\n\n\tget endless(): boolean {\n\t\treturn this.hasAttribute('endless');\n\t}\n\n\tset endless(isEndless: boolean) {\n\t\tthis.reflect('endless', isEndless);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\treadonly #dial = renderDial();\n\n\treadonly #modulation = createModulation(this.#dial, 'dial', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('endless', 'modulationValue', 'modulation');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst dial = this.#dial;\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: dial, marks: requireChild(dial, '.sonic-dial-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(dial, 'dial.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.bindGestures(dial, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(dial, '--_sonic-dial-travel'),\n\t\t}));\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.endless ? 0 : this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#dial;\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn this.endless;\n\t}\n}\n"],"mappings":";;;;;;AAaA,MAAM,aAAa,SACP;;;;;;;;;;;;;;;;;;;;IAqBX,cACD;AAEA,IAAa,YAAb,cAA+B,kBAAkB;CAChD,OAAyB,qBAAqB;EAC7C,GAAG,kBAAkB;EACrB;EACA;CACD;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,aAAa,SAAS;CACnC;CAEA,IAAI,QAAQ,WAAoB;EAC/B,KAAK,QAAQ,WAAW,SAAS;CAClC;;CAIA,IAAI,aAAqB;EACxB,OAAO,KAAK,gBAAgB,cAAc,CAAC;CAC5C;CAEA,IAAI,WAAW,QAA4B;EAC1C,KAAK,QAAQ,cAAc,MAAM;CAClC;;CAIA,IAAI,kBAAsC;EACzC,OAAO,KAAK,YAAY,MAAM;CAC/B;CAEA,IAAI,gBAAgB,OAAkC;EACrD,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,MAAS;CAC1E;CAEA,AAAS,QAAQ,WAAW;CAE5B,AAAS,cAAc,iBAAiB,KAAK,OAAO,SAAS,gBAAgB;EAC5E,KAAK,YAAY,aAAa,WAAW;CAC1C,CAAC;CAED,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,WAAW,mBAAmB,YAAY;EACjE,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,OAAO,KAAK;EAElB,UACC,MACA;GAAE,SAAS;GAAM,OAAO,aAAa,MAAM,qBAAqB,cAAc;EAAE,GAChF,MACD;EACA,KAAK,OAAO;EAEX,KAAK,YAAY,MAAM,YAAY;GAAE,UAAU;GAAiB,UAAU;EAAY,CAAC;EACxF,KAAK,aAAa,MAAM,eAAe;GACtC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,MAAM,sBAAsB;EACrD,EAAE;CACH;CAEA,AAAU,OAAa;EACtB,KAAK,YAAY,KAAK;GACrB,SAAS,KAAK,QAAQ;GACtB,YAAY,KAAK,UAAU,IAAI,KAAK;GACpC,QAAQ,KAAK,YAAY;GACzB,OAAO,KAAK;EACb,CAAC;CACF;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,aAAsB;EACxC,OAAO,KAAK;CACb;AACD"}
@@ -69,11 +69,7 @@ var SonicDisplayElement = class extends SonicElement {
69
69
  },
70
70
  signal
71
71
  });
72
- this.checkStyles(control, this.sheet, { material: [
73
- "glass",
74
- "pane",
75
- "well"
76
- ] });
72
+ this.checkStyles(control, this.sheet);
77
73
  this.#surface.setFill(this.fill);
78
74
  }
79
75
  surface() {
@@ -1 +1 @@
1
- {"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\n\ndeclare const __DEV__: boolean;\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'fill',\n\t\t'max',\n\t\t'min',\n\t];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\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\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\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 numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'fill', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */\n\tpush(decibels: Float32Array): void {\n\t\tthis.receive(decibels);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\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.#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\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tif (__DEV__) this.checkStyles(control, this.sheet, { material: ['glass', 'pane', 'well'] });\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;AAQA,IAAsB,sBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAC7C,GAAG,aAAa;EAChB;EACA;EACA;CACD;CAEA,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,OAAgB;EACnB,OAAO,KAAK,aAAa,MAAM;CAChC;CAEA,IAAI,KAAK,WAAoB;EAC5B,KAAK,QAAQ,QAAQ,SAAS;CAC/B;CAGA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAcA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,IAAI;EACrD,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,QAAQ,OAAO,KAAK;EACvD,MAAM,kBAAkB;CACzB;;CAGA,KAAK,UAA8B;EAClC,KAAK,QAAQ,QAAQ;EACrB,KAAK,UAAU,aAAa;CAC7B;CAGA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,MAAM;IAAE;IAAS,cAAc,KAAK;GAAa;GACjD,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACY,KAAK,YAAY,SAAS,KAAK,OAAO,EAAE,UAAU;GAAC;GAAS;GAAQ;EAAM,EAAE,CAAC;EAC1F,KAAK,SAAS,QAAQ,KAAK,IAAI;CAChC;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
1
+ {"version":3,"file":"display-element.js","names":[],"sources":["../../../src/elements/display-element.ts"],"sourcesContent":["import type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\n\ndeclare const __DEV__: boolean;\n\nexport abstract class SonicDisplayElement<\n\tColour extends string,\n\tNumeric extends string = never,\n> extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'fill',\n\t\t'max',\n\t\t'min',\n\t];\n\n\tget analyser(): AnalyserNode | undefined {\n\t\treturn this.#analyser;\n\t}\n\n\tset analyser(node: AnalyserNode | null | undefined) {\n\t\tthis.#analyser = node ?? undefined;\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\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\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -90);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\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 numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#analyser: AnalyserNode | undefined;\n\n\t#buffer = new Float32Array(0);\n\n\t#surface: Surface | undefined;\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t\tthis.#surface?.invalidate();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('analyser', 'fill', 'max', 'min');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** One level per bin, as `AnalyserNode.getFloatFrequencyData` fills it */\n\tpush(decibels: Float32Array): void {\n\t\tthis.receive(decibels);\n\t\tthis.#surface?.requestFrame();\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- the wave controls keep their own copy of the surface binding\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.#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\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.#paint(context, frame);\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tif (__DEV__) this.checkStyles(control, this.sheet);\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, never, Numeric>,\n\t): void;\n\n\tprotected abstract pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void;\n\n\tprotected abstract receive(frame: Float32Array): void;\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n\n\t// The node never signals that sound came back, so every paint pulls\n\t#paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, never, Numeric>): void {\n\t\tconst analyser = this.isDisabled() ? undefined : this.#analyser;\n\n\t\tif (analyser) {\n\t\t\tif (this.#buffer.length !== analyser.frequencyBinCount) {\n\t\t\t\tthis.#buffer = new Float32Array(analyser.frequencyBinCount);\n\t\t\t}\n\t\t\tthis.pull(analyser, this.#buffer);\n\t\t\tthis.receive(this.#buffer);\n\t\t}\n\t\tthis.paint(context, frame);\n\t\tif (analyser) this.#surface?.requestFrame();\n\t}\n}\n"],"mappings":";;;;AAQA,IAAsB,sBAAtB,cAGU,aAAa;CACtB,OAAyB,qBAAqB;EAC7C,GAAG,aAAa;EAChB;EACA;EACA;CACD;CAEA,IAAI,WAAqC;EACxC,OAAO,KAAK;CACb;CAEA,IAAI,SAAS,MAAuC;EACnD,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,aAAa;CAC7B;CAGA,IAAI,OAAgB;EACnB,OAAO,KAAK,aAAa,MAAM;CAChC;CAEA,IAAI,KAAK,WAAoB;EAC5B,KAAK,QAAQ,QAAQ,SAAS;CAC/B;CAGA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAcA;CAEA,0BAAU,IAAI,aAAa,CAAC;CAE5B;CAEA,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,IAAI;EACrD,KAAK,UAAU,WAAW;CAC3B;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,YAAY,QAAQ,OAAO,KAAK;EACvD,MAAM,kBAAkB;CACzB;;CAGA,KAAK,UAA8B;EAClC,KAAK,QAAQ,QAAQ;EACrB,KAAK,UAAU,aAAa;CAC7B;CAGA,UAAgB;EACf,KAAK,UAAU,QAAQ;CACxB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,UAAU,KAAK;EAErB,KAAK,YAAY,SAAS,MAAM;EAChC,KAAK,WAAW,YAAY;GAC3B,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,MAAM;IAAE;IAAS,cAAc,KAAK;GAAa;GACjD,SAAS,KAAK;GACd,QAAQ,SAAS,UAAU;IAC1B,KAAK,OAAO,SAAS,KAAK;GAC3B;GACA;EACD,CAAC;EACY,KAAK,YAAY,SAAS,KAAK,KAAK;EACjD,KAAK,SAAS,QAAQ,KAAK,IAAI;CAChC;CAWA,AAAU,UAA+B;EACxC,OAAO,KAAK;CACb;CAGA,OAAO,SAAmC,OAAmD;EAC5F,MAAM,WAAW,KAAK,WAAW,IAAI,SAAY,KAAK;EAEtD,IAAI,UAAU;GACb,IAAI,KAAK,QAAQ,WAAW,SAAS,mBACpC,KAAK,UAAU,IAAI,aAAa,SAAS,iBAAiB;GAE3D,KAAK,KAAK,UAAU,KAAK,OAAO;GAChC,KAAK,QAAQ,KAAK,OAAO;EAC1B;EACA,KAAK,MAAM,SAAS,KAAK;EACzB,IAAI,UAAU,KAAK,UAAU,aAAa;CAC3C;AACD"}
@@ -162,21 +162,8 @@ var SonicEnvelope = class extends SonicElement {
162
162
  this.keepControl(envelope, signal);
163
163
  this.#bind();
164
164
  this.checkStyles(envelope, "envelope.css", {
165
- box: {
166
- property: "margin-bottom",
167
- selector: "[popover]"
168
- },
169
- material: [
170
- "glass",
171
- "pane",
172
- "well",
173
- "readout",
174
- [
175
- "bracket",
176
- ".sonic-envelope-bracket",
177
- "::before"
178
- ]
179
- ]
165
+ property: "margin-bottom",
166
+ selector: "[popover]"
180
167
  });
181
168
  this.#bindPointer(envelope, signal);
182
169
  signal.addEventListener("abort", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"envelope.js","names":[],"sources":["../../../src/elements/envelope.ts"],"sourcesContent":["import type { FieldGesture, FieldHold } from '#elements/field-gesture.ts';\nimport type { ValueLink } from '#elements/value-element.ts';\nimport type { AdsrCurves, AdsrProportions, AdsrShape } from '#lib/adsr-shape.ts';\nimport type { FieldAxis, FieldPoint } from '#lib/field.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { adsrPath, adsrShape, adsrStages, timeStages } from '#lib/adsr-shape.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-envelope': SonicEnvelope;\n\t}\n}\n\ninterface Binding {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tunwatch: () => void;\n}\n\ninterface Drive {\n\taxis: FieldAxis;\n\tbinding: Binding;\n}\n\ninterface HandleHold extends FieldHold {\n\tdrives: Array<Drive>;\n\tisFlipped: boolean;\n\tpart: HTMLElement;\n}\n\nconst curveStages = ['attack', 'decay', 'release'] as const;\n\nconst bindable = [...adsrStages, 'attack-curve', 'decay-curve', 'release-curve'] as const;\n\nconst bindableNames = new Set<string>(bindable);\n\nfunction isParent(node: Node): node is Node & ParentNode {\n\treturn 'querySelector' in node;\n}\n\nconst emptyPath = { fill: '', floor: { from: 0, to: 0 }, stroke: '' };\n\nfunction stageParts<Stage extends string>(\n\tenvelope: Element,\n\thook: string,\n\tstages: ReadonlyArray<Stage>,\n): Map<Stage, HTMLDivElement> {\n\treturn new Map(\n\t\tstages.map((stage) => [\n\t\t\tstage,\n\t\t\trequireChild(envelope, `.${hook}[data-sonic-stage=\"${stage}\"]`, HTMLDivElement),\n\t\t]),\n\t);\n}\n\nconst renderEnvelope = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-envelope\" aria-hidden=\"true\">\n\t\t\t<div class=\"sonic-envelope-bracket\"></div>\n\t\t\t<svg class=\"sonic-envelope-graph\" preserveAspectRatio=\"none\" viewBox=\"0 0 1 1\">\n\t\t\t\t<path class=\"sonic-envelope-fill\" />\n\t\t\t\t<rect class=\"sonic-envelope-fill sonic-envelope-floor\" y=\"1\" />\n\t\t\t\t<path class=\"sonic-envelope-line\" />\n\t\t\t</svg>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"delay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"hold\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-readout\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicEnvelope extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t...bindable,\n\t\t'readout',\n\t];\n\n\tget attack(): string {\n\t\treturn this.getAttribute('attack') ?? '';\n\t}\n\n\tset attack(id: string | undefined) {\n\t\tthis.reflect('attack', id);\n\t}\n\n\tget attackCurve(): string {\n\t\treturn this.getAttribute('attack-curve') ?? '';\n\t}\n\n\tset attackCurve(id: string | undefined) {\n\t\tthis.reflect('attack-curve', id);\n\t}\n\n\tget decay(): string {\n\t\treturn this.getAttribute('decay') ?? '';\n\t}\n\n\tset decay(id: string | undefined) {\n\t\tthis.reflect('decay', id);\n\t}\n\n\tget decayCurve(): string {\n\t\treturn this.getAttribute('decay-curve') ?? '';\n\t}\n\n\tset decayCurve(id: string | undefined) {\n\t\tthis.reflect('decay-curve', id);\n\t}\n\n\tget delay(): string {\n\t\treturn this.getAttribute('delay') ?? '';\n\t}\n\n\tset delay(id: string | undefined) {\n\t\tthis.reflect('delay', id);\n\t}\n\n\tget hold(): string {\n\t\treturn this.getAttribute('hold') ?? '';\n\t}\n\n\tset hold(id: string | undefined) {\n\t\tthis.reflect('hold', id);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isOn: boolean) {\n\t\tthis.reflect('readout', isOn);\n\t}\n\n\tget release(): string {\n\t\treturn this.getAttribute('release') ?? '';\n\t}\n\n\tset release(id: string | undefined) {\n\t\tthis.reflect('release', id);\n\t}\n\n\tget releaseCurve(): string {\n\t\treturn this.getAttribute('release-curve') ?? '';\n\t}\n\n\tset releaseCurve(id: string | undefined) {\n\t\tthis.reflect('release-curve', id);\n\t}\n\n\tget sustain(): string {\n\t\treturn this.getAttribute('sustain') ?? '';\n\t}\n\n\tset sustain(id: string | undefined) {\n\t\tthis.reflect('sustain', id);\n\t}\n\n\treadonly #bindings = new Map<string, Binding>();\n\n\treadonly #envelope = renderEnvelope();\n\n\treadonly #bracket = requireChild(this.#envelope, '.sonic-envelope-bracket', HTMLDivElement);\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\treadonly #curveHandles = stageParts(this.#envelope, 'sonic-envelope-curve', curveStages);\n\n\treadonly #fill = requireChild(this.#envelope, '.sonic-envelope-fill', SVGElement);\n\n\treadonly #floor = requireChild(this.#envelope, '.sonic-envelope-floor', SVGElement);\n\n\t#gesture: FieldGesture<HandleHold> | undefined;\n\n\treadonly #graph = requireChild(this.#envelope, '.sonic-envelope-graph', SVGElement);\n\n\treadonly #handles = stageParts(this.#envelope, 'sonic-envelope-handle', timeStages);\n\n\t#held: HTMLElement | undefined;\n\n\t#isDriving = false;\n\n\treadonly #line = requireChild(this.#envelope, '.sonic-envelope-line', SVGElement);\n\n\treadonly #readout = new Readout(\n\t\trequireChild(this.#envelope, '.sonic-envelope-readout', HTMLDivElement),\n\t);\n\n\t#shape: AdsrShape = adsrShape({});\n\n\t#signal: AbortSignal | undefined;\n\n\treadonly #waiting = new Set<unknown>();\n\n\treadonly #warned = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (!this.#signal) return;\n\t\tif (name === 'disabled') {\n\t\t\tthis.#gesture?.end();\n\t\t\tthis.#draw();\n\t\t\treturn;\n\t\t}\n\t\tif (bindableNames.has(name)) this.#bind();\n\t\telse this.#renderReadout();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(...adsrStages, 'attackCurve', 'decayCurve', 'readout', 'releaseCurve');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst envelope = this.#envelope;\n\n\t\tthis.#signal = signal;\n\t\tthis.keepControl(envelope, signal);\n\t\tthis.#bind();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(envelope, 'envelope.css', {\n\t\t\t\tbox: { property: 'margin-bottom', selector: '[popover]' },\n\t\t\t\tmaterial: [\n\t\t\t\t\t'glass',\n\t\t\t\t\t'pane',\n\t\t\t\t\t'well',\n\t\t\t\t\t'readout',\n\t\t\t\t\t['bracket', '.sonic-envelope-bracket', '::before'],\n\t\t\t\t],\n\t\t\t});\n\t\tthis.#bindPointer(envelope, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#signal = undefined;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bind(): void {\n\t\tthis.#gesture?.end();\n\t\tthis.#unbind();\n\t\tfor (const name of bindable) {\n\t\t\tconst element = this.#resolve(name);\n\t\t\tif (!element) continue;\n\n\t\t\tconst link = linkValue(element);\n\t\t\tconst unwatch = link.model.watch(() => {\n\t\t\t\tif (!this.#isDriving) this.#draw();\n\t\t\t});\n\n\t\t\tthis.#bindings.set(name, { element, link, unwatch });\n\t\t}\n\t\tthis.#draw();\n\t}\n\n\t#bindPointer(envelope: HTMLElement, signal: AbortSignal): void {\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\tenvelope,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tinput: ({ drives }, next) => {\n\t\t\t\t\tthis.#isDriving = true;\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tconst value = next[axis];\n\n\t\t\t\t\t\tif (value !== undefined) binding.link.input(value);\n\t\t\t\t\t}\n\t\t\t\t\tthis.#isDriving = false;\n\t\t\t\t\tthis.#draw();\n\t\t\t\t},\n\t\t\t\trelease: ({ drives }, moved) => {\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tif (!moved.includes(axis)) continue;\n\n\t\t\t\t\t\tbinding.element.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#curveDrives(stage: string): Array<Drive> {\n\t\tconst binding = this.#bindings.get(`${stage}-curve`);\n\n\t\treturn binding ? [{ axis: 'y', binding }] : [];\n\t}\n\n\t#draw(): void {\n\t\tconst shape = this.#readShape();\n\t\tconst path = this.#bindings.size === 0 ? emptyPath : adsrPath(shape);\n\n\t\tthis.#shape = shape;\n\t\twriteAttribute(this.#line, 'd', path.stroke);\n\t\twriteAttribute(this.#fill, 'd', path.fill);\n\t\twriteAttribute(this.#floor, 'x', String(path.floor.from));\n\t\twriteAttribute(this.#floor, 'width', String(path.floor.to - path.floor.from));\n\t\tfor (const [stage, part] of this.#handles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.handles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#drives(stage),\n\t\t\t});\n\t\t}\n\t\tfor (const [stage, part] of this.#curveHandles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.curveHandles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#curveDrives(stage),\n\t\t\t});\n\t\t}\n\t\tthis.#drawBracket();\n\t\tthis.#renderReadout();\n\t}\n\n\t#drawBracket(): void {\n\t\tconst part = this.#held;\n\t\tif (!part) return;\n\n\t\tfor (const name of ['--_sonic-envelope-x', '--_sonic-envelope-y']) {\n\t\t\tthis.#bracket.style.setProperty(name, part.style.getPropertyValue(name));\n\t\t}\n\t\twriteAttribute(\n\t\t\tthis.#bracket,\n\t\t\t'data-sonic-part',\n\t\t\tpart.classList.contains('sonic-envelope-curve') ? 'curve' : undefined,\n\t\t);\n\t}\n\n\t#drawPoint(\n\t\tpart: HTMLDivElement,\n\t\tpoint: { at: FieldPoint | undefined; drives: Array<Drive> },\n\t): void {\n\t\tconst { at, drives } = point;\n\n\t\twriteAttribute(part, 'hidden', at ? undefined : '');\n\t\tif (!at) return;\n\n\t\tconst isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());\n\n\t\tpart.style.setProperty('--_sonic-envelope-x', String(at.x));\n\t\tpart.style.setProperty('--_sonic-envelope-y', String(at.y));\n\t\twriteAttribute(part, 'data-sonic-axes', drives.map(({ axis }) => axis).join(''));\n\t\twriteAttribute(part, 'data-sonic-disabled', isStill ? '' : undefined);\n\t}\n\n\t#drives(stage: string): Array<Drive> {\n\t\tconst x = this.#bindings.get(stage);\n\t\tconst y = stage === 'decay' ? this.#bindings.get('sustain') : undefined;\n\n\t\treturn [\n\t\t\t...(x ? [{ axis: 'x' as const, binding: x }] : []),\n\t\t\t...(y ? [{ axis: 'y' as const, binding: y }] : []),\n\t\t];\n\t}\n\n\t#grab(event: PointerEvent): HandleHold | undefined {\n\t\tconst pressed = this.#pressed(event);\n\t\tif (!pressed || pressed.drives.length === 0) return undefined;\n\n\t\tthis.#held = pressed.part;\n\t\tthis.#drawBracket();\n\n\t\tconst box = this.#graph.getBoundingClientRect();\n\t\tconst travel = { x: Math.max(1, box.width * this.#shape.share), y: Math.max(1, box.height) };\n\t\tconst axes: HandleHold['axes'] = {};\n\n\t\tfor (const { axis, binding } of pressed.drives) {\n\t\t\taxes[axis] = { model: binding.link.model, travelPx: travel[axis] };\n\t\t}\n\n\t\treturn { ...pressed, axes };\n\t}\n\n\t#pressed(event: PointerEvent): Omit<HandleHold, 'axes'> | undefined {\n\t\tconst part =\n\t\t\tevent.target instanceof Element\n\t\t\t\t? event.target.closest<HTMLElement>('.sonic-envelope-handle, .sonic-envelope-curve')\n\t\t\t\t: undefined;\n\t\tconst stage = part?.dataset.sonicStage;\n\t\tif (!part || stage === undefined || this.isDisabled()) return undefined;\n\n\t\tconst isCurveHandle = part.classList.contains('sonic-envelope-curve');\n\t\tconst drives = isCurveHandle ? this.#curveDrives(stage) : this.#drives(stage);\n\n\t\treturn {\n\t\t\tdrives: drives.filter(({ binding }) => !binding.link.isDisabled()),\n\t\t\tisFlipped: isCurveHandle && stage === 'attack',\n\t\t\tpart,\n\t\t};\n\t}\n\n\t#readShape(): AdsrShape {\n\t\tconst proportions: AdsrProportions = {};\n\t\tconst curves: AdsrCurves = {};\n\n\t\tfor (const stage of adsrStages) {\n\t\t\tconst model = this.#bindings.get(stage)?.link.model;\n\n\t\t\tproportions[stage] = model?.mapping().proportionOf(model.value);\n\t\t}\n\t\tfor (const stage of curveStages) {\n\t\t\tcurves[stage] = this.#bindings.get(`${stage}-curve`)?.link.model.value;\n\t\t}\n\n\t\treturn adsrShape(proportions, curves);\n\t}\n\n\t#rebindOnce(key: unknown, arrives: (rebind: () => void) => void): void {\n\t\tif (this.#waiting.has(key)) return;\n\n\t\tthis.#waiting.add(key);\n\t\tarrives(() => {\n\t\t\tthis.#waiting.delete(key);\n\t\t\tif (this.#signal) this.#bind();\n\t\t});\n\t}\n\n\t#renderReadout(): void {\n\t\tconst drag = this.#gesture?.current();\n\n\t\tif (!drag) {\n\t\t\tthis.#readout.close();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readout.show({\n\t\t\tanchor: drag.part,\n\t\t\tisOpen: this.#claim.isRevealed && this.readout,\n\t\t\ttext: drag.drives.map(({ binding }) => binding.element.valueText).join(', '),\n\t\t});\n\t}\n\n\t#resolve(name: string): SonicValueElement | undefined {\n\t\tconst id = this.getAttribute(name);\n\t\tconst root = this.getRootNode();\n\t\tif (!id || !isParent(root)) return undefined;\n\n\t\tconst found = root.querySelector(`#${CSS.escape(id)}`);\n\t\tif (found instanceof SonicValueElement) return found;\n\n\t\tthis.#wait(id, found);\n\n\t\treturn undefined;\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { unwatch } of this.#bindings.values()) unwatch();\n\n\t\tthis.#bindings.clear();\n\t}\n\n\t#wait(id: string, found: Element | null): void {\n\t\tconst tag = found?.localName;\n\t\tconst { ownerDocument } = this;\n\n\t\tif (tag?.includes('-') && !customElements.get(tag)) {\n\t\t\tthis.#rebindOnce(tag, (rebind) => {\n\t\t\t\tvoid customElements.whenDefined(tag).then(rebind);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (!found && ownerDocument.readyState === 'loading') {\n\t\t\tthis.#rebindOnce(ownerDocument, (rebind) => {\n\t\t\t\townerDocument.addEventListener('DOMContentLoaded', rebind, { once: true });\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#warn(id);\n\t}\n\n\t#warn(id: string): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#warned.has(id)) return;\n\n\t\tthis.#warned.add(id);\n\t\tconsole.warn(`<sonic-envelope> found no dial, slider or number box with the id \"${id}\"`);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"envelope.js","names":[],"sources":["../../../src/elements/envelope.ts"],"sourcesContent":["import type { FieldGesture, FieldHold } from '#elements/field-gesture.ts';\nimport type { ValueLink } from '#elements/value-element.ts';\nimport type { AdsrCurves, AdsrProportions, AdsrShape } from '#lib/adsr-shape.ts';\nimport type { FieldAxis, FieldPoint } from '#lib/field.ts';\n\nimport { bindFieldGesture } from '#elements/field-gesture.ts';\nimport { ReadoutClaim } from '#elements/readout-claim.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { adsrPath, adsrShape, adsrStages, timeStages } from '#lib/adsr-shape.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-envelope': SonicEnvelope;\n\t}\n}\n\ninterface Binding {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tunwatch: () => void;\n}\n\ninterface Drive {\n\taxis: FieldAxis;\n\tbinding: Binding;\n}\n\ninterface HandleHold extends FieldHold {\n\tdrives: Array<Drive>;\n\tisFlipped: boolean;\n\tpart: HTMLElement;\n}\n\nconst curveStages = ['attack', 'decay', 'release'] as const;\n\nconst bindable = [...adsrStages, 'attack-curve', 'decay-curve', 'release-curve'] as const;\n\nconst bindableNames = new Set<string>(bindable);\n\nfunction isParent(node: Node): node is Node & ParentNode {\n\treturn 'querySelector' in node;\n}\n\nconst emptyPath = { fill: '', floor: { from: 0, to: 0 }, stroke: '' };\n\nfunction stageParts<Stage extends string>(\n\tenvelope: Element,\n\thook: string,\n\tstages: ReadonlyArray<Stage>,\n): Map<Stage, HTMLDivElement> {\n\treturn new Map(\n\t\tstages.map((stage) => [\n\t\t\tstage,\n\t\t\trequireChild(envelope, `.${hook}[data-sonic-stage=\"${stage}\"]`, HTMLDivElement),\n\t\t]),\n\t);\n}\n\nconst renderEnvelope = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-envelope\" aria-hidden=\"true\">\n\t\t\t<div class=\"sonic-envelope-bracket\"></div>\n\t\t\t<svg class=\"sonic-envelope-graph\" preserveAspectRatio=\"none\" viewBox=\"0 0 1 1\">\n\t\t\t\t<path class=\"sonic-envelope-fill\" />\n\t\t\t\t<rect class=\"sonic-envelope-fill sonic-envelope-floor\" y=\"1\" />\n\t\t\t\t<path class=\"sonic-envelope-line\" />\n\t\t\t</svg>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"delay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"hold\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-handle\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"attack\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"decay\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-curve\" data-sonic-stage=\"release\" hidden></div>\n\t\t\t<div class=\"sonic-envelope-readout\" popover=\"manual\"><span></span></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicEnvelope extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t...bindable,\n\t\t'readout',\n\t];\n\n\tget attack(): string {\n\t\treturn this.getAttribute('attack') ?? '';\n\t}\n\n\tset attack(id: string | undefined) {\n\t\tthis.reflect('attack', id);\n\t}\n\n\tget attackCurve(): string {\n\t\treturn this.getAttribute('attack-curve') ?? '';\n\t}\n\n\tset attackCurve(id: string | undefined) {\n\t\tthis.reflect('attack-curve', id);\n\t}\n\n\tget decay(): string {\n\t\treturn this.getAttribute('decay') ?? '';\n\t}\n\n\tset decay(id: string | undefined) {\n\t\tthis.reflect('decay', id);\n\t}\n\n\tget decayCurve(): string {\n\t\treturn this.getAttribute('decay-curve') ?? '';\n\t}\n\n\tset decayCurve(id: string | undefined) {\n\t\tthis.reflect('decay-curve', id);\n\t}\n\n\tget delay(): string {\n\t\treturn this.getAttribute('delay') ?? '';\n\t}\n\n\tset delay(id: string | undefined) {\n\t\tthis.reflect('delay', id);\n\t}\n\n\tget hold(): string {\n\t\treturn this.getAttribute('hold') ?? '';\n\t}\n\n\tset hold(id: string | undefined) {\n\t\tthis.reflect('hold', id);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget pointerType(): string | undefined {\n\t\treturn this.#gesture?.pointerType();\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isOn: boolean) {\n\t\tthis.reflect('readout', isOn);\n\t}\n\n\tget release(): string {\n\t\treturn this.getAttribute('release') ?? '';\n\t}\n\n\tset release(id: string | undefined) {\n\t\tthis.reflect('release', id);\n\t}\n\n\tget releaseCurve(): string {\n\t\treturn this.getAttribute('release-curve') ?? '';\n\t}\n\n\tset releaseCurve(id: string | undefined) {\n\t\tthis.reflect('release-curve', id);\n\t}\n\n\tget sustain(): string {\n\t\treturn this.getAttribute('sustain') ?? '';\n\t}\n\n\tset sustain(id: string | undefined) {\n\t\tthis.reflect('sustain', id);\n\t}\n\n\treadonly #bindings = new Map<string, Binding>();\n\n\treadonly #envelope = renderEnvelope();\n\n\treadonly #bracket = requireChild(this.#envelope, '.sonic-envelope-bracket', HTMLDivElement);\n\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#renderReadout();\n\t});\n\n\treadonly #curveHandles = stageParts(this.#envelope, 'sonic-envelope-curve', curveStages);\n\n\treadonly #fill = requireChild(this.#envelope, '.sonic-envelope-fill', SVGElement);\n\n\treadonly #floor = requireChild(this.#envelope, '.sonic-envelope-floor', SVGElement);\n\n\t#gesture: FieldGesture<HandleHold> | undefined;\n\n\treadonly #graph = requireChild(this.#envelope, '.sonic-envelope-graph', SVGElement);\n\n\treadonly #handles = stageParts(this.#envelope, 'sonic-envelope-handle', timeStages);\n\n\t#held: HTMLElement | undefined;\n\n\t#isDriving = false;\n\n\treadonly #line = requireChild(this.#envelope, '.sonic-envelope-line', SVGElement);\n\n\treadonly #readout = new Readout(\n\t\trequireChild(this.#envelope, '.sonic-envelope-readout', HTMLDivElement),\n\t);\n\n\t#shape: AdsrShape = adsrShape({});\n\n\t#signal: AbortSignal | undefined;\n\n\treadonly #waiting = new Set<unknown>();\n\n\treadonly #warned = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (!this.#signal) return;\n\t\tif (name === 'disabled') {\n\t\t\tthis.#gesture?.end();\n\t\t\tthis.#draw();\n\t\t\treturn;\n\t\t}\n\t\tif (bindableNames.has(name)) this.#bind();\n\t\telse this.#renderReadout();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(...adsrStages, 'attackCurve', 'decayCurve', 'readout', 'releaseCurve');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst envelope = this.#envelope;\n\n\t\tthis.#signal = signal;\n\t\tthis.keepControl(envelope, signal);\n\t\tthis.#bind();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(envelope, 'envelope.css', {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#bindPointer(envelope, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#signal = undefined;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#bind(): void {\n\t\tthis.#gesture?.end();\n\t\tthis.#unbind();\n\t\tfor (const name of bindable) {\n\t\t\tconst element = this.#resolve(name);\n\t\t\tif (!element) continue;\n\n\t\t\tconst link = linkValue(element);\n\t\t\tconst unwatch = link.model.watch(() => {\n\t\t\t\tif (!this.#isDriving) this.#draw();\n\t\t\t});\n\n\t\t\tthis.#bindings.set(name, { element, link, unwatch });\n\t\t}\n\t\tthis.#draw();\n\t}\n\n\t#bindPointer(envelope: HTMLElement, signal: AbortSignal): void {\n\t\tthis.#gesture = bindFieldGesture(\n\t\t\tenvelope,\n\t\t\t{\n\t\t\t\tclaim: this.#claim,\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tinput: ({ drives }, next) => {\n\t\t\t\t\tthis.#isDriving = true;\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tconst value = next[axis];\n\n\t\t\t\t\t\tif (value !== undefined) binding.link.input(value);\n\t\t\t\t\t}\n\t\t\t\t\tthis.#isDriving = false;\n\t\t\t\t\tthis.#draw();\n\t\t\t\t},\n\t\t\t\trelease: ({ drives }, moved) => {\n\t\t\t\t\tfor (const { axis, binding } of drives) {\n\t\t\t\t\t\tif (!moved.includes(axis)) continue;\n\n\t\t\t\t\t\tbinding.element.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\tthis.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#curveDrives(stage: string): Array<Drive> {\n\t\tconst binding = this.#bindings.get(`${stage}-curve`);\n\n\t\treturn binding ? [{ axis: 'y', binding }] : [];\n\t}\n\n\t#draw(): void {\n\t\tconst shape = this.#readShape();\n\t\tconst path = this.#bindings.size === 0 ? emptyPath : adsrPath(shape);\n\n\t\tthis.#shape = shape;\n\t\twriteAttribute(this.#line, 'd', path.stroke);\n\t\twriteAttribute(this.#fill, 'd', path.fill);\n\t\twriteAttribute(this.#floor, 'x', String(path.floor.from));\n\t\twriteAttribute(this.#floor, 'width', String(path.floor.to - path.floor.from));\n\t\tfor (const [stage, part] of this.#handles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.handles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#drives(stage),\n\t\t\t});\n\t\t}\n\t\tfor (const [stage, part] of this.#curveHandles) {\n\t\t\tthis.#drawPoint(part, {\n\t\t\t\tat: shape.curveHandles.find((point) => point.stage === stage),\n\t\t\t\tdrives: this.#curveDrives(stage),\n\t\t\t});\n\t\t}\n\t\tthis.#drawBracket();\n\t\tthis.#renderReadout();\n\t}\n\n\t#drawBracket(): void {\n\t\tconst part = this.#held;\n\t\tif (!part) return;\n\n\t\tfor (const name of ['--_sonic-envelope-x', '--_sonic-envelope-y']) {\n\t\t\tthis.#bracket.style.setProperty(name, part.style.getPropertyValue(name));\n\t\t}\n\t\twriteAttribute(\n\t\t\tthis.#bracket,\n\t\t\t'data-sonic-part',\n\t\t\tpart.classList.contains('sonic-envelope-curve') ? 'curve' : undefined,\n\t\t);\n\t}\n\n\t#drawPoint(\n\t\tpart: HTMLDivElement,\n\t\tpoint: { at: FieldPoint | undefined; drives: Array<Drive> },\n\t): void {\n\t\tconst { at, drives } = point;\n\n\t\twriteAttribute(part, 'hidden', at ? undefined : '');\n\t\tif (!at) return;\n\n\t\tconst isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());\n\n\t\tpart.style.setProperty('--_sonic-envelope-x', String(at.x));\n\t\tpart.style.setProperty('--_sonic-envelope-y', String(at.y));\n\t\twriteAttribute(part, 'data-sonic-axes', drives.map(({ axis }) => axis).join(''));\n\t\twriteAttribute(part, 'data-sonic-disabled', isStill ? '' : undefined);\n\t}\n\n\t#drives(stage: string): Array<Drive> {\n\t\tconst x = this.#bindings.get(stage);\n\t\tconst y = stage === 'decay' ? this.#bindings.get('sustain') : undefined;\n\n\t\treturn [\n\t\t\t...(x ? [{ axis: 'x' as const, binding: x }] : []),\n\t\t\t...(y ? [{ axis: 'y' as const, binding: y }] : []),\n\t\t];\n\t}\n\n\t#grab(event: PointerEvent): HandleHold | undefined {\n\t\tconst pressed = this.#pressed(event);\n\t\tif (!pressed || pressed.drives.length === 0) return undefined;\n\n\t\tthis.#held = pressed.part;\n\t\tthis.#drawBracket();\n\n\t\tconst box = this.#graph.getBoundingClientRect();\n\t\tconst travel = { x: Math.max(1, box.width * this.#shape.share), y: Math.max(1, box.height) };\n\t\tconst axes: HandleHold['axes'] = {};\n\n\t\tfor (const { axis, binding } of pressed.drives) {\n\t\t\taxes[axis] = { model: binding.link.model, travelPx: travel[axis] };\n\t\t}\n\n\t\treturn { ...pressed, axes };\n\t}\n\n\t#pressed(event: PointerEvent): Omit<HandleHold, 'axes'> | undefined {\n\t\tconst part =\n\t\t\tevent.target instanceof Element\n\t\t\t\t? event.target.closest<HTMLElement>('.sonic-envelope-handle, .sonic-envelope-curve')\n\t\t\t\t: undefined;\n\t\tconst stage = part?.dataset.sonicStage;\n\t\tif (!part || stage === undefined || this.isDisabled()) return undefined;\n\n\t\tconst isCurveHandle = part.classList.contains('sonic-envelope-curve');\n\t\tconst drives = isCurveHandle ? this.#curveDrives(stage) : this.#drives(stage);\n\n\t\treturn {\n\t\t\tdrives: drives.filter(({ binding }) => !binding.link.isDisabled()),\n\t\t\tisFlipped: isCurveHandle && stage === 'attack',\n\t\t\tpart,\n\t\t};\n\t}\n\n\t#readShape(): AdsrShape {\n\t\tconst proportions: AdsrProportions = {};\n\t\tconst curves: AdsrCurves = {};\n\n\t\tfor (const stage of adsrStages) {\n\t\t\tconst model = this.#bindings.get(stage)?.link.model;\n\n\t\t\tproportions[stage] = model?.mapping().proportionOf(model.value);\n\t\t}\n\t\tfor (const stage of curveStages) {\n\t\t\tcurves[stage] = this.#bindings.get(`${stage}-curve`)?.link.model.value;\n\t\t}\n\n\t\treturn adsrShape(proportions, curves);\n\t}\n\n\t#rebindOnce(key: unknown, arrives: (rebind: () => void) => void): void {\n\t\tif (this.#waiting.has(key)) return;\n\n\t\tthis.#waiting.add(key);\n\t\tarrives(() => {\n\t\t\tthis.#waiting.delete(key);\n\t\t\tif (this.#signal) this.#bind();\n\t\t});\n\t}\n\n\t#renderReadout(): void {\n\t\tconst drag = this.#gesture?.current();\n\n\t\tif (!drag) {\n\t\t\tthis.#readout.close();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readout.show({\n\t\t\tanchor: drag.part,\n\t\t\tisOpen: this.#claim.isRevealed && this.readout,\n\t\t\ttext: drag.drives.map(({ binding }) => binding.element.valueText).join(', '),\n\t\t});\n\t}\n\n\t#resolve(name: string): SonicValueElement | undefined {\n\t\tconst id = this.getAttribute(name);\n\t\tconst root = this.getRootNode();\n\t\tif (!id || !isParent(root)) return undefined;\n\n\t\tconst found = root.querySelector(`#${CSS.escape(id)}`);\n\t\tif (found instanceof SonicValueElement) return found;\n\n\t\tthis.#wait(id, found);\n\n\t\treturn undefined;\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { unwatch } of this.#bindings.values()) unwatch();\n\n\t\tthis.#bindings.clear();\n\t}\n\n\t#wait(id: string, found: Element | null): void {\n\t\tconst tag = found?.localName;\n\t\tconst { ownerDocument } = this;\n\n\t\tif (tag?.includes('-') && !customElements.get(tag)) {\n\t\t\tthis.#rebindOnce(tag, (rebind) => {\n\t\t\t\tvoid customElements.whenDefined(tag).then(rebind);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (!found && ownerDocument.readyState === 'loading') {\n\t\t\tthis.#rebindOnce(ownerDocument, (rebind) => {\n\t\t\t\townerDocument.addEventListener('DOMContentLoaded', rebind, { once: true });\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#warn(id);\n\t}\n\n\t#warn(id: string): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#warned.has(id)) return;\n\n\t\tthis.#warned.add(id);\n\t\tconsole.warn(`<sonic-envelope> found no dial, slider or number box with the id \"${id}\"`);\n\t}\n}\n"],"mappings":"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"}
@@ -53,6 +53,9 @@ function bindFieldGesture(target, options, signal) {
53
53
  })
54
54
  };
55
55
  },
56
+ lift: ({ hold }, event) => {
57
+ options.lift?.(hold, event);
58
+ },
56
59
  move: (held, event) => {
57
60
  const { axes } = held.hold;
58
61
  const step = stepFieldDrag({
@@ -1 +1 @@
1
- {"version":3,"file":"field-gesture.js","names":[],"sources":["../../../src/elements/field-gesture.ts"],"sourcesContent":["import type { ReadoutClaim } from '#elements/readout-claim.ts';\nimport type { FieldAxis, FieldDragState, FieldPoint } from '#lib/field.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { revealDelay } from '#elements/readout-claim.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from '#lib/field.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\n\ninterface FieldAxisHold {\n\tfrom?: number;\n\tmodel: ValueModel;\n\tproportion?: number;\n\ttravelPx: number;\n}\n\nexport interface FieldHold {\n\taxes: Partial<Record<FieldAxis, FieldAxisHold>>;\n\tisFlipped?: boolean;\n}\n\nexport interface FieldInput {\n\tx?: number | undefined;\n\ty?: number | undefined;\n}\n\ninterface FieldGestureOptions<Hold extends FieldHold> {\n\tclaim: ReadoutClaim;\n\tgrab: (event: PointerEvent) => Hold | undefined;\n\tinput: (hold: Hold, next: FieldInput) => void;\n\trelease: (hold: Hold, moved: Array<FieldAxis>) => void;\n\ttoggle: (isDragging: boolean) => void;\n}\n\nexport interface FieldGesture<Hold> {\n\tcurrent: () => Hold | undefined;\n\tend: () => void;\n\tpointerType: () => string | undefined;\n}\n\ninterface Held<Hold> {\n\tfrom: FieldInput;\n\thold: Hold;\n\tstate: FieldDragState;\n}\n\nconst fieldAxes = ['x', 'y'] as const;\n\nfunction flip<Point extends FieldPoint>(point: Point, hold: FieldHold): Point {\n\treturn hold.isFlipped === true ? { ...point, y: -point.y } : point;\n}\n\nfunction startAxis(hold: FieldAxisHold | undefined) {\n\tif (!hold) return;\n\n\tconst { model, travelPx } = hold;\n\tconst mapping = model.mapping();\n\tconst from = model.value;\n\n\treturn { from, mapping, proportion: hold.proportion ?? mapping.proportionOf(from), travelPx };\n}\n\nexport function bindFieldGesture<Hold extends FieldHold>(\n\ttarget: HTMLElement,\n\toptions: FieldGestureOptions<Hold>,\n\tsignal: AbortSignal,\n): FieldGesture<Hold> {\n\tconst { claim } = options;\n\n\tlet pointerType: string | undefined;\n\n\tconst drag = bindDrag<Held<Hold>>(\n\t\ttarget,\n\t\t{\n\t\t\tcancel: ({ from, hold }) => {\n\t\t\t\toptions.input(hold, from);\n\t\t\t},\n\t\t\tgrab: (event) => {\n\t\t\t\t// Set first, so an `input` the grab itself fires can read it\n\t\t\t\tpointerType = event.pointerType;\n\n\t\t\t\tconst hold = options.grab(event);\n\t\t\t\tif (!hold) {\n\t\t\t\t\tpointerType = undefined;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { x, y } = hold.axes;\n\n\t\t\t\tclaim.press(true, revealDelay(target));\n\n\t\t\t\treturn {\n\t\t\t\t\tfrom: { x: x?.from ?? x?.model.value, y: y?.from ?? y?.model.value },\n\t\t\t\t\thold,\n\t\t\t\t\tstate: startFieldDrag({\n\t\t\t\t\t\tposition: flip(pointerPosition(event), hold),\n\t\t\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\t\t\tx: startAxis(x),\n\t\t\t\t\t\ty: startAxis(y),\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t},\n\t\t\tmove: (held, event) => {\n\t\t\t\tconst { axes } = held.hold;\n\t\t\t\tconst step = stepFieldDrag(\n\t\t\t\t\t{ x: axes.x?.model.mapping(), y: axes.y?.model.mapping() },\n\t\t\t\t\theld.state,\n\t\t\t\t\tflip(pointerMove(event), held.hold),\n\t\t\t\t);\n\n\t\t\t\theld.state = step.state;\n\t\t\t\tif (step.state.isEngaged) claim.reveal('drag');\n\t\t\t\toptions.input(held.hold, step);\n\t\t\t},\n\t\t\trelease: ({ from, hold }) => {\n\t\t\t\tclaim.press(false);\n\t\t\t\toptions.release(\n\t\t\t\t\thold,\n\t\t\t\t\tfieldAxes.filter((axis) => hold.axes[axis]?.model.value !== from[axis]),\n\t\t\t\t);\n\t\t\t\tpointerType = undefined;\n\t\t\t},\n\t\t\ttoggle: options.toggle,\n\t\t},\n\t\tsignal,\n\t);\n\n\treturn { current: () => drag.current()?.hold, end: drag.end, pointerType: () => pointerType };\n}\n"],"mappings":";;;;;;AA8CA,MAAM,YAAY,CAAC,KAAK,GAAG;AAE3B,SAAS,KAA+B,OAAc,MAAwB;CAC7E,OAAO,KAAK,cAAc,OAAO;EAAE,GAAG;EAAO,GAAG,CAAC,MAAM;CAAE,IAAI;AAC9D;AAEA,SAAS,UAAU,MAAiC;CACnD,IAAI,CAAC,MAAM;CAEX,MAAM,EAAE,OAAO,aAAa;CAC5B,MAAM,UAAU,MAAM,QAAQ;CAC9B,MAAM,OAAO,MAAM;CAEnB,OAAO;EAAE;EAAM;EAAS,YAAY,KAAK,cAAc,QAAQ,aAAa,IAAI;EAAG;CAAS;AAC7F;AAEA,SAAgB,iBACf,QACA,SACA,QACqB;CACrB,MAAM,EAAE,UAAU;CAElB,IAAI;CAEJ,MAAM,OAAO,SACZ,QACA;EACC,SAAS,EAAE,MAAM,WAAW;GAC3B,QAAQ,MAAM,MAAM,IAAI;EACzB;EACA,OAAO,UAAU;GAEhB,cAAc,MAAM;GAEpB,MAAM,OAAO,QAAQ,KAAK,KAAK;GAC/B,IAAI,CAAC,MAAM;IACV,cAAc;IACd;GACD;GAEA,MAAM,EAAE,GAAG,MAAM,KAAK;GAEtB,MAAM,MAAM,MAAM,YAAY,MAAM,CAAC;GAErC,OAAO;IACN,MAAM;KAAE,GAAG,GAAG,QAAQ,GAAG,MAAM;KAAO,GAAG,GAAG,QAAQ,GAAG,MAAM;IAAM;IACnE;IACA,OAAO,eAAe;KACrB,UAAU,KAAK,gBAAgB,KAAK,GAAG,IAAI;KAC3C,aAAa,gBAAgB,MAAM,WAAW;KAC9C,GAAG,UAAU,CAAC;KACd,GAAG,UAAU,CAAC;IACf,CAAC;GACF;EACD;EACA,OAAO,MAAM,UAAU;GACtB,MAAM,EAAE,SAAS,KAAK;GACtB,MAAM,OAAO,cACZ;IAAE,GAAG,KAAK,GAAG,MAAM,QAAQ;IAAG,GAAG,KAAK,GAAG,MAAM,QAAQ;GAAE,GACzD,KAAK,OACL,KAAK,YAAY,KAAK,GAAG,KAAK,IAAI,CACnC;GAEA,KAAK,QAAQ,KAAK;GAClB,IAAI,KAAK,MAAM,WAAW,MAAM,OAAO,MAAM;GAC7C,QAAQ,MAAM,KAAK,MAAM,IAAI;EAC9B;EACA,UAAU,EAAE,MAAM,WAAW;GAC5B,MAAM,MAAM,KAAK;GACjB,QAAQ,QACP,MACA,UAAU,QAAQ,SAAS,KAAK,KAAK,KAAK,EAAE,MAAM,UAAU,KAAK,KAAK,CACvE;GACA,cAAc;EACf;EACA,QAAQ,QAAQ;CACjB,GACA,MACD;CAEA,OAAO;EAAE,eAAe,KAAK,QAAQ,CAAC,EAAE;EAAM,KAAK,KAAK;EAAK,mBAAmB;CAAY;AAC7F"}
1
+ {"version":3,"file":"field-gesture.js","names":[],"sources":["../../../src/elements/field-gesture.ts"],"sourcesContent":["import type { ReadoutClaim } from '#elements/readout-claim.ts';\nimport type { FieldAxis, FieldDragState, FieldPoint } from '#lib/field.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { revealDelay } from '#elements/readout-claim.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from '#lib/field.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\n\ninterface FieldAxisHold {\n\tfrom?: number;\n\tmodel: ValueModel;\n\tproportion?: number;\n\ttravelPx: number;\n}\n\nexport interface FieldHold {\n\taxes: Partial<Record<FieldAxis, FieldAxisHold>>;\n\tisFlipped?: boolean;\n}\n\nexport interface FieldInput {\n\tx?: number | undefined;\n\ty?: number | undefined;\n}\n\ninterface FieldGestureOptions<Hold extends FieldHold> {\n\tclaim: ReadoutClaim;\n\tgrab: (event: PointerEvent) => Hold | undefined;\n\tinput: (hold: Hold, next: FieldInput) => void;\n\tlift?: (hold: Hold, event: PointerEvent) => void;\n\trelease: (hold: Hold, moved: Array<FieldAxis>) => void;\n\ttoggle: (isDragging: boolean) => void;\n}\n\nexport interface FieldGesture<Hold> {\n\tcurrent: () => Hold | undefined;\n\tend: () => void;\n\tpointerType: () => string | undefined;\n}\n\ninterface Held<Hold> {\n\tfrom: FieldInput;\n\thold: Hold;\n\tstate: FieldDragState;\n}\n\nconst fieldAxes = ['x', 'y'] as const;\n\nfunction flip<Point extends FieldPoint>(point: Point, hold: FieldHold): Point {\n\treturn hold.isFlipped === true ? { ...point, y: -point.y } : point;\n}\n\nfunction startAxis(hold: FieldAxisHold | undefined) {\n\tif (!hold) return;\n\n\tconst { model, travelPx } = hold;\n\tconst mapping = model.mapping();\n\tconst from = model.value;\n\n\treturn { from, mapping, proportion: hold.proportion ?? mapping.proportionOf(from), travelPx };\n}\n\nexport function bindFieldGesture<Hold extends FieldHold>(\n\ttarget: HTMLElement,\n\toptions: FieldGestureOptions<Hold>,\n\tsignal: AbortSignal,\n): FieldGesture<Hold> {\n\tconst { claim } = options;\n\n\tlet pointerType: string | undefined;\n\n\tconst drag = bindDrag<Held<Hold>>(\n\t\ttarget,\n\t\t{\n\t\t\tcancel: ({ from, hold }) => {\n\t\t\t\toptions.input(hold, from);\n\t\t\t},\n\t\t\tgrab: (event) => {\n\t\t\t\t// Set first, so an `input` the grab itself fires can read it\n\t\t\t\tpointerType = event.pointerType;\n\n\t\t\t\tconst hold = options.grab(event);\n\t\t\t\tif (!hold) {\n\t\t\t\t\tpointerType = undefined;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { x, y } = hold.axes;\n\n\t\t\t\tclaim.press(true, revealDelay(target));\n\n\t\t\t\treturn {\n\t\t\t\t\tfrom: { x: x?.from ?? x?.model.value, y: y?.from ?? y?.model.value },\n\t\t\t\t\thold,\n\t\t\t\t\tstate: startFieldDrag({\n\t\t\t\t\t\tposition: flip(pointerPosition(event), hold),\n\t\t\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\t\t\tx: startAxis(x),\n\t\t\t\t\t\ty: startAxis(y),\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t},\n\t\t\tlift: ({ hold }, event) => {\n\t\t\t\toptions.lift?.(hold, event);\n\t\t\t},\n\t\t\tmove: (held, event) => {\n\t\t\t\tconst { axes } = held.hold;\n\t\t\t\tconst step = stepFieldDrag(\n\t\t\t\t\t{ x: axes.x?.model.mapping(), y: axes.y?.model.mapping() },\n\t\t\t\t\theld.state,\n\t\t\t\t\tflip(pointerMove(event), held.hold),\n\t\t\t\t);\n\n\t\t\t\theld.state = step.state;\n\t\t\t\tif (step.state.isEngaged) claim.reveal('drag');\n\t\t\t\toptions.input(held.hold, step);\n\t\t\t},\n\t\t\trelease: ({ from, hold }) => {\n\t\t\t\tclaim.press(false);\n\t\t\t\toptions.release(\n\t\t\t\t\thold,\n\t\t\t\t\tfieldAxes.filter((axis) => hold.axes[axis]?.model.value !== from[axis]),\n\t\t\t\t);\n\t\t\t\tpointerType = undefined;\n\t\t\t},\n\t\t\ttoggle: options.toggle,\n\t\t},\n\t\tsignal,\n\t);\n\n\treturn { current: () => drag.current()?.hold, end: drag.end, pointerType: () => pointerType };\n}\n"],"mappings":";;;;;;AA+CA,MAAM,YAAY,CAAC,KAAK,GAAG;AAE3B,SAAS,KAA+B,OAAc,MAAwB;CAC7E,OAAO,KAAK,cAAc,OAAO;EAAE,GAAG;EAAO,GAAG,CAAC,MAAM;CAAE,IAAI;AAC9D;AAEA,SAAS,UAAU,MAAiC;CACnD,IAAI,CAAC,MAAM;CAEX,MAAM,EAAE,OAAO,aAAa;CAC5B,MAAM,UAAU,MAAM,QAAQ;CAC9B,MAAM,OAAO,MAAM;CAEnB,OAAO;EAAE;EAAM;EAAS,YAAY,KAAK,cAAc,QAAQ,aAAa,IAAI;EAAG;CAAS;AAC7F;AAEA,SAAgB,iBACf,QACA,SACA,QACqB;CACrB,MAAM,EAAE,UAAU;CAElB,IAAI;CAEJ,MAAM,OAAO,SACZ,QACA;EACC,SAAS,EAAE,MAAM,WAAW;GAC3B,QAAQ,MAAM,MAAM,IAAI;EACzB;EACA,OAAO,UAAU;GAEhB,cAAc,MAAM;GAEpB,MAAM,OAAO,QAAQ,KAAK,KAAK;GAC/B,IAAI,CAAC,MAAM;IACV,cAAc;IACd;GACD;GAEA,MAAM,EAAE,GAAG,MAAM,KAAK;GAEtB,MAAM,MAAM,MAAM,YAAY,MAAM,CAAC;GAErC,OAAO;IACN,MAAM;KAAE,GAAG,GAAG,QAAQ,GAAG,MAAM;KAAO,GAAG,GAAG,QAAQ,GAAG,MAAM;IAAM;IACnE;IACA,OAAO,eAAe;KACrB,UAAU,KAAK,gBAAgB,KAAK,GAAG,IAAI;KAC3C,aAAa,gBAAgB,MAAM,WAAW;KAC9C,GAAG,UAAU,CAAC;KACd,GAAG,UAAU,CAAC;IACf,CAAC;GACF;EACD;EACA,OAAO,EAAE,QAAQ,UAAU;GAC1B,QAAQ,OAAO,MAAM,KAAK;EAC3B;EACA,OAAO,MAAM,UAAU;GACtB,MAAM,EAAE,SAAS,KAAK;GACtB,MAAM,OAAO,cACZ;IAAE,GAAG,KAAK,GAAG,MAAM,QAAQ;IAAG,GAAG,KAAK,GAAG,MAAM,QAAQ;GAAE,GACzD,KAAK,OACL,KAAK,YAAY,KAAK,GAAG,KAAK,IAAI,CACnC;GAEA,KAAK,QAAQ,KAAK;GAClB,IAAI,KAAK,MAAM,WAAW,MAAM,OAAO,MAAM;GAC7C,QAAQ,MAAM,KAAK,MAAM,IAAI;EAC9B;EACA,UAAU,EAAE,MAAM,WAAW;GAC5B,MAAM,MAAM,KAAK;GACjB,QAAQ,QACP,MACA,UAAU,QAAQ,SAAS,KAAK,KAAK,KAAK,EAAE,MAAM,UAAU,KAAK,KAAK,CACvE;GACA,cAAc;EACf;EACA,QAAQ,QAAQ;CACjB,GACA,MACD;CAEA,OAAO;EAAE,eAAe,KAAK,QAAQ,CAAC,EAAE;EAAM,KAAK,KAAK;EAAK,mBAAmB;CAAY;AAC7F"}
@@ -147,7 +147,7 @@ var SonicMeter = class extends SonicElement {
147
147
  this.#frame = void 0;
148
148
  this.#lastFrame = void 0;
149
149
  }, { once: true });
150
- this.checkStyles(this.#meter, "meter.css", { material: ["lens", "groove"] });
150
+ this.checkStyles(this.#meter, "meter.css");
151
151
  }
152
152
  #isSettled(now) {
153
153
  if (this.#isValueShown) return true;
@@ -1 +1 @@
1
- {"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { ballisticsRates, fall, rise, stepNeedle } from '#lib/ballistics.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { template } from '#lib/render.ts';\nimport { linearTaper } from '#lib/taper.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-bar\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst holdMs = 1500;\n\nconst ballisticsModes = ['peak', 'ppm-1', 'ppm-2', 'vu'] as const;\n\nconst settledDecibels = 0.01;\nconst settledAmplitude = 1e-4;\nconst settledPerSecond = 1e-2;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\nfunction toAmplitude(decibels: number): number {\n\treturn 10 ** (decibels / 20);\n}\n\n// A `calc()` rather than a count, so the zone's token stays live\nfunction segmentsBelow(segments: Array<number>, zone: 'clip' | 'hot'): string {\n\tconst terms = segments.map(\n\t\t(threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`,\n\t);\n\n\treturn `calc(${terms.join(' + ')})`;\n}\n\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t'ballistics',\n\t\t'disabled',\n\t\t'max',\n\t\t'min',\n\t\t'origin',\n\t\t'segments',\n\t\t'value',\n\t];\n\n\tget ballistics(): (typeof ballisticsModes)[number] {\n\t\tconst name = this.getAttribute('ballistics');\n\n\t\treturn ballisticsModes.find((mode) => mode === name) ?? 'peak';\n\t}\n\n\tset ballistics(mode: (typeof ballisticsModes)[number] | undefined) {\n\t\tthis.reflect('ballistics', mode);\n\t}\n\n\t/** Linear peak amplitude, 1 at full scale; the meter converts to decibels and applies its ballistics */\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(level: number) {\n\t\tconst amplitude = toNumber(level);\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst decibels = toDecibels(amplitude);\n\n\t\tthis.#level = amplitude;\n\t\tthis.#target = decibels;\n\t\tif (this.#isValueShown) return;\n\n\t\tconst now = performance.now();\n\n\t\tthis.#peak = Math.max(this.#peak, decibels);\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#step(now, 0);\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\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\t/** Highest level since `resetPeak()`; `-Infinity` before any signal */\n\tget peak(): Decibels {\n\t\treturn this.#peak;\n\t}\n\n\t/** Ladder thresholds, one per segment; unset draws a bar */\n\tget segments(): Array<Decibels> | undefined {\n\t\treturn parseNumberList(this.getAttribute('segments'));\n\t}\n\n\tset segments(thresholds: Array<Decibels> | undefined) {\n\t\tthis.reflect('segments', thresholds?.join(' '));\n\t}\n\n\t/** A reading in the meter's own units, drawn as given with no ballistics; takes over from `level` while set */\n\tget value(): number | undefined {\n\t\treturn this.optionalNumberAttribute('value');\n\t}\n\n\tset value(value: number | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\t#bar = -Infinity;\n\n\t#carryMs = 0;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#isValueShown = false;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakHold = -Infinity;\n\n\t#peakHoldAt = -Infinity;\n\n\t#segments: Array<number> | undefined;\n\n\t#target = -Infinity;\n\n\t#velocity = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst now = performance.now();\n\n\t\tif (name === 'ballistics') this.#velocity = 0;\n\t\telse if (name === 'value') this.#readValue(now);\n\t\tthis.#segments = this.segments;\n\t\tif (name === 'segments') this.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = -Infinity;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties(\n\t\t\t'ballistics',\n\t\t\t'max',\n\t\t\t'min',\n\t\t\t'orientation',\n\t\t\t'origin',\n\t\t\t'segments',\n\t\t\t'value',\n\t\t\t'level',\n\t\t);\n\t\tthis.keepControl(this.#meter, signal);\n\t\tthis.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tif (__DEV__) this.checkStyles(this.#meter, 'meter.css', { material: ['lens', 'groove'] });\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tif (this.#isValueShown) return true;\n\t\tif (now - this.#clipAt < holdMs) return false;\n\t\tif (this.ballistics === 'vu') return this.#bar === this.#target && this.#velocity === 0;\n\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar >= this.#target &&\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peakHold <= Math.max(this.#bar, floor)\n\t\t);\n\t}\n\n\t#proportionOf(level: number): number {\n\t\tconst segments = this.#segments;\n\t\tif (segments) {\n\t\t\treturn segments.filter((threshold) => threshold <= level).length / segments.length;\n\t\t}\n\n\t\treturn linearTaper(this.min, this.max).proportionOf(level);\n\t}\n\n\t#readValue(now: number): void {\n\t\tconst value = this.value;\n\t\tconst isValueShown = value !== undefined;\n\n\t\tif (isValueShown !== this.#isValueShown) {\n\t\t\tthis.#isValueShown = isValueShown;\n\t\t\tthis.resetPeak();\n\t\t\tif (!isValueShown) this.#step(now, 0);\n\t\t}\n\t\tif (value !== undefined) this.#peak = Math.max(this.#peak, value);\n\t}\n\n\t#render(now: number): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\n\t\tconst origin = this.origin === undefined ? 0 : this.#proportionOf(this.origin);\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\t\tmeter.style.setProperty('--_sonic-meter-origin', String(origin));\n\n\t\tif (this.disabled) {\n\t\t\tthis.#renderStill(origin);\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = this.value;\n\n\t\tif (value !== undefined) {\n\t\t\tthis.#renderStill(this.#proportionOf(value));\n\t\t\treturn;\n\t\t}\n\n\t\tconst peakHold = this.ballistics === 'vu' ? 0 : this.#proportionOf(this.#peakHold);\n\n\t\tmeter.style.setProperty('--_sonic-meter-bar', String(this.#proportionOf(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak-hold', String(peakHold));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#renderSegments(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\t\tconst segments = this.#segments;\n\n\t\tthis.toggleState('ladder', segments !== undefined);\n\t\tif (!segments) {\n\t\t\tfor (const name of ['count', 'hot-segments', 'clip-segments']) {\n\t\t\t\tmeter.style.removeProperty(`--_sonic-meter-${name}`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-count', String(segments.length));\n\t\tmeter.style.setProperty('--_sonic-meter-hot-segments', segmentsBelow(segments, 'hot'));\n\t\tmeter.style.setProperty('--_sonic-meter-clip-segments', segmentsBelow(segments, 'clip'));\n\t}\n\n\t#renderStill(bar: number): void {\n\t\tconst { style } = this.#meter;\n\n\t\tstyle.setProperty('--_sonic-meter-bar', String(bar));\n\t\tstyle.setProperty('--_sonic-meter-peak-hold', '0');\n\t\tstyle.setProperty('--_sonic-meter-clipped', '0');\n\t}\n\n\t#risen(timeConstantMs: number, elapsedMs: number): number {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst risen = toDecibels(rise(toAmplitude(this.#bar), { target, timeConstantMs }, elapsedMs));\n\n\t\treturn this.#target - risen < settledDecibels ? this.#target : risen;\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t#step(now: number, elapsedMs: number): void {\n\t\tconst mode = this.ballistics;\n\n\t\tif (mode === 'vu') {\n\t\t\tthis.#stepNeedle(elapsedMs);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];\n\t\tconst floor = Math.max(this.#target, this.min);\n\n\t\tthis.#bar =\n\t\t\tthis.#target > this.#bar\n\t\t\t\t? this.#risen(riseTimeConstantMs, elapsedMs)\n\t\t\t\t: fall(this.#bar, { fallDecibelsPerSecond, floor }, elapsedMs);\n\t\tif (this.#bar >= this.#peakHold) {\n\t\t\tthis.#peakHold = this.#bar;\n\t\t\tthis.#peakHoldAt = now;\n\t\t}\n\n\t\tconst pastHoldMs = now - this.#peakHoldAt - holdMs;\n\n\t\tif (pastHoldMs >= 0) {\n\t\t\tthis.#peakHold = fall(\n\t\t\t\tthis.#peakHold,\n\t\t\t\t{ fallDecibelsPerSecond, floor: this.#bar },\n\t\t\t\tMath.min(elapsedMs, pastHoldMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t#stepNeedle(elapsedMs: number): void {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst needle = stepNeedle(\n\t\t\t{ carryMs: this.#carryMs, position: toAmplitude(this.#bar), velocity: this.#velocity },\n\t\t\ttarget,\n\t\t\telapsedMs,\n\t\t);\n\t\tconst isLanded =\n\t\t\tMath.abs(needle.position - target) < settledAmplitude &&\n\t\t\tMath.abs(needle.velocity) < settledPerSecond;\n\n\t\tthis.#carryMs = needle.carryMs;\n\t\tthis.#velocity = isLanded ? 0 : needle.velocity;\n\t\tthis.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));\n\t}\n\n\treadonly #tick = (time: number): void => {\n\t\tconst elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#step(time, elapsedMs);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { ballisticsRates, fall, rise, stepNeedle } from '#lib/ballistics.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { template } from '#lib/render.ts';\nimport { linearTaper } from '#lib/taper.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-bar\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst holdMs = 1500;\n\nconst ballisticsModes = ['peak', 'ppm-1', 'ppm-2', 'vu'] as const;\n\nconst settledDecibels = 0.01;\nconst settledAmplitude = 1e-4;\nconst settledPerSecond = 1e-2;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\nfunction toAmplitude(decibels: number): number {\n\treturn 10 ** (decibels / 20);\n}\n\n// A `calc()` rather than a count, so the zone's token stays live\nfunction segmentsBelow(segments: Array<number>, zone: 'clip' | 'hot'): string {\n\tconst terms = segments.map(\n\t\t(threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`,\n\t);\n\n\treturn `calc(${terms.join(' + ')})`;\n}\n\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t'ballistics',\n\t\t'disabled',\n\t\t'max',\n\t\t'min',\n\t\t'origin',\n\t\t'segments',\n\t\t'value',\n\t];\n\n\tget ballistics(): (typeof ballisticsModes)[number] {\n\t\tconst name = this.getAttribute('ballistics');\n\n\t\treturn ballisticsModes.find((mode) => mode === name) ?? 'peak';\n\t}\n\n\tset ballistics(mode: (typeof ballisticsModes)[number] | undefined) {\n\t\tthis.reflect('ballistics', mode);\n\t}\n\n\t/** Linear peak amplitude, 1 at full scale; the meter converts to decibels and applies its ballistics */\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(level: number) {\n\t\tconst amplitude = toNumber(level);\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst decibels = toDecibels(amplitude);\n\n\t\tthis.#level = amplitude;\n\t\tthis.#target = decibels;\n\t\tif (this.#isValueShown) return;\n\n\t\tconst now = performance.now();\n\n\t\tthis.#peak = Math.max(this.#peak, decibels);\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#step(now, 0);\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\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\t/** Highest level since `resetPeak()`; `-Infinity` before any signal */\n\tget peak(): Decibels {\n\t\treturn this.#peak;\n\t}\n\n\t/** Ladder thresholds, one per segment; unset draws a bar */\n\tget segments(): Array<Decibels> | undefined {\n\t\treturn parseNumberList(this.getAttribute('segments'));\n\t}\n\n\tset segments(thresholds: Array<Decibels> | undefined) {\n\t\tthis.reflect('segments', thresholds?.join(' '));\n\t}\n\n\t/** A reading in the meter's own units, drawn as given with no ballistics; takes over from `level` while set */\n\tget value(): number | undefined {\n\t\treturn this.optionalNumberAttribute('value');\n\t}\n\n\tset value(value: number | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\t#bar = -Infinity;\n\n\t#carryMs = 0;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#isValueShown = false;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakHold = -Infinity;\n\n\t#peakHoldAt = -Infinity;\n\n\t#segments: Array<number> | undefined;\n\n\t#target = -Infinity;\n\n\t#velocity = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst now = performance.now();\n\n\t\tif (name === 'ballistics') this.#velocity = 0;\n\t\telse if (name === 'value') this.#readValue(now);\n\t\tthis.#segments = this.segments;\n\t\tif (name === 'segments') this.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = -Infinity;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties(\n\t\t\t'ballistics',\n\t\t\t'max',\n\t\t\t'min',\n\t\t\t'orientation',\n\t\t\t'origin',\n\t\t\t'segments',\n\t\t\t'value',\n\t\t\t'level',\n\t\t);\n\t\tthis.keepControl(this.#meter, signal);\n\t\tthis.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tif (__DEV__) this.checkStyles(this.#meter, 'meter.css');\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tif (this.#isValueShown) return true;\n\t\tif (now - this.#clipAt < holdMs) return false;\n\t\tif (this.ballistics === 'vu') return this.#bar === this.#target && this.#velocity === 0;\n\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar >= this.#target &&\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peakHold <= Math.max(this.#bar, floor)\n\t\t);\n\t}\n\n\t#proportionOf(level: number): number {\n\t\tconst segments = this.#segments;\n\t\tif (segments) {\n\t\t\treturn segments.filter((threshold) => threshold <= level).length / segments.length;\n\t\t}\n\n\t\treturn linearTaper(this.min, this.max).proportionOf(level);\n\t}\n\n\t#readValue(now: number): void {\n\t\tconst value = this.value;\n\t\tconst isValueShown = value !== undefined;\n\n\t\tif (isValueShown !== this.#isValueShown) {\n\t\t\tthis.#isValueShown = isValueShown;\n\t\t\tthis.resetPeak();\n\t\t\tif (!isValueShown) this.#step(now, 0);\n\t\t}\n\t\tif (value !== undefined) this.#peak = Math.max(this.#peak, value);\n\t}\n\n\t#render(now: number): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\n\t\tconst origin = this.origin === undefined ? 0 : this.#proportionOf(this.origin);\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\t\tmeter.style.setProperty('--_sonic-meter-origin', String(origin));\n\n\t\tif (this.disabled) {\n\t\t\tthis.#renderStill(origin);\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = this.value;\n\n\t\tif (value !== undefined) {\n\t\t\tthis.#renderStill(this.#proportionOf(value));\n\t\t\treturn;\n\t\t}\n\n\t\tconst peakHold = this.ballistics === 'vu' ? 0 : this.#proportionOf(this.#peakHold);\n\n\t\tmeter.style.setProperty('--_sonic-meter-bar', String(this.#proportionOf(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak-hold', String(peakHold));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#renderSegments(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\t\tconst segments = this.#segments;\n\n\t\tthis.toggleState('ladder', segments !== undefined);\n\t\tif (!segments) {\n\t\t\tfor (const name of ['count', 'hot-segments', 'clip-segments']) {\n\t\t\t\tmeter.style.removeProperty(`--_sonic-meter-${name}`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-count', String(segments.length));\n\t\tmeter.style.setProperty('--_sonic-meter-hot-segments', segmentsBelow(segments, 'hot'));\n\t\tmeter.style.setProperty('--_sonic-meter-clip-segments', segmentsBelow(segments, 'clip'));\n\t}\n\n\t#renderStill(bar: number): void {\n\t\tconst { style } = this.#meter;\n\n\t\tstyle.setProperty('--_sonic-meter-bar', String(bar));\n\t\tstyle.setProperty('--_sonic-meter-peak-hold', '0');\n\t\tstyle.setProperty('--_sonic-meter-clipped', '0');\n\t}\n\n\t#risen(timeConstantMs: number, elapsedMs: number): number {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst risen = toDecibels(rise(toAmplitude(this.#bar), { target, timeConstantMs }, elapsedMs));\n\n\t\treturn this.#target - risen < settledDecibels ? this.#target : risen;\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t#step(now: number, elapsedMs: number): void {\n\t\tconst mode = this.ballistics;\n\n\t\tif (mode === 'vu') {\n\t\t\tthis.#stepNeedle(elapsedMs);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];\n\t\tconst floor = Math.max(this.#target, this.min);\n\n\t\tthis.#bar =\n\t\t\tthis.#target > this.#bar\n\t\t\t\t? this.#risen(riseTimeConstantMs, elapsedMs)\n\t\t\t\t: fall(this.#bar, { fallDecibelsPerSecond, floor }, elapsedMs);\n\t\tif (this.#bar >= this.#peakHold) {\n\t\t\tthis.#peakHold = this.#bar;\n\t\t\tthis.#peakHoldAt = now;\n\t\t}\n\n\t\tconst pastHoldMs = now - this.#peakHoldAt - holdMs;\n\n\t\tif (pastHoldMs >= 0) {\n\t\t\tthis.#peakHold = fall(\n\t\t\t\tthis.#peakHold,\n\t\t\t\t{ fallDecibelsPerSecond, floor: this.#bar },\n\t\t\t\tMath.min(elapsedMs, pastHoldMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t#stepNeedle(elapsedMs: number): void {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst needle = stepNeedle(\n\t\t\t{ carryMs: this.#carryMs, position: toAmplitude(this.#bar), velocity: this.#velocity },\n\t\t\ttarget,\n\t\t\telapsedMs,\n\t\t);\n\t\tconst isLanded =\n\t\t\tMath.abs(needle.position - target) < settledAmplitude &&\n\t\t\tMath.abs(needle.velocity) < settledPerSecond;\n\n\t\tthis.#carryMs = needle.carryMs;\n\t\tthis.#velocity = isLanded ? 0 : needle.velocity;\n\t\tthis.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));\n\t}\n\n\treadonly #tick = (time: number): void => {\n\t\tconst elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#step(time, elapsedMs);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":"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"}
@@ -32,19 +32,7 @@ var SonicNumber = class extends SonicValueElement {
32
32
  const number = this.#number;
33
33
  this.keepControl(number, signal);
34
34
  this.render();
35
- this.checkStyles(number, "number.css", {
36
- box: {
37
- property: "padding-inline-start",
38
- ratio: "--_sonic-number-inset-ratio"
39
- },
40
- material: [
41
- "cap",
42
- "glass",
43
- "pane",
44
- "well",
45
- "value"
46
- ]
47
- });
35
+ this.checkStyles(number, "number.css");
48
36
  this.bindGestures(number, signal, () => ({
49
37
  position: (event) => -event.clientY,
50
38
  travelPx: this.travelPx(number, "--_sonic-number-travel")
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(number, 'number.css', {\n\t\t\t\tbox: {\n\t\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\t\tratio: '--_sonic-number-inset-ratio',\n\t\t\t\t},\n\t\t\t\tmaterial: ['cap', 'glass', 'pane', 'well', 'value'],\n\t\t\t});\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(number, '--_sonic-number-travel'),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#digits.textContent = this.valueText;\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override tapTarget(): number | undefined {\n\t\tif (this.press !== 'step') return undefined;\n\n\t\tconst mapping = this.mapping();\n\t\tconst next = mapping.keyTarget('ArrowUp', this.value);\n\n\t\treturn next === undefined || next === this.value ? mapping.bounds[0] : next;\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,IAAI,QAAyB;EAC5B,OAAO,KAAK,aAAa,OAAO,MAAM,SAAS,SAAS;CACzD;CAEA,IAAI,MAAM,SAAsC;EAC/C,KAAK,QAAQ,SAAS,OAAO;CAC9B;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,OAAO;EAC9B,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,YAAY,QAAQ,MAAM;EAC/B,KAAK,OAAO;EAEX,KAAK,YAAY,QAAQ,cAAc;GACtC,KAAK;IACJ,UAAU;IACV,OAAO;GACR;GACA,UAAU;IAAC;IAAO;IAAS;IAAQ;IAAQ;GAAO;EACnD,CAAC;EACF,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,QAAQ,wBAAwB;EACzD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAU,OAAa;EACtB,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,YAAgC;EAClD,IAAI,KAAK,UAAU,QAAQ,OAAO;EAElC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OAAO,QAAQ,UAAU,WAAW,KAAK,KAAK;EAEpD,OAAO,SAAS,UAAa,SAAS,KAAK,QAAQ,QAAQ,OAAO,KAAK;CACxE;AACD"}
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tif (__DEV__) this.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(number, '--_sonic-number-travel'),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#digits.textContent = this.valueText;\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override tapTarget(): number | undefined {\n\t\tif (this.press !== 'step') return undefined;\n\n\t\tconst mapping = this.mapping();\n\t\tconst next = mapping.keyTarget('ArrowUp', this.value);\n\n\t\treturn next === undefined || next === this.value ? mapping.bounds[0] : next;\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,IAAI,QAAyB;EAC5B,OAAO,KAAK,aAAa,OAAO,MAAM,SAAS,SAAS;CACzD;CAEA,IAAI,MAAM,SAAsC;EAC/C,KAAK,QAAQ,SAAS,OAAO;CAC9B;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,OAAO;EAC9B,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,YAAY,QAAQ,MAAM;EAC/B,KAAK,OAAO;EACC,KAAK,YAAY,QAAQ,YAAY;EAClD,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,QAAQ,wBAAwB;EACzD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAU,OAAa;EACtB,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,YAAgC;EAClD,IAAI,KAAK,UAAU,QAAQ,OAAO;EAElC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OAAO,QAAQ,UAAU,WAAW,KAAK,KAAK;EAEpD,OAAO,SAAS,UAAa,SAAS,KAAK,QAAQ,QAAQ,OAAO,KAAK;CACxE;AACD"}
@@ -13,16 +13,9 @@ var SonicSegmented = class extends SonicRadioGroupElement {
13
13
  connect(signal) {
14
14
  super.connect(signal);
15
15
  this.checkStyles(this.group, "segmented.css", {
16
- box: {
17
- property: "padding-inline-start",
18
- ratio: "--_sonic-segmented-padding-ratio",
19
- selector: ".sonic-segmented-cap"
20
- },
21
- material: [
22
- "well",
23
- "keycap",
24
- ["cap", ".sonic-segmented-option"]
25
- ]
16
+ property: "padding-inline-start",
17
+ ratio: "--_sonic-segmented-padding-ratio",
18
+ selector: ".sonic-segmented-cap"
26
19
  });
27
20
  }
28
21
  renderOption() {
@@ -1 +1 @@
1
- {"version":3,"file":"segmented.js","names":[],"sources":["../../../src/elements/segmented.ts"],"sourcesContent":["import { SonicRadioGroupElement } from '#elements/radio-group.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-segmented': SonicSegmented;\n\t}\n}\n\nconst renderSegmented = template(\n\t/* HTML */ `<div class=\"sonic-segmented\" role=\"radiogroup\"></div>`,\n\tHTMLDivElement,\n);\n\nconst renderOption = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-segmented-option\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-segmented-cap\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nexport class SonicSegmented extends SonicRadioGroupElement {\n\tprotected readonly group = renderSegmented();\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'segmented.css', {\n\t\t\t\tbox: {\n\t\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\t\tratio: '--_sonic-segmented-padding-ratio',\n\t\t\t\t\tselector: '.sonic-segmented-cap',\n\t\t\t\t},\n\t\t\t\tmaterial: ['well', 'keycap', ['cap', '.sonic-segmented-option']],\n\t\t\t});\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderOption();\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,kBAAkB,SACZ,yDACX,cACD;AAEA,MAAM,eAAe,SACT;;;;IAKX,iBACD;AAEA,IAAa,iBAAb,cAAoC,uBAAuB;CAC1D,AAAmB,QAAQ,gBAAgB;CAE3C,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,MAAM;EAEnB,KAAK,YAAY,KAAK,OAAO,iBAAiB;GAC7C,KAAK;IACJ,UAAU;IACV,OAAO;IACP,UAAU;GACX;GACA,UAAU;IAAC;IAAQ;IAAU,CAAC,OAAO,yBAAyB;GAAC;EAChE,CAAC;CACH;CAEA,AAAU,eAAkC;EAC3C,OAAO,aAAa;CACrB;AACD"}
1
+ {"version":3,"file":"segmented.js","names":[],"sources":["../../../src/elements/segmented.ts"],"sourcesContent":["import { SonicRadioGroupElement } from '#elements/radio-group.ts';\nimport { template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-segmented': SonicSegmented;\n\t}\n}\n\nconst renderSegmented = template(\n\t/* HTML */ `<div class=\"sonic-segmented\" role=\"radiogroup\"></div>`,\n\tHTMLDivElement,\n);\n\nconst renderOption = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-segmented-option\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-segmented-cap\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nexport class SonicSegmented extends SonicRadioGroupElement {\n\tprotected readonly group = renderSegmented();\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'segmented.css', {\n\t\t\t\tproperty: 'padding-inline-start',\n\t\t\t\tratio: '--_sonic-segmented-padding-ratio',\n\t\t\t\tselector: '.sonic-segmented-cap',\n\t\t\t});\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderOption();\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,kBAAkB,SACZ,yDACX,cACD;AAEA,MAAM,eAAe,SACT;;;;IAKX,iBACD;AAEA,IAAa,iBAAb,cAAoC,uBAAuB;CAC1D,AAAmB,QAAQ,gBAAgB;CAE3C,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,MAAM;EAEnB,KAAK,YAAY,KAAK,OAAO,iBAAiB;GAC7C,UAAU;GACV,OAAO;GACP,UAAU;EACX,CAAC;CACH;CAEA,AAAU,eAAkC;EAC3C,OAAO,aAAa;CACrB;AACD"}
@@ -119,18 +119,8 @@ var SonicSlider = class extends SonicValueElement {
119
119
  });
120
120
  this.render();
121
121
  this.checkStyles(slider, "slider.css", {
122
- box: {
123
- property: "margin-bottom",
124
- selector: "[popover]"
125
- },
126
- material: [
127
- "cap",
128
- "readout",
129
- "value",
130
- "groove",
131
- ["glass", ".sonic-slider-readout"],
132
- ["scale", ".sonic-slider-scale"]
133
- ]
122
+ property: "margin-bottom",
123
+ selector: "[popover]"
134
124
  });
135
125
  this.bindGestures(slider, signal, (event) => {
136
126
  const axis = this.#axis(slider, cap);