{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "Anchored non-modal dialog: native top layer + light dismiss ([popover=auto] with a declarative popoverTarget invoker), CSS anchor positioning with position-try-fallbacks, @starting-style enter/exit. Names itself through registered Title/Description parts or a `label`; physical sides stay physical in RTL and logical sides are asked for by name. No portals, no JS positioning.",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui Popover — the reference platform-native primitive\n * (docs/HEIDI-UI.md § 2, § 8 slice 2). All behavior is the browser's: top layer +\n * light dismiss come from [popover=\"auto\"] (with the trigger wired\n * declaratively via popoverTarget), positioning is\n * CSS anchor positioning (per-instance anchor-name from useId), enter/exit\n * animation is @starting-style in popover.theme.css. No portals: content stays in\n * the authored DOM, so scoped theming and the cascade behave normally.\n * Positioning and dismiss are zero-JS; a tiny toggle listener mirrors native\n * state into React, while an effect keeps controlled React state synchronized\n * back to the native top layer.\n *\n * Accessible name (required): the content is a non-modal `role=\"dialog\"`\n * (APG Dialog pattern, which Radix Popover.Content also implements) — render\n * <Popover.Title>, pass `label`, or supply an explicit aria-label /\n * aria-labelledby. A dev-only warning fires when an opened popover has none.\n *\n * Controlled/uncontrolled: `open` + `onOpenChange`, or `defaultOpen`.\n * Composition (Base-shaped): className / style / render / ref via\n * renderHeidiElement. Structural CSS: popover.base.css. Theme: popover.theme.css.\n *\n * RSC rule: named exports (PopoverRoot, …) are canonical from Server Components;\n * the `Popover.X` namespace object is client-only sugar.\n */\n\nimport {\n  type ComponentPropsWithoutRef,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type CSSProperties,\n  type ReactNode,\n  type Ref,\n  type SyntheticEvent,\n  type ToggleEvent\n} from \"react\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { synchronizeNativePopoverOpen } from \"../_internal/native-popover-sync\";\nimport { safeId } from \"../_internal/safe-id\";\nimport { useHeidiLayoutEffect } from \"../_internal/use-layout-effect\";\nimport { type PopoverAlign, type PopoverSide } from \"./popover.anatomy.generated\";\nimport { POPOVER_CLASSES } from \"./popover.classes.generated\";\n\nexport type { PopoverAlign, PopoverSide };\n\ntype PopoverContextValue = {\n  anchorName: string;\n  contentId: string;\n  descriptionIds: string[];\n  open: boolean;\n  registerDescription: (id: string) => () => void;\n  registerTitle: (id: string) => () => void;\n  setOpen: (open: boolean) => void;\n  titleIds: string[];\n};\n\nconst PopoverContext = createContext<PopoverContextValue | null>(null);\n\n/**\n * ponytail: byte-identical to dialog.tsx / hover-card.tsx. Kept local because\n * `_internal/` is shared surface pinned by contract 20c (its exact file set is\n * asserted), so lifting it is a registry change, not a primitive change. The\n * rejected alternative — importing Dialog's copy across primitives — would\n * couple Popover's registry closure to Dialog's. Extraction into\n * `_internal/registered-ids.ts` is the right next slice for all three.\n */\nfunction addRegisteredId(\n  setter: (update: (ids: string[]) => string[]) => void,\n  id: string\n): () => void {\n  setter((ids) => (ids.includes(id) ? ids : [...ids, id]));\n  return () => setter((ids) => ids.filter((candidate) => candidate !== id));\n}\n\n/**\n * `data-open` / `data-closed` / `data-state` — the styling contract Base UI and\n * Radix consumers write against. `:popover-open` cannot serve as the public\n * hook: it is not addressable from the trigger and does not survive `render`\n * onto a non-popover host.\n */\nfunction popoverStateAttributes(\n  open: boolean\n): Record<string, string | true | undefined> {\n  return open\n    ? { \"data-closed\": undefined, \"data-open\": true, \"data-state\": \"open\" }\n    : { \"data-closed\": true, \"data-open\": undefined, \"data-state\": \"closed\" };\n}\n\ntype PopoverPhysicalSide = Exclude<PopoverSide, \"inline-end\" | \"inline-start\">;\n\n/**\n * ponytail: `side` is PHYSICAL in this library, matching Radix and the prop's\n * own name — `left` must stay the physical left under `dir=\"rtl\"`, and only\n * `align` mirrors. The rejected alternative was leaving the CSS on\n * `position-area: inline-start/inline-end`, which silently mirrored a\n * physically-named side (CSS Anchor Positioning L1 §position-area resolves the\n * logical keywords against writing mode + direction). Consumers who WANT the\n * mirroring ask for it by name via `inline-start` / `inline-end`, exactly as\n * HoverCard and Menu already do. Duplicated from hover-card.tsx for the same\n * contract-20c reason as addRegisteredId; a shared `_internal/physical-side.ts`\n * would collapse four copies.\n */\nfunction resolvePopoverPhysicalSide(\n  side: PopoverSide,\n  direction: \"ltr\" | \"rtl\"\n): PopoverPhysicalSide {\n  if (side === \"inline-start\") {\n    return direction === \"rtl\" ? \"right\" : \"left\";\n  }\n  if (side === \"inline-end\") {\n    return direction === \"rtl\" ? \"left\" : \"right\";\n  }\n  return side;\n}\n\nfunction popoverHasAccessibleName(content: HTMLDivElement): boolean {\n  if (content.getAttribute(\"aria-label\")?.trim()) {\n    return true;\n  }\n  const labelledBy = content.getAttribute(\"aria-labelledby\")?.trim();\n  if (!labelledBy) {\n    return false;\n  }\n  return labelledBy.split(/\\s+/).some((id) => {\n    const element = document.getElementById(id);\n    return element != null && !!element.textContent?.trim();\n  });\n}\n\nfunction usePopoverContext(part: string): PopoverContextValue {\n  const context = useContext(PopoverContext);\n  if (!context) {\n    throw new Error(`Popover.${part} must be rendered inside Popover.Root.`);\n  }\n  return context;\n}\n\nexport type PopoverRootProps = {\n  children?: ReactNode;\n  /** Uncontrolled initial open state. */\n  defaultOpen?: boolean;\n  /** Called when open state changes (native toggle, Escape, light dismiss). */\n  onOpenChange?: (open: boolean) => void;\n  /** Controlled open — wins over internal state when provided. */\n  open?: boolean;\n};\n\nexport function PopoverRoot({\n  children,\n  defaultOpen = false,\n  onOpenChange,\n  open: openProp\n}: PopoverRootProps) {\n  const id = useId();\n  const [descriptionIds, setDescriptionIds] = useState<string[]>([]);\n  const [titleIds, setTitleIds] = useState<string[]>([]);\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const controlled = openProp !== undefined;\n  const open = openProp ?? internalOpen;\n  const openRef = useRef(open);\n  openRef.current = open;\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (next === openRef.current) {\n        return;\n      }\n      if (!controlled) {\n        openRef.current = next;\n        setInternalOpen(next);\n      }\n      onOpenChange?.(next);\n    },\n    [controlled, onOpenChange]\n  );\n  const registerDescription = useCallback(\n    (descriptionId: string) => addRegisteredId(setDescriptionIds, descriptionId),\n    []\n  );\n  const registerTitle = useCallback(\n    (titleId: string) => addRegisteredId(setTitleIds, titleId),\n    []\n  );\n  const value = useMemo(() => {\n    const safe = safeId(id);\n    return {\n      anchorName: `--hui-popover-anchor-${safe}`,\n      contentId: `hui-popover-${safe}`,\n      descriptionIds,\n      open,\n      registerDescription,\n      registerTitle,\n      setOpen,\n      titleIds\n    };\n  }, [\n    descriptionIds,\n    id,\n    open,\n    registerDescription,\n    registerTitle,\n    setOpen,\n    titleIds\n  ]);\n  return <PopoverContext value={value}>{children}</PopoverContext>;\n}\n\nexport type PopoverTriggerState = { open: boolean };\n\ntype PopoverTriggerNativeProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  | \"aria-controls\"\n  | \"aria-expanded\"\n  | \"aria-haspopup\"\n  | \"children\"\n  | \"className\"\n  | \"popoverTarget\"\n  | \"ref\"\n  | \"style\"\n  | \"type\"\n>;\n\nexport type PopoverTriggerProps = HeidiIntrinsicHostProps<\n  PopoverTriggerState,\n  \"button\"\n> &\n  PopoverTriggerNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function PopoverTrigger({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: PopoverTriggerProps) {\n  const { anchorName, contentId, open } = usePopoverContext(\"Trigger\");\n  return renderHeidiElement({\n    className: POPOVER_CLASSES.trigger,\n    dataPart: \"popover-trigger\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      \"aria-controls\": contentId,\n      \"aria-expanded\": open,\n      // ARIA 1.2 aria-haspopup: the trigger advertises the TYPE of popup it\n      // controls. The content is role=dialog, so the trigger says so — the\n      // same contract HoverCard.Trigger and Dialog.Trigger already ship.\n      \"aria-haspopup\": \"dialog\",\n      children,\n      ...popoverStateAttributes(open),\n      popoverTarget: contentId,\n      ref,\n      type: \"button\"\n    },\n    renderProps: { className, render, style },\n    state: { open },\n    structuralStyle: { anchorName } as CSSProperties\n  });\n}\n\nexport type PopoverContentState = {\n  align: PopoverAlign;\n  open: boolean;\n  side: PopoverSide;\n};\n\ntype PopoverContentNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  | \"children\"\n  | \"className\"\n  | \"id\"\n  | \"onBeforeToggle\"\n  | \"onToggle\"\n  | \"popover\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n>;\n\nexport type PopoverContentProps = HeidiIntrinsicHostProps<\n  PopoverContentState,\n  \"div\"\n> &\n  PopoverContentNativeProps & {\n    align?: PopoverAlign;\n    /** Cross-axis offset in CSS pixels. */\n    alignOffset?: number;\n    children?: ReactNode;\n    /**\n     * Accessible name for a popover with no visible <Popover.Title> — the\n     * APG Dialog pattern requires one or the other.\n     */\n    label?: string;\n    onBeforeToggle?: ComponentPropsWithoutRef<\"div\">[\"onBeforeToggle\"];\n    onToggle?: ComponentPropsWithoutRef<\"div\">[\"onToggle\"];\n    ref?: Ref<HTMLDivElement>;\n    side?: PopoverSide;\n    /** Main-axis gap in CSS pixels. */\n    sideOffset?: number;\n  };\n\nexport function PopoverContent({\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  align = \"center\",\n  alignOffset = 0,\n  children,\n  className,\n  label,\n  onBeforeToggle: onBeforeToggleProp,\n  onToggle: onToggleProp,\n  ref,\n  render,\n  side = \"bottom\",\n  sideOffset = 4,\n  style,\n  ...nativeProps\n}: PopoverContentProps) {\n  const context = usePopoverContext(\"Content\");\n  const { anchorName, contentId, open, setOpen } = context;\n  const contentRef = useRef<HTMLDivElement | null>(null);\n  const [direction, setDirection] = useState<\"ltr\" | \"rtl\">(\"ltr\");\n  const internalNativeTransitionRef = useRef(false);\n  const lastBeforeToggleRef = useRef<{\n    internal: boolean;\n    open: boolean;\n  } | null>(null);\n  const [nativeTransitionVersion, setNativeTransitionVersion] = useState(0);\n\n  const synchronizeNativeOpen = useCallback(\n    (element: HTMLDivElement, next: boolean) => {\n      synchronizeNativePopoverOpen(element, next, {\n        transitionRef: internalNativeTransitionRef\n      });\n    },\n    []\n  );\n\n  // Keep controlled/default React state and the browser's native top-layer\n  // state in lockstep. Declarative popoverTarget activation still owns normal\n  // trigger interactions; this path covers external state changes such as a\n  // page selection or publish action closing its parent popover.\n  useEffect(() => {\n    const element = contentRef.current;\n    if (!element || typeof element.showPopover !== \"function\") {\n      return;\n    }\n    synchronizeNativeOpen(element, open);\n  }, [nativeTransitionVersion, open, synchronizeNativeOpen]);\n\n  // Physical side resolution needs the COMPUTED direction, which only exists\n  // on the client. The content stays in the authored DOM (no portals), so it\n  // inherits `direction` from the consumer's dir=\"rtl\" subtree directly.\n  useHeidiLayoutEffect(() => {\n    const element = contentRef.current;\n    if (!element) {\n      return;\n    }\n    const next =\n      getComputedStyle(element).direction === \"rtl\" ? \"rtl\" : \"ltr\";\n    setDirection((current) => (current === next ? current : next));\n  }, [open]);\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\" || !open) {\n      return;\n    }\n    const timeout = window.setTimeout(() => {\n      const element = contentRef.current;\n      if (element && !popoverHasAccessibleName(element)) {\n        // oxlint-disable-next-line no-console -- an unnamed dialog is a WCAG failure.\n        console.warn(\n          \"heidi-ui Popover: missing accessible name — provide <Popover.Title>, label, aria-label, or aria-labelledby.\"\n        );\n      }\n    }, 0);\n    return () => window.clearTimeout(timeout);\n  }, [open]);\n\n  const onBeforeToggle = composeHeidiEventHandlers(\n    onBeforeToggleProp,\n    (event: ToggleEvent<HTMLDivElement>) => {\n    if (event.target !== event.currentTarget) {\n      return;\n    }\n    const nativeToggle = event.nativeEvent as Event & {\n      newState?: string;\n    };\n    lastBeforeToggleRef.current = {\n      internal: internalNativeTransitionRef.current,\n      open: nativeToggle.newState === \"open\"\n    };\n    }\n  );\n\n  const onToggle = composeHeidiEventHandlers(\n    onToggleProp,\n    (event: SyntheticEvent<HTMLDivElement>) => {\n    if (event.target !== event.currentTarget) {\n      return;\n    }\n    const nativeToggle = event.nativeEvent as unknown as { newState?: string };\n    const nativeOpen = nativeToggle.newState === \"open\";\n    const transition = lastBeforeToggleRef.current;\n    lastBeforeToggleRef.current = null;\n    const internallySynchronized =\n      transition?.internal === true && transition.open === nativeOpen;\n    if (!internallySynchronized) {\n      setOpen(nativeOpen);\n      // A controlled owner can preserve an unchanged prop to reject the\n      // browser request. Re-run synchronization even when React state did not\n      // otherwise change.\n      setNativeTransitionVersion((version) => version + 1);\n    }\n    }\n  );\n  const computedLabel = ariaLabel ?? label;\n  const computedLabelledBy =\n    ariaLabelledBy ??\n    (computedLabel ? undefined : context.titleIds.join(\" \") || undefined);\n  const computedDescribedBy =\n    ariaDescribedBy ?? (context.descriptionIds.join(\" \") || undefined);\n  return renderHeidiElement({\n    className: POPOVER_CLASSES.content,\n    dataPart: \"popover-content\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      \"aria-describedby\": computedDescribedBy,\n      \"aria-label\": computedLabel,\n      \"aria-labelledby\": computedLabelledBy,\n      children,\n      \"data-align\": align,\n      \"data-position-side\": resolvePopoverPhysicalSide(side, direction),\n      \"data-side\": side,\n      ...popoverStateAttributes(open),\n      id: contentId,\n      onBeforeToggle,\n      onToggle,\n      popover: \"auto\",\n      ref: mergeHeidiRefs(contentRef, ref),\n      // APG Dialog pattern: \"The element that serves as the dialog container\n      // has a role of dialog.\" Non-modal — focus is deliberately not trapped.\n      role: \"dialog\"\n    },\n    renderProps: { className, render, style },\n    state: { align, open, side },\n    structuralStyle: {\n      \"--_hui-popover-align-offset\": `${alignOffset}px`,\n      \"--_hui-popover-side-offset\": `${Math.max(0, sideOffset)}px`,\n      positionAnchor: anchorName\n    } as CSSProperties\n  });\n}\n\ntype PopoverEmptyState = Record<string, never>;\n\ntype PopoverNativeHostProps<Tag extends keyof HTMLElementTagNameMap> = Omit<\n  ComponentPropsWithoutRef<Tag>,\n  \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\nexport type PopoverTitleProps = HeidiIntrinsicHostProps<\n  PopoverEmptyState,\n  \"h2\"\n> &\n  Omit<PopoverNativeHostProps<\"h2\">, \"id\"> & {\n    children?: ReactNode;\n    id?: string;\n    ref?: Ref<HTMLHeadingElement>;\n  };\n\nexport function PopoverTitle({\n  children,\n  className,\n  id,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: PopoverTitleProps) {\n  const generatedId = safeId(useId());\n  const { registerTitle } = usePopoverContext(\"Title\");\n  const titleId = id ?? `hui-popover-title-${generatedId}`;\n  useHeidiLayoutEffect(() => registerTitle(titleId), [registerTitle, titleId]);\n  return renderHeidiElement({\n    className: POPOVER_CLASSES.title,\n    dataPart: \"popover-title\",\n    element: \"h2\",\n    props: { ...nativeProps, children, id: titleId, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type PopoverDescriptionProps = HeidiIntrinsicHostProps<\n  PopoverEmptyState,\n  \"p\"\n> &\n  Omit<PopoverNativeHostProps<\"p\">, \"id\"> & {\n    children?: ReactNode;\n    id?: string;\n    ref?: Ref<HTMLParagraphElement>;\n  };\n\nexport function PopoverDescription({\n  children,\n  className,\n  id,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: PopoverDescriptionProps) {\n  const generatedId = safeId(useId());\n  const { registerDescription } = usePopoverContext(\"Description\");\n  const descriptionId = id ?? `hui-popover-description-${generatedId}`;\n  useHeidiLayoutEffect(\n    () => registerDescription(descriptionId),\n    [descriptionId, registerDescription]\n  );\n  return renderHeidiElement({\n    className: POPOVER_CLASSES.description,\n    dataPart: \"popover-description\",\n    element: \"p\",\n    props: { ...nativeProps, children, id: descriptionId, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\n/**\n * Namespace sugar for client components (`<Popover.Root>`). From a Server\n * Component use the named exports (PopoverRoot, …) instead — dotting into this\n * object from RSC throws \"Element type is invalid\".\n */\nexport const Popover = {\n  Content: PopoverContent,\n  Description: PopoverDescription,\n  Root: PopoverRoot,\n  Title: PopoverTitle,\n  Trigger: PopoverTrigger\n} as const;\n",
      "path": "packages/heidi-ui/src/popover/popover.tsx",
      "target": "components/ui/heidi/popover/popover.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui popover — STRUCTURAL CSS only (platform behavior).\n * No --heidi-* tokens. No visual opinion. Required for CSS anchor\n * positioning + native [popover] to work correctly.\n *\n * Pair with popover.theme.css for the Heidi look, or style via\n * className / [data-hui-part] yourself.\n */\n\n@layer heidi-ui-base {\n  /* Gated on data-side so the offset custom properties and the anchored\n     geometry only apply once the component has declared a placement — the\n     same shape hover-card.base.css uses. */\n  .hui-popover-content:is(\n    [data-side=\"bottom\"],\n    [data-side=\"inline-end\"],\n    [data-side=\"inline-start\"],\n    [data-side=\"left\"],\n    [data-side=\"right\"],\n    [data-side=\"top\"]\n  ) {\n    --_hui-popover-align-offset: 0px;\n    --_hui-popover-side-offset: 4px;\n    --_hui-popover-viewport-gutter: 1rem;\n\n    box-sizing: border-box;\n    /* undo the UA's centered-dialog styles so anchor positioning takes over */\n    inset: auto;\n    /* main-axis gap between anchor and panel; sideOffset writes this var */\n    margin: var(--_hui-popover-side-offset);\n    max-block-size: calc(\n      100dvb - var(--_hui-popover-viewport-gutter) - 2 *\n        var(--_hui-popover-side-offset)\n    );\n    max-inline-size: calc(\n      100dvi - var(--_hui-popover-viewport-gutter) - 2 *\n        var(--_hui-popover-side-offset)\n    );\n    overflow: auto;\n    overscroll-behavior: contain;\n    position: fixed;\n    position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n    scrollbar-gutter: stable;\n    /* longhands on purpose: allow-discrete inside the transition shorthand\n       still trips conservative CSS parsers */\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n\n  /* Author layout classes (for example display:grid) must never override the\n     UA's closed-popover display:none. */\n  .hui-popover-content:not(:popover-open) {\n    display: none !important;\n  }\n\n  /* Cross-axis nudge (alignOffset) runs along the side's perpendicular axis. */\n  .hui-popover-content[data-position-side=\"top\"],\n  .hui-popover-content[data-position-side=\"bottom\"] {\n    translate: var(--_hui-popover-align-offset) 0;\n  }\n\n  .hui-popover-content[data-position-side=\"left\"],\n  .hui-popover-content[data-position-side=\"right\"] {\n    translate: 0 var(--_hui-popover-align-offset);\n  }\n\n  /* side × align → position-area. Spelled out per combination on purpose:\n     this table IS the data-attribute styling contract — greppable by humans\n     and agents, and the seed for the anatomy manifest.\n\n     Keyed on data-position-side (the direction-resolved PHYSICAL side), not\n     data-side: `left`/`right` here are the physical keywords, so a consumer\n     asking for side=\"left\" gets the physical left in both LTR and RTL. The\n     inline axis mirrors only when it is asked for by name\n     (side=\"inline-start\" / \"inline-end\"). `align` stays logical via\n     span-inline-*, which is correct — alignment SHOULD mirror in RTL. */\n  .hui-popover-content[data-position-side=\"top\"][data-align=\"start\"] {\n    position-area: block-start span-inline-end;\n  }\n  .hui-popover-content[data-position-side=\"top\"][data-align=\"center\"] {\n    position-area: block-start;\n  }\n  .hui-popover-content[data-position-side=\"top\"][data-align=\"end\"] {\n    position-area: block-start span-inline-start;\n  }\n  .hui-popover-content[data-position-side=\"bottom\"][data-align=\"start\"] {\n    position-area: block-end span-inline-end;\n  }\n  .hui-popover-content[data-position-side=\"bottom\"][data-align=\"center\"] {\n    position-area: block-end;\n  }\n  .hui-popover-content[data-position-side=\"bottom\"][data-align=\"end\"] {\n    position-area: block-end span-inline-start;\n  }\n  .hui-popover-content[data-position-side=\"left\"][data-align=\"start\"] {\n    position-area: left span-block-end;\n  }\n  .hui-popover-content[data-position-side=\"left\"][data-align=\"center\"] {\n    position-area: left;\n  }\n  .hui-popover-content[data-position-side=\"left\"][data-align=\"end\"] {\n    position-area: left span-block-start;\n  }\n  .hui-popover-content[data-position-side=\"right\"][data-align=\"start\"] {\n    position-area: right span-block-end;\n  }\n  .hui-popover-content[data-position-side=\"right\"][data-align=\"center\"] {\n    position-area: right;\n  }\n  .hui-popover-content[data-position-side=\"right\"][data-align=\"end\"] {\n    position-area: right span-block-start;\n  }\n}\n",
      "path": "packages/heidi-ui/src/popover/popover.base.css",
      "target": "components/ui/heidi/popover/popover.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui popover — VISUAL theme (opt-in). Consumes --hui-* semantic theme vars (wired via adapters/heidi.css).\n * Import with popover.base.css (via popover.css or styles.css).\n */\n\n@layer heidi-ui {\n  .hui-popover-trigger {\n    background: var(--hui-color-bg-elevated);\n    border: var(--hui-border-width) var(--hui-border-style) var(--hui-color-border-default);\n    border-radius: var(--hui-radius-md);\n    color: var(--hui-color-fg-default);\n    cursor: pointer;\n    font: inherit;\n    padding: var(--hui-space-1-5) var(--hui-space-3);\n  }\n\n  .hui-popover-trigger:focus-visible {\n    outline: var(--hui-focus-ring-width) solid var(--hui-color-focus-ring);\n    outline-offset: var(--hui-focus-ring-offset);\n  }\n\n  /* No `margin` here on purpose: the trigger↔panel gap is the structural\n     sideOffset (--_hui-popover-side-offset, default 4px = space-1) in\n     popover.base.css. A theme margin would sit in a later layer and make the\n     sideOffset prop silently inert. */\n  .hui-popover-content {\n    background: var(--hui-color-bg-raised);\n    border: 0;\n    border-radius: var(--hui-radius-2xl);\n    box-shadow: var(--hui-shadow-surface-lg);\n    color: var(--hui-color-fg-default);\n    max-width: 24rem;\n    opacity: 0;\n    padding: var(--hui-space-3);\n    transform: scale(0.97);\n    transition-duration: var(--hui-duration-fast);\n    transition-property: display, opacity, overlay, transform;\n    transition-timing-function: var(--hui-ease-out);\n  }\n\n  .hui-popover-content[data-state=\"open\"] {\n    opacity: 1;\n    transform: none;\n  }\n\n  .hui-popover-content[data-state=\"closed\"] {\n    opacity: 0;\n    transform: scale(0.97);\n  }\n\n  @starting-style {\n    .hui-popover-content:popover-open[data-state=\"open\"] {\n      opacity: 0;\n      transform: scale(0.97);\n    }\n  }\n\n  .hui-popover-title {\n    font-size: var(--hui-text-heading-3-size);\n    font-weight: var(--hui-font-weight-medium);\n    margin: 0 0 var(--hui-space-1);\n  }\n\n  .hui-popover-description {\n    color: var(--hui-color-fg-muted);\n    font-size: var(--hui-text-body-size);\n    margin: 0 0 var(--hui-space-2);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .hui-popover-content {\n      transition-duration: 0s;\n    }\n  }\n\n  @media (forced-colors: active) {\n    .hui-popover-trigger {\n      border-color: CanvasText;\n    }\n\n    .hui-popover-content {\n      border: var(--hui-border-width) solid CanvasText;\n    }\n\n    .hui-popover-trigger:focus-visible {\n      outline-color: Highlight;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/popover/popover.theme.css",
      "target": "components/ui/heidi/popover/popover.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui popover — aggregator (base + Heidi adapter + theme).\n * Headless: import popover.base.css only.\n * Themed: import this file (or heidi-ui/styles.css).\n */\n\n@import \"./popover.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./popover.theme.css\";\n",
      "path": "packages/heidi-ui/src/popover/popover.css",
      "target": "components/ui/heidi/popover/popover.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/popover/popover.base.css + packages/heidi-ui/src/popover/popover.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const POPOVER_CLASSES = {\n  content: \"hui-popover-content\",\n  description: \"hui-popover-description\",\n  title: \"hui-popover-title\",\n  trigger: \"hui-popover-trigger\",\n} as const;\n",
      "path": "packages/heidi-ui/src/popover/popover.classes.generated.ts",
      "target": "components/ui/heidi/popover/popover.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from popover.anatomy.json + popover.base.css + popover.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type PopoverAlign = \"center\" | \"end\" | \"start\";\nexport type PopoverPositionSide = \"bottom\" | \"left\" | \"right\" | \"top\";\nexport type PopoverSide = \"bottom\" | \"inline-end\" | \"inline-start\" | \"left\" | \"right\" | \"top\";\nexport type PopoverState = \"closed\" | \"open\";\n\nexport const POPOVER_ANATOMY = {\n  \"component\": \"popover\",\n  \"description\": \"Anchored non-modal dialog: native top layer + light dismiss ([popover=auto] with a declarative popoverTarget invoker), CSS anchor positioning with position-try-fallbacks, @starting-style enter/exit. Names itself through registered Title/Description parts or a `label`; physical sides stay physical in RTL and logical sides are asked for by name. No portals, no JS positioning.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-describedby\",\n          \"aria-label\",\n          \"aria-labelledby\"\n        ],\n        \"role\": \"dialog\"\n      },\n      \"class\": \"hui-popover-content\",\n      \"css\": {\n        \"structural\": [\n          \"box-sizing\",\n          \"inset\",\n          \"margin\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"overscroll-behavior\",\n          \"position\",\n          \"position-area\",\n          \"position-try-fallbacks\",\n          \"scrollbar-gutter\",\n          \"transition-behavior\",\n          \"transition-property\",\n          \"translate\"\n        ]\n      },\n      \"dataPart\": \"popover-content\",\n      \"description\": \"The [popover] panel, anchored to its trigger; a non-modal role=dialog (APG Dialog pattern) that carries the side/align/open styling contract and animates via @starting-style.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"align\",\n        \"alignOffset\",\n        \"className\",\n        \"label\",\n        \"onBeforeToggle\",\n        \"onToggle\",\n        \"ref\",\n        \"render\",\n        \"side\",\n        \"sideOffset\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":popover-open\"\n      ],\n      \"states\": {\n        \"data-align\": [\n          \"center\",\n          \"end\",\n          \"start\"\n        ],\n        \"data-position-side\": [\n          \"bottom\",\n          \"left\",\n          \"right\",\n          \"top\"\n        ],\n        \"data-side\": [\n          \"bottom\",\n          \"inline-end\",\n          \"inline-start\",\n          \"left\",\n          \"right\",\n          \"top\"\n        ],\n        \"data-state\": [\n          \"closed\",\n          \"open\"\n        ]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-popover-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"popover-description\",\n      \"description\": \"Optional supporting text registered into the panel's aria-describedby without dangling references.\",\n      \"element\": \"p\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"id\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"title\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-popover-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"popover-title\",\n      \"description\": \"Heading registered as the panel's accessible name; the APG Dialog pattern requires this or an explicit label.\",\n      \"element\": \"h2\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"id\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"trigger\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-controls\",\n          \"aria-expanded\",\n          \"aria-haspopup\"\n        ],\n        \"role\": null\n      },\n      \"class\": \"hui-popover-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"popover-trigger\",\n      \"description\": \"Invoker button; opens the content declaratively via popoverTarget, advertises the dialog popup, and carries the per-instance anchor-name.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-state\": [\n          \"closed\",\n          \"open\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultOpen\",\n    \"onOpenChange\",\n    \"open\"\n  ],\n  \"tokens\": [\n    \"--hui-border-style\",\n    \"--hui-border-width\",\n    \"--hui-color-bg-elevated\",\n    \"--hui-color-bg-raised\",\n    \"--hui-color-border-default\",\n    \"--hui-color-fg-default\",\n    \"--hui-color-fg-muted\",\n    \"--hui-color-focus-ring\",\n    \"--hui-duration-fast\",\n    \"--hui-ease-out\",\n    \"--hui-focus-ring-offset\",\n    \"--hui-focus-ring-width\",\n    \"--hui-font-weight-medium\",\n    \"--hui-radius-2xl\",\n    \"--hui-radius-md\",\n    \"--hui-shadow-surface-lg\",\n    \"--hui-space-1\",\n    \"--hui-space-1-5\",\n    \"--hui-space-2\",\n    \"--hui-space-3\",\n    \"--hui-text-body-size\",\n    \"--hui-text-heading-3-size\"\n  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/popover/popover.anatomy.generated.ts",
      "target": "components/ui/heidi/popover/popover.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"popover\",\n  \"description\": \"Anchored non-modal dialog: native top layer + light dismiss ([popover=auto] with a declarative popoverTarget invoker), CSS anchor positioning with position-try-fallbacks, @starting-style enter/exit. Names itself through registered Title/Description parts or a `label`; physical sides stay physical in RTL and logical sides are asked for by name. No portals, no JS positioning.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\"aria-describedby\", \"aria-label\", \"aria-labelledby\"],\n        \"role\": \"dialog\"\n      },\n      \"class\": \"hui-popover-content\",\n      \"css\": {\n        \"structural\": [\n          \"box-sizing\",\n          \"inset\",\n          \"margin\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"overscroll-behavior\",\n          \"position\",\n          \"position-area\",\n          \"position-try-fallbacks\",\n          \"scrollbar-gutter\",\n          \"transition-behavior\",\n          \"transition-property\",\n          \"translate\"\n        ]\n      },\n      \"dataPart\": \"popover-content\",\n      \"description\": \"The [popover] panel, anchored to its trigger; a non-modal role=dialog (APG Dialog pattern) that carries the side/align/open styling contract and animates via @starting-style.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"align\",\n        \"alignOffset\",\n        \"className\",\n        \"label\",\n        \"onBeforeToggle\",\n        \"onToggle\",\n        \"ref\",\n        \"render\",\n        \"side\",\n        \"sideOffset\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\":popover-open\"],\n      \"states\": {\n        \"data-align\": [\"center\", \"end\", \"start\"],\n        \"data-position-side\": [\"bottom\", \"left\", \"right\", \"top\"],\n        \"data-side\": [\n          \"bottom\",\n          \"inline-end\",\n          \"inline-start\",\n          \"left\",\n          \"right\",\n          \"top\"\n        ],\n        \"data-state\": [\"closed\", \"open\"]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-popover-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"popover-description\",\n      \"description\": \"Optional supporting text registered into the panel's aria-describedby without dangling references.\",\n      \"element\": \"p\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"id\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"title\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-popover-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"popover-title\",\n      \"description\": \"Heading registered as the panel's accessible name; the APG Dialog pattern requires this or an explicit label.\",\n      \"element\": \"h2\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"id\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"trigger\": {\n      \"aria\": {\n        \"owns\": [\"aria-controls\", \"aria-expanded\", \"aria-haspopup\"],\n        \"role\": null\n      },\n      \"class\": \"hui-popover-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"popover-trigger\",\n      \"description\": \"Invoker button; opens the content declaratively via popoverTarget, advertises the dialog popup, and carries the per-instance anchor-name.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":focus-visible\"],\n      \"states\": {\n        \"data-state\": [\"closed\", \"open\"]\n      }\n    }\n  },\n  \"rootProps\": [\"defaultOpen\", \"onOpenChange\", \"open\"]\n}\n",
      "path": "packages/heidi-ui/src/popover/popover.anatomy.json",
      "target": "components/ui/heidi/popover/popover.anatomy.json",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Pushing React's open state onto the native popover, once (P5).\n *\n * Popover, Toast, Select, NavigationMenu and Menu (root + submenu) each carried\n * this block. It exists because `showPopover()`/`hidePopover()` fire `toggle`\n * synchronously, and the `toggle` listener is also what reconciles NATIVE\n * dismissal (Esc, light dismiss) back into React. Without a marker, every\n * React-driven open would look like a user-driven one and echo a redundant\n * open-change request back to the owner.\n *\n * ponytail: six of the seven copies the audit counted share this boolean-flag\n * model and differ only in how they SHOW — four call `element.showPopover()`,\n * Menu's two call `showPopoverFrom(element, trigger)` so the popover anchors to\n * its trigger. That is the `show` option and nothing else.\n *\n * ContextMenu is the seventh and does NOT adopt this. It marks transitions by\n * pushing onto a queue that a later `toggle` drains, rather than by raising a\n * flag it lowers in `finally`. Those answer \"is this transition ours?\" at\n * different times — synchronously inside the call, versus whenever the event\n * arrives — and a nested context menu can have more than one transition in\n * flight, which is what the queue is for. Forcing it into the flag model would\n * be a rewrite of its reentrancy handling to make a count read 1 instead of 7,\n * which is the same trade P6 and P9 declined.\n */\n\nexport type NativePopoverSyncOptions = {\n  /**\n   * Anchored show, for popovers that position against a trigger. Defaults to\n   * `element.showPopover()`.\n   */\n  show?: (element: HTMLElement) => void;\n  /**\n   * Raised for the duration of the native call so the component's own `toggle`\n   * listener can tell its own transition from a user's.\n   */\n  transitionRef: { current: boolean };\n};\n\nexport function synchronizeNativePopoverOpen(\n  element: HTMLElement,\n  next: boolean,\n  { show, transitionRef }: NativePopoverSyncOptions\n): void {\n  if (element.matches(\":popover-open\") === next) {\n    return;\n  }\n  transitionRef.current = true;\n  try {\n    if (next) {\n      if (show) {\n        show(element);\n      } else {\n        element.showPopover();\n      }\n    } else {\n      element.hidePopover();\n    }\n  } catch {\n    // A detached or already-transitioning popover can reject the request. The\n    // next state/effect pass retries against React's authority, so swallowing\n    // it here loses nothing — and throwing would take down a render.\n  } finally {\n    // `finally`, not the end of `try`: a rejected request must still lower the\n    // flag, or the component would treat every later native transition as its\n    // own and stop reconciling user dismissal for the rest of its life.\n    transitionRef.current = false;\n  }\n}\n",
      "path": "packages/heidi-ui/src/_internal/native-popover-sync.ts",
      "target": "components/ui/heidi/_internal/native-popover-sync.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared host rendering for heidi-ui parts — Base-UI-shaped composition\n * (docs/HEIDI-UI-HEADLESS.md § 4 / HEIDI-UI.md § 4.5):\n *   - className / style as value or (state) => value\n *   - render as ReactElement or (props, state) => ReactElement\n *   - ref forwarding\n *   - data-hui-part always set (stable unstyled hook)\n *\n * Structural inline styles (e.g. anchorName) are merged last so a consumer\n * style override cannot drop platform wiring.\n */\n\nimport {\n  cloneElement,\n  createElement,\n  isValidElement,\n  type CSSProperties,\n  type ComponentPropsWithRef,\n  type HTMLAttributes,\n  type ReactElement,\n  type ReactNode,\n  type Ref,\n  type RefCallback\n} from \"react\";\n\nexport type HeidiClassName<State> = string | ((state: State) => string | undefined) | undefined;\n\nexport type HeidiStyle<State> =\n  | CSSProperties\n  | ((state: State) => CSSProperties | undefined)\n  | undefined;\n\nexport type HeidiRenderFn<State, Props> = (\n  props: Props,\n  state: State\n) => ReactElement;\n\nexport type HeidiRender<State, Props> =\n  | ReactElement\n  | HeidiRenderFn<State, Props>\n  | undefined;\n\ntype DefaultRenderProps = HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> };\n\nexport type HeidiHostProps<\n  State,\n  RenderProps extends object = DefaultRenderProps\n> = {\n  className?: HeidiClassName<State>;\n  render?: HeidiRender<State, RenderProps>;\n  style?: HeidiStyle<State>;\n};\n\nexport type HeidiIntrinsicHostProps<\n  State,\n  Tag extends keyof HTMLElementTagNameMap\n> = HeidiHostProps<State, ComponentPropsWithRef<Tag>>;\n\nfunction resolveClassName<State>(\n  className: HeidiClassName<State>,\n  state: State\n): string | undefined {\n  return typeof className === \"function\" ? className(state) : className;\n}\n\nfunction resolveStyle<State>(style: HeidiStyle<State>, state: State): CSSProperties | undefined {\n  return typeof style === \"function\" ? style(state) : style;\n}\n\nfunction mergeClassNames(...parts: Array<string | undefined>): string | undefined {\n  const merged = parts.filter(Boolean).join(\" \");\n  return merged.length > 0 ? merged : undefined;\n}\n\n/**\n * Compose a consumer event with library behavior. Consumers run first and may\n * cancel the component behavior with `event.preventDefault()`. Heidi keeps\n * this native-event convention intentionally: it avoids a second branded event\n * API while still making async/controlled actions vetoable.\n */\nexport function composeHeidiEventHandlers<Event extends { defaultPrevented: boolean }>(\n  consumer: ((event: Event) => void) | undefined,\n  library: (event: Event) => void\n): (event: Event) => void {\n  return (event) => {\n    consumer?.(event);\n    if (!event.defaultPrevented) {\n      library(event);\n    }\n  };\n}\n\n/** React 19-safe ref fan-out, including callback-ref cleanup functions. */\nexport function mergeHeidiRefs<Element>(\n  ...inputRefs: Array<Ref<Element> | undefined>\n): Ref<Element> | undefined {\n  const refs = Array.from(\n    new Set(\n      inputRefs.filter(\n        (ref): ref is Exclude<Ref<Element>, null> => ref != null\n      )\n    )\n  );\n  if (refs.length === 0) {\n    return undefined;\n  }\n  if (refs.length === 1) {\n    return refs[0];\n  }\n\n  let cache = mergedRefCache;\n  for (const ref of refs) {\n    const key = ref as object;\n    let child = cache.children.get(key);\n    if (!child) {\n      child = { children: new WeakMap() };\n      cache.children.set(key, child);\n    }\n    cache = child;\n  }\n  if (cache.callback) {\n    return cache.callback as RefCallback<Element>;\n  }\n\n  const callback: RefCallback<Element> = (node) => {\n    const cleanups: Array<() => void> = [];\n    for (const ref of refs) {\n      if (typeof ref === \"function\") {\n        const cleanup = ref(node);\n        if (node !== null) {\n          cleanups.push(typeof cleanup === \"function\" ? cleanup : () => ref(null));\n        }\n      } else if (ref) {\n        ref.current = node;\n        if (node !== null) {\n          cleanups.push(() => {\n            ref.current = null;\n          });\n        }\n      }\n    }\n    return cleanups.length > 0\n      ? () => {\n          for (const cleanup of cleanups) {\n            cleanup();\n          }\n        }\n      : undefined;\n  };\n  cache.callback = callback as RefCallback<unknown>;\n  return callback;\n}\n\ntype MergedRefCache = {\n  callback?: RefCallback<unknown>;\n  children: WeakMap<object, MergedRefCache>;\n};\n\nconst mergedRefCache: MergedRefCache = { children: new WeakMap() };\n\ntype UnknownHandler = (...args: never[]) => unknown;\n\nfunction isEventHandler(key: string, value: unknown): value is UnknownHandler {\n  return /^on[A-Z]/.test(key) && typeof value === \"function\";\n}\n\nfunction defaultPrevented(args: unknown[]): boolean {\n  const event = args[0];\n  return (\n    typeof event === \"object\" &&\n    event !== null &&\n    \"defaultPrevented\" in event &&\n    event.defaultPrevented === true\n  );\n}\n\nfunction composeUnknownHandlers(\n  consumer: UnknownHandler,\n  library: UnknownHandler\n): UnknownHandler {\n  if (consumer === library) {\n    return library;\n  }\n  return ((...args: unknown[]) => {\n    (consumer as (...handlerArgs: unknown[]) => unknown)(...args);\n    if (!defaultPrevented(args)) {\n      (library as (...handlerArgs: unknown[]) => unknown)(...args);\n    }\n  }) as UnknownHandler;\n}\n\n/** Library-owned host props — permissive so button `type`, `data-*`, `popover`, etc. type-check. */\nexport type HeidiElementProps<Tag extends keyof HTMLElementTagNameMap> = Record<\n  string,\n  unknown\n> & {\n  children?: ReactNode;\n  className?: string;\n  ref?: Ref<HTMLElementTagNameMap[Tag]>;\n  style?: CSSProperties;\n};\n\ntype RenderElementParams<\n  State,\n  Tag extends keyof HTMLElementTagNameMap,\n  RenderProps extends object = DefaultRenderProps\n> = {\n  /** Default class from the generated hui-* map (theme/base target). */\n  className?: string;\n  /** Stable machine hook — always emitted. */\n  dataPart: string;\n  /** Intrinsic tag when `render` is omitted. */\n  element: Tag;\n  /** Props the library owns (aria, ids, handlers, popover, …). */\n  props: HeidiElementProps<Tag>;\n  /** Optional consumer composition props. */\n  renderProps?: HeidiHostProps<State, RenderProps>;\n  /** Typed state passed to functional className/style/render. */\n  state: State;\n  /** Structural inline styles that must survive consumer style merges. */\n  structuralStyle?: CSSProperties;\n  /**\n   * Render-element handlers to remove instead of composing. This is reserved\n   * for states such as a focusable disabled composite item where the public\n   * contract requires press handlers to be completely inert.\n   */\n  suppressRenderedHandlers?: readonly string[];\n};\n\n/**\n * Render a heidi-ui host element with optional Base-shaped composition.\n */\nexport function renderHeidiElement<\n  State,\n  Tag extends keyof HTMLElementTagNameMap,\n  RenderProps extends object = DefaultRenderProps\n>(\n  params: RenderElementParams<State, Tag, RenderProps>\n): ReactElement {\n  const {\n    className,\n    dataPart,\n    element,\n    props,\n    renderProps,\n    state,\n    structuralStyle,\n    suppressRenderedHandlers\n  } = params;\n  const consumerClass = resolveClassName(renderProps?.className, state);\n  const consumerStyle = resolveStyle(renderProps?.style, state);\n  const mergedStyle: CSSProperties | undefined =\n    props.style || consumerStyle || structuralStyle\n      ? { ...props.style, ...consumerStyle, ...structuralStyle }\n      : undefined;\n\n  const outProps: HTMLAttributes<HTMLElement> & {\n    \"data-hui-part\": string;\n    ref?: Ref<HTMLElement>;\n  } = {\n    ...(props as HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> }),\n    className: mergeClassNames(consumerClass, className),\n    \"data-hui-part\": dataPart,\n    style: mergedStyle\n  };\n\n  const render = renderProps?.render;\n  if (typeof render === \"function\") {\n    // Post-merge the returned element as well. This deliberately makes the\n    // stable data hook, owned semantics, refs, and internal handlers survive\n    // even when a render function forgets to spread one of the supplied props.\n    return mergeRenderedElement(\n      render(outProps as unknown as RenderProps, state),\n      outProps,\n      dataPart,\n      structuralStyle,\n      suppressRenderedHandlers\n    );\n  }\n  if (isValidElement(render)) {\n    return mergeRenderedElement(\n      render,\n      outProps,\n      dataPart,\n      structuralStyle,\n      suppressRenderedHandlers\n    );\n  }\n\n  return createElement(element, outProps as never, props.children);\n}\n\ntype RenderedProps = Record<string, unknown> & {\n  children?: ReactNode;\n  className?: string;\n  ref?: Ref<HTMLElement>;\n  style?: CSSProperties;\n};\n\nfunction mergeRenderedElement(\n  element: ReactElement,\n  libraryProps: HTMLAttributes<HTMLElement> & {\n    \"data-hui-part\": string;\n    ref?: Ref<HTMLElement>;\n  },\n  dataPart: string,\n  structuralStyle: CSSProperties | undefined,\n  suppressRenderedHandlers: readonly string[] | undefined\n): ReactElement {\n  const rendered = element as ReactElement<RenderedProps>;\n  const renderedProps = rendered.props;\n  const renderedClass =\n    renderedProps.className === libraryProps.className\n      ? undefined\n      : renderedProps.className;\n  const merged: RenderedProps = {\n    ...renderedProps,\n    ...libraryProps,\n    className: mergeClassNames(renderedClass, libraryProps.className),\n    \"data-hui-part\": dataPart,\n    ref: mergeHeidiRefs(libraryProps.ref, renderedProps.ref),\n    style: {\n      ...libraryProps.style,\n      ...renderedProps.style,\n      ...structuralStyle\n    }\n  };\n\n  if (Object.prototype.hasOwnProperty.call(renderedProps, \"children\")) {\n    merged.children = renderedProps.children;\n  }\n\n  const suppressedHandlers = suppressRenderedHandlers\n    ? new Set(suppressRenderedHandlers)\n    : null;\n  for (const key of new Set([...Object.keys(renderedProps), ...Object.keys(libraryProps)])) {\n    const consumer = renderedProps[key];\n    const library = (libraryProps as unknown as Record<string, unknown>)[key];\n    if (suppressedHandlers?.has(key)) {\n      merged[key] = library;\n      continue;\n    }\n    if (isEventHandler(key, consumer) && isEventHandler(key, library)) {\n      merged[key] = composeUnknownHandlers(consumer, library);\n    }\n  }\n\n  return cloneElement(rendered, merged as never);\n}\n",
      "path": "packages/heidi-ui/src/_internal/render-element.ts",
      "target": "components/ui/heidi/_internal/render-element.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared id sanitizer for heidi-ui hosts.\n *\n * React's useId() emits characters that are invalid in HTML id / attribute\n * selectors and in CSS identifiers (`«r0»` in React 19, `:r0:` earlier).\n * heidi-ui interpolates these ids into element ids, aria relationships, and\n * dashed-ident anchor names (`--hui-*-anchor-…`), so everything outside\n * [a-zA-Z0-9_-] is stripped.\n */\n\nexport function safeId(id: string): string {\n  return id.replace(/[^a-zA-Z0-9_-]/g, \"\");\n}\n",
      "path": "packages/heidi-ui/src/_internal/safe-id.ts",
      "target": "components/ui/heidi/_internal/safe-id.ts",
      "type": "registry:ui"
    },
    {
      "content": "import { useEffect, useLayoutEffect } from \"react\";\n\n/**\n * `useLayoutEffect` that does not warn on the server.\n *\n * ponytail: fifteen primitives imported `useLayoutEffect` straight from React.\n * Every one of them carries `\"use client\"`, which is easy to misread as\n * \"client-only\" — in the App Router it means \"hydrated on the client\", and the\n * component is still rendered to HTML on the server first. React logs\n * \"useLayoutEffect does nothing on the server\" for each one, so a consumer\n * doing SSR saw a wall of warnings the app that ships this library never saw,\n * because it renders these routes on the client path.\n *\n * The branch is evaluated ONCE at module scope, not per render, and it is\n * therefore stable across a component's lifetime — swapping which hook is\n * called between renders would violate the rules of hooks. `typeof document`\n * rather than `typeof window`: both work, but `document` is the thing the\n * effect actually needs, and it keeps the check honest in exotic runtimes that\n * define a partial `window`.\n *\n * Rejected: `useInsertionEffect`, which runs earlier but is specified for\n * style injection and is not a general layout hook; and per-file guards, which\n * is what the 15 copies would have become.\n */\nexport const useHeidiLayoutEffect =\n  typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n",
      "path": "packages/heidi-ui/src/_internal/use-layout-effect.ts",
      "target": "components/ui/heidi/_internal/use-layout-effect.ts",
      "type": "registry:ui"
    }
  ],
  "name": "popover",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Popover",
  "type": "registry:ui"
}
