{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "Base-UI-shaped modal state machine on native <dialog>. Controlled state is authoritative and cancellable; Content/Popup is lazy by default, retains exit motion, reconciles native close paths, isolates nested dialogs, wraps APG Tab focus, restores the actual invoker, and stays viewport-contained. Pointer dismissal is owned in JavaScript so a controlled consumer can reject it without a native close/reopen flash.",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui Dialog — a Base-shaped modal state machine on the native\n * <dialog> top layer. Native HTML owns modality, inertness, and ::backdrop;\n * Heidi adds authoritative controlled state, cancellable change reasons,\n * exact APG focus wrapping/return, lazy presence, nested-dialog isolation,\n * and animation completion.\n *\n * Accessible name (required): render <Dialog.Title>, pass `label`, or supply\n * an explicit aria-label/aria-labelledby on <Dialog.Content>. Description is\n * optional and can be omitted from the accessibility tree with\n * `describe={false}` when the content is structurally complex.\n *\n * RSC rule: named exports are canonical from Server Components. The Dialog.X\n * namespace is client-component sugar.\n */\n\nimport {\n  type ComponentPropsWithoutRef,\n  createContext,\n  type KeyboardEvent,\n  type MouseEvent,\n  type PointerEvent,\n  type ReactNode,\n  type Ref,\n  type RefObject,\n  type SyntheticEvent,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState\n} from \"react\";\nimport { waitForElementAnimations } from \"../_internal/animation-wait\";\nimport { ariaDisabledAttrs, nativeDisabledAttrs } from \"../_internal/disabled\";\nimport {\n  containHeidiModalTabFocus,\n  getHeidiTabbableElements\n} from \"../_internal/focus-scope\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { safeId } from \"../_internal/safe-id\";\nimport { useHeidiLayoutEffect } from \"../_internal/use-layout-effect\";\nimport { type DialogSide } from \"./dialog.anatomy.generated\";\nimport { DIALOG_CLASSES } from \"./dialog.classes.generated\";\n\nexport type { DialogSide };\n\ntype NativeHostProps<Tag extends keyof HTMLElementTagNameMap> = Omit<\n  ComponentPropsWithoutRef<Tag>,\n  \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\nexport type DialogInteractionType =\n  | \"\"\n  | \"keyboard\"\n  | \"mouse\"\n  | \"pen\"\n  | \"touch\";\n\nexport type DialogTransitionStatus =\n  | \"ending\"\n  | \"idle\"\n  | \"starting\"\n  | undefined;\n\nexport type DialogRootChangeEventReason =\n  | \"close-press\"\n  | \"escape-key\"\n  | \"imperative-action\"\n  | \"none\"\n  | \"outside-press\"\n  | \"trigger-press\";\n\nexport type DialogRootChangeEventDetails = {\n  /** Opts a nested dialog's source event back into propagation. */\n  allowPropagation: () => void;\n  /** Cancels Heidi's requested state change. */\n  cancel: () => void;\n  /** Native event which requested the state change. */\n  event: Event;\n  readonly isCanceled: boolean;\n  readonly isPropagationAllowed: boolean;\n  readonly isUnmountPrevented: boolean;\n  /** Retains closed content after Heidi's exit motion has completed. */\n  preventUnmountOnClose: () => void;\n  reason: DialogRootChangeEventReason;\n  trigger: Element | undefined;\n};\n\nfunction createChangeEventDetails(\n  reason: DialogRootChangeEventReason,\n  event: Event,\n  trigger?: Element\n): DialogRootChangeEventDetails {\n  let canceled = false;\n  let propagationAllowed = false;\n  let unmountPrevented = false;\n  return {\n    allowPropagation: () => {\n      propagationAllowed = true;\n    },\n    cancel: () => {\n      canceled = true;\n    },\n    event,\n    get isCanceled() {\n      return canceled;\n    },\n    get isPropagationAllowed() {\n      return propagationAllowed;\n    },\n    get isUnmountPrevented() {\n      return unmountPrevented;\n    },\n    preventUnmountOnClose: () => {\n      unmountPrevented = true;\n    },\n    reason,\n    trigger\n  };\n}\n\nfunction interactionTypeFromEvent(event: Event): DialogInteractionType {\n  if (\"pointerType\" in event && typeof event.pointerType === \"string\") {\n    if (event.pointerType === \"touch\" || event.pointerType === \"pen\") {\n      return event.pointerType;\n    }\n    return \"mouse\";\n  }\n  if (event.type.startsWith(\"key\")) {\n    return \"keyboard\";\n  }\n  if (event.type.startsWith(\"touch\")) {\n    return \"touch\";\n  }\n  if (\"detail\" in event && typeof event.detail === \"number\") {\n    return event.detail === 0 ? \"keyboard\" : \"mouse\";\n  }\n  return \"\";\n}\n\nfunction dataState(open: boolean): \"closed\" | \"open\" {\n  return open ? \"open\" : \"closed\";\n}\n\nfunction openStateAttributes(open: boolean): Record<string, true | undefined> {\n  return open\n    ? { \"data-closed\": undefined, \"data-open\": true }\n    : { \"data-closed\": true, \"data-open\": undefined };\n}\n\nfunction transitionAttributes(\n  status: DialogTransitionStatus\n): Record<string, true | undefined> {\n  return {\n    \"data-ending-style\": status === \"ending\" ? true : undefined,\n    \"data-starting-style\": status === \"starting\" ? true : undefined\n  };\n}\n\n\n\n\n\n\n\nexport type DialogFocusTarget =\n  | boolean\n  | RefObject<HTMLElement | null>\n  | ((\n      interactionType: DialogInteractionType\n    ) => boolean | HTMLElement | null | undefined | void);\n\ntype FocusDirective =\n  | { kind: \"default\" }\n  | { kind: \"skip\" }\n  | { kind: \"target\"; target: HTMLElement };\n\nfunction resolveFocusDirective(\n  value: DialogFocusTarget | undefined,\n  interactionType: DialogInteractionType\n): FocusDirective {\n  if (value === undefined || value === true) {\n    return { kind: \"default\" };\n  }\n  if (value === false) {\n    return { kind: \"skip\" };\n  }\n  if (typeof value === \"function\") {\n    const result = value(interactionType);\n    if (result === null || result === true) {\n      return { kind: \"default\" };\n    }\n    if (result === undefined || result === false) {\n      return { kind: \"skip\" };\n    }\n    return { kind: \"target\", target: result };\n  }\n  return value.current\n    ? { kind: \"target\", target: value.current }\n    : { kind: \"default\" };\n}\n\ntype DialogContextValue = {\n  activeTriggerId: string | null;\n  completeOpenChange: (open: boolean) => void;\n  contentId: string;\n  descriptionIds: string[];\n  disablePointerDismissal: boolean;\n  ensureReturnFocus: () => void;\n  getInteractionType: () => DialogInteractionType;\n  getOpen: () => boolean;\n  nested: boolean;\n  nestedDialogOpen: boolean;\n  open: boolean;\n  registerDescription: (id: string) => () => void;\n  registerNestedOpen: () => () => void;\n  registerTitle: (id: string) => () => void;\n  rememberReturnFocus: (\n    element: HTMLElement,\n    interactionType: DialogInteractionType\n  ) => void;\n  requestOpen: (\n    open: boolean,\n    reason: DialogRootChangeEventReason,\n    event: Event,\n    trigger?: Element,\n    interactionType?: DialogInteractionType\n  ) => DialogRootChangeEventDetails | null;\n  restoreFocus: (\n    dialog: HTMLDialogElement,\n    finalFocus: DialogFocusTarget | undefined\n  ) => void;\n  shouldRetainClosedContent: () => boolean;\n  titleIds: string[];\n};\n\nconst DialogContext = createContext<DialogContextValue | null>(null);\n\nfunction useDialogContext(part: string): DialogContextValue {\n  const context = useContext(DialogContext);\n  if (!context) {\n    throw new Error(`Dialog.${part} must be rendered inside Dialog.Root.`);\n  }\n  return context;\n}\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\nexport type DialogRootProps = {\n  children?: ReactNode;\n  defaultOpen?: boolean;\n  /** Base-compatible root alias for disabling outside pointer dismissal. */\n  disablePointerDismissal?: boolean;\n  defaultTriggerId?: string | null;\n  onOpenChange?: (\n    open: boolean,\n    details: DialogRootChangeEventDetails\n  ) => void;\n  onOpenChangeComplete?: (open: boolean) => void;\n  open?: boolean;\n  triggerId?: string | null;\n};\n\nexport function DialogRoot({\n  children,\n  defaultOpen = false,\n  defaultTriggerId = null,\n  disablePointerDismissal = false,\n  onOpenChange,\n  onOpenChangeComplete,\n  open: openProp,\n  triggerId: triggerIdProp\n}: DialogRootProps) {\n  const parentContext = useContext(DialogContext);\n  const generatedId = safeId(useId());\n  const [descriptionIds, setDescriptionIds] = useState<string[]>([]);\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const [internalTriggerId, setInternalTriggerId] = useState<string | null>(\n    defaultTriggerId\n  );\n  const [nestedOpenCount, setNestedOpenCount] = useState(0);\n  const [titleIds, setTitleIds] = useState<string[]>([]);\n  const controlled = openProp !== undefined;\n  const open = openProp ?? internalOpen;\n  const activeTriggerId =\n    triggerIdProp !== undefined ? triggerIdProp : internalTriggerId;\n  const openRef = useRef(open);\n  openRef.current = open;\n  const activeTriggerIdRef = useRef(activeTriggerId);\n  activeTriggerIdRef.current = activeTriggerId;\n  const interactionTypeRef = useRef<DialogInteractionType>(\"\");\n  const returnFocusRef = useRef<HTMLElement | null>(null);\n  const preventUnmountRef = useRef(false);\n  const completedStateRef = useRef(open);\n\n  const getOpen = useCallback(() => openRef.current, []);\n  const getInteractionType = useCallback(\n    () => interactionTypeRef.current,\n    []\n  );\n  const ensureReturnFocus = useCallback(() => {\n    if (returnFocusRef.current?.isConnected) {\n      return;\n    }\n    const associated = activeTriggerIdRef.current\n      ? document.getElementById(activeTriggerIdRef.current)\n      : null;\n    const active = document.activeElement;\n    returnFocusRef.current =\n      associated instanceof HTMLElement\n        ? associated\n        : active instanceof HTMLElement\n          ? active\n          : null;\n  }, []);\n\n  const rememberReturnFocus = useCallback(\n    (element: HTMLElement, interactionType: DialogInteractionType) => {\n      returnFocusRef.current = element;\n      interactionTypeRef.current = interactionType;\n      setInternalTriggerId(element.id || null);\n    },\n    []\n  );\n\n  const requestOpen = useCallback(\n    (\n      next: boolean,\n      reason: DialogRootChangeEventReason,\n      event: Event,\n      trigger?: Element,\n      interactionType = interactionTypeFromEvent(event)\n    ) => {\n      if (next === openRef.current) {\n        return null;\n      }\n      const details = createChangeEventDetails(reason, event, trigger);\n      onOpenChange?.(next, details);\n      if (details.isCanceled) {\n        return details;\n      }\n      interactionTypeRef.current = interactionType;\n      if (next) {\n        preventUnmountRef.current = false;\n        ensureReturnFocus();\n      } else if (details.isUnmountPrevented) {\n        preventUnmountRef.current = true;\n      }\n      if (!controlled) {\n        openRef.current = next;\n        setInternalOpen(next);\n      }\n      return details;\n    },\n    [controlled, ensureReturnFocus, onOpenChange]\n  );\n\n  const completeOpenChange = useCallback(\n    (next: boolean) => {\n      if (completedStateRef.current === next) {\n        return;\n      }\n      completedStateRef.current = next;\n      onOpenChangeComplete?.(next);\n    },\n    [onOpenChangeComplete]\n  );\n\n  const restoreFocus = useCallback(\n    (dialog: HTMLDialogElement, finalFocus: DialogFocusTarget | undefined) => {\n      const active = document.activeElement;\n      const remembered = returnFocusRef.current;\n      const associated = activeTriggerIdRef.current\n        ? document.getElementById(activeTriggerIdRef.current)\n        : null;\n      const shouldRestore =\n        active == null ||\n        active === document.body ||\n        active === dialog ||\n        active === remembered ||\n        active === associated ||\n        dialog.contains(active);\n      if (shouldRestore) {\n        const directive = resolveFocusDirective(\n          finalFocus,\n          interactionTypeRef.current\n        );\n        if (directive.kind !== \"skip\") {\n          const target =\n            directive.kind === \"target\"\n              ? directive.target\n              : remembered?.isConnected\n                ? remembered\n                : associated instanceof HTMLElement\n                  ? associated\n                  : null;\n          if (target?.isConnected) {\n            target.focus({ preventScroll: true });\n          }\n        }\n      }\n      returnFocusRef.current = null;\n      interactionTypeRef.current = \"\";\n    },\n    []\n  );\n\n  const registerTitle = useCallback(\n    (id: string) => addRegisteredId(setTitleIds, id),\n    []\n  );\n  const registerDescription = useCallback(\n    (id: string) => addRegisteredId(setDescriptionIds, id),\n    []\n  );\n  const registerNestedOpen = useCallback(() => {\n    setNestedOpenCount((count) => count + 1);\n    return () => setNestedOpenCount((count) => Math.max(0, count - 1));\n  }, []);\n  const shouldRetainClosedContent = useCallback(() => {\n    const retain = preventUnmountRef.current;\n    preventUnmountRef.current = false;\n    return retain;\n  }, []);\n\n  const parentRegisterNestedOpen = parentContext?.registerNestedOpen;\n  useEffect(() => {\n    if (!open || !parentRegisterNestedOpen) {\n      return;\n    }\n    return parentRegisterNestedOpen();\n  }, [open, parentRegisterNestedOpen]);\n\n  const value = useMemo<DialogContextValue>(\n    () => ({\n      activeTriggerId,\n      completeOpenChange,\n      contentId: `hui-dialog-${generatedId}`,\n      descriptionIds,\n      disablePointerDismissal,\n      ensureReturnFocus,\n      getInteractionType,\n      getOpen,\n      nested: parentContext !== null,\n      nestedDialogOpen: nestedOpenCount > 0,\n      open,\n      registerDescription,\n      registerNestedOpen,\n      registerTitle,\n      rememberReturnFocus,\n      requestOpen,\n      restoreFocus,\n      shouldRetainClosedContent,\n      titleIds\n    }),\n    [\n      activeTriggerId,\n      completeOpenChange,\n      descriptionIds,\n      disablePointerDismissal,\n      ensureReturnFocus,\n      generatedId,\n      getInteractionType,\n      getOpen,\n      nestedOpenCount,\n      open,\n      parentContext,\n      registerDescription,\n      registerNestedOpen,\n      registerTitle,\n      rememberReturnFocus,\n      requestOpen,\n      restoreFocus,\n      shouldRetainClosedContent,\n      titleIds\n    ]\n  );\n\n  return <DialogContext value={value}>{children}</DialogContext>;\n}\n\nexport type DialogTriggerState = {\n  disabled: boolean;\n  open: boolean;\n};\n\ntype DialogTriggerNativeProps = Omit<\n  NativeHostProps<\"button\">,\n  \"aria-controls\" | \"aria-expanded\" | \"aria-haspopup\" | \"disabled\" | \"type\"\n>;\n\nexport type DialogTriggerProps = HeidiIntrinsicHostProps<\n  DialogTriggerState,\n  \"button\"\n> &\n  DialogTriggerNativeProps & {\n    children?: ReactNode;\n    disabled?: boolean;\n    /** Set false only when render returns a non-button host. */\n    nativeButton?: boolean;\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function DialogTrigger({\n  children,\n  className,\n  disabled = false,\n  id,\n  nativeButton = true,\n  onClick,\n  onKeyDown,\n  onKeyUp,\n  onPointerDown,\n  ref,\n  render,\n  style,\n  tabIndex,\n  ...nativeProps\n}: DialogTriggerProps) {\n  const generatedId = safeId(useId());\n  const context = useDialogContext(\"Trigger\");\n  const triggerId = id ?? `hui-dialog-trigger-${generatedId}`;\n  const triggerOpen =\n    context.open &&\n    (context.activeTriggerId === null || context.activeTriggerId === triggerId);\n  const pointerTypeRef = useRef<DialogInteractionType>(\"\");\n  const state = useMemo<DialogTriggerState>(\n    () => ({ disabled, open: triggerOpen }),\n    [disabled, triggerOpen]\n  );\n\n  const handlePointerDown = composeHeidiEventHandlers(\n    onPointerDown,\n    (event: PointerEvent<HTMLButtonElement>) => {\n      pointerTypeRef.current =\n        event.pointerType === \"touch\" || event.pointerType === \"pen\"\n          ? event.pointerType\n          : \"mouse\";\n    }\n  );\n  const handleClick = composeHeidiEventHandlers(\n    onClick,\n    (event: MouseEvent<HTMLButtonElement>) => {\n      if (disabled) {\n        event.preventDefault();\n        return;\n      }\n      const interactionType =\n        pointerTypeRef.current || interactionTypeFromEvent(event.nativeEvent);\n      pointerTypeRef.current = \"\";\n      context.rememberReturnFocus(event.currentTarget, interactionType);\n      context.requestOpen(\n        true,\n        \"trigger-press\",\n        event.nativeEvent,\n        event.currentTarget,\n        interactionType\n      );\n    }\n  );\n  const handleKeyDown = composeHeidiEventHandlers(\n    onKeyDown,\n    (event: KeyboardEvent<HTMLButtonElement>) => {\n      if (nativeButton || event.target !== event.currentTarget) {\n        return;\n      }\n      if (event.key === \" \") {\n        event.preventDefault();\n        return;\n      }\n      if (event.key === \"Enter\") {\n        event.preventDefault();\n        if (!disabled && !event.repeat) {\n          event.currentTarget.click();\n        }\n      }\n    }\n  );\n  const handleKeyUp = composeHeidiEventHandlers(\n    onKeyUp,\n    (event: KeyboardEvent<HTMLButtonElement>) => {\n      if (\n        nativeButton ||\n        event.target !== event.currentTarget ||\n        event.key !== \" \"\n      ) {\n        return;\n      }\n      event.preventDefault();\n      if (!disabled) {\n        event.currentTarget.click();\n      }\n    }\n  );\n\n  return renderHeidiElement({\n    className: DIALOG_CLASSES.trigger,\n    dataPart: \"dialog-trigger\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      ...(nativeButton\n        ? nativeDisabledAttrs(disabled)\n        : ariaDisabledAttrs(disabled)),\n      \"aria-controls\": triggerOpen ? context.contentId : undefined,\n      \"aria-expanded\": triggerOpen,\n      \"aria-haspopup\": \"dialog\",\n      children,\n      \"data-popup-open\": triggerOpen ? true : undefined,\n      id: triggerId,\n      onClick: handleClick,\n      onKeyDown: handleKeyDown,\n      onKeyUp: handleKeyUp,\n      onPointerDown: handlePointerDown,\n      ref,\n      role: nativeButton ? undefined : \"button\",\n      tabIndex: nativeButton ? tabIndex : (tabIndex ?? 0),\n      type: nativeButton ? \"button\" : undefined\n    },\n    renderProps: { className, render, style },\n    state\n  });\n}\n\ntype DialogEmptyState = Record<string, never>;\n\nexport type DialogTitleProps = HeidiIntrinsicHostProps<\n  DialogEmptyState,\n  \"h2\"\n> &\n  Omit<NativeHostProps<\"h2\">, \"id\"> & {\n    children?: ReactNode;\n    id?: string;\n    ref?: Ref<HTMLHeadingElement>;\n  };\n\nexport function DialogTitle({\n  children,\n  className,\n  id,\n  ref,\n  render,\n  style,\n  tabIndex = -1,\n  ...nativeProps\n}: DialogTitleProps) {\n  const generatedId = safeId(useId());\n  const { registerTitle } = useDialogContext(\"Title\");\n  const titleId = id ?? `hui-dialog-title-${generatedId}`;\n  useHeidiLayoutEffect(() => registerTitle(titleId), [registerTitle, titleId]);\n  return renderHeidiElement({\n    className: DIALOG_CLASSES.title,\n    dataPart: \"dialog-title\",\n    element: \"h2\",\n    props: {\n      ...nativeProps,\n      children,\n      \"data-hui-focus-fallback\": \"\",\n      id: titleId,\n      ref,\n      tabIndex\n    },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type DialogDescriptionProps = HeidiIntrinsicHostProps<\n  DialogEmptyState,\n  \"p\"\n> &\n  Omit<NativeHostProps<\"p\">, \"id\"> & {\n    children?: ReactNode;\n    id?: string;\n    ref?: Ref<HTMLParagraphElement>;\n  };\n\nexport function DialogDescription({\n  children,\n  className,\n  id,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: DialogDescriptionProps) {\n  const generatedId = safeId(useId());\n  const { registerDescription } = useDialogContext(\"Description\");\n  const descriptionId = id ?? `hui-dialog-description-${generatedId}`;\n  useHeidiLayoutEffect(\n    () => registerDescription(descriptionId),\n    [descriptionId, registerDescription]\n  );\n  return renderHeidiElement({\n    className: DIALOG_CLASSES.description,\n    dataPart: \"dialog-description\",\n    element: \"p\",\n    props: { ...nativeProps, children, id: descriptionId, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type DialogContentState = {\n  dismissible: boolean;\n  nested: boolean;\n  nestedDialogOpen: boolean;\n  open: boolean;\n  side: DialogSide | null;\n  transitionStatus: DialogTransitionStatus;\n};\n\ntype DialogContentNativeProps = Omit<\n  NativeHostProps<\"dialog\">,\n  | \"children\"\n  | \"closedby\"\n  | \"id\"\n  | \"onCancel\"\n  | \"onClose\"\n  | \"onKeyDown\"\n  | \"onPointerDown\"\n  | \"open\"\n  | \"role\"\n>;\n\nexport type DialogContentProps = Omit<\n  HeidiIntrinsicHostProps<DialogContentState, \"dialog\">,\n  \"render\"\n> &\n  DialogContentNativeProps & {\n    children?: ReactNode;\n    /** Whether a pointer press on the backdrop requests close. Escape remains enabled. */\n    dismissible?: boolean;\n    /** Set false for complex structured content that should not be announced as one description. */\n    describe?: boolean;\n    finalFocus?: DialogFocusTarget;\n    initialFocus?: DialogFocusTarget;\n    /** Keep the closed native element in the DOM after exit motion. */\n    keepMounted?: boolean;\n    /** Accessible-name convenience when no Title is rendered. */\n    label?: string;\n    onCancel?: ComponentPropsWithoutRef<\"dialog\">[\"onCancel\"];\n    onClose?: ComponentPropsWithoutRef<\"dialog\">[\"onClose\"];\n    onKeyDown?: ComponentPropsWithoutRef<\"dialog\">[\"onKeyDown\"];\n    onPointerDown?: ComponentPropsWithoutRef<\"dialog\">[\"onPointerDown\"];\n    ref?: Ref<HTMLDialogElement>;\n    /** Edge-anchored sheet variant on the same native modal foundation. */\n    side?: DialogSide;\n  };\n\nfunction pointerIsOutsideDialog(\n  event: PointerEvent<HTMLDialogElement>\n): boolean {\n  if (event.target !== event.currentTarget) {\n    return false;\n  }\n  const rect = event.currentTarget.getBoundingClientRect();\n  return (\n    event.clientX < rect.left ||\n    event.clientX > rect.right ||\n    event.clientY < rect.top ||\n    event.clientY > rect.bottom\n  );\n}\n\nfunction focusInitialElement(\n  dialog: HTMLDialogElement,\n  initialFocus: DialogFocusTarget | undefined,\n  interactionType: DialogInteractionType\n) {\n  const directive = resolveFocusDirective(initialFocus, interactionType);\n  if (directive.kind === \"skip\") {\n    return;\n  }\n  if (\n    directive.kind === \"target\" &&\n    directive.target.isConnected &&\n    (directive.target === dialog || dialog.contains(directive.target))\n  ) {\n    directive.target.focus({ preventScroll: true });\n    return;\n  }\n  if (interactionType === \"touch\") {\n    dialog.focus({ preventScroll: true });\n    return;\n  }\n  const title = dialog.querySelector<HTMLElement>(\"[data-hui-focus-fallback]\");\n  if (dialog.scrollHeight > dialog.clientHeight + 1 && title) {\n    title.focus({ preventScroll: true });\n    return;\n  }\n  const active = document.activeElement;\n  if (\n    active instanceof HTMLElement &&\n    dialog.contains(active) &&\n    active.hasAttribute(\"autofocus\")\n  ) {\n    return;\n  }\n  const target = getHeidiTabbableElements(dialog)[0] ?? title ?? dialog;\n  target.focus({ preventScroll: true });\n}\n\nfunction dialogHasAccessibleName(dialog: HTMLDialogElement): boolean {\n  if (dialog.getAttribute(\"aria-label\")?.trim()) {\n    return true;\n  }\n  const labelledBy = dialog.getAttribute(\"aria-labelledby\")?.trim();\n  if (!labelledBy) {\n    return false;\n  }\n  return labelledBy\n    .split(/\\s+/)\n    .some((id) => {\n      const element = document.getElementById(id);\n      return element != null && dialog.contains(element) && !!element.textContent?.trim();\n    });\n}\n\nexport function DialogContent({\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  children,\n  className,\n  describe = true,\n  dismissible: dismissibleProp,\n  finalFocus,\n  initialFocus,\n  keepMounted = false,\n  label,\n  onCancel,\n  onClose,\n  onKeyDown,\n  onPointerDown,\n  ref,\n  side,\n  style,\n  ...nativeProps\n}: DialogContentProps) {\n  const context = useDialogContext(\"Content\");\n  const {\n    completeOpenChange,\n    contentId,\n    ensureReturnFocus,\n    getInteractionType,\n    open,\n    restoreFocus,\n    shouldRetainClosedContent\n  } = context;\n  const dismissible =\n    dismissibleProp ?? !context.disablePointerDismissal;\n  const dialogRef = useRef<HTMLDialogElement | null>(null);\n  const [retained, setRetained] = useState(open || keepMounted);\n  const [transitionStatus, setTransitionStatus] =\n    useState<DialogTransitionStatus>(open ? \"starting\" : undefined);\n  const cycleRef = useRef(0);\n  const internallyClosingRef = useRef(false);\n  const lastEscapeRef = useRef<\n    { event: globalThis.KeyboardEvent; time: number } | undefined\n  >(undefined);\n  const openRef = useRef(open);\n  openRef.current = open;\n  const initialFocusRef = useRef(initialFocus);\n  initialFocusRef.current = initialFocus;\n  const finalFocusRef = useRef(finalFocus);\n  finalFocusRef.current = finalFocus;\n  const keepMountedRef = useRef(keepMounted);\n  keepMountedRef.current = keepMounted;\n  const shouldRender = keepMounted || open || retained;\n\n  useHeidiLayoutEffect(() => {\n    if (open) {\n      setRetained(true);\n    }\n  }, [open]);\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\" || !shouldRender) {\n      return;\n    }\n    const timeout = window.setTimeout(() => {\n      const dialog = dialogRef.current;\n      if (dialog && !dialogHasAccessibleName(dialog)) {\n        // oxlint-disable-next-line no-console -- an unnamed modal is a WCAG failure.\n        console.warn(\n          \"heidi-ui Dialog: missing accessible name — provide <Dialog.Title>, label, aria-label, or aria-labelledby.\"\n        );\n      }\n    }, 0);\n    return () => window.clearTimeout(timeout);\n  }, [shouldRender]);\n\n  useEffect(() => {\n    const dialog = dialogRef.current;\n    if (!dialog) {\n      return;\n    }\n    const cycle = ++cycleRef.current;\n    if (open) {\n      setRetained(true);\n      setTransitionStatus(\"starting\");\n      ensureReturnFocus();\n      if (!dialog.open) {\n        dialog.showModal();\n      }\n      focusInitialElement(\n        dialog,\n        initialFocusRef.current,\n        getInteractionType()\n      );\n      void waitForElementAnimations(dialog, { awaitPaint: true, subtree: true }).then(() => {\n        if (cycle !== cycleRef.current || !openRef.current) {\n          return;\n        }\n        setTransitionStatus(\"idle\");\n        completeOpenChange(true);\n      });\n      return;\n    }\n\n    setTransitionStatus(dialog.open ? \"ending\" : undefined);\n    void waitForElementAnimations(dialog, { awaitPaint: true, subtree: true }).then(() => {\n      if (cycle !== cycleRef.current || openRef.current) {\n        return;\n      }\n      if (dialog.open) {\n        internallyClosingRef.current = true;\n        dialog.close();\n      }\n      setTransitionStatus(undefined);\n      completeOpenChange(false);\n      requestAnimationFrame(() => {\n        if (!openRef.current) {\n          restoreFocus(dialog, finalFocusRef.current);\n        }\n      });\n      if (\n        !keepMountedRef.current &&\n        !shouldRetainClosedContent()\n      ) {\n        setRetained(false);\n      }\n    });\n  }, [\n    completeOpenChange,\n    contentId,\n    ensureReturnFocus,\n    getInteractionType,\n    open,\n    restoreFocus,\n    shouldRetainClosedContent\n  ]);\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  const computedLabel = ariaLabel ?? label;\n  const computedLabelledBy =\n    ariaLabelledBy ?? (computedLabel ? undefined : context.titleIds.join(\" \") || undefined);\n  const computedDescribedBy = describe\n    ? (ariaDescribedBy ?? (context.descriptionIds.join(\" \") || undefined))\n    : undefined;\n  const state: DialogContentState = {\n    dismissible,\n    nested: context.nested,\n    nestedDialogOpen: context.nestedDialogOpen,\n    open: context.open,\n    side: side ?? null,\n    transitionStatus\n  };\n\n  const handleCancel = (event: SyntheticEvent<HTMLDialogElement>) => {\n    onCancel?.(event);\n    if (event.defaultPrevented) {\n      event.stopPropagation();\n      return;\n    }\n    event.preventDefault();\n    const escape = lastEscapeRef.current;\n    const fromEscape = escape != null && performance.now() - escape.time < 500;\n    const nativeEvent = fromEscape ? escape.event : event.nativeEvent;\n    const details = context.requestOpen(\n      false,\n      fromEscape ? \"escape-key\" : \"none\",\n      nativeEvent,\n      undefined,\n      fromEscape ? \"keyboard\" : interactionTypeFromEvent(nativeEvent)\n    );\n    lastEscapeRef.current = undefined;\n    if (!details?.isPropagationAllowed) {\n      event.stopPropagation();\n    }\n  };\n\n  const handleClose = (event: SyntheticEvent<HTMLDialogElement>) => {\n    // ponytail: capture the host synchronously — React nulls\n    // SyntheticEvent.currentTarget after dispatch, so reading it inside the\n    // rAF below crashed every external native close (method=dialog submit,\n    // programmatic dialog.close()). Same convention as _internal/focus-scope.\n    const dialog = event.currentTarget;\n    event.stopPropagation();\n    onClose?.(event);\n    if (internallyClosingRef.current) {\n      internallyClosingRef.current = false;\n      return;\n    }\n    const details = context.getOpen()\n      ? context.requestOpen(false, \"none\", event.nativeEvent)\n      : null;\n    requestAnimationFrame(() => {\n      if (context.getOpen() && !dialog.open && dialog.isConnected) {\n        dialog.showModal();\n        focusInitialElement(\n          dialog,\n          initialFocusRef.current,\n          context.getInteractionType()\n        );\n        return;\n      }\n      if (!context.getOpen()) {\n        context.completeOpenChange(false);\n        context.restoreFocus(dialog, finalFocusRef.current);\n        if (\n          !keepMountedRef.current &&\n          !details?.isUnmountPrevented &&\n          !context.shouldRetainClosedContent()\n        ) {\n          setRetained(false);\n        }\n      }\n    });\n  };\n\n  const handleKeyDown = composeHeidiEventHandlers(\n    onKeyDown,\n    (event: KeyboardEvent<HTMLDialogElement>) => {\n      if (event.key === \"Escape\") {\n        lastEscapeRef.current = {\n          event: event.nativeEvent,\n          time: performance.now()\n        };\n      }\n      containHeidiModalTabFocus(event);\n      if (context.nested) {\n        event.stopPropagation();\n      }\n    }\n  );\n\n  const handlePointerDown = composeHeidiEventHandlers(\n    onPointerDown,\n    (event: PointerEvent<HTMLDialogElement>) => {\n      if (!dismissible || !pointerIsOutsideDialog(event)) {\n        return;\n      }\n      const interactionType =\n        event.pointerType === \"touch\" || event.pointerType === \"pen\"\n          ? event.pointerType\n          : \"mouse\";\n      const details = context.requestOpen(\n        false,\n        \"outside-press\",\n        event.nativeEvent,\n        undefined,\n        interactionType\n      );\n      if (!details?.isPropagationAllowed) {\n        event.stopPropagation();\n      }\n    }\n  );\n\n  return renderHeidiElement({\n    className: DIALOG_CLASSES.content,\n    dataPart: \"dialog-content\",\n    element: \"dialog\",\n    props: {\n      ...nativeProps,\n      ...openStateAttributes(context.open),\n      ...transitionAttributes(transitionStatus),\n      \"aria-describedby\": computedDescribedBy,\n      \"aria-label\": computedLabel,\n      \"aria-labelledby\": computedLabelledBy,\n      children,\n      // Keep UA Escape close requests, but own pointer dismissal so controlled\n      // dialogs can reject it without a close/reopen flash.\n      closedby: \"closerequest\",\n      \"data-dismissible\": dismissible ? true : undefined,\n      \"data-nested\": context.nested ? true : undefined,\n      \"data-nested-dialog-open\": context.nestedDialogOpen ? true : undefined,\n      \"data-side\": side,\n      \"data-state\": dataState(context.open),\n      id: context.contentId,\n      onCancel: handleCancel,\n      onClose: handleClose,\n      onKeyDown: handleKeyDown,\n      onPointerDown: handlePointerDown,\n      ref: mergeHeidiRefs(dialogRef, ref),\n      tabIndex: -1\n    },\n    renderProps: { className, style },\n    state\n  });\n}\n\n/** Base UI calls this part Popup; Content remains the backwards-compatible name. */\nexport const DialogPopup = DialogContent;\nexport type DialogPopupProps = DialogContentProps;\nexport type DialogPopupState = DialogContentState;\n\nexport type DialogCloseState = { disabled: boolean };\n\ntype DialogCloseNativeProps = Omit<\n  NativeHostProps<\"button\">,\n  \"disabled\" | \"type\"\n>;\n\nexport type DialogCloseProps = HeidiIntrinsicHostProps<\n  DialogCloseState,\n  \"button\"\n> &\n  DialogCloseNativeProps & {\n    children?: ReactNode;\n    disabled?: boolean;\n    /** Set false only when render returns a non-button host. */\n    nativeButton?: boolean;\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function DialogClose({\n  children,\n  className,\n  disabled = false,\n  nativeButton = true,\n  onClick,\n  onKeyDown,\n  onKeyUp,\n  onPointerDown,\n  ref,\n  render,\n  style,\n  tabIndex,\n  ...nativeProps\n}: DialogCloseProps) {\n  const context = useDialogContext(\"Close\");\n  const pointerTypeRef = useRef<DialogInteractionType>(\"\");\n  const state = useMemo<DialogCloseState>(() => ({ disabled }), [disabled]);\n  const handlePointerDown = composeHeidiEventHandlers(\n    onPointerDown,\n    (event: PointerEvent<HTMLButtonElement>) => {\n      pointerTypeRef.current =\n        event.pointerType === \"touch\" || event.pointerType === \"pen\"\n          ? event.pointerType\n          : \"mouse\";\n    }\n  );\n  const handleClick = composeHeidiEventHandlers(\n    onClick,\n    (event: MouseEvent<HTMLButtonElement>) => {\n      if (disabled) {\n        event.preventDefault();\n        return;\n      }\n      const interactionType =\n        pointerTypeRef.current || interactionTypeFromEvent(event.nativeEvent);\n      pointerTypeRef.current = \"\";\n      const details = context.requestOpen(\n        false,\n        \"close-press\",\n        event.nativeEvent,\n        event.currentTarget,\n        interactionType\n      );\n      if (!details?.isPropagationAllowed) {\n        event.stopPropagation();\n      }\n    }\n  );\n  const handleKeyDown = composeHeidiEventHandlers(\n    onKeyDown,\n    (event: KeyboardEvent<HTMLButtonElement>) => {\n      if (nativeButton || event.target !== event.currentTarget) {\n        return;\n      }\n      if (event.key === \" \") {\n        event.preventDefault();\n        return;\n      }\n      if (event.key === \"Enter\") {\n        event.preventDefault();\n        if (!disabled && !event.repeat) {\n          event.currentTarget.click();\n        }\n      }\n    }\n  );\n  const handleKeyUp = composeHeidiEventHandlers(\n    onKeyUp,\n    (event: KeyboardEvent<HTMLButtonElement>) => {\n      if (\n        nativeButton ||\n        event.target !== event.currentTarget ||\n        event.key !== \" \"\n      ) {\n        return;\n      }\n      event.preventDefault();\n      if (!disabled) {\n        event.currentTarget.click();\n      }\n    }\n  );\n\n  return renderHeidiElement({\n    className: DIALOG_CLASSES.close,\n    dataPart: \"dialog-close\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      ...(nativeButton\n        ? nativeDisabledAttrs(disabled)\n        : ariaDisabledAttrs(disabled)),\n      children,\n      onClick: handleClick,\n      onKeyDown: handleKeyDown,\n      onKeyUp: handleKeyUp,\n      onPointerDown: handlePointerDown,\n      ref,\n      role: nativeButton ? undefined : \"button\",\n      tabIndex: nativeButton ? tabIndex : (tabIndex ?? 0),\n      type: nativeButton ? \"button\" : undefined\n    },\n    renderProps: { className, render, style },\n    state\n  });\n}\n\nexport const Dialog = {\n  Close: DialogClose,\n  Content: DialogContent,\n  Description: DialogDescription,\n  Popup: DialogPopup,\n  Root: DialogRoot,\n  Title: DialogTitle,\n  Trigger: DialogTrigger\n} as const;\n",
      "path": "packages/heidi-ui/src/dialog/dialog.tsx",
      "target": "components/ui/heidi/dialog/dialog.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui dialog — STRUCTURAL CSS only.\n * The native top layer owns modality; these rules guarantee viewport safety,\n * exact exit-presence wiring, scroll containment, and the edge-sheet layout.\n */\n\n@layer heidi-ui-base {\n  /* ponytail: native showModal() inerts the page for pointer and keyboard, but\n     the document scroller still answers the wheel, the trackpad and the\n     scrollbar — so the page slid behind an open modal. Each native-<dialog>\n     sheet declares only its own selector, which is sufficient because a\n     primitive never renders without its own stylesheet. Rejected: naming the\n     sibling's class here too, so \"the family\" is locked from either sheet — it\n     protects nothing reachable and violates the hui-<component>-<part> prefix\n     rule the classmap builder enforces. AlertDialog going unlocked was a\n     missing rule, not a missing cross-reference; the contract is what stops the\n     next modal forgetting. Also rejected: a JS body-scroll lock — it fights the\n     UA, needs scrollbar-width compensation, and does nothing during SSR.\n     APG modal-dialog: content outside the dialog is inert. */\n  html:has(.hui-dialog-content:modal) {\n    overflow: hidden;\n  }\n\n  .hui-dialog-content {\n    --_hui-dialog-viewport-gutter: 1rem;\n\n    box-sizing: border-box;\n    margin: auto;\n    max-block-size: calc(100dvb - var(--_hui-dialog-viewport-gutter));\n    max-inline-size: calc(100dvi - var(--_hui-dialog-viewport-gutter));\n    overflow: auto;\n    overscroll-behavior: contain;\n    scrollbar-gutter: stable;\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n\n  .hui-dialog-content[data-ending-style=\"true\"] {\n    pointer-events: none;\n  }\n\n  /* `left`/`right` are logical inline-start/inline-end sheet aliases so the\n     panel follows writing direction without adding a parallel RTL prop. */\n  .hui-dialog-content[data-side=\"left\"],\n  .hui-dialog-content[data-side=\"right\"] {\n    block-size: 100dvb;\n    inset-block: 0;\n    margin-block: 0;\n    margin-inline: 0;\n    max-block-size: 100dvb;\n    max-inline-size: 100dvi;\n  }\n\n  .hui-dialog-content[data-side=\"left\"] {\n    inset-inline: 0 auto;\n  }\n\n  .hui-dialog-content[data-side=\"right\"] {\n    inset-inline: auto 0;\n  }\n\n  .hui-dialog-content::backdrop {\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n}\n",
      "path": "packages/heidi-ui/src/dialog/dialog.base.css",
      "target": "components/ui/heidi/dialog/dialog.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui dialog — VISUAL theme (opt-in). Consumes only --hui-* semantic\n * theme variables supplied by the selected adapter.\n */\n\n@layer heidi-ui {\n  /*\n   * ponytail: the app CSS transform rewrites :dir(rtl) into language\n   * selectors. Direction-bound custom properties inherit from the nearest\n   * authored dir boundary and keep logical sheet motion correct.\n  */\n  :where([dir=\"ltr\"]) {\n    --_hui-dialog-inline-end-translate: 100%;\n    --_hui-dialog-inline-start-translate: -100%;\n  }\n\n  :where([dir=\"rtl\"]) {\n    --_hui-dialog-inline-end-translate: -100%;\n    --_hui-dialog-inline-start-translate: 100%;\n  }\n\n  .hui-dialog-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-dialog-trigger:disabled,\n  .hui-dialog-trigger[data-disabled=\"true\"] {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  .hui-dialog-trigger:focus-visible,\n  .hui-dialog-close: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  .hui-dialog-trigger[data-popup-open=\"true\"] {\n    background: var(--hui-color-interactive-ghost-bg-hover);\n    color: var(--hui-color-fg-strong);\n  }\n\n  /*\n   * ponytail: bg-RAISED, not bg-elevated. The two agree in light (both pure\n   * white) but invert in dark — elevated is neutral-900 (L 20.5) while raised\n   * is neutral-800 (L 24.5). A modal owns the top layer, so on elevated it\n   * rendered a rung BELOW the menus and popovers that open on top of it, and\n   * level with bg-subtle. border is 0 for the same reason menu/popover set it:\n   * the surface ladder's first stop already draws the 1px hairline, so a CSS\n   * border stacked a second one only on the dialog family.\n  */\n  .hui-dialog-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-xl);\n    color: var(--hui-color-fg-default);\n    /* Viewport safety lives in dialog.base.css via the shared gutter clamp. */\n    inline-size: 32rem;\n    opacity: 0;\n    padding: var(--hui-space-5);\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-dialog-content[data-state=\"open\"] {\n    opacity: 1;\n    transform: none;\n  }\n\n  .hui-dialog-content[data-state=\"closed\"] {\n    opacity: 0;\n  }\n\n  .hui-dialog-content[data-starting-style=\"true\"],\n  .hui-dialog-content[data-ending-style=\"true\"] {\n    will-change: opacity, transform;\n  }\n\n  .hui-dialog-content[data-side=\"left\"],\n  .hui-dialog-content[data-side=\"right\"] {\n    border-radius: 0;\n    display: flex;\n    flex-direction: column;\n    inline-size: min(30rem, 100dvi);\n    opacity: 1;\n  }\n\n  .hui-dialog-content[data-side=\"left\"] {\n    transform: translateX(\n      var(--_hui-dialog-inline-start-translate, -100%)\n    );\n  }\n\n  .hui-dialog-content[data-side=\"right\"] {\n    transform: translateX(\n      var(--_hui-dialog-inline-end-translate, 100%)\n    );\n  }\n\n  .hui-dialog-content[data-side=\"left\"][data-state=\"open\"],\n  .hui-dialog-content[data-side=\"right\"][data-state=\"open\"] {\n    transform: none;\n  }\n\n  .hui-dialog-content::backdrop {\n    background: var(--hui-color-overlay);\n    opacity: 0;\n    transition-duration: var(--hui-duration-fast);\n    transition-property: display, opacity, overlay;\n    transition-timing-function: var(--hui-ease-out);\n  }\n\n  .hui-dialog-content[data-state=\"open\"]::backdrop {\n    opacity: 1;\n  }\n\n  @starting-style {\n    .hui-dialog-content[data-state=\"open\"] {\n      opacity: 0;\n      transform: scale(0.97);\n    }\n\n    .hui-dialog-content[data-side=\"left\"][data-state=\"open\"] {\n      opacity: 1;\n      transform: translateX(\n        var(--_hui-dialog-inline-start-translate, -100%)\n      );\n    }\n\n    .hui-dialog-content[data-side=\"right\"][data-state=\"open\"] {\n      opacity: 1;\n      transform: translateX(\n        var(--_hui-dialog-inline-end-translate, 100%)\n      );\n    }\n\n    .hui-dialog-content[data-state=\"open\"]::backdrop {\n      opacity: 0;\n    }\n  }\n\n  .hui-dialog-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-2);\n  }\n\n  .hui-dialog-title:focus-visible {\n    outline: none;\n  }\n\n  .hui-dialog-description {\n    color: var(--hui-color-fg-muted);\n    font-size: var(--hui-text-body-size);\n    margin: 0 0 var(--hui-space-4);\n  }\n\n  .hui-dialog-close {\n    background: transparent;\n    border: var(--hui-border-width) var(--hui-border-style) var(--hui-color-border-default);\n    border-radius: var(--hui-radius-sm);\n    color: var(--hui-color-fg-muted);\n    cursor: pointer;\n    font: inherit;\n    min-block-size: calc(var(--hui-space-3) * 2);\n    padding: var(--hui-space-0-5) var(--hui-space-2);\n  }\n\n  .hui-dialog-close:disabled,\n  .hui-dialog-close[data-disabled=\"true\"] {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .hui-dialog-content,\n    .hui-dialog-content::backdrop {\n      transition-duration: 0s;\n    }\n  }\n\n  @media (forced-colors: active) {\n    /* The base rule ships border: 0 because the surface ladder draws the\n       hairline; forced colors erase shadows, so restore a real edge here. */\n    .hui-dialog-content {\n      border: var(--hui-border-width) solid CanvasText;\n    }\n\n    .hui-dialog-content::backdrop {\n      background: Canvas;\n      opacity: 0.72;\n    }\n\n    .hui-dialog-trigger,\n    .hui-dialog-close {\n      border-color: CanvasText;\n    }\n\n    .hui-dialog-trigger:focus-visible,\n    .hui-dialog-close:focus-visible {\n      outline-color: Highlight;\n    }\n\n    .hui-dialog-trigger:disabled,\n    .hui-dialog-trigger[data-disabled=\"true\"],\n    .hui-dialog-close:disabled,\n    .hui-dialog-close[data-disabled=\"true\"] {\n      border-color: GrayText;\n      color: GrayText;\n      opacity: 1;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/dialog/dialog.theme.css",
      "target": "components/ui/heidi/dialog/dialog.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui dialog — aggregator (base + Heidi adapter + theme).\n * Headless: import dialog.base.css only.\n * Themed: import this file (or heidi-ui/styles.css).\n */\n\n@import \"./dialog.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./dialog.theme.css\";\n",
      "path": "packages/heidi-ui/src/dialog/dialog.css",
      "target": "components/ui/heidi/dialog/dialog.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/dialog/dialog.base.css + packages/heidi-ui/src/dialog/dialog.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const DIALOG_CLASSES = {\n  close: \"hui-dialog-close\",\n  content: \"hui-dialog-content\",\n  description: \"hui-dialog-description\",\n  title: \"hui-dialog-title\",\n  trigger: \"hui-dialog-trigger\",\n} as const;\n",
      "path": "packages/heidi-ui/src/dialog/dialog.classes.generated.ts",
      "target": "components/ui/heidi/dialog/dialog.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from dialog.anatomy.json + dialog.base.css + dialog.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type DialogDisabled = \"true\";\nexport type DialogEndingStyle = \"true\";\nexport type DialogPopupOpen = \"true\";\nexport type DialogSide = \"left\" | \"right\";\nexport type DialogStartingStyle = \"true\";\nexport type DialogState = \"closed\" | \"open\";\n\nexport const DIALOG_ANATOMY = {\n  \"component\": \"dialog\",\n  \"description\": \"Base-UI-shaped modal state machine on native <dialog>. Controlled state is authoritative and cancellable; Content/Popup is lazy by default, retains exit motion, reconciles native close paths, isolates nested dialogs, wraps APG Tab focus, restores the actual invoker, and stays viewport-contained. Pointer dismissal is owned in JavaScript so a controlled consumer can reject it without a native close/reopen flash.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"close\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-disabled\",\n          \"aria-label\"\n        ],\n        \"role\": null\n      },\n      \"class\": \"hui-dialog-close\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-close\",\n      \"description\": \"Native button by default; requests a cancellable close-press state change and supports safe non-button composition.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"nativeButton\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ]\n      }\n    },\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-describedby\",\n          \"aria-label\",\n          \"aria-labelledby\"\n        ],\n        \"role\": \"dialog\"\n      },\n      \"class\": \"hui-dialog-content\",\n      \"css\": {\n        \"structural\": [\n          \"block-size\",\n          \"box-sizing\",\n          \"inset-block\",\n          \"inset-inline\",\n          \"margin\",\n          \"margin-block\",\n          \"margin-inline\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"overscroll-behavior\",\n          \"pointer-events\",\n          \"scrollbar-gutter\",\n          \"transition-behavior\",\n          \"transition-property\"\n        ]\n      },\n      \"dataPart\": \"dialog-content\",\n      \"description\": \"Native-only <dialog>, also exported as Dialog.Popup. Lazily mounts, animates in and out on the top layer, supports explicit initial/final focus, and prevents overflow in centered and logical inline-start/inline-end sheet layouts.\",\n      \"element\": \"dialog\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\n        \"className\",\n        \"describe\",\n        \"dismissible\",\n        \"finalFocus\",\n        \"initialFocus\",\n        \"keepMounted\",\n        \"label\",\n        \"onCancel\",\n        \"onClose\",\n        \"onKeyDown\",\n        \"onPointerDown\",\n        \"ref\",\n        \"side\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":modal\",\n        \"::backdrop\"\n      ],\n      \"states\": {\n        \"data-ending-style\": [\n          \"true\"\n        ],\n        \"data-side\": [\n          \"left\",\n          \"right\"\n        ],\n        \"data-starting-style\": [\n          \"true\"\n        ],\n        \"data-state\": [\n          \"closed\",\n          \"open\"\n        ]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-dialog-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-description\",\n      \"description\": \"Optional supporting text registered without a dangling aria-describedby reference; multiple descriptions are combined in DOM order.\",\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-dialog-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-title\",\n      \"description\": \"Heading registered as the dialog name and available as the APG static focus fallback for long or non-interactive content.\",\n      \"element\": \"h2\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"id\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"tabIndex\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"trigger\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-controls\",\n          \"aria-disabled\",\n          \"aria-expanded\",\n          \"aria-haspopup\"\n        ],\n        \"role\": null\n      },\n      \"class\": \"hui-dialog-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-trigger\",\n      \"description\": \"Native button by default; supports multiple invokers, tracks the active trigger, and safely composes to a non-button host.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"id\",\n        \"nativeButton\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-popup-open\": [\n          \"true\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultOpen\",\n    \"defaultTriggerId\",\n    \"disablePointerDismissal\",\n    \"onOpenChange\",\n    \"onOpenChangeComplete\",\n    \"open\",\n    \"triggerId\"\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-fg-strong\",\n    \"--hui-color-focus-ring\",\n    \"--hui-color-interactive-ghost-bg-hover\",\n    \"--hui-color-overlay\",\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-radius-sm\",\n    \"--hui-shadow-surface-xl\",\n    \"--hui-space-0-5\",\n    \"--hui-space-1-5\",\n    \"--hui-space-2\",\n    \"--hui-space-3\",\n    \"--hui-space-4\",\n    \"--hui-space-5\",\n    \"--hui-text-body-size\",\n    \"--hui-text-heading-3-size\"\n  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/dialog/dialog.anatomy.generated.ts",
      "target": "components/ui/heidi/dialog/dialog.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"dialog\",\n  \"description\": \"Base-UI-shaped modal state machine on native <dialog>. Controlled state is authoritative and cancellable; Content/Popup is lazy by default, retains exit motion, reconciles native close paths, isolates nested dialogs, wraps APG Tab focus, restores the actual invoker, and stays viewport-contained. Pointer dismissal is owned in JavaScript so a controlled consumer can reject it without a native close/reopen flash.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"close\": {\n      \"aria\": {\n        \"owns\": [\"aria-disabled\", \"aria-label\"],\n        \"role\": null\n      },\n      \"class\": \"hui-dialog-close\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-close\",\n      \"description\": \"Native button by default; requests a cancellable close-press state change and supports safe non-button composition.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"nativeButton\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\":disabled\", \":focus-visible\"],\n      \"states\": {\n        \"data-disabled\": [\"true\"]\n      }\n    },\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\"aria-describedby\", \"aria-label\", \"aria-labelledby\"],\n        \"role\": \"dialog\"\n      },\n      \"class\": \"hui-dialog-content\",\n      \"css\": {\n        \"structural\": [\n          \"block-size\",\n          \"box-sizing\",\n          \"inset-block\",\n          \"inset-inline\",\n          \"margin\",\n          \"margin-block\",\n          \"margin-inline\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"overscroll-behavior\",\n          \"pointer-events\",\n          \"scrollbar-gutter\",\n          \"transition-behavior\",\n          \"transition-property\"\n        ]\n      },\n      \"dataPart\": \"dialog-content\",\n      \"description\": \"Native-only <dialog>, also exported as Dialog.Popup. Lazily mounts, animates in and out on the top layer, supports explicit initial/final focus, and prevents overflow in centered and logical inline-start/inline-end sheet layouts.\",\n      \"element\": \"dialog\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\n        \"className\",\n        \"describe\",\n        \"dismissible\",\n        \"finalFocus\",\n        \"initialFocus\",\n        \"keepMounted\",\n        \"label\",\n        \"onCancel\",\n        \"onClose\",\n        \"onKeyDown\",\n        \"onPointerDown\",\n        \"ref\",\n        \"side\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\":modal\", \"::backdrop\"],\n      \"states\": {\n        \"data-ending-style\": [\"true\"],\n        \"data-side\": [\"left\", \"right\"],\n        \"data-starting-style\": [\"true\"],\n        \"data-state\": [\"closed\", \"open\"]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-dialog-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-description\",\n      \"description\": \"Optional supporting text registered without a dangling aria-describedby reference; multiple descriptions are combined in DOM order.\",\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-dialog-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-title\",\n      \"description\": \"Heading registered as the dialog name and available as the APG static focus fallback for long or non-interactive content.\",\n      \"element\": \"h2\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"id\", \"ref\", \"render\", \"style\", \"tabIndex\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"trigger\": {\n      \"aria\": {\n        \"owns\": [\"aria-controls\", \"aria-disabled\", \"aria-expanded\", \"aria-haspopup\"],\n        \"role\": null\n      },\n      \"class\": \"hui-dialog-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"dialog-trigger\",\n      \"description\": \"Native button by default; supports multiple invokers, tracks the active trigger, and safely composes to a non-button host.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"id\",\n        \"nativeButton\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\":disabled\", \":focus-visible\"],\n      \"states\": {\n        \"data-disabled\": [\"true\"],\n        \"data-popup-open\": [\"true\"]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultOpen\",\n    \"defaultTriggerId\",\n    \"disablePointerDismissal\",\n    \"onOpenChange\",\n    \"onOpenChangeComplete\",\n    \"open\",\n    \"triggerId\"\n  ]\n}\n",
      "path": "packages/heidi-ui/src/dialog/dialog.anatomy.json",
      "target": "components/ui/heidi/dialog/dialog.anatomy.json",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared Web-Animations settling helpers (P6).\n *\n * Dialog, HoverCard and Collapsible each carried a byte-identical async\n * \"wait for this element's animations, but never forever\" routine, and all\n * four popover-family components carried their own `finiteAnimationDuration`.\n *\n * ponytail: the audit read this as one helper duplicated four times and\n * prescribed a single `waitForElementAnimations` covering \"the four call-site\n * variants\". Diffing them first — which that same entry insisted on — shows\n * that is not the shape. THREE are the same async routine differing only in\n * two flags. Menu's `afterPopoverAnimations` is a different algorithm: it is\n * callback-shaped rather than awaited, returns a canceller, subscribes to\n * `finish`/`cancel` events instead of racing `animation.finished`, and falls\n * back to computed CSS on engines without `getAnimations`. Forcing it into the\n * awaited shape would have rewritten working popover-dismissal logic to make a\n * count go from four to one. So Menu keeps its own routine and shares only the\n * duration helper it genuinely had in common.\n *\n * The duration helper resolves to MENU's copy, which was the strict one: the\n * other three returned `timing.endTime` raw, so a non-finite or negative\n * `endTime` (reachable via a negative `endDelay`) propagated into\n * `setTimeout` — and `setTimeout(fn, NaN)` fires at 0ms, collapsing the wait\n * it was supposed to bound. Same class as the non-finite bounds that reached\n * Slider's and Progress's ARIA.\n */\n\n/** Ceiling on any settle wait. A stuck animation must not strand a component. */\nexport const MAX_ANIMATION_WAIT_MS = 30_000;\n\n/** Grace added to the longest observed animation before the wait gives up. */\nexport const ANIMATION_WAIT_GRACE_MS = 250;\n\nexport function finiteAnimationDuration(\n  animation: Animation\n): number | undefined {\n  const timing = animation.effect?.getComputedTiming();\n  if (\n    !timing ||\n    timing.iterations === Infinity ||\n    typeof timing.endTime !== \"number\" ||\n    !Number.isFinite(timing.endTime)\n  ) {\n    return undefined;\n  }\n  return Math.max(0, timing.endTime);\n}\n\nexport async function afterNextPaint(): Promise<void> {\n  await new Promise<void>((resolve) => {\n    requestAnimationFrame(() => requestAnimationFrame(() => resolve()));\n  });\n}\n\n/** Animations worth waiting on: real, still running, and finitely long. */\nexport function pendingAnimations(\n  element: Element,\n  subtree: boolean\n): Array<{ animation: Animation; duration: number }> {\n  // Embedded and older engines can omit the Web Animations inspection API.\n  // In that no-observer path the state must still settle instead of throwing.\n  if (typeof element.getAnimations !== \"function\") {\n    return [];\n  }\n  return element\n    .getAnimations(subtree ? { subtree: true } : undefined)\n    .map((animation) => ({\n      animation,\n      duration: finiteAnimationDuration(animation)\n    }))\n    .filter(\n      (entry): entry is { animation: Animation; duration: number } =>\n        entry.duration !== undefined &&\n        entry.duration > 0 &&\n        entry.animation.playState !== \"finished\" &&\n        entry.animation.playState !== \"idle\"\n    );\n}\n\nexport type WaitForElementAnimationsOptions = {\n  /**\n   * Wait two frames before inspecting. Dialog and HoverCard need this: they\n   * ask immediately after a state flip, before the engine has started the\n   * animations they mean to wait for. Collapsible asks after the fact and\n   * must NOT gain the extra frames — that would be an observable timing\n   * change, and this slice does not make those.\n   */\n  awaitPaint?: boolean;\n  /** Inspect descendants too — for panels whose motion lives on children. */\n  subtree?: boolean;\n};\n\nexport async function waitForElementAnimations(\n  element: Element,\n  { awaitPaint = false, subtree = false }: WaitForElementAnimationsOptions = {}\n): Promise<void> {\n  if (awaitPaint) {\n    await afterNextPaint();\n    // ponytail: the connectedness check is deliberately INSIDE the awaitPaint\n    // branch rather than unconditional. It is not a general safety net — it\n    // exists because those two frames are a window in which the element can be\n    // torn down, and it was present in exactly the two copies that wait. The\n    // non-waiting caller has no such window: nothing can run between its own\n    // check and this call, so hoisting the guard would be adding behaviour to\n    // Collapsible in a slice whose whole premise is changing none.\n    if (!element.isConnected) {\n      return;\n    }\n  }\n  const animations = pendingAnimations(element, subtree);\n  if (animations.length === 0) {\n    return;\n  }\n  const maximum = Math.min(\n    MAX_ANIMATION_WAIT_MS,\n    Math.max(...animations.map(({ duration }) => duration)) +\n      ANIMATION_WAIT_GRACE_MS\n  );\n  let timeout = 0;\n  await Promise.race([\n    Promise.allSettled(animations.map(({ animation }) => animation.finished)),\n    new Promise<void>((resolve) => {\n      timeout = window.setTimeout(resolve, maximum);\n    })\n  ]);\n  window.clearTimeout(timeout);\n}\n",
      "path": "packages/heidi-ui/src/_internal/animation-wait.ts",
      "target": "components/ui/heidi/_internal/animation-wait.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Cross-cutting disabled attrs for heidi-ui interactive hosts.\n *\n * House convention (Phase 23):\n * - Button widgets → native `disabled` + `data-disabled=\"true\"` (CSS + a11y).\n * - Non-button options (Select.Item) → `aria-disabled` + `data-disabled=\"true\"`\n *   (native disabled is invalid on role=option divs).\n * - Group roots may cascade `disabled` to children and mirror `data-disabled`.\n * - Keyboard nav / activation always skip disabled hosts.\n */\n\nexport type NativeDisabledAttrs = {\n  \"data-disabled\"?: true;\n  disabled?: true;\n};\n\nexport type AriaDisabledAttrs = {\n  \"aria-disabled\"?: true;\n  \"data-disabled\"?: true;\n};\n\nexport type DataDisabledAttrs = {\n  \"data-disabled\"?: true;\n};\n\n/** Native button/input disabled + styling hook. */\nexport function nativeDisabledAttrs(disabled: boolean | undefined): NativeDisabledAttrs {\n  if (!disabled) {\n    return {};\n  }\n  return {\n    \"data-disabled\": true,\n    disabled: true\n  };\n}\n\n/** ARIA-disabled for non-native hosts (e.g. role=option). */\nexport function ariaDisabledAttrs(disabled: boolean | undefined): AriaDisabledAttrs {\n  if (!disabled) {\n    return {};\n  }\n  return {\n    \"aria-disabled\": true,\n    \"data-disabled\": true\n  };\n}\n\n/** Styling hook only (group roots that cascade disabled). */\nexport function dataDisabledAttrs(disabled: boolean | undefined): DataDisabledAttrs {\n  if (!disabled) {\n    return {};\n  }\n  return {\n    \"data-disabled\": true\n  };\n}\n\nexport function isDisabledElement(el: Element | null | undefined): boolean {\n  if (!el || !(el instanceof HTMLElement)) {\n    return false;\n  }\n  if (el.matches(\":disabled\")) {\n    return true;\n  }\n  return el.getAttribute(\"aria-disabled\") === \"true\" || el.getAttribute(\"data-disabled\") === \"true\";\n}\n",
      "path": "packages/heidi-ui/src/_internal/disabled.ts",
      "target": "components/ui/heidi/_internal/disabled.ts",
      "type": "registry:ui"
    },
    {
      "content": "import type { KeyboardEvent as ReactKeyboardEvent } from \"react\";\n\nconst TABBABLE_SELECTOR = [\n  \"a[href]\",\n  \"area[href]\",\n  \"audio[controls]\",\n  \"button\",\n  \"details > summary:first-of-type\",\n  \"embed\",\n  \"iframe\",\n  \"input\",\n  \"object\",\n  \"select\",\n  \"textarea\",\n  \"video[controls]\",\n  \"[contenteditable]:not([contenteditable='false'])\",\n  \"[tabindex]\"\n].join(\",\");\n\nfunction isActuallyTabbable(element: HTMLElement): boolean {\n  if (\n    element.tabIndex < 0 ||\n    element.closest(\"[hidden], [inert], [aria-hidden='true']\") !== null\n  ) {\n    return false;\n  }\n  if (\n    (element instanceof HTMLButtonElement ||\n      element instanceof HTMLInputElement ||\n      element instanceof HTMLSelectElement ||\n      element instanceof HTMLTextAreaElement) &&\n    (element.disabled || element.matches(\":disabled\"))\n  ) {\n    return false;\n  }\n  if (element instanceof HTMLInputElement && element.type === \"hidden\") {\n    return false;\n  }\n  const style = getComputedStyle(element);\n  return style.display !== \"none\" && style.visibility !== \"hidden\" && element.getClientRects().length > 0;\n}\n\nfunction isActiveRadio(element: HTMLElement, candidates: HTMLElement[]): boolean {\n  if (!(element instanceof HTMLInputElement) || element.type !== \"radio\" || !element.name) {\n    return true;\n  }\n  const group = candidates.filter(\n    (candidate): candidate is HTMLInputElement =>\n      candidate instanceof HTMLInputElement &&\n      candidate.type === \"radio\" &&\n      candidate.name === element.name &&\n      candidate.form === element.form\n  );\n  const checked = group.find((radio) => radio.checked);\n  return checked ? checked === element : group[0] === element;\n}\n\n/** Return the elements reached by sequential keyboard focus in browser order. */\nexport function getHeidiTabbableElements(container: HTMLElement): HTMLElement[] {\n  const candidates = Array.from(\n    container.querySelectorAll<HTMLElement>(TABBABLE_SELECTOR)\n  ).filter(isActuallyTabbable);\n\n  return candidates\n    .filter((element) => isActiveRadio(element, candidates))\n    .map((element, index) => ({ element, index }))\n    .sort((left, right) => {\n      const leftOrder = left.element.tabIndex > 0 ? left.element.tabIndex : Number.MAX_SAFE_INTEGER;\n      const rightOrder = right.element.tabIndex > 0 ? right.element.tabIndex : Number.MAX_SAFE_INTEGER;\n      return leftOrder - rightOrder || left.index - right.index;\n    })\n    .map(({ element }) => element);\n}\n\n/**\n * Restore focus after the browser has settled a top-layer transition.\n *\n * Callers must pass the host captured synchronously from a React event:\n * React clears `SyntheticEvent.currentTarget` after listener dispatch.\n */\nexport function focusHeidiElementNextFrame(element: HTMLElement): void {\n  element.ownerDocument.defaultView?.requestAnimationFrame(() => {\n    if (element.isConnected) {\n      element.focus({ preventScroll: true });\n    }\n  });\n}\n\n/**\n * Keep sequential Tab navigation inside a modal. The native dialog top layer\n * makes the rest of the document inert, but Chromium can still place focus on\n * `body` for one keystroke at either edge; APG requires an immediate wrap.\n */\nexport function containHeidiModalTabFocus(\n  event: ReactKeyboardEvent<HTMLElement>\n): void {\n  if (\n    event.key !== \"Tab\" ||\n    event.defaultPrevented ||\n    event.altKey ||\n    event.ctrlKey ||\n    event.metaKey\n  ) {\n    return;\n  }\n\n  const container = event.currentTarget;\n  const tabbables = getHeidiTabbableElements(container);\n  const active = document.activeElement;\n  const first = tabbables[0];\n  const last = tabbables.at(-1);\n\n  if (!first || !last) {\n    event.preventDefault();\n    container\n      .querySelector<HTMLElement>(\"[data-hui-focus-fallback]\")\n      ?.focus({ preventScroll: true });\n    return;\n  }\n\n  if (event.shiftKey) {\n    if (active === first || !(active instanceof Node) || !container.contains(active)) {\n      event.preventDefault();\n      last.focus();\n    }\n    return;\n  }\n\n  if (active === last || active === container || !(active instanceof Node) || !container.contains(active)) {\n    event.preventDefault();\n    first.focus();\n  }\n}\n",
      "path": "packages/heidi-ui/src/_internal/focus-scope.ts",
      "target": "components/ui/heidi/_internal/focus-scope.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": "dialog",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Dialog",
  "type": "registry:ui"
}
