@xsynaptic/sonic-ui 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +5 -3
  3. package/dist/default/define/region.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/envelope.js +11 -11
  6. package/dist/default/elements/form-element.js +10 -0
  7. package/dist/default/elements/readout.js +3 -2
  8. package/dist/default/elements/region-layer.js +251 -0
  9. package/dist/default/elements/region.js +157 -0
  10. package/dist/default/elements/sonic-element.js +26 -4
  11. package/dist/default/elements/split.js +2 -2
  12. package/dist/default/elements/toggle.js +2 -2
  13. package/dist/default/elements/value-element.js +9 -5
  14. package/dist/default/elements/value-gestures.js +7 -5
  15. package/dist/default/elements/wave-element.js +12 -3
  16. package/dist/default/elements/waveform.js +38 -2
  17. package/dist/default/elements/wavestrip.js +90 -17
  18. package/dist/default/elements/xy.js +3 -11
  19. package/dist/default/index.js +2 -1
  20. package/dist/default/lib/clock.js +1 -0
  21. package/dist/default/lib/drag-step.js +1 -1
  22. package/dist/default/lib/marker-band.js +40 -29
  23. package/dist/default/lib/region-drag.js +48 -0
  24. package/dist/default/lib/spoken-duration.js +3 -0
  25. package/dist/dev/define/region.d.ts +2 -0
  26. package/dist/dev/define/region.js +8 -0
  27. package/dist/dev/define/region.js.map +1 -0
  28. package/dist/dev/define.d.ts +3 -1
  29. package/dist/dev/define.js +1 -0
  30. package/dist/dev/elements/envelope.js +11 -11
  31. package/dist/dev/elements/envelope.js.map +1 -1
  32. package/dist/dev/elements/form-element.d.ts +1 -0
  33. package/dist/dev/elements/form-element.js +10 -0
  34. package/dist/dev/elements/form-element.js.map +1 -1
  35. package/dist/dev/elements/readout.js +3 -2
  36. package/dist/dev/elements/readout.js.map +1 -1
  37. package/dist/dev/elements/region-layer.js +252 -0
  38. package/dist/dev/elements/region-layer.js.map +1 -0
  39. package/dist/dev/elements/region.d.ts +29 -0
  40. package/dist/dev/elements/region.js +165 -0
  41. package/dist/dev/elements/region.js.map +1 -0
  42. package/dist/dev/elements/sonic-element.d.ts +1 -0
  43. package/dist/dev/elements/sonic-element.js +27 -5
  44. package/dist/dev/elements/sonic-element.js.map +1 -1
  45. package/dist/dev/elements/split.js +2 -2
  46. package/dist/dev/elements/split.js.map +1 -1
  47. package/dist/dev/elements/toggle.js +2 -2
  48. package/dist/dev/elements/toggle.js.map +1 -1
  49. package/dist/dev/elements/value-element.d.ts +3 -1
  50. package/dist/dev/elements/value-element.js +9 -5
  51. package/dist/dev/elements/value-element.js.map +1 -1
  52. package/dist/dev/elements/value-gestures.js +7 -5
  53. package/dist/dev/elements/value-gestures.js.map +1 -1
  54. package/dist/dev/elements/wave-element.d.ts +7 -1
  55. package/dist/dev/elements/wave-element.js +12 -3
  56. package/dist/dev/elements/wave-element.js.map +1 -1
  57. package/dist/dev/elements/waveform.d.ts +7 -0
  58. package/dist/dev/elements/waveform.js +38 -2
  59. package/dist/dev/elements/waveform.js.map +1 -1
  60. package/dist/dev/elements/wavestrip.d.ts +17 -1
  61. package/dist/dev/elements/wavestrip.js +90 -17
  62. package/dist/dev/elements/wavestrip.js.map +1 -1
  63. package/dist/dev/elements/xy.js +3 -11
  64. package/dist/dev/elements/xy.js.map +1 -1
  65. package/dist/dev/index.d.ts +3 -2
  66. package/dist/dev/index.js +2 -1
  67. package/dist/dev/lib/clock.js +1 -0
  68. package/dist/dev/lib/clock.js.map +1 -1
  69. package/dist/dev/lib/drag-step.js +1 -1
  70. package/dist/dev/lib/drag-step.js.map +1 -1
  71. package/dist/dev/lib/marker-band.d.ts +7 -0
  72. package/dist/dev/lib/marker-band.js +40 -29
  73. package/dist/dev/lib/marker-band.js.map +1 -1
  74. package/dist/dev/lib/region-drag.js +49 -0
  75. package/dist/dev/lib/region-drag.js.map +1 -0
  76. package/dist/dev/lib/spoken-duration.js +3 -0
  77. package/dist/dev/lib/spoken-duration.js.map +1 -1
  78. package/dist/styles/button.css +42 -15
  79. package/dist/styles/controls.css +1 -0
  80. package/dist/styles/dial.css +48 -33
  81. package/dist/styles/envelope.css +7 -6
  82. package/dist/styles/led.css +28 -13
  83. package/dist/styles/material/arc.css +2 -2
  84. package/dist/styles/material/cap.css +25 -15
  85. package/dist/styles/material/core.css +53 -1
  86. package/dist/styles/material/glass.css +12 -2
  87. package/dist/styles/material/groove.css +3 -3
  88. package/dist/styles/material/keycap.css +5 -5
  89. package/dist/styles/material/lens.css +1 -1
  90. package/dist/styles/material/pane.css +21 -11
  91. package/dist/styles/material/readout.css +23 -10
  92. package/dist/styles/material/{bracket.css → reticle.css} +15 -8
  93. package/dist/styles/material/scale.css +4 -4
  94. package/dist/styles/material/value.css +12 -3
  95. package/dist/styles/material/well.css +2 -2
  96. package/dist/styles/material.css +1 -1
  97. package/dist/styles/meter.css +24 -11
  98. package/dist/styles/number.css +2 -1
  99. package/dist/styles/panel.css +5 -6
  100. package/dist/styles/region.css +88 -0
  101. package/dist/styles/ring.css +5 -4
  102. package/dist/styles/screen.css +7 -5
  103. package/dist/styles/segmented.css +8 -7
  104. package/dist/styles/slider.css +40 -32
  105. package/dist/styles/spectrum.css +9 -1
  106. package/dist/styles/switch.css +52 -53
  107. package/dist/styles/toggle.css +33 -38
  108. package/dist/styles/waveform.css +5 -4
  109. package/dist/styles/wavestrip.css +15 -12
  110. package/dist/styles/xy.css +19 -19
  111. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"value-gestures.js","names":[],"sources":["../../../src/elements/value-gestures.ts"],"sourcesContent":["import type { DragState } from '#lib/drag-step.ts';\nimport type { PointerDrag } from '#lib/pointer-drag.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { ReadoutClaim, revealDelay } from '#elements/readout-claim.ts';\nimport { ValueEntry } from '#elements/value-entry.ts';\nimport { dragThresholdPx, startDrag, stepDrag } from '#lib/drag-step.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clamp } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\n\nexport interface ValueAxis {\n\tfromProportion?: number;\n\tisKeptOnCancel?: boolean;\n\toutside?: (event: PointerEvent) => boolean;\n\tposition: (event: PointerEvent) => number;\n\ttravelPx: number;\n}\n\ninterface GestureHost {\n\tdefault: () => number | undefined;\n\tdetent: () => number | undefined;\n\tdispatch: (type: 'change' | 'sonic-reveal') => void;\n\tdoublePress: () => 'entry' | 'none' | 'reset';\n\tentryId: string;\n\tentryText: () => string;\n\tforwardNaming: (target: Element, isNamed: boolean) => void;\n\thasEntry: () => boolean;\n\tinput: (next: number) => boolean;\n\tisDisabled: () => boolean;\n\tkeyStep: () => number | undefined;\n\tmodel: ValueModel;\n\tparse: (text: string) => number;\n\trender: () => void;\n\tscrubChanged: () => void;\n\tscrubsKeyRepeat: () => boolean;\n\tspringTarget: () => number | undefined;\n\ttapTarget: () => number | undefined;\n\ttoggleState: (state: string, isOn: boolean) => void;\n}\n\ninterface KeyScrub {\n\tfromValue: number;\n\tkey: string;\n}\n\ninterface ValueDrag extends ValueAxis {\n\tfromValue: number;\n\tisOutside: boolean;\n\tisRevealed: boolean;\n\trevealTimer: ReturnType<typeof setTimeout> | undefined;\n\tstate: DragState;\n}\n\nconst detentZonePx = 8;\n\nexport class ValueGestures {\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#showRevealed();\n\t\tthis.#host.scrubChanged();\n\t});\n\n\treadonly #control: HTMLElement;\n\n\t#entry: undefined | ValueEntry;\n\n\treadonly #host: GestureHost;\n\n\t#isRevealed = false;\n\n\t#keyScrub: KeyScrub | undefined;\n\n\t#pointerDrag: PointerDrag<ValueDrag> | undefined;\n\n\t#pointerType: string | undefined;\n\n\tconstructor(host: GestureHost, control: HTMLElement) {\n\t\tthis.#host = host;\n\t\tthis.#control = control;\n\t}\n\n\tabandon(): void {\n\t\tconst drag = this.#dragging();\n\t\tif (!drag) return;\n\n\t\tthis.#host.input(drag.fromValue);\n\t\tthis.#entry?.forgetPress();\n\t\tthis.#pointerDrag?.end();\n\t}\n\n\tbind(signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void {\n\t\tconst control = this.#control;\n\t\tconst host = this.#host;\n\t\tconst entry = this.#bindEntry(signal);\n\n\t\tthis.#pointerDrag = bindDrag<ValueDrag>(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcancel: (drag) => {\n\t\t\t\t\tif (drag.isKeptOnCancel !== true) host.input(drag.fromValue);\n\t\t\t\t},\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (isMenuPress(event) || host.isDisabled() || entry.isOpen) return;\n\n\t\t\t\t\thost.toggleState('springing', false);\n\t\t\t\t\t// A click listener above makes WebKit send a tap's compatibility mousedown, which blurs the entry a double tap just opened\n\t\t\t\t\tif (event.pointerType === 'touch') event.preventDefault();\n\t\t\t\t\tfocusByPointer(control);\n\t\t\t\t\tthis.#pointerType = event.pointerType;\n\t\t\t\t\tif (isResetPress(event)) {\n\t\t\t\t\t\tthis.#reset();\n\t\t\t\t\t\tthis.#pointerType = undefined;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst fromValue = host.model.value;\n\n\t\t\t\t\thost.toggleState('dragging', true);\n\n\t\t\t\t\tconst axis = grab(event);\n\t\t\t\t\tif (!axis) {\n\t\t\t\t\t\thost.toggleState('dragging', false);\n\t\t\t\t\t\tthis.#pointerType = undefined;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst drag: ValueDrag = {\n\t\t\t\t\t\t...axis,\n\t\t\t\t\t\tfromValue,\n\t\t\t\t\t\tisOutside: false,\n\t\t\t\t\t\tisRevealed: false,\n\t\t\t\t\t\trevealTimer: undefined,\n\t\t\t\t\t\tstate: this.#startDrag(axis, event),\n\t\t\t\t\t};\n\n\t\t\t\t\tdrag.revealTimer = setTimeout(() => {\n\t\t\t\t\t\tthis.#reveal(drag);\n\t\t\t\t\t}, revealDelay(control));\n\n\t\t\t\t\treturn drag;\n\t\t\t\t},\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tthis.#pointerType = event.pointerType;\n\t\t\t\t\tthis.#lift(drag, event, entry);\n\t\t\t\t\tthis.#pointerType = undefined;\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\tthis.#dragTo(drag, event);\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tthis.#releaseDrag(drag);\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\thost.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t() => {\n\t\t\t\tentry.forgetPress();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindKeys(entry, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t\tthis.#concealKeyReveal();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tend(): void {\n\t\tthis.#entry?.close(false);\n\t\tthis.#pointerDrag?.end();\n\t\tthis.#endKeyScrub();\n\t}\n\n\tisEditing(): boolean {\n\t\treturn this.#entry?.isOpen === true;\n\t}\n\n\tisHeld(): boolean {\n\t\treturn this.#dragging() !== undefined || this.#keyScrub !== undefined;\n\t}\n\n\tisRevealed(): boolean {\n\t\treturn this.#claim.isRevealed;\n\t}\n\n\tpointerType(): string | undefined {\n\t\treturn this.#pointerType;\n\t}\n\n\tscrubFrom(): number | undefined {\n\t\treturn this.#dragging()?.fromValue ?? this.#keyScrub?.fromValue;\n\t}\n\n\t#bindEntry(signal: AbortSignal): ValueEntry {\n\t\tconst control = this.#control;\n\t\tconst host = this.#host;\n\t\tconst input = requireChild(control, 'input', HTMLInputElement);\n\n\t\t// Chrome's issues panel flags a form field with no id or name; a `name` would submit it\n\t\tinput.id = host.entryId;\n\n\t\tconst entry = new ValueEntry(input, {\n\t\t\tcommit: (text) => {\n\t\t\t\tthis.#commit(host.parse(text));\n\t\t\t},\n\t\t\ttext: host.entryText,\n\t\t\ttoggle: (isOpen) => {\n\t\t\t\tif (isOpen) host.forwardNaming(input, true);\n\t\t\t\thost.toggleState('editing', isOpen);\n\t\t\t\thost.render();\n\t\t\t},\n\t\t});\n\n\t\tthis.#entry = entry;\n\t\tentry.bind(control, signal);\n\n\t\treturn entry;\n\t}\n\n\t#bindKeys(entry: ValueEntry, signal: AbortSignal): void {\n\t\tconst control = this.#control;\n\t\tconst host = this.#host;\n\n\t\tcontrol.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (host.isDisabled() || event.defaultPrevented || event.target !== control) return;\n\n\t\t\t\thost.toggleState('springing', false);\n\t\t\t\tif (event.key === 'Enter') {\n\t\t\t\t\tif (host.springTarget() !== undefined || !host.hasEntry()) return;\n\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tentry.open();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst next = this.#keyTarget(event.key);\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#showRevealed();\n\t\t\t\tthis.#keyTo(event, next);\n\t\t\t\thost.scrubChanged();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'keyup',\n\t\t\t(event) => {\n\t\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\t\tconst isMeta = event.key === 'Meta';\n\n\t\t\t\tif (isMeta || event.key === this.#keyScrub?.key) this.#endKeyScrub();\n\t\t\t\tif (isMeta && this.#dragging()) return;\n\t\t\t\tif (isMeta || host.model.mapping().keyTarget(event.key, host.model.value) !== undefined) {\n\t\t\t\t\tthis.#springBack();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t\tthis.#concealKeyReveal();\n\t\t\t\tthis.#springBack();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#commit(next: number, direction: -1 | 0 | 1 = 0): void {\n\t\tconst host = this.#host;\n\t\tconst landed = Number.isFinite(next) ? host.model.land(next, direction) : next;\n\n\t\tif (host.input(landed)) host.dispatch('change');\n\t}\n\n\t#concealKeyReveal(): void {\n\t\tif (!this.#claim.conceal('keys')) return;\n\n\t\tthis.#showRevealed();\n\t\tthis.#host.scrubChanged();\n\t}\n\n\t#crossCancelZone(drag: ValueDrag, value: number): void {\n\t\tconst host = this.#host;\n\t\tconst isOutside = !drag.isOutside;\n\n\t\tdrag.isOutside = isOutside;\n\t\thost.toggleState('cancelling', isOutside);\n\t\tif (isOutside) this.#renderDragReveal(drag);\n\t\thost.input(isOutside ? drag.fromValue : value);\n\t\tif (!isOutside) this.#renderDragReveal(drag);\n\t}\n\n\t#dragging(): undefined | ValueDrag {\n\t\treturn this.#pointerDrag?.current();\n\t}\n\n\t#dragTo(drag: ValueDrag, event: PointerEvent): void {\n\t\tconst host = this.#host;\n\t\tconst isOutside = drag.outside?.(event) === true;\n\t\tconst wasEngaged = drag.state.isEngaged;\n\t\tconst mapping = host.model.mapping();\n\t\tconst { state, value } = stepDrag(mapping, drag.state, {\n\t\t\tisFine: event.shiftKey,\n\t\t\tisOutside,\n\t\t\tposition: drag.position(event),\n\t\t});\n\n\t\tdrag.state = this.#withinLimit(mapping, state);\n\t\tif (value === undefined) return;\n\n\t\tif (!wasEngaged) this.#reveal(drag);\n\n\t\tif (isOutside === drag.isOutside) host.input(isOutside ? drag.fromValue : value);\n\t\telse this.#crossCancelZone(drag, value);\n\t}\n\n\t#endKeyScrub(): void {\n\t\tconst host = this.#host;\n\t\tconst scrub = this.#keyScrub;\n\t\tif (!scrub) return;\n\n\t\tthis.#keyScrub = undefined;\n\t\thost.scrubChanged();\n\t\tif (host.model.value !== scrub.fromValue) host.dispatch('change');\n\t}\n\n\t#keyTarget(key: string): number | undefined {\n\t\tconst host = this.#host;\n\n\t\tif (resetKeys.has(key)) return host.default();\n\n\t\treturn host.model.mapping().keyTarget(key, host.model.value, host.keyStep());\n\t}\n\n\t#keyTo(event: KeyboardEvent, next: number): void {\n\t\tconst host = this.#host;\n\n\t\tif (event.repeat && host.scrubsKeyRepeat()) {\n\t\t\tthis.#scrubKey(event.key, next);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#endKeyScrub();\n\t\tthis.#commit(next, next > host.model.value ? 1 : -1);\n\t}\n\n\t#lift(drag: ValueDrag, event: PointerEvent, entry: ValueEntry): void {\n\t\tconst host = this.#host;\n\t\tconst tap = drag.state.isEngaged ? undefined : host.tapTarget();\n\n\t\tif (tap !== undefined) {\n\t\t\tentry.forgetPress();\n\t\t\tthis.#commit(tap);\n\t\t\treturn;\n\t\t}\n\t\tif (!entry.press(event) || host.springTarget() !== undefined) return;\n\n\t\tconst gesture = host.doublePress();\n\n\t\tif (gesture === 'reset') this.#reset();\n\t\telse if (gesture === 'entry' && host.hasEntry()) entry.open();\n\t}\n\n\t#releaseDrag(drag: ValueDrag): void {\n\t\tconst host = this.#host;\n\n\t\tclearTimeout(drag.revealTimer);\n\t\tthis.#claim.conceal('drag');\n\t\thost.toggleState('cancelling', false);\n\t\thost.scrubChanged();\n\t\tthis.#showRevealed();\n\t\tif (host.model.value !== drag.fromValue) host.dispatch('change');\n\t\tthis.#springBack();\n\t\tthis.#pointerType = undefined;\n\t}\n\n\t#renderDragReveal(drag: ValueDrag): void {\n\t\tconst isRevealed = drag.isRevealed && !drag.isOutside;\n\n\t\tif (isRevealed) this.#claim.reveal('drag');\n\t\telse this.#claim.conceal('drag');\n\t\tthis.#host.scrubChanged();\n\t\tthis.#showRevealed();\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit(this.#host.default() ?? NaN);\n\t}\n\n\t#reveal(drag: ValueDrag): void {\n\t\tif (drag.isRevealed || this.#dragging() !== drag) return;\n\n\t\tdrag.isRevealed = true;\n\t\tthis.#renderDragReveal(drag);\n\t}\n\n\t#scrubKey(key: string, next: number): void {\n\t\tconst host = this.#host;\n\n\t\tif (this.#keyScrub?.key !== key) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tthis.#keyScrub = { fromValue: host.model.value, key };\n\t\t}\n\t\thost.input(next);\n\t}\n\n\t#showRevealed(): void {\n\t\tconst { isRevealed } = this.#claim;\n\t\tif (isRevealed === this.#isRevealed) return;\n\n\t\tthis.#isRevealed = isRevealed;\n\t\tthis.#host.toggleState('revealed', isRevealed);\n\t\tthis.#host.dispatch('sonic-reveal');\n\t}\n\n\t#springBack(): void {\n\t\tconst host = this.#host;\n\t\tconst target = host.springTarget();\n\t\tif (target === undefined) return;\n\n\t\t// Set before the commit so a `change` listener reading styles sees the glide\n\t\tif (host.model.mapping().snap(target) !== host.model.value) host.toggleState('springing', true);\n\t\tthis.#commit(target);\n\t}\n\n\t#startDrag(axis: ValueAxis, event: PointerEvent): DragState {\n\t\tconst host = this.#host;\n\t\tconst mapping = host.model.mapping();\n\t\tconst detent = host.detent();\n\t\tconst value = host.model.value;\n\t\tconst start = {\n\t\t\tfrom: value,\n\t\t\tposition: axis.position(event),\n\t\t\tproportion: axis.fromProportion ?? mapping.proportionOf(value),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\t\tif (detent === undefined) return startDrag(mapping, start);\n\n\t\tconst zonePx = readPxProperty(\n\t\t\tgetComputedStyle(this.#control),\n\t\t\t'--_sonic-detent-zone',\n\t\t\tdetentZonePx,\n\t\t);\n\n\t\treturn startDrag(mapping, {\n\t\t\t...start,\n\t\t\tdetent: { value: detent, zone: zonePx / axis.travelPx },\n\t\t});\n\t}\n\n\t#withinLimit(mapping: ValueMapping, state: DragState): DragState {\n\t\tconst limit = this.#host.model.limit();\n\t\tif (!limit || mapping.isWrapping) return state;\n\n\t\tconst [low, high] = limit;\n\n\t\treturn {\n\t\t\t...state,\n\t\t\trawProportion: clamp(\n\t\t\t\tstate.rawProportion,\n\t\t\t\tmapping.proportionOf(low),\n\t\t\t\tmapping.proportionOf(high),\n\t\t\t),\n\t\t};\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"value-gestures.js","names":[],"sources":["../../../src/elements/value-gestures.ts"],"sourcesContent":["import type { DragState } from '#lib/drag-step.ts';\nimport type { PointerDrag } from '#lib/pointer-drag.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { ReadoutClaim, revealDelay } from '#elements/readout-claim.ts';\nimport { ValueEntry } from '#elements/value-entry.ts';\nimport { dragThresholdPx, startDrag, stepDrag } from '#lib/drag-step.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { clamp } from '#lib/math.ts';\nimport { isMenuPress, isResetPress } from '#lib/modifier-press.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\nimport { resetKeys } from '#lib/value-mapping.ts';\n\nexport interface ValueAxis {\n\tfromProportion?: number;\n\tisKeptOnCancel?: boolean;\n\toutside?: (event: PointerEvent) => boolean;\n\tposition: (event: PointerEvent) => number;\n\ttravelPx: number;\n}\n\ninterface GestureHost {\n\tdefault: () => number | undefined;\n\tdetent: () => number | undefined;\n\tdispatch: (type: 'change' | 'sonic-reveal') => void;\n\tdoublePress: () => 'entry' | 'none' | 'reset';\n\tentryId: string;\n\tentryText: () => string;\n\tforwardNaming: (target: Element, isNamed: boolean) => void;\n\thasEntry: () => boolean;\n\tinput: (next: number) => boolean;\n\tisDisabled: () => boolean;\n\tkeyStep: () => number | undefined;\n\tmodel: ValueModel;\n\tparse: (text: string) => number;\n\trender: () => void;\n\tscrubChanged: () => void;\n\tscrubsKeyRepeat: () => boolean;\n\tspringTarget: () => number | undefined;\n\ttapTarget: () => number | undefined;\n\ttoggleState: (state: string, isOn: boolean) => void;\n}\n\ninterface KeyScrub {\n\tfromValue: number;\n\tkey: string;\n}\n\ninterface ValueDrag extends ValueAxis {\n\tfromValue: number;\n\tisOutside: boolean;\n\tisRevealed: boolean;\n\trevealTimer: ReturnType<typeof setTimeout> | undefined;\n\tstate: DragState;\n}\n\nconst detentZonePx = 8;\n\nexport class ValueGestures {\n\treadonly #claim = new ReadoutClaim(() => {\n\t\tthis.#showRevealed();\n\t\tthis.#host.scrubChanged();\n\t});\n\n\treadonly #control: HTMLElement;\n\n\t#entry: undefined | ValueEntry;\n\n\t// The part that holds focus, the role and the keys; the control itself on all but the strip\n\treadonly #focus: HTMLElement;\n\n\treadonly #host: GestureHost;\n\n\t#isRevealed = false;\n\n\t#keyScrub: KeyScrub | undefined;\n\n\t#pointerDrag: PointerDrag<ValueDrag> | undefined;\n\n\t#pointerType: string | undefined;\n\n\tconstructor(host: GestureHost, control: HTMLElement, focus: HTMLElement) {\n\t\tthis.#host = host;\n\t\tthis.#control = control;\n\t\tthis.#focus = focus;\n\t}\n\n\tabandon(): void {\n\t\tconst drag = this.#dragging();\n\t\tif (!drag) return;\n\n\t\tthis.#host.input(drag.fromValue);\n\t\tthis.#entry?.forgetPress();\n\t\tthis.#pointerDrag?.end();\n\t}\n\n\tbind(signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void {\n\t\tconst control = this.#control;\n\t\tconst host = this.#host;\n\t\tconst entry = this.#bindEntry(signal);\n\n\t\tthis.#pointerDrag = bindDrag<ValueDrag>(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcancel: (drag) => {\n\t\t\t\t\tif (drag.isKeptOnCancel !== true) host.input(drag.fromValue);\n\t\t\t\t},\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tif (isMenuPress(event) || host.isDisabled() || entry.isOpen) return;\n\n\t\t\t\t\thost.toggleState('springing', false);\n\t\t\t\t\t// A click listener above makes WebKit send a tap's compatibility mousedown, which blurs the entry a double tap just opened\n\t\t\t\t\tif (event.pointerType === 'touch') event.preventDefault();\n\t\t\t\t\tfocusByPointer(this.#focus);\n\t\t\t\t\tthis.#pointerType = event.pointerType;\n\t\t\t\t\tif (isResetPress(event)) {\n\t\t\t\t\t\tthis.#reset();\n\t\t\t\t\t\tthis.#pointerType = undefined;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst fromValue = host.model.value;\n\n\t\t\t\t\thost.toggleState('dragging', true);\n\n\t\t\t\t\tconst axis = grab(event);\n\t\t\t\t\tif (!axis) {\n\t\t\t\t\t\thost.toggleState('dragging', false);\n\t\t\t\t\t\tthis.#pointerType = undefined;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst drag: ValueDrag = {\n\t\t\t\t\t\t...axis,\n\t\t\t\t\t\tfromValue,\n\t\t\t\t\t\tisOutside: false,\n\t\t\t\t\t\tisRevealed: false,\n\t\t\t\t\t\trevealTimer: undefined,\n\t\t\t\t\t\tstate: this.#startDrag(axis, event),\n\t\t\t\t\t};\n\n\t\t\t\t\tdrag.revealTimer = setTimeout(() => {\n\t\t\t\t\t\tthis.#reveal(drag);\n\t\t\t\t\t}, revealDelay(control));\n\n\t\t\t\t\treturn drag;\n\t\t\t\t},\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tthis.#pointerType = event.pointerType;\n\t\t\t\t\tthis.#lift(drag, event, entry);\n\t\t\t\t\tthis.#pointerType = undefined;\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\tthis.#dragTo(drag, event);\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tthis.#releaseDrag(drag);\n\t\t\t\t},\n\t\t\t\ttoggle: (isDragging) => {\n\t\t\t\t\thost.toggleState('dragging', isDragging);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'pointercancel',\n\t\t\t() => {\n\t\t\t\tentry.forgetPress();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindKeys(entry, signal);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t\tthis.#concealKeyReveal();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tend(): void {\n\t\tthis.#entry?.close(false);\n\t\tthis.#pointerDrag?.end();\n\t\tthis.#endKeyScrub();\n\t}\n\n\tisEditing(): boolean {\n\t\treturn this.#entry?.isOpen === true;\n\t}\n\n\tisHeld(): boolean {\n\t\treturn this.#dragging() !== undefined || this.#keyScrub !== undefined;\n\t}\n\n\tisRevealed(): boolean {\n\t\treturn this.#claim.isRevealed;\n\t}\n\n\tpointerType(): string | undefined {\n\t\treturn this.#pointerType;\n\t}\n\n\tscrubFrom(): number | undefined {\n\t\treturn this.#dragging()?.fromValue ?? this.#keyScrub?.fromValue;\n\t}\n\n\t#bindEntry(signal: AbortSignal): ValueEntry {\n\t\tconst control = this.#control;\n\t\tconst host = this.#host;\n\t\tconst input = requireChild(control, 'input', HTMLInputElement);\n\n\t\t// Chrome's issues panel flags a form field with no id or name; a `name` would submit it\n\t\tinput.id = host.entryId;\n\n\t\tconst entry = new ValueEntry(input, {\n\t\t\tcommit: (text) => {\n\t\t\t\tthis.#commit(host.parse(text));\n\t\t\t},\n\t\t\ttext: host.entryText,\n\t\t\ttoggle: (isOpen) => {\n\t\t\t\tif (isOpen) host.forwardNaming(input, true);\n\t\t\t\thost.toggleState('editing', isOpen);\n\t\t\t\thost.render();\n\t\t\t},\n\t\t});\n\n\t\tthis.#entry = entry;\n\t\tentry.bind(this.#focus, signal);\n\n\t\treturn entry;\n\t}\n\n\t#bindKeys(entry: ValueEntry, signal: AbortSignal): void {\n\t\tconst control = this.#control;\n\t\tconst host = this.#host;\n\n\t\tcontrol.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (host.isDisabled() || event.defaultPrevented || event.target !== this.#focus) return;\n\n\t\t\t\thost.toggleState('springing', false);\n\t\t\t\tif (event.key === 'Enter') {\n\t\t\t\t\tif (host.springTarget() !== undefined || !host.hasEntry()) return;\n\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tentry.open();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst next = this.#keyTarget(event.key);\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.#claim.reveal('keys');\n\t\t\t\tthis.#showRevealed();\n\t\t\t\tthis.#keyTo(event, next);\n\t\t\t\thost.scrubChanged();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tcontrol.addEventListener(\n\t\t\t'keyup',\n\t\t\t(event) => {\n\t\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\t\tconst isMeta = event.key === 'Meta';\n\n\t\t\t\tif (isMeta || event.key === this.#keyScrub?.key) this.#endKeyScrub();\n\t\t\t\tif (isMeta && this.#dragging()) return;\n\t\t\t\tif (isMeta || host.model.mapping().keyTarget(event.key, host.model.value) !== undefined) {\n\t\t\t\t\tthis.#springBack();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#focus.addEventListener(\n\t\t\t'blur',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t\tthis.#concealKeyReveal();\n\t\t\t\tthis.#springBack();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#commit(next: number, direction: -1 | 0 | 1 = 0): void {\n\t\tconst host = this.#host;\n\t\tconst landed = Number.isFinite(next) ? host.model.land(next, direction) : next;\n\n\t\tif (host.input(landed)) host.dispatch('change');\n\t}\n\n\t#concealKeyReveal(): void {\n\t\tif (!this.#claim.conceal('keys')) return;\n\n\t\tthis.#showRevealed();\n\t\tthis.#host.scrubChanged();\n\t}\n\n\t#crossCancelZone(drag: ValueDrag, value: number): void {\n\t\tconst host = this.#host;\n\t\tconst isOutside = !drag.isOutside;\n\n\t\tdrag.isOutside = isOutside;\n\t\thost.toggleState('cancelling', isOutside);\n\t\tif (isOutside) this.#renderDragReveal(drag);\n\t\thost.input(isOutside ? drag.fromValue : value);\n\t\tif (!isOutside) this.#renderDragReveal(drag);\n\t}\n\n\t#dragging(): undefined | ValueDrag {\n\t\treturn this.#pointerDrag?.current();\n\t}\n\n\t#dragTo(drag: ValueDrag, event: PointerEvent): void {\n\t\tconst host = this.#host;\n\t\tconst isOutside = drag.outside?.(event) === true;\n\t\tconst wasEngaged = drag.state.isEngaged;\n\t\tconst mapping = host.model.mapping();\n\t\tconst { state, value } = stepDrag(mapping, drag.state, {\n\t\t\tisFine: event.shiftKey,\n\t\t\tisOutside,\n\t\t\tposition: drag.position(event),\n\t\t});\n\n\t\tdrag.state = this.#withinLimit(mapping, state);\n\t\tif (value === undefined) return;\n\n\t\tif (!wasEngaged) this.#reveal(drag);\n\n\t\tif (isOutside === drag.isOutside) host.input(isOutside ? drag.fromValue : value);\n\t\telse this.#crossCancelZone(drag, value);\n\t}\n\n\t#endKeyScrub(): void {\n\t\tconst host = this.#host;\n\t\tconst scrub = this.#keyScrub;\n\t\tif (!scrub) return;\n\n\t\tthis.#keyScrub = undefined;\n\t\thost.scrubChanged();\n\t\tif (host.model.value !== scrub.fromValue) host.dispatch('change');\n\t}\n\n\t#keyTarget(key: string): number | undefined {\n\t\tconst host = this.#host;\n\n\t\tif (resetKeys.has(key)) return host.default();\n\n\t\treturn host.model.mapping().keyTarget(key, host.model.value, host.keyStep());\n\t}\n\n\t#keyTo(event: KeyboardEvent, next: number): void {\n\t\tconst host = this.#host;\n\n\t\tif (event.repeat && host.scrubsKeyRepeat()) {\n\t\t\tthis.#scrubKey(event.key, next);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#endKeyScrub();\n\t\tthis.#commit(next, next > host.model.value ? 1 : -1);\n\t}\n\n\t#lift(drag: ValueDrag, event: PointerEvent, entry: ValueEntry): void {\n\t\tconst host = this.#host;\n\t\tconst tap = drag.state.isEngaged ? undefined : host.tapTarget();\n\n\t\tif (tap !== undefined) {\n\t\t\tentry.forgetPress();\n\t\t\tthis.#commit(tap);\n\t\t\treturn;\n\t\t}\n\t\tif (!entry.press(event) || host.springTarget() !== undefined) return;\n\n\t\tconst gesture = host.doublePress();\n\n\t\tif (gesture === 'reset') this.#reset();\n\t\telse if (gesture === 'entry' && host.hasEntry()) entry.open();\n\t}\n\n\t#releaseDrag(drag: ValueDrag): void {\n\t\tconst host = this.#host;\n\n\t\tclearTimeout(drag.revealTimer);\n\t\tthis.#claim.conceal('drag');\n\t\thost.toggleState('cancelling', false);\n\t\thost.scrubChanged();\n\t\tthis.#showRevealed();\n\t\tif (host.model.value !== drag.fromValue) host.dispatch('change');\n\t\tthis.#springBack();\n\t\tthis.#pointerType = undefined;\n\t}\n\n\t#renderDragReveal(drag: ValueDrag): void {\n\t\tconst isRevealed = drag.isRevealed && !drag.isOutside;\n\n\t\tif (isRevealed) this.#claim.reveal('drag');\n\t\telse this.#claim.conceal('drag');\n\t\tthis.#host.scrubChanged();\n\t\tthis.#showRevealed();\n\t}\n\n\t#reset(): void {\n\t\tthis.#commit(this.#host.default() ?? NaN);\n\t}\n\n\t#reveal(drag: ValueDrag): void {\n\t\tif (drag.isRevealed || this.#dragging() !== drag) return;\n\n\t\tdrag.isRevealed = true;\n\t\tthis.#renderDragReveal(drag);\n\t}\n\n\t#scrubKey(key: string, next: number): void {\n\t\tconst host = this.#host;\n\n\t\tif (this.#keyScrub?.key !== key) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tthis.#keyScrub = { fromValue: host.model.value, key };\n\t\t}\n\t\thost.input(next);\n\t}\n\n\t#showRevealed(): void {\n\t\tconst { isRevealed } = this.#claim;\n\t\tif (isRevealed === this.#isRevealed) return;\n\n\t\tthis.#isRevealed = isRevealed;\n\t\tthis.#host.toggleState('revealed', isRevealed);\n\t\tthis.#host.dispatch('sonic-reveal');\n\t}\n\n\t#springBack(): void {\n\t\tconst host = this.#host;\n\t\tconst target = host.springTarget();\n\t\tif (target === undefined) return;\n\n\t\t// Set before the commit so a `change` listener reading styles sees the glide\n\t\tif (host.model.mapping().snap(target) !== host.model.value) host.toggleState('springing', true);\n\t\tthis.#commit(target);\n\t}\n\n\t#startDrag(axis: ValueAxis, event: PointerEvent): DragState {\n\t\tconst host = this.#host;\n\t\tconst mapping = host.model.mapping();\n\t\tconst detent = host.detent();\n\t\tconst value = host.model.value;\n\t\tconst start = {\n\t\t\tfrom: value,\n\t\t\tposition: axis.position(event),\n\t\t\tproportion: axis.fromProportion ?? mapping.proportionOf(value),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravelPx: axis.travelPx,\n\t\t};\n\t\tif (detent === undefined) return startDrag(mapping, start);\n\n\t\tconst zonePx = readPxProperty(\n\t\t\tgetComputedStyle(this.#control),\n\t\t\t'--_sonic-detent-zone',\n\t\t\tdetentZonePx,\n\t\t);\n\n\t\treturn startDrag(mapping, {\n\t\t\t...start,\n\t\t\tdetent: { value: detent, zone: zonePx / axis.travelPx },\n\t\t});\n\t}\n\n\t#withinLimit(mapping: ValueMapping, state: DragState): DragState {\n\t\tconst limit = this.#host.model.limit();\n\t\tif (!limit || mapping.isWrapping) return state;\n\n\t\tconst [low, high] = limit;\n\n\t\treturn {\n\t\t\t...state,\n\t\t\trawProportion: clamp(\n\t\t\t\tstate.rawProportion,\n\t\t\t\tmapping.proportionOf(low),\n\t\t\t\tmapping.proportionOf(high),\n\t\t\t),\n\t\t};\n\t}\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { SonicValueElement, ValueNotation } from "./value-element.js";
2
2
  import { Seconds } from "../lib/units.js";
3
3
  import { Surface, SurfaceFrame } from "../lib/canvas-surface.js";
4
+ import { Point } from "../lib/marker-band.js";
4
5
  //#region src/elements/wave-element.d.ts
5
6
  declare global {
6
7
  interface HTMLElementEventMap {
@@ -34,13 +35,18 @@ export declare abstract class SonicWaveElement<Colour extends string, Length ext
34
35
  protected abstract readonly sheet: string;
35
36
  protected abstract readonly sizeProperty: `--_sonic-${string}`;
36
37
  attributeChangedCallback(name: string): void;
38
+ abstract clientXOf(value: Seconds): number;
37
39
  connectedCallback(): void;
40
+ /** Copies; none outside the canvas */
41
+ markersFromPoint(clientX: number, clientY: number): Array<WaveMarker>;
38
42
  repaint(): void;
43
+ /** The time at a viewport x, clamped to the bounds and not snapped to the step */
44
+ abstract valueFromPoint(clientX: number, clientY: number): Seconds;
39
45
  protected connect(signal: AbortSignal): void;
40
- protected focusTarget(): HTMLElement;
41
46
  protected abstract isEmpty(): boolean;
42
47
  protected markerList(): ReadonlyArray<WaveMarker>;
43
48
  protected markerProportions(): Array<[number, number]>;
49
+ protected abstract markersInReach(point: Point, widthPx: number): Array<number>;
44
50
  protected abstract paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length, Numeric>): void;
45
51
  protected proportionsChanged(): void;
46
52
  protected renderEmpty(): void;
@@ -48,6 +48,18 @@ var SonicWaveElement = class extends SonicValueElement {
48
48
  this.upgradeProperties("fill", "markers");
49
49
  super.connectedCallback();
50
50
  }
51
+ /** Copies; none outside the canvas */
52
+ markersFromPoint(clientX, clientY) {
53
+ const box = this.canvas.getBoundingClientRect();
54
+ if (!(clientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom)) return [];
55
+ return this.markersInReach({
56
+ x: clientX - box.left,
57
+ y: clientY - box.top
58
+ }, box.width).flatMap((index) => {
59
+ const marker = this.#markers[index];
60
+ return marker ? [{ ...marker }] : [];
61
+ });
62
+ }
51
63
  repaint() {
52
64
  this.#surface?.rebuild();
53
65
  }
@@ -79,9 +91,6 @@ var SonicWaveElement = class extends SonicValueElement {
79
91
  });
80
92
  this.#surface.setFill(this.fill);
81
93
  }
