{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "Transient status message on the top layer: [popover=manual] (no light-dismiss). Announcement is owned by a persistent off-screen live region rendered beside the popover — role=status/aria-live=polite by default, role=alert/aria-live=assertive when assertive — because a live region only announces mutations made while it is already in the accessibility tree. Controlled open + optional duration auto-dismiss. Close and Action buttons. Fixed viewport corner (not pointer-anchored). Composition via renderHeidiElement. Two non-DOM pieces have no part entry because they render no styled element: Toast.Provider (queue owner — configurable limit and default duration; hosts the two persistent announcer regions, one polite, one assertive, so a queued toast mutates a region that has been in the accessibility tree since mount) and useToast() (the imperative handle: add / update / dismiss / dismissAll plus the visible `toasts` and the waiting `queued`).",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui Toast — APG live region beside the popover shell\n * (docs/HEIDI-UI-HEADLESS.md § P4 / Slice G). [popover=manual] top layer\n * (no light-dismiss) for the visible surface; a persistent off-screen\n * announcer carries role=status (polite) or role=alert (assertive), because\n * a live region only announces mutations made while it is ALREADY in the\n * accessibility tree. Fixed viewport corner — not pointer-anchored. Optional\n * duration auto-dismiss; Escape + Close dismiss. Controlled open supported.\n *\n * Two usable shapes, one implementation:\n *   1. Declarative — `Toast.Root` + `Toast.Content`, one toast, its own\n *      popover and its own announcer. Unchanged since Slice G.\n *   2. System — `Toast.Provider` owns a FIFO queue with a visible `limit` and\n *      a default `duration`; `useToast()` is the imperative handle;\n *      `Toast.Viewport` is the named region toasts are laid out in.\n *\n * ponytail: `useToast()` (a hook reading Provider context) rather than a\n * standalone `createToastManager()` store object. Every cross-part channel in\n * this package is React context read through a `use*Context` accessor that\n * throws by part name, and a module-level store would be the one piece of\n * heidi-ui callable outside React — a second source of truth that has to be\n * kept in sync with the Provider's state and that silently no-ops when no\n * Provider is mounted. The hook cannot: it throws. Rejected: shipping both\n * (Base UI's shape) — the extra surface cannot be removed after 1.0, and\n * nothing in this repo needs to toast from outside a React tree.\n *\n * RSC rule: named exports from Server Components; Toast.X is client sugar.\n */\n\nimport {\n  Children,\n  Fragment,\n  type ComponentPropsWithoutRef,\n  createContext,\n  isValidElement,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type CSSProperties,\n  type ReactNode,\n  type Ref,\n  type ToggleEvent\n} from \"react\";\nimport {\n  addToOpenStack,\n  hasEscapeConsumerOutsideStack,\n  isTopmost\n} from \"../_internal/escape-layer\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { synchronizeNativePopoverOpen } from \"../_internal/native-popover-sync\";\nimport { safeId } from \"../_internal/safe-id\";\nimport { TOAST_CLASSES } from \"./toast.classes.generated\";\n\ntype ToastContextValue = {\n  /**\n   * False when the Provider already announced this toast from the queue. The\n   * visible surface then renders no announcer of its own — two live regions\n   * carrying the same message is one announcement too many.\n   */\n  announces: boolean;\n  assertive: boolean;\n  close: () => void;\n  contentId: string;\n  contentRef: { current: HTMLDivElement | null };\n  descriptionId: string;\n  hasDescription: boolean;\n  hasTitle: boolean;\n  open: boolean;\n  /** Acquire a WCAG 2.2.1 hold on the auto-dismiss timer. Balance with resume. */\n  pause: () => void;\n  registerDescription: () => () => void;\n  registerTitle: () => () => void;\n  /** Release one hold acquired by pause. */\n  resume: () => void;\n  setOpen: (open: boolean) => void;\n  titleId: string;\n  triggerId: string;\n};\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nfunction useToastContext(part: string): ToastContextValue {\n  const context = useContext(ToastContext);\n  if (!context) {\n    throw new Error(`Toast.${part} must be rendered inside Toast.Root.`);\n  }\n  return context;\n}\n\nfunction dataState(open: boolean): \"open\" | \"closed\" {\n  return open ? \"open\" : \"closed\";\n}\n\n/**\n * Off-screen but ALWAYS rendered. Never `display: none`, never `hidden`, never\n * inside the popover: a live region has to be in the accessibility tree before\n * its content changes, and anything that hides it takes it back out.\n * `position: fixed` (not absolute) so an ancestor transform cannot drag it into\n * view, and `clip-path` rather than the legacy `clip`.\n */\nconst TOAST_ANNOUNCER_STYLE: CSSProperties = {\n  blockSize: 1,\n  border: 0,\n  clipPath: \"inset(50%)\",\n  inlineSize: 1,\n  insetBlockStart: 0,\n  insetInlineStart: 0,\n  // ponytail: no negative margin. The legacy sr-only recipe pulls the box to\n  // -1px, and the headless geometry smoke fails anything whose left edge is\n  // outside the viewport — `clip-path` already hides it completely, so the\n  // offset bought nothing and only put the box on that assertion's boundary.\n  margin: 0,\n  overflow: \"hidden\",\n  padding: 0,\n  position: \"fixed\",\n  whiteSpace: \"nowrap\"\n};\n\n/**\n * role and aria-live for one announcer, kept in a single place so the two\n * cannot disagree — a polite region wearing role=alert is a bug no test would\n * catch.\n *\n * ponytail: built as an object and spread, not written inline on the JSX. It\n * reads as a lint dodge (`jsx-a11y/prefer-tag-over-role` wants `<output>` for\n * role=status) and is not: `<output>` is a form-associated element with a\n * `for`/`name`/form-owner contract and is announced by some ATs as a\n * calculation result. This is a bare notification region, and there is no\n * element whose implicit role is `alert` at all, so the assertive half would\n * need the attribute regardless — one shape for both beats two.\n */\nfunction toastAnnouncerProps(assertive: boolean) {\n  return {\n    \"aria-atomic\": true,\n    \"aria-live\": assertive ? (\"assertive\" as const) : (\"polite\" as const),\n    role: assertive ? \"alert\" : \"status\"\n  };\n}\n\n/**\n * What a screen reader should hear, read back off the rendered toast so it\n * matches what sighted users see even when the message is composed from\n * arbitrary children rather than Title/Description.\n *\n * Dismiss chrome and decorative (aria-hidden) content are dropped: \"Dismiss\"\n * is an affordance the user finds by Tab, not part of the message.\n */\nfunction announcementTextFrom(content: HTMLElement): string {\n  const clone = content.cloneNode(true) as HTMLElement;\n  for (const excluded of clone.querySelectorAll(\n    '[aria-hidden=\"true\"], [data-hui-part=\"toast-close\"]'\n  )) {\n    excluded.remove();\n  }\n  return (clone.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n}\n\nfunction findAuthoredTextParts(children: ReactNode): {\n  description: boolean;\n  title: boolean;\n} {\n  const result = { description: false, title: false };\n  const visit = (nodes: ReactNode) => {\n    Children.forEach(nodes, (child) => {\n      if (!isValidElement(child)) {\n        return;\n      }\n      if (child.type === ToastTitle) {\n        result.title = true;\n      } else if (child.type === ToastDescription) {\n        result.description = true;\n      }\n      if (child.type === Fragment || typeof child.type === \"string\") {\n        visit((child.props as { children?: ReactNode }).children);\n      }\n    });\n  };\n  visit(children);\n  return result;\n}\n\n/** One queued message. `id` is assigned by `add` unless you supply one. */\nexport type ToastItem = {\n  /** Route the announcement through role=alert instead of role=status. */\n  assertive?: boolean;\n  description?: string;\n  /** Overrides the Provider's `duration` for this toast only. */\n  duration?: number;\n  id: string;\n  title?: string;\n};\n\n/** `add` input: an item without a required id (supply one to upsert). */\nexport type ToastOptions = Omit<ToastItem, \"id\"> & { id?: string };\n\n/** `update` input: an item patch; the id is the separate argument. */\nexport type ToastPatch = Omit<ToastItem, \"id\">;\n\nexport type ToastManager = {\n  /** Enqueue (or upsert by `options.id`). Returns the toast's id. */\n  add: (options?: ToastOptions) => string;\n  dismiss: (id: string) => void;\n  dismissAll: () => void;\n  /** Accepted but not yet visible, oldest first — nothing is ever dropped. */\n  queued: readonly ToastItem[];\n  /** Currently visible, oldest first. At most `limit` entries. */\n  toasts: readonly ToastItem[];\n  update: (id: string, patch: ToastPatch) => void;\n};\n\ntype ToastQueueContextValue = {\n  duration: number;\n  limit: number;\n  manager: ToastManager;\n  /** WCAG 2.2.1 hold covering EVERY queued toast. Balance with resumeAll. */\n  pauseAll: () => void;\n  paused: boolean;\n  resumeAll: () => void;\n};\n\nconst ToastQueueContext = createContext<ToastQueueContextValue | null>(null);\n\n/**\n * True for anything rendered inside `Toast.Viewport`. The viewport owns the\n * top layer for the whole stack, so a toast inside one must NOT be its own\n * popover: a popover is painted in the top layer and therefore out of its\n * ancestor's flow, and three self-popovering toasts would all pin themselves\n * to the same corner and overlap.\n */\nconst ToastViewportContext = createContext(false);\n\nfunction useToastQueueContext(part: string): ToastQueueContextValue {\n  const context = useContext(ToastQueueContext);\n  if (!context) {\n    throw new Error(`Toast.${part} must be rendered inside Toast.Provider.`);\n  }\n  return context;\n}\n\n/**\n * The imperative handle. Throws outside a `Toast.Provider` rather than\n * no-oping, so a missing Provider is a first-render crash in development and\n * not a message that silently never appears in production.\n */\nexport function useToast(): ToastManager {\n  return useToastQueueContext(\"useToast\").manager;\n}\n\nfunction announcementTextFor(item: ToastItem): string {\n  return [item.title, item.description]\n    .filter((value): value is string => typeof value === \"string\")\n    .join(\" \")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\ntype ToastAnnouncement = { seq: number; text: string };\n\n/**\n * A live-region text slot that always mutates in TWO commits: cleared first,\n * filled second.\n *\n * ponytail: the `seq` counter is load-bearing, not bookkeeping. Clearing with\n * a bare `setText(\"\")` when the text is already `\"\"` is a React bail-out — no\n * re-render, so the flush effect never runs and the message is never spoken.\n * The counter guarantees the state object changes identity every time, so the\n * clear→fill pair always produces two commits. Rejected: a `setTimeout` /\n * `requestAnimationFrame` gap between the two writes, which is the same two\n * mutations plus a timer to leak on unmount and a race for tests to poll.\n *\n * The clear is what makes a REPEATED message announce: writing the same string\n * into a live region is not a mutation, so \"Saved\" twice in a row would be\n * silent the second time without the empty state in between.\n */\nfunction useToastAnnouncement(): [string, (text: string) => void] {\n  const [state, setState] = useState<ToastAnnouncement>({ seq: 0, text: \"\" });\n  const pendingRef = useRef<string | null>(null);\n  const announce = useCallback((text: string) => {\n    pendingRef.current = text;\n    setState((current) => ({ seq: current.seq + 1, text: \"\" }));\n  }, []);\n  useEffect(() => {\n    const pending = pendingRef.current;\n    if (pending === null || state.text !== \"\") {\n      return;\n    }\n    pendingRef.current = null;\n    if (pending === \"\") {\n      return;\n    }\n    setState((current) => ({ seq: current.seq + 1, text: pending }));\n  }, [state]);\n  return [state.text, announce];\n}\n\nexport type ToastProviderProps = {\n  children?: ReactNode;\n  /** Default auto-dismiss ms for queued toasts. Default: 5000. */\n  duration?: number;\n  /**\n   * How many toasts are visible at once. Default: 3. Extras WAIT in `queued`\n   * and become visible as room frees — they are never dropped.\n   */\n  limit?: number;\n};\n\n/**\n * Owns the queue and the two persistent live regions.\n *\n * ponytail: the announcers live HERE, on the Provider, and not on the Viewport\n * or on each queued toast. A live region only announces mutations made while\n * it is already in the accessibility tree, so a region that mounts together\n * with the toast that needs announcing is exactly the bug the Slice G split\n * fixed — a queue that mounts a fresh region per toast reintroduces it\n * verbatim. The Provider is mounted for the life of the app, so by the time\n * anything can call `add()` both regions have been in the tree for a while and\n * adding a toast only rewrites their text. There are two because polite and\n * assertive cannot share one element: role/aria-live are not per-message.\n * Rejected: the Viewport, which a consumer may render conditionally (empty\n * state, route change) and which is `display: none` whenever the queue is\n * empty — either takes the region back out of the tree.\n */\nexport function ToastProvider({\n  children,\n  duration = 5000,\n  limit = 3\n}: ToastProviderProps) {\n  const safe = safeId(useId());\n  const sequenceRef = useRef(0);\n  const [items, setItems] = useState<readonly ToastItem[]>([]);\n\n  const add = useCallback(\n    (options: ToastOptions = {}) => {\n      // Outside the updater on purpose: a StrictMode double-invoked updater\n      // would burn two ids and hand back the wrong one.\n      sequenceRef.current += 1;\n      const id = options.id ?? `hui-toast-${safe}-${sequenceRef.current}`;\n      setItems((current) =>\n        current.some((item) => item.id === id)\n          ? current.map((item) =>\n              item.id === id ? { ...item, ...options, id } : item\n            )\n          : [...current, { ...options, id }]\n      );\n      return id;\n    },\n    [safe]\n  );\n\n  const dismiss = useCallback((id: string) => {\n    setItems((current) => current.filter((item) => item.id !== id));\n  }, []);\n\n  const dismissAll = useCallback(() => setItems([]), []);\n\n  const update = useCallback((id: string, patch: ToastPatch) => {\n    setItems((current) =>\n      current.map((item) => (item.id === id ? { ...item, ...patch, id } : item))\n    );\n  }, []);\n\n  const visibleCount = Math.max(limit, 0);\n  const toasts = useMemo(\n    () => items.slice(0, visibleCount),\n    [items, visibleCount]\n  );\n  const queued = useMemo(() => items.slice(visibleCount), [items, visibleCount]);\n\n  // Provider-wide WCAG 2.2.1 holds, counted for the same reason Root counts\n  // its own: the Viewport can be under the pointer AND hold focus at once.\n  const [pauseHolds, setPauseHolds] = useState(0);\n  const pauseAll = useCallback(() => setPauseHolds((holds) => holds + 1), []);\n  const resumeAll = useCallback(\n    () => setPauseHolds((holds) => (holds > 0 ? holds - 1 : 0)),\n    []\n  );\n\n  const [politeMessage, announcePolite] = useToastAnnouncement();\n  const [assertiveMessage, announceAssertive] = useToastAnnouncement();\n  const announcedRef = useRef(new Map<string, string>());\n\n  // Announce on becoming VISIBLE, not on being accepted: a toast held back by\n  // the limit has not been shown to anyone yet, and announcing it early would\n  // describe a message no sighted user can point at. Text changes on a visible\n  // toast (`update`) announce through the same path — a duration-only update\n  // does not, because the text it compares is unchanged.\n  useEffect(() => {\n    const announced = announcedRef.current;\n    const visibleIds = new Set(toasts.map((item) => item.id));\n    for (const id of [...announced.keys()]) {\n      if (!visibleIds.has(id)) {\n        announced.delete(id);\n      }\n    }\n    const fresh: { assertive: string[]; polite: string[] } = {\n      assertive: [],\n      polite: []\n    };\n    for (const item of toasts) {\n      const text = announcementTextFor(item);\n      if (announced.get(item.id) === text) {\n        continue;\n      }\n      announced.set(item.id, text);\n      if (text !== \"\") {\n        fresh[item.assertive === true ? \"assertive\" : \"polite\"].push(text);\n      }\n    }\n    if (fresh.polite.length > 0) {\n      announcePolite(fresh.polite.join(\" \"));\n    }\n    if (fresh.assertive.length > 0) {\n      announceAssertive(fresh.assertive.join(\" \"));\n    }\n    if (visibleIds.size === 0) {\n      announcePolite(\"\");\n      announceAssertive(\"\");\n    }\n  }, [announceAssertive, announcePolite, toasts]);\n\n  const manager = useMemo<ToastManager>(\n    () => ({ add, dismiss, dismissAll, queued, toasts, update }),\n    [add, dismiss, dismissAll, queued, toasts, update]\n  );\n\n  const value = useMemo<ToastQueueContextValue>(\n    () => ({\n      duration,\n      limit,\n      manager,\n      pauseAll,\n      paused: pauseHolds > 0,\n      resumeAll\n    }),\n    [duration, limit, manager, pauseAll, pauseHolds, resumeAll]\n  );\n\n  return (\n    <ToastQueueContext value={value}>\n      <div\n        {...toastAnnouncerProps(false)}\n        data-hui-live=\"polite\"\n        data-hui-part=\"toast-announcer\"\n        style={TOAST_ANNOUNCER_STYLE}\n      >\n        {politeMessage}\n      </div>\n      <div\n        {...toastAnnouncerProps(true)}\n        data-hui-live=\"assertive\"\n        data-hui-part=\"toast-announcer\"\n        style={TOAST_ANNOUNCER_STYLE}\n      >\n        {assertiveMessage}\n      </div>\n      {children}\n    </ToastQueueContext>\n  );\n}\n\nexport type ToastRootProps = {\n  /** When true, content uses role=alert (assertive). Default: role=status. */\n  assertive?: boolean;\n  children?: ReactNode;\n  defaultOpen?: boolean;\n  /**\n   * Auto-dismiss ms after open. `0` / `Infinity` / negative = no auto-dismiss.\n   * Falls back to the Provider's `duration`, then to 5000.\n   */\n  duration?: number;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n  /**\n   * A queued item from `useToast().toasts`. Supplying it makes this Root the\n   * rendering of that item: it starts open, takes its assertiveness and\n   * duration from the item, and every dismissal path removes it from the\n   * queue instead of only flipping local state.\n   */\n  toast?: ToastItem;\n};\n\nexport function ToastRoot({\n  assertive,\n  children,\n  defaultOpen = false,\n  duration: durationProp,\n  onOpenChange,\n  open: openProp,\n  toast\n}: ToastRootProps) {\n  const id = useId();\n  const safe = safeId(id);\n  const queue = useContext(ToastQueueContext);\n  const managedId = toast?.id;\n  const [titleCount, setTitleCount] = useState(0);\n  const [descriptionCount, setDescriptionCount] = useState(0);\n  // A queued toast is open by definition — it exists exactly as long as the\n  // manager keeps it in the list.\n  const [internalOpen, setInternalOpen] = useState(\n    managedId === undefined ? defaultOpen : true\n  );\n  const controlled = openProp !== undefined;\n  const open = openProp ?? internalOpen;\n  const openRef = useRef(open);\n  openRef.current = open;\n  const contentRef = useRef<HTMLDivElement | null>(null);\n  const contentId = `hui-toast-${safe}`;\n  const resolvedAssertive = toast?.assertive ?? assertive ?? false;\n  // Item → explicit prop → Provider default → the standalone 5000. Resolved\n  // rather than defaulted in the signature, because `duration = 5000` cannot\n  // tell \"the consumer asked for 5000\" from \"the consumer said nothing\", and\n  // a Provider default that a Root silently overrode would be a lie.\n  const duration = toast?.duration ?? durationProp ?? queue?.duration ?? 5000;\n  const dismissFromQueue = queue?.manager.dismiss;\n\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      if (!next && managedId !== undefined) {\n        dismissFromQueue?.(managedId);\n      }\n    },\n    [controlled, dismissFromQueue, managedId, onOpenChange]\n  );\n\n  const close = useCallback(() => setOpen(false), [setOpen]);\n\n  const registerTitle = useCallback(() => {\n    setTitleCount((count) => count + 1);\n    return () => setTitleCount((count) => count - 1);\n  }, []);\n  const registerDescription = useCallback(() => {\n    setDescriptionCount((count) => count + 1);\n    return () => setDescriptionCount((count) => count - 1);\n  }, []);\n\n  // WCAG 2.2.1 pause holds. A hold is a *count*, not a flag: pointer and\n  // keyboard focus can overlap on one toast (hover, then Tab to Close), and a\n  // flag would let the pointer leaving resume a timer the focused Close button\n  // is still holding. Content acquires and releases; Root only counts.\n  const [pauseHolds, setPauseHolds] = useState(0);\n  const pause = useCallback(() => setPauseHolds((holds) => holds + 1), []);\n  const resume = useCallback(\n    () => setPauseHolds((holds) => (holds > 0 ? holds - 1 : 0)),\n    []\n  );\n\n  // Duration auto-dismiss. `0` / `Infinity` / negative still mean \"never\".\n  const remainingRef = useRef(duration);\n\n  // ponytail: this reset must be its own effect declared BEFORE the timer.\n  // React runs every cleanup for a commit before any body, so the timer's\n  // cleanup has already debited the elapsed slice by the time this restores\n  // the full duration — a reopened toast gets a whole duration, while a\n  // pause/resume within one open run keeps the debited remainder.\n  useEffect(() => {\n    remainingRef.current = duration;\n  }, [duration, open]);\n\n  // The Provider's hold counts as this toast's hold. Hovering ANY toast in a\n  // viewport must freeze the whole stack: the one under the cursor is not the\n  // only one the user is reading, and letting the others expire on schedule\n  // would delete the very messages they hovered to catch up on.\n  const queuePaused = queue?.paused ?? false;\n\n  useEffect(() => {\n    if (!open || pauseHolds > 0 || queuePaused) {\n      return;\n    }\n    if (!Number.isFinite(duration) || duration <= 0) {\n      return;\n    }\n    const startedAt = Date.now();\n    const timer = setTimeout(\n      () => setOpen(false),\n      Math.max(remainingRef.current, 0)\n    );\n    return () => {\n      clearTimeout(timer);\n      remainingRef.current = Math.max(\n        remainingRef.current - (Date.now() - startedAt),\n        0\n      );\n    };\n  }, [duration, open, pauseHolds, queuePaused, setOpen]);\n\n  // Escape closes even though popover=manual does not light-dismiss. Layered:\n  // only the topmost transient surface consumes — one keystroke dismisses the\n  // newest toast, not every open toast — and an unfocused toast defers to\n  // Escape-consuming widgets the stack does not track (menu, select, popover).\n  // Consuming cancels the keydown so a dialog underneath survives.\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n    const removeFromStack = addToOpenStack(id, () => contentRef.current);\n    const onKeyDown = (event: KeyboardEvent) => {\n      if (event.defaultPrevented || event.key !== \"Escape\" || !isTopmost(id)) {\n        return;\n      }\n      const content = contentRef.current;\n      const focusWithin =\n        content !== null && content.contains(document.activeElement);\n      if (!focusWithin && hasEscapeConsumerOutsideStack()) {\n        return;\n      }\n      close();\n      event.preventDefault();\n      event.stopPropagation();\n    };\n    document.addEventListener(\"keydown\", onKeyDown);\n    return () => {\n      removeFromStack();\n      document.removeEventListener(\"keydown\", onKeyDown);\n    };\n  }, [close, id, open]);\n\n  const value = useMemo(\n    () => ({\n      announces: managedId === undefined,\n      assertive: resolvedAssertive,\n      close,\n      contentId,\n      contentRef,\n      descriptionId: `hui-toast-description-${safe}`,\n      hasDescription: descriptionCount > 0,\n      hasTitle: titleCount > 0,\n      open,\n      pause,\n      registerDescription,\n      registerTitle,\n      resume,\n      setOpen,\n      titleId: `hui-toast-title-${safe}`,\n      triggerId: `hui-toast-trigger-${safe}`\n    }),\n    [\n      close,\n      contentId,\n      descriptionCount,\n      managedId,\n      open,\n      pause,\n      registerDescription,\n      registerTitle,\n      resolvedAssertive,\n      resume,\n      safe,\n      setOpen,\n      titleCount\n    ]\n  );\n\n  return <ToastContext value={value}>{children}</ToastContext>;\n}\n\nexport type ToastViewportState = { open: boolean };\n\ntype ToastViewportNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  | \"aria-label\"\n  | \"aria-labelledby\"\n  | \"children\"\n  | \"className\"\n  | \"popover\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n>;\n\nexport type ToastViewportProps = HeidiIntrinsicHostProps<\n  ToastViewportState,\n  \"div\"\n> &\n  ToastViewportNativeProps & {\n    \"aria-label\"?: string;\n    \"aria-labelledby\"?: string;\n    children?: ReactNode;\n    ref?: Ref<HTMLDivElement>;\n  };\n\n/**\n * The region toasts are laid out in — APG's notification-landmark shape:\n * `role=\"region\"` with an accessible name, so a screen-reader user can jump\n * to the messages after hearing one instead of hunting for them.\n *\n * ponytail: the VIEWPORT carries `popover=manual`, not the toasts inside it.\n * A popover paints in the top layer, which takes it out of its ancestor's\n * flow entirely — three self-popovering toasts each pin themselves to the\n * same fixed corner and stack on top of one another. Hoisting the attribute\n * one level up buys the whole stack the top layer (nothing on the page can\n * z-index over it) while the toasts inside stay ordinary in-flow children the\n * viewport can lay out. `Toast.Content` keeps its own `popover` when there is\n * no viewport, so the standalone declarative toast is unchanged. Rejected:\n * offsetting each fixed toast by its index, which needs every toast's\n * measured height and is wrong for one frame after any of them changes.\n */\nexport function ToastViewport({\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  children,\n  className,\n  onBlurCapture,\n  onFocusCapture,\n  onPointerEnter,\n  onPointerLeave,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastViewportProps) {\n  const { manager, pauseAll, resumeAll } = useToastQueueContext(\"Viewport\");\n  const open = manager.toasts.length > 0;\n  const viewportRef = useRef<HTMLDivElement | null>(null);\n  const nativeTransitionRef = useRef(false);\n\n  useEffect(() => {\n    const element = viewportRef.current;\n    if (!element || typeof element.showPopover !== \"function\") {\n      return;\n    }\n    synchronizeNativePopoverOpen(element, open, {\n      transitionRef: nativeTransitionRef\n    });\n  }, [open]);\n\n  // Same per-source hold bookkeeping as Content: pointer and focus can overlap\n  // on the viewport (hover the stack, then Tab into a toast's Undo button), so\n  // a bare pause/resume pair per event would release a hold the other source\n  // still needs.\n  const pointerHoldRef = useRef(false);\n  const focusHoldRef = useRef(false);\n  const releaseHolds = useCallback(() => {\n    if (pointerHoldRef.current) {\n      pointerHoldRef.current = false;\n      resumeAll();\n    }\n    if (focusHoldRef.current) {\n      focusHoldRef.current = false;\n      resumeAll();\n    }\n  }, [resumeAll]);\n\n  // ponytail: releasing when the viewport empties is not belt-and-braces. The\n  // last toast dismissing under the cursor hides the viewport (display:none),\n  // and a hidden element does not emit pointerleave — the hold would strand\n  // and the NEXT toast added would never auto-dismiss for the rest of the\n  // session.\n  useEffect(() => {\n    if (!open) {\n      releaseHolds();\n    }\n    return releaseHolds;\n  }, [open, releaseHolds]);\n\n  return renderHeidiElement({\n    className: TOAST_CLASSES.viewport,\n    dataPart: \"toast-viewport\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      \"aria-label\":\n        ariaLabel ?? (ariaLabelledBy === undefined ? \"Notifications\" : undefined),\n      \"aria-labelledby\": ariaLabelledBy,\n      children: (\n        <ToastViewportContext value={true}>{children}</ToastViewportContext>\n      ),\n      \"data-state\": dataState(open),\n      onBlurCapture: composeHeidiEventHandlers(onBlurCapture, (event) => {\n        const next = event.relatedTarget;\n        if (next instanceof Node && viewportRef.current?.contains(next)) {\n          return;\n        }\n        if (focusHoldRef.current) {\n          focusHoldRef.current = false;\n          resumeAll();\n        }\n      }),\n      onFocusCapture: composeHeidiEventHandlers(onFocusCapture, () => {\n        if (!focusHoldRef.current) {\n          focusHoldRef.current = true;\n          pauseAll();\n        }\n      }),\n      onPointerEnter: composeHeidiEventHandlers(onPointerEnter, () => {\n        if (!pointerHoldRef.current) {\n          pointerHoldRef.current = true;\n          pauseAll();\n        }\n      }),\n      onPointerLeave: composeHeidiEventHandlers(onPointerLeave, () => {\n        if (pointerHoldRef.current) {\n          pointerHoldRef.current = false;\n          resumeAll();\n        }\n      }),\n      popover: \"manual\",\n      ref: mergeHeidiRefs(viewportRef, ref),\n      role: \"region\"\n    },\n    renderProps: { className, render, style },\n    state: { open }\n  });\n}\n\nexport type ToastTriggerState = { open: boolean };\n\ntype ToastTriggerNativeProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  | \"aria-controls\"\n  | \"aria-expanded\"\n  | \"children\"\n  | \"className\"\n  | \"id\"\n  | \"onClick\"\n  | \"ref\"\n  | \"style\"\n  | \"type\"\n>;\n\nexport type ToastTriggerProps = HeidiIntrinsicHostProps<\n  ToastTriggerState,\n  \"button\"\n> &\n  ToastTriggerNativeProps & {\n    children?: ReactNode;\n    onClick?: ComponentPropsWithoutRef<\"button\">[\"onClick\"];\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function ToastTrigger({\n  children,\n  className,\n  onClick,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastTriggerProps) {\n  const { contentId, open, setOpen, triggerId } = useToastContext(\"Trigger\");\n  return renderHeidiElement({\n    className: TOAST_CLASSES.trigger,\n    dataPart: \"toast-trigger\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      // ponytail: no `aria-expanded`. A toast is a status region, not a\n      // disclosure: the trigger only opens (onClick below), and duration\n      // auto-dismiss flips `open` back with no user action — an expanded\n      // state the button would then announce falsely. `aria-controls` still\n      // names the region. The attribute stays in the props Omit.\n      \"aria-controls\": contentId,\n      children,\n      id: triggerId,\n      onClick: composeHeidiEventHandlers(onClick, () => setOpen(true)),\n      ref,\n      type: \"button\"\n    },\n    renderProps: { className, render, style },\n    state: { open }\n  });\n}\n\nexport type ToastContentState = { open: boolean };\n\ntype ToastContentNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  | \"aria-describedby\"\n  | \"aria-labelledby\"\n  | \"aria-live\"\n  | \"children\"\n  | \"className\"\n  | \"id\"\n  | \"onBeforeToggle\"\n  | \"onToggle\"\n  | \"popover\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n>;\n\nexport type ToastContentProps = HeidiIntrinsicHostProps<\n  ToastContentState,\n  \"div\"\n> &\n  ToastContentNativeProps & {\n    children?: ReactNode;\n    onBeforeToggle?: ComponentPropsWithoutRef<\"div\">[\"onBeforeToggle\"];\n    onToggle?: ComponentPropsWithoutRef<\"div\">[\"onToggle\"];\n    ref?: Ref<HTMLDivElement>;\n  };\n\nexport function ToastContent({\n  children,\n  className,\n  onBeforeToggle,\n  onBlurCapture,\n  onFocusCapture,\n  onPointerEnter,\n  onPointerLeave,\n  onToggle,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastContentProps) {\n  const {\n    announces,\n    assertive,\n    contentId,\n    contentRef,\n    descriptionId,\n    hasDescription,\n    hasTitle,\n    open,\n    pause,\n    resume,\n    setOpen,\n    titleId\n  } = useToastContext(\"Content\");\n  const inViewport = useContext(ToastViewportContext);\n  const authoredTextParts = findAuthoredTextParts(children);\n  const [announcement, setAnnouncement] = useState(\"\");\n  const internalNativeTransitionRef = useRef(false);\n  const lastBeforeToggleRef = useRef<{\n    internal: boolean;\n    open: boolean;\n  } | null>(null);\n  const [nativeTransitionVersion, setNativeTransitionVersion] = useState(0);\n\n  // WCAG 2.2.1: pointer and keyboard focus each hold the auto-dismiss timer.\n  // Held state is per-source so a balanced release is possible; a bare\n  // pause()/resume() pair on each event would double-acquire on re-entry.\n  const pointerHoldRef = useRef(false);\n  const focusHoldRef = useRef(false);\n  const releaseHolds = useCallback(() => {\n    if (pointerHoldRef.current) {\n      pointerHoldRef.current = false;\n      resume();\n    }\n    if (focusHoldRef.current) {\n      focusHoldRef.current = false;\n      resume();\n    }\n  }, [resume]);\n\n  // ponytail: a closing toast is hidden by the popover API, not unmounted, and\n  // an element hidden under the cursor does not reliably emit pointerleave.\n  // Releasing on close (and on unmount) is what stops a stranded hold from\n  // making the NEXT open of this toast never auto-dismiss.\n  useEffect(() => {\n    if (!open) {\n      releaseHolds();\n    }\n    return releaseHolds;\n  }, [open, releaseHolds]);\n\n  const synchronizeNativeOpen = useCallback(\n    (element: HTMLDivElement, next: boolean) => {\n      synchronizeNativePopoverOpen(element, next, {\n        transitionRef: internalNativeTransitionRef\n      });\n    },\n    []\n  );\n\n  useEffect(() => {\n    const element = contentRef.current;\n    // `inViewport` is checked against the DOM, not just the context: without\n    // the `popover` attribute, showPopover() throws InvalidStateError, and the\n    // shared sync would swallow it on every open forever.\n    if (\n      inViewport ||\n      !element ||\n      !element.hasAttribute(\"popover\") ||\n      typeof element.showPopover !== \"function\"\n    ) {\n      return;\n    }\n    synchronizeNativeOpen(element, open);\n  }, [\n    contentRef,\n    inViewport,\n    nativeTransitionVersion,\n    open,\n    synchronizeNativeOpen\n  ]);\n\n  // ponytail: the announcement is a text mutation inside a region that was\n  // already rendered — that is the whole fix. MDN's live-region rule is that\n  // the role must be in the tree BEFORE the content changes (\"start with an\n  // empty live region, then — in a separate step — change the content inside\n  // it\"); the popover goes from display:none to visible carrying its text in\n  // one operation, which is exactly the case that never announces. This runs\n  // as a passive effect, so even a toast that mounts already-open renders the\n  // empty region in the first commit and fills it in the next.\n  //\n  // Rejected: rendering Title/Description into the announcer instead of their\n  // text. Their ids are wired to the visible region, and duplicating live\n  // nodes duplicates ids and focusables. Rejected: reading the text during\n  // render — the DOM does not exist yet on first paint, and it is not a render\n  // input.\n  useEffect(() => {\n    if (!announces) {\n      return;\n    }\n    const element = contentRef.current;\n    setAnnouncement(open && element ? announcementTextFrom(element) : \"\");\n  }, [announces, contentRef, open]);\n\n  const handleBeforeToggle = composeHeidiEventHandlers(\n    onBeforeToggle,\n    (event: ToggleEvent<HTMLDivElement>) => {\n      if (event.target !== event.currentTarget) {\n        return;\n      }\n      lastBeforeToggleRef.current = {\n        internal: internalNativeTransitionRef.current,\n        open: event.newState === \"open\"\n      };\n    }\n  );\n\n  // ponytail: the consumer handler runs unconditionally and first, exactly as\n  // `composeHeidiEventHandlers` promises — the descendant-target filter is\n  // library behavior, not a reason to withhold the event. Filtering before the\n  // callback (the previous shape) silently swallowed every bubbled toggle and\n  // also ran the library body after a consumer had already vetoed it.\n  const handleToggle = composeHeidiEventHandlers(\n    onToggle,\n    (event: ToggleEvent<HTMLDivElement>) => {\n      if (event.target !== event.currentTarget) {\n        return;\n      }\n      const nativeOpen = event.newState === \"open\";\n      const transition = lastBeforeToggleRef.current;\n      lastBeforeToggleRef.current = null;\n      const internallySynchronized =\n        transition?.internal === true && transition.open === nativeOpen;\n      if (!internallySynchronized) {\n        setOpen(nativeOpen);\n        setNativeTransitionVersion((version) => version + 1);\n      }\n    }\n  );\n\n  const announcerProps = toastAnnouncerProps(assertive);\n\n  const content = renderHeidiElement({\n    className: TOAST_CLASSES.content,\n    dataPart: \"toast-content\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      \"aria-describedby\":\n        hasDescription || authoredTextParts.description\n          ? descriptionId\n          : undefined,\n      \"aria-labelledby\":\n        hasTitle || authoredTextParts.title ? titleId : undefined,\n      // ponytail: the visible surface is a status REGION, not the live region\n      // — role=status keeps it findable and named, aria-live=off keeps it\n      // silent. Rejected: leaving role=alert here for `assertive` and adding\n      // the announcer anyway. ATs special-case alert on insertion, so that\n      // pairing announces the same toast twice; the polite variant, which is\n      // the default, still announced nothing. Splitting is the only shape\n      // where both paths behave the same way.\n      \"aria-live\": \"off\",\n      children,\n      \"data-state\": dataState(open),\n      id: contentId,\n      onBeforeToggle: handleBeforeToggle,\n      onBlurCapture: composeHeidiEventHandlers(onBlurCapture, (event) => {\n        const next = event.relatedTarget;\n        if (next instanceof Node && contentRef.current?.contains(next)) {\n          return;\n        }\n        if (focusHoldRef.current) {\n          focusHoldRef.current = false;\n          resume();\n        }\n      }),\n      onFocusCapture: composeHeidiEventHandlers(onFocusCapture, () => {\n        if (!focusHoldRef.current) {\n          focusHoldRef.current = true;\n          pause();\n        }\n      }),\n      onPointerEnter: composeHeidiEventHandlers(onPointerEnter, () => {\n        if (!pointerHoldRef.current) {\n          pointerHoldRef.current = true;\n          pause();\n        }\n      }),\n      onPointerLeave: composeHeidiEventHandlers(onPointerLeave, () => {\n        if (pointerHoldRef.current) {\n          pointerHoldRef.current = false;\n          resume();\n        }\n      }),\n      onToggle: handleToggle,\n      // Inside a viewport the viewport owns the top layer; see ToastViewport.\n      popover: inViewport ? undefined : \"manual\",\n      ref: mergeHeidiRefs(contentRef, ref),\n      role: \"status\"\n    },\n    renderProps: { className, render, style },\n    state: { open }\n  });\n\n  // The pause holds (WCAG 2.2.1) and `contentRef` stay on the visible surface\n  // untouched: an off-screen announcer can take neither pointer nor focus, so\n  // splitting the live region out changes nothing about hover-pause or the\n  // focus-leave guard above.\n  if (!announces) {\n    // The Provider's persistent regions already spoke this one, at the moment\n    // it became visible. Mounting a second region here would announce the same\n    // message twice — and mount it in the same commit as the toast, which is\n    // the mutation-on-insert that never announces in the first place.\n    return content;\n  }\n  return (\n    <>\n      <div\n        {...announcerProps}\n        data-hui-part=\"toast-announcer\"\n        style={TOAST_ANNOUNCER_STYLE}\n      >\n        {announcement}\n      </div>\n      {content}\n    </>\n  );\n}\n\nexport type ToastTitleState = Record<string, never>;\n\ntype ToastTitleNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"children\" | \"className\" | \"id\" | \"ref\" | \"style\"\n>;\n\nexport type ToastTitleProps = HeidiIntrinsicHostProps<\n  ToastTitleState,\n  \"div\"\n> &\n  ToastTitleNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLDivElement>;\n  };\n\nexport function ToastTitle({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastTitleProps) {\n  const { registerTitle, titleId } = useToastContext(\"Title\");\n  useEffect(() => registerTitle(), [registerTitle]);\n  return renderHeidiElement({\n    className: TOAST_CLASSES.title,\n    dataPart: \"toast-title\",\n    element: \"div\",\n    props: { ...nativeProps, children, id: titleId, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type ToastDescriptionState = Record<string, never>;\n\ntype ToastDescriptionNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"children\" | \"className\" | \"id\" | \"ref\" | \"style\"\n>;\n\nexport type ToastDescriptionProps = HeidiIntrinsicHostProps<\n  ToastDescriptionState,\n  \"div\"\n> &\n  ToastDescriptionNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLDivElement>;\n  };\n\nexport function ToastDescription({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastDescriptionProps) {\n  const { descriptionId, registerDescription } = useToastContext(\"Description\");\n  useEffect(() => registerDescription(), [registerDescription]);\n  return renderHeidiElement({\n    className: TOAST_CLASSES.description,\n    dataPart: \"toast-description\",\n    element: \"div\",\n    props: { ...nativeProps, children, id: descriptionId, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type ToastActionState = Record<string, never>;\n\ntype ToastActionNativeProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  \"children\" | \"className\" | \"onClick\" | \"ref\" | \"style\" | \"type\"\n>;\n\nexport type ToastActionProps = HeidiIntrinsicHostProps<\n  ToastActionState,\n  \"button\"\n> &\n  ToastActionNativeProps & {\n    children?: ReactNode;\n    onClick?: ComponentPropsWithoutRef<\"button\">[\"onClick\"];\n    ref?: Ref<HTMLButtonElement>;\n  };\n\n/**\n * The toast's affordance — \"Undo\", \"Retry\", \"View\". Dismisses after the\n * consumer's handler runs.\n *\n * ponytail: no `closeOnClick` prop. `composeHeidiEventHandlers` already gives\n * every part in this package one veto — call `event.preventDefault()` in your\n * own `onClick` and the toast stays open (an action that starts an async job\n * and wants to keep the toast alive to report the result does exactly that).\n * A boolean prop would be a second, part-specific way to express the same\n * thing, and the two would have to be reconciled when they disagree.\n *\n * Unlike Close, the action's label is NOT stripped from the declarative\n * toast's announcement: \"Dismiss\" is on every toast and carries no\n * information, while \"Undo\" is the reason the message was worth announcing.\n */\nexport function ToastAction({\n  children,\n  className,\n  onClick,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastActionProps) {\n  const { close } = useToastContext(\"Action\");\n  return renderHeidiElement({\n    className: TOAST_CLASSES.action,\n    dataPart: \"toast-action\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      children,\n      onClick: composeHeidiEventHandlers(onClick, close),\n      ref,\n      type: \"button\"\n    },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type ToastCloseState = Record<string, never>;\n\ntype ToastCloseNativeProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  | \"aria-label\"\n  | \"children\"\n  | \"className\"\n  | \"onClick\"\n  | \"ref\"\n  | \"style\"\n  | \"type\"\n>;\n\nexport type ToastCloseProps = HeidiIntrinsicHostProps<\n  ToastCloseState,\n  \"button\"\n> &\n  ToastCloseNativeProps & {\n    \"aria-label\"?: string;\n    children?: ReactNode;\n    onClick?: ComponentPropsWithoutRef<\"button\">[\"onClick\"];\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function ToastClose({\n  \"aria-label\": ariaLabel,\n  children,\n  className,\n  onClick,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToastCloseProps) {\n  const { close } = useToastContext(\"Close\");\n  return renderHeidiElement({\n    className: TOAST_CLASSES.close,\n    dataPart: \"toast-close\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      \"aria-label\":\n        ariaLabel ?? (typeof children === \"string\" ? undefined : \"Dismiss\"),\n      children: children ?? \"Dismiss\",\n      onClick: composeHeidiEventHandlers(onClick, close),\n      ref,\n      type: \"button\"\n    },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport const Toast = {\n  Action: ToastAction,\n  Close: ToastClose,\n  Content: ToastContent,\n  Description: ToastDescription,\n  Provider: ToastProvider,\n  Root: ToastRoot,\n  Title: ToastTitle,\n  Trigger: ToastTrigger,\n  Viewport: ToastViewport\n} as const;\n",
      "path": "packages/heidi-ui/src/toast/toast.tsx",
      "target": "components/ui/heidi/toast/toast.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui toast — STRUCTURAL CSS only. No --hui-*.\n * Fixed viewport corner; popover=manual top layer (no light-dismiss).\n * The popover attribute owns closed display:none until showPopover().\n */\n\n@layer heidi-ui-base {\n  .hui-toast-content {\n    --_hui-toast-viewport-gutter: 1rem;\n\n    box-sizing: border-box;\n    inset: auto;\n    inset-block-end: var(--_hui-toast-viewport-gutter);\n    inset-inline-end: var(--_hui-toast-viewport-gutter);\n    margin: 0;\n    /* Corner-inset panel: the gutter doubles so the far edge keeps it too. */\n    max-block-size: calc(100dvb - 2 * var(--_hui-toast-viewport-gutter));\n    max-inline-size: calc(100dvi - 2 * var(--_hui-toast-viewport-gutter));\n    overflow: auto;\n    overscroll-behavior: contain;\n    position: fixed;\n    scrollbar-gutter: stable;\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n\n  /* `[popover]` is the whole point of the compound: a toast inside a viewport\n     is NOT its own popover (the viewport owns the top layer for the stack), so\n     :popover-open never matches on it and an unscoped rule would hide every\n     queued toast permanently. The !important survives the theme layer, which\n     sets display: grid on the same class. */\n  .hui-toast-content:not(:popover-open)[popover] {\n    display: none !important;\n  }\n\n  .hui-toast-viewport {\n    --_hui-toast-viewport-gap: 0.75rem;\n    --_hui-toast-viewport-gutter: 1rem;\n\n    border: 0;\n    box-sizing: border-box;\n    gap: var(--_hui-toast-viewport-gap);\n    inset: auto;\n    inset-block-end: var(--_hui-toast-viewport-gutter);\n    inset-inline-end: var(--_hui-toast-viewport-gutter);\n    margin: 0;\n    /* Corner-inset region: the gutter doubles so the far edge keeps it too. */\n    max-block-size: calc(100dvb - 2 * var(--_hui-toast-viewport-gutter));\n    max-inline-size: calc(100dvi - 2 * var(--_hui-toast-viewport-gutter));\n    overflow: auto;\n    overscroll-behavior: contain;\n    padding: 0;\n    position: fixed;\n    scrollbar-gutter: stable;\n  }\n\n  .hui-toast-viewport:popover-open {\n    display: grid;\n  }\n\n  .hui-toast-viewport:not(:popover-open) {\n    display: none !important;\n  }\n\n  /* A toast the viewport lays out must drop the corner pinning it uses when it\n     is its own popover, or every toast in the stack would sit on the corner\n     the viewport already occupies. */\n  .hui-toast-viewport .hui-toast-content {\n    inset: auto;\n    max-block-size: none;\n    max-inline-size: none;\n    overflow: visible;\n    position: static;\n    scrollbar-gutter: auto;\n  }\n}\n",
      "path": "packages/heidi-ui/src/toast/toast.base.css",
      "target": "components/ui/heidi/toast/toast.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui toast — VISUAL theme (opt-in). Consumes --hui-*.\n */\n\n@layer heidi-ui {\n  .hui-toast-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-toast-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  /* Floating-panel recipe, same as menu/popover/dialog: bg-raised (bg-elevated\n     inverts a rung below them in dark), no CSS border (the ladder draws the\n     hairline), radius-2xl. */\n  .hui-toast-content {\n    background: var(--hui-color-bg-raised);\n    border: 0;\n    border-radius: var(--hui-radius-2xl);\n    box-shadow: var(--hui-shadow-surface-lg);\n    color: var(--hui-color-fg-default);\n    display: grid;\n    gap: var(--hui-space-1);\n    inset-block-end: var(--hui-space-4);\n    inset-inline-end: var(--hui-space-4);\n    max-width: 22rem;\n    opacity: 0;\n    padding: var(--hui-space-3);\n    transform: translateY(var(--hui-space-2));\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-toast-content[data-state=\"open\"],\n  .hui-toast-content:popover-open {\n    opacity: 1;\n    transform: none;\n  }\n\n  .hui-toast-content[data-state=\"closed\"] {\n    opacity: 0;\n  }\n\n  @starting-style {\n    .hui-toast-content:popover-open {\n      opacity: 0;\n      transform: translateY(var(--hui-space-2));\n    }\n  }\n\n  .hui-toast-title {\n    color: var(--hui-color-fg-strong);\n    font-weight: var(--hui-font-weight-medium);\n  }\n\n  .hui-toast-description {\n    color: var(--hui-color-fg-muted);\n    font-size: var(--hui-text-body-size);\n  }\n\n  .hui-toast-action {\n    background: transparent;\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    justify-self: start;\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-toast-action: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-toast-close {\n    background: transparent;\n    border: var(--hui-border-width) var(--hui-border-style) transparent;\n    border-radius: var(--hui-radius-md);\n    color: var(--hui-color-fg-muted);\n    cursor: pointer;\n    font: inherit;\n    justify-self: end;\n    min-block-size: calc(var(--hui-space-3) * 2);\n    padding: var(--hui-space-0-5) var(--hui-space-1);\n  }\n\n  .hui-toast-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  @media (prefers-reduced-motion: reduce) {\n    .hui-toast-content {\n      transition-duration: 0s;\n    }\n  }\n\n  @media (forced-colors: active) {\n    .hui-toast-action,\n    .hui-toast-trigger {\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-toast-content {\n      border: var(--hui-border-width) solid CanvasText;\n    }\n\n    .hui-toast-action:focus-visible,\n    .hui-toast-trigger:focus-visible,\n    .hui-toast-close:focus-visible {\n      outline-color: Highlight;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/toast/toast.theme.css",
      "target": "components/ui/heidi/toast/toast.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui toast — aggregator (base + Heidi adapter + theme).\n */\n\n@import \"./toast.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./toast.theme.css\";\n",
      "path": "packages/heidi-ui/src/toast/toast.css",
      "target": "components/ui/heidi/toast/toast.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/toast/toast.base.css + packages/heidi-ui/src/toast/toast.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const TOAST_CLASSES = {\n  action: \"hui-toast-action\",\n  close: \"hui-toast-close\",\n  content: \"hui-toast-content\",\n  description: \"hui-toast-description\",\n  title: \"hui-toast-title\",\n  trigger: \"hui-toast-trigger\",\n  viewport: \"hui-toast-viewport\",\n} as const;\n",
      "path": "packages/heidi-ui/src/toast/toast.classes.generated.ts",
      "target": "components/ui/heidi/toast/toast.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from toast.anatomy.json + toast.base.css + toast.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type ToastState = \"closed\" | \"open\";\n\nexport const TOAST_ANATOMY = {\n  \"component\": \"toast\",\n  \"description\": \"Transient status message on the top layer: [popover=manual] (no light-dismiss). Announcement is owned by a persistent off-screen live region rendered beside the popover — role=status/aria-live=polite by default, role=alert/aria-live=assertive when assertive — because a live region only announces mutations made while it is already in the accessibility tree. Controlled open + optional duration auto-dismiss. Close and Action buttons. Fixed viewport corner (not pointer-anchored). Composition via renderHeidiElement. Two non-DOM pieces have no part entry because they render no styled element: Toast.Provider (queue owner — configurable limit and default duration; hosts the two persistent announcer regions, one polite, one assertive, so a queued toast mutates a region that has been in the accessibility tree since mount) and useToast() (the imperative handle: add / update / dismiss / dismissAll plus the visible `toasts` and the waiting `queued`).\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"action\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-action\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-action\",\n      \"description\": \"The toast's affordance (Undo, Retry, View). Runs the consumer handler, then dismisses — preventDefault in that handler keeps the toast open. Unlike Close, its label stays in the declarative toast's announcement.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\"\n      ],\n      \"states\": {}\n    },\n    \"close\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-label\"\n        ],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-close\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-close\",\n      \"description\": \"Dismiss button; closes the toast.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"aria-label\",\n        \"className\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\"\n      ],\n      \"states\": {}\n    },\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-describedby\",\n          \"aria-labelledby\",\n          \"aria-live\"\n        ],\n        \"role\": \"status\"\n      },\n      \"class\": \"hui-toast-content\",\n      \"css\": {\n        \"structural\": [\n          \"inset\",\n          \"margin\",\n          \"position\",\n          \"transition-behavior\",\n          \"transition-property\"\n        ]\n      },\n      \"dataPart\": \"toast-content\",\n      \"description\": \"The visible surface. Standalone it is the [popover=manual] element itself, fixed to the viewport corner; inside Toast.Viewport it drops the popover attribute and is laid out by the viewport, which owns the top layer for the whole stack. role=status names and exposes it as a status region, aria-live=off keeps it silent: a popover is display:none until showPopover(), so announcing from here is exactly the mutation-on-reveal that assistive technology drops. The off-screen announcer — the Root's own when standalone, the Provider's when queued — does the announcing.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"onBeforeToggle\",\n        \"onToggle\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":popover-open\"\n      ],\n      \"states\": {\n        \"data-state\": [\n          \"closed\",\n          \"open\"\n        ]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-description\",\n      \"description\": \"Supporting body text; id wired to content via aria-describedby.\",\n      \"element\": \"div\",\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-toast-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-title\",\n      \"description\": \"Primary toast heading; id wired to content via aria-labelledby.\",\n      \"element\": \"div\",\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        ],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-trigger\",\n      \"description\": \"Optional button that opens the toast. Open-only, and no aria-expanded: a toast is a status region, not a disclosure, and auto-dismiss would flip an announced expanded state with no user action.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"onClick\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\"\n      ],\n      \"states\": {}\n    },\n    \"viewport\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-label\",\n          \"aria-labelledby\"\n        ],\n        \"role\": \"region\"\n      },\n      \"class\": \"hui-toast-viewport\",\n      \"css\": {\n        \"structural\": [\n          \"gap\",\n          \"inset\",\n          \"margin\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"position\"\n        ]\n      },\n      \"dataPart\": \"toast-viewport\",\n      \"description\": \"The positioned region queued toasts render into — APG's notification-landmark shape: role=region with an accessible name (default aria-label \\\"Notifications\\\"), so a screen-reader user can navigate to the messages instead of hunting for them. It carries [popover=manual] for the whole stack and is shown only while a toast is visible; hovering or focusing it takes a Provider-wide WCAG 2.2.1 hold that freezes every queued toast's timer, not only the one under the cursor.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"aria-label\",\n        \"aria-labelledby\",\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":popover-open\"\n      ],\n      \"states\": {\n        \"data-state\": [\n          \"closed\",\n          \"open\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"assertive\",\n    \"defaultOpen\",\n    \"duration\",\n    \"onOpenChange\",\n    \"open\",\n    \"toast\"\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-duration-fast\",\n    \"--hui-ease-out\",\n    \"--hui-focus-ring-offset\",\n    \"--hui-focus-ring-width\",\n    \"--hui-font-weight-medium\",\n    \"--hui-radius-2xl\",\n    \"--hui-radius-md\",\n    \"--hui-shadow-surface-lg\",\n    \"--hui-space-0-5\",\n    \"--hui-space-1\",\n    \"--hui-space-1-5\",\n    \"--hui-space-2\",\n    \"--hui-space-3\",\n    \"--hui-space-4\",\n    \"--hui-text-body-size\"\n  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/toast/toast.anatomy.generated.ts",
      "target": "components/ui/heidi/toast/toast.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"toast\",\n  \"description\": \"Transient status message on the top layer: [popover=manual] (no light-dismiss). Announcement is owned by a persistent off-screen live region rendered beside the popover — role=status/aria-live=polite by default, role=alert/aria-live=assertive when assertive — because a live region only announces mutations made while it is already in the accessibility tree. Controlled open + optional duration auto-dismiss. Close and Action buttons. Fixed viewport corner (not pointer-anchored). Composition via renderHeidiElement. Two non-DOM pieces have no part entry because they render no styled element: Toast.Provider (queue owner — configurable limit and default duration; hosts the two persistent announcer regions, one polite, one assertive, so a queued toast mutates a region that has been in the accessibility tree since mount) and useToast() (the imperative handle: add / update / dismiss / dismissAll plus the visible `toasts` and the waiting `queued`).\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"action\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-action\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-action\",\n      \"description\": \"The toast's affordance (Undo, Retry, View). Runs the consumer handler, then dismisses — preventDefault in that handler keeps the toast open. Unlike Close, its label stays in the declarative toast's announcement.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"onClick\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":focus-visible\"],\n      \"states\": {}\n    },\n    \"close\": {\n      \"aria\": {\n        \"owns\": [\"aria-label\"],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-close\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-close\",\n      \"description\": \"Dismiss button; closes the toast.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\"aria-label\", \"className\", \"onClick\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":focus-visible\"],\n      \"states\": {}\n    },\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\"aria-describedby\", \"aria-labelledby\", \"aria-live\"],\n        \"role\": \"status\"\n      },\n      \"class\": \"hui-toast-content\",\n      \"css\": {\n        \"structural\": [\n          \"inset\",\n          \"margin\",\n          \"position\",\n          \"transition-behavior\",\n          \"transition-property\"\n        ]\n      },\n      \"dataPart\": \"toast-content\",\n      \"description\": \"The visible surface. Standalone it is the [popover=manual] element itself, fixed to the viewport corner; inside Toast.Viewport it drops the popover attribute and is laid out by the viewport, which owns the top layer for the whole stack. role=status names and exposes it as a status region, aria-live=off keeps it silent: a popover is display:none until showPopover(), so announcing from here is exactly the mutation-on-reveal that assistive technology drops. The off-screen announcer — the Root's own when standalone, the Provider's when queued — does the announcing.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"onBeforeToggle\", \"onToggle\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":popover-open\"],\n      \"states\": {\n        \"data-state\": [\"closed\", \"open\"]\n      }\n    },\n    \"description\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-description\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-description\",\n      \"description\": \"Supporting body text; id wired to content via aria-describedby.\",\n      \"element\": \"div\",\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-toast-title\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-title\",\n      \"description\": \"Primary toast heading; id wired to content via aria-labelledby.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"trigger\": {\n      \"aria\": {\n        \"owns\": [\"aria-controls\"],\n        \"role\": null\n      },\n      \"class\": \"hui-toast-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"toast-trigger\",\n      \"description\": \"Optional button that opens the toast. Open-only, and no aria-expanded: a toast is a status region, not a disclosure, and auto-dismiss would flip an announced expanded state with no user action.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"onClick\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":focus-visible\"],\n      \"states\": {}\n    },\n    \"viewport\": {\n      \"aria\": {\n        \"owns\": [\"aria-label\", \"aria-labelledby\"],\n        \"role\": \"region\"\n      },\n      \"class\": \"hui-toast-viewport\",\n      \"css\": {\n        \"structural\": [\n          \"gap\",\n          \"inset\",\n          \"margin\",\n          \"max-block-size\",\n          \"max-inline-size\",\n          \"overflow\",\n          \"position\"\n        ]\n      },\n      \"dataPart\": \"toast-viewport\",\n      \"description\": \"The positioned region queued toasts render into — APG's notification-landmark shape: role=region with an accessible name (default aria-label \\\"Notifications\\\"), so a screen-reader user can navigate to the messages instead of hunting for them. It carries [popover=manual] for the whole stack and is shown only while a toast is visible; hovering or focusing it takes a Provider-wide WCAG 2.2.1 hold that freezes every queued toast's timer, not only the one under the cursor.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\"aria-label\", \"aria-labelledby\", \"className\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [\":popover-open\"],\n      \"states\": {\n        \"data-state\": [\"closed\", \"open\"]\n      }\n    }\n  },\n  \"rootProps\": [\"assertive\", \"defaultOpen\", \"duration\", \"onOpenChange\", \"open\", \"toast\"]\n}\n",
      "path": "packages/heidi-ui/src/toast/toast.anatomy.json",
      "target": "components/ui/heidi/toast/toast.anatomy.json",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared Escape layering for transient top-layer surfaces (tooltip, toast,\n * hover-card). Each surface registers while open; only the topmost entry may\n * consume an Escape keydown, and consuming cancels the event\n * (preventDefault + stopPropagation) so one keystroke never also dismisses\n * the layer underneath — native <dialog closedby=\"closerequest\"> hosts only\n * close on an uncancelled Escape keydown.\n */\n\ntype EscapeLayer = {\n  getElement: () => Element | null;\n  id: string;\n};\n\nconst openEscapeLayers: EscapeLayer[] = [];\n\nfunction removeFromStack(id: string): void {\n  const index = openEscapeLayers.findIndex((layer) => layer.id === id);\n  if (index >= 0) {\n    openEscapeLayers.splice(index, 1);\n  }\n}\n\n/**\n * Register an open surface (re-registering moves it to the top). Returns the\n * stack cleanup. `getElement` identifies the surface's top-layer element so\n * `hasEscapeConsumerOutsideStack` can tell stack popovers from foreign ones.\n */\nexport function addToOpenStack(\n  id: string,\n  getElement: () => Element | null = () => null\n): () => void {\n  removeFromStack(id);\n  openEscapeLayers.push({ getElement, id });\n  return () => removeFromStack(id);\n}\n\nexport function isTopmost(id: string): boolean {\n  return openEscapeLayers.at(-1)?.id === id;\n}\n\n/**\n * True when an open popover that does not participate in this stack (menu,\n * select, context-menu, navigation-menu, popover) could consume the same\n * Escape keystroke — an unfocused toast must defer to it.\n * ponytail: open `<dialog>` hosts are deliberately NOT counted. Dialogs close\n * via a native close request only when the Escape keydown goes uncancelled,\n * and a transient layer consuming (cancelling) the keydown is exactly what\n * shields them; counting them would leave an unfocused toast unclosable\n * whenever any modal is open.\n */\nexport function hasEscapeConsumerOutsideStack(): boolean {\n  const stackElements = new Set<Element>();\n  for (const layer of openEscapeLayers) {\n    const element = layer.getElement();\n    if (element !== null) {\n      stackElements.add(element);\n    }\n  }\n  for (const element of document.querySelectorAll(\":popover-open\")) {\n    if (!stackElements.has(element)) {\n      return true;\n    }\n  }\n  return false;\n}\n",
      "path": "packages/heidi-ui/src/_internal/escape-layer.ts",
      "target": "components/ui/heidi/_internal/escape-layer.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Pushing React's open state onto the native popover, once (P5).\n *\n * Popover, Toast, Select, NavigationMenu and Menu (root + submenu) each carried\n * this block. It exists because `showPopover()`/`hidePopover()` fire `toggle`\n * synchronously, and the `toggle` listener is also what reconciles NATIVE\n * dismissal (Esc, light dismiss) back into React. Without a marker, every\n * React-driven open would look like a user-driven one and echo a redundant\n * open-change request back to the owner.\n *\n * ponytail: six of the seven copies the audit counted share this boolean-flag\n * model and differ only in how they SHOW — four call `element.showPopover()`,\n * Menu's two call `showPopoverFrom(element, trigger)` so the popover anchors to\n * its trigger. That is the `show` option and nothing else.\n *\n * ContextMenu is the seventh and does NOT adopt this. It marks transitions by\n * pushing onto a queue that a later `toggle` drains, rather than by raising a\n * flag it lowers in `finally`. Those answer \"is this transition ours?\" at\n * different times — synchronously inside the call, versus whenever the event\n * arrives — and a nested context menu can have more than one transition in\n * flight, which is what the queue is for. Forcing it into the flag model would\n * be a rewrite of its reentrancy handling to make a count read 1 instead of 7,\n * which is the same trade P6 and P9 declined.\n */\n\nexport type NativePopoverSyncOptions = {\n  /**\n   * Anchored show, for popovers that position against a trigger. Defaults to\n   * `element.showPopover()`.\n   */\n  show?: (element: HTMLElement) => void;\n  /**\n   * Raised for the duration of the native call so the component's own `toggle`\n   * listener can tell its own transition from a user's.\n   */\n  transitionRef: { current: boolean };\n};\n\nexport function synchronizeNativePopoverOpen(\n  element: HTMLElement,\n  next: boolean,\n  { show, transitionRef }: NativePopoverSyncOptions\n): void {\n  if (element.matches(\":popover-open\") === next) {\n    return;\n  }\n  transitionRef.current = true;\n  try {\n    if (next) {\n      if (show) {\n        show(element);\n      } else {\n        element.showPopover();\n      }\n    } else {\n      element.hidePopover();\n    }\n  } catch {\n    // A detached or already-transitioning popover can reject the request. The\n    // next state/effect pass retries against React's authority, so swallowing\n    // it here loses nothing — and throwing would take down a render.\n  } finally {\n    // `finally`, not the end of `try`: a rejected request must still lower the\n    // flag, or the component would treat every later native transition as its\n    // own and stop reconciling user dismissal for the rest of its life.\n    transitionRef.current = false;\n  }\n}\n",
      "path": "packages/heidi-ui/src/_internal/native-popover-sync.ts",
      "target": "components/ui/heidi/_internal/native-popover-sync.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared host rendering for heidi-ui parts — Base-UI-shaped composition\n * (docs/HEIDI-UI-HEADLESS.md § 4 / HEIDI-UI.md § 4.5):\n *   - className / style as value or (state) => value\n *   - render as ReactElement or (props, state) => ReactElement\n *   - ref forwarding\n *   - data-hui-part always set (stable unstyled hook)\n *\n * Structural inline styles (e.g. anchorName) are merged last so a consumer\n * style override cannot drop platform wiring.\n */\n\nimport {\n  cloneElement,\n  createElement,\n  isValidElement,\n  type CSSProperties,\n  type ComponentPropsWithRef,\n  type HTMLAttributes,\n  type ReactElement,\n  type ReactNode,\n  type Ref,\n  type RefCallback\n} from \"react\";\n\nexport type HeidiClassName<State> = string | ((state: State) => string | undefined) | undefined;\n\nexport type HeidiStyle<State> =\n  | CSSProperties\n  | ((state: State) => CSSProperties | undefined)\n  | undefined;\n\nexport type HeidiRenderFn<State, Props> = (\n  props: Props,\n  state: State\n) => ReactElement;\n\nexport type HeidiRender<State, Props> =\n  | ReactElement\n  | HeidiRenderFn<State, Props>\n  | undefined;\n\ntype DefaultRenderProps = HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> };\n\nexport type HeidiHostProps<\n  State,\n  RenderProps extends object = DefaultRenderProps\n> = {\n  className?: HeidiClassName<State>;\n  render?: HeidiRender<State, RenderProps>;\n  style?: HeidiStyle<State>;\n};\n\nexport type HeidiIntrinsicHostProps<\n  State,\n  Tag extends keyof HTMLElementTagNameMap\n> = HeidiHostProps<State, ComponentPropsWithRef<Tag>>;\n\nfunction resolveClassName<State>(\n  className: HeidiClassName<State>,\n  state: State\n): string | undefined {\n  return typeof className === \"function\" ? className(state) : className;\n}\n\nfunction resolveStyle<State>(style: HeidiStyle<State>, state: State): CSSProperties | undefined {\n  return typeof style === \"function\" ? style(state) : style;\n}\n\nfunction mergeClassNames(...parts: Array<string | undefined>): string | undefined {\n  const merged = parts.filter(Boolean).join(\" \");\n  return merged.length > 0 ? merged : undefined;\n}\n\n/**\n * Compose a consumer event with library behavior. Consumers run first and may\n * cancel the component behavior with `event.preventDefault()`. Heidi keeps\n * this native-event convention intentionally: it avoids a second branded event\n * API while still making async/controlled actions vetoable.\n */\nexport function composeHeidiEventHandlers<Event extends { defaultPrevented: boolean }>(\n  consumer: ((event: Event) => void) | undefined,\n  library: (event: Event) => void\n): (event: Event) => void {\n  return (event) => {\n    consumer?.(event);\n    if (!event.defaultPrevented) {\n      library(event);\n    }\n  };\n}\n\n/** React 19-safe ref fan-out, including callback-ref cleanup functions. */\nexport function mergeHeidiRefs<Element>(\n  ...inputRefs: Array<Ref<Element> | undefined>\n): Ref<Element> | undefined {\n  const refs = Array.from(\n    new Set(\n      inputRefs.filter(\n        (ref): ref is Exclude<Ref<Element>, null> => ref != null\n      )\n    )\n  );\n  if (refs.length === 0) {\n    return undefined;\n  }\n  if (refs.length === 1) {\n    return refs[0];\n  }\n\n  let cache = mergedRefCache;\n  for (const ref of refs) {\n    const key = ref as object;\n    let child = cache.children.get(key);\n    if (!child) {\n      child = { children: new WeakMap() };\n      cache.children.set(key, child);\n    }\n    cache = child;\n  }\n  if (cache.callback) {\n    return cache.callback as RefCallback<Element>;\n  }\n\n  const callback: RefCallback<Element> = (node) => {\n    const cleanups: Array<() => void> = [];\n    for (const ref of refs) {\n      if (typeof ref === \"function\") {\n        const cleanup = ref(node);\n        if (node !== null) {\n          cleanups.push(typeof cleanup === \"function\" ? cleanup : () => ref(null));\n        }\n      } else if (ref) {\n        ref.current = node;\n        if (node !== null) {\n          cleanups.push(() => {\n            ref.current = null;\n          });\n        }\n      }\n    }\n    return cleanups.length > 0\n      ? () => {\n          for (const cleanup of cleanups) {\n            cleanup();\n          }\n        }\n      : undefined;\n  };\n  cache.callback = callback as RefCallback<unknown>;\n  return callback;\n}\n\ntype MergedRefCache = {\n  callback?: RefCallback<unknown>;\n  children: WeakMap<object, MergedRefCache>;\n};\n\nconst mergedRefCache: MergedRefCache = { children: new WeakMap() };\n\ntype UnknownHandler = (...args: never[]) => unknown;\n\nfunction isEventHandler(key: string, value: unknown): value is UnknownHandler {\n  return /^on[A-Z]/.test(key) && typeof value === \"function\";\n}\n\nfunction defaultPrevented(args: unknown[]): boolean {\n  const event = args[0];\n  return (\n    typeof event === \"object\" &&\n    event !== null &&\n    \"defaultPrevented\" in event &&\n    event.defaultPrevented === true\n  );\n}\n\nfunction composeUnknownHandlers(\n  consumer: UnknownHandler,\n  library: UnknownHandler\n): UnknownHandler {\n  if (consumer === library) {\n    return library;\n  }\n  return ((...args: unknown[]) => {\n    (consumer as (...handlerArgs: unknown[]) => unknown)(...args);\n    if (!defaultPrevented(args)) {\n      (library as (...handlerArgs: unknown[]) => unknown)(...args);\n    }\n  }) as UnknownHandler;\n}\n\n/** Library-owned host props — permissive so button `type`, `data-*`, `popover`, etc. type-check. */\nexport type HeidiElementProps<Tag extends keyof HTMLElementTagNameMap> = Record<\n  string,\n  unknown\n> & {\n  children?: ReactNode;\n  className?: string;\n  ref?: Ref<HTMLElementTagNameMap[Tag]>;\n  style?: CSSProperties;\n};\n\ntype RenderElementParams<\n  State,\n  Tag extends keyof HTMLElementTagNameMap,\n  RenderProps extends object = DefaultRenderProps\n> = {\n  /** Default class from the generated hui-* map (theme/base target). */\n  className?: string;\n  /** Stable machine hook — always emitted. */\n  dataPart: string;\n  /** Intrinsic tag when `render` is omitted. */\n  element: Tag;\n  /** Props the library owns (aria, ids, handlers, popover, …). */\n  props: HeidiElementProps<Tag>;\n  /** Optional consumer composition props. */\n  renderProps?: HeidiHostProps<State, RenderProps>;\n  /** Typed state passed to functional className/style/render. */\n  state: State;\n  /** Structural inline styles that must survive consumer style merges. */\n  structuralStyle?: CSSProperties;\n  /**\n   * Render-element handlers to remove instead of composing. This is reserved\n   * for states such as a focusable disabled composite item where the public\n   * contract requires press handlers to be completely inert.\n   */\n  suppressRenderedHandlers?: readonly string[];\n};\n\n/**\n * Render a heidi-ui host element with optional Base-shaped composition.\n */\nexport function renderHeidiElement<\n  State,\n  Tag extends keyof HTMLElementTagNameMap,\n  RenderProps extends object = DefaultRenderProps\n>(\n  params: RenderElementParams<State, Tag, RenderProps>\n): ReactElement {\n  const {\n    className,\n    dataPart,\n    element,\n    props,\n    renderProps,\n    state,\n    structuralStyle,\n    suppressRenderedHandlers\n  } = params;\n  const consumerClass = resolveClassName(renderProps?.className, state);\n  const consumerStyle = resolveStyle(renderProps?.style, state);\n  const mergedStyle: CSSProperties | undefined =\n    props.style || consumerStyle || structuralStyle\n      ? { ...props.style, ...consumerStyle, ...structuralStyle }\n      : undefined;\n\n  const outProps: HTMLAttributes<HTMLElement> & {\n    \"data-hui-part\": string;\n    ref?: Ref<HTMLElement>;\n  } = {\n    ...(props as HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> }),\n    className: mergeClassNames(consumerClass, className),\n    \"data-hui-part\": dataPart,\n    style: mergedStyle\n  };\n\n  const render = renderProps?.render;\n  if (typeof render === \"function\") {\n    // Post-merge the returned element as well. This deliberately makes the\n    // stable data hook, owned semantics, refs, and internal handlers survive\n    // even when a render function forgets to spread one of the supplied props.\n    return mergeRenderedElement(\n      render(outProps as unknown as RenderProps, state),\n      outProps,\n      dataPart,\n      structuralStyle,\n      suppressRenderedHandlers\n    );\n  }\n  if (isValidElement(render)) {\n    return mergeRenderedElement(\n      render,\n      outProps,\n      dataPart,\n      structuralStyle,\n      suppressRenderedHandlers\n    );\n  }\n\n  return createElement(element, outProps as never, props.children);\n}\n\ntype RenderedProps = Record<string, unknown> & {\n  children?: ReactNode;\n  className?: string;\n  ref?: Ref<HTMLElement>;\n  style?: CSSProperties;\n};\n\nfunction mergeRenderedElement(\n  element: ReactElement,\n  libraryProps: HTMLAttributes<HTMLElement> & {\n    \"data-hui-part\": string;\n    ref?: Ref<HTMLElement>;\n  },\n  dataPart: string,\n  structuralStyle: CSSProperties | undefined,\n  suppressRenderedHandlers: readonly string[] | undefined\n): ReactElement {\n  const rendered = element as ReactElement<RenderedProps>;\n  const renderedProps = rendered.props;\n  const renderedClass =\n    renderedProps.className === libraryProps.className\n      ? undefined\n      : renderedProps.className;\n  const merged: RenderedProps = {\n    ...renderedProps,\n    ...libraryProps,\n    className: mergeClassNames(renderedClass, libraryProps.className),\n    \"data-hui-part\": dataPart,\n    ref: mergeHeidiRefs(libraryProps.ref, renderedProps.ref),\n    style: {\n      ...libraryProps.style,\n      ...renderedProps.style,\n      ...structuralStyle\n    }\n  };\n\n  if (Object.prototype.hasOwnProperty.call(renderedProps, \"children\")) {\n    merged.children = renderedProps.children;\n  }\n\n  const suppressedHandlers = suppressRenderedHandlers\n    ? new Set(suppressRenderedHandlers)\n    : null;\n  for (const key of new Set([...Object.keys(renderedProps), ...Object.keys(libraryProps)])) {\n    const consumer = renderedProps[key];\n    const library = (libraryProps as unknown as Record<string, unknown>)[key];\n    if (suppressedHandlers?.has(key)) {\n      merged[key] = library;\n      continue;\n    }\n    if (isEventHandler(key, consumer) && isEventHandler(key, library)) {\n      merged[key] = composeUnknownHandlers(consumer, library);\n    }\n  }\n\n  return cloneElement(rendered, merged as never);\n}\n",
      "path": "packages/heidi-ui/src/_internal/render-element.ts",
      "target": "components/ui/heidi/_internal/render-element.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared id sanitizer for heidi-ui hosts.\n *\n * React's useId() emits characters that are invalid in HTML id / attribute\n * selectors and in CSS identifiers (`«r0»` in React 19, `:r0:` earlier).\n * heidi-ui interpolates these ids into element ids, aria relationships, and\n * dashed-ident anchor names (`--hui-*-anchor-…`), so everything outside\n * [a-zA-Z0-9_-] is stripped.\n */\n\nexport function safeId(id: string): string {\n  return id.replace(/[^a-zA-Z0-9_-]/g, \"\");\n}\n",
      "path": "packages/heidi-ui/src/_internal/safe-id.ts",
      "target": "components/ui/heidi/_internal/safe-id.ts",
      "type": "registry:ui"
    }
  ],
  "name": "toast",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Toast",
  "type": "registry:ui"
}
