Bind rendered editing callbacks to captured element lifetimes

This commit is contained in:
2026-11-09 19:26:00 +08:00
parent 28c792d640
commit 9efe6e7c9f
14 changed files with 103 additions and 45 deletions
+45
View File
@@ -0,0 +1,45 @@
"use client";
import {createContext,useContext,useMemo} from 'react';
import type {XYPosition} from '@xyflow/react';
import type {NodeDirection} from '@/lib/canvas';
import type {TextStyle} from '@/lib/canvas-elements';
import type {NodeTransform} from '@/lib/canvas-geometry';
import type {EditingLease} from './editing-lease';
import type {RenderedTarget} from '@/lib/canvas-bindings/compat-types';
import {CanvasOverlays} from './canvas-overlays';
const defaultEditing = {
beginTransform: undefined as undefined | ((id: string) => boolean | void | EditingLease<NodeTransform>), finishTransform: undefined as undefined | (() => void), cancelTransform: undefined as undefined | (() => void),
endEditing: undefined as undefined | ((id: string) => void),
workspace: "", schema: 1 as 1 | 2,
begin: (_id?: string) => {}, select: (_id: string) => {}, edit: (_id: string, _text: string) => {},
branch: (_id: string, _direction: NodeDirection, _point: XYPosition) => {},
transform: (_id: string, _transform: NodeTransform) => {},
style: (_id: string, _style: TextStyle, _fontSize?: number) => {},
rename: (_id: string, _title: string) => {},
frame: (_id: string, _patch: { title?: string; labelMode?: import("@/lib/canvas-elements").FrameLabelMode; presentation?: import("@/lib/canvas-v2").FramePresentation }) => {},
rotating: null as string | null, rotate: (_id: string | null) => {},
resizing: null as string | null, resize: (_id: string | null) => {},
};
type GestureHooks = 'beginTransform' | 'finishTransform' | 'cancelTransform' | 'endEditing';
export const NodeEditing = createContext<Omit<typeof defaultEditing, GestureHooks> & Partial<Pick<typeof defaultEditing, GestureHooks>>>(defaultEditing);
/** Bind legacy renderer actions to the lifetime and placement that rendered them. */
export function useScopedNodeEditing(id:string,rendered?:RenderedTarget){
const editing=useContext(NodeEditing),scope=useContext(CanvasOverlays),incarnation=rendered?.id===id?rendered.incarnation:undefined,owner=rendered?.ownerCanvasId,canvas=rendered?.canvasId;
return useMemo(()=>{if(!scope)return editing;
const current=(candidate:string|null|undefined=id)=>{const durability=scope.engine.getDurability(),view=scope.engine.sessions.getSnapshot().views.get(scope.index.viewId),record=scope.engine.getSnapshot().document.elements.get(id);return !durability.closing&&!durability.closed&&!!view&&view.canvasId===canvas&&incarnation!==undefined&&record?.incarnation===incarnation&&record.ownerCanvasId===owner&&(candidate===undefined||candidate===null||candidate===id);};
const ownGesture=()=>scope.engine.sessions.getSnapshot().gesture?.viewId===scope.index.viewId&&scope.engine.sessions.getSnapshot().gesture?.targets.some(target=>target.id===id&&target.incarnation===incarnation);
const scoped:typeof editing={...editing,
begin:candidate=>{if(current(candidate))editing.begin(candidate??id);},select:candidate=>{if(current(candidate))editing.select(candidate);},
edit:(candidate,text)=>{if(current(candidate))editing.edit(candidate,text);},rename:(candidate,title)=>{if(current(candidate))editing.rename(candidate,title);},
style:(candidate,style,size)=>{if(current(candidate))editing.style(candidate,style,size);},frame:(candidate,patch)=>{if(current(candidate))editing.frame(candidate,patch);},
transform:(candidate,change)=>{if(current(candidate))editing.transform(candidate,change);},branch:(candidate,direction,point)=>{if(current(candidate))editing.branch(candidate,direction,point);},
rotate:candidate=>{if(current(candidate)&&(candidate!==null||scope.engine.sessions.getSnapshot().views.get(scope.index.viewId)?.modes.get(id)==='rotate'))editing.rotate(candidate);},resize:candidate=>{if(current(candidate)&&(candidate!==null||scope.engine.sessions.getSnapshot().views.get(scope.index.viewId)?.modes.get(id)==='resize'))editing.resize(candidate);},
};
if(editing.beginTransform)scoped.beginTransform=candidate=>current(candidate)?editing.beginTransform!(candidate):false;
if(editing.endEditing)scoped.endEditing=candidate=>{if(current(candidate))editing.endEditing!(candidate);};
if(editing.finishTransform)scoped.finishTransform=()=>{if(current()&&ownGesture())editing.finishTransform!();};
if(editing.cancelTransform)scoped.cancelTransform=()=>{if(current()&&ownGesture())editing.cancelTransform!();};
return scoped;
},[editing,scope,id,incarnation,owner,canvas]);
}