{"version":3,"file":"useHydrationStatus.mjs","sources":["../../../../src/lib/hydration/hooks/useHydrationStatus.ts"],"sourcesContent":["/**\n * @file useHydrationStatus Hook\n * @description Hook for tracking the hydration status of a specific boundary.\n *\n * This hook provides real-time status tracking for hydration boundaries,\n * including state transitions, timing information, and error details.\n *\n * @module hydration/hooks/useHydrationStatus\n *\n * @example\n * ```tsx\n * function ComponentWithStatus() {\n *   const status = useHydrationStatus('my-boundary-id');\n *\n *   if (status.state === 'pending') {\n *     return <Skeleton />;\n *   }\n *\n *   if (status.state === 'error') {\n *     return <ErrorDisplay error={status.error} />;\n *   }\n *\n *   return <FullyHydratedComponent />;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\n\nimport { useOptionalHydrationContext } from '../HydrationProvider';\nimport { getHydrationScheduler } from '../hydration-scheduler';\n\nimport type {\n  HydrationStatus,\n  HydrationState,\n  HydrationBoundaryId,\n} from '../types';\n\nimport { createInitialHydrationStatus, createBoundaryId } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Extended status information with computed properties.\n */\nexport interface UseHydrationStatusReturn extends HydrationStatus {\n  /**\n   * Whether the boundary is fully hydrated and interactive.\n   */\n  readonly isHydrated: boolean;\n\n  /**\n   * Whether the boundary is currently hydrating.\n   */\n  readonly isHydrating: boolean;\n\n  /**\n   * Whether the boundary is pending hydration.\n   */\n  readonly isPending: boolean;\n\n  /**\n   * Whether the boundary encountered an error.\n   */\n  readonly hasError: boolean;\n\n  /**\n   * Whether the boundary was skipped (SSR-only).\n   */\n  readonly isSkipped: boolean;\n\n  /**\n   * Progress percentage (0-100) based on state.\n   */\n  readonly progress: number;\n\n  /**\n   * Human-readable status description.\n   */\n  readonly description: string;\n}\n\n/**\n * Options for the useHydrationStatus hook.\n */\nexport interface UseHydrationStatusOptions {\n  /**\n   * Polling interval in milliseconds for status updates.\n   * Set to 0 to disable polling (only event-driven updates).\n   *\n   * @default 100\n   */\n  readonly pollInterval?: number;\n\n  /**\n   * Whether to subscribe to scheduler events for real-time updates.\n   *\n   * @default true\n   */\n  readonly subscribeToEvents?: boolean;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Calculates progress percentage based on hydration state.\n */\nfunction calculateProgress(state: HydrationState): number {\n  switch (state) {\n    case 'pending':\n      return 0;\n    case 'hydrating':\n      return 50;\n    case 'hydrated':\n    case 'skipped':\n      return 100;\n    case 'error':\n      return 0;\n  }\n}\n\n/**\n * Gets a human-readable description of the hydration state.\n */\nfunction getStatusDescription(state: HydrationState, error?: Error | null): string {\n  switch (state) {\n    case 'pending':\n      return 'Waiting for hydration';\n    case 'hydrating':\n      return 'Hydrating component...';\n    case 'hydrated':\n      return 'Fully interactive';\n    case 'skipped':\n      return 'Server-rendered only';\n    case 'error':\n      return error?.message ?? 'Hydration failed';\n  }\n}\n\n/**\n * Creates an extended status object with computed properties.\n */\nfunction createExtendedStatus(status: HydrationStatus): UseHydrationStatusReturn {\n  return {\n    ...status,\n    isHydrated: status.state === 'hydrated',\n    isHydrating: status.state === 'hydrating',\n    isPending: status.state === 'pending',\n    hasError: status.state === 'error',\n    isSkipped: status.state === 'skipped',\n    progress: calculateProgress(status.state),\n    description: getStatusDescription(status.state, status.error),\n  };\n}\n\n// ============================================================================\n// Hook Implementation\n// ============================================================================\n\n/**\n * Hook for tracking the hydration status of a specific boundary.\n *\n * Provides real-time status updates with computed convenience properties.\n *\n * @param boundaryId - ID of the boundary to track (string will be converted)\n * @param options - Hook options\n * @returns Extended hydration status with computed properties\n *\n * @example\n * ```tsx\n * function StatusIndicator({ boundaryId }: { boundaryId: string }) {\n *   const {\n *     isHydrated,\n *     isHydrating,\n *     isPending,\n *     hasError,\n *     progress,\n *     description,\n *     duration,\n *   } = useHydrationStatus(boundaryId);\n *\n *   if (hasError) {\n *     return <span className=\"error\">{description}</span>;\n *   }\n *\n *   return (\n *     <div>\n *       <progress value={progress} max={100} />\n *       <span>{description}</span>\n *       {isHydrated && duration && (\n *         <span>Hydrated in {duration.toFixed(0)}ms</span>\n *       )}\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useHydrationStatus(\n  boundaryId: string | HydrationBoundaryId,\n  options: UseHydrationStatusOptions = {}\n): UseHydrationStatusReturn {\n  const { pollInterval = 100, subscribeToEvents = true } = options;\n\n  // Normalize boundary ID\n  const normalizedId = useMemo(\n    () =>\n      typeof boundaryId === 'string'\n        ? createBoundaryId(boundaryId)\n        : boundaryId,\n    [boundaryId]\n  );\n\n  // Get context (optional, as we can fallback to direct scheduler access)\n  const context = useOptionalHydrationContext();\n\n  // ==========================================================================\n  // State\n  // ==========================================================================\n\n  const [status, setStatus] = useState<UseHydrationStatusReturn>(() =>\n    createExtendedStatus(createInitialHydrationStatus())\n  );\n\n  // ==========================================================================\n  // Status Fetcher\n  // ==========================================================================\n\n  const fetchStatus = useCallback(() => {\n    let newStatus: HydrationStatus | undefined;\n\n    if (context) {\n      newStatus = context.getBoundaryStatus(normalizedId);\n    } else {\n      // Try direct scheduler access\n      try {\n        const scheduler = getHydrationScheduler();\n        newStatus = scheduler.getStatus(normalizedId);\n      } catch {\n        // Scheduler not available\n      }\n    }\n\n    if (newStatus) {\n      setStatus(createExtendedStatus(newStatus));\n    }\n  }, [context, normalizedId]);\n\n  // ==========================================================================\n  // Event Subscription\n  // ==========================================================================\n\n  useEffect(() => {\n    if (!subscribeToEvents) {\n      return;\n    }\n\n    // Try to subscribe to scheduler events\n    try {\n      const scheduler = getHydrationScheduler();\n\n      const unsubscribeStart = scheduler.on('hydration:start', (event) => {\n        if (event.boundaryId === normalizedId) {\n          fetchStatus();\n        }\n      });\n\n      const unsubscribeComplete = scheduler.on('hydration:complete', (event) => {\n        if (event.boundaryId === normalizedId) {\n          fetchStatus();\n        }\n      });\n\n      const unsubscribeError = scheduler.on('hydration:error', (event) => {\n        if (event.boundaryId === normalizedId) {\n          fetchStatus();\n        }\n      });\n\n      return () => {\n        unsubscribeStart();\n        unsubscribeComplete();\n        unsubscribeError();\n      };\n    } catch {\n      // Scheduler not available, rely on polling\n      return;\n    }\n  }, [normalizedId, subscribeToEvents, fetchStatus]);\n\n  // ==========================================================================\n  // Polling\n  // ==========================================================================\n\n  useEffect(() => {\n    // Initial fetch (use queueMicrotask to avoid synchronous setState in effect)\n    queueMicrotask(() => {\n      fetchStatus();\n    });\n\n    // Setup polling if enabled\n    if (pollInterval > 0) {\n      const intervalId = setInterval(fetchStatus, pollInterval);\n      return () => clearInterval(intervalId);\n    }\n\n    return;\n  }, [fetchStatus, pollInterval]);\n\n  // ==========================================================================\n  // Return\n  // ==========================================================================\n\n  return status;\n}\n\n/**\n * Hook for checking if a boundary is hydrated.\n *\n * Simplified hook when only hydration completion matters.\n *\n * @param boundaryId - ID of the boundary to check\n * @returns true if the boundary is fully hydrated\n *\n * @example\n * ```tsx\n * function InteractiveButton({ boundaryId }: { boundaryId: string }) {\n *   const isHydrated = useIsHydrated(boundaryId);\n *\n *   return (\n *     <button\n *       disabled={!isHydrated}\n *       onClick={handleClick}\n *     >\n *       {isHydrated ? 'Click Me' : 'Loading...'}\n *     </button>\n *   );\n * }\n * ```\n */\nexport function useIsHydrated(boundaryId: string | HydrationBoundaryId): boolean {\n  const { isHydrated } = useHydrationStatus(boundaryId);\n  return isHydrated;\n}\n\n/**\n * Hook for waiting until a boundary is hydrated.\n *\n * Returns a promise that resolves when the boundary is hydrated.\n * Useful for imperative code that needs to wait for hydration.\n *\n * @param boundaryId - ID of the boundary to wait for\n * @param timeout - Maximum time to wait in milliseconds (default: 10000)\n * @returns Promise that resolves when hydrated\n *\n * @example\n * ```tsx\n * function ComponentWithEffect({ boundaryId }: { boundaryId: string }) {\n *   const waitForHydration = useWaitForHydration(boundaryId);\n *\n *   useEffect(() => {\n *     async function init() {\n *       await waitForHydration();\n *       // Safe to interact with hydrated component now\n *       performPostHydrationSetup();\n *     }\n *     init();\n *   }, [waitForHydration]);\n *\n *   return <div>...</div>;\n * }\n * ```\n */\nexport function useWaitForHydration(\n  boundaryId: string | HydrationBoundaryId,\n  timeout = 10000\n): () => Promise<void> {\n  const { isHydrated, hasError } = useHydrationStatus(boundaryId);\n\n  return useCallback(async (): Promise<void> => {\n    return new Promise((resolve, reject) => {\n      // Already hydrated\n      if (isHydrated) {\n        resolve();\n        return;\n      }\n\n      // Already errored\n      if (hasError) {\n        reject(new Error('Hydration failed'));\n        return;\n      }\n\n      // Setup timeout\n      const timeoutId = setTimeout(() => {\n        reject(new Error(`Hydration timeout after ${timeout}ms`));\n      }, timeout);\n\n      // Poll for completion\n      const checkInterval = setInterval(() => {\n        try {\n          const scheduler = getHydrationScheduler();\n          const status = scheduler.getStatus(\n            typeof boundaryId === 'string' ? createBoundaryId(boundaryId) : boundaryId\n          );\n\n          if (status?.state === 'hydrated') {\n            clearTimeout(timeoutId);\n            clearInterval(checkInterval);\n            resolve();\n          } else if (status?.state === 'error') {\n            clearTimeout(timeoutId);\n            clearInterval(checkInterval);\n            reject(status.error ?? new Error('Hydration failed'));\n          }\n        } catch {\n          // Continue polling\n        }\n      }, 50);\n    });\n  }, [boundaryId, isHydrated, hasError, timeout]);\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\n// Types are already exported inline above\n"],"names":["calculateProgress","state","getStatusDescription","error","createExtendedStatus","status","useHydrationStatus","boundaryId","options","pollInterval","subscribeToEvents","normalizedId","useMemo","createBoundaryId","context","useOptionalHydrationContext","setStatus","useState","createInitialHydrationStatus","fetchStatus","useCallback","newStatus","getHydrationScheduler","useEffect","scheduler","unsubscribeStart","event","unsubscribeComplete","unsubscribeError","intervalId","useIsHydrated","isHydrated","useWaitForHydration","timeout","hasError","resolve","reject","timeoutId","checkInterval"],"mappings":"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"}