82
- focusTarget() {
83
- return this.control;
84
- }
85
94
  markerList() {
86
95
  return this.#markers;
87
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> extends SonicValueElement {\n\tstatic override readonly observedAttributes = [...SonicValueElement.observedAttributes, 'fill'];\n\n\tget currentMarker(): undefined | WaveMarker {\n\t\treturn this.#current ? { ...this.#current } : undefined;\n\t}\n\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\tget markers(): Array<WaveMarker> {\n\t\treturn this.#markers.map((marker) => ({ ...marker }));\n\t}\n\n\tset markers(list: Iterable<WaveMarker> | undefined) {\n\t\tthis.#markers = [...(list ?? [])]\n\t\t\t.filter((marker) => Number.isFinite(marker.start))\n\t\t\t.map((marker) => ({ ...marker }))\n\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\tif (!this.isBound()) return;\n\n\t\tthis.renderMarkers();\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.renderEmpty();\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tfill: { control, sizeProperty: this.sizeProperty },\n\t\t\tlengths: this.lengths,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(control, this.sheet, {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected abstract isEmpty(): boolean;\n\n\tprotected markerList(): ReadonlyArray<WaveMarker> {\n\t\treturn this.#markers;\n\t}\n\n\tprotected markerProportions(): Array<[number, number]> {\n\t\tconst mapping = this.mapping();\n\n\t\treturn this.#markers.map(({ end, start }) => {\n\t\t\tconst at = mapping.proportionOf(start);\n\t\t\tconst to = end === undefined ? at : mapping.proportionOf(end);\n\n\t\t\treturn [Math.min(at, to), Math.max(at, to)];\n\t\t});\n\t}\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length, Numeric>,\n\t): void;\n\n\tprotected override proportionsChanged(): void {\n\t\tthis.renderMarkers();\n\t}\n\n\tprotected renderEmpty(): void {\n\t\tthis.toggleState('empty', this.isEmpty());\n\t}\n\n\tprotected abstract renderMarkers(): void;\n\n\tprotected override scrubChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"wave-element.js","names":[],"sources":["../../../src/elements/wave-element.ts"],"sourcesContent":["import type { ValueNotation } from '#elements/value-element.ts';\nimport type { Surface, SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { bindSurface } from '#lib/canvas-surface.ts';\nimport { formatClock, parseClock } from '#lib/clock.ts';\nimport { spokenDuration } from '#lib/spoken-duration.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-marker': Event;\n\t}\n}\n\nexport interface WaveMarker {\n\t// Open to a consumer's own keys, which `renderLabel` draws from\n\t[key: string]: unknown;\n\tdimmed?: boolean;\n\t/** Makes the marker a region */\n\tend?: Seconds;\n\t/** Lowercase letters, digits and hyphens; colours the marker through `--sonic-marker-<kind>` */\n\tkind?: string;\n\tlabel?: string;\n\tstart: Seconds;\n}\n\nfunction markerKey(marker: undefined | WaveMarker): string {\n\treturn marker ? [marker.start, marker.end, marker.kind, marker.label].join('|') : '';\n}\n\nexport abstract class SonicWaveElement<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> extends SonicValueElement {\n\tstatic override readonly observedAttributes = [...SonicValueElement.observedAttributes, 'fill'];\n\n\tget currentMarker(): undefined | WaveMarker {\n\t\treturn this.#current ? { ...this.#current } : undefined;\n\t}\n\n\tget fill(): boolean {\n\t\treturn this.hasAttribute('fill');\n\t}\n\n\tset fill(isFilling: boolean) {\n\t\tthis.reflect('fill', isFilling);\n\t}\n\n\tget markers(): Array<WaveMarker> {\n\t\treturn this.#markers.map((marker) => ({ ...marker }));\n\t}\n\n\tset markers(list: Iterable<WaveMarker> | undefined) {\n\t\tthis.#markers = [...(list ?? [])]\n\t\t\t.filter((marker) => Number.isFinite(marker.start))\n\t\t\t.map((marker) => ({ ...marker }))\n\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\tif (!this.isBound()) return;\n\n\t\tthis.renderMarkers();\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly canvas: HTMLCanvasElement;\n\n\tprotected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;\n\n\tprotected abstract readonly control: HTMLElement;\n\n\tprotected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;\n\n\tprotected override readonly notation: ValueNotation = {\n\t\tformat: formatClock,\n\t\tparse: parseClock,\n\t\tspeak: (seconds) => spokenDuration(seconds, this.closest<HTMLElement>('[lang]')?.lang),\n\t};\n\n\tprotected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;\n\n\tprotected abstract readonly sheet: string;\n\n\tprotected abstract readonly sizeProperty: `--_sonic-${string}`;\n\n\t#current: undefined | WaveMarker;\n\n\t#markers: Array<WaveMarker> = [];\n\n\t#surface: Surface | undefined;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'fill') this.#surface?.setFill(this.fill);\n\t}\n\n\tabstract clientXOf(value: Seconds): number;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('fill', 'markers');\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** Copies; none outside the canvas */\n\tmarkersFromPoint(clientX: number, clientY: number): Array<WaveMarker> {\n\t\tconst box = this.canvas.getBoundingClientRect();\n\t\tconst isInside =\n\t\t\tclientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom;\n\t\tif (!isInside) return [];\n\n\t\treturn this.markersInReach({ x: clientX - box.left, y: clientY - box.top }, box.width).flatMap(\n\t\t\t(index) => {\n\t\t\t\tconst marker = this.#markers[index];\n\n\t\t\t\treturn marker ? [{ ...marker }] : [];\n\t\t\t},\n\t\t);\n\t}\n\n\trepaint(): void {\n\t\tthis.#surface?.rebuild();\n\t}\n\n\t/** The time at a viewport x, clamped to the bounds and not snapped to the step */\n\tabstract valueFromPoint(clientX: number, clientY: number): Seconds;\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst control = this.control;\n\n\t\tthis.keepControl(control, signal);\n\t\tthis.renderEmpty();\n\t\tthis.#surface = bindSurface({\n\t\t\tcanvas: this.canvas,\n\t\t\tcolours: this.colours,\n\t\t\tfill: { control, sizeProperty: this.sizeProperty },\n\t\t\tlengths: this.lengths,\n\t\t\tnumbers: this.numbers,\n\t\t\tpaint: (context, frame) => {\n\t\t\t\tthis.paint(context, frame);\n\t\t\t},\n\t\t\tresize: () => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\tsignal,\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(control, this.sheet, {\n\t\t\t\tproperty: 'margin-bottom',\n\t\t\t\tselector: '[popover]',\n\t\t\t});\n\t\tthis.#surface.setFill(this.fill);\n\t}\n\n\tprotected abstract isEmpty(): boolean;\n\n\tprotected markerList(): ReadonlyArray<WaveMarker> {\n\t\treturn this.#markers;\n\t}\n\n\tprotected markerProportions(): Array<[number, number]> {\n\t\tconst mapping = this.mapping();\n\n\t\treturn this.#markers.map(({ end, start }) => {\n\t\t\tconst at = mapping.proportionOf(start);\n\t\t\tconst to = end === undefined ? at : mapping.proportionOf(end);\n\n\t\t\treturn [Math.min(at, to), Math.max(at, to)];\n\t\t});\n\t}\n\n\tprotected abstract markersInReach(point: Point, widthPx: number): Array<number>;\n\n\tprotected abstract paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\tframe: SurfaceFrame<Colour, Length, Numeric>,\n\t): void;\n\n\tprotected override proportionsChanged(): void {\n\t\tthis.renderMarkers();\n\t}\n\n\tprotected renderEmpty(): void {\n\t\tthis.toggleState('empty', this.isEmpty());\n\t}\n\n\tprotected abstract renderMarkers(): void;\n\n\tprotected override scrubChanged(): void {\n\t\tthis.render();\n\t}\n\n\tprotected showCurrentMarker(seconds: number): void {\n\t\tconst current = this.#markers.findLast((marker) => marker.start <= seconds);\n\t\tif (markerKey(current) === markerKey(this.#current)) return;\n\n\t\tthis.#current = current;\n\t\tthis.dispatchEvent(new Event('sonic-marker', { bubbles: true }));\n\t}\n\n\tprotected surface(): Surface | undefined {\n\t\treturn this.#surface;\n\t}\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { Milliseconds, PixelsPerSecond, Seconds } from "../lib/units.js";
2
2
  import { TimeRegions } from "../lib/time-regions.js";
3
3
  import { SurfaceFrame } from "../lib/canvas-surface.js";
4
+ import { Point } from "../lib/marker-band.js";
4
5
  import { SonicWaveElement, WaveMarker } from "./wave-element.js";
5
6
  import { PeaksRequest } from "../lib/peaks-request.js";
6
7
  import { WaveformBands } from "../lib/waveform-bands.js";
@@ -74,10 +75,16 @@ export declare class SonicWaveform extends SonicWaveElement<Colour, never, Numer
74
75
  protected readonly sheet = "waveform.css";
75
76
  protected readonly sizeProperty = "--_sonic-waveform-size";
76
77
  attributeChangedCallback(name: string): void;
78
+ /** Viewport x of a time as last drawn; not clamped to the window */
79
+ clientXOf(value: Seconds): number;
77
80
  connectedCallback(): void;
81
+ /** The time at a viewport x as last drawn */
82
+ valueFromPoint(clientX: number, _clientY: number): Seconds;
78
83
  protected connect(signal: AbortSignal): void;
79
84
  protected draw(): void;
85
+ protected focusTarget(): HTMLElement;
80
86
  protected isEmpty(): boolean;
87
+ protected markersInReach({ x }: Point, widthPx: number): Array<number>;
81
88
  protected paint(context: CanvasRenderingContext2D, { frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>): void;
82
89
  protected renderMarkers(): void;
83
90
  protected scrubChanged(): void;
@@ -15,6 +15,7 @@ import { bindZoom, bindZoomKeys } from "../lib/zoom-gesture.js";
15
15
  const defaultZoom = 70;
16
16
  const defaultZoomMin = 20;
17
17
  const defaultZoomMax = 280;
18
+ const lineReachPx = 4;
18
19
  const renderWaveform = template(`
19
20
  <div class="sonic-waveform" role="slider" tabindex="0">
20
21
  <canvas class="sonic-waveform-canvas" aria-hidden="true"></canvas>
@@ -201,10 +202,21 @@ var SonicWaveform = class extends SonicWaveElement {
201
202
  super.attributeChangedCallback(name);
202
203
  if (name === "zoomable") this.#zoomGesture?.sync();
203
204
  }
205
+ /** Viewport x of a time as last drawn; not clamped to the window */
206
+ clientXOf(value) {
207
+ const { left, width } = this.canvas.getBoundingClientRect();
208
+ return left + this.#drawnX(value, width);
209
+ }
204
210
  connectedCallback() {
205
211
  this.upgradeProperties("bands", "peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
206
212
  super.connectedCallback();
207
213
  }
214
+ /** The time at a viewport x as last drawn */
215
+ valueFromPoint(clientX, _clientY) {
216
+ const { left, width } = this.canvas.getBoundingClientRect();
217
+ const { startSeconds, zoom } = this.#drawnWindow(width);
218
+ return clamp(startSeconds + (clientX - left) / zoom, ...this.mapping().bounds);
219
+ }
208
220
  connect(signal) {
209
221
  super.connect(signal);
210
222
  const zoomTarget = {
@@ -231,9 +243,18 @@ var SonicWaveform = class extends SonicWaveElement {
231
243
  surface?.requestFrame();
232
244
  if (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());
233
245
  }
246
+ focusTarget() {
247
+ return this.control;
248
+ }
234
249
  isEmpty() {
235
250
  return !this.#peaks || this.#peaks.samples.length === 0;
236
251
  }
252
+ markersInReach({ x }, widthPx) {
253
+ return this.markerList().map(({ start }, index) => ({
254
+ distance: Math.abs(this.#drawnX(start, widthPx) - x),
255
+ index
256
+ })).filter(({ distance }) => distance <= lineReachPx).toSorted((first, second) => first.distance - second.distance).map(({ index }) => index);
257
+ }
237
258
  paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
238
259
  const held = this.#held();
239
260
  const isPaged = look.isReducedMotion && this.reducedMotion === "page";
@@ -258,7 +279,8 @@ var SonicWaveform = class extends SonicWaveElement {
258
279
  const { seconds, view } = timeline;
259
280
  this.#drawn = {
260
281
  seconds,
261
- startSeconds: view.startSeconds
282
+ startSeconds: view.startSeconds,
283
+ zoom: this.zoom
262
284
  };
263
285
  placeLine(this.#playhead, timeline.playheadAt);
264
286
  placeLine(this.#ghost, timeline.ghostAt);
@@ -281,7 +303,10 @@ var SonicWaveform = class extends SonicWaveElement {
281
303
  }, {
282
304
  bands: this.#tint(isRebuilt),
283
305
  kindColours: this.#kindColours,
284
- markers: this.markerList(),
306
+ markers: this.markerList().map((marker) => ({
307
+ ...marker,
308
+ start: clamp(marker.start, ...range)
309
+ })),
285
310
  peaks: this.#peaks,
286
311
  range
287
312
  });
@@ -311,6 +336,17 @@ var SonicWaveform = class extends SonicWaveElement {
311
336
  scrubsKeyRepeat() {
312
337
  return true;
313
338
  }
339
+ #drawnWindow(widthPx) {
340
+ const zoom = this.#drawn?.zoom ?? this.zoom;
341
+ return {
342
+ startSeconds: this.#drawn?.startSeconds ?? this.value - widthPx / zoom / 2,
343
+ zoom
344
+ };
345
+ }
346
+ #drawnX(value, widthPx) {
347
+ const { startSeconds, zoom } = this.#drawnWindow(widthPx);
348
+ return (clamp(value, ...this.mapping().bounds) - startSeconds) * zoom;
349
+ }
314
350
  #duePending() {
315
351
  const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
316
352
  if (due.length < this.#pending.length) this.surface()?.requestFrame();
@@ -1 +1 @@
1
- {"version":3,"file":"waveform.js","names":[],"sources":["../../../src/elements/waveform.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Drawn } from '#lib/frame-timeline.ts';\nimport type { PeaksRequest } from '#lib/peaks-request.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Milliseconds, PixelsPerSecond, Seconds } from '#lib/units.ts';\nimport type { BandChannels, WaveformBands } from '#lib/waveform-bands.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\nimport type { WaveformTint } from '#lib/waveform-scene.ts';\nimport type { ZoomGesture } from '#lib/zoom-gesture.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { frameTimeline } from '#lib/frame-timeline.ts';\nimport { isKind, writeKind } from '#lib/marker-band.ts';\nimport { createLabelRider } from '#lib/marker-rider.ts';\nimport { clamp, roundTo } from '#lib/math.ts';\nimport { createPeaksRequest } from '#lib/peaks-request.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { bandTable } from '#lib/waveform-bands.ts';\nimport {\n\tbandChannels,\n\tpaintWaveform,\n\twaveformColours,\n\twaveformNumbers,\n} from '#lib/waveform-scene.ts';\nimport { bindZoom, bindZoomKeys } from '#lib/zoom-gesture.ts';\n\nexport type { PeaksRequest } from '#lib/peaks-request.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-zoom': Event;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-waveform': SonicWaveform;\n\t}\n}\n\ntype Colour = keyof typeof waveformColours;\n\ntype Numeric = keyof typeof waveformNumbers;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\n\nconst renderWaveform = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-waveform\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-waveform-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-waveform-scrim\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-ghost\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-playhead\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-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-waveform-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction placeLine(line: HTMLElement, at: number | undefined): void {\n\tconst isHidden = at === undefined || at < 0 || at > 1;\n\n\tif (line.hidden !== isHidden) line.hidden = isHidden;\n\tif (isHidden) return;\n\n\tconst translate = `${String(at * 100)}cqi`;\n\n\tif (line.style.getPropertyValue('translate') !== translate) {\n\t\tline.style.setProperty('translate', translate);\n\t}\n}\n\nexport class SonicWaveform extends SonicWaveElement<Colour, never, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicWaveElement.observedAttributes,\n\t\t'pending-delay',\n\t\t'playing',\n\t\t'playback-rate',\n\t\t'reduced-motion',\n\t\t'zoom',\n\t\t'zoom-max',\n\t\t'zoom-min',\n\t\t'zoomable',\n\t];\n\n\tget bands(): undefined | WaveformBands {\n\t\treturn this.#bands;\n\t}\n\n\tset bands(bands: undefined | WaveformBands) {\n\t\tthis.#bands = bands;\n\t\tthis.#bandTable = bandTable(bands);\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\tget peaks(): undefined | WaveformPeaks {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: undefined | WaveformPeaks) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\t/** Spans being fetched; drawn as placeholders until cleared */\n\tget pending(): Array<[Seconds, Seconds]> {\n\t\treturn this.#pending.map(([start, end]) => [start, end]);\n\t}\n\n\tset pending(regions: TimeRegions | undefined) {\n\t\tconst pending = regions ? readRegions(regions) : [];\n\t\tif (String(pending) === String(this.#pending)) return;\n\n\t\tconst listedMs = this.#pendingListedMs;\n\t\tconst nowMs = performance.now();\n\n\t\tthis.#pending = pending;\n\t\tthis.#pendingListedMs = new Map(\n\t\t\tthis.#pending.map((region) => {\n\t\t\t\tconst key = region.join(':');\n\n\t\t\t\treturn [key, listedMs.get(key) ?? nowMs];\n\t\t\t}),\n\t\t);\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\t/** How long a span stays listed in `pending` before its placeholder draws */\n\tget pendingDelay(): Milliseconds {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: Milliseconds | undefined) {\n\t\tthis.reflect('pending-delay', value);\n\t}\n\n\tget playbackRate(): number {\n\t\tconst playbackRate = this.numberAttribute('playback-rate', 1);\n\n\t\treturn playbackRate > 0 ? playbackRate : 1;\n\t}\n\n\tset playbackRate(value: number | undefined) {\n\t\tthis.reflect('playback-rate', value);\n\t}\n\n\tget playing(): boolean {\n\t\treturn this.hasAttribute('playing');\n\t}\n\n\tset playing(isPlaying: boolean) {\n\t\tthis.reflect('playing', isPlaying);\n\t}\n\n\t/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */\n\tget readTime(): (() => Seconds | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => Seconds | undefined) | undefined) {\n\t\tthis.#readTime = read;\n\t\tthis.render();\n\t}\n\n\tget reducedMotion(): 'page' | 'scroll' {\n\t\treturn this.getAttribute('reduced-motion') === 'scroll' ? 'scroll' : 'page';\n\t}\n\n\tset reducedMotion(motion: 'page' | 'scroll' | undefined) {\n\t\tthis.reflect('reduced-motion', motion);\n\t}\n\n\tget renderLabel(): LabelRender | undefined {\n\t\treturn this.#renderLabel;\n\t}\n\n\tset renderLabel(render: LabelRender | undefined) {\n\t\tthis.#renderLabel = render;\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tget requestPeaks(): PeaksRequest | undefined {\n\t\treturn this.#requestPeaks;\n\t}\n\n\tset requestPeaks(request: PeaksRequest | undefined) {\n\t\tthis.#requestPeaks = request;\n\t\tthis.#peaksRequest.reset();\n\t}\n\n\tget zoom(): PixelsPerSecond {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom', value);\n\t}\n\n\tget zoomable(): boolean {\n\t\treturn this.hasAttribute('zoomable');\n\t}\n\n\tset zoomable(isZoomable: boolean) {\n\t\tthis.reflect('zoomable', isZoomable);\n\t}\n\n\tget zoomMax(): PixelsPerSecond {\n\t\tconst zoomMax = this.numberAttribute('zoom-max', defaultZoomMax);\n\n\t\treturn Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);\n\t}\n\n\tset zoomMax(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): PixelsPerSecond {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected readonly control = renderWaveform();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-waveform-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = waveformColours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly numbers = waveformNumbers;\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\t#bandColours: Array<BandChannels | undefined> = [];\n\n\t#bands: undefined | WaveformBands;\n\n\t#bandTable: ReturnType<typeof bandTable>;\n\n\treadonly #clock = createTrackingClock();\n\n\t#drawn: Drawn | undefined;\n\n\treadonly #ghost = requireChild(this.control, '.sonic-waveform-ghost', HTMLDivElement);\n\n\t#grabbed: Drawn | undefined;\n\n\t#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\treadonly #peaksRequest = createPeaksRequest(() => {\n\t\tthis.surface()?.invalidate();\n\t});\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\n\n\treadonly #playhead = requireChild(this.control, '.sonic-waveform-playhead', HTMLDivElement);\n\n\t#readTime: (() => number | undefined) | undefined;\n\n\t#renderLabel: LabelRender | undefined;\n\n\t#requestPeaks: PeaksRequest | undefined;\n\n\treadonly #rider = createLabelRider(this.control, {\n\t\tclassName: 'sonic-waveform-label',\n\t\tcolourProperty: '--_sonic-waveform-marker',\n\t\tfadeProperties: ['--_sonic-waveform-label-fade-start', '--_sonic-waveform-label-fade-end'],\n\t\tinsetProperty: '--_sonic-waveform-label-inset',\n\t\tparkProperty: '--_sonic-waveform-label-park',\n\t});\n\n\t#strip: CanvasRenderingContext2D | undefined;\n\n\t#zoomGesture: undefined | ZoomGesture;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#zoomGesture?.sync();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'bands',\n\t\t\t'peaks',\n\t\t\t'pending',\n\t\t\t'pendingDelay',\n\t\t\t'playing',\n\t\t\t'playbackRate',\n\t\t\t'readTime',\n\t\t\t'reducedMotion',\n\t\t\t'renderLabel',\n\t\t\t'requestPeaks',\n\t\t\t'zoom',\n\t\t\t'zoomable',\n\t\t\t'zoomMax',\n\t\t\t'zoomMin',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\n\t\tconst zoomTarget = {\n\t\t\tclaim: () => {\n\t\t\t\tthis.abandonHold();\n\t\t\t},\n\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\tisZoomable: () => this.zoomable,\n\t\t\tzoom: () => this.zoom,\n\t\t\tzoomTo: (target: number) => {\n\t\t\t\tthis.#zoomTo(target);\n\t\t\t},\n\t\t};\n\n\t\tthis.#zoomGesture = bindZoom(this.control, zoomTarget, signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\t// After the value keys, which stand down once a key is taken\n\t\tbindZoomKeys(this.control, zoomTarget, signal);\n\t\tif (!('fonts' in document)) return;\n\n\t\tdocument.fonts.addEventListener(\n\t\t\t'loadingdone',\n\t\t\t() => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tsurface?.requestFrame();\n\t\t// No frame follows the position out of view, or before the first one paints\n\t\tif (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.samples.length === 0;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tconst held = this.#held();\n\t\tconst isPaged = look.isReducedMotion && this.reducedMotion === 'page';\n\t\tconst pending = this.#duePending();\n\t\tconst range: [number, number] = [this.min, Math.max(this.min, this.max)];\n\t\tconst timeline = frameTimeline({\n\t\t\tclockSeconds: this.#clock.read(frameMs, {\n\t\t\t\tisPlaying: this.playing,\n\t\t\t\tplaybackRate: this.playbackRate,\n\t\t\t\tseconds: this.#sourceSeconds(),\n\t\t\t}),\n\t\t\tframeMs,\n\t\t\t...(held ? { held } : {}),\n\t\t\tisPaged,\n\t\t\tisPlaying: this.playing,\n\t\t\tisStill: look.isReducedMotion,\n\t\t\tpending,\n\t\t\trange,\n\t\t\tsize,\n\t\t\tzoom: this.zoom,\n\t\t});\n\t\tconst { seconds, view } = timeline;\n\n\t\tthis.#drawn = { seconds, startSeconds: view.startSeconds };\n\t\tplaceLine(this.#playhead, timeline.playheadAt);\n\t\tplaceLine(this.#ghost, timeline.ghostAt);\n\t\tthis.showCurrentMarker(seconds);\n\t\tthis.toggleState('pending', view.pending.length > 0);\n\t\tthis.#rider.place({\n\t\t\tisPaged,\n\t\t\tplayheadSeconds: seconds,\n\t\t\tstartSeconds: view.startSeconds,\n\t\t\twidthPx: view.width / view.dpr,\n\t\t\twindowSeconds: view.width / view.pixelsPerSecond,\n\t\t});\n\t\tif (isDirty || timeline.paintKey !== this.#paintedKey) {\n\t\t\tthis.#paintedKey = timeline.paintKey;\n\t\t\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\t\t\tpaintWaveform(\n\t\t\t\tcontext,\n\t\t\t\t{ ...view, colours: look.colours, numbers: look.numbers },\n\t\t\t\t{\n\t\t\t\t\tbands: this.#tint(isRebuilt),\n\t\t\t\t\tkindColours: this.#kindColours,\n\t\t\t\t\tmarkers: this.markerList(),\n\t\t\t\t\tpeaks: this.#peaks,\n\t\t\t\t\trange,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.#peaksRequest.ask(this.#requestPeaks, timeline.wanted);\n\t\t}\n\t\tif (timeline.isMoving) this.surface()?.requestFrame();\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tconst bounds = this.mapping().bounds;\n\n\t\tconst render = this.#renderLabel;\n\n\t\tthis.#rider.measure(\n\t\t\tthis.markers.flatMap((marker) =>\n\t\t\t\tmarker.label === undefined || marker.label === ''\n\t\t\t\t\t? []\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tisDimmed: marker.dimmed === true,\n\t\t\t\t\t\t\t\t...(marker.kind === undefined ? {} : { kind: marker.kind }),\n\t\t\t\t\t\t\t\tstart: clamp(marker.start, ...bounds),\n\t\t\t\t\t\t\t\ttext: marker.label,\n\t\t\t\t\t\t\t\t...(render\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\twrite: (element: HTMLElement) => {\n\t\t\t\t\t\t\t\t\t\t\t\trender(marker, element);\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t),\n\t\t);\n\t\tthis.surface()?.rebuild();\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tconst isScrubbing = this.scrubState().from !== undefined;\n\n\t\tthis.#grabbed = isScrubbing ? (this.#grabbed ?? this.#lastDrawn()) : undefined;\n\t\tsuper.scrubChanged();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t#duePending(): Array<[number, number]> {\n\t\tconst due = dueRegions(this.#pending, this.#pendingListedMs, [\n\t\t\tperformance.now(),\n\t\t\tthis.pendingDelay,\n\t\t]);\n\n\t\tif (due.length < this.#pending.length) this.surface()?.requestFrame();\n\n\t\treturn due;\n\t}\n\n\t#grab(): undefined | ValueAxis {\n\t\tif (this.#zoomGesture?.isPinching() === true) return undefined;\n\n\t\tconst drawn = this.#lastDrawn();\n\n\t\tthis.#grabbed = drawn;\n\n\t\tconst mapping = this.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn {\n\t\t\tfromProportion: mapping.proportionOf(drawn.seconds),\n\t\t\tisKeptOnCancel: true,\n\t\t\tposition: (event) => -event.clientX,\n\t\t\ttravelPx: Math.max(1, (high - low) * this.zoom),\n\t\t};\n\t}\n\n\t#held(): undefined | { grabbed: Drawn; seconds: number } {\n\t\tconst grabbed = this.#grabbed;\n\t\tconst { from } = this.scrubState();\n\t\tif (!grabbed || from === undefined) return undefined;\n\n\t\treturn { grabbed, seconds: this.value === from ? grabbed.seconds : this.value };\n\t}\n\n\t#lastDrawn(): Drawn {\n\t\treturn this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\t}\n\n\t#readBandColours(\n\t\tstrip: CanvasRenderingContext2D,\n\t\tbandCount: number,\n\t): Array<BandChannels | undefined> {\n\t\tconst probes = Array.from({ length: bandCount }, (_probe, band) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.style.setProperty(\n\t\t\t\t'color',\n\t\t\t\t`var(--sonic-waveform-band-${String(band + 1)}, transparent)`,\n\t\t\t);\n\n\t\t\treturn probe;\n\t\t});\n\n\t\treturn this.#readProbes(probes).map((colour) => bandChannels(strip, colour));\n\t}\n\n\t#readKindColours(): Map<string, string> {\n\t\tconst kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];\n\t\tconst probes = kinds.map((kind) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.className = 'sonic-waveform-label';\n\t\t\twriteKind(probe, kind, '--_sonic-waveform-marker');\n\n\t\t\treturn probe;\n\t\t});\n\n\t\tconst read = this.#readProbes(probes);\n\n\t\treturn new Map(kinds.map((kind, index) => [kind, read[index] ?? '']));\n\t}\n\n\t#readProbes(probes: Array<HTMLElement>): Array<string> {\n\t\tthis.control.append(...probes);\n\n\t\tconst read = probes.map((probe) => getComputedStyle(probe).color);\n\n\t\tfor (const probe of probes) probe.remove();\n\n\t\treturn read;\n\t}\n\n\t#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.scrubState().played;\n\t}\n\n\t#tint(isRebuilt: boolean): undefined | WaveformTint {\n\t\tconst table = this.#bandTable;\n\t\tif (!table) return undefined;\n\n\t\tconst strip =\n\t\t\tthis.#strip ??\n\t\t\tdocument.createElement('canvas').getContext('2d', { willReadFrequently: true }) ??\n\t\t\tundefined;\n\t\tif (!strip) return undefined;\n\n\t\tthis.#strip = strip;\n\n\t\tif (isRebuilt || this.#bandColours.length !== table.bandCount) {\n\t\t\tstrip.canvas.height = 1;\n\t\t\tthis.#bandColours = this.#readBandColours(strip, table.bandCount);\n\t\t}\n\n\t\treturn { colours: this.#bandColours, strip, table };\n\t}\n\n\t#zoomTo(target: number): void {\n\t\tconst next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);\n\t\tif (next === this.zoom) return;\n\n\t\tthis.zoom = next;\n\t\tthis.dispatchEvent(new Event('sonic-zoom', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"waveform.js","names":[],"sources":["../../../src/elements/waveform.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { WaveMarker } from '#elements/wave-element.ts';\nimport type { SurfaceFrame } from '#lib/canvas-surface.ts';\nimport type { Drawn } from '#lib/frame-timeline.ts';\nimport type { Point } from '#lib/marker-band.ts';\nimport type { PeaksRequest } from '#lib/peaks-request.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\nimport type { Milliseconds, PixelsPerSecond, Seconds } from '#lib/units.ts';\nimport type { BandChannels, WaveformBands } from '#lib/waveform-bands.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\nimport type { WaveformTint } from '#lib/waveform-scene.ts';\nimport type { ZoomGesture } from '#lib/zoom-gesture.ts';\n\nimport { SonicWaveElement } from '#elements/wave-element.ts';\nimport { frameTimeline } from '#lib/frame-timeline.ts';\nimport { isKind, writeKind } from '#lib/marker-band.ts';\nimport { createLabelRider } from '#lib/marker-rider.ts';\nimport { clamp, roundTo } from '#lib/math.ts';\nimport { createPeaksRequest } from '#lib/peaks-request.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { dueRegions, readRegions } from '#lib/time-regions.ts';\nimport { createTrackingClock } from '#lib/tracking-clock.ts';\nimport { bandTable } from '#lib/waveform-bands.ts';\nimport {\n\tbandChannels,\n\tpaintWaveform,\n\twaveformColours,\n\twaveformNumbers,\n} from '#lib/waveform-scene.ts';\nimport { bindZoom, bindZoomKeys } from '#lib/zoom-gesture.ts';\n\nexport type { PeaksRequest } from '#lib/peaks-request.ts';\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-zoom': Event;\n\t}\n\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-waveform': SonicWaveform;\n\t}\n}\n\ntype Colour = keyof typeof waveformColours;\n\ntype Numeric = keyof typeof waveformNumbers;\n\nexport type LabelRender = (marker: WaveMarker, element: HTMLElement) => void;\n\nconst defaultZoom = 70;\nconst defaultZoomMin = 20;\nconst defaultZoomMax = 280;\nconst lineReachPx = 4;\n\nconst renderWaveform = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-waveform\" role=\"slider\" tabindex=\"0\">\n\t\t\t<canvas class=\"sonic-waveform-canvas\" aria-hidden=\"true\"></canvas>\n\t\t\t<div class=\"sonic-waveform-scrim\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-ghost\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-playhead\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-label\" aria-hidden=\"true\" hidden></div>\n\t\t\t<div class=\"sonic-waveform-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-waveform-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction placeLine(line: HTMLElement, at: number | undefined): void {\n\tconst isHidden = at === undefined || at < 0 || at > 1;\n\n\tif (line.hidden !== isHidden) line.hidden = isHidden;\n\tif (isHidden) return;\n\n\tconst translate = `${String(at * 100)}cqi`;\n\n\tif (line.style.getPropertyValue('translate') !== translate) {\n\t\tline.style.setProperty('translate', translate);\n\t}\n}\n\nexport class SonicWaveform extends SonicWaveElement<Colour, never, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicWaveElement.observedAttributes,\n\t\t'pending-delay',\n\t\t'playing',\n\t\t'playback-rate',\n\t\t'reduced-motion',\n\t\t'zoom',\n\t\t'zoom-max',\n\t\t'zoom-min',\n\t\t'zoomable',\n\t];\n\n\tget bands(): undefined | WaveformBands {\n\t\treturn this.#bands;\n\t}\n\n\tset bands(bands: undefined | WaveformBands) {\n\t\tthis.#bands = bands;\n\t\tthis.#bandTable = bandTable(bands);\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\tget peaks(): undefined | WaveformPeaks {\n\t\treturn this.#peaks;\n\t}\n\n\tset peaks(peaks: undefined | WaveformPeaks) {\n\t\tthis.#peaks = peaks;\n\t\tthis.renderEmpty();\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\t/** Spans being fetched; drawn as placeholders until cleared */\n\tget pending(): Array<[Seconds, Seconds]> {\n\t\treturn this.#pending.map(([start, end]) => [start, end]);\n\t}\n\n\tset pending(regions: TimeRegions | undefined) {\n\t\tconst pending = regions ? readRegions(regions) : [];\n\t\tif (String(pending) === String(this.#pending)) return;\n\n\t\tconst listedMs = this.#pendingListedMs;\n\t\tconst nowMs = performance.now();\n\n\t\tthis.#pending = pending;\n\t\tthis.#pendingListedMs = new Map(\n\t\t\tthis.#pending.map((region) => {\n\t\t\t\tconst key = region.join(':');\n\n\t\t\t\treturn [key, listedMs.get(key) ?? nowMs];\n\t\t\t}),\n\t\t);\n\t\tthis.#peaksRequest.changed();\n\t}\n\n\t/** How long a span stays listed in `pending` before its placeholder draws */\n\tget pendingDelay(): Milliseconds {\n\t\treturn Math.max(0, this.numberAttribute('pending-delay', 0));\n\t}\n\n\tset pendingDelay(value: Milliseconds | undefined) {\n\t\tthis.reflect('pending-delay', value);\n\t}\n\n\tget playbackRate(): number {\n\t\tconst playbackRate = this.numberAttribute('playback-rate', 1);\n\n\t\treturn playbackRate > 0 ? playbackRate : 1;\n\t}\n\n\tset playbackRate(value: number | undefined) {\n\t\tthis.reflect('playback-rate', value);\n\t}\n\n\tget playing(): boolean {\n\t\treturn this.hasAttribute('playing');\n\t}\n\n\tset playing(isPlaying: boolean) {\n\t\tthis.reflect('playing', isPlaying);\n\t}\n\n\t/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */\n\tget readTime(): (() => Seconds | undefined) | undefined {\n\t\treturn this.#readTime;\n\t}\n\n\tset readTime(read: (() => Seconds | undefined) | undefined) {\n\t\tthis.#readTime = read;\n\t\tthis.render();\n\t}\n\n\tget reducedMotion(): 'page' | 'scroll' {\n\t\treturn this.getAttribute('reduced-motion') === 'scroll' ? 'scroll' : 'page';\n\t}\n\n\tset reducedMotion(motion: 'page' | 'scroll' | undefined) {\n\t\tthis.reflect('reduced-motion', motion);\n\t}\n\n\tget renderLabel(): LabelRender | undefined {\n\t\treturn this.#renderLabel;\n\t}\n\n\tset renderLabel(render: LabelRender | undefined) {\n\t\tthis.#renderLabel = render;\n\t\tif (this.isBound()) this.renderMarkers();\n\t}\n\n\tget requestPeaks(): PeaksRequest | undefined {\n\t\treturn this.#requestPeaks;\n\t}\n\n\tset requestPeaks(request: PeaksRequest | undefined) {\n\t\tthis.#requestPeaks = request;\n\t\tthis.#peaksRequest.reset();\n\t}\n\n\tget zoom(): PixelsPerSecond {\n\t\tconst zoom = this.numberAttribute('zoom', defaultZoom);\n\n\t\treturn zoom > 0 ? zoom : defaultZoom;\n\t}\n\n\tset zoom(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom', value);\n\t}\n\n\tget zoomable(): boolean {\n\t\treturn this.hasAttribute('zoomable');\n\t}\n\n\tset zoomable(isZoomable: boolean) {\n\t\tthis.reflect('zoomable', isZoomable);\n\t}\n\n\tget zoomMax(): PixelsPerSecond {\n\t\tconst zoomMax = this.numberAttribute('zoom-max', defaultZoomMax);\n\n\t\treturn Math.max(this.zoomMin, zoomMax > 0 ? zoomMax : defaultZoomMax);\n\t}\n\n\tset zoomMax(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-max', value);\n\t}\n\n\tget zoomMin(): PixelsPerSecond {\n\t\tconst zoomMin = this.numberAttribute('zoom-min', defaultZoomMin);\n\n\t\treturn zoomMin > 0 ? zoomMin : defaultZoomMin;\n\t}\n\n\tset zoomMin(value: PixelsPerSecond | undefined) {\n\t\tthis.reflect('zoom-min', value);\n\t}\n\n\tprotected readonly control = renderWaveform();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-waveform-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = waveformColours;\n\n\tprotected readonly lengths = {};\n\n\tprotected readonly numbers = waveformNumbers;\n\n\tprotected readonly sheet = 'waveform.css';\n\n\tprotected readonly sizeProperty = '--_sonic-waveform-size';\n\n\t#bandColours: Array<BandChannels | undefined> = [];\n\n\t#bands: undefined | WaveformBands;\n\n\t#bandTable: ReturnType<typeof bandTable>;\n\n\treadonly #clock = createTrackingClock();\n\n\t#drawn: (Drawn & { zoom: PixelsPerSecond }) | undefined;\n\n\treadonly #ghost = requireChild(this.control, '.sonic-waveform-ghost', HTMLDivElement);\n\n\t#grabbed: Drawn | undefined;\n\n\t#kindColours = new Map<string, string>();\n\n\t#paintedKey = '';\n\n\t#peaks: undefined | WaveformPeaks;\n\n\treadonly #peaksRequest = createPeaksRequest(() => {\n\t\tthis.surface()?.invalidate();\n\t});\n\n\t#pending: Array<[number, number]> = [];\n\n\t#pendingListedMs = new Map<string, number>();\n\n\treadonly #playhead = requireChild(this.control, '.sonic-waveform-playhead', HTMLDivElement);\n\n\t#readTime: (() => number | undefined) | undefined;\n\n\t#renderLabel: LabelRender | undefined;\n\n\t#requestPeaks: PeaksRequest | undefined;\n\n\treadonly #rider = createLabelRider(this.control, {\n\t\tclassName: 'sonic-waveform-label',\n\t\tcolourProperty: '--_sonic-waveform-marker',\n\t\tfadeProperties: ['--_sonic-waveform-label-fade-start', '--_sonic-waveform-label-fade-end'],\n\t\tinsetProperty: '--_sonic-waveform-label-inset',\n\t\tparkProperty: '--_sonic-waveform-label-park',\n\t});\n\n\t#strip: CanvasRenderingContext2D | undefined;\n\n\t#zoomGesture: undefined | ZoomGesture;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tsuper.attributeChangedCallback(name);\n\t\tif (name === 'zoomable') this.#zoomGesture?.sync();\n\t}\n\n\t/** Viewport x of a time as last drawn; not clamped to the window */\n\tclientXOf(value: Seconds): number {\n\t\tconst { left, width } = this.canvas.getBoundingClientRect();\n\n\t\treturn left + this.#drawnX(value, width);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'bands',\n\t\t\t'peaks',\n\t\t\t'pending',\n\t\t\t'pendingDelay',\n\t\t\t'playing',\n\t\t\t'playbackRate',\n\t\t\t'readTime',\n\t\t\t'reducedMotion',\n\t\t\t'renderLabel',\n\t\t\t'requestPeaks',\n\t\t\t'zoom',\n\t\t\t'zoomable',\n\t\t\t'zoomMax',\n\t\t\t'zoomMin',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\t/** The time at a viewport x as last drawn */\n\tvalueFromPoint(clientX: number, _clientY: number): Seconds {\n\t\tconst { left, width } = this.canvas.getBoundingClientRect();\n\t\tconst { startSeconds, zoom } = this.#drawnWindow(width);\n\n\t\treturn clamp(startSeconds + (clientX - left) / zoom, ...this.mapping().bounds);\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\n\t\tconst zoomTarget = {\n\t\t\tclaim: () => {\n\t\t\t\tthis.abandonHold();\n\t\t\t},\n\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\tisZoomable: () => this.zoomable,\n\t\t\tzoom: () => this.zoom,\n\t\t\tzoomTo: (target: number) => {\n\t\t\t\tthis.#zoomTo(target);\n\t\t\t},\n\t\t};\n\n\t\tthis.#zoomGesture = bindZoom(this.control, zoomTarget, signal);\n\t\tthis.bindGestures(this.control, signal, () => this.#grab());\n\t\t// After the value keys, which stand down once a key is taken\n\t\tbindZoomKeys(this.control, zoomTarget, signal);\n\t\tif (!('fonts' in document)) return;\n\n\t\tdocument.fonts.addEventListener(\n\t\t\t'loadingdone',\n\t\t\t() => {\n\t\t\t\tthis.renderMarkers();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected draw(): void {\n\t\tconst surface = this.surface();\n\n\t\tsurface?.requestFrame();\n\t\t// No frame follows the position out of view, or before the first one paints\n\t\tif (surface?.isVisible === false || !this.#drawn) this.showCurrentMarker(this.#sourceSeconds());\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected isEmpty(): boolean {\n\t\treturn !this.#peaks || this.#peaks.samples.length === 0;\n\t}\n\n\t// The canvas paints a region as its start line alone\n\tprotected markersInReach({ x }: Point, widthPx: number): Array<number> {\n\t\treturn this.markerList()\n\t\t\t.map(({ start }, index) => ({\n\t\t\t\tdistance: Math.abs(this.#drawnX(start, widthPx) - x),\n\t\t\t\tindex,\n\t\t\t}))\n\t\t\t.filter(({ distance }) => distance <= lineReachPx)\n\t\t\t.toSorted((first, second) => first.distance - second.distance)\n\t\t\t.map(({ index }) => index);\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, isRebuilt, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tconst held = this.#held();\n\t\tconst isPaged = look.isReducedMotion && this.reducedMotion === 'page';\n\t\tconst pending = this.#duePending();\n\t\tconst range: [number, number] = [this.min, Math.max(this.min, this.max)];\n\t\tconst timeline = frameTimeline({\n\t\t\tclockSeconds: this.#clock.read(frameMs, {\n\t\t\t\tisPlaying: this.playing,\n\t\t\t\tplaybackRate: this.playbackRate,\n\t\t\t\tseconds: this.#sourceSeconds(),\n\t\t\t}),\n\t\t\tframeMs,\n\t\t\t...(held ? { held } : {}),\n\t\t\tisPaged,\n\t\t\tisPlaying: this.playing,\n\t\t\tisStill: look.isReducedMotion,\n\t\t\tpending,\n\t\t\trange,\n\t\t\tsize,\n\t\t\tzoom: this.zoom,\n\t\t});\n\t\tconst { seconds, view } = timeline;\n\n\t\tthis.#drawn = { seconds, startSeconds: view.startSeconds, zoom: this.zoom };\n\t\tplaceLine(this.#playhead, timeline.playheadAt);\n\t\tplaceLine(this.#ghost, timeline.ghostAt);\n\t\tthis.showCurrentMarker(seconds);\n\t\tthis.toggleState('pending', view.pending.length > 0);\n\t\tthis.#rider.place({\n\t\t\tisPaged,\n\t\t\tplayheadSeconds: seconds,\n\t\t\tstartSeconds: view.startSeconds,\n\t\t\twidthPx: view.width / view.dpr,\n\t\t\twindowSeconds: view.width / view.pixelsPerSecond,\n\t\t});\n\t\tif (isDirty || timeline.paintKey !== this.#paintedKey) {\n\t\t\tthis.#paintedKey = timeline.paintKey;\n\t\t\tif (isRebuilt) this.#kindColours = this.#readKindColours();\n\t\t\tpaintWaveform(\n\t\t\t\tcontext,\n\t\t\t\t{ ...view, colours: look.colours, numbers: look.numbers },\n\t\t\t\t{\n\t\t\t\t\tbands: this.#tint(isRebuilt),\n\t\t\t\t\tkindColours: this.#kindColours,\n\t\t\t\t\tmarkers: this.markerList().map((marker) => ({\n\t\t\t\t\t\t...marker,\n\t\t\t\t\t\tstart: clamp(marker.start, ...range),\n\t\t\t\t\t})),\n\t\t\t\t\tpeaks: this.#peaks,\n\t\t\t\t\trange,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.#peaksRequest.ask(this.#requestPeaks, timeline.wanted);\n\t\t}\n\t\tif (timeline.isMoving) this.surface()?.requestFrame();\n\t}\n\n\tprotected override renderMarkers(): void {\n\t\tconst bounds = this.mapping().bounds;\n\n\t\tconst render = this.#renderLabel;\n\n\t\tthis.#rider.measure(\n\t\t\tthis.markers.flatMap((marker) =>\n\t\t\t\tmarker.label === undefined || marker.label === ''\n\t\t\t\t\t? []\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tisDimmed: marker.dimmed === true,\n\t\t\t\t\t\t\t\t...(marker.kind === undefined ? {} : { kind: marker.kind }),\n\t\t\t\t\t\t\t\tstart: clamp(marker.start, ...bounds),\n\t\t\t\t\t\t\t\ttext: marker.label,\n\t\t\t\t\t\t\t\t...(render\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\twrite: (element: HTMLElement) => {\n\t\t\t\t\t\t\t\t\t\t\t\trender(marker, element);\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t),\n\t\t);\n\t\tthis.surface()?.rebuild();\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tconst isScrubbing = this.scrubState().from !== undefined;\n\n\t\tthis.#grabbed = isScrubbing ? (this.#grabbed ?? this.#lastDrawn()) : undefined;\n\t\tsuper.scrubChanged();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn true;\n\t}\n\n\t// Before the first frame, the window a centerd playhead would draw\n\t#drawnWindow(widthPx: number): { startSeconds: Seconds; zoom: PixelsPerSecond } {\n\t\tconst zoom = this.#drawn?.zoom ?? this.zoom;\n\n\t\treturn { startSeconds: this.#drawn?.startSeconds ?? this.value - widthPx / zoom / 2, zoom };\n\t}\n\n\t#drawnX(value: Seconds, widthPx: number): number {\n\t\tconst { startSeconds, zoom } = this.#drawnWindow(widthPx);\n\n\t\treturn (clamp(value, ...this.mapping().bounds) - startSeconds) * zoom;\n\t}\n\n\t#duePending(): Array<[number, number]> {\n\t\tconst due = dueRegions(this.#pending, this.#pendingListedMs, [\n\t\t\tperformance.now(),\n\t\t\tthis.pendingDelay,\n\t\t]);\n\n\t\tif (due.length < this.#pending.length) this.surface()?.requestFrame();\n\n\t\treturn due;\n\t}\n\n\t#grab(): undefined | ValueAxis {\n\t\tif (this.#zoomGesture?.isPinching() === true) return undefined;\n\n\t\tconst drawn = this.#lastDrawn();\n\n\t\tthis.#grabbed = drawn;\n\n\t\tconst mapping = this.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn {\n\t\t\tfromProportion: mapping.proportionOf(drawn.seconds),\n\t\t\tisKeptOnCancel: true,\n\t\t\tposition: (event) => -event.clientX,\n\t\t\ttravelPx: Math.max(1, (high - low) * this.zoom),\n\t\t};\n\t}\n\n\t#held(): undefined | { grabbed: Drawn; seconds: number } {\n\t\tconst grabbed = this.#grabbed;\n\t\tconst { from } = this.scrubState();\n\t\tif (!grabbed || from === undefined) return undefined;\n\n\t\treturn { grabbed, seconds: this.value === from ? grabbed.seconds : this.value };\n\t}\n\n\t#lastDrawn(): Drawn {\n\t\treturn this.#drawn ?? { seconds: this.value, startSeconds: this.value };\n\t}\n\n\t#readBandColours(\n\t\tstrip: CanvasRenderingContext2D,\n\t\tbandCount: number,\n\t): Array<BandChannels | undefined> {\n\t\tconst probes = Array.from({ length: bandCount }, (_probe, band) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.style.setProperty(\n\t\t\t\t'color',\n\t\t\t\t`var(--sonic-waveform-band-${String(band + 1)}, transparent)`,\n\t\t\t);\n\n\t\t\treturn probe;\n\t\t});\n\n\t\treturn this.#readProbes(probes).map((colour) => bandChannels(strip, colour));\n\t}\n\n\t#readKindColours(): Map<string, string> {\n\t\tconst kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];\n\t\tconst probes = kinds.map((kind) => {\n\t\t\tconst probe = document.createElement('div');\n\n\t\t\tprobe.className = 'sonic-waveform-label';\n\t\t\twriteKind(probe, kind, '--_sonic-waveform-marker');\n\n\t\t\treturn probe;\n\t\t});\n\n\t\tconst read = this.#readProbes(probes);\n\n\t\treturn new Map(kinds.map((kind, index) => [kind, read[index] ?? '']));\n\t}\n\n\t#readProbes(probes: Array<HTMLElement>): Array<string> {\n\t\tthis.control.append(...probes);\n\n\t\tconst read = probes.map((probe) => getComputedStyle(probe).color);\n\n\t\tfor (const probe of probes) probe.remove();\n\n\t\treturn read;\n\t}\n\n\t#sourceSeconds(): number {\n\t\treturn this.#readTime?.() ?? this.scrubState().played;\n\t}\n\n\t#tint(isRebuilt: boolean): undefined | WaveformTint {\n\t\tconst table = this.#bandTable;\n\t\tif (!table) return undefined;\n\n\t\tconst strip =\n\t\t\tthis.#strip ??\n\t\t\tdocument.createElement('canvas').getContext('2d', { willReadFrequently: true }) ??\n\t\t\tundefined;\n\t\tif (!strip) return undefined;\n\n\t\tthis.#strip = strip;\n\n\t\tif (isRebuilt || this.#bandColours.length !== table.bandCount) {\n\t\t\tstrip.canvas.height = 1;\n\t\t\tthis.#bandColours = this.#readBandColours(strip, table.bandCount);\n\t\t}\n\n\t\treturn { colours: this.#bandColours, strip, table };\n\t}\n\n\t#zoomTo(target: number): void {\n\t\tconst next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);\n\t\tif (next === this.zoom) return;\n\n\t\tthis.zoom = next;\n\t\tthis.dispatchEvent(new Event('sonic-zoom', { bubbles: true }));\n\t}\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { Seconds } from "../lib/units.js";
2
2
  import { TimeRegions } from "../lib/time-regions.js";
3
3
  import { SurfaceFrame } from "../lib/canvas-surface.js";
4
- import { SonicWaveElement } from "./wave-element.js";
4
+ import { Point } from "../lib/marker-band.js";
5
+ import { SonicWaveElement, WaveMarker } from "./wave-element.js";
5
6
  //#region src/elements/wavestrip.d.ts
6
7
  declare global {
7
8
  interface HTMLElementTagNameMap {
@@ -10,6 +11,7 @@ declare global {
10
11
  }
11
12
  type Colour = keyof typeof colours;
12
13
  type Length = keyof typeof lengths;
14
+ export type MarkerRender = (marker: WaveMarker, element: HTMLElement) => void;
13
15
  declare const colours: {
14
16
  readonly buffered: "--_sonic-wavestrip-buffered";
15
17
  readonly played: "--_sonic-wavestrip-played";
@@ -30,9 +32,18 @@ export declare class SonicWavestrip extends SonicWaveElement<Colour, Length> {
30
32
  set buffered(regions: TimeRegions | undefined);
31
33
  get cancellable(): boolean;
32
34
  set cancellable(isCancellable: boolean);
35
+ /** Words a region's `aria-valuetext`; unset, a region speaks its start as the strip speaks a time */
36
+ get formatSpokenRegion(): ((start: Seconds, end: Seconds) => string) | undefined;
37
+ set formatSpokenRegion(format: ((start: Seconds, end: Seconds) => string) | undefined);
33
38
  /** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */
34
39
  get peaks(): ArrayLike<number> | undefined;
35
40
  set peaks(peaks: ArrayLike<number> | undefined);
41
+ /** A time a release will seek to, drawn as a scrub's edge; a hold or a hover of the strip's own is drawn instead */
42
+ get preview(): Seconds | undefined;
43
+ set preview(seconds: null | Seconds | undefined);
44
+ /** Runs once per point marker on each `markers` or `renderMarker` write; the element lasts until the next */
45
+ get renderMarker(): MarkerRender | undefined;
46
+ set renderMarker(render: MarkerRender | undefined);
36
47
  protected readonly control: HTMLDivElement;
37
48
  protected readonly canvas: HTMLCanvasElement;
38
49
  protected readonly colours: {
@@ -54,12 +65,17 @@ export declare class SonicWavestrip extends SonicWaveElement<Colour, Length> {
54
65
  /** Viewport x of a time, clamped to the strip */
55
66
  clientXOf(value: Seconds): number;
56
67
  connectedCallback(): void;
68
+ valueFromPoint(clientX: number, _clientY: number): Seconds;
57
69
  protected connect(signal: AbortSignal): void;
58
70
  protected draw(): void;
71
+ protected focusTarget(): HTMLElement;
59
72
  protected isEmpty(): boolean;
73
+ /** The first point marker is the one a press there snaps to */
74
+ protected markersInReach(point: Point, widthPx: number): Array<number>;
60
75
  protected paint(context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length>): void;
61
76
  protected renderMarkers(): void;
62
77
  protected scrubsKeyRepeat(): boolean;
78
+ protected usesChild(child: Node): boolean;
63
79
  }
64
80
  //#endregion
65
81
  //# sourceMappingURL=wavestrip.d.ts.map