import type {Id,Target,Geometry} from '../canvas-core/types.ts'; import type {SessionSnapshot,GestureState} from '../canvas-core/gestures.ts'; import {equal} from '../canvas-core/values.ts'; import type {ExternalSource} from './selectors.ts'; export type GestureSelection={readonly preview:Geometry|undefined;readonly active:boolean}; /** Derived view/target subscriptions. Values retain no document or full per-hook session. */ export function createSessionSources(source:ExternalSource){ type Observer={update(snapshot:SessionSnapshot):boolean;notify():void}; const modes=new Map>>(),gestures=new Map>(); const targetIndexes=new WeakMap>(); let stop:(()=>void)|undefined,previous:SessionSnapshot|undefined; function targets(gesture:GestureState){let index=targetIndexes.get(gesture.targets);if(!index){index=new Map(gesture.targets.map(target=>[target.id,target.incarnation]));targetIndexes.set(gesture.targets,index);}return index;} function changed(){ if(!previous)return;const before=previous,next=source.getSnapshot();previous=next; const candidates=new Set(),add=(items:Set|undefined)=>{for(const item of items??[])candidates.add(item);}; if(before.views!==next.views)for(const[viewId,byId]of modes){ const old=before.views.get(viewId)?.modes,current=next.views.get(viewId)?.modes;if(old===current)continue; for(const id of new Set([...(old?.keys()??[]),...(current?.keys()??[])]))if(old?.get(id)!==current?.get(id))add(byId.get(id)); } const old=before.gesture,current=next.gesture; if(old!==current){ const ids=new Set(); if(!old||!current||old.token!==current.token||old.viewId!==current.viewId||old.targets!==current.targets){for(const gesture of [old,current])for(const target of gesture?.targets??[])ids.add(target.id);} else if(old.preview!==current.preview){for(const[id,value]of old.preview)if(current.preview.get(id)!==value)ids.add(id);for(const[id,value]of current.preview)if(old.preview.get(id)!==value)ids.add(id);} for(const id of ids)add(gestures.get(id)); } const notifications:Observer[]=[];for(const observer of candidates)if(observer.update(next))notifications.push(observer); let failed=false,error:unknown;for(const observer of notifications)try{observer.notify();}catch(caught){if(!failed){failed=true;error=caught;}}if(failed)throw error; } function channel(read:(snapshot:SessionSnapshot)=>T,register:(observer:Observer)=>()=>void):ExternalSource{ let initialized=false,value:T; return Object.freeze({ getSnapshot(){const next=read(source.getSnapshot());if(!initialized||!equal(value,next))value=next;initialized=true;return value;}, subscribe(notify:()=>void){ let observed=read(source.getSnapshot()),failed=false; const observer:Observer={notify,update(snapshot){try{const next=read(snapshot);if(!failed&&equal(observed,next))return false;observed=next;failed=false;}catch{failed=true;}return true;}}; const remove=register(observer); if(!stop){previous=source.getSnapshot();try{stop=source.subscribe(changed);}catch(error){remove();previous=undefined;throw error;}} let active=true;return()=>{if(!active)return;active=false;remove();if(!modes.size&&!gestures.size){stop?.();stop=undefined;previous=undefined;}}; }, }); } return Object.freeze({ mode(viewId:Id,id:Id):ExternalSource{return channel(snapshot=>snapshot.views.get(viewId)?.modes.get(id)??null,observer=>{ let byId=modes.get(viewId);if(!byId){byId=new Map();modes.set(viewId,byId);}let bucket=byId.get(id);if(!bucket){bucket=new Set();byId.set(id,bucket);}bucket.add(observer); return()=>{bucket!.delete(observer);if(!bucket!.size)byId!.delete(id);if(!byId!.size)modes.delete(viewId);}; });}, gesture(viewId:Id,target:Target):ExternalSource{const{id,incarnation}=target;return channel(snapshot=>{ const gesture=snapshot.gesture,matched=!!gesture&&targets(gesture).get(id)===incarnation; return{preview:matched?gesture!.preview.get(id):undefined,active:matched&&gesture!.viewId===viewId}; },observer=>{let bucket=gestures.get(id);if(!bucket){bucket=new Set();gestures.set(id,bucket);}bucket.add(observer);return()=>{bucket!.delete(observer);if(!bucket!.size)gestures.delete(id);};});}, }); }