{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "APG alertdialog on a native <dialog> shell. Root is the single source of truth for controlled and native open state; Title + Description are required and wired on first render; backdrop dismissal is disabled; Escape requests close; focus starts on Cancel, wraps inside the modal, and returns to the invoker. Native host props and safe composition are supported.",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui AlertDialog — APG alertdialog on the native <dialog> shell\n * (docs/HEIDI-UI-HEADLESS.md § P4 / Slice G). role=alertdialog, required\n * Title + Description, closedby=closerequest (no backdrop dismiss; Escape\n * still closes). Action + Cancel. Initial focus prefers Cancel.\n *\n * Accessible name (required): render <AlertDialog.Title>, pass `label`, or set\n * aria-label/aria-labelledby on Content — the naming IDREFs are emitted only\n * when the part they point at is actually mounted. `initialFocus`/`finalFocus`\n * override the default focus targets; `finalFocus` is the escape hatch for the\n * defining case where the confirm's own invoker is removed by the action it\n * confirmed.\n *\n * RSC rule: named exports from Server Components; AlertDialog.X is client 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 KeyboardEvent,\n  type MouseEvent,\n  type PointerEvent,\n  type ReactNode,\n  type Ref,\n  type RefObject,\n  type SyntheticEvent\n} from \"react\";\nimport { ariaDisabledAttrs, nativeDisabledAttrs } from \"../_internal/disabled\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { containHeidiModalTabFocus } from \"../_internal/focus-scope\";\nimport { safeId } from \"../_internal/safe-id\";\nimport { useHeidiLayoutEffect } from \"../_internal/use-layout-effect\";\nimport { ALERT_DIALOG_CLASSES } from \"./alert-dialog.classes.generated\";\n\nexport type AlertDialogInteractionType =\n  | \"\"\n  | \"keyboard\"\n  | \"mouse\"\n  | \"pen\"\n  | \"touch\";\n\n/**\n * Same shape Dialog uses: `true`/undefined keeps Heidi's default target,\n * `false` skips focus entirely, a ref or a function picks an element.\n */\nexport type AlertDialogFocusTarget =\n  | boolean\n  | RefObject<HTMLElement | null>\n  | ((\n      interactionType: AlertDialogInteractionType\n    ) => boolean | HTMLElement | null | undefined | void);\n\ntype FocusDirective =\n  | { kind: \"default\" }\n  | { kind: \"skip\" }\n  | { kind: \"target\"; target: HTMLElement };\n\ntype AlertDialogFocusOverrides = {\n  finalFocus?: AlertDialogFocusTarget;\n  initialFocus?: AlertDialogFocusTarget;\n};\n\nfunction resolveFocusDirective(\n  value: AlertDialogFocusTarget | undefined,\n  interactionType: AlertDialogInteractionType\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\nfunction interactionTypeFromEvent(event: Event): AlertDialogInteractionType {\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 (\"detail\" in event && typeof event.detail === \"number\") {\n    return event.detail === 0 ? \"keyboard\" : \"mouse\";\n  }\n  return \"\";\n}\n\ntype AlertDialogContextValue = {\n  cancelRef: { current: HTMLButtonElement | null };\n  contentId: string;\n  descriptionCount: number;\n  descriptionId: string;\n  dialogRef: { current: HTMLDialogElement | null };\n  focusOverridesRef: { current: AlertDialogFocusOverrides };\n  handleNativeClose: (dialog: HTMLDialogElement) => void;\n  mounted: boolean;\n  nested: boolean;\n  open: boolean;\n  registerDescription: () => () => void;\n  registerTitle: () => () => void;\n  rememberReturnFocus: (\n    element: HTMLElement,\n    interactionType: AlertDialogInteractionType\n  ) => void;\n  setOpen: (open: boolean) => void;\n  titleCount: number;\n  titleId: string;\n};\n\nconst AlertDialogContext = createContext<AlertDialogContextValue | null>(null);\n\nfunction useAlertDialogContext(part: string): AlertDialogContextValue {\n  const context = useContext(AlertDialogContext);\n  if (!context) {\n    throw new Error(`AlertDialog.${part} must be rendered inside AlertDialog.Root.`);\n  }\n  return context;\n}\n\nexport type AlertDialogRootProps = {\n  children?: ReactNode;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n};\n\nexport function AlertDialogRoot({\n  children,\n  defaultOpen = false,\n  onOpenChange,\n  open: openProp\n}: AlertDialogRootProps) {\n  const nested = useContext(AlertDialogContext) !== null;\n  const id = useId();\n  const cancelRef = useRef<HTMLButtonElement | null>(null);\n  const dialogRef = useRef<HTMLDialogElement | null>(null);\n  const focusOverridesRef = useRef<AlertDialogFocusOverrides>({});\n  const interactionTypeRef = useRef<AlertDialogInteractionType>(\"\");\n  const returnFocusRef = useRef<HTMLElement | null>(null);\n  const returnFocusIdRef = useRef<string | null>(null);\n  const [descriptionCount, setDescriptionCount] = useState(0);\n  const [titleCount, setTitleCount] = useState(0);\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const controlled = openProp !== undefined;\n  const open = openProp ?? internalOpen;\n  const hasOpenedRef = useRef(defaultOpen || openProp === true);\n  if (open) {\n    hasOpenedRef.current = true;\n  }\n  const mounted = open || hasOpenedRef.current;\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\n  const rememberReturnFocus = useCallback(\n    (element: HTMLElement, interactionType: AlertDialogInteractionType) => {\n      returnFocusRef.current = element;\n      returnFocusIdRef.current = element.id || null;\n      interactionTypeRef.current = interactionType;\n    },\n    []\n  );\n\n  const ensureReturnFocus = useCallback(() => {\n    if (returnFocusRef.current?.isConnected) {\n      return;\n    }\n    const active = document.activeElement;\n    returnFocusRef.current = active instanceof HTMLElement ? active : null;\n    returnFocusIdRef.current = returnFocusRef.current?.id || null;\n  }, []);\n\n  const registerDescription = useCallback(() => {\n    setDescriptionCount((count) => count + 1);\n    return () => setDescriptionCount((count) => Math.max(0, count - 1));\n  }, []);\n  const registerTitle = useCallback(() => {\n    setTitleCount((count) => count + 1);\n    return () => setTitleCount((count) => Math.max(0, count - 1));\n  }, []);\n\n  const focusInitialElement = useCallback((dialog: HTMLDialogElement) => {\n    const directive = resolveFocusDirective(\n      focusOverridesRef.current.initialFocus,\n      interactionTypeRef.current\n    );\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    const cancel = cancelRef.current;\n    const title = dialog.querySelector<HTMLElement>(\"[data-hui-focus-fallback]\");\n    const contentOverflows = dialog.scrollHeight > dialog.clientHeight + 1;\n    // A non-native Cancel host carries aria-disabled instead of :disabled.\n    const cancelDisabled =\n      cancel?.disabled === true ||\n      cancel?.getAttribute(\"aria-disabled\") === \"true\";\n    if (cancel && !cancelDisabled && !contentOverflows) {\n      cancel.focus({ preventScroll: true });\n      return;\n    }\n    title?.focus({ preventScroll: true });\n  }, []);\n\n  const showModal = useCallback(\n    (dialog: HTMLDialogElement) => {\n      dialog.showModal();\n      focusInitialElement(dialog);\n    },\n    [focusInitialElement]\n  );\n\n  /*\n   * ponytail: the defining AlertDialog case is a confirm whose own invoker is\n   * destroyed by the action it confirmed (\"Delete project\"), and the previous\n   * implementation then left focus on <body> — a screen-reader user is dumped\n   * at the top of the page, which APG names explicitly: \"focus returns to the\n   * element that invoked the dialog unless the invoking element no longer\n   * exists ... In those cases, focus is set on another element that provides a\n   * logical work flow.\" So there are now three tiers: the consumer's\n   * `finalFocus`, the remembered node, then the same node re-resolved by id\n   * (React can replace the trigger's DOM node while the dialog is open).\n   * Resolution goes through `dialog.ownerDocument`, not the global `document`,\n   * so a dialog rendered into another window/iframe document still finds its\n   * own trigger. Rejected: focusing document.body with a temporary tabindex —\n   * it announces nothing and leaves a stray focusable node behind.\n   */\n  const restoreFocus = useCallback((dialog: HTMLDialogElement) => {\n    const active = document.activeElement;\n    const remembered = returnFocusRef.current;\n    const associated = returnFocusIdRef.current\n      ? dialog.ownerDocument.getElementById(returnFocusIdRef.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        focusOverridesRef.current.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    returnFocusIdRef.current = null;\n    interactionTypeRef.current = \"\";\n  }, []);\n\n  const handleNativeClose = useCallback(\n    (dialog: HTMLDialogElement) => {\n      if (openRef.current) {\n        setOpen(false);\n      }\n      requestAnimationFrame(() => {\n        if (openRef.current) {\n          if (!dialog.open && dialog.isConnected) {\n            showModal(dialog);\n          }\n          return;\n        }\n        restoreFocus(dialog);\n      });\n    },\n    [restoreFocus, setOpen, showModal]\n  );\n\n  const value = useMemo<AlertDialogContextValue>(() => {\n    const safe = safeId(id);\n    return {\n      cancelRef,\n      contentId: `hui-alert-dialog-${safe}`,\n      descriptionCount,\n      descriptionId: `hui-alert-dialog-description-${safe}`,\n      dialogRef,\n      focusOverridesRef,\n      handleNativeClose,\n      mounted,\n      nested,\n      open,\n      registerDescription,\n      registerTitle,\n      rememberReturnFocus,\n      setOpen,\n      titleCount,\n      titleId: `hui-alert-dialog-title-${safe}`\n    };\n  }, [\n    descriptionCount,\n    handleNativeClose,\n    id,\n    mounted,\n    nested,\n    open,\n    registerDescription,\n    registerTitle,\n    rememberReturnFocus,\n    setOpen,\n    titleCount\n  ]);\n\n  useEffect(() => {\n    const dialog = dialogRef.current;\n    if (!dialog) {\n      return;\n    }\n    if (open && !dialog.open) {\n      ensureReturnFocus();\n      showModal(dialog);\n    } else if (!open && dialog.open) {\n      dialog.close();\n    }\n  }, [ensureReturnFocus, open, showModal, value.contentId]);\n\n  return (\n    <AlertDialogContext value={value}>{children}</AlertDialogContext>\n  );\n}\n\ntype EmptyState = Record<string, never>;\n\ntype NativeHostProps<Tag extends keyof HTMLElementTagNameMap> = Omit<\n  ComponentPropsWithoutRef<Tag>,\n  \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\ntype ButtonKeyHandlerOptions = {\n  disabled: boolean;\n  nativeButton: boolean;\n  onKeyDown?: (event: KeyboardEvent<HTMLButtonElement>) => void;\n  onKeyUp?: (event: KeyboardEvent<HTMLButtonElement>) => void;\n};\n\n/*\n * ponytail: every AlertDialog control hardcoded `type=\"button\"`, so\n * `render={(props) => <a {...props} />}` produced an anchor carrying an invalid\n * `type` attribute and no keyboard activation at all — Enter and Space did\n * nothing. `nativeButton={false}` swaps the native button contract for the\n * role=button one APG requires (role, tabindex, Enter on keydown, Space on\n * keyup) exactly as Dialog's Trigger/Close already do. Kept local rather than\n * lifted into _internal in this slice because a sibling session is reading that\n * directory; the extraction is named in the handoff.\n */\nfunction buttonKeyHandlers({\n  disabled,\n  nativeButton,\n  onKeyDown,\n  onKeyUp\n}: ButtonKeyHandlerOptions) {\n  return {\n    onKeyDown: 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    onKeyUp: 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}\n\nfunction nonNativeButtonAttrs(nativeButton: boolean, tabIndex?: number) {\n  return {\n    role: nativeButton ? undefined : \"button\",\n    tabIndex: nativeButton ? tabIndex : (tabIndex ?? 0),\n    type: nativeButton ? \"button\" : undefined\n  };\n}\n\n/** State handed to an `AlertDialogTrigger` className/style/render callback. */\nexport type AlertDialogTriggerState = { disabled: boolean; open: boolean };\n\nexport type AlertDialogTriggerProps = HeidiIntrinsicHostProps<\n  AlertDialogTriggerState,\n  \"button\"\n> &\n  Omit<\n    NativeHostProps<\"button\">,\n    \"aria-controls\" | \"aria-expanded\" | \"aria-haspopup\" | \"disabled\" | \"type\"\n  > & {\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 AlertDialogTrigger({\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}: AlertDialogTriggerProps) {\n  const generatedId = safeId(useId());\n  const { contentId, open, rememberReturnFocus, setOpen } =\n    useAlertDialogContext(\"Trigger\");\n  const pointerTypeRef = useRef<AlertDialogInteractionType>(\"\");\n  const state = useMemo<AlertDialogTriggerState>(\n    () => ({ disabled, open }),\n    [disabled, open]\n  );\n  return renderHeidiElement({\n    className: ALERT_DIALOG_CLASSES.trigger,\n    dataPart: \"alert-dialog-trigger\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      ...(nativeButton\n        ? nativeDisabledAttrs(disabled)\n        : ariaDisabledAttrs(disabled)),\n      ...nonNativeButtonAttrs(nativeButton, tabIndex),\n      ...buttonKeyHandlers({ disabled, nativeButton, onKeyDown, onKeyUp }),\n      \"aria-controls\": contentId,\n      \"aria-expanded\": open,\n      \"aria-haspopup\": \"dialog\",\n      children,\n      id: id ?? `hui-alert-dialog-trigger-${generatedId}`,\n      onClick: composeHeidiEventHandlers(\n        onClick,\n        (event: MouseEvent<HTMLButtonElement>) => {\n          if (disabled) {\n            event.preventDefault();\n            return;\n          }\n          const interactionType =\n            pointerTypeRef.current ||\n            interactionTypeFromEvent(event.nativeEvent);\n          pointerTypeRef.current = \"\";\n          rememberReturnFocus(event.currentTarget, interactionType);\n          setOpen(true);\n        }\n      ),\n      onPointerDown: 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      ref\n    },\n    renderProps: { className, render, style },\n    state\n  });\n}\n\nexport type AlertDialogTitleProps = HeidiIntrinsicHostProps<EmptyState, \"h2\"> &\n  Omit<NativeHostProps<\"h2\">, \"autoFocus\" | \"id\" | \"tabIndex\"> & {\n  children?: ReactNode;\n  ref?: Ref<HTMLHeadingElement>;\n};\n\nexport function AlertDialogTitle({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: AlertDialogTitleProps) {\n  const { registerTitle, titleId } = useAlertDialogContext(\"Title\");\n  useHeidiLayoutEffect(() => registerTitle(), [registerTitle]);\n  return renderHeidiElement({\n    className: ALERT_DIALOG_CLASSES.title,\n    dataPart: \"alert-dialog-title\",\n    element: \"h2\",\n    props: {\n      ...nativeProps,\n      autoFocus: true,\n      children,\n      \"data-hui-focus-fallback\": \"\",\n      id: titleId,\n      ref,\n      tabIndex: -1\n    },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type AlertDialogDescriptionProps = HeidiIntrinsicHostProps<EmptyState, \"p\"> &\n  Omit<NativeHostProps<\"p\">, \"id\"> & {\n  children?: ReactNode;\n  ref?: Ref<HTMLParagraphElement>;\n};\n\nexport function AlertDialogDescription({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: AlertDialogDescriptionProps) {\n  const { descriptionId, registerDescription } =\n    useAlertDialogContext(\"Description\");\n  useHeidiLayoutEffect(() => registerDescription(), [registerDescription]);\n  return renderHeidiElement({\n    className: ALERT_DIALOG_CLASSES.description,\n    dataPart: \"alert-dialog-description\",\n    element: \"p\",\n    props: { ...nativeProps, children, id: descriptionId, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\n/** State handed to an `AlertDialogContent` className/style callback. */\nexport type AlertDialogContentState = { open: boolean };\n\n/**\n * ponytail: the public name is `AlertDialogContentState`, but contract 20b\n * pins the literal needle `HeidiIntrinsicHostProps<ContentState, \"dialog\">` to\n * prove Content stays a native dialog host, so the short local alias has to\n * survive at that use site. Rejected: renaming the use site to the public name,\n * which reds a contract this slice does not own; and leaving the type private,\n * which is the defect being fixed.\n */\ntype ContentState = AlertDialogContentState;\n\n/** True only when the referenced IDREFs actually resolve to text inside the dialog. */\nfunction resolvesToText(\n  dialog: HTMLDialogElement,\n  idref: string | null\n): boolean {\n  if (!idref?.trim()) {\n    return false;\n  }\n  return idref\n    .trim()\n    .split(/\\s+/)\n    .some((id) => {\n      const element = dialog.ownerDocument.getElementById(id);\n      return (\n        element != null &&\n        dialog.contains(element) &&\n        !!element.textContent?.trim()\n      );\n    });\n}\n\nfunction alertDialogHasAccessibleName(dialog: HTMLDialogElement): boolean {\n  if (dialog.getAttribute(\"aria-label\")?.trim()) {\n    return true;\n  }\n  return resolvesToText(dialog, dialog.getAttribute(\"aria-labelledby\"));\n}\n\nexport type AlertDialogContentProps = Omit<\n  HeidiIntrinsicHostProps<ContentState, \"dialog\">,\n  \"render\"\n> &\n  Omit<\n    NativeHostProps<\"dialog\">,\n    | \"closedby\"\n    | \"id\"\n    | \"onCancel\"\n    | \"onClose\"\n    | \"onKeyDown\"\n    | \"role\"\n    | \"tabIndex\"\n  > & {\n  children?: ReactNode;\n  /** Focus target after close. Defaults to the invoker, then its id, then nothing. */\n  finalFocus?: AlertDialogFocusTarget;\n  /** Focus target on open. Defaults to Cancel, then the static Title. */\n  initialFocus?: AlertDialogFocusTarget;\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  ref?: Ref<HTMLDialogElement>;\n};\n\nexport function AlertDialogContent({\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  children,\n  className,\n  finalFocus,\n  initialFocus,\n  label,\n  onCancel,\n  onClose,\n  onKeyDown,\n  ref,\n  style,\n  ...nativeProps\n}: AlertDialogContentProps) {\n  const {\n    contentId,\n    descriptionCount,\n    descriptionId: descriptionElementId,\n    dialogRef,\n    focusOverridesRef,\n    handleNativeClose,\n    mounted,\n    nested,\n    open,\n    setOpen,\n    titleCount,\n    titleId: titleElementId\n  } = useAlertDialogContext(\"Content\");\n\n  // Published before the Root effect that calls showModal/restoreFocus (child\n  // layout effects run first), so the very first open already honours them.\n  useHeidiLayoutEffect(() => {\n    focusOverridesRef.current = { finalFocus, initialFocus };\n    return () => {\n      focusOverridesRef.current = {};\n    };\n  }, [finalFocus, focusOverridesRef, initialFocus]);\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") {\n      return;\n    }\n    if (!mounted) {\n      return;\n    }\n    const timeout = window.setTimeout(() => {\n      const dialog = dialogRef.current;\n      if (dialog == null) {\n        return;\n      }\n      // Exactly one registered part is the house shape. Zero is legitimate when\n      // `label` / aria-* supplies the value instead — which is why the name and\n      // description are checked on the settled DOM, not on the count alone —\n      // and anything above one duplicates an id.\n      const duplicateTitles = titleCount !== 1 && titleCount !== 0;\n      const duplicateDescriptions =\n        descriptionCount !== 1 && descriptionCount !== 0;\n      if (!alertDialogHasAccessibleName(dialog) || duplicateTitles) {\n        // oxlint-disable-next-line no-console -- an unnamed modal is a WCAG 4.1.2 failure.\n        console.warn(\n          \"heidi-ui AlertDialog: give the alert exactly one accessible name — one <AlertDialog.Title>, or label / aria-label / aria-labelledby.\"\n        );\n      }\n      if (\n        !resolvesToText(dialog, dialog.getAttribute(\"aria-describedby\")) ||\n        duplicateDescriptions\n      ) {\n        // oxlint-disable-next-line no-console -- APG alertdialog requires a description.\n        console.warn(\n          \"heidi-ui AlertDialog: give the alert exactly one message — one <AlertDialog.Description>, or aria-describedby.\"\n        );\n      }\n    }, 0);\n    return () => window.clearTimeout(timeout);\n  }, [contentId, descriptionCount, dialogRef, mounted, titleCount]);\n\n  if (!mounted) {\n    return null;\n  }\n\n  /*\n   * ponytail: these were emitted unconditionally, so a consumer who omitted\n   * <AlertDialog.Title> shipped role=alertdialog whose aria-labelledby pointed\n   * at nothing — the name computation walks a dead IDREF and resolves to the\n   * empty string, i.e. an unnamed modal (WCAG 4.1.2; APG alertdialog requires\n   * aria-labelledby *or* aria-label). Radix ships the same bug; matching it is\n   * not parity. The parts now register, and the attribute only appears when\n   * something is there to point at. Rejected: keeping the attribute and relying\n   * on the dev warning — the warning is stripped in production, which is the\n   * only build a user ever meets.\n   */\n  const computedLabel = ariaLabel ?? label;\n  const titleId =\n    ariaLabelledBy ??\n    (computedLabel || titleCount === 0 ? undefined : titleElementId);\n  const descriptionId =\n    ariaDescribedBy ?? (descriptionCount === 0 ? undefined : descriptionElementId);\n\n  return renderHeidiElement({\n    className: ALERT_DIALOG_CLASSES.content,\n    dataPart: \"alert-dialog-content\",\n    element: \"dialog\",\n    props: {\n      ...nativeProps,\n      \"aria-describedby\": descriptionId,\n      \"aria-label\": computedLabel,\n      \"aria-labelledby\": titleId,\n      children,\n      // APG: no backdrop dismiss — Escape (closerequest) still works.\n      closedby: \"closerequest\",\n      \"data-closed\": open ? undefined : true,\n      \"data-nested\": nested ? \"true\" : undefined,\n      \"data-open\": open ? true : undefined,\n      \"data-state\": open ? \"open\" : \"closed\",\n      id: contentId,\n      onCancel: (event: SyntheticEvent<HTMLDialogElement>) => {\n        try {\n          onCancel?.(event);\n        } finally {\n          // A nested dialog's close request must never reach an ancestor modal.\n          event.stopPropagation();\n          if (!event.defaultPrevented) {\n            event.preventDefault();\n            setOpen(false);\n          }\n        }\n      },\n      onClose: (event: SyntheticEvent<HTMLDialogElement>) => {\n        // React delegates `close` even though the native event does not bubble;\n        // keep a nested lifecycle event out of ancestor dialog handlers.\n        event.stopPropagation();\n        try {\n          onClose?.(event);\n        } finally {\n          handleNativeClose(event.currentTarget);\n        }\n      },\n      onKeyDown: composeHeidiEventHandlers(onKeyDown, containHeidiModalTabFocus),\n      ref: mergeHeidiRefs(dialogRef, ref),\n      role: \"alertdialog\"\n    },\n    renderProps: { className, style },\n    state: { open }\n  });\n}\n\n/**\n * A confirm that deletes and a confirm that saves are not the same button.\n * `destructive` swaps the brand fill for the danger fill at the same weight;\n * it changes nothing about focus, order, or behaviour.\n */\nexport type AlertDialogActionTone = \"default\" | \"destructive\";\n\nexport type AlertDialogActionState = {\n  disabled: boolean;\n  tone: AlertDialogActionTone;\n};\n\nexport type AlertDialogActionProps = HeidiIntrinsicHostProps<\n  AlertDialogActionState,\n  \"button\"\n> &\n  Omit<NativeHostProps<\"button\">, \"disabled\" | \"type\"> & {\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  tone?: AlertDialogActionTone;\n};\n\nexport function AlertDialogAction({\n  children,\n  className,\n  disabled = false,\n  nativeButton = true,\n  onClick,\n  onKeyDown,\n  onKeyUp,\n  ref,\n  render,\n  style,\n  tabIndex,\n  tone = \"default\",\n  ...nativeProps\n}: AlertDialogActionProps) {\n  const { setOpen } = useAlertDialogContext(\"Action\");\n  const state = useMemo<AlertDialogActionState>(\n    () => ({ disabled, tone }),\n    [disabled, tone]\n  );\n  return renderHeidiElement({\n    className: ALERT_DIALOG_CLASSES.action,\n    dataPart: \"alert-dialog-action\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      ...(nativeButton\n        ? nativeDisabledAttrs(disabled)\n        : ariaDisabledAttrs(disabled)),\n      ...nonNativeButtonAttrs(nativeButton, tabIndex),\n      ...buttonKeyHandlers({ disabled, nativeButton, onKeyDown, onKeyUp }),\n      children,\n      \"data-tone\": tone,\n      onClick: composeHeidiEventHandlers(\n        onClick,\n        (event: MouseEvent<HTMLButtonElement>) => {\n          if (disabled) {\n            event.preventDefault();\n            return;\n          }\n          setOpen(false);\n        }\n      ),\n      ref\n    },\n    renderProps: { className, render, style },\n    state\n  });\n}\n\n/** State handed to an `AlertDialogCancel` className/style/render callback. */\nexport type AlertDialogCancelState = { disabled: boolean };\n\nexport type AlertDialogCancelProps = HeidiIntrinsicHostProps<\n  AlertDialogCancelState,\n  \"button\"\n> &\n  Omit<NativeHostProps<\"button\">, \"autoFocus\" | \"disabled\" | \"type\"> & {\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 AlertDialogCancel({\n  children,\n  className,\n  disabled = false,\n  nativeButton = true,\n  onClick,\n  onKeyDown,\n  onKeyUp,\n  ref,\n  render,\n  style,\n  tabIndex,\n  ...nativeProps\n}: AlertDialogCancelProps) {\n  const { cancelRef, setOpen } = useAlertDialogContext(\"Cancel\");\n  const state = useMemo<AlertDialogCancelState>(() => ({ disabled }), [disabled]);\n\n  return renderHeidiElement({\n    className: ALERT_DIALOG_CLASSES.cancel,\n    dataPart: \"alert-dialog-cancel\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      ...(nativeButton\n        ? nativeDisabledAttrs(disabled)\n        : ariaDisabledAttrs(disabled)),\n      ...nonNativeButtonAttrs(nativeButton, tabIndex),\n      ...buttonKeyHandlers({ disabled, nativeButton, onKeyDown, onKeyUp }),\n      children,\n      onClick: composeHeidiEventHandlers(\n        onClick,\n        (event: MouseEvent<HTMLButtonElement>) => {\n          if (disabled) {\n            event.preventDefault();\n            return;\n          }\n          setOpen(false);\n        }\n      ),\n      ref: mergeHeidiRefs(cancelRef, ref)\n    },\n    renderProps: { className, render, style },\n    state\n  });\n}\n\nexport const AlertDialog = {\n  Action: AlertDialogAction,\n  Cancel: AlertDialogCancel,\n  Content: AlertDialogContent,\n  Description: AlertDialogDescription,\n  Root: AlertDialogRoot,\n  Title: AlertDialogTitle,\n  Trigger: AlertDialogTrigger\n} as const;\n",
      "path": "packages/heidi-ui/src/alert-dialog/alert-dialog.tsx",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui alert-dialog — STRUCTURAL CSS only (platform behavior).\n */\n\n@layer heidi-ui-base {\n  /* ponytail: an interruptive confirm must not let the page scroll underneath\n     it — showModal() blocks pointer and keyboard interaction with the page but\n     not wheel/trackpad/scrollbar scrolling of the document. Dialog shipped this\n     lock and AlertDialog did not, which nobody noticed because the contract\n     asserted it for Dialog alone. Each sheet declares only its own selector;\n     naming Dialog's class here as well protects nothing reachable (a primitive\n     never renders without its own stylesheet) and breaks the\n     hui-<component>-<part> prefix rule the classmap builder enforces.\n     Rejected: a JS body-scroll lock — fights the UA, needs scrollbar\n     compensation, dead during SSR. */\n  html:has(.hui-alert-dialog-content:modal) {\n    overflow: hidden;\n  }\n\n  .hui-alert-dialog-content {\n    /* Deliberately larger than the shared 1rem popup gutter: the interruptive\n       confirm modal keeps extra breathing room (owner call, G2 audit). */\n    --_hui-alert-dialog-viewport-gutter: 3rem;\n\n    box-sizing: border-box;\n    max-block-size: calc(100dvb - var(--_hui-alert-dialog-viewport-gutter));\n    max-inline-size: calc(100dvi - var(--_hui-alert-dialog-viewport-gutter));\n    overflow: auto;\n    overscroll-behavior: contain;\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n\n  .hui-alert-dialog-content:not([open]) {\n    display: none !important;\n  }\n\n  .hui-alert-dialog-content::backdrop {\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n}\n",
      "path": "packages/heidi-ui/src/alert-dialog/alert-dialog.base.css",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui alert-dialog — VISUAL theme (opt-in). Consumes --hui-*.\n */\n\n@layer heidi-ui {\n  .hui-alert-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-alert-dialog-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  /* Same surface recipe as Dialog: bg-raised (bg-elevated inverts below the\n     menu rung in dark), no CSS border (the ladder's first stop is the\n     hairline), radius-2xl, and the top rung of the ladder. */\n  .hui-alert-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    inline-size: 24rem;\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-alert-dialog-content[open] {\n    opacity: 1;\n    transform: none;\n  }\n\n  .hui-alert-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-alert-dialog-content[open]::backdrop {\n    opacity: 1;\n  }\n\n  .hui-alert-dialog-content[data-nested=\"true\"]::backdrop {\n    background: transparent;\n  }\n\n  @starting-style {\n    .hui-alert-dialog-content[open] {\n      opacity: 0;\n      transform: scale(0.97);\n    }\n\n    .hui-alert-dialog-content[open]::backdrop {\n      opacity: 0;\n    }\n  }\n\n  .hui-alert-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    overflow-wrap: anywhere;\n  }\n\n  .hui-alert-dialog-title:focus-visible {\n    outline: none;\n  }\n\n  .hui-alert-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    overflow-wrap: anywhere;\n  }\n\n  .hui-alert-dialog-action,\n  .hui-alert-dialog-cancel {\n    border: var(--hui-border-width) var(--hui-border-style) var(--hui-color-border-default);\n    border-radius: var(--hui-radius-sm);\n    cursor: pointer;\n    font: inherit;\n    min-block-size: calc(var(--hui-space-3) * 2);\n    min-inline-size: 0;\n    overflow-wrap: anywhere;\n    padding: var(--hui-space-1-5) var(--hui-space-3);\n  }\n\n  .hui-alert-dialog-action {\n    background: var(--hui-color-brand-primary);\n    border-color: var(--hui-color-brand-primary);\n    color: var(--hui-color-on-brand, #fff);\n  }\n\n  /*\n   * ponytail: a confirm that deletes and a confirm that saves used the same\n   * brand fill, so \"Archive project\" and \"Publish version\" were the same\n   * button. Tone is carried on the Action part rather than by the consumer\n   * swapping classes, because a consumer class lands in @layer components and\n   * silently outranks every other Action rule here.\n  */\n  .hui-alert-dialog-action[data-tone=\"destructive\"] {\n    background: var(--hui-color-danger);\n    border-color: var(--hui-color-danger);\n    color: var(--hui-color-on-brand, #fff);\n  }\n\n  .hui-alert-dialog-cancel {\n    background: transparent;\n    color: var(--hui-color-fg-default);\n  }\n\n  .hui-alert-dialog-trigger:disabled,\n  .hui-alert-dialog-action:disabled,\n  .hui-alert-dialog-cancel:disabled {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  .hui-alert-dialog-action:focus-visible,\n  .hui-alert-dialog-cancel: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  @media (prefers-reduced-motion: reduce) {\n    .hui-alert-dialog-content,\n    .hui-alert-dialog-content::backdrop {\n      transition-duration: 0s;\n    }\n\n    .hui-alert-dialog-content {\n      transform: none;\n    }\n  }\n\n  @media (forced-colors: active) {\n    .hui-alert-dialog-trigger,\n    .hui-alert-dialog-action,\n    .hui-alert-dialog-cancel {\n      border-color: CanvasText;\n    }\n\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-alert-dialog-content {\n      border: var(--hui-border-width) solid CanvasText;\n    }\n\n    .hui-alert-dialog-content::backdrop {\n      background: Canvas;\n      opacity: 0.72;\n    }\n\n    .hui-alert-dialog-action {\n      background: Highlight;\n      border-color: Highlight;\n      color: HighlightText;\n      forced-color-adjust: none;\n    }\n\n    .hui-alert-dialog-trigger:focus-visible,\n    .hui-alert-dialog-action:focus-visible,\n    .hui-alert-dialog-cancel:focus-visible {\n      outline-color: Highlight;\n    }\n\n    .hui-alert-dialog-trigger:disabled,\n    .hui-alert-dialog-action:disabled,\n    .hui-alert-dialog-cancel:disabled {\n      border-color: GrayText;\n      color: GrayText;\n      opacity: 1;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/alert-dialog/alert-dialog.theme.css",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui alert-dialog — aggregator.\n */\n\n@import \"./alert-dialog.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./alert-dialog.theme.css\";\n",
      "path": "packages/heidi-ui/src/alert-dialog/alert-dialog.css",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/alert-dialog/alert-dialog.base.css + packages/heidi-ui/src/alert-dialog/alert-dialog.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const ALERT_DIALOG_CLASSES = {\n  action: \"hui-alert-dialog-action\",\n  cancel: \"hui-alert-dialog-cancel\",\n  content: \"hui-alert-dialog-content\",\n  description: \"hui-alert-dialog-description\",\n  title: \"hui-alert-dialog-title\",\n  trigger: \"hui-alert-dialog-trigger\",\n} as const;\n",
      "path": "packages/heidi-ui/src/alert-dialog/alert-dialog.classes.generated.ts",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from alert-dialog.anatomy.json + alert-dialog.base.css + alert-dialog.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type AlertDialogNested = \"true\";\nexport type AlertDialogTone = \"destructive\";\n\nexport const ALERT_DIALOG_ANATOMY = {\n  \"component\": \"alert-dialog\",\n  \"description\": \"APG alertdialog on a native <dialog> shell. Root is the single source of truth for controlled and native open state; Title + Description are required and wired on first render; backdrop dismissal is disabled; Escape requests close; focus starts on Cancel, wraps inside the modal, and returns to the invoker. Native host props and safe composition are supported.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"action\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-action\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-action\",\n      \"description\": \"Confirm/primary button; closes the alert dialog. tone=\\\"destructive\\\" swaps the brand fill for the danger fill at the same weight, so a delete never looks like a save.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"tone\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-tone\": [\n          \"destructive\"\n        ]\n      }\n    },\n    \"cancel\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-cancel\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-cancel\",\n      \"description\": \"Cancel/safe button; closes the alert dialog. Preferred initial focus target when the content fits without scrolling.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {}\n    },\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-describedby\",\n          \"aria-labelledby\"\n        ],\n        \"role\": \"alertdialog\"\n      },\n      \"class\": \"hui-alert-dialog-content\",\n      \"css\": {\n        \"structural\": [\n          \"box-sizing\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"overscroll-behavior\",\n          \"transition-behavior\",\n          \"transition-property\"\n        ]\n      },\n      \"dataPart\": \"alert-dialog-content\",\n      \"description\": \"Native-only <dialog> with role=alertdialog. Requires exactly one Title and Description; owns modal focus containment and native close reconciliation.\",\n      \"element\": \"dialog\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\n        \"className\",\n        \"onCancel\",\n        \"onClose\",\n        \"onKeyDown\",\n        \"ref\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \"[open]\",\n        \"::backdrop\"\n      ],\n      \"states\": {\n        \"data-nested\": [\n          \"true\"\n        ]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-description\",\n      \"description\": \"Required supporting text; wired via aria-describedby.\",\n      \"element\": \"p\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\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-alert-dialog-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-title\",\n      \"description\": \"Required heading; wired via aria-labelledby and used as the static initial-focus target for overflowed content or an unavailable Cancel action.\",\n      \"element\": \"h2\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\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-alert-dialog-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-trigger\",\n      \"description\": \"Button that opens the alert dialog modally.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"id\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {}\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-brand-primary\",\n    \"--hui-color-danger\",\n    \"--hui-color-fg-default\",\n    \"--hui-color-fg-muted\",\n    \"--hui-color-focus-ring\",\n    \"--hui-color-on-brand\",\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-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/alert-dialog/alert-dialog.anatomy.generated.ts",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"alert-dialog\",\n  \"description\": \"APG alertdialog on a native <dialog> shell. Root is the single source of truth for controlled and native open state; Title + Description are required and wired on first render; backdrop dismissal is disabled; Escape requests close; focus starts on Cancel, wraps inside the modal, and returns to the invoker. Native host props and safe composition are supported.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"action\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-action\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-action\",\n      \"description\": \"Confirm/primary button; closes the alert dialog. tone=\\\"destructive\\\" swaps the brand fill for the danger fill at the same weight, so a delete never looks like a save.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"tone\"\n      ],\n      \"pseudoStates\": [\":disabled\", \":focus-visible\"],\n      \"states\": { \"data-tone\": [\"destructive\"] }\n    },\n    \"cancel\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-cancel\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-cancel\",\n      \"description\": \"Cancel/safe button; closes the alert dialog. Preferred initial focus target when the content fits without scrolling.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"disabled\", \"onClick\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":disabled\", \":focus-visible\"],\n      \"states\": {}\n    },\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\"aria-describedby\", \"aria-labelledby\"],\n        \"role\": \"alertdialog\"\n      },\n      \"class\": \"hui-alert-dialog-content\",\n      \"css\": {\n        \"structural\": [\n          \"box-sizing\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"overscroll-behavior\",\n          \"transition-behavior\",\n          \"transition-property\"\n        ]\n      },\n      \"dataPart\": \"alert-dialog-content\",\n      \"description\": \"Native-only <dialog> with role=alertdialog. Requires exactly one Title and Description; owns modal focus containment and native close reconciliation.\",\n      \"element\": \"dialog\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\"className\", \"onCancel\", \"onClose\", \"onKeyDown\", \"ref\", \"style\"],\n      \"pseudoStates\": [\"[open]\", \"::backdrop\"],\n      \"states\": {\n        \"data-nested\": [\"true\"]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-description\",\n      \"description\": \"Required supporting text; wired via aria-describedby.\",\n      \"element\": \"p\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"title\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-alert-dialog-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-title\",\n      \"description\": \"Required heading; wired via aria-labelledby and used as the static initial-focus target for overflowed content or an unavailable Cancel action.\",\n      \"element\": \"h2\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"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-alert-dialog-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"alert-dialog-trigger\",\n      \"description\": \"Button that opens the alert dialog modally.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"disabled\", \"id\", \"onClick\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":disabled\", \":focus-visible\"],\n      \"states\": {}\n    }\n  },\n  \"rootProps\": [\"defaultOpen\", \"onOpenChange\", \"open\"]\n}\n",
      "path": "packages/heidi-ui/src/alert-dialog/alert-dialog.anatomy.json",
      "target": "components/ui/heidi/alert-dialog/alert-dialog.anatomy.json",
      "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": "alert-dialog",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Alert-dialog",
  "type": "registry:ui"
}
