{"version":3,"file":"chat-panel-root.cjs","sources":["../../../components/chat-panel/chat-panel-root.tsx"],"sourcesContent":["'use client';\n\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport {\n  DndContext,\n  type DragEndEvent,\n  type DragStartEvent,\n  type Modifier,\n  PointerSensor,\n  useDraggable,\n  useSensor,\n  useSensors\n} from '@dnd-kit/core';\nimport { cx } from 'class-variance-authority';\nimport {\n  ComponentProps,\n  CSSProperties,\n  ReactNode,\n  PointerEvent as ReactPointerEvent,\n  RefObject,\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState\n} from 'react';\nimport styles from './chat-panel.module.css';\nimport {\n  ChatPanelContext,\n  type ChatPanelContextValue,\n  type ChatPanelMode,\n  type ChatPanelSide\n} from './chat-panel-context';\n\nexport interface ChatPanelPosition {\n  x: number;\n  y: number;\n}\n\nexport interface ChatPanelSize {\n  width: number;\n  height: number;\n}\n\n/** An element (or ref to one) that confines floating-window dragging. */\nexport type ChatPanelDragBoundary = HTMLElement | RefObject<HTMLElement | null>;\n\n/** Keep at least this much of the header on screen while clamping. */\nconst HEADER_SAFE_PX = 48;\n\nconst DEFAULT_SIZE: ChatPanelSize = { width: 400, height: 560 };\nconst DEFAULT_MIN_SIZE: ChatPanelSize = { width: 280, height: 320 };\n\ntype ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';\n\n/** Which resize handles to render, mirroring the CSS `resize` vocabulary. */\nexport type ChatPanelResize = 'both' | 'horizontal' | 'vertical' | 'none';\n\n/** How a mode change animates. */\nexport type ChatPanelTransition = 'minimal' | 'morph';\n\nconst RESIZE_HANDLES: Record<ChatPanelResize, ResizeDirection[]> = {\n  both: ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'],\n  horizontal: ['e', 'w'],\n  vertical: ['n', 's'],\n  none: []\n};\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), Math.max(min, max));\n}\n\ninterface DragBounds {\n  left: number;\n  top: number;\n  right: number;\n  bottom: number;\n}\n\nfunction resolveDragBoundary(\n  boundary: ChatPanelDragBoundary | undefined\n): HTMLElement | null {\n  if (!boundary) return null;\n  return 'current' in boundary ? boundary.current : boundary;\n}\n\n/**\n * The mode the panel is leaving, for the one frame that renders the new mode so\n * there are values to transition *from*. Null on first paint and once settled.\n */\nfunction useModeChange(mode: ChatPanelMode) {\n  const [settledMode, setSettledMode] = useState(mode);\n  const from = settledMode === mode ? null : settledMode;\n\n  useEffect(() => {\n    if (!from) return;\n    const frame = requestAnimationFrame(() => setSettledMode(mode));\n    return () => cancelAnimationFrame(frame);\n  }, [from, mode]);\n\n  return from;\n}\n\ninterface MorphBox {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\nfunction readBox(node: HTMLElement): MorphBox {\n  const { left, top, width, height } = node.getBoundingClientRect();\n  return { x: left, y: top, width, height };\n}\n\n/**\n * Draws the panel back over the box it just left — an inverse translate and\n * scale off its top-left corner — and lets go a frame later, so the two modes\n * read as one shape moving. The old box has to be read while the DOM still\n * shows the old mode, hence the measurement in render; the inverse can only be\n * worked out once the new mode has laid out, hence the layout effect.\n */\nfunction useMorph(\n  panelRef: RefObject<HTMLElement | null>,\n  mode: ChatPanelMode,\n  starting: boolean,\n  enabled: boolean\n) {\n  const measuredModeRef = useRef(mode);\n  const fromRef = useRef<MorphBox | null>(null);\n\n  if (measuredModeRef.current !== mode) {\n    measuredModeRef.current = mode;\n    const panel = panelRef.current;\n    fromRef.current = enabled && panel ? readBox(panel) : null;\n  }\n\n  useIsoLayoutEffect(() => {\n    const panel = panelRef.current;\n    if (!panel) return;\n    const from = fromRef.current;\n    if (!starting || !from) {\n      panel.style.translate = '';\n      panel.style.scale = '';\n      return;\n    }\n    const to = readBox(panel);\n    if (!to.width || !to.height) return;\n    // Minimized is the one mode whose whole content is a single small control.\n    // Blowing the bubble up to the panel's box would stretch its icon into mush\n    // for the length of the tween, so it only travels — from the middle of the\n    // box it is replacing, since there is no shape left to line its edges up\n    // with.\n    if (mode === 'minimized') {\n      const x = from.x + from.width / 2 - (to.x + to.width / 2);\n      const y = from.y + from.height / 2 - (to.y + to.height / 2);\n      panel.style.translate = `${x}px ${y}px`;\n      return;\n    }\n    panel.style.translate = `${from.x - to.x}px ${from.y - to.y}px`;\n    panel.style.scale = `${from.width / to.width} ${from.height / to.height}`;\n    // `mode` is a dependency so a second change mid-tween re-inverts from\n    // wherever the panel has visually reached instead of leaving it stranded.\n  }, [panelRef, starting, mode]);\n}\n\n/**\n * A PointerSensor that never starts a drag from interactive children. The\n * minimized bubble is the one draggable button: its listeners are only\n * attached while its drag is enabled, so allowing it here is safe.\n */\nclass ChatPanelPointerSensor extends PointerSensor {\n  static activators = [\n    {\n      eventName: 'onPointerDown' as const,\n      handler: ({ nativeEvent: event }: ReactPointerEvent) => {\n        if (event.button !== 0 || event.isPrimary === false) return false;\n        const target = event.target as Element;\n        if (target.closest('[data-chat-panel-trigger]')) return true;\n        return !target.closest(\n          'button, a, input, textarea, select, [data-chat-panel-no-drag]'\n        );\n      }\n    }\n  ];\n}\n\nexport interface ChatPanelRootProps extends ComponentProps<'aside'> {\n  /** Presentation mode of the panel (controlled). */\n  mode?: ChatPanelMode;\n  /**\n   * Initial mode when uncontrolled.\n   * @defaultValue \"docked\"\n   */\n  defaultMode?: ChatPanelMode;\n  /** Called when the mode changes. */\n  onModeChange?: (mode: ChatPanelMode) => void;\n  /**\n   * Which edge the panel docks to; also picks the corner used by the\n   * floating default position and the minimized trigger.\n   * @defaultValue \"right\"\n   */\n  side?: ChatPanelSide;\n  /** Floating window position in viewport pixels (controlled). */\n  position?: ChatPanelPosition | null;\n  /**\n   * Initial floating position when uncontrolled. When omitted the window\n   * starts at the bottom corner on the docked `side`.\n   */\n  defaultPosition?: ChatPanelPosition;\n  /** Called when a drag ends or resizing moves the floating window. */\n  onPositionChange?: (position: ChatPanelPosition) => void;\n  /** Floating window size in pixels (controlled). */\n  size?: ChatPanelSize;\n  /**\n   * Initial floating size when uncontrolled.\n   * @defaultValue { width: 400, height: 560 }\n   */\n  defaultSize?: ChatPanelSize;\n  /** Called when resizing changes the floating window size. */\n  onSizeChange?: (size: ChatPanelSize) => void;\n  /**\n   * Smallest allowed floating size.\n   * @defaultValue { width: 280, height: 320 }\n   */\n  minSize?: ChatPanelSize;\n  /**\n   * Largest allowed floating size, always additionally clamped by the\n   * viewport.\n   * @defaultValue the initial floating size — out of the box the window can\n   * only shrink; pass a larger `maxSize` to let it grow.\n   */\n  maxSize?: ChatPanelSize;\n  /**\n   * Which axes the floating window can be resized on; mirrors the CSS\n   * `resize` property vocabulary.\n   * @defaultValue \"both\"\n   */\n  resize?: ChatPanelResize;\n  /**\n   * Whether the floating window can be dragged by its header. Shadows the\n   * (useless here) native `draggable` attribute.\n   * @defaultValue true\n   */\n  draggable?: boolean;\n  /**\n   * Confines floating-window dragging to an element instead of the\n   * viewport. Accepts the element or a ref to it.\n   */\n  dragBoundary?: ChatPanelDragBoundary;\n  /**\n   * How a mode change animates. `\"minimal\"` eases the new mode in from its own\n   * edge or corner; `\"morph\"` measures the box the panel is leaving and tweens\n   * the new mode out of it, so the panel moves and reshapes into place.\n   * @defaultValue \"minimal\"\n   */\n  transition?: ChatPanelTransition;\n}\n\nexport function ChatPanelRoot({\n  className,\n  children,\n  style,\n  mode: modeProp,\n  defaultMode = 'docked',\n  onModeChange,\n  side = 'right',\n  position: positionProp,\n  defaultPosition,\n  onPositionChange,\n  size: sizeProp,\n  defaultSize,\n  onSizeChange,\n  minSize,\n  maxSize,\n  resize = 'both',\n  draggable = true,\n  dragBoundary,\n  transition = 'minimal',\n  ref,\n  ...props\n}: ChatPanelRootProps) {\n  const panelRef = useRef<HTMLElement | null>(null);\n  const bubbleElementRef = useRef<HTMLElement | null>(null);\n  const draggableId = useId();\n  const bubbleDraggableId = useId();\n\n  const [mode, setModeUnwrapped] = useControlled({\n    controlled: modeProp,\n    default: defaultMode,\n    name: 'ChatPanel',\n    state: 'mode'\n  });\n  const [position, setPositionUnwrapped] =\n    useControlled<ChatPanelPosition | null>({\n      controlled: positionProp,\n      default: defaultPosition ?? null,\n      name: 'ChatPanel',\n      state: 'position'\n    });\n  const [size, setSizeUnwrapped] = useControlled({\n    controlled: sizeProp,\n    default: defaultSize ?? DEFAULT_SIZE,\n    name: 'ChatPanel',\n    state: 'size'\n  });\n\n  const [resizing, setResizing] = useState(false);\n  // Where the minimized bubble was dropped; internal only, survives\n  // minimize/restore cycles because it lives here rather than in the trigger.\n  const [bubblePosition, setBubblePosition] =\n    useState<ChatPanelPosition | null>(null);\n\n  // The size the window first resolved to; the default maxSize, so a custom\n  // defaultSize never contradicts its own max.\n  const initialSizeRef = useRef(sizeProp ?? defaultSize ?? DEFAULT_SIZE);\n\n  const modeFrom = useModeChange(mode);\n  useMorph(panelRef, mode, modeFrom !== null, transition === 'morph');\n\n  const modeRef = useRef(mode);\n  modeRef.current = mode;\n  const positionRef = useRef(position);\n  positionRef.current = position;\n  const sizeRef = useRef(size);\n  sizeRef.current = size;\n  // The mode restored when leaving 'minimized'.\n  const previousModeRef = useRef<Exclude<ChatPanelMode, 'minimized'>>(\n    defaultMode === 'minimized' ? 'docked' : defaultMode\n  );\n\n  const onModeChangeRef = useRef(onModeChange);\n  onModeChangeRef.current = onModeChange;\n  const onPositionChangeRef = useRef(onPositionChange);\n  onPositionChangeRef.current = onPositionChange;\n  const onSizeChangeRef = useRef(onSizeChange);\n  onSizeChangeRef.current = onSizeChange;\n  const minSizeRef = useRef(minSize ?? DEFAULT_MIN_SIZE);\n  minSizeRef.current = minSize ?? DEFAULT_MIN_SIZE;\n  const maxSizeRef = useRef(maxSize ?? initialSizeRef.current);\n  maxSizeRef.current = maxSize ?? initialSizeRef.current;\n  const dragBoundaryRef = useRef(dragBoundary);\n  dragBoundaryRef.current = dragBoundary;\n\n  const setMode = useCallback(\n    (next: ChatPanelMode) => {\n      if (next === modeRef.current) return;\n      if (modeRef.current !== 'minimized') {\n        previousModeRef.current = modeRef.current as Exclude<\n          ChatPanelMode,\n          'minimized'\n        >;\n      }\n      setModeUnwrapped(next);\n      onModeChangeRef.current?.(next);\n    },\n    [setModeUnwrapped]\n  );\n\n  const setPosition = useCallback(\n    (next: ChatPanelPosition) => {\n      setPositionUnwrapped(next);\n      onPositionChangeRef.current?.(next);\n    },\n    [setPositionUnwrapped]\n  );\n\n  const setSize = useCallback(\n    (next: ChatPanelSize) => {\n      setSizeUnwrapped(next);\n      onSizeChangeRef.current?.(next);\n    },\n    [setSizeUnwrapped]\n  );\n\n  const getDragBounds = useCallback((): DragBounds => {\n    const element = resolveDragBoundary(dragBoundaryRef.current);\n    if (element) {\n      const rect = element.getBoundingClientRect();\n      return {\n        left: rect.left,\n        top: rect.top,\n        right: rect.right,\n        bottom: rect.bottom\n      };\n    }\n    return {\n      left: 0,\n      top: 0,\n      right: window.innerWidth,\n      bottom: window.innerHeight\n    };\n  }, []);\n\n  const clampPosition = useCallback(\n    (next: ChatPanelPosition, width: number): ChatPanelPosition => {\n      const bounds = getDragBounds();\n      return {\n        x: Math.round(clamp(next.x, bounds.left, bounds.right - width)),\n        y: Math.round(clamp(next.y, bounds.top, bounds.bottom - HEADER_SAFE_PX))\n      };\n    },\n    [getDragBounds]\n  );\n\n  /* ------------------------------- dragging ------------------------------ */\n\n  // The distance constraint keeps bubble clicks working: a press that moves\n  // less than 4px stays a click and never activates a drag.\n  const sensors = useSensors(\n    useSensor(ChatPanelPointerSensor, {\n      activationConstraint: { distance: 4 }\n    })\n  );\n\n  // Clamps the live drag transform the same way the committed position is\n  // clamped: fully inside the bounds horizontally, header kept reachable\n  // vertically. The bubble is small, so it stays fully on screen instead.\n  const restrictToDragBounds = useCallback<Modifier>(\n    ({ transform, draggingNodeRect, active }) => {\n      if (!draggingNodeRect) return transform;\n      if (active?.id === bubbleDraggableId) {\n        return {\n          ...transform,\n          x: clamp(\n            transform.x,\n            -draggingNodeRect.left,\n            window.innerWidth - draggingNodeRect.left - draggingNodeRect.width\n          ),\n          y: clamp(\n            transform.y,\n            -draggingNodeRect.top,\n            window.innerHeight - draggingNodeRect.top - draggingNodeRect.height\n          )\n        };\n      }\n      const bounds = getDragBounds();\n      return {\n        ...transform,\n        x: clamp(\n          transform.x,\n          bounds.left - draggingNodeRect.left,\n          bounds.right - draggingNodeRect.left - draggingNodeRect.width\n        ),\n        y: clamp(\n          transform.y,\n          bounds.top - draggingNodeRect.top,\n          bounds.bottom - HEADER_SAFE_PX - draggingNodeRect.top\n        )\n      };\n    },\n    [getDragBounds, bubbleDraggableId]\n  );\n  const modifiers = useMemo(\n    () => [restrictToDragBounds],\n    [restrictToDragBounds]\n  );\n\n  const dragOriginRef = useRef<{ x: number; y: number; width: number } | null>(\n    null\n  );\n  const bubbleDragOriginRef = useRef<{\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n  } | null>(null);\n\n  const handleDragStart = useCallback(\n    (event: DragStartEvent) => {\n      if (event.active.id === bubbleDraggableId) {\n        const bubble = bubbleElementRef.current;\n        if (!bubble) return;\n        const rect = bubble.getBoundingClientRect();\n        bubbleDragOriginRef.current = {\n          x: rect.left,\n          y: rect.top,\n          width: rect.width,\n          height: rect.height\n        };\n        return;\n      }\n      const panel = panelRef.current;\n      if (!panel) return;\n      const rect = panel.getBoundingClientRect();\n      dragOriginRef.current = { x: rect.left, y: rect.top, width: rect.width };\n      // Anchor a corner-positioned panel so the drag delta has a fixed origin.\n      if (!positionRef.current) {\n        setPosition({ x: Math.round(rect.left), y: Math.round(rect.top) });\n      }\n    },\n    [setPosition, bubbleDraggableId]\n  );\n\n  // dnd-kit's end delta is the raw translate (modifiers are not applied to\n  // it), so the commit re-clamps with the same bounds as the modifier.\n  const handleDragEnd = useCallback(\n    (event: DragEndEvent) => {\n      if (event.active.id === bubbleDraggableId) {\n        const origin = bubbleDragOriginRef.current;\n        bubbleDragOriginRef.current = null;\n        if (!origin) return;\n        setBubblePosition({\n          x: Math.round(\n            clamp(origin.x + event.delta.x, 0, window.innerWidth - origin.width)\n          ),\n          y: Math.round(\n            clamp(\n              origin.y + event.delta.y,\n              0,\n              window.innerHeight - origin.height\n            )\n          )\n        });\n        return;\n      }\n      const origin = dragOriginRef.current;\n      dragOriginRef.current = null;\n      if (!origin) return;\n      setPosition(\n        clampPosition(\n          { x: origin.x + event.delta.x, y: origin.y + event.delta.y },\n          origin.width\n        )\n      );\n    },\n    [clampPosition, setPosition, bubbleDraggableId]\n  );\n\n  const handleDragCancel = useCallback(() => {\n    dragOriginRef.current = null;\n    bubbleDragOriginRef.current = null;\n  }, []);\n\n  /* ------------------------------- resizing ------------------------------ */\n\n  const resizeStateRef = useRef<{\n    pointerId: number;\n    direction: ResizeDirection;\n    startX: number;\n    startY: number;\n    rect: { left: number; top: number; width: number; height: number };\n  } | null>(null);\n\n  const handleResizeDown = useCallback(\n    (direction: ResizeDirection) =>\n      (event: ReactPointerEvent<HTMLDivElement>) => {\n        if (event.button !== 0) return;\n        const panel = panelRef.current;\n        if (!panel) return;\n        const rect = panel.getBoundingClientRect();\n        resizeStateRef.current = {\n          pointerId: event.pointerId,\n          direction,\n          startX: event.clientX,\n          startY: event.clientY,\n          rect: {\n            left: rect.left,\n            top: rect.top,\n            width: rect.width,\n            height: rect.height\n          }\n        };\n        // Anchor a corner-positioned panel so growing an edge doesn't slide\n        // the opposite one.\n        if (!positionRef.current) {\n          setPosition({ x: Math.round(rect.left), y: Math.round(rect.top) });\n        }\n        event.currentTarget.setPointerCapture(event.pointerId);\n        setResizing(true);\n        event.preventDefault();\n      },\n    [setPosition]\n  );\n\n  const handleResizeMove = useCallback(\n    (event: ReactPointerEvent<HTMLDivElement>) => {\n      const state = resizeStateRef.current;\n      if (!state || event.pointerId !== state.pointerId) return;\n      const { direction, rect } = state;\n      const dx = event.clientX - state.startX;\n      const dy = event.clientY - state.startY;\n      const min = minSizeRef.current;\n      const maxWidth = Math.min(\n        maxSizeRef.current?.width ?? Infinity,\n        window.innerWidth\n      );\n      const maxHeight = Math.min(\n        maxSizeRef.current?.height ?? Infinity,\n        window.innerHeight\n      );\n      let { left, top, width, height } = rect;\n      if (direction.includes('e')) {\n        width = clamp(\n          rect.width + dx,\n          min.width,\n          Math.min(maxWidth, window.innerWidth - rect.left)\n        );\n      }\n      if (direction.includes('s')) {\n        height = clamp(\n          rect.height + dy,\n          min.height,\n          Math.min(maxHeight, window.innerHeight - rect.top)\n        );\n      }\n      if (direction.includes('w')) {\n        const right = rect.left + rect.width;\n        width = clamp(rect.width - dx, min.width, Math.min(maxWidth, right));\n        left = right - width;\n      }\n      if (direction.includes('n')) {\n        const bottom = rect.top + rect.height;\n        height = clamp(\n          rect.height - dy,\n          min.height,\n          Math.min(maxHeight, bottom)\n        );\n        top = bottom - height;\n      }\n      setSize({ width: Math.round(width), height: Math.round(height) });\n      if (direction.includes('w') || direction.includes('n')) {\n        setPosition({ x: Math.round(left), y: Math.round(top) });\n      }\n    },\n    [setPosition, setSize]\n  );\n\n  const handleResizeEnd = useCallback(\n    (event: ReactPointerEvent<HTMLDivElement>) => {\n      const state = resizeStateRef.current;\n      if (!state || event.pointerId !== state.pointerId) return;\n      resizeStateRef.current = null;\n      if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n        event.currentTarget.releasePointerCapture(event.pointerId);\n      }\n      setResizing(false);\n    },\n    []\n  );\n\n  // Keep the floating window reachable when the viewport shrinks.\n  useEffect(() => {\n    if (mode !== 'floating' || typeof window === 'undefined') return;\n    const handleWindowResize = () => {\n      const current = positionRef.current;\n      const panel = panelRef.current;\n      if (!current || !panel) return;\n      const next = clampPosition(current, panel.getBoundingClientRect().width);\n      if (next.x !== current.x || next.y !== current.y) setPosition(next);\n    };\n    window.addEventListener('resize', handleWindowResize);\n    return () => window.removeEventListener('resize', handleWindowResize);\n  }, [mode, clampPosition, setPosition]);\n\n  // Keep a dropped bubble reachable too: unlike the floating window it has\n  // no header to grab, so an off-screen bubble would strand the panel. Runs\n  // on entering minimized as well, covering resizes made in other modes.\n  useEffect(() => {\n    if (mode !== 'minimized' || typeof window === 'undefined') return;\n    const clampBubble = () => {\n      setBubblePosition(current => {\n        if (!current) return current;\n        const rect = bubbleElementRef.current?.getBoundingClientRect();\n        const next = {\n          x: Math.round(\n            clamp(current.x, 0, window.innerWidth - (rect?.width ?? 0))\n          ),\n          y: Math.round(\n            clamp(current.y, 0, window.innerHeight - (rect?.height ?? 0))\n          )\n        };\n        return next.x === current.x && next.y === current.y ? current : next;\n      });\n    };\n    clampBubble();\n    window.addEventListener('resize', clampBubble);\n    return () => window.removeEventListener('resize', clampBubble);\n  }, [mode]);\n\n  const baseContext = useMemo(\n    () => ({\n      mode,\n      side,\n      setMode,\n      minimize: () => setMode('minimized'),\n      restore: () => setMode(previousModeRef.current),\n      toggleFloating: () =>\n        setMode(modeRef.current === 'floating' ? 'docked' : 'floating'),\n      bubbleDraggableId,\n      bubbleElementRef\n    }),\n    [mode, side, setMode, bubbleDraggableId]\n  );\n\n  const floatingStyle =\n    mode === 'floating'\n      ? {\n          width: size.width,\n          height: size.height,\n          ...(position\n            ? {\n                left: position.x,\n                top: position.y,\n                right: 'auto',\n                bottom: 'auto'\n              }\n            : null)\n        }\n      : mode === 'minimized' && bubblePosition\n        ? {\n            // A dropped bubble overrides the CSS corner pinning.\n            left: bubblePosition.x,\n            top: bubblePosition.y,\n            right: 'auto',\n            bottom: 'auto'\n          }\n        : null;\n\n  return (\n    <DndContext\n      sensors={sensors}\n      modifiers={modifiers}\n      onDragStart={handleDragStart}\n      onDragEnd={handleDragEnd}\n      onDragCancel={handleDragCancel}\n      autoScroll={false}\n    >\n      <ChatPanelFrame\n        draggableId={draggableId}\n        baseContext={baseContext}\n        panelRef={panelRef}\n        mode={mode}\n        side={side}\n        draggable={draggable}\n        resizing={resizing}\n        transition={transition}\n        modeFrom={modeFrom}\n        floatingStyle={floatingStyle}\n        className={className}\n        style={style}\n        ref={ref}\n        resizeHandles={\n          mode === 'floating'\n            ? RESIZE_HANDLES[resize].map(direction => (\n                <div\n                  key={direction}\n                  aria-hidden='true'\n                  data-slot='chat-panel-resize-handle'\n                  className={cx(\n                    styles['resize-handle'],\n                    styles[`resize-${direction}`]\n                  )}\n                  onPointerDown={handleResizeDown(direction)}\n                  onPointerMove={handleResizeMove}\n                  onPointerUp={handleResizeEnd}\n                  onPointerCancel={handleResizeEnd}\n                />\n              ))\n            : null\n        }\n        {...props}\n      >\n        {children}\n      </ChatPanelFrame>\n    </DndContext>\n  );\n}\n\nChatPanelRoot.displayName = 'ChatPanel';\n\ninterface ChatPanelFrameProps extends ComponentProps<'aside'> {\n  draggableId: string;\n  baseContext: Omit<ChatPanelContextValue, 'dragHandleRef' | 'dragListeners'>;\n  panelRef: RefObject<HTMLElement | null>;\n  mode: ChatPanelMode;\n  side: ChatPanelSide;\n  draggable: boolean;\n  resizing: boolean;\n  transition: ChatPanelTransition;\n  modeFrom: ChatPanelMode | null;\n  floatingStyle: CSSProperties | null;\n  resizeHandles: ReactNode;\n}\n\n// useDraggable needs the DndContext provider above it, so the frame lives in\n// its own component under the root's DndContext.\nfunction ChatPanelFrame({\n  draggableId,\n  baseContext,\n  panelRef,\n  mode,\n  side,\n  draggable,\n  resizing,\n  transition,\n  modeFrom,\n  floatingStyle,\n  resizeHandles,\n  className,\n  style,\n  children,\n  ref,\n  ...props\n}: ChatPanelFrameProps) {\n  const dragEnabled = mode === 'floating' && draggable;\n  const { setNodeRef, setActivatorNodeRef, listeners, transform, isDragging } =\n    useDraggable({\n      id: draggableId,\n      disabled: !dragEnabled\n    });\n\n  const contextValue = useMemo<ChatPanelContextValue>(\n    () => ({\n      ...baseContext,\n      dragHandleRef: setActivatorNodeRef,\n      dragListeners: listeners\n    }),\n    [baseContext, setActivatorNodeRef, listeners]\n  );\n\n  return (\n    <aside\n      ref={node => {\n        panelRef.current = node;\n        setNodeRef(node);\n        if (typeof ref === 'function') ref(node);\n        else if (ref) ref.current = node;\n      }}\n      className={cx(styles.root, className)}\n      data-slot='chat-panel'\n      data-mode={mode}\n      data-side={side}\n      data-draggable={dragEnabled || undefined}\n      data-dragging={isDragging || undefined}\n      data-resizing={resizing || undefined}\n      data-transition={transition}\n      data-mode-starting={modeFrom ? '' : undefined}\n      data-mode-from={modeFrom ?? undefined}\n      style={{\n        ...floatingStyle,\n        // The committed position only updates when the drag ends; the live\n        // movement is the dnd-kit transform.\n        ...(transform\n          ? { transform: `translate3d(${transform.x}px, ${transform.y}px, 0)` }\n          : null),\n        ...style\n      }}\n      {...props}\n    >\n      <ChatPanelContext.Provider value={contextValue}>\n        {children}\n        {resizeHandles}\n      </ChatPanelContext.Provider>\n    </aside>\n  );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAiDA;AACA;AAEA;AACA;AAUA;AACE;AACA;AACA;AACA;;AAGF;;AAEA;AASA;AAGE;AAAe;AACf;AACF;AAEA;;;AAGG;AACH;;AAEE;;AAGE;;AACA;AACA;AACF;AAEA;AACF;AASA;AACE;AACA;AACF;AAEA;;;;;;AAMG;AACH;AAME;AACA;AAEA;AACE;AACA;AACA;;;AAIA;AACA;;AACA;AACA;AACE;AACA;;;AAGF;;;;;;;;AAOA;;;;;;;;;;;AAWJ;AAEA;;;;AAIG;AACH;;AAEI;AACE;;;AAEuD;AACrD;AACA;AAAiD;AACjD;;AAIH;;;AA4EW;AAuBd;AACA;AACA;AACA;AAEA;AACE;AACA;AACA;AACA;AACD;AACD;AAEI;;AAEA;AACA;AACD;AACH;AACE;;AAEA;AACA;AACD;;;;;;;;AAYD;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AAEA;AAEI;;AACA;AACE;;;AAMF;AACF;AAIF;;AAGI;AACF;AAIF;;AAGI;AACF;AAIF;;;AAGI;;;;;;;;;AASA;AACA;;;;;;AAQA;;;;;AAKF;;;;AAQF;AAEI;AACD;;;;AAMH;AAEI;AAAuB;AACvB;;AAEI;;;;;AAaJ;;AAEE;;;;AAYJ;AAGF;AAKA;AAGA;AAOA;;AAGM;AACA;;AACA;;;;;;;;;AASF;AACA;;AACA;;;AAGA;;;AAGF;;;AAMF;;AAGM;AACA;AACA;;AACA;;;AAWC;;;AAGH;AACA;AACA;;AACA;;AAUJ;AACE;AACA;;;AAKF;AAQA;AAGM;;AACA;AACA;;AACA;;;;;;AAME;;;;;AAKC;;;;AAIH;;;;;;AAMF;AAIJ;AAEI;;;AAEA;;;AAGA;AACA;AAIA;;AAKA;AACE;;AAMF;AACE;;AAMF;;;AAGE;;AAEF;;;AAOE;;;AAGF;;;AAGF;AAIF;AAEI;;;AAEA;;;;;;;;AAWF;;;AAEE;AACA;AACA;;AACA;AACA;;AACF;AACA;;;;;;;AAQA;;;;AAGI;AAAc;;AAEd;;;;;AASF;AACF;AACA;AACA;;AAEF;AAEA;;;;AAKI;;AAEA;;;;AAQJ;AAEI;;;AAGI;AACE;;;AAGI;AACA;AACD;;AAEN;AACH;AACE;;;;AAII;AACA;AACD;;AAGT;;;AAgDF;AAEA;AAgBA;AACA;AACA;AAkBE;AACA;AAEI;;AAED;AAEH;AAEI;AACA;AACA;;AAKJ;AAGM;;;;AAGK;AAAS;;AAad;;;AAGA;AACE;;AAEF;AACD;AASP;;"}