sceneInputFocus.js 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839
  1. /** Three/Cesium 视口的输入焦点边界;只接管视口内部的浏览器菜单。 */
  2. export function attachSceneFocus(element) {
  3. if (!element || typeof element.addEventListener !== 'function') return () => {}
  4. const tabIndex = element.getAttribute?.('tabindex') ?? null
  5. if (tabIndex === null) element.setAttribute?.('tabindex', '0')
  6. const editable = (target) => target?.closest?.('input, textarea, select, [contenteditable]')
  7. const focus = () => element.focus?.({ preventScroll: true })
  8. const onDown = (event) => {
  9. if (event.button !== 0 && event.button !== 2) return
  10. if (editable(event.target) || event.target?.closest?.('button, a, [role="button"]')) return
  11. focus()
  12. }
  13. const onMenu = (event) => {
  14. if (editable(event.target)) return
  15. event.preventDefault()
  16. event.stopPropagation()
  17. focus()
  18. }
  19. const onFocus = () => element.setAttribute?.('data-scene-focused', 'true')
  20. const onBlur = (event) => {
  21. if (!element.contains?.(event.relatedTarget)) element.removeAttribute?.('data-scene-focused')
  22. }
  23. // 捕获阶段覆盖 canvas、三维标签和覆盖层,即使后代停止冒泡也不会漏掉右键。
  24. const capture = { capture: true }
  25. element.addEventListener('pointerdown', onDown, capture)
  26. element.addEventListener('contextmenu', onMenu, capture)
  27. element.addEventListener('focusin', onFocus)
  28. element.addEventListener('focusout', onBlur)
  29. return () => {
  30. element.removeEventListener('pointerdown', onDown, capture)
  31. element.removeEventListener('contextmenu', onMenu, capture)
  32. element.removeEventListener('focusin', onFocus)
  33. element.removeEventListener('focusout', onBlur)
  34. element.removeAttribute?.('data-scene-focused')
  35. if (tabIndex === null) element.removeAttribute?.('tabindex')
  36. }
  37. }