@xsynaptic/sonic-ui 0.18.0 → 0.19.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 (102) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +12 -2
  3. package/dist/default/elements/button.js +0 -5
  4. package/dist/default/elements/dial.js +0 -7
  5. package/dist/default/elements/display-element.js +0 -5
  6. package/dist/default/elements/envelope.js +0 -6
  7. package/dist/default/elements/meter.js +0 -1
  8. package/dist/default/elements/number.js +0 -7
  9. package/dist/default/elements/segmented.js +0 -5
  10. package/dist/default/elements/slider.js +0 -7
  11. package/dist/default/elements/sonic-element.js +3 -1
  12. package/dist/default/elements/switch.js +0 -1
  13. package/dist/default/elements/toggle.js +0 -5
  14. package/dist/default/elements/value-element.js +8 -4
  15. package/dist/default/elements/wave-element.js +7 -8
  16. package/dist/default/elements/xy.js +0 -7
  17. package/dist/default/index.js +2 -1
  18. package/dist/default/lib/clock.js +23 -0
  19. package/dist/default/lib/spoken-duration.js +30 -0
  20. package/dist/default/lib/waveform-buckets.js +7 -1
  21. package/dist/dev/elements/button.js +12 -5
  22. package/dist/dev/elements/button.js.map +1 -1
  23. package/dist/dev/elements/dial.js +14 -7
  24. package/dist/dev/elements/dial.js.map +1 -1
  25. package/dist/dev/elements/display-element.js +2 -2
  26. package/dist/dev/elements/display-element.js.map +1 -1
  27. package/dist/dev/elements/envelope.js +17 -6
  28. package/dist/dev/elements/envelope.js.map +1 -1
  29. package/dist/dev/elements/meter.js +1 -1
  30. package/dist/dev/elements/meter.js.map +1 -1
  31. package/dist/dev/elements/number.js +13 -7
  32. package/dist/dev/elements/number.js.map +1 -1
  33. package/dist/dev/elements/segmented.js +12 -5
  34. package/dist/dev/elements/segmented.js.map +1 -1
  35. package/dist/dev/elements/slider.js +14 -7
  36. package/dist/dev/elements/slider.js.map +1 -1
  37. package/dist/dev/elements/sonic-element.d.ts +13 -2
  38. package/dist/dev/elements/sonic-element.js +63 -11
  39. package/dist/dev/elements/sonic-element.js.map +1 -1
  40. package/dist/dev/elements/split.js.map +1 -1
  41. package/dist/dev/elements/switch.js +7 -1
  42. package/dist/dev/elements/switch.js.map +1 -1
  43. package/dist/dev/elements/toggle.js +12 -5
  44. package/dist/dev/elements/toggle.js.map +1 -1
  45. package/dist/dev/elements/value-element.d.ts +6 -0
  46. package/dist/dev/elements/value-element.js +8 -4
  47. package/dist/dev/elements/value-element.js.map +1 -1
  48. package/dist/dev/elements/wave-element.d.ts +2 -1
  49. package/dist/dev/elements/wave-element.js +21 -8
  50. package/dist/dev/elements/wave-element.js.map +1 -1
  51. package/dist/dev/elements/waveform.d.ts +1 -1
  52. package/dist/dev/elements/xy.js +14 -7
  53. package/dist/dev/elements/xy.js.map +1 -1
  54. package/dist/dev/index.d.ts +3 -2
  55. package/dist/dev/index.js +2 -1
  56. package/dist/dev/lib/clock.d.ts +5 -0
  57. package/dist/dev/lib/clock.js +24 -0
  58. package/dist/dev/lib/clock.js.map +1 -0
  59. package/dist/dev/lib/mirror-children.js.map +1 -1
  60. package/dist/dev/lib/owned-control.js.map +1 -1
  61. package/dist/dev/lib/spoken-duration.js +31 -0
  62. package/dist/dev/lib/spoken-duration.js.map +1 -0
  63. package/dist/dev/lib/waveform-buckets.d.ts +2 -2
  64. package/dist/dev/lib/waveform-buckets.js +7 -1
  65. package/dist/dev/lib/waveform-buckets.js.map +1 -1
  66. package/dist/skins/amber.css +1 -0
  67. package/dist/skins/ivory.css +1 -0
  68. package/dist/skins/lime.css +1 -0
  69. package/dist/skins/slate.css +1 -0
  70. package/dist/styles/button.css +29 -5
  71. package/dist/styles/dial.css +10 -9
  72. package/dist/styles/envelope.css +10 -4
  73. package/dist/styles/led.css +5 -1
  74. package/dist/styles/material/arc.css +3 -2
  75. package/dist/styles/material/bracket.css +2 -2
  76. package/dist/styles/material/cap.css +33 -12
  77. package/dist/styles/material/core.css +4 -0
  78. package/dist/styles/material/glass.css +1 -1
  79. package/dist/styles/material/groove.css +4 -3
  80. package/dist/styles/material/keycap.css +3 -2
  81. package/dist/styles/material/lens.css +1 -1
  82. package/dist/styles/material/pane.css +5 -4
  83. package/dist/styles/material/readout.css +6 -6
  84. package/dist/styles/material/well.css +2 -2
  85. package/dist/styles/meter.css +2 -7
  86. package/dist/styles/number.css +5 -1
  87. package/dist/styles/panel.css +4 -3
  88. package/dist/styles/ring.css +1 -1
  89. package/dist/styles/screen.css +5 -1
  90. package/dist/styles/segmented.css +17 -4
  91. package/dist/styles/slider.css +9 -3
  92. package/dist/styles/spectrum.css +16 -3
  93. package/dist/styles/switch.css +35 -10
  94. package/dist/styles/toggle.css +14 -7
  95. package/dist/styles/waveform.css +15 -4
  96. package/dist/styles/wavestrip.css +11 -3
  97. package/dist/styles/xy.css +9 -5
  98. package/package.json +1 -6
  99. package/dist/default/dat.js +0 -52
  100. package/dist/dev/dat.d.ts +0 -20
  101. package/dist/dev/dat.js +0 -53
  102. package/dist/dev/dat.js.map +0 -1
