import type { Camera, Point } from '../core/coords'; import type { Node, NodeId } from '../types'; type NodeChange = { kind: 'add'; node: Node; } | { kind: 'remove'; node: Node; } | { kind: 'update'; id: NodeId; before: Node; after: Node; }; type GuideChange = { kind: 'add'; guide: Guide; } | { kind: 'remove'; guide: Guide; } | { kind: 'clear'; guides: Guide[]; } | { kind: 'setActive'; before: GuideId | null; after: GuideId | null; }; type VisualGroup = { id: string; members: NodeId[]; }; type VisualGroupChange = { kind: 'add' | 'remove'; group: VisualGroup; }; type HistoryEntry = { label?: string; changes: NodeChange[]; guideChanges?: GuideChange[]; visualGroupChanges?: VisualGroupChange[]; }; export type GuideId = string; export type Guide = { id: GuideId; axis: 'x' | 'y'; value: number; }; export type AlignGuide = { axis: 'x' | 'y'; at: number; kind: 'edge' | 'center'; targetId: NodeId | 'selection'; }; export type ResizeSelectionParams = { dx: number; dy: number; anchor?: 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'; proportional?: boolean; fromCenter?: boolean; /** Base dimensions at gesture start (world units). If provided, use these instead of current node size for temporary updates. */ fromWidth?: number; fromHeight?: number; }; export declare const MIN_ZOOM = 0.6; export declare const MAX_ZOOM = 2.4; export type CanvasState = { readonly camera: Camera; readonly nodes: Record; /** Map of selected node IDs for O(1) membership checks. */ readonly selected: Record; /** UI: box-select (lasso) is active */ readonly boxSelecting: boolean; /** UI: true while a node drag gesture is actively moving (after threshold). */ readonly isDraggingNode: boolean; /** UI: purely visual groups, do NOT affect nodes hierarchy */ readonly visualGroups: Record; /** UI: currently selected visual group id (single-select for now) */ readonly selectedVisualGroupId: string | null; /** UI: currently hovered visual group id (drives container highlight from node hover) */ readonly hoveredVisualGroupId: string | null; /** UI: secondary hovered visual group id for dual highlight (e.g., local group alongside parent) */ readonly hoveredVisualGroupIdSecondary: string | null; /** UI: inner-edit mode target node; when set, drags affect only this node (and its descendants). */ readonly innerEditNodeId: NodeId | null; /** UI: true while a transform gesture is active (resize/rotate/radius). */ readonly isTransforming: boolean; /** Internal counter for add-at-center offset progression. */ readonly centerAddIndex: number; /** Clipboard buffer storing a snapshot of nodes to paste. */ readonly clipboard: { nodes: Node[]; } | null; /** Internal counter for paste offset progression. */ readonly pasteIndex: number; /** History stacks (nodes-only). */ readonly historyPast: HistoryEntry[]; readonly historyFuture: HistoryEntry[]; /** Active batch for coalescing changes (e.g., drag). */ readonly historyBatch: { label?: string; changes: NodeChange[]; updateIndexById: Record; groupChanges?: VisualGroupChange[]; } | null; /** UI: show rulers overlay */ readonly showRulers: boolean; /** UI: collection of guide lines (world-locked) */ readonly guides: Guide[]; /** UI: currently active guide (for deletion, highlight) */ readonly activeGuideId: GuideId | null; /** UI: alignment helper guides for snapping (ephemeral) */ readonly alignmentGuides: AlignGuide[]; /** UI: current snap offset applied to pointer (world units) */ readonly snapOffset: { dx: number; dy: number; } | null; /** UI: ephemeral snap lock on X axis during drag (hysteresis) */ readonly snapLockX: { at: number; kind: 'edge' | 'center'; } | null; /** UI: ephemeral snap lock on Y axis during drag (hysteresis) */ readonly snapLockY: { at: number; kind: 'edge' | 'center'; } | null; /** UI: global toggle for snapping */ readonly alignSnapEnabled: boolean; /** UI: snapping tolerance in screen pixels */ readonly alignSnapTolerancePx: number; }; export type CanvasActions = { setCamera: (camera: Camera) => void; panBy: (dx: number, dy: number) => void; zoomTo: (zoom: number) => void; /** Zoom by factor centered at screenPoint (screen coords in px). */ zoomByAt: (screenPoint: Point, factor: number) => void; enterInnerEdit: (id: NodeId) => void; exitInnerEdit: () => void; setBoxSelecting: (active: boolean) => void; setDraggingNode: (active: boolean) => void; toggleRulers: () => void; addGuide: (axis: 'x' | 'y', value: number) => GuideId; moveGuideTemporary: (id: GuideId, value: number) => void; moveGuide: (id: GuideId, value: number) => void; moveGuideCommit: (id: GuideId, fromValue: number, toValue: number) => void; removeGuide: (id: GuideId) => void; clearGuides: () => void; setActiveGuide: (id: GuideId | null) => void; setAlignSnapEnabled: (enabled: boolean) => void; setAlignSnapTolerancePx: (px: number) => void; setAlignmentGuides: (guides: AlignGuide[]) => void; clearAlignmentGuides: () => void; setSnapOffset: (offset: { dx: number; dy: number; } | null) => void; clearSnapOffset: () => void; addNode: (node: Node) => void; /** Add node at the visible center regardless of zoom, with slight diagonal offset per call. */ addNodeAtCenter: (node: Pick) => void; updateNode: (id: NodeId, patch: Partial) => void; removeNode: (id: NodeId) => void; /** Remove multiple nodes at once. */ removeNodes: (ids: NodeId[]) => void; /** Group: assign parentId to given children (no cycles). */ groupNodes: (parentId: NodeId, childIds: NodeId[]) => void; /** Ungroup: clear parentId for given nodes. */ ungroup: (ids: NodeId[]) => void; /** Move all currently selected nodes by dx,dy in WORLD units. */ moveSelectedBy: (dx: number, dy: number) => void; resizeSelectionTemporary: (params: ResizeSelectionParams) => void; resizeSelectionCommit: (from: { width: number; height: number; }, to: { width: number; height: number; }) => void; rotateSelectionTemporary: (deltaAngle: number) => void; rotateSelectionCommit: (fromAngle: number, toAngle: number) => void; setCornerRadiusTemporary: (value: Node['cornerRadius']) => void; setCornerRadiusCommit: (from: Node['cornerRadius'], to: Node['cornerRadius']) => void; clearSelection: () => void; /** Select only the given node id (single selection). */ selectOnly: (id: NodeId) => void; addToSelection: (id: NodeId) => void; removeFromSelection: (id: NodeId) => void; toggleInSelection: (id: NodeId) => void; /** Delete all currently selected nodes. */ deleteSelected: () => void; createVisualGroupFromSelection: () => void; selectVisualGroup: (id: string | null) => void; setHoveredVisualGroupId: (id: string | null) => void; setHoveredVisualGroupIdSecondary: (id: string | null) => void; copySelection: () => void; cutSelection: () => void; pasteClipboard: (position?: Point) => void; beginHistory: (label?: string) => void; endHistory: () => void; undo: () => void; redo: () => void; }; export type CanvasStore = CanvasState & CanvasActions; export declare const useCanvasStore: import("zustand").UseBoundStore>; export declare function useCamera(): Camera; export declare function useCanvasActions(): Pick; export declare function useInnerEdit(): NodeId | null; export declare function useInnerEditActions(): Pick; export declare function useNodes(): Node[]; export declare function useNode(id: NodeId): Node | undefined; export declare function useNodeActions(): Pick; export declare function useDeleteActions(): Pick; export declare function useSelectedIds(): NodeId[]; export declare function useIsSelected(id: NodeId): boolean; export declare function useSelectionActions(): Pick; export declare function useDndActions(): Pick; export declare function useGroupingActions(): Pick; export declare function useHistoryActions(): Pick; export declare function useClipboardActions(): Pick; export declare function useHasClipboard(): boolean; export declare function useShowRulers(): boolean; export declare function useGuides(): Guide[]; export declare function useActiveGuideId(): GuideId | null; export declare function useRulersActions(): Pick; export declare function useIsTransforming(): boolean; export declare function useActiveEditNodeId(): NodeId | null; export declare function useEditBoundingBox(): { left: number; top: number; right: number; bottom: number; width: number; height: number; } | null; export {};