{"version":3,"file":"useContentDensity.mjs","sources":["../../../../../src/lib/layouts/adaptive/hooks/useContentDensity.ts"],"sourcesContent":["/**\n * @fileoverview useContentDensity Hook\n *\n * Hook for analyzing and monitoring content density within a container.\n * Provides metrics about content distribution, size variance, and content type.\n *\n * @module layouts/adaptive/hooks/useContentDensity\n * @version 1.0.0\n */\n\nimport type React from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type { ContentDensity, Dimensions, UseContentDensityReturn } from '../types';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/**\n * Density thresholds based on items per 10000 square pixels.\n */\nconst DENSITY_THRESHOLDS: Record<ContentDensity, number> = {\n  minimal: 0.5,\n  low: 1,\n  medium: 2,\n  high: 4,\n  extreme: 8,\n} as const;\n\n/**\n * Minimum items for reliable analysis.\n */\nconst MIN_ITEMS_FOR_ANALYSIS = 2;\n\n// =============================================================================\n// HELPER FUNCTIONS\n// =============================================================================\n\n/**\n * Classifies density ratio into a ContentDensity category.\n */\nfunction classifyDensity(densityRatio: number): ContentDensity {\n  if (densityRatio >= DENSITY_THRESHOLDS.extreme) return 'extreme';\n  if (densityRatio >= DENSITY_THRESHOLDS.high) return 'high';\n  if (densityRatio >= DENSITY_THRESHOLDS.medium) return 'medium';\n  if (densityRatio >= DENSITY_THRESHOLDS.low) return 'low';\n  return 'minimal';\n}\n\n/**\n * Calculates coefficient of variation.\n */\nfunction calculateCV(values: number[]): number {\n  if (values.length === 0) return 0;\n  const mean = values.reduce((sum, val) => sum + val, 0) / values.length;\n  if (mean === 0) return 0;\n  const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;\n  return Math.sqrt(variance) / mean;\n}\n\n/**\n * Checks if element contains primarily images.\n */\nfunction hasImageContent(element: Element): boolean {\n  return element.querySelector('img, picture, video, canvas, svg') !== null;\n}\n\n/**\n * Checks if element contains primarily text.\n */\nfunction hasTextContent(element: Element): boolean {\n  const textLength = element.textContent?.length ?? 0;\n  return textLength > 100;\n}\n\n// =============================================================================\n// HOOK IMPLEMENTATION\n// =============================================================================\n\n/**\n * Options for useContentDensity hook.\n */\nexport interface UseContentDensityOptions {\n  /** Ref to the container element */\n  containerRef: React.RefObject<HTMLElement>;\n  /** Whether to automatically recalculate on changes */\n  autoRecalculate?: boolean;\n  /** Debounce interval for recalculation in ms */\n  debounceMs?: number;\n  /** Selector for child items (defaults to direct children) */\n  itemSelector?: string;\n}\n\n/**\n * Hook for analyzing content density within a container.\n *\n * @param options - Hook configuration options\n * @returns Content density metrics and controls\n *\n * @example\n * ```tsx\n * function DensityAwareLayout() {\n *   const containerRef = useRef<HTMLDivElement>(null);\n *   const {\n *     density,\n *     itemCount,\n *     averageItemSize,\n *     sizeVariance,\n *     isImageHeavy,\n *     isTextHeavy,\n *     recalculate\n *   } = useContentDensity({\n *     containerRef,\n *     autoRecalculate: true,\n *     debounceMs: 200\n *   });\n *\n *   const layoutMode = useMemo(() => {\n *     if (isImageHeavy && density === 'low') return 'grid';\n *     if (isTextHeavy) return 'list';\n *     if (density === 'high') return 'compact';\n *     return 'grid';\n *   }, [density, isImageHeavy, isTextHeavy]);\n *\n *   return (\n *     <div ref={containerRef} data-layout-mode={layoutMode}>\n *       {children}\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useContentDensity(options: UseContentDensityOptions): UseContentDensityReturn {\n  const { containerRef, autoRecalculate = true, debounceMs = 150, itemSelector } = options;\n\n  // State\n  const [density, setDensity] = useState<ContentDensity>('minimal');\n  const [itemCount, setItemCount] = useState(0);\n  const [averageItemSize, setAverageItemSize] = useState<Dimensions | null>(null);\n  const [sizeVariance, setSizeVariance] = useState(0);\n  const [isImageHeavy, setIsImageHeavy] = useState(false);\n  const [isTextHeavy, setIsTextHeavy] = useState(false);\n\n  // Refs\n  const observerRef = useRef<ResizeObserver | null>(null);\n  const mutationObserverRef = useRef<MutationObserver | null>(null);\n  const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n  // Calculate density\n  const recalculate = useCallback(() => {\n    const container = containerRef.current;\n    if (container == null) return;\n\n    // Get items\n    const items =\n      itemSelector != null && itemSelector !== ''\n        ? Array.from(container.querySelectorAll(itemSelector))\n        : Array.from(container.children);\n\n    const count = items.length;\n    setItemCount(count);\n\n    if (count < MIN_ITEMS_FOR_ANALYSIS) {\n      setDensity('minimal');\n      setAverageItemSize(null);\n      setSizeVariance(0);\n      setIsImageHeavy(false);\n      setIsTextHeavy(false);\n      return;\n    }\n\n    // Get container dimensions\n    const containerRect = container.getBoundingClientRect();\n    const containerArea = containerRect.width * containerRect.height;\n\n    if (containerArea === 0) {\n      setDensity('minimal');\n      return;\n    }\n\n    // Calculate item dimensions\n    const widths: number[] = [];\n    const heights: number[] = [];\n    const areas: number[] = [];\n    let imageCount = 0;\n    let textCount = 0;\n\n    for (const item of items) {\n      const rect = item.getBoundingClientRect();\n      widths.push(rect.width);\n      heights.push(rect.height);\n      areas.push(rect.width * rect.height);\n\n      if (hasImageContent(item)) imageCount++;\n      if (hasTextContent(item)) textCount++;\n    }\n\n    // Calculate averages\n    const avgWidth = widths.reduce((sum, w) => sum + w, 0) / count;\n    const avgHeight = heights.reduce((sum, h) => sum + h, 0) / count;\n    // const _totalArea = areas.reduce((sum, a) => sum + a, 0);\n\n    setAverageItemSize({ width: avgWidth, height: avgHeight });\n\n    // Calculate variance\n    const widthCV = calculateCV(widths);\n    const heightCV = calculateCV(heights);\n    setSizeVariance((widthCV + heightCV) / 2);\n\n    // Calculate density\n    const densityRatio = (count / containerArea) * 10000;\n    setDensity(classifyDensity(densityRatio));\n\n    // Content type analysis\n    setIsImageHeavy(imageCount / count > 0.5);\n    setIsTextHeavy(textCount / count > 0.5);\n  }, [containerRef, itemSelector]);\n\n  // Debounced recalculation\n  const debouncedRecalculate = useCallback(() => {\n    if (debounceTimerRef.current) {\n      clearTimeout(debounceTimerRef.current);\n    }\n\n    debounceTimerRef.current = setTimeout(recalculate, debounceMs);\n  }, [recalculate, debounceMs]);\n\n  // Set up observers\n  useEffect(() => {\n    if (!autoRecalculate) return;\n\n    const container = containerRef.current;\n    if (container == null) return;\n\n    // Observe size changes\n    observerRef.current = new ResizeObserver(debouncedRecalculate);\n    observerRef.current.observe(container);\n\n    // Observe DOM changes (children added/removed)\n    mutationObserverRef.current = new MutationObserver(debouncedRecalculate);\n    mutationObserverRef.current.observe(container, {\n      childList: true,\n      subtree: itemSelector != null && itemSelector !== '',\n    });\n\n    // Initial calculation - use requestAnimationFrame to defer\n    requestAnimationFrame(() => {\n      recalculate();\n    });\n\n    return () => {\n      observerRef.current?.disconnect();\n      mutationObserverRef.current?.disconnect();\n      if (debounceTimerRef.current) {\n        clearTimeout(debounceTimerRef.current);\n      }\n    };\n  }, [autoRecalculate, containerRef, itemSelector, debouncedRecalculate, recalculate]);\n\n  return {\n    density,\n    itemCount,\n    averageItemSize,\n    sizeVariance,\n    isImageHeavy,\n    isTextHeavy,\n    recalculate,\n  };\n}\n\n// =============================================================================\n// HELPER HOOKS\n// =============================================================================\n\n/**\n * Hook that returns true when content density exceeds a threshold.\n *\n * @param currentDensity - Current content density\n * @param threshold - Minimum density threshold\n * @returns Whether density meets or exceeds threshold\n *\n * @example\n * ```tsx\n * function CompactModeToggle({ density }: { density: ContentDensity }) {\n *   const shouldUseCompactMode = useDensityThreshold(density, 'high');\n *\n *   return shouldUseCompactMode ? <CompactLayout /> : <NormalLayout />;\n * }\n * ```\n */\nexport function useDensityThreshold(\n  currentDensity: ContentDensity,\n  threshold: ContentDensity\n): boolean {\n  const densityOrder: ContentDensity[] = ['minimal', 'low', 'medium', 'high', 'extreme'];\n  const currentIndex = densityOrder.indexOf(currentDensity);\n  const thresholdIndex = densityOrder.indexOf(threshold);\n\n  return currentIndex >= thresholdIndex;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { UseContentDensityReturn };\n"],"names":["DENSITY_THRESHOLDS","MIN_ITEMS_FOR_ANALYSIS","classifyDensity","densityRatio","calculateCV","values","mean","sum","val","variance","hasImageContent","element","hasTextContent","useContentDensity","options","containerRef","autoRecalculate","debounceMs","itemSelector","density","setDensity","useState","itemCount","setItemCount","averageItemSize","setAverageItemSize","sizeVariance","setSizeVariance","isImageHeavy","setIsImageHeavy","isTextHeavy","setIsTextHeavy","observerRef","useRef","mutationObserverRef","debounceTimerRef","recalculate","useCallback","container","items","count","containerRect","containerArea","widths","heights","areas","imageCount","textCount","item","rect","avgWidth","w","avgHeight","h","widthCV","heightCV","debouncedRecalculate","useEffect","useDensityThreshold","currentDensity","threshold","densityOrder","currentIndex","thresholdIndex"],"mappings":"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"}