45 lines
3.4 KiB
TypeScript
45 lines
3.4 KiB
TypeScript
/**
|
||
* 手势使用的可见坐标上下文签名。它描述目标 owner 到当前 view canvas 之间的入口链,
|
||
* 不从项目根出发;view 之外的父级变化不必被误当作当前视图内的目标坐标变化。
|
||
*/
|
||
import type {Document,Geometry,Id,Registry} from './types.ts';
|
||
import {fault} from './values.ts';
|
||
import {unitScale} from './units.ts';
|
||
|
||
/** 一层入口的身份/类型/版本和变换相关值;上传状态、普通文字等无关字段不进入签名。 */
|
||
type PlacementStep={canvasId:Id;parentCanvasId:Id;entryId:Id;incarnation:number;componentId:string;dataVersion:number;geometry:Geometry;contentScale:number;unitScale:number};
|
||
|
||
/** A short-lived reader of the visible owner chain relative to one view, not the project root.
|
||
* Cache by owner only during this admission/publication, so multi-selection shares ancestry work. */
|
||
/** 给定不可变文档/view/registry,返回短生命周期 owner→入口链读取器。
|
||
* Map 只在一次准入或发布比较中复用,多选共享祖先扫描,不能跨文档更新继续使用。
|
||
* 返回数组/步骤没有运行时 freeze,geometry 也沿用记录引用;readonly 是调用约定,不应修改缓存结果。 */
|
||
export function createGesturePlacementReader(document:Document,viewCanvasId:Id,registry:Registry){
|
||
// 链按 owner→view 方向排列,不包含 view 自身在更上级的入口;读同一 owner 会返回同一个数组。
|
||
const values=new Map<Id,readonly PlacementStep[]>();
|
||
/** 向 view 回溯目标拥有链,缺失/循环/隐藏入口明确拒绝;成功后才缓存完整链。 */
|
||
return(ownerCanvasId:Id):readonly PlacementStep[]=>{
|
||
const cached=values.get(ownerCanvasId);if(cached)return cached;
|
||
if(!document.canvases.has(viewCanvasId))throw fault('canvas_missing',[viewCanvasId]);
|
||
const chain:PlacementStep[]=[],seen=new Set<Id>();let id=ownerCanvasId;
|
||
// 当前 view 本身无需入口步骤;回溯到根仍没遇到 view,说明目标不在当前视图子树。
|
||
while(id!==viewCanvasId){
|
||
if(seen.has(id))throw fault('canvas_cycle',[id]);seen.add(id);
|
||
const parent=document.canvases.get(id)?.parent;
|
||
// owner 缺失或已回溯到无父根,统一视为不在此 view;仅 view 本身缺失在上方报 canvas_missing。
|
||
if(!parent)throw fault('target_outside_view',[ownerCanvasId]);
|
||
const entry=document.elements.get(parent.entryElementId);
|
||
// A future/unknown entry is a collapsed placeholder; never interpret its payload.
|
||
if(!entry||entry.structure!=='node'||registry.resolve(entry)!=='known')throw fault('target_hidden',[ownerCanvasId]);
|
||
// 未知/未来入口已在上方拒绝;已知入口仍可能折叠,折叠子内容不允许开始手势。
|
||
const raw=entry.data.presentation,presentation=raw&&typeof raw==='object'&&!Array.isArray(raw)?raw:{};
|
||
if(presentation.collapsed===true)throw fault('target_hidden',[ownerCanvasId]);
|
||
// 签名只读这些坐标字段,不校验整个 presentation;contentScale 这里仅检查 number 且 >0。
|
||
const contentScale=typeof presentation.contentScale==='number'&&presentation.contentScale>0?presentation.contentScale:1;
|
||
chain.push({canvasId:id,parentCanvasId:parent.canvasId,entryId:entry.id,incarnation:entry.incarnation,componentId:entry.componentId,dataVersion:entry.dataVersion,geometry:entry.geometry,contentScale,unitScale:unitScale(raw)});
|
||
id=parent.canvasId;
|
||
}
|
||
values.set(ownerCanvasId,chain);return chain;
|
||
};
|
||
}
|