{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "APG radio group with a native form bridge: visible role=radiogroup + role=radio buttons keep roving focus, composition, and automatic Arrow/Home/End selection, while one inert, aria-hidden native radio per Item owns labels, FormData, constraint validation, reset, and external form association without creating a second accessible radio. Controlled via value/defaultValue + onValueChange; callback-time native checked truth reflects the requested value before controlled authority is reasserted.",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui Radio — APG radio group with native form semantics.\n *\n * The visible controls remain role=radio buttons with roving focus and APG\n * automatic selection. Each Item also owns an inert, aria-hidden native radio\n * input for labels, FormData, constraint validation, reset, and external `form`\n * ownership. The native group is synchronized before onValueChange runs, then\n * controlled state is reasserted after the interaction when an owner rejects\n * the requested value.\n *\n * RSC rule: named exports from Server Components; Radio.X is client sugar.\n */\n\nimport {\n  Children,\n  type ChangeEvent,\n  cloneElement,\n  type ComponentPropsWithoutRef,\n  createContext,\n  Fragment,\n  isValidElement,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type KeyboardEvent,\n  type MouseEvent,\n  type ReactNode,\n  type Ref\n} from \"react\";\nimport {\n  VISUALLY_HIDDEN_INPUT_STYLE\n} from \"../_internal/form-bridge\";\nimport {\n  collectRovingItems,\n  NAVIGATION_KEYS,\n  resolveRovingIndex\n} from \"../_internal/roving-focus\";\nimport { dataDisabledAttrs, isDisabledElement, nativeDisabledAttrs } from \"../_internal/disabled\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiHostProps,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { safeId } from \"../_internal/safe-id\";\nimport { useHeidiLayoutEffect } from \"../_internal/use-layout-effect\";\nimport { type RadioOrientation } from \"./radio.anatomy.generated\";\nimport { RADIO_CLASSES } from \"./radio.classes.generated\";\n\nexport type { RadioOrientation };\n\ntype RadioItemRegistration = {\n  disabled: boolean;\n  inputRef: { current: HTMLInputElement | null };\n  value: string;\n  visualRef: { current: HTMLButtonElement | null };\n};\n\ntype RadioContextValue = {\n  claimInitialTabStop: (\n    id: string,\n    disabled: boolean,\n    checked: boolean,\n    value: string\n  ) => boolean;\n  claimInvalidFocus: (id: string) => void;\n  disabled: boolean;\n  form: string | undefined;\n  handleInputChange: (event: ChangeEvent<HTMLInputElement>, value: string) => void;\n  labelledByInputId: ReadonlyMap<string, string>;\n  name: string | undefined;\n  orientation: RadioOrientation;\n  readOnly: boolean;\n  registerForm: (form: HTMLFormElement | null) => () => void;\n  registerItem: (\n    id: string,\n    registrationRef: { current: RadioItemRegistration }\n  ) => () => void;\n  required: boolean;\n  selectItem: (id: string) => void;\n  selected: string | null;\n  tabbableItemId: string | null;\n  updateItem: (id: string, disabled: boolean, value: string) => void;\n};\n\nconst RadioContext = createContext<RadioContextValue | null>(null);\n\nfunction useRadioContext(part: string): RadioContextValue {\n  const context = useContext(RadioContext);\n  if (!context) {\n    throw new Error(`Radio.${part} must be rendered inside Radio.Root.`);\n  }\n  return context;\n}\n\ntype RadioItemContextValue = {\n  checked: boolean;\n  value: string;\n};\n\nconst RadioItemContext = createContext<RadioItemContextValue | null>(null);\n\nfunction useRadioItemContext(part: string): RadioItemContextValue {\n  const context = useContext(RadioItemContext);\n  if (!context) {\n    throw new Error(`Radio.${part} must be rendered inside Radio.Item.`);\n  }\n  return context;\n}\n\nfunction radioItems(container: HTMLElement): HTMLElement[] {\n  return collectRovingItems<HTMLElement>(\n    container,\n    '[role=\"radio\"]',\n    '[data-hui-part=\"radio-root\"]',\n    isDisabledElement\n  );\n}\n\nfunction findAssociatedLabels(input: HTMLInputElement | null): HTMLLabelElement[] {\n  return Array.from(input?.labels ?? []);\n}\n\ntype RadioItemCandidateProps = {\n  children?: ReactNode;\n  disabled?: boolean;\n  value?: string;\n};\n\ntype RadioTreeElementProps = {\n  children?: ReactNode;\n  htmlFor?: string;\n  id?: string;\n};\n\ntype PreparedRadioChildren = {\n  children: ReactNode;\n  labelledByInputId: ReadonlyMap<string, string>;\n};\n\nfunction prepareRadioChildren(\n  children: ReactNode,\n  generatedLabelIdPrefix: string\n): PreparedRadioChildren {\n  const labelIdsByInputId = new Map<string, string[]>();\n  let generatedLabelSequence = 0;\n\n  const prepare = (nodes: ReactNode): ReactNode =>\n    Children.map(nodes, (child) => {\n      if (!isValidElement<RadioTreeElementProps>(child)) {\n        return child;\n      }\n      if (child.type !== Fragment && typeof child.type !== \"string\") {\n        return child;\n      }\n\n      const preparedChildren =\n        child.props.children === undefined\n          ? undefined\n          : prepare(child.props.children);\n      const childProps =\n        preparedChildren === undefined\n          ? undefined\n          : { children: preparedChildren };\n\n      if (\n        child.type !== \"label\" ||\n        typeof child.props.htmlFor !== \"string\" ||\n        child.props.htmlFor === \"\"\n      ) {\n        return childProps === undefined\n          ? child\n          : cloneElement(child, childProps);\n      }\n\n      const labelId =\n        typeof child.props.id === \"string\" && child.props.id !== \"\"\n          ? child.props.id\n          : `${generatedLabelIdPrefix}-${++generatedLabelSequence}`;\n      const existing = labelIdsByInputId.get(child.props.htmlFor) ?? [];\n      if (!existing.includes(labelId)) {\n        labelIdsByInputId.set(child.props.htmlFor, [...existing, labelId]);\n      }\n      return cloneElement(child, { ...childProps, id: labelId });\n    });\n\n  const preparedChildren = prepare(children);\n  return {\n    children: preparedChildren,\n    labelledByInputId: new Map(\n      [...labelIdsByInputId].map(([inputId, labelIds]) => [\n        inputId,\n        labelIds.join(\" \")\n      ])\n    )\n  };\n}\n\nfunction initialRadioTabStopValue(\n  children: ReactNode,\n  selected: string | null\n): string | null {\n  const enabledValues: string[] = [];\n\n  const visit = (nodes: ReactNode) => {\n    Children.forEach(nodes, (child) => {\n      if (!isValidElement<RadioItemCandidateProps>(child)) {\n        return;\n      }\n      if (\n        child.type === RadioItem &&\n        child.props.disabled !== true &&\n        child.props.value !== undefined\n      ) {\n        enabledValues.push(child.props.value);\n        return;\n      }\n      if (child.type === Fragment || typeof child.type === \"string\") {\n        visit(child.props.children);\n      }\n    });\n  };\n\n  visit(children);\n  return (\n    (selected === null\n      ? undefined\n      : enabledValues.find((value) => value === selected)) ??\n    enabledValues[0] ??\n    null\n  );\n}\n\nfunction useRadioItemLabelledBy(\n  explicit: string | undefined,\n  ariaLabel: string | undefined,\n  initialFallback: string | undefined,\n  inputRef: { current: HTMLInputElement | null }\n): string | undefined {\n  const generatedLabelId = `hui-radio-label-${safeId(useId())}`;\n  const generatedLabelSequenceRef = useRef(0);\n  const [fallback, setFallback] = useState<string | undefined>(initialFallback);\n\n  // Native label associations may change without a prop change, so reflect\n  // every current label into the visible role after each commit.\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  useHeidiLayoutEffect(() => {\n    const labels = explicit || ariaLabel ? [] : findAssociatedLabels(inputRef.current);\n    for (const label of labels) {\n      if (!label.id) {\n        generatedLabelSequenceRef.current += 1;\n        label.id = `${generatedLabelId}-${generatedLabelSequenceRef.current}`;\n      }\n    }\n    const next = labels.map((label) => label.id).filter(Boolean).join(\" \") || undefined;\n    if (next !== fallback) {\n      setFallback(next);\n    }\n  });\n\n  return explicit ?? (ariaLabel ? undefined : fallback);\n}\n\nexport type RadioRootState = {\n  disabled: boolean;\n  orientation: RadioOrientation;\n  readOnly: boolean;\n  required: boolean;\n  value: string | null;\n};\n\ntype RadioRootNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  | \"aria-disabled\"\n  | \"aria-label\"\n  | \"aria-orientation\"\n  | \"aria-readonly\"\n  | \"aria-required\"\n  | \"children\"\n  | \"className\"\n  | \"defaultValue\"\n  | \"onKeyDown\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n  | \"value\"\n>;\n\nexport type RadioRootProps = HeidiIntrinsicHostProps<RadioRootState, \"div\"> &\n  RadioRootNativeProps & {\n    children?: ReactNode;\n    defaultValue?: string | null;\n    disabled?: boolean;\n    form?: string;\n    /** Accessible name for the radiogroup (aria-label). Required. */\n    label: string;\n    /**\n     * Shared native input name. Required unnamed groups receive an opaque,\n     * instance-stable name; author this prop for a stable FormData key.\n     */\n    name?: string;\n    onKeyDown?: ComponentPropsWithoutRef<\"div\">[\"onKeyDown\"];\n    onValueChange?: (value: string) => void;\n    orientation?: RadioOrientation;\n    readOnly?: boolean;\n    ref?: Ref<HTMLDivElement>;\n    /** Enables native group constraint validation, including when name is omitted. */\n    required?: boolean;\n    value?: string | null;\n  };\n\nexport function RadioRoot({\n  children,\n  className,\n  defaultValue = null,\n  disabled = false,\n  form,\n  label,\n  name,\n  onKeyDown: onKeyDownProp,\n  onValueChange,\n  orientation = \"vertical\",\n  readOnly = false,\n  ref,\n  render,\n  required = false,\n  style,\n  value: valueProp,\n  ...nativeProps\n}: RadioRootProps) {\n  const generatedRootId = safeId(useId());\n  // ponytail: a native label names the hidden bridge, not the visible role.\n  // Prepare inspectable labels so that bridge relationship also names the\n  // visible Item in server HTML; the Item effect covers opaque/out-of-tree labels.\n  const generatedLabelIdPrefix = `hui-radio-native-label-${generatedRootId}`;\n  const preparedChildren = prepareRadioChildren(\n    children,\n    generatedLabelIdPrefix\n  );\n  const nativeName =\n    name ?? (required ? `hui-radio-required-${generatedRootId}` : undefined);\n  const controlled = valueProp !== undefined;\n  const initialValueRef = useRef(defaultValue);\n  const [internal, setInternal] = useState<string | null>(defaultValue);\n  const [order, setOrder] = useState<Array<{ disabled: boolean; id: string; value: string }>>([]);\n  const selected = valueProp !== undefined ? valueProp : internal;\n  const rootRef = useRef<HTMLDivElement | null>(null);\n  const selectedRef = useRef(selected);\n  selectedRef.current = selected;\n  const itemsRef = useRef(\n    new Map<string, { current: RadioItemRegistration }>()\n  );\n  const invalidFocusClaimedRef = useRef(false);\n  const invalidFocusReleaseFrameRef = useRef(0);\n  const resetFrameRef = useRef(0);\n  const formRegistrationsRef = useRef(new Map<HTMLFormElement, number>());\n  const authorityRef = useRef({ controlled, disabled, onValueChange, readOnly });\n  authorityRef.current = { controlled, disabled, onValueChange, readOnly };\n\n  const syncNativeInputs = useCallback((next = selectedRef.current) => {\n    for (const registrationRef of itemsRef.current.values()) {\n      const registration = registrationRef.current;\n      const input = registration.inputRef.current;\n      if (!input) {\n        continue;\n      }\n      input.checked = next !== null && registration.value === next;\n      input.defaultChecked =\n        initialValueRef.current !== null && registration.value === initialValueRef.current;\n    }\n  }, []);\n\n  useHeidiLayoutEffect(syncNativeInputs, [selected, syncNativeInputs]);\n\n  const handleFormReset = useCallback(() => {\n    const authority = authorityRef.current;\n    if (!authority.controlled) {\n      selectedRef.current = initialValueRef.current;\n      setInternal(initialValueRef.current);\n    }\n    cancelAnimationFrame(resetFrameRef.current);\n    resetFrameRef.current = requestAnimationFrame(() => syncNativeInputs());\n  }, [syncNativeInputs]);\n\n  useEffect(\n    () => () => {\n      cancelAnimationFrame(invalidFocusReleaseFrameRef.current);\n      cancelAnimationFrame(resetFrameRef.current);\n    },\n    []\n  );\n\n  const registerForm = useCallback(\n    (ownerForm: HTMLFormElement | null) => {\n      if (!ownerForm) {\n        return () => undefined;\n      }\n      const count = formRegistrationsRef.current.get(ownerForm) ?? 0;\n      if (count === 0) {\n        ownerForm.addEventListener(\"reset\", handleFormReset);\n      }\n      formRegistrationsRef.current.set(ownerForm, count + 1);\n      return () => {\n        const nextCount = (formRegistrationsRef.current.get(ownerForm) ?? 1) - 1;\n        if (nextCount <= 0) {\n          formRegistrationsRef.current.delete(ownerForm);\n          ownerForm.removeEventListener(\"reset\", handleFormReset);\n        } else {\n          formRegistrationsRef.current.set(ownerForm, nextCount);\n        }\n      };\n    },\n    [handleFormReset]\n  );\n\n  const registerItem = useCallback(\n    (id: string, registrationRef: { current: RadioItemRegistration }) => {\n      itemsRef.current.set(id, registrationRef);\n      const registration = registrationRef.current;\n      setOrder((previous) => {\n        const existing = previous.find((entry) => entry.id === id);\n        if (existing) {\n          return previous.map((entry) =>\n            entry.id === id\n              ? { disabled: registration.disabled, id, value: registration.value }\n              : entry\n          );\n        }\n        return [\n          ...previous,\n          { disabled: registration.disabled, id, value: registration.value }\n        ];\n      });\n      syncNativeInputs();\n      return () => {\n        if (itemsRef.current.get(id) === registrationRef) {\n          itemsRef.current.delete(id);\n          setOrder((previous) => previous.filter((entry) => entry.id !== id));\n        }\n      };\n    },\n    [syncNativeInputs]\n  );\n\n  const updateItem = useCallback((id: string, itemDisabled: boolean, itemValue: string) => {\n    setOrder((previous) => {\n      const existing = previous.find((entry) => entry.id === id);\n      if (\n        !existing ||\n        (existing.disabled === itemDisabled && existing.value === itemValue)\n      ) {\n        return previous;\n      }\n      return previous.map((entry) =>\n        entry.id === id\n          ? { disabled: itemDisabled, id, value: itemValue }\n          : entry\n      );\n    });\n  }, []);\n\n  const reconcileDomOrder = useCallback(() => {\n    const root = rootRef.current;\n    if (!root) {\n      return;\n    }\n    const next = Array.from(\n      root.querySelectorAll<HTMLElement>(\"[data-hui-radio-item-id]\")\n    )\n      .filter((element) => element.closest('[data-hui-part=\"radio-root\"]') === root)\n      .flatMap((element) => {\n        const id = element.dataset.huiRadioItemId;\n        if (!id) {\n          return [];\n        }\n        const registration = itemsRef.current.get(id)?.current;\n        return registration\n          ? [{ disabled: registration.disabled, id, value: registration.value }]\n          : [];\n      });\n    setOrder((previous) =>\n      previous.length === next.length &&\n      previous.every(\n        (entry, index) =>\n          entry.disabled === next[index]?.disabled &&\n          entry.id === next[index]?.id &&\n          entry.value === next[index]?.value\n      )\n        ? previous\n        : next\n    );\n  }, []);\n\n  // Keyed children can move without re-running their registration effects.\n  // Reconcile from the committed DOM after every Root render so fallback focus\n  // follows current visual order rather than historical mount order.\n  useHeidiLayoutEffect(reconcileDomOrder);\n\n  const handleInputChange = useCallback(\n    (event: ChangeEvent<HTMLInputElement>, next: string) => {\n      const authority = authorityRef.current;\n      if (authority.disabled || authority.readOnly) {\n        event.preventDefault();\n        queueMicrotask(syncNativeInputs);\n        return;\n      }\n      if (!event.currentTarget.checked) {\n        queueMicrotask(syncNativeInputs);\n        return;\n      }\n\n      // `input.click()` already updates same-name native radios. This explicit\n      // pass also keeps unnamed groups mutually exclusive and guarantees that\n      // callback-time FormData/checked IDL expose the requested value.\n      syncNativeInputs(next);\n      if (!authority.controlled) {\n        selectedRef.current = next;\n        setInternal(next);\n      }\n      authority.onValueChange?.(next);\n      queueMicrotask(syncNativeInputs);\n    },\n    [syncNativeInputs]\n  );\n\n  const selectItem = useCallback((id: string) => {\n    const authority = authorityRef.current;\n    const registration = itemsRef.current.get(id)?.current;\n    if (\n      !registration ||\n      authority.disabled ||\n      authority.readOnly ||\n      registration.disabled\n    ) {\n      return;\n    }\n    registration.inputRef.current?.click();\n  }, []);\n\n  const claimInvalidFocus = useCallback((id: string) => {\n    if (invalidFocusClaimedRef.current) {\n      return;\n    }\n    invalidFocusClaimedRef.current = true;\n    itemsRef.current.get(id)?.current.visualRef.current?.focus({\n      preventScroll: true\n    });\n    invalidFocusReleaseFrameRef.current = requestAnimationFrame(() => {\n      invalidFocusClaimedRef.current = false;\n    });\n  }, []);\n\n  const enabledOrder = order.filter((entry) => !entry.disabled);\n  const selectedEntry =\n    selected === null\n      ? undefined\n      : enabledOrder.find((entry) => entry.value === selected);\n  const tabbableItemId = selectedEntry?.id ?? enabledOrder[0]?.id ?? null;\n  const initialTabStopValue = initialRadioTabStopValue(children, selected);\n\n  let initialTabStopItemId: string | null = null;\n  const context: RadioContextValue = {\n    claimInitialTabStop: (itemId, itemDisabled, itemChecked, itemValue) => {\n      if (\n        itemDisabled ||\n        (initialTabStopItemId !== null && initialTabStopItemId !== itemId) ||\n        (initialTabStopValue === null\n          ? selected !== null && !itemChecked\n          : itemValue !== initialTabStopValue)\n      ) {\n        return false;\n      }\n      // React Strict Mode can render the same Item twice against this context.\n      // Repeated claims by that Item must return the same hydration result.\n      initialTabStopItemId = itemId;\n      return true;\n    },\n    claimInvalidFocus,\n    disabled,\n    form,\n    handleInputChange,\n    labelledByInputId: preparedChildren.labelledByInputId,\n    name: nativeName,\n    orientation,\n    readOnly,\n    registerForm,\n    registerItem,\n    required,\n    selectItem,\n    selected,\n    tabbableItemId,\n    updateItem\n  };\n\n  const onKeyDown = composeHeidiEventHandlers(\n    onKeyDownProp,\n    (event: KeyboardEvent<HTMLDivElement>) => {\n    if (!NAVIGATION_KEYS.includes(event.key)) {\n      return;\n    }\n    const items = radioItems(event.currentTarget);\n    if (items.length === 0) {\n      return;\n    }\n    event.preventDefault();\n    const nextIndex = resolveRovingIndex(\n      items,\n      document.activeElement,\n      event.key,\n      event.currentTarget,\n      { crossAxis: true, orientation }\n    );\n    if (nextIndex === null) {\n      return;\n    }\n    const next = items[nextIndex];\n    if (!next) {\n      return;\n    }\n    next.focus();\n    const itemId = next.dataset.huiRadioItemId;\n    if (itemId !== undefined) {\n      selectItem(itemId);\n    }\n    }\n  );\n\n  const state = { disabled, orientation, readOnly, required, value: selected };\n\n  return (\n    <RadioContext value={context}>\n      {renderHeidiElement({\n        className: RADIO_CLASSES.root,\n        dataPart: \"radio-root\",\n        element: \"div\",\n        props: {\n          ...nativeProps,\n          \"aria-disabled\": disabled || undefined,\n          \"aria-label\": label,\n          \"aria-orientation\": orientation,\n          \"aria-readonly\": readOnly || undefined,\n          \"aria-required\": required || undefined,\n          children: preparedChildren.children,\n          \"data-orientation\": orientation,\n          \"data-readonly\": readOnly ? true : undefined,\n          \"data-required\": required ? true : undefined,\n          onKeyDown,\n          ref: mergeHeidiRefs(rootRef, ref),\n          role: \"radiogroup\",\n          ...dataDisabledAttrs(disabled)\n        },\n        renderProps: { className, render, style },\n        state\n      })}\n    </RadioContext>\n  );\n}\n\nexport type RadioItemState = {\n  checked: boolean;\n  disabled: boolean;\n  readOnly: boolean;\n  required: boolean;\n  value: string;\n};\n\ntype ItemRenderProps = ComponentPropsWithoutRef<\"button\"> & { ref?: Ref<HTMLElement> };\ntype NativeItemProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  | \"aria-checked\"\n  | \"aria-disabled\"\n  | \"aria-readonly\"\n  | \"aria-required\"\n  | \"children\"\n  | \"className\"\n  | \"disabled\"\n  | \"id\"\n  | \"onClick\"\n  | \"onKeyDown\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n  | \"tabIndex\"\n  | \"type\"\n  | \"value\"\n>;\n\nexport type RadioItemProps = HeidiHostProps<RadioItemState, ItemRenderProps> &\n  NativeItemProps & {\n    children?: ReactNode;\n    disabled?: boolean;\n    /** DOM id for the inert native input bridge. */\n    id?: string;\n    inputRef?: Ref<HTMLInputElement>;\n    onClick?: (event: MouseEvent<HTMLElement>) => void;\n    onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void;\n    ref?: Ref<HTMLButtonElement>;\n    value: string;\n    /** DOM id for the visible role=radio button host. */\n    visualId?: string;\n  };\n\nexport function RadioItem({\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledByProp,\n  children,\n  className,\n  disabled: disabledProp = false,\n  id: inputId,\n  inputRef: inputRefProp,\n  onClick,\n  onKeyDown,\n  ref,\n  render,\n  style,\n  value,\n  visualId,\n  ...nativeProps\n}: RadioItemProps) {\n  const {\n    claimInitialTabStop,\n    claimInvalidFocus,\n    disabled: groupDisabled,\n    form,\n    handleInputChange,\n    labelledByInputId,\n    name,\n    readOnly,\n    registerForm,\n    registerItem,\n    required,\n    selectItem,\n    selected,\n    tabbableItemId,\n    updateItem\n  } = useRadioContext(\"Item\");\n  const disabled = groupDisabled || disabledProp;\n  const checked = selected === value;\n  const dataState = checked ? \"checked\" : \"unchecked\";\n  const itemId = `hui-radio-item-${safeId(useId())}`;\n  const generatedVisualId = `hui-radio-${safeId(useId())}`;\n  const visualRef = useRef<HTMLButtonElement | null>(null);\n  const nativeInputRef = useRef<HTMLInputElement | null>(null);\n  const mergedInputRef = mergeHeidiRefs(nativeInputRef, inputRefProp);\n  const registrationRef = useRef<RadioItemRegistration>({\n    disabled,\n    inputRef: nativeInputRef,\n    value,\n    visualRef\n  });\n  registrationRef.current = { disabled, inputRef: nativeInputRef, value, visualRef };\n  const ariaLabelledBy = useRadioItemLabelledBy(\n    ariaLabelledByProp,\n    ariaLabel,\n    inputId === undefined ? undefined : labelledByInputId.get(inputId),\n    nativeInputRef\n  );\n\n  // ponytail: keep item registration passive during hydration. A layout-time\n  // registration can synchronously re-render Root while React is still walking\n  // sibling items, letting incomplete mount order override the SSR tab stop.\n  useEffect(\n    () => registerItem(itemId, registrationRef),\n    [itemId, registerItem]\n  );\n\n  useEffect(() => {\n    updateItem(itemId, disabled, value);\n  }, [disabled, itemId, updateItem, value]);\n\n  useEffect(\n    () => registerForm(nativeInputRef.current?.form ?? null),\n    [form, registerForm]\n  );\n\n  const state: RadioItemState = { checked, disabled, readOnly, required, value };\n  const initialTabStop =\n    tabbableItemId === null &&\n    claimInitialTabStop(itemId, disabled, checked, value);\n\n  return (\n    <RadioItemContext value={{ checked, value }}>\n      {renderHeidiElement({\n        className: RADIO_CLASSES.item,\n        dataPart: \"radio-item\",\n        element: \"button\",\n        props: {\n          ...nativeProps,\n          \"aria-checked\": checked,\n          \"aria-label\": ariaLabel,\n          \"aria-labelledby\": ariaLabelledBy,\n          children,\n          \"data-hui-radio-item-id\": itemId,\n          \"data-readonly\": readOnly ? true : undefined,\n          \"data-required\": required ? true : undefined,\n          \"data-state\": dataState,\n          \"data-value\": value,\n          id: visualId ?? generatedVisualId,\n          onClick: composeHeidiEventHandlers(onClick, (event) => {\n            event.preventDefault();\n            selectItem(itemId);\n          }),\n          onKeyDown: composeHeidiEventHandlers(onKeyDown, (event) => {\n            // ponytail: Enter must not select. The APG Radio Group keyboard\n            // table (group not in a toolbar) lists only Tab/Shift+Tab, Space\n            // and the four arrows — Enter is absent, and a native radio does\n            // not select on Enter either. The visible Item is a native\n            // <button>, so without this guard Enter fired a click and silently\n            // reassigned the user's answer. Root's own key handler cannot do\n            // it: it returns early for anything outside NAVIGATION_KEYS.\n            //\n            // Enter therefore does nothing at all here, which matches Radix\n            // (\"radio groups don't activate items on enter keypress\"). It does\n            // NOT implicitly submit the owning form the way a real radio would,\n            // because the host is type=\"button\"; wiring that needs Checkbox's\n            // default-submitter machinery and belongs in a slice that can\n            // share it rather than fork it.\n            if (event.key === \"Enter\") {\n              event.preventDefault();\n            }\n          }),\n          ref: mergeHeidiRefs(visualRef, ref),\n          role: \"radio\",\n          tabIndex:\n            !disabled && (tabbableItemId === itemId || initialTabStop)\n              ? 0\n              : -1,\n          type: \"button\",\n          ...nativeDisabledAttrs(disabled)\n        },\n        renderProps: { className, render, style },\n        state\n      })}\n      {/*\n       * ponytail: aria-hidden alone does not keep a programmatically focusable\n       * native radio out of every semantic snapshot. `inert` makes the input\n       * an unexposed form bridge while preserving radio checkedness,\n       * FormData, labels, reset, and constraint validity. Invalid and label\n       * focus are redirected explicitly to the visible APG Item.\n       */}\n      <input\n        {...nativeDisabledAttrs(disabled)}\n        aria-hidden=\"true\"\n        checked={checked}\n        data-hui-radio-input=\"\"\n        form={form}\n        id={inputId}\n        inert\n        name={name}\n        onChange={(event) => handleInputChange(event, value)}\n        onClick={(event) => {\n          if (readOnly) {\n            event.preventDefault();\n          }\n          visualRef.current?.focus({ preventScroll: true });\n        }}\n        onFocus={() => visualRef.current?.focus({ preventScroll: true })}\n        onInvalid={(event) => {\n          event.preventDefault();\n          claimInvalidFocus(itemId);\n        }}\n        readOnly={readOnly}\n        ref={mergedInputRef}\n        required={required}\n        style={VISUALLY_HIDDEN_INPUT_STYLE}\n        suppressHydrationWarning\n        tabIndex={-1}\n        type=\"radio\"\n        value={value}\n      />\n    </RadioItemContext>\n  );\n}\n\nexport type RadioIndicatorState = { checked: boolean };\n\ntype RadioIndicatorNativeProps = Omit<\n  ComponentPropsWithoutRef<\"span\">,\n  \"aria-hidden\" | \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\nexport type RadioIndicatorProps = HeidiIntrinsicHostProps<\n  RadioIndicatorState,\n  \"span\"\n> &\n  RadioIndicatorNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLSpanElement>;\n  };\n\n/** Presentational ring/dot; nest inside Radio.Item. aria-hidden always. */\nexport function RadioIndicator({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: RadioIndicatorProps) {\n  const { checked } = useRadioItemContext(\"Indicator\");\n  const dataState = checked ? \"checked\" : \"unchecked\";\n  return renderHeidiElement({\n    className: RADIO_CLASSES.indicator,\n    dataPart: \"radio-indicator\",\n    element: \"span\",\n    props: {\n      ...nativeProps,\n      \"aria-hidden\": true,\n      children,\n      \"data-state\": dataState,\n      ref\n    },\n    renderProps: { className, render, style },\n    state: { checked }\n  });\n}\n\nexport const Radio = {\n  Indicator: RadioIndicator,\n  Item: RadioItem,\n  Root: RadioRoot\n} as const;\n",
      "path": "packages/heidi-ui/src/radio/radio.tsx",
      "target": "components/ui/heidi/radio/radio.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui radio — STRUCTURAL CSS only. No --hui-*.\n */\n\n@layer heidi-ui-base {\n  .hui-radio-root {\n    display: flex;\n    flex-direction: column;\n    gap: 0.5rem;\n  }\n\n  .hui-radio-root[data-orientation=\"horizontal\"] {\n    flex-direction: row;\n    flex-wrap: wrap;\n  }\n\n  .hui-radio-root[data-orientation=\"vertical\"] {\n    flex-direction: column;\n  }\n\n  .hui-radio-item {\n    align-items: center;\n    display: inline-flex;\n    gap: 0.5rem;\n  }\n\n  .hui-radio-indicator {\n    block-size: 1.125rem;\n    display: inline-grid;\n    flex-shrink: 0;\n    inline-size: 1.125rem;\n    place-items: center;\n  }\n}\n",
      "path": "packages/heidi-ui/src/radio/radio.base.css",
      "target": "components/ui/heidi/radio/radio.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui radio — VISUAL theme (opt-in). Consumes --hui-*.\n */\n\n@layer heidi-ui {\n  .hui-radio-root {\n    gap: var(--hui-space-2);\n  }\n\n  .hui-radio-item {\n    background: transparent;\n    border: none;\n    color: var(--hui-color-fg-default);\n    cursor: pointer;\n    font: inherit;\n    gap: var(--hui-space-2);\n    padding: 0;\n    text-align: start;\n  }\n\n  .hui-radio-item: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-radio-item[data-state=\"unchecked\"],\n  .hui-radio-item[data-state=\"checked\"] {\n    color: var(--hui-color-fg-default);\n  }\n\n  .hui-radio-indicator {\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-full);\n  }\n\n  .hui-radio-indicator[data-state=\"unchecked\"] {\n    border-color: var(--hui-color-border-default);\n  }\n\n  .hui-radio-indicator[data-state=\"checked\"] {\n    border-color: var(--hui-color-brand-primary);\n    border-width: var(--hui-border-width-strong);\n  }\n\n  .hui-radio-indicator[data-state=\"checked\"]::after {\n    background: var(--hui-color-brand-primary);\n    block-size: var(--hui-space-2);\n    border-radius: var(--hui-radius-full);\n    content: \"\";\n    inline-size: var(--hui-space-2);\n  }\n\n  .hui-radio-root[data-disabled=\"true\"] {\n    cursor: not-allowed;\n  }\n\n  .hui-radio-root[data-readonly=\"true\"],\n  .hui-radio-item[data-readonly=\"true\"] {\n    cursor: default;\n  }\n\n  .hui-radio-item:disabled,\n  .hui-radio-item[data-disabled=\"true\"] {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  @media (forced-colors: active) {\n    .hui-radio-indicator {\n      background: Canvas;\n      border-color: CanvasText;\n      color: CanvasText;\n      forced-color-adjust: none;\n    }\n\n    .hui-radio-indicator[data-state=\"checked\"] {\n      border-color: Highlight;\n    }\n\n    .hui-radio-indicator[data-state=\"checked\"]::after {\n      background: Highlight;\n    }\n\n    .hui-radio-item:focus-visible {\n      outline-color: Highlight;\n    }\n\n    .hui-radio-item:disabled,\n    .hui-radio-item[data-disabled=\"true\"] {\n      color: GrayText;\n      opacity: 1;\n    }\n\n    .hui-radio-item:disabled .hui-radio-indicator,\n    .hui-radio-item[data-disabled=\"true\"] .hui-radio-indicator {\n      border-color: GrayText;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/radio/radio.theme.css",
      "target": "components/ui/heidi/radio/radio.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui radio — aggregator (base + Heidi adapter + theme).\n */\n\n@import \"./radio.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./radio.theme.css\";\n",
      "path": "packages/heidi-ui/src/radio/radio.css",
      "target": "components/ui/heidi/radio/radio.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/radio/radio.base.css + packages/heidi-ui/src/radio/radio.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const RADIO_CLASSES = {\n  indicator: \"hui-radio-indicator\",\n  item: \"hui-radio-item\",\n  root: \"hui-radio-root\",\n} as const;\n",
      "path": "packages/heidi-ui/src/radio/radio.classes.generated.ts",
      "target": "components/ui/heidi/radio/radio.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from radio.anatomy.json + radio.base.css + radio.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type RadioDisabled = \"true\";\nexport type RadioOrientation = \"horizontal\" | \"vertical\";\nexport type RadioReadonly = \"true\";\nexport type RadioState = \"checked\" | \"unchecked\";\n\nexport const RADIO_ANATOMY = {\n  \"component\": \"radio\",\n  \"description\": \"APG radio group with a native form bridge: visible role=radiogroup + role=radio buttons keep roving focus, composition, and automatic Arrow/Home/End selection, while one inert, aria-hidden native radio per Item owns labels, FormData, constraint validation, reset, and external form association without creating a second accessible radio. Controlled via value/defaultValue + onValueChange; callback-time native checked truth reflects the requested value before controlled authority is reasserted.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"indicator\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-hidden\"\n        ],\n        \"role\": null\n      },\n      \"class\": \"hui-radio-indicator\",\n      \"css\": {\n        \"structural\": [\n          \"block-size\",\n          \"display\",\n          \"inline-size\",\n          \"place-items\"\n        ]\n      },\n      \"dataPart\": \"radio-indicator\",\n      \"description\": \"Visual radio ring; aria-hidden; filled center when data-state=checked (theme ::after).\",\n      \"element\": \"span\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-state\": [\n          \"checked\",\n          \"unchecked\"\n        ]\n      }\n    },\n    \"item\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-checked\"\n        ],\n        \"role\": \"radio\"\n      },\n      \"class\": \"hui-radio-item\",\n      \"css\": {\n        \"structural\": [\n          \"align-items\",\n          \"display\"\n        ]\n      },\n      \"dataPart\": \"radio-item\",\n      \"description\": \"Focusable role=radio button with roving tabindex. Space and the arrow keys select; Enter is refused, as the APG radio table omits it and a native radio does not select on Enter. For compatibility, `id` identifies the sibling visually-hidden inert native input that owns form behavior and optional native labels; `visualId` explicitly identifies this visible host. Item owns aria-checked, invalid/label focus redirection, and state synchronization.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"id\",\n        \"inputRef\",\n        \"onClick\",\n        \"onKeyDown\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"value\",\n        \"visualId\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-readonly\": [\n          \"true\"\n        ],\n        \"data-state\": [\n          \"checked\",\n          \"unchecked\"\n        ]\n      }\n    },\n    \"root\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-disabled\",\n          \"aria-label\",\n          \"aria-orientation\",\n          \"aria-readonly\",\n          \"aria-required\"\n        ],\n        \"role\": \"radiogroup\"\n      },\n      \"class\": \"hui-radio-root\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\"\n        ]\n      },\n      \"dataPart\": \"radio-root\",\n      \"description\": \"role=radiogroup; owns selection, arrow-key navigation, shared name/form/required/readOnly input semantics, and native reset reconciliation. A required group without an authored name receives one opaque, instance-stable native name so constraint validation remains effective; author name for a stable FormData key.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"defaultValue\",\n        \"disabled\",\n        \"form\",\n        \"label\",\n        \"name\",\n        \"onKeyDown\",\n        \"onValueChange\",\n        \"orientation\",\n        \"readOnly\",\n        \"ref\",\n        \"render\",\n        \"required\",\n        \"style\",\n        \"value\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-orientation\": [\n          \"horizontal\",\n          \"vertical\"\n        ],\n        \"data-readonly\": [\n          \"true\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultValue\",\n    \"disabled\",\n    \"form\",\n    \"label\",\n    \"name\",\n    \"onValueChange\",\n    \"orientation\",\n    \"readOnly\",\n    \"required\",\n    \"value\"\n  ],\n  \"tokens\": [\n    \"--hui-border-style\",\n    \"--hui-border-width\",\n    \"--hui-border-width-strong\",\n    \"--hui-color-bg-elevated\",\n    \"--hui-color-border-default\",\n    \"--hui-color-brand-primary\",\n    \"--hui-color-fg-default\",\n    \"--hui-color-focus-ring\",\n    \"--hui-focus-ring-offset\",\n    \"--hui-focus-ring-width\",\n    \"--hui-radius-full\",\n    \"--hui-space-2\"\n  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/radio/radio.anatomy.generated.ts",
      "target": "components/ui/heidi/radio/radio.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"radio\",\n  \"description\": \"APG radio group with a native form bridge: visible role=radiogroup + role=radio buttons keep roving focus, composition, and automatic Arrow/Home/End selection, while one inert, aria-hidden native radio per Item owns labels, FormData, constraint validation, reset, and external form association without creating a second accessible radio. Controlled via value/defaultValue + onValueChange; callback-time native checked truth reflects the requested value before controlled authority is reasserted.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"indicator\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-hidden\"\n        ],\n        \"role\": null\n      },\n      \"class\": \"hui-radio-indicator\",\n      \"css\": {\n        \"structural\": [\n          \"block-size\",\n          \"display\",\n          \"inline-size\",\n          \"place-items\"\n        ]\n      },\n      \"dataPart\": \"radio-indicator\",\n      \"description\": \"Visual radio ring; aria-hidden; filled center when data-state=checked (theme ::after).\",\n      \"element\": \"span\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-state\": [\n          \"checked\",\n          \"unchecked\"\n        ]\n      }\n    },\n    \"item\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-checked\"\n        ],\n        \"role\": \"radio\"\n      },\n      \"class\": \"hui-radio-item\",\n      \"css\": {\n        \"structural\": [\n          \"align-items\",\n          \"display\"\n        ]\n      },\n      \"dataPart\": \"radio-item\",\n      \"description\": \"Focusable role=radio button with roving tabindex. Space and the arrow keys select; Enter is refused, as the APG radio table omits it and a native radio does not select on Enter. For compatibility, `id` identifies the sibling visually-hidden inert native input that owns form behavior and optional native labels; `visualId` explicitly identifies this visible host. Item owns aria-checked, invalid/label focus redirection, and state synchronization.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"id\",\n        \"inputRef\",\n        \"onClick\",\n        \"onKeyDown\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"value\",\n        \"visualId\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-readonly\": [\n          \"true\"\n        ],\n        \"data-state\": [\n          \"checked\",\n          \"unchecked\"\n        ]\n      }\n    },\n    \"root\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-disabled\",\n          \"aria-label\",\n          \"aria-orientation\",\n          \"aria-readonly\",\n          \"aria-required\"\n        ],\n        \"role\": \"radiogroup\"\n      },\n      \"class\": \"hui-radio-root\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\"\n        ]\n      },\n      \"dataPart\": \"radio-root\",\n      \"description\": \"role=radiogroup; owns selection, arrow-key navigation, shared name/form/required/readOnly input semantics, and native reset reconciliation. A required group without an authored name receives one opaque, instance-stable native name so constraint validation remains effective; author name for a stable FormData key.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"defaultValue\",\n        \"disabled\",\n        \"form\",\n        \"label\",\n        \"name\",\n        \"onKeyDown\",\n        \"onValueChange\",\n        \"orientation\",\n        \"readOnly\",\n        \"ref\",\n        \"render\",\n        \"required\",\n        \"style\",\n        \"value\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-orientation\": [\n          \"horizontal\",\n          \"vertical\"\n        ],\n        \"data-readonly\": [\n          \"true\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultValue\",\n    \"disabled\",\n    \"form\",\n    \"label\",\n    \"name\",\n    \"onValueChange\",\n    \"orientation\",\n    \"readOnly\",\n    \"required\",\n    \"value\"\n  ]\n}\n",
      "path": "packages/heidi-ui/src/radio/radio.anatomy.json",
      "target": "components/ui/heidi/radio/radio.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": "/**\n * Shared form-association plumbing for the native-input primitives (P9).\n *\n * Checkbox, Switch and Radio each render a visually hidden, form-associated\n * `<input>` behind a styled control. Three pieces of that were copy-pasted.\n *\n * ponytail: as with P6, diffing first changed the slice. The style constant is\n * byte-identical in all three. The form-reset effect and the change-details\n * construction are byte-identical in TWO — Checkbox and Switch — and Radio has\n * neither: it is a GROUP, so N items share one form, and it refcounts\n * registrations per form instead of running one effect per control. Wrapping\n * that in a per-control hook would not be a thin wrapper, it would be a\n * rewrite of working group-registration logic. Radio therefore takes the style\n * constant only. The same is true of the labelled-by hooks the audit listed as\n * optional: Radio's takes an extra `initialFallback` parameter because a group\n * item inherits a name its sibling may supply, so they are not one hook with a\n * different prefix, and they stay where they are.\n */\n\nimport { useEffect, useRef, type CSSProperties } from \"react\";\n\n/**\n * Off-screen but focusable and form-associated. `position: fixed` (not\n * absolute) so an ancestor's transform cannot drag the input into view, and\n * `clip-path` rather than the legacy `clip`.\n */\nexport const VISUALLY_HIDDEN_INPUT_STYLE: CSSProperties = {\n  blockSize: 1,\n  border: 0,\n  clipPath: \"inset(50%)\",\n  inlineSize: 1,\n  insetBlockStart: 0,\n  insetInlineStart: 0,\n  margin: -1,\n  overflow: \"hidden\",\n  padding: 0,\n  position: \"fixed\",\n  whiteSpace: \"nowrap\"\n};\n\n/**\n * The details object a cancelable change callback receives. Structurally\n * identical to `CheckboxRootChangeEventDetails` and\n * `SwitchRootChangeEventDetails`, which stay declared in their own files so\n * the public type names — and their doc comments — remain per-component.\n */\n// Generic in the trigger element: Checkbox's is an `HTMLElement` (its root can\n// be re-rendered as any host) while Switch narrows to `HTMLButtonElement`. The\n// two constructions looked byte-identical because the difference lives in the\n// TYPE annotation above each one, not in the object literal.\nexport type NativeChangeEventDetails<TTrigger extends HTMLElement = HTMLElement> = {\n  cancel: () => void;\n  event: Event;\n  readonly isCanceled: boolean;\n  reason: \"none\";\n  trigger: TTrigger | undefined;\n};\n\nexport function createNativeChangeEventDetails<TTrigger extends HTMLElement>(\n  event: Event,\n  trigger: TTrigger | undefined\n): NativeChangeEventDetails<TTrigger> {\n  let canceled = false;\n  return {\n    cancel: () => {\n      canceled = true;\n    },\n    event,\n    get isCanceled() {\n      return canceled;\n    },\n    reason: \"none\",\n    trigger\n  };\n}\n\nexport type FormResetSyncOptions = {\n  /** Controlled inputs keep owner state; only the DOM input is re-synced. */\n  controlled: boolean;\n  /** The `form` prop. Present so re-parenting re-runs the subscription. */\n  form: string | undefined;\n  nativeInputRef: { current: HTMLInputElement | null };\n  /** Restore uncontrolled state to its initial value. */\n  resetToInitial: () => void;\n  /** Push current state back onto the DOM input. */\n  syncNativeInput: () => void;\n};\n\n/**\n * Re-sync a form-associated input when its owning form resets.\n *\n * The native reset lands on the input BEFORE any React state update, so the\n * push back onto the DOM is deferred a frame — and the pending frame is\n * cancelled on both re-reset and unmount, so a rapid double reset cannot leave\n * a stale frame writing over newer state.\n */\nexport function useFormResetSync({\n  controlled,\n  form,\n  nativeInputRef,\n  resetToInitial,\n  syncNativeInput\n}: FormResetSyncOptions): void {\n  // ponytail: `resetToInitial` is read through a ref rather than listed as a\n  // dependency. Both call sites' effects re-subscribed on exactly\n  // [controlled, form, syncNativeInput]; adding a fourth dependency would make\n  // them re-subscribe whenever the caller's closure changed identity, which is\n  // a behaviour change in a slice whose premise is changing none.\n  const resetToInitialRef = useRef(resetToInitial);\n  resetToInitialRef.current = resetToInitial;\n\n  useEffect(() => {\n    const input = nativeInputRef.current;\n    const ownerForm = input?.form;\n    if (!input || !ownerForm) {\n      return;\n    }\n    let resetFrame = 0;\n    const handleReset = () => {\n      if (!controlled) {\n        resetToInitialRef.current();\n      }\n      cancelAnimationFrame(resetFrame);\n      resetFrame = requestAnimationFrame(syncNativeInput);\n    };\n    ownerForm.addEventListener(\"reset\", handleReset);\n    return () => {\n      cancelAnimationFrame(resetFrame);\n      ownerForm.removeEventListener(\"reset\", handleReset);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps -- see ponytail above\n  }, [controlled, form, syncNativeInput]);\n}\n",
      "path": "packages/heidi-ui/src/_internal/form-bridge.ts",
      "target": "components/ui/heidi/_internal/form-bridge.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 roving-tabindex arrow navigation (P7).\n *\n * Radio, Tabs, Toolbar and ToggleGroup each declared `NAVIGATION_KEYS`\n * byte-identically, each wrote the same enabled-descendants query with two\n * different selector strings, and each resolved arrow keys to a target index.\n *\n * ponytail: the audit listed the resolvers as four divergent implementations —\n * \"radio treats ArrowDown as forward regardless of orientation; toggle-group\n * accepts both axes when horizontal; tabs restricts to its axis\". Normalising\n * the spellings shows THREE of the four agree exactly. Radio's\n * `ArrowDown || (rtl ? ArrowLeft : ArrowRight)` and Toolbar's\n * `(horizontal ? horizontalForward : \"ArrowDown\") || (horizontal && \"ArrowDown\")\n * || (!horizontal && \"ArrowRight\")` accept the identical key set in both\n * orientations and both directions; they only look different. The single real\n * divergence is Tabs, which returns early on a cross-axis key. So the option\n * that matters is `crossAxis`, and it is false in exactly one place.\n *\n * That distinction is deliberate and worth keeping: a Toolbar is a\n * two-dimensional cluster where reaching for either axis is reasonable, while\n * a tablist has one axis and a cross-axis arrow should stay available to the\n * page (scrolling, or an outer roving container).\n *\n * `rtl` is read per event rather than per render because `direction` can change\n * without a React update, and it is gated on horizontal orientation: on a\n * vertical list the inline direction says nothing about which way is \"next\".\n */\n\nexport const NAVIGATION_KEYS = [\n  \"ArrowDown\",\n  \"ArrowLeft\",\n  \"ArrowRight\",\n  \"ArrowUp\",\n  \"End\",\n  \"Home\"\n];\n\n/**\n * Enabled items owned by THIS container — `closest()` re-anchors the match so\n * a nested roving container's items never join its ancestor's ring.\n */\nexport function collectRovingItems<TItem extends HTMLElement>(\n  container: HTMLElement,\n  itemSelector: string,\n  rootSelector: string,\n  isDisabled: (element: HTMLElement) => boolean\n): TItem[] {\n  return [...container.querySelectorAll<TItem>(itemSelector)].filter(\n    (element) =>\n      !isDisabled(element) && element.closest(rootSelector) === container\n  );\n}\n\nexport type RovingResolveOptions = {\n  /** Accept the perpendicular axis too. False only for Tabs. */\n  crossAxis: boolean;\n  orientation: \"horizontal\" | \"vertical\";\n};\n\n/**\n * The index this key should move focus to, or `null` when the key does not\n * apply — the caller must then return WITHOUT calling `preventDefault`, so an\n * unhandled arrow stays available to the page.\n */\nexport function resolveRovingIndex(\n  items: readonly HTMLElement[],\n  activeElement: Element | null,\n  key: string,\n  container: HTMLElement,\n  { crossAxis, orientation }: RovingResolveOptions\n): number | null {\n  if (items.length === 0) {\n    return null;\n  }\n  if (key === \"Home\") {\n    return 0;\n  }\n  if (key === \"End\") {\n    return items.length - 1;\n  }\n  const horizontal = orientation === \"horizontal\";\n  const rtl =\n    horizontal && getComputedStyle(container).direction === \"rtl\";\n  const inlineForward = rtl ? \"ArrowLeft\" : \"ArrowRight\";\n  const inlineBackward = rtl ? \"ArrowRight\" : \"ArrowLeft\";\n  const forwardKey = horizontal ? inlineForward : \"ArrowDown\";\n  const backwardKey = horizontal ? inlineBackward : \"ArrowUp\";\n  const crossForward = horizontal ? \"ArrowDown\" : inlineForward;\n  const crossBackward = horizontal ? \"ArrowUp\" : inlineBackward;\n\n  const current = items.indexOf(activeElement as HTMLElement);\n  if (key === forwardKey || (crossAxis && key === crossForward)) {\n    return current === -1 ? 0 : (current + 1) % items.length;\n  }\n  if (key === backwardKey || (crossAxis && key === crossBackward)) {\n    return (current <= 0 ? items.length : current) - 1;\n  }\n  return null;\n}\n",
      "path": "packages/heidi-ui/src/_internal/roving-focus.ts",
      "target": "components/ui/heidi/_internal/roving-focus.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": "radio",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Radio",
  "type": "registry:ui"
}
