/** * 浏览器指针捕获的容错边界。这里只处理 DOM 事件传输所有权;手势 token、预览和几何仍归 editor/session。 * DOM 捕获丢失不等于任意事件都能取消当前手势,必须匹配指针与捕获目标。 */ /** Transport ownership only: document geometry and gesture tokens stay in the editor. */ /** 最小结构化 DOM 端口,让纯测试无需真实 HTMLElement。 */ export type PointerCapturePort={setPointerCapture(id:number):void;hasPointerCapture(id:number):boolean;releasePointerCapture(id:number):void}; /** 普通事件匹配 pointerId 即可;lostpointercapture 还要匹配原捕获目标且确认它已失去捕获。 * 如果同目标已重新捕获该指针,旧 lost 事件不拥有新手势;脱离 DOM 导致查询抛错则允许匹配目标清理。 */ export function ownsPointerCapture(owner:{pointerId:number;capture:unknown}|null|undefined,input:{pointerId:number;type?:string;target?:unknown}){if(!owner||owner.pointerId!==input.pointerId)return false;if(input.type!=='lostpointercapture')return true;if(input.target!==owner.capture)return false;const capture=owner.capture as Partial|undefined;try{if(capture?.hasPointerCapture?.(owner.pointerId))return false;}catch{/* A detached matching target may still need cancellation. */}return true;} /** 平台可能因指针失效/节点卸载拒绝捕获,以布尔结果交给宿主决定是否继续手势。 */ export function acquirePointerCapture(target:PointerCapturePort,id:number){try{target.setPointerCapture(id);return true;}catch{return false;}} /** 尽力释放且吞掉平台清理异常;DOM 释放失败不能取得 editor 的提交或取消权限。 */ export function releasePointerCapture(target:PointerCapturePort,id:number){try{if(target.hasPointerCapture(id))target.releasePointerCapture(id);}catch{/* Detached DOM or an already released platform capture has no editor authority. */}}