@@ -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\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\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\tthis.checkStyles(envelope, 'envelope.css', ['glass', 'pane', 'well', 'readout']);\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\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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"}
@@ -142,12 +142,12 @@ var SonicMeter = class extends SonicElement {
142
142
  this.#renderSegments();
143
143
  this.#render(now);
144
144
  this.#schedule(now);
145
- this.checkStyles(this.#meter, "meter.css", ["lens", "groove"]);
146
145
  signal.addEventListener("abort", () => {
147
146
  if (this.#frame !== void 0) cancelAnimationFrame(this.#frame);
148
147
  this.#frame = void 0;
149
148
  this.#lastFrame = void 0;
150
149
  }, { once: true });
150
+ this.checkStyles(this.#meter, "meter.css", { material: ["lens", "groove"] });
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 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\tthis.checkStyles(this.#meter, 'meter.css', ['lens', 'groove']);\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}\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', { 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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"}
@@ -32,13 +32,19 @@ 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
- "cap",
37
- "glass",
38
- "pane",
39
- "well",
40
- "value"
41
- ]);
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
+ });
42
48
  this.bindGestures(number, signal, () => ({
43
49
  position: (event) => -event.clientY,
44
50
  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 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\tthis.checkStyles(number, 'number.css', ['cap', 'glass', 'pane', 'well', 'value']);\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":";;;;AASA,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;EACZ,KAAK,YAAY,QAAQ,cAAc;GAAC;GAAO;GAAS;GAAQ;GAAQ;EAAO,CAAC;EAChF,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__)\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"}
@@ -12,11 +12,18 @@ var SonicSegmented = class extends SonicRadioGroupElement {
12
12
  group = renderSegmented();
13
13
  connect(signal) {
14
14
  super.connect(signal);
15
- this.checkStyles(this.group, "segmented.css", [
16
- "well",
17
- "keycap",
18
- ["cap", ".sonic-segmented-option"]
19
- ]);
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
+ ]
26
+ });
20
27
  }
21
28
  renderOption() {
22
29
  return 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 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\tthis.checkStyles(this.group, 'segmented.css', [\n\t\t\t'well',\n\t\t\t'keycap',\n\t\t\t['cap', '.sonic-segmented-option'],\n\t\t]);\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderOption();\n\t}\n}\n"],"mappings":";;;;AASA,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;EACpB,KAAK,YAAY,KAAK,OAAO,iBAAiB;GAC7C;GACA;GACA,CAAC,OAAO,yBAAyB;EAClC,CAAC;CACF;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\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"}
@@ -118,13 +118,20 @@ var SonicSlider = class extends SonicValueElement {
118
118
  }
119
119
  });
120
120
  this.render();
