/** Three/Cesium 视口的输入焦点边界;只接管视口内部的浏览器菜单。 */ export function attachSceneFocus(element) { if (!element || typeof element.addEventListener !== 'function') return () => {} const tabIndex = element.getAttribute?.('tabindex') ?? null if (tabIndex === null) element.setAttribute?.('tabindex', '0') const editable = (target) => target?.closest?.('input, textarea, select, [contenteditable]') const focus = () => element.focus?.({ preventScroll: true }) const onDown = (event) => { if (event.button !== 0 && event.button !== 2) return if (editable(event.target) || event.target?.closest?.('button, a, [role="button"]')) return focus() } const onMenu = (event) => { if (editable(event.target)) return event.preventDefault() event.stopPropagation() focus() } const onFocus = () => element.setAttribute?.('data-scene-focused', 'true') const onBlur = (event) => { if (!element.contains?.(event.relatedTarget)) element.removeAttribute?.('data-scene-focused') } // 捕获阶段覆盖 canvas、三维标签和覆盖层,即使后代停止冒泡也不会漏掉右键。 const capture = { capture: true } element.addEventListener('pointerdown', onDown, capture) element.addEventListener('contextmenu', onMenu, capture) element.addEventListener('focusin', onFocus) element.addEventListener('focusout', onBlur) return () => { element.removeEventListener('pointerdown', onDown, capture) element.removeEventListener('contextmenu', onMenu, capture) element.removeEventListener('focusin', onFocus) element.removeEventListener('focusout', onBlur) element.removeAttribute?.('data-scene-focused') if (tabIndex === null) element.removeAttribute?.('tabindex') } }