{"version":3,"file":"AdaptiveStack.mjs","sources":["../../../../src/lib/layouts/adaptive/AdaptiveStack.tsx"],"sourcesContent":["/**\n * @fileoverview AdaptiveStack Component\n *\n * A morphing stack layout component that automatically switches between\n * horizontal and vertical layouts based on available space or explicit\n * configuration. Supports smooth transitions between orientations.\n *\n * @module layouts/adaptive/AdaptiveStack\n * @version 1.0.0\n */\n\nimport {\n  Children,\n  cloneElement,\n  isValidElement,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  type CSSProperties,\n  type ReactElement,\n  type ReactNode,\n} from 'react';\nimport type { AdaptiveStackProps } from './types.ts';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/**\n * Default breakpoint for responsive direction switch (768px).\n */\nconst DEFAULT_BREAKPOINT = 768;\n\n/**\n * Default gap between items.\n */\nconst DEFAULT_GAP = 16;\n\n// =============================================================================\n// HELPER FUNCTIONS\n// =============================================================================\n\n/**\n * Maps justify prop values to CSS justify-content values.\n */\nfunction mapJustify(justify: AdaptiveStackProps['justify']): string {\n  switch (justify) {\n    case 'start':\n      return 'flex-start';\n    case 'center':\n      return 'center';\n    case 'end':\n      return 'flex-end';\n    case 'between':\n      return 'space-between';\n    case 'around':\n      return 'space-around';\n    case 'evenly':\n      return 'space-evenly';\n    default:\n      return 'flex-start';\n  }\n}\n\n/**\n * Maps align prop values to CSS align-items values.\n */\nfunction mapAlign(align: AdaptiveStackProps['align']): string {\n  switch (align) {\n    case 'start':\n      return 'flex-start';\n    case 'center':\n      return 'center';\n    case 'end':\n      return 'flex-end';\n    case 'stretch':\n      return 'stretch';\n    default:\n      return 'stretch';\n  }\n}\n\n/**\n * Generates a unique item ID.\n */\nfunction generateItemId(index: number): string {\n  return `stack-item-${index}`;\n}\n\n// =============================================================================\n// ADAPTIVE STACK COMPONENT\n// =============================================================================\n\n/**\n * AdaptiveStack is a layout component that automatically manages stacking\n * direction based on available space or explicit configuration.\n *\n * @example\n * ```tsx\n * // Responsive stack that switches at 768px\n * <AdaptiveStack direction=\"responsive\" gap={20}>\n *   <Card>Item 1</Card>\n *   <Card>Item 2</Card>\n *   <Card>Item 3</Card>\n * </AdaptiveStack>\n *\n * // Horizontal stack with space-between\n * <AdaptiveStack direction=\"horizontal\" justify=\"between\" align=\"center\">\n *   <Logo />\n *   <Navigation />\n *   <Actions />\n * </AdaptiveStack>\n * ```\n */\nexport function AdaptiveStack({\n  children,\n  direction = 'vertical',\n  breakpoint = DEFAULT_BREAKPOINT,\n  gap = DEFAULT_GAP,\n  align = 'stretch',\n  justify = 'start',\n  wrap = false,\n  className,\n  style,\n}: AdaptiveStackProps): ReactNode {\n  // Refs\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  // State\n  const [containerWidth, setContainerWidth] = useState<number>(0);\n  const [isTransitioning, setIsTransitioning] = useState(false);\n\n  // Calculate effective direction based on container width\n  const effectiveDirection = useMemo(() => {\n    if (direction !== 'responsive') return direction;\n    return containerWidth >= breakpoint ? 'horizontal' : 'vertical';\n  }, [direction, containerWidth, breakpoint]);\n\n  // Track previous direction for transitions\n  const previousDirectionRef = useRef(effectiveDirection);\n\n  // Observe container size for responsive behavior\n  useEffect(() => {\n    if (direction !== 'responsive') return;\n\n    const container = containerRef.current;\n    if (!container) return;\n\n    const observer = new ResizeObserver((entries) => {\n      const [entry] = entries;\n      if (entry !== undefined) {\n        setContainerWidth(entry.contentRect.width);\n      }\n    });\n\n    observer.observe(container);\n\n    return () => observer.disconnect();\n  }, [direction]);\n\n  // Handle direction transitions\n  // Use layout effect to ensure state updates happen synchronously before paint\n  useLayoutEffect(() => {\n    if (effectiveDirection !== previousDirectionRef.current) {\n      let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n      // Use requestAnimationFrame to defer state update after render\n      const frameId = requestAnimationFrame(() => {\n        setIsTransitioning(true);\n\n        // Reset transition state after animation\n        timeoutId = setTimeout(() => {\n          setIsTransitioning(false);\n          previousDirectionRef.current = effectiveDirection;\n        }, 300); // Match transition duration\n      });\n\n      return () => {\n        cancelAnimationFrame(frameId);\n        if (timeoutId !== null) {\n          clearTimeout(timeoutId);\n        }\n      };\n    }\n    return undefined;\n  }, [effectiveDirection]);\n\n  // Container styles\n  const containerStyles = useMemo<CSSProperties>(() => {\n    const baseStyles: CSSProperties = {\n      display: 'flex',\n      flexDirection: effectiveDirection === 'horizontal' ? 'row' : 'column',\n      justifyContent: mapJustify(justify),\n      alignItems: mapAlign(align),\n      gap: `${gap}px`,\n      flexWrap: wrap ? 'wrap' : 'nowrap',\n      width: '100%',\n      // Smooth transition for direction changes\n      transition: isTransitioning ? 'all 0.3s ease-out' : undefined,\n      ...style,\n    };\n\n    return baseStyles;\n  }, [effectiveDirection, justify, align, gap, wrap, isTransitioning, style]);\n\n  // Render children with layout IDs\n  const renderedChildren = useMemo(() => {\n    return Children.map(children, (child, index): ReactNode => {\n      if (!isValidElement(child)) return child;\n\n      const itemId = generateItemId(index);\n\n      return cloneElement(\n        child as ReactElement<{ 'data-layout-id'?: string; style?: CSSProperties }>,\n        {\n          'data-layout-id': itemId,\n          style: {\n            ...(child.props as { style?: CSSProperties }).style,\n            // Add transition for child items during direction change\n            transition: isTransitioning ? 'all 0.3s ease-out' : undefined,\n          },\n        }\n      );\n    });\n  }, [children, isTransitioning]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={className}\n      style={containerStyles}\n      data-adaptive-stack\n      data-direction={effectiveDirection}\n      data-transitioning={isTransitioning}\n    >\n      {renderedChildren}\n    </div>\n  );\n}\n\n// =============================================================================\n// SPECIALIZED STACK VARIANTS\n// =============================================================================\n\n/**\n * HStack is a horizontal adaptive stack.\n */\nexport function HStack(props: Omit<AdaptiveStackProps, 'direction'>): ReactNode {\n  return <AdaptiveStack {...props} direction=\"horizontal\" />;\n}\n\n/**\n * VStack is a vertical adaptive stack.\n */\nexport function VStack(props: Omit<AdaptiveStackProps, 'direction'>): ReactNode {\n  return <AdaptiveStack {...props} direction=\"vertical\" />;\n}\n\n/**\n * ResponsiveStack automatically switches between horizontal and vertical.\n */\nexport function ResponsiveStack(props: Omit<AdaptiveStackProps, 'direction'>): ReactNode {\n  return <AdaptiveStack {...props} direction=\"responsive\" />;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { AdaptiveStackProps };\n"],"names":["DEFAULT_BREAKPOINT","DEFAULT_GAP","mapJustify","justify","mapAlign","align","generateItemId","index","AdaptiveStack","children","direction","breakpoint","gap","wrap","className","style","containerRef","useRef","containerWidth","setContainerWidth","useState","isTransitioning","setIsTransitioning","effectiveDirection","useMemo","previousDirectionRef","useEffect","container","observer","entries","entry","useLayoutEffect","timeoutId","frameId","containerStyles","renderedChildren","Children","child","isValidElement","itemId","cloneElement","jsx","HStack","props","VStack","ResponsiveStack"],"mappings":";;AAiCA,MAAMA,IAAqB,KAKrBC,IAAc;AASpB,SAASC,EAAWC,GAAgD;AAClE,UAAQA,GAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;AAKA,SAASC,EAASC,GAA4C;AAC5D,UAAQA,GAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;AAKA,SAASC,EAAeC,GAAuB;AAC7C,SAAO,cAAcA,CAAK;AAC5B;AA2BO,SAASC,EAAc;AAAA,EAC5B,UAAAC;AAAA,EACA,WAAAC,IAAY;AAAA,EACZ,YAAAC,IAAaX;AAAA,EACb,KAAAY,IAAMX;AAAA,EACN,OAAAI,IAAQ;AAAA,EACR,SAAAF,IAAU;AAAA,EACV,MAAAU,IAAO;AAAA,EACP,WAAAC;AAAA,EACA,OAAAC;AACF,GAAkC;AAEhC,QAAMC,IAAeC,EAAuB,IAAI,GAG1C,CAACC,GAAgBC,CAAiB,IAAIC,EAAiB,CAAC,GACxD,CAACC,GAAiBC,CAAkB,IAAIF,EAAS,EAAK,GAGtDG,IAAqBC,EAAQ,MAC7Bd,MAAc,eAAqBA,IAChCQ,KAAkBP,IAAa,eAAe,YACpD,CAACD,GAAWQ,GAAgBP,CAAU,CAAC,GAGpCc,IAAuBR,EAAOM,CAAkB;AAGtD,EAAAG,EAAU,MAAM;AACd,QAAIhB,MAAc,aAAc;AAEhC,UAAMiB,IAAYX,EAAa;AAC/B,QAAI,CAACW,EAAW;AAEhB,UAAMC,IAAW,IAAI,eAAe,CAACC,MAAY;AAC/C,YAAM,CAACC,CAAK,IAAID;AAChB,MAAIC,MAAU,UACZX,EAAkBW,EAAM,YAAY,KAAK;AAAA,IAE7C,CAAC;AAED,WAAAF,EAAS,QAAQD,CAAS,GAEnB,MAAMC,EAAS,WAAA;AAAA,EACxB,GAAG,CAAClB,CAAS,CAAC,GAIdqB,EAAgB,MAAM;AACpB,QAAIR,MAAuBE,EAAqB,SAAS;AACvD,UAAIO,IAAkD;AAGtD,YAAMC,IAAU,sBAAsB,MAAM;AAC1C,QAAAX,EAAmB,EAAI,GAGvBU,IAAY,WAAW,MAAM;AAC3B,UAAAV,EAAmB,EAAK,GACxBG,EAAqB,UAAUF;AAAA,QACjC,GAAG,GAAG;AAAA,MACR,CAAC;AAED,aAAO,MAAM;AACX,6BAAqBU,CAAO,GACxBD,MAAc,QAChB,aAAaA,CAAS;AAAA,MAE1B;AAAA,IACF;AAAA,EAEF,GAAG,CAACT,CAAkB,CAAC;AAGvB,QAAMW,IAAkBV,EAAuB,OACX;AAAA,IAChC,SAAS;AAAA,IACT,eAAeD,MAAuB,eAAe,QAAQ;AAAA,IAC7D,gBAAgBrB,EAAWC,CAAO;AAAA,IAClC,YAAYC,EAASC,CAAK;AAAA,IAC1B,KAAK,GAAGO,CAAG;AAAA,IACX,UAAUC,IAAO,SAAS;AAAA,IAC1B,OAAO;AAAA;AAAA,IAEP,YAAYQ,IAAkB,sBAAsB;AAAA,IACpD,GAAGN;AAAA,EAAA,IAIJ,CAACQ,GAAoBpB,GAASE,GAAOO,GAAKC,GAAMQ,GAAiBN,CAAK,CAAC,GAGpEoB,IAAmBX,EAAQ,MACxBY,EAAS,IAAI3B,GAAU,CAAC4B,GAAO9B,MAAqB;AACzD,QAAI,CAAC+B,EAAeD,CAAK,EAAG,QAAOA;AAEnC,UAAME,IAASjC,EAAeC,CAAK;AAEnC,WAAOiC;AAAA,MACLH;AAAA,MACA;AAAA,QACE,kBAAkBE;AAAA,QAClB,OAAO;AAAA,UACL,GAAIF,EAAM,MAAoC;AAAA;AAAA,UAE9C,YAAYhB,IAAkB,sBAAsB;AAAA,QAAA;AAAA,MACtD;AAAA,IACF;AAAA,EAEJ,CAAC,GACA,CAACZ,GAAUY,CAAe,CAAC;AAE9B,SACE,gBAAAoB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAKzB;AAAA,MACL,WAAAF;AAAA,MACA,OAAOoB;AAAA,MACP,uBAAmB;AAAA,MACnB,kBAAgBX;AAAA,MAChB,sBAAoBF;AAAA,MAEnB,UAAAc;AAAA,IAAA;AAAA,EAAA;AAGP;AASO,SAASO,EAAOC,GAAyD;AAC9E,SAAO,gBAAAF,EAACjC,GAAA,EAAe,GAAGmC,GAAO,WAAU,cAAa;AAC1D;AAKO,SAASC,EAAOD,GAAyD;AAC9E,SAAO,gBAAAF,EAACjC,GAAA,EAAe,GAAGmC,GAAO,WAAU,YAAW;AACxD;AAKO,SAASE,EAAgBF,GAAyD;AACvF,SAAO,gBAAAF,EAACjC,GAAA,EAAe,GAAGmC,GAAO,WAAU,cAAa;AAC1D;"}