121
- this.checkStyles(slider, "slider.css", [
122
- "cap",
123
- "readout",
124
- "value",
125
- "groove",
126
- ["glass", ".sonic-slider-readout"]
127
- ]);
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
+ ]
134
+ });
128
135
  this.bindGestures(slider, signal, (event) => {
129
136
  const axis = this.#axis(slider, cap);
130
137
  if (event.target instanceof Node && cap.contains(event.target)) return axis;
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-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-slider-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 SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\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\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\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css', [\n\t\t\t'cap',\n\t\t\t'readout',\n\t\t\t'value',\n\t\t\t'groove',\n\t\t\t['glass', '.sonic-slider-readout'],\n\t\t]);\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-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-slider-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 SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\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\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\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(slider, 'slider.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'groove',\n\t\t\t\t\t['glass', '.sonic-slider-readout'],\n\t\t\t\t\t['scale', '.sonic-slider-scale'],\n\t\t\t\t],\n\t\t\t});\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -1,6 +1,12 @@
1
1
  //#region src/elements/sonic-element.d.ts
2
+ interface BoxProbe {
3
+ property: string;
4
+ ratio?: `--_sonic-${string}`;
5
+ selector?: string;
6
+ }
2
7
  declare const materialTokens: {
3
8
  readonly arc: "--_sonic-arc-mask";
9
+ readonly bracket: "border-top-style";
4
10
  readonly cap: "--_sonic-cap";
5
11
  readonly core: "--_sonic-unlit";
6
12
  readonly glass: "--_sonic-glass-slab";
@@ -9,11 +15,16 @@ declare const materialTokens: {
9
15
  readonly lens: "--_sonic-lens";
10
16
  readonly pane: "--_sonic-glass-pitch";
11
17
  readonly readout: "--_sonic-readout-size";
18
+ readonly scale: "position";
12
19
  readonly value: "--_sonic-detent-zone";
13
20
  readonly well: "--_sonic-well-depth";
14
21
  };
15
22
  type MaterialSheet = keyof typeof materialTokens;
16
- type MaterialProbe = [sheet: MaterialSheet, selector: string] | MaterialSheet;
23
+ type MaterialProbe = [sheet: MaterialSheet, selector: string, pseudo?: string] | MaterialSheet;
24
+ interface StyleChecks {
25
+ box?: BoxProbe;
26
+ material: Array<MaterialProbe>;
27
+ }
17
28
  export declare abstract class SonicElement extends HTMLElement {
18
29
  #private;
19
30
  static readonly observedAttributes: string[];
@@ -25,7 +36,7 @@ export declare abstract class SonicElement extends HTMLElement {
25
36
  connectedMoveCallback(): void;
26
37
  disconnectedCallback(): void;
27
38
  focus(options?: FocusOptions): void;
28
- protected checkStyles(control: HTMLElement, sheet: string, material?: Array<MaterialProbe>): void;
39
+ protected checkStyles(control: HTMLElement, sheet: string, { box, material }: StyleChecks): void;
29
40
  protected abstract connect(signal: AbortSignal): void;
30
41
  protected focusTarget(): HTMLElement | undefined;
31
42
  protected forwardNaming(target: Element, isNamed: boolean): void;
@@ -11,6 +11,7 @@ const namingAttributes = [
11
11
  const checkedSheets = /* @__PURE__ */ new WeakMap();
12
12
  const materialTokens = {
13
13
  arc: "--_sonic-arc-mask",
14
+ bracket: "border-top-style",
14
15
  cap: "--_sonic-cap",
15
16
  core: "--_sonic-unlit",
16
17
  glass: "--_sonic-glass-slab",
@@ -19,21 +20,55 @@ const materialTokens = {
19
20
  lens: "--_sonic-lens",
20
21
  pane: "--_sonic-glass-pitch",
21
22
  readout: "--_sonic-readout-size",
23
+ scale: "position",
22
24
  value: "--_sonic-detent-zone",
23
25
  well: "--_sonic-well-depth"
24
26
  };
27
+ const lostValues = {
28
+ bracket: "none",
29
+ scale: "static"
30
+ };
25
31
  function materialProbe(entry) {
26
- if (typeof entry === "string") return {
27
- sheet: `material/${entry}.css`,
28
- token: materialTokens[entry]
29
- };
30
- const [name, selector] = entry;
32
+ const [name, selector, pseudo] = typeof entry === "string" ? [entry] : entry;
33
+ const lost = lostValues[name];
31
34
  return {
32
- selector,
33
35
  sheet: `material/${name}.css`,
34
- token: materialTokens[name]
36
+ token: materialTokens[name],
37
+ ...lost === void 0 ? {} : { lost },
38
+ ...pseudo === void 0 ? {} : { pseudo },
39
+ ...selector === void 0 ? {} : { selector }
35
40
  };
36
41
  }
42
+ function isLost(drawn, { lost = "", pseudo, token }) {
43
+ return getComputedStyle(drawn, pseudo).getPropertyValue(token) === lost;
44
+ }
45
+ const hostBoxProperties = [
46
+ "margin-top",
47
+ "margin-right",
48
+ "margin-bottom",
49
+ "margin-left",
50
+ "padding-top",
51
+ "padding-right",
52
+ "padding-bottom",
53
+ "padding-left",
54
+ "width",
55
+ "height"
56
+ ];
57
+ function readHostBox(host) {
58
+ const style = getComputedStyle(host);
59
+ if (style.display === "none") return void 0;
60
+ if (style.display !== "contents") return "display";
61
+ return hostBoxProperties.find((property) => ![
62
+ "",
63
+ "0px",
64
+ "auto"
65
+ ].includes(style.getPropertyValue(property)));
66
+ }
67
+ function isZeroed(control, { property, ratio, selector }) {
68
+ const part = selector === void 0 ? control : control.querySelector(selector);
69
+ if (!part || getComputedStyle(part).getPropertyValue(property) !== "0px") return false;
70
+ return ratio === void 0 || Number(getComputedStyle(control).getPropertyValue(ratio)) !== 0;
71
+ }
37
72
  function writeLabelledBy(target, labels) {
38
73
  const current = target.ariaLabelledByElements;
39
74
  if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
@@ -84,7 +119,7 @@ var SonicElement = class extends HTMLElement {
84
119
  }
85
120
  if (!this.isDisabled() && !this.#focused()) target.focus(options);
86
121
  }
87
- checkStyles(control, sheet, material = []) {
122
+ checkStyles(control, sheet, { box, material }) {
88
123
  const elementClass = this.constructor;
89
124
  const checked = checkedSheets.get(elementClass) ?? /* @__PURE__ */ new Set();
90
125
  const part = ({ selector }) => selector === void 0 ? control : this.querySelector(selector) ?? void 0;
@@ -100,7 +135,7 @@ var SonicElement = class extends HTMLElement {
100
135
  token: "--_sonic-led-lens-ratio"
101
136
  }
102
137
  ].filter((probe) => !checked.has(probe.sheet) && part(probe) !== void 0);
103
- if (probes.length === 0) return;
138
+ if ((!box || checked.has("reset")) && checked.has("host") && probes.length === 0) return;
104
139
  checkedSheets.set(elementClass, checked);
105
140
  for (const probe of probes) checked.add(probe.sheet);
106
141
  requestAnimationFrame(() => {
@@ -108,9 +143,13 @@ var SonicElement = class extends HTMLElement {
108
143
  for (const probe of probes) {
109
144
  const drawn = control.isConnected ? part(probe) : void 0;
110
145
  if (!drawn) checked.delete(probe.sheet);
111
- else if (getComputedStyle(drawn).getPropertyValue(probe.token) === "") missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);
146
+ else if (isLost(drawn, probe)) missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);
112
147
  }
113
- if (missing.length === 0) return;
148
+ if (missing.length === 0) {
149
+ this.#checkBox(control, checked, box);
150
+ return;
151
+ }
152
+ checked.add("host").add("reset");
114
153
  console.warn(`<${this.localName}> draws blank without ${missing.join(" and ")} (or controls.css)`);
115
154
  });
116
155
  }
@@ -180,6 +219,19 @@ var SonicElement = class extends HTMLElement {
180
219
  this[name] = value;
181
220
  }
182
221
  }
222
+ #checkBox(control, checked, box) {
223
+ if (!control.isConnected) return;
224
+ if (!checked.has("host")) this.#checkHost(checked);
225
+ if (!box || checked.has("reset") || !isZeroed(control, box)) return;
226
+ checked.add("reset");
227
+ console.warn(`<${this.localName}> has lost its ${box.property} to a rule outside a layer, which beats every sonic rule; import that stylesheet into a layer declared before sonic`);
228
+ }
229
+ #checkHost(checked) {
230
+ const hostBox = readHostBox(this);
231
+ if (hostBox === void 0) return;
232
+ checked.add("host");
233
+ console.warn(hostBox === "display" ? `<${this.localName}> is given a display, but it has to stay display: contents; lay it out through a parent or a wrapper` : `<${this.localName}> is display: contents and has no box, so its ${hostBox} does nothing; set it on a parent or a wrapper`);
234
+ }
183
235
  #focused() {
184
236
  const root = this.getRootNode();
185
237
  const active = root instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;