{"version":3,"file":"useLayoutMorph.mjs","sources":["../../../../../src/lib/layouts/adaptive/hooks/useLayoutMorph.ts"],"sourcesContent":["/**\n * @fileoverview useLayoutMorph Hook\n *\n * Hook for triggering and controlling FLIP-based morph transitions.\n * Manages element registration, snapshots, and animation playback.\n *\n * @module layouts/adaptive/hooks/useLayoutMorph\n * @version 1.0.0\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react';\nimport type {\n  LayoutRect,\n  LayoutState,\n  MorphTransitionConfig,\n  UseLayoutMorphReturn,\n} from '../types';\nimport { DEFAULT_MORPH_TRANSITION_CONFIG } from '../types';\nimport { createMorphController } from '../morph-transition';\n\n// =============================================================================\n// HOOK IMPLEMENTATION\n// =============================================================================\n\n/**\n * Options for useLayoutMorph hook.\n */\nexport interface UseLayoutMorphOptions {\n  /** Morph transition configuration */\n  config?: Partial<MorphTransitionConfig>;\n  /** Callback when morph starts */\n  onMorphStart?: () => void;\n  /** Callback when morph completes */\n  onMorphComplete?: () => void;\n  /** Callback when morph is interrupted */\n  onMorphInterrupt?: () => void;\n}\n\n/**\n * Hook for controlling FLIP-based morph transitions.\n *\n * @param options - Hook configuration options\n * @returns Morph transition controls\n *\n * @example\n * ```tsx\n * function MorphingLayout() {\n *   const {\n *     morph,\n *     cancel,\n *     progress,\n *     isTransitioning,\n *     registerElement,\n *     snapshot\n *   } = useLayoutMorph({\n *     config: { duration: 300, easing: 'ease-out' },\n *     onMorphComplete: () => console.log('Morph complete!')\n *   });\n *\n *   const handleLayoutChange = async () => {\n *     snapshot(); // Capture current state\n *     // Make layout changes...\n *     await morph({ mode: 'list' });\n *   };\n *\n *   return (\n *     <div>\n *       <button onClick={handleLayoutChange}>Change Layout</button>\n *       {isTransitioning && <p>Progress: {(progress * 100).toFixed(0)}%</p>}\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useLayoutMorph(options: UseLayoutMorphOptions = {}): UseLayoutMorphReturn {\n  const { config = {}, onMorphStart, onMorphComplete, onMorphInterrupt } = options;\n\n  // Refs\n  const controllerRef = useRef<ReturnType<typeof createMorphController> | null>(null);\n  const elementsRef = useRef<Map<string, RefObject<HTMLElement>>>(new Map());\n  const firstSnapshotRef = useRef<Map<string, LayoutRect> | null>(null);\n\n  // State\n  const [progress, setProgress] = useState(0);\n  const [isTransitioning, setIsTransitioning] = useState(false);\n\n  // Merge config\n  const mergedConfig = useMemo<MorphTransitionConfig>(\n    () => ({\n      ...DEFAULT_MORPH_TRANSITION_CONFIG,\n      ...config,\n      onStart: () => {\n        onMorphStart?.();\n        config.onStart?.();\n      },\n      onFrame: (p) => {\n        setProgress(p);\n        config.onFrame?.(p);\n      },\n      onComplete: () => {\n        setIsTransitioning(false);\n        onMorphComplete?.();\n        config.onComplete?.();\n      },\n      onInterrupt: () => {\n        setIsTransitioning(false);\n        onMorphInterrupt?.();\n        config.onInterrupt?.();\n      },\n    }),\n    [config, onMorphStart, onMorphComplete, onMorphInterrupt]\n  );\n\n  // Initialize controller\n  useEffect(() => {\n    controllerRef.current = createMorphController(mergedConfig);\n\n    return () => {\n      controllerRef.current?.destroy();\n      controllerRef.current = null;\n    };\n  }, [mergedConfig]);\n\n  // Register element for FLIP tracking\n  const registerElement = useCallback((id: string, ref: RefObject<HTMLElement>) => {\n    elementsRef.current.set(id, ref);\n  }, []);\n\n  // Unregister element\n  const unregisterElement = useCallback((id: string) => {\n    elementsRef.current.delete(id);\n  }, []);\n\n  // Get current elements as HTMLElement map\n  const getElements = useCallback((): Map<string, HTMLElement> => {\n    const elements = new Map<string, HTMLElement>();\n\n    for (const [id, ref] of elementsRef.current) {\n      if (ref.current != null) {\n        elements.set(id, ref.current);\n      }\n    }\n\n    return elements;\n  }, []);\n\n  // Take snapshot of current state\n  const snapshot = useCallback(() => {\n    const controller = controllerRef.current;\n    if (!controller) return;\n\n    const elements = getElements();\n    firstSnapshotRef.current = controller.snapshotFirst(elements);\n  }, [getElements]);\n\n  // Trigger morph transition\n  const morph = useCallback(\n    async (_targetState: Partial<LayoutState>): Promise<void> => {\n      const controller = controllerRef.current;\n      if (!controller) return;\n\n      setIsTransitioning(true);\n      setProgress(0);\n\n      const elements = getElements();\n\n      // If we have a first snapshot, use it; otherwise take one now\n      const first = firstSnapshotRef.current ?? controller.snapshotFirst(elements);\n      firstSnapshotRef.current = null;\n\n      // Force layout update\n      // This would typically be triggered by the layout engine updating positions\n      // For now, we'll just wait a frame\n      await new Promise((resolve) => requestAnimationFrame(resolve));\n\n      const last = controller.snapshotLast(elements);\n      const context = controller.createAnimation(first, last, elements);\n\n      try {\n        await controller.play(context);\n      } catch (error) {\n        // Animation was cancelled or interrupted\n        console.warn('[useLayoutMorph] Animation interrupted:', error);\n      }\n    },\n    [getElements]\n  );\n\n  // Cancel current transition\n  const cancel = useCallback(() => {\n    controllerRef.current?.cancel();\n  }, []);\n\n  return {\n    morph,\n    cancel,\n    progress,\n    isTransitioning,\n    registerElement,\n    unregisterElement,\n    snapshot,\n  };\n}\n\n// =============================================================================\n// HELPER HOOKS\n// =============================================================================\n\n/**\n * Hook for automatically registering an element with the morph system.\n *\n * @param morphHook - The morph hook return value\n * @param id - Unique ID for the element\n * @returns Ref to attach to the element\n *\n * @example\n * ```tsx\n * function MorphableCard({ id, morphHook }: { id: string; morphHook: UseLayoutMorphReturn }) {\n *   const ref = useMorphElement(morphHook, id);\n *\n *   return <div ref={ref} data-layout-id={id}>Content</div>;\n * }\n * ```\n */\nexport function useMorphElement(\n  morphHook: Pick<UseLayoutMorphReturn, 'registerElement' | 'unregisterElement'>,\n  id: string\n): RefObject<HTMLDivElement | null> {\n  const ref = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    morphHook.registerElement(id, ref as RefObject<HTMLElement>);\n\n    return () => {\n      morphHook.unregisterElement(id);\n    };\n  }, [morphHook, id]);\n\n  return ref;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { UseLayoutMorphReturn };\n"],"names":["useLayoutMorph","options","config","onMorphStart","onMorphComplete","onMorphInterrupt","controllerRef","useRef","elementsRef","firstSnapshotRef","progress","setProgress","useState","isTransitioning","setIsTransitioning","mergedConfig","useMemo","DEFAULT_MORPH_TRANSITION_CONFIG","p","useEffect","createMorphController","registerElement","useCallback","id","ref","unregisterElement","getElements","elements","snapshot","controller","morph","_targetState","first","resolve","last","context","error","cancel","useMorphElement","morphHook"],"mappings":"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"}