{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "APG select-only combobox on the popover shell: [popover=auto] top layer + light dismiss, CSS anchor positioning, role=combobox/listbox/option. DOM focus stays on the trigger; aria-activedescendant tracks the highlighted option. Typeahead via printable characters. Controlled value + open. `multiple` switches the value axis to arrays: aria-multiselectable listbox, options toggle without closing it, and the hidden control becomes a <select multiple> that submits one repeated name=value pair per selection. Participates in forms through a hidden native <select> (name/form/required/disabled/readOnly + reset). No editable text field (select-only).",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui Select — APG select-only combobox on the popover shell\n * (docs/HEIDI-UI-HEADLESS.md § P4 / Slice G). Platform: [popover=auto] +\n * CSS anchors + light dismiss (same shell as Menu/Popover). Library owns\n * the APG layer: role=combobox/listbox/option, aria-expanded,\n * aria-activedescendant (DOM focus stays on the trigger), typeahead,\n * controlled value + open. Not an editable combobox; not a native <select>.\n *\n * `multiple` switches the whole value axis to arrays: the listbox becomes\n * aria-multiselectable, options toggle without closing it, and the hidden\n * form control becomes a `<select multiple>` that submits one repeated\n * `name=value` pair per selection.\n *\n * Composition: className / style / render / ref via renderHeidiElement.\n * Item `disabled` → aria-disabled + data-disabled; highlight/select skip them.\n * RSC rule: named exports from Server Components; Select.X is client sugar.\n */\n\nimport {\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 KeyboardEvent,\n  type MouseEvent,\n  type ReactNode,\n  type Ref,\n  type ToggleEvent\n} from \"react\";\nimport { synchronizeNativePopoverOpen } from \"../_internal/native-popover-sync\";\nimport { ariaDisabledAttrs, nativeDisabledAttrs } from \"../_internal/disabled\";\nimport { VISUALLY_HIDDEN_INPUT_STYLE } from \"../_internal/form-bridge\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { safeId } from \"../_internal/safe-id\";\nimport { type SelectAlign, type SelectSide } from \"./select.anatomy.generated\";\nimport { SELECT_CLASSES } from \"./select.classes.generated\";\n\nexport type { SelectAlign, SelectSide };\n\ntype SelectItemRecord = {\n  disabled: boolean;\n  id: string;\n  label: string;\n  value: string;\n};\n\n/** Reset and autofill commit through the same setter as a user pick. */\ntype SelectCommitOptions = { close?: boolean };\n\n/**\n * What one commit carries: a single option value (a pick, or a toggle when\n * `multiple`), or a whole selection to replace the current one with — which is\n * what a form reset does, and the only thing that does.\n */\ntype SelectCommitValue = string | readonly string[] | null;\n\ntype SelectHighlightOrigin = \"keyboard\" | \"pointer\" | \"seed\";\n\n/** Nothing selected. A module constant so it is referentially stable. */\nconst EMPTY_SELECTION: readonly string[] = [];\n\ntype SelectContextValue = {\n  activeDescendantId: string | undefined;\n  anchorName: string;\n  contentId: string;\n  disabled: boolean;\n  highlightOrigin: SelectHighlightOrigin;\n  highlightValue: string | null;\n  labelId: string;\n  labelPresent: boolean;\n  multiple: boolean;\n  open: boolean;\n  readOnly: boolean;\n  registerItem: (item: SelectItemRecord) => void;\n  required: boolean;\n  selectValue: (value: SelectCommitValue, options?: SelectCommitOptions) => void;\n  setHighlightOrigin: (origin: SelectHighlightOrigin) => void;\n  setHighlightValue: (value: string | null) => void;\n  setLabelPresent: (present: boolean) => void;\n  setOpen: (open: boolean) => void;\n  triggerId: string;\n  unregisterItem: (value: string) => void;\n  /** Single-select value. When `multiple`, the first selected option. */\n  value: string | null;\n  /** Every selected option, in submission order. Empty when nothing is picked. */\n  values: readonly string[];\n};\n\nconst SelectContext = createContext<SelectContextValue | null>(null);\n\nfunction useSelectContext(part: string): SelectContextValue {\n  const context = useContext(SelectContext);\n  if (!context) {\n    throw new Error(`Select.${part} must be rendered inside Select.Root.`);\n  }\n  return context;\n}\n\ntype SelectItemsContextValue = {\n  /** Options in DOM order, hidden ones removed. Never registration order. */\n  getOrderedItems: () => SelectItemRecord[];\n  itemsRef: { current: Map<string, SelectItemRecord> };\n  itemsVersion: number;\n};\n\nconst SelectItemsContext = createContext<SelectItemsContextValue | null>(null);\n\nfunction useSelectItems(): SelectItemsContextValue {\n  const context = useContext(SelectItemsContext);\n  if (!context) {\n    throw new Error(\"Select items context missing — render inside Select.Root.\");\n  }\n  return context;\n}\n\ntype SelectGroupContextValue = {\n  labelId: string;\n  setGroupLabelPresent: (present: boolean) => void;\n};\n\nconst SelectGroupContext = createContext<SelectGroupContextValue | null>(null);\n\ntype SelectItemTextContextValue = {\n  selected: boolean;\n  setItemText: (text: string) => void;\n};\n\nconst SelectItemTextContext = createContext<SelectItemTextContextValue | null>(null);\n\nconst TYPEAHEAD_RESET_MS = 500;\n\n/**\n * An option is navigable only if it is actually presented. `hidden`, `inert`,\n * `aria-hidden` and `display: none` all remove it.\n *\n * ponytail: this checks the OPTION's own computed `display`, never its box\n * geometry. The closed listbox is `display: none !important`, so every\n * offsetParent/getBoundingClientRect probe (the shape `_internal/menu-items.ts`\n * uses, because Menu's ring is only read while the menu is open) reports every\n * option as invisible — and Select reads this list to SEED the highlight at the\n * instant the popover opens, before layout exists. A descendant of a\n * `display: none` subtree still computes its own `display` normally, so this\n * test is stable in both states.\n */\nfunction isNavigableOption(element: HTMLElement): boolean {\n  if (\n    element.hidden ||\n    element.hasAttribute(\"inert\") ||\n    element.getAttribute(\"aria-hidden\") === \"true\"\n  ) {\n    return false;\n  }\n  return getComputedStyle(element).display !== \"none\";\n}\n\n/**\n * Deep text extraction for `Select.Item` children, used when neither an\n * explicit `label` nor a `Select.ItemText` supplies one.\n *\n * ponytail: recurses through element children instead of the old\n * `children.filter(typeof === \"string\")`, which returned \"\" for the extremely\n * common `<Item><Flag /><span>United States</span></Item>` and then registered\n * that empty string as the option's label — blanking the trigger after\n * selection and making the option untypeaheadable. Rejected: reading\n * `element.textContent` as the only source, because registration happens in an\n * effect and the trigger would render one frame of placeholder first.\n */\nfunction extractItemText(node: ReactNode): string {\n  if (node == null || typeof node === \"boolean\") {\n    return \"\";\n  }\n  if (typeof node === \"string\") {\n    return node;\n  }\n  if (typeof node === \"number\") {\n    return String(node);\n  }\n  if (Array.isArray(node)) {\n    return node.map((child) => extractItemText(child as ReactNode)).join(\"\");\n  }\n  if (isValidElement<{ children?: ReactNode }>(node)) {\n    return extractItemText(node.props.children);\n  }\n  return \"\";\n}\n\n/** One commit value read as a selection. `null` is \"nothing\", not `[null]`. */\nfunction toSelectionArray(value: SelectCommitValue): readonly string[] {\n  if (value == null) {\n    return EMPTY_SELECTION;\n  }\n  return typeof value === \"string\" ? [value] : value;\n}\n\n/** Add or remove one option from a multi-selection. */\nfunction toggleSelection(current: readonly string[], candidate: string): string[] {\n  return current.includes(candidate)\n    ? current.filter((entry) => entry !== candidate)\n    : [...current, candidate];\n}\n\n/**\n * The selection, deduplicated and ordered like the hidden control's options.\n *\n * ponytail: ordered against the registration Map, NOT the DOM. A native\n * `<select multiple>` emits its `name=value` pairs in OPTION order regardless\n * of the order the user clicked, and those options are rendered from this same\n * Map — so ordering here makes the array a consumer receives, the text\n * Select.Value paints, and the pairs the server reads one single order by\n * construction. Rejected: keeping click order (what the user did, but it\n * disagrees with the wire the moment a second option is picked above the\n * first) and ordering by `getOrderedItems()` (true DOM order, but it reads\n * layout, and it would disagree with the wire whenever DOM and registration\n * order diverge — which is exactly the case `getOrderedItems` exists for).\n * Values with no registered option keep their relative order at the end: they\n * cannot be submitted, but silently dropping a controlled owner's value would\n * be worse than carrying it.\n */\nfunction orderSelection(\n  selected: readonly string[],\n  registered: Iterable<string>\n): string[] {\n  const remaining = new Set(selected);\n  const ordered: string[] = [];\n  for (const candidate of registered) {\n    if (remaining.delete(candidate)) {\n      ordered.push(candidate);\n    }\n  }\n  for (const leftover of selected) {\n    if (remaining.delete(leftover)) {\n      ordered.push(leftover);\n    }\n  }\n  return ordered;\n}\n\ntype SelectFormResetOptions = {\n  /** Controlled owners keep their state; only the DOM control is re-synced. */\n  controlled: boolean;\n  /** The `form` prop. Present so re-parenting re-runs the subscription. */\n  form: string | undefined;\n  nativeSelectRef: { current: HTMLSelectElement | null };\n  resetToInitial: () => void;\n  syncNativeSelect: () => void;\n};\n\n/**\n * Re-sync the hidden form control when its owning form resets.\n *\n * ponytail: this is `_internal/form-bridge.ts`'s `useFormResetSync`, re-stated\n * here for one reason — that hook's `nativeInputRef` is typed\n * `{ current: HTMLInputElement | null }`, and Select's hidden control is a\n * `<select>`. Widening the shared signature is the right end state, but\n * Checkbox, Switch and Radio all read that file, so it is reported as a\n * follow-up extraction instead of edited underneath them.\n */\nfunction useSelectFormReset({\n  controlled,\n  form,\n  nativeSelectRef,\n  resetToInitial,\n  syncNativeSelect\n}: SelectFormResetOptions): void {\n  const resetToInitialRef = useRef(resetToInitial);\n  resetToInitialRef.current = resetToInitial;\n\n  useEffect(() => {\n    const control = nativeSelectRef.current;\n    const ownerForm = control?.form;\n    if (!control || !ownerForm) {\n      return;\n    }\n    let resetFrame = 0;\n    const handleReset = () => {\n      if (!controlled) {\n        resetToInitialRef.current();\n      }\n      cancelAnimationFrame(resetFrame);\n      resetFrame = requestAnimationFrame(syncNativeSelect);\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, syncNativeSelect]);\n}\n\ntype SelectRootSharedProps = {\n  children?: ReactNode;\n  defaultOpen?: boolean;\n  /** Blocks interaction AND removes the control from form submission. */\n  disabled?: boolean;\n  /** `id` of the owning <form> when the Select is rendered outside it. */\n  form?: string;\n  /** Ref to the hidden form-associated <select>. */\n  inputRef?: Ref<HTMLSelectElement>;\n  /** Submitted field name. Without it the control is not submitted at all. */\n  name?: string;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n  /** Value still submits, but the user cannot open or change the listbox. */\n  readOnly?: boolean;\n  required?: boolean;\n};\n\n/**\n * The value axis follows `multiple`, the way Base UI's generic does: omit the\n * prop and every value-carrying prop is exactly the single-select one it has\n * always been. `multiple={someBoolean}` widens honestly to the union rather\n * than picking a side.\n */\nexport type SelectRootValue<Multiple extends boolean> = Multiple extends true\n  ? readonly string[]\n  : string | null;\n\nexport type SelectRootProps<Multiple extends boolean = false> =\n  SelectRootSharedProps & {\n    defaultValue?: SelectRootValue<Multiple>;\n    /**\n     * Toggle selection. Picking does not close the listbox, the listbox is\n     * `aria-multiselectable`, the hidden control becomes a\n     * `<select multiple>` (one repeated `name=value` pair per selection), and\n     * `value` / `defaultValue` / `onValueChange` all become arrays.\n     */\n    multiple?: Multiple;\n    onValueChange?: (value: Multiple extends true ? string[] : string) => void;\n    value?: SelectRootValue<Multiple>;\n  };\n\n/**\n * The same props with the conditional axis already widened.\n *\n * ponytail: the implementation reads props through ONE cast to this type\n * rather than sprinkling casts through the body, because TypeScript cannot\n * narrow `Multiple extends true ? … : …` from a runtime `if (multiple)` — the\n * conditional stays deferred inside a generic function. Rejected: `any` on the\n * value props (it would silently accept a number from a consumer who typed the\n * generic wrong) and overloading `SelectRoot` (two signatures cannot share one\n * exported `SelectRootProps`, and every consumer already annotating that type\n * would have had to pick a variant by hand).\n */\ntype SelectRootRuntimeProps = SelectRootSharedProps & {\n  defaultValue?: string | readonly string[] | null;\n  multiple?: boolean;\n  onValueChange?: (value: string | string[]) => void;\n  value?: string | readonly string[] | null;\n};\n\nexport function SelectRoot<Multiple extends boolean = false>(\n  props: SelectRootProps<Multiple>\n) {\n  const {\n    children,\n    defaultOpen = false,\n    defaultValue,\n    disabled = false,\n    form,\n    inputRef,\n    multiple = false,\n    name,\n    onOpenChange,\n    onValueChange,\n    open: openProp,\n    readOnly = false,\n    required = false,\n    value: valueProp\n  } = props as SelectRootRuntimeProps;\n  const id = useId();\n  const safe = safeId(id);\n  const contentId = `hui-select-${safe}`;\n  const labelId = `hui-select-label-${safe}`;\n  const triggerId = `hui-select-trigger-${safe}`;\n  const anchorName = `--hui-select-anchor-${safe}`;\n\n  // An omitted defaultValue means \"nothing\", which is `null` for one value and\n  // an empty array for many — the same state expressed in each axis's own type.\n  // Read once, by both the initial state and the reset baseline, so a consumer\n  // who flips `multiple` after mount cannot leave the two disagreeing.\n  const initialValue: string | readonly string[] | null =\n    defaultValue ?? (multiple ? EMPTY_SELECTION : null);\n  const [internalValue, setInternalValue] = useState(initialValue);\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const [highlightOrigin, setHighlightOrigin] =\n    useState<SelectHighlightOrigin>(\"seed\");\n  const [highlightValue, setHighlightValue] = useState<string | null>(null);\n  const [itemsVersion, setItemsVersion] = useState(0);\n  const [labelPresent, setLabelPresent] = useState(false);\n  const itemsRef = useRef(new Map<string, SelectItemRecord>());\n  const nativeSelectRef = useRef<HTMLSelectElement | null>(null);\n  const initialValueRef = useRef(initialValue);\n\n  // Select has two independent controlled axes; each setter consults its own.\n  const openControlled = openProp !== undefined;\n  const valueControlled = valueProp !== undefined;\n  const open = openProp ?? internalOpen;\n  const rawValue = valueControlled ? valueProp : internalValue;\n  const valueRef = useRef(rawValue);\n  valueRef.current = rawValue;\n\n  const values = useMemo(() => {\n    // itemsVersion is the read trigger, not an input: the order comes out of a\n    // ref'd Map, so registering or unregistering an option has to re-derive it.\n    void itemsVersion;\n    return orderSelection(toSelectionArray(rawValue), itemsRef.current.keys());\n  }, [itemsVersion, rawValue]);\n  // Every part that only ever shows one option keeps reading `value`. In\n  // multiple mode that is the first selection, which is what a collapsed\n  // single-line trigger would show anyway; `values` carries the whole truth.\n  const value = multiple\n    ? values[0] ?? null\n    : typeof rawValue === \"string\"\n      ? rawValue\n      : null;\n\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (!openControlled) {\n        setInternalOpen(next);\n      }\n      onOpenChange?.(next);\n    },\n    [onOpenChange, openControlled]\n  );\n\n  // ponytail: form reset commits through THIS setter rather than calling\n  // `setInternalValue` from its own handler. Contract 20o counts every\n  // `setInternalValue(` in the file and requires all of them to sit inside this\n  // callback's `!valueControlled` guard — a second write site would be exactly\n  // the split authority that lock exists to prevent. `close: false` is the only\n  // difference a reset needs, because `hidePopover()` throws InvalidStateError\n  // on a popover that is not showing.\n  const selectValue = useCallback(\n    (next: SelectCommitValue, options?: SelectCommitOptions) => {\n      const item = typeof next === \"string\" ? itemsRef.current.get(next) : undefined;\n      if (item?.disabled) {\n        return;\n      }\n      // One write site, so the whole next selection is computed first: a\n      // string toggles, an array (form reset) replaces the lot.\n      const nextValue = multiple\n        ? orderSelection(\n            typeof next === \"string\"\n              ? toggleSelection(toSelectionArray(valueRef.current), next)\n              : toSelectionArray(next),\n            itemsRef.current.keys()\n          )\n        : typeof next === \"string\"\n          ? next\n          : null;\n      if (!valueControlled) {\n        setInternalValue(nextValue);\n        // ponytail: refresh the render-time mirror inside the guard, the same\n        // way navigation-menu does. Two toggles inside one tick both read this\n        // ref, and the second would otherwise re-toggle against pre-first\n        // state and drop the first option again.\n        valueRef.current = nextValue;\n      }\n      if (nextValue != null) {\n        onValueChange?.(nextValue);\n      }\n      // Toggling never closes a multi-select: the user is still choosing, and\n      // Escape / outside click / Tab remain the ways out.\n      if (options?.close === false || multiple) {\n        return;\n      }\n      // Close via the native popover API — onToggle syncs React open state\n      // (same pattern as Menu.Item). Avoids double onOpenChange.\n      document.getElementById(contentId)?.hidePopover();\n    },\n    [contentId, multiple, onValueChange, valueControlled]\n  );\n\n  const registerItem = useCallback((item: SelectItemRecord) => {\n    itemsRef.current.set(item.value, item);\n    setItemsVersion((n) => n + 1);\n  }, []);\n\n  const unregisterItem = useCallback((itemValue: string) => {\n    itemsRef.current.delete(itemValue);\n    setItemsVersion((n) => n + 1);\n  }, []);\n\n  // ponytail: keyboard order is read from the DOM, not from the registration\n  // Map. The Map is insertion-ordered, and the registering effect re-runs (so\n  // `delete` + `set`, which APPENDS) whenever an item's `disabled` or label\n  // changes — flipping option #2 of 5 silently moved it to the end of the\n  // Arrow/Home/End/typeahead ring. The Map stays for value → record lookup.\n  // Rejected: sorting registered records with `compareDocumentPosition`, which\n  // is the same DOM read at O(n log n) with a comparator instead of O(n).\n  const getOrderedItems = useCallback((): SelectItemRecord[] => {\n    const registered = [...itemsRef.current.values()];\n    if (typeof document === \"undefined\") {\n      return registered;\n    }\n    const content = document.getElementById(contentId);\n    if (!content) {\n      return registered;\n    }\n    const byId = new Map(registered.map((record) => [record.id, record]));\n    const ordered: SelectItemRecord[] = [];\n    for (const element of content.querySelectorAll<HTMLElement>('[role=\"option\"]')) {\n      const record = byId.get(element.id);\n      if (record && isNavigableOption(element)) {\n        ordered.push(record);\n      }\n    }\n    // Registration is context-based, so options may legitimately live outside\n    // this element. Falling back keeps their keyboard ring alive instead of\n    // trading a wrong order for no navigation at all.\n    return ordered.length > 0 || registered.length === 0 ? ordered : registered;\n  }, [contentId]);\n\n  const syncNativeSelect = useCallback(() => {\n    const control = nativeSelectRef.current;\n    if (!control) {\n      return;\n    }\n    if (multiple) {\n      // ponytail: `option.selected` per option, never `control.value`. That\n      // setter selects the FIRST option matching the string and deselects\n      // every other one, so a two-option selection would quietly submit a\n      // single pair — the exact bug this whole feature exists to avoid.\n      const initial = new Set(toSelectionArray(initialValueRef.current));\n      const current = new Set(toSelectionArray(valueRef.current));\n      for (const option of control.options) {\n        option.defaultSelected = initial.has(option.value);\n        option.selected = current.has(option.value);\n      }\n      return;\n    }\n    const initial =\n      typeof initialValueRef.current === \"string\" ? initialValueRef.current : \"\";\n    for (const option of control.options) {\n      option.defaultSelected = option.value === initial;\n    }\n    control.value = typeof valueRef.current === \"string\" ? valueRef.current : \"\";\n  }, [multiple]);\n\n  useEffect(syncNativeSelect, [itemsVersion, rawValue, syncNativeSelect]);\n\n  useSelectFormReset({\n    controlled: valueControlled,\n    form,\n    nativeSelectRef,\n    resetToInitial: () => selectValue(initialValueRef.current, { close: false }),\n    syncNativeSelect\n  });\n\n  const activeDescendantId =\n    open && highlightValue != null\n      ? itemsRef.current.get(highlightValue)?.id\n      : undefined;\n\n  // Seed highlight when the list opens (selected value, else first enabled).\n  useEffect(() => {\n    if (!open) {\n      setHighlightOrigin(\"seed\");\n      setHighlightValue(null);\n      return;\n    }\n    const enabled = getOrderedItems().filter((item) => !item.disabled);\n    if (enabled.length === 0) {\n      return;\n    }\n    // The first selection in submission order, which for a single-select\n    // Select is simply the selected option.\n    const preferred = values[0] ?? null;\n    setHighlightValue((current) => {\n      if (current != null) {\n        const currentItem = itemsRef.current.get(current);\n        if (currentItem && !currentItem.disabled) {\n          return current;\n        }\n      }\n      if (preferred != null) {\n        const selectedItem = itemsRef.current.get(preferred);\n        if (selectedItem && !selectedItem.disabled) {\n          return preferred;\n        }\n      }\n      return enabled[0]?.value ?? null;\n    });\n  }, [getOrderedItems, open, values, itemsVersion]);\n\n  // aria-activedescendant moves accessibility focus without moving DOM focus.\n  // Keep that virtual focus visible inside long, scrollable option lists.\n  useEffect(() => {\n    if (!open || highlightValue == null) {\n      return;\n    }\n    const highlightedId = itemsRef.current.get(highlightValue)?.id;\n    if (highlightedId == null) {\n      return;\n    }\n    document.getElementById(highlightedId)?.scrollIntoView({\n      block: \"nearest\",\n      inline: \"nearest\"\n    });\n  }, [contentId, highlightValue, itemsVersion, open]);\n\n  const contextValue = useMemo(\n    () => ({\n      activeDescendantId,\n      anchorName,\n      contentId,\n      disabled,\n      highlightOrigin,\n      highlightValue,\n      labelId,\n      labelPresent,\n      multiple,\n      open,\n      readOnly,\n      registerItem,\n      required,\n      selectValue,\n      setHighlightOrigin,\n      setHighlightValue,\n      setLabelPresent,\n      setOpen,\n      triggerId,\n      unregisterItem,\n      value,\n      values\n    }),\n    [\n      activeDescendantId,\n      anchorName,\n      contentId,\n      disabled,\n      highlightOrigin,\n      highlightValue,\n      labelId,\n      labelPresent,\n      multiple,\n      open,\n      readOnly,\n      registerItem,\n      required,\n      selectValue,\n      setOpen,\n      triggerId,\n      unregisterItem,\n      value,\n      values\n    ]\n  );\n\n  const itemsContext = useMemo(\n    () => ({ getOrderedItems, itemsRef, itemsVersion }),\n    [getOrderedItems, itemsVersion]\n  );\n\n  // The hidden control mirrors the registered options. It is never painted, so\n  // the labels are there for autofill heuristics and devtools, not for display.\n  // ponytail: an empty-string option is dropped only in single mode, where it\n  // would collide with the \"nothing selected\" placeholder option below. A\n  // `<select multiple>` has no placeholder option to collide with — nothing\n  // selected is zero selected options and zero submitted pairs — so there the\n  // empty string is an ordinary value and stays submittable.\n  const submittableItems = [...itemsRef.current.values()].filter(\n    (item) => multiple || item.value !== \"\"\n  );\n\n  return (\n    <SelectItemsContext value={itemsContext}>\n      <SelectContext value={contextValue}>\n        {children}\n        {/*\n          ponytail: a real <select> (Radix's shape), not an <input>. It is what\n          makes native `required` and native form reset work at all, and it is\n          the only element type `Field.Control` can ever accept. Kept off-screen\n          with clip-path at 1x1 rather than `display: none`, because a\n          display-none control is unfocusable and Chrome then refuses to submit\n          the form at all with \"An invalid form control is not focusable\".\n          No `onChange`: the browser can autofill this control with a value the\n          listbox does not contain, which would render an empty trigger — a\n          bug worse than not honouring autofill.\n        */}\n        <select\n          {...nativeDisabledAttrs(disabled)}\n          aria-hidden=\"true\"\n          data-hui-select-input=\"\"\n          defaultValue={\n            multiple\n              ? [...toSelectionArray(initialValueRef.current)]\n              : typeof initialValueRef.current === \"string\"\n                ? initialValueRef.current\n                : \"\"\n          }\n          form={form}\n          multiple={multiple}\n          name={name}\n          ref={mergeHeidiRefs(nativeSelectRef, inputRef)}\n          required={required}\n          style={VISUALLY_HIDDEN_INPUT_STYLE}\n          suppressHydrationWarning\n          tabIndex={-1}\n        >\n          {/*\n            \"Nothing selected\", and its empty text is that state rather than a\n            missing label. An option whose own value is the empty string\n            collapses onto this one: a form field submits a string, so the two\n            are indistinguishable on the wire and `required` cannot tell them\n            apart either. Radix has the same floor. Rejected: a sentinel like\n            \"__none__\", which would corrupt the submitted payload.\n            A multiple control needs none of this: it expresses \"nothing\" by\n            selecting no option at all, and a placeholder option here would be\n            a real, pickable, submittable empty pair.\n          */}\n          {multiple ? null : <option value=\"\">{\"\"}</option>}\n          {submittableItems.map((item) => (\n            <option key={item.value} value={item.value}>\n              {item.label}\n            </option>\n          ))}\n        </select>\n      </SelectContext>\n    </SelectItemsContext>\n  );\n}\n\nexport type SelectTriggerState = {\n  disabled: boolean;\n  multiple: boolean;\n  open: boolean;\n  readOnly: boolean;\n  required: boolean;\n  /** When `multiple`, the first selected option; `values` has them all. */\n  value: string | null;\n  values: readonly string[];\n};\n\ntype SelectTriggerNativeProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  | \"aria-activedescendant\"\n  | \"aria-autocomplete\"\n  | \"aria-controls\"\n  | \"aria-expanded\"\n  | \"aria-haspopup\"\n  | \"children\"\n  | \"className\"\n  | \"id\"\n  | \"onKeyDown\"\n  | \"popoverTarget\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n  | \"type\"\n>;\n\nexport type SelectTriggerProps = HeidiIntrinsicHostProps<\n  SelectTriggerState,\n  \"button\"\n> &\n  SelectTriggerNativeProps & {\n    children?: ReactNode;\n    onKeyDown?: ComponentPropsWithoutRef<\"button\">[\"onKeyDown\"];\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function SelectTrigger({\n  \"aria-labelledby\": ariaLabelledByProp,\n  children,\n  className,\n  onKeyDown: onKeyDownProp,\n  onPointerDown: onPointerDownProp,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: SelectTriggerProps) {\n  const {\n    activeDescendantId,\n    anchorName,\n    contentId,\n    disabled,\n    highlightValue,\n    labelId,\n    labelPresent,\n    multiple,\n    open,\n    readOnly,\n    required,\n    selectValue,\n    setHighlightOrigin,\n    setHighlightValue,\n    triggerId,\n    value,\n    values\n  } = useSelectContext(\"Trigger\");\n  const { getOrderedItems } = useSelectItems();\n  const typeaheadRef = useRef({ buffer: \"\", timer: null as ReturnType<typeof setTimeout> | null });\n  const inert = disabled || readOnly;\n\n  const moveHighlight = (delta: number) => {\n    const items = getOrderedItems().filter((item) => !item.disabled);\n    if (items.length === 0) {\n      return;\n    }\n    const currentIndex = highlightValue != null\n      ? items.findIndex((item) => item.value === highlightValue)\n      : -1;\n    const next =\n      delta < 0\n        ? (currentIndex <= 0 ? items.length : currentIndex) - 1\n        : currentIndex === -1\n          ? 0\n          : (currentIndex + 1) % items.length;\n    setHighlightValue(items[next]?.value ?? null);\n  };\n\n  const jumpHighlight = (to: \"first\" | \"last\") => {\n    const items = getOrderedItems().filter((item) => !item.disabled);\n    if (items.length === 0) {\n      return;\n    }\n    setHighlightValue((to === \"first\" ? items[0] : items[items.length - 1])?.value ?? null);\n  };\n\n  const typeahead = (key: string) => {\n    const state = typeaheadRef.current;\n    if (state.timer) {\n      clearTimeout(state.timer);\n    }\n    const normalizedKey = key.toLowerCase();\n    const repeatedCharacter =\n      state.buffer.length > 0 &&\n      [...state.buffer].every((character) => character === normalizedKey);\n    state.buffer = repeatedCharacter\n      ? normalizedKey\n      : `${state.buffer}${normalizedKey}`;\n    state.timer = setTimeout(() => {\n      state.buffer = \"\";\n      state.timer = null;\n    }, TYPEAHEAD_RESET_MS);\n\n    const items = getOrderedItems().filter((item) => !item.disabled);\n    const start = highlightValue != null\n      ? items.findIndex((item) => item.value === highlightValue) + 1\n      : 0;\n    const ordered = [...items.slice(start), ...items.slice(0, start)];\n    const match = ordered.find((item) => item.label.toLowerCase().startsWith(state.buffer));\n    if (match) {\n      setHighlightValue(match.value);\n    }\n  };\n\n  const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n    // readOnly keeps the trigger focusable (a disabled control is skipped by\n    // the tab ring and cannot be read by a keyboard user at all) but owns no\n    // listbox interaction. `disabled` is belt-and-braces: a disabled button\n    // receives no key events.\n    if (inert) {\n      return;\n    }\n    setHighlightOrigin(\"keyboard\");\n    if (event.key === \"ArrowDown\") {\n      event.preventDefault();\n      if (!open) {\n        document.getElementById(contentId)?.showPopover();\n        return;\n      }\n      moveHighlight(1);\n      return;\n    }\n    if (event.key === \"ArrowUp\") {\n      event.preventDefault();\n      if (!open) {\n        document.getElementById(contentId)?.showPopover();\n        return;\n      }\n      moveHighlight(-1);\n      return;\n    }\n    if (event.key === \"Home\" && open) {\n      event.preventDefault();\n      jumpHighlight(\"first\");\n      return;\n    }\n    if (event.key === \"End\" && open) {\n      event.preventDefault();\n      jumpHighlight(\"last\");\n      return;\n    }\n    if ((event.key === \"Enter\" || event.key === \" \") && open) {\n      event.preventDefault();\n      if (highlightValue != null) {\n        selectValue(highlightValue);\n      }\n      return;\n    }\n    if (event.key === \"Escape\" && open) {\n      event.preventDefault();\n      document.getElementById(contentId)?.hidePopover();\n      return;\n    }\n    if (event.key === \"Tab\" && open) {\n      // ponytail: a multi-select commits nothing on the way out. Every toggle\n      // already committed, so \"commit the highlighted option\" here would add a\n      // selection the user only ever arrowed past. Tab just leaves.\n      if (multiple) {\n        document.getElementById(contentId)?.hidePopover();\n        return;\n      }\n      if (highlightValue != null) {\n        // Do not prevent the native Tab action: commit, close, then let focus\n        // continue to the next sequential target.\n        selectValue(highlightValue);\n      } else {\n        document.getElementById(contentId)?.hidePopover();\n      }\n      return;\n    }\n    // Space while closed: let the native popoverTarget / button activation open.\n    if (\n      event.key !== \" \" &&\n      event.key.length === 1 &&\n      !event.ctrlKey &&\n      !event.metaKey &&\n      !event.altKey\n    ) {\n      event.preventDefault();\n      if (!open) {\n        document.getElementById(contentId)?.showPopover();\n      }\n      typeahead(event.key);\n    }\n  };\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.trigger,\n    dataPart: \"select-trigger\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      \"aria-activedescendant\": activeDescendantId,\n      // ponytail: no `aria-autocomplete`. This is the APG *select-only*\n      // combobox (header docblock) — the typeahead highlights an existing\n      // option and never filters the list, so announcing list autocompletion\n      // promises a filtering behaviour that does not exist. The attribute\n      // stays in the props Omit so consumers cannot re-add it.\n      \"aria-controls\": contentId,\n      \"aria-expanded\": open,\n      \"aria-haspopup\": \"listbox\",\n      // ponytail: when a Select.Label exists the combobox names itself from the\n      // label AND its own subtree (the selected value) — the shape Base UI\n      // ships. Naming it from the label alone, as the bare APG example does,\n      // drops the value out of the accessible name, and naming it from content\n      // alone leaves a keyboard user with no idea what the control is for.\n      \"aria-labelledby\":\n        ariaLabelledByProp ?? (labelPresent ? `${labelId} ${triggerId}` : undefined),\n      \"aria-readonly\": readOnly || undefined,\n      \"aria-required\": required || undefined,\n      children,\n      \"data-readonly\": readOnly ? \"true\" : undefined,\n      id: triggerId,\n      onKeyDown: composeHeidiEventHandlers(onKeyDownProp, onKeyDown),\n      onPointerDown: composeHeidiEventHandlers(onPointerDownProp, () => {\n        if (!inert) {\n          setHighlightOrigin(\"pointer\");\n        }\n      }),\n      // A readOnly Select must not open. Dropping the invoker relationship is\n      // what stops the platform from toggling the popover behind React's back.\n      popoverTarget: inert ? undefined : contentId,\n      ref,\n      role: \"combobox\",\n      type: \"button\",\n      ...nativeDisabledAttrs(disabled)\n    },\n    renderProps: { className, render, style },\n    state: { disabled, multiple, open, readOnly, required, value, values },\n    structuralStyle: { anchorName } as CSSProperties\n  });\n}\n\nexport type SelectValueState = {\n  multiple: boolean;\n  placeholder: boolean;\n  value: string | null;\n  values: readonly string[];\n};\n\ntype SelectValueNativeProps = Omit<\n  ComponentPropsWithoutRef<\"span\">,\n  \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\nexport type SelectValueProps = HeidiIntrinsicHostProps<\n  SelectValueState,\n  \"span\"\n> &\n  SelectValueNativeProps & {\n    placeholder?: string;\n    ref?: Ref<HTMLSpanElement>;\n    /**\n     * Joins the selected option labels when the Select is `multiple`.\n     * Ignored otherwise.\n     */\n    separator?: string;\n  };\n\nexport function SelectValue({\n  className,\n  placeholder = \"Select…\",\n  ref,\n  render,\n  // ponytail: the joiner is a PROP with a documented default rather than a\n  // hard-coded string, because \", \" is a choice no library can make correctly\n  // for every language, and a consumer who needs \" · \" or \" / \" should not\n  // have to abandon the part to get it. The default is the comma-space of\n  // ordinary prose. For anything that is not a joined list — a count, chips, a\n  // \"+2 more\" — use `render` and read `state.values`, which is the same array,\n  // in the same order, that the form submits.\n  separator = \", \",\n  style,\n  ...nativeProps\n}: SelectValueProps) {\n  const { multiple, value, values } = useSelectContext(\"Value\");\n  const { itemsRef, itemsVersion } = useSelectItems();\n  // itemsVersion forces re-read when option labels register after mount.\n  void itemsVersion;\n  // ponytail: an empty registered label must never beat the value. The old\n  // `?? value` only caught `undefined`, so an option whose label resolved to \"\"\n  // rendered a trigger with nothing in it at all — no label, no placeholder.\n  const registeredLabel = value != null ? itemsRef.current.get(value)?.label : undefined;\n  const labelFor = (candidate: string): string => {\n    const registered = itemsRef.current.get(candidate)?.label;\n    return registered != null && registered.length > 0 ? registered : candidate;\n  };\n  const selectedLabel = multiple\n    ? values.length > 0\n      ? values.map(labelFor).join(separator)\n      : null\n    : value != null\n      ? registeredLabel != null && registeredLabel.length > 0\n        ? registeredLabel\n        : value\n      : null;\n  const showingPlaceholder = selectedLabel === null;\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.value,\n    dataPart: \"select-value\",\n    element: \"span\",\n    props: {\n      ...nativeProps,\n      children: showingPlaceholder ? placeholder : selectedLabel,\n      \"data-placeholder\": showingPlaceholder ? \"true\" : undefined,\n      ref\n    },\n    renderProps: { className, render, style },\n    state: { multiple, placeholder: showingPlaceholder, value, values }\n  });\n}\n\nexport type SelectContentState = {\n  align: SelectAlign;\n  multiple: boolean;\n  open: boolean;\n  side: SelectSide;\n};\n\ntype SelectContentNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  // aria-multiselectable is the library's to state: it must agree with the\n  // Root's `multiple`, and a consumer who could set it could contradict it.\n  | \"aria-multiselectable\"\n  | \"children\"\n  | \"className\"\n  | \"id\"\n  | \"onBeforeToggle\"\n  | \"onToggle\"\n  | \"popover\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n  | \"tabIndex\"\n>;\n\nexport type SelectContentProps = HeidiIntrinsicHostProps<\n  SelectContentState,\n  \"div\"\n> &\n  SelectContentNativeProps & {\n    align?: SelectAlign;\n    /** Cross-axis offset in CSS pixels. */\n    alignOffset?: number;\n    children?: ReactNode;\n    onBeforeToggle?: ComponentPropsWithoutRef<\"div\">[\"onBeforeToggle\"];\n    onToggle?: ComponentPropsWithoutRef<\"div\">[\"onToggle\"];\n    ref?: Ref<HTMLDivElement>;\n    side?: SelectSide;\n    /** Main-axis gap in CSS pixels. */\n    sideOffset?: number;\n  };\n\nexport function SelectContent({\n  align = \"start\",\n  alignOffset = 0,\n  \"aria-labelledby\": ariaLabelledByProp,\n  children,\n  className,\n  onBeforeToggle: onBeforeToggleProp,\n  onToggle: onToggleProp,\n  ref,\n  render,\n  side = \"bottom\",\n  sideOffset = 4,\n  style,\n  ...nativeProps\n}: SelectContentProps) {\n  const {\n    anchorName,\n    contentId,\n    labelId,\n    labelPresent,\n    multiple,\n    open,\n    setOpen,\n    triggerId\n  } = useSelectContext(\"Content\");\n  const contentRef = useRef<HTMLDivElement | null>(null);\n  const internalNativeTransitionRef = useRef(false);\n  const lastBeforeToggleRef = useRef<{\n    internal: boolean;\n    open: boolean;\n  } | null>(null);\n  const [nativeTransitionVersion, setNativeTransitionVersion] = useState(0);\n\n  const synchronizeNativeOpen = useCallback(\n    (element: HTMLDivElement, next: boolean) => {\n      synchronizeNativePopoverOpen(element, next, {\n        transitionRef: internalNativeTransitionRef\n      });\n    },\n    []\n  );\n\n  // React state is authoritative for defaultOpen and controlled open. Native\n  // trigger/light-dismiss transitions still request changes through onToggle;\n  // when a controlled owner rejects one, this pass restores the top layer so\n  // :popover-open and aria-expanded cannot remain contradictory.\n  useEffect(() => {\n    const element = contentRef.current;\n    if (!element || typeof element.showPopover !== \"function\") {\n      return;\n    }\n    synchronizeNativeOpen(element, open);\n  }, [nativeTransitionVersion, open, synchronizeNativeOpen]);\n\n  const onBeforeToggle = (event: ToggleEvent<HTMLDivElement>) => {\n    onBeforeToggleProp?.(event);\n    if (event.defaultPrevented) {\n      return;\n    }\n    if (event.target !== event.currentTarget) {\n      return;\n    }\n    const nativeToggle = event.nativeEvent as Event & { newState?: string };\n    lastBeforeToggleRef.current = {\n      internal: internalNativeTransitionRef.current,\n      open: nativeToggle.newState === \"open\"\n    };\n  };\n\n  const onToggle = (event: ToggleEvent<HTMLDivElement>) => {\n    if (event.target !== event.currentTarget) {\n      return;\n    }\n    onToggleProp?.(event);\n    const nativeToggle = event.nativeEvent as unknown as { newState?: string };\n    const isOpen = nativeToggle.newState === \"open\";\n    const transition = lastBeforeToggleRef.current;\n    lastBeforeToggleRef.current = null;\n    const internallySynchronized =\n      transition?.internal === true && transition.open === isOpen;\n    if (!internallySynchronized) {\n      setOpen(isOpen);\n      // A controlled owner may preserve the same React prop. Force the\n      // synchronization effect to compare that unchanged prop with native\n      // state after every browser-owned transition.\n      setNativeTransitionVersion((version) => version + 1);\n    }\n    if (!isOpen) {\n      const active = document.activeElement;\n      if (!active || active === document.body) {\n        document.getElementById(triggerId)?.focus();\n      }\n    } else {\n      // Keep DOM focus on the combobox (APG select-only pattern).\n      document.getElementById(triggerId)?.focus();\n    }\n  };\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.content,\n    dataPart: \"select-content\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      // ponytail: NOT `triggerId`. The trigger's accessible name is computed\n      // from its own subtree — Select.Value — so labelling the listbox with it\n      // named the listbox \"Apple\" the moment Apple was picked, and \"Pick a\n      // fruit\" before that. The APG select-only example points BOTH the\n      // combobox and the listbox at the external label, which is what a\n      // Select.Label (or a consumer-supplied aria-labelledby) now does. With\n      // neither, the listbox stays unnamed: no name beats a wrong name.\n      \"aria-labelledby\": ariaLabelledByProp ?? (labelPresent ? labelId : undefined),\n      // Absent, not \"false\", in a single-select listbox: the default IS false,\n      // and an explicit one is noise a screen reader may still announce.\n      \"aria-multiselectable\": multiple || undefined,\n      children,\n      \"data-align\": align,\n      \"data-side\": side,\n      id: contentId,\n      onBeforeToggle,\n      onToggle,\n      popover: \"auto\",\n      ref: mergeHeidiRefs(contentRef, ref),\n      role: \"listbox\",\n      tabIndex: -1\n    },\n    renderProps: { className, render, style },\n    state: { align, multiple, open, side },\n    structuralStyle: {\n      \"--_hui-select-align-offset\": `${alignOffset}px`,\n      \"--_hui-select-side-offset\": `${Math.max(0, sideOffset)}px`,\n      positionAnchor: anchorName\n    } as CSSProperties\n  });\n}\n\nexport type SelectItemState = {\n  disabled: boolean;\n  highlighted: boolean;\n  selected: boolean;\n  value: string;\n};\n\ntype SelectItemNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  | \"aria-selected\"\n  | \"children\"\n  | \"className\"\n  | \"onClick\"\n  | \"onMouseEnter\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n>;\n\nexport type SelectItemProps = HeidiIntrinsicHostProps<\n  SelectItemState,\n  \"div\"\n> &\n  SelectItemNativeProps & {\n    children?: ReactNode;\n    disabled?: boolean;\n    /**\n     * Overrides the text used by the trigger and by typeahead. Prefer this (or\n     * `Select.ItemText`) whenever the option's children are not plain text.\n     */\n    label?: string;\n    onClick?: ComponentPropsWithoutRef<\"div\">[\"onClick\"];\n    onMouseEnter?: ComponentPropsWithoutRef<\"div\">[\"onMouseEnter\"];\n    ref?: Ref<HTMLDivElement>;\n    value: string;\n  };\n\nexport function SelectItem({\n  children,\n  className,\n  disabled = false,\n  label: labelProp,\n  onClick,\n  onMouseEnter,\n  ref,\n  render,\n  style,\n  value: itemValue,\n  ...nativeProps\n}: SelectItemProps) {\n  const {\n    contentId,\n    disabled: rootDisabled,\n    highlightOrigin,\n    highlightValue,\n    readOnly,\n    registerItem,\n    selectValue,\n    setHighlightOrigin,\n    setHighlightValue,\n    unregisterItem,\n    values\n  } = useSelectContext(\"Item\");\n  const [itemText, setItemText] = useState<string | null>(null);\n  // Value-derived ids collide for valid pairs such as \"\" / \"empty\" or\n  // \"a b\" / \"ab\". React's per-instance id keeps aria-activedescendant exact.\n  const id = `${contentId}-option-${safeId(useId())}`;\n  const derivedText = extractItemText(children);\n  // Precedence: explicit prop, then a Select.ItemText part, then the option's\n  // own text, then the raw value. The value is a floor, never a preference —\n  // registering \"\" here is what blanked the trigger and killed typeahead.\n  const resolvedLabel =\n    labelProp ??\n    (itemText != null && itemText.length > 0\n      ? itemText\n      : derivedText.length > 0\n        ? derivedText\n        : String(itemValue));\n\n  useEffect(() => {\n    registerItem({ disabled, id, label: resolvedLabel, value: itemValue });\n    return () => unregisterItem(itemValue);\n  }, [disabled, id, itemValue, registerItem, resolvedLabel, unregisterItem]);\n\n  // One membership test for both axes: `values` is the single value as a\n  // one-element array, so an option whose value is \"\" still matches exactly.\n  const selected = values.includes(itemValue);\n  const highlighted = highlightValue === itemValue;\n  const itemTextContext = useMemo(\n    () => ({ selected, setItemText }),\n    [selected]\n  );\n  const inert = disabled || rootDisabled || readOnly;\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.item,\n    dataPart: \"select-item\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      \"aria-selected\": selected,\n      children: (\n        <SelectItemTextContext value={itemTextContext}>{children}</SelectItemTextContext>\n      ),\n      \"data-highlighted\": highlighted ? \"true\" : undefined,\n      \"data-highlight-origin\": highlighted ? highlightOrigin : undefined,\n      \"data-selected\": selected ? \"true\" : undefined,\n      id,\n      onClick: composeHeidiEventHandlers(\n        onClick,\n        (event: MouseEvent<HTMLDivElement>) => {\n          if (inert) {\n            return;\n          }\n          // FieldSelect is commonly nested in a visible <label>. An option is a\n          // non-labelable div, so an uncanceled click would run the label's\n          // default activation after hidePopover() and click the trigger again,\n          // reopening the menu. The option has no useful browser default of its\n          // own; cancel it while preserving our APG selection behavior.\n          event.preventDefault();\n          event.stopPropagation();\n          selectValue(itemValue);\n        }\n      ),\n      onMouseEnter: composeHeidiEventHandlers(onMouseEnter, () => {\n        if (!inert) {\n          setHighlightOrigin(\"pointer\");\n          setHighlightValue(itemValue);\n        }\n      }),\n      ref,\n      role: \"option\",\n      ...ariaDisabledAttrs(disabled)\n    },\n    renderProps: { className, render, style },\n    state: { disabled, highlighted, selected, value: itemValue }\n  });\n}\n\nexport type SelectItemTextState = { selected: boolean };\n\ntype SelectItemTextNativeProps = Omit<\n  ComponentPropsWithoutRef<\"span\">,\n  \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\nexport type SelectItemTextProps = HeidiIntrinsicHostProps<\n  SelectItemTextState,\n  \"span\"\n> &\n  SelectItemTextNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLSpanElement>;\n  };\n\n/**\n * The option's authoritative text. Wrap the words an option is known by when\n * the option also renders icons, badges or counts; the trigger and typeahead\n * then read exactly this and nothing else.\n */\nexport function SelectItemText({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: SelectItemTextProps) {\n  const context = useContext(SelectItemTextContext);\n  if (!context) {\n    throw new Error(\"Select.ItemText must be rendered inside Select.Item.\");\n  }\n  const { selected, setItemText } = context;\n  const textRef = useRef<HTMLSpanElement | null>(null);\n\n  // ponytail: no dependency array. The text may come from a child component\n  // this element cannot inspect (an i18n <Trans>, a memoized cell), so the only\n  // honest source is what the browser actually rendered. React bails out of the\n  // re-render when the string is unchanged, so running every commit is cheap.\n  useEffect(() => {\n    const text = textRef.current?.textContent?.trim() ?? \"\";\n    if (text.length > 0) {\n      setItemText(text);\n    }\n  });\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.itemText,\n    dataPart: \"select-item-text\",\n    element: \"span\",\n    props: {\n      ...nativeProps,\n      children,\n      ref: mergeHeidiRefs(textRef, ref)\n    },\n    renderProps: { className, render, style },\n    state: { selected }\n  });\n}\n\nexport type SelectLabelState = { disabled: boolean; required: boolean };\n\ntype SelectLabelNativeProps = Omit<\n  ComponentPropsWithoutRef<\"label\">,\n  \"children\" | \"className\" | \"htmlFor\" | \"id\" | \"ref\" | \"style\"\n>;\n\nexport type SelectLabelProps = HeidiIntrinsicHostProps<SelectLabelState, \"label\"> &\n  SelectLabelNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLLabelElement>;\n  };\n\n/**\n * The Select's own name. Renders a real `<label for>` — a button IS a labelable\n * element — so clicking it focuses the combobox with no JavaScript, and both\n * the combobox and the listbox point their `aria-labelledby` here.\n */\nexport function SelectLabel({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: SelectLabelProps) {\n  const { disabled, labelId, required, setLabelPresent, triggerId } =\n    useSelectContext(\"Label\");\n\n  useEffect(() => {\n    setLabelPresent(true);\n    return () => setLabelPresent(false);\n  }, [setLabelPresent]);\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.label,\n    dataPart: \"select-label\",\n    element: \"label\",\n    props: {\n      ...nativeProps,\n      children,\n      \"data-disabled\": disabled ? \"true\" : undefined,\n      htmlFor: triggerId,\n      id: labelId,\n      ref\n    },\n    renderProps: { className, render, style },\n    state: { disabled, required }\n  });\n}\n\nexport type SelectGroupState = { labelled: boolean };\n\ntype SelectGroupNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"aria-labelledby\" | \"children\" | \"className\" | \"ref\" | \"role\" | \"style\"\n>;\n\nexport type SelectGroupProps = HeidiIntrinsicHostProps<SelectGroupState, \"div\"> &\n  SelectGroupNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLDivElement>;\n  };\n\n/**\n * `role=\"group\"` inside the listbox (ARIA 1.2 allows exactly `option` and\n * `group` as listbox children). Name it with `Select.GroupLabel`.\n */\nexport function SelectGroup({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: SelectGroupProps) {\n  const labelId = `hui-select-group-${safeId(useId())}`;\n  const [labelled, setLabelled] = useState(false);\n  const groupContext = useMemo(\n    () => ({ labelId, setGroupLabelPresent: setLabelled }),\n    [labelId]\n  );\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.group,\n    dataPart: \"select-group\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      // ponytail: only pointed at the label once one has mounted. APG requires\n      // an option group to be named, but a dangling aria-labelledby is worse\n      // than an unnamed group — it makes the name resolve to the empty string,\n      // which some screen readers announce as a group with no members.\n      \"aria-labelledby\": labelled ? labelId : undefined,\n      children: (\n        <SelectGroupContext value={groupContext}>{children}</SelectGroupContext>\n      ),\n      ref,\n      role: \"group\"\n    },\n    renderProps: { className, render, style },\n    state: { labelled }\n  });\n}\n\nexport type SelectGroupLabelState = Record<string, never>;\n\ntype SelectGroupLabelNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"children\" | \"className\" | \"id\" | \"ref\" | \"style\"\n>;\n\nexport type SelectGroupLabelProps = HeidiIntrinsicHostProps<\n  SelectGroupLabelState,\n  \"div\"\n> &\n  SelectGroupLabelNativeProps & {\n    children?: ReactNode;\n    ref?: Ref<HTMLDivElement>;\n  };\n\n/** Names the enclosing `Select.Group`. Not an option; never navigable. */\nexport function SelectGroupLabel({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: SelectGroupLabelProps) {\n  const context = useContext(SelectGroupContext);\n  if (!context) {\n    throw new Error(\"Select.GroupLabel must be rendered inside Select.Group.\");\n  }\n  const { labelId, setGroupLabelPresent } = context;\n\n  useEffect(() => {\n    setGroupLabelPresent(true);\n    return () => setGroupLabelPresent(false);\n  }, [setGroupLabelPresent]);\n\n  return renderHeidiElement({\n    className: SELECT_CLASSES.groupLabel,\n    dataPart: \"select-group-label\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      children,\n      id: labelId,\n      ref\n    },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type SelectSeparatorState = Record<string, never>;\n\ntype SelectSeparatorNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  \"aria-hidden\" | \"children\" | \"className\" | \"ref\" | \"role\" | \"style\"\n>;\n\nexport type SelectSeparatorProps = HeidiIntrinsicHostProps<\n  SelectSeparatorState,\n  \"div\"\n> &\n  SelectSeparatorNativeProps & {\n    ref?: Ref<HTMLDivElement>;\n  };\n\n/**\n * A decorative rule between groups.\n *\n * ponytail: `role=\"presentation\"` + `aria-hidden`, NOT `role=\"separator\"`.\n * ARIA 1.2 permits only `option` and `group` as owned children of a listbox, so\n * a real separator role here would make the listbox's own content model\n * invalid; the visible rule carries no information the group labels do not.\n */\nexport function SelectSeparator({\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: SelectSeparatorProps) {\n  return renderHeidiElement({\n    className: SELECT_CLASSES.separator,\n    dataPart: \"select-separator\",\n    element: \"div\",\n    props: {\n      ...nativeProps,\n      \"aria-hidden\": \"true\",\n      ref,\n      role: \"presentation\"\n    },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\n/**\n * Namespace sugar for client components (`<Select.Root>`). From a Server\n * Component use the named exports (SelectRoot, …) instead — dotting into this\n * object from RSC throws \"Element type is invalid\".\n */\nexport const Select = {\n  Content: SelectContent,\n  Group: SelectGroup,\n  GroupLabel: SelectGroupLabel,\n  Item: SelectItem,\n  ItemText: SelectItemText,\n  Label: SelectLabel,\n  Root: SelectRoot,\n  Separator: SelectSeparator,\n  Trigger: SelectTrigger,\n  Value: SelectValue\n} as const;\n",
      "path": "packages/heidi-ui/src/select/select.tsx",
      "target": "components/ui/heidi/select/select.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui select — STRUCTURAL CSS only (platform behavior).\n * Popover shell positioning + flex column for options. No --hui-*.\n */\n\n@layer heidi-ui-base {\n  .hui-select-content {\n    --_hui-select-align-offset: 0px;\n    --_hui-select-side-offset: 4px;\n\n    box-sizing: border-box;\n    flex-direction: column;\n    inset: auto;\n    /* main-axis gap between trigger and listbox; sideOffset writes this var */\n    margin: var(--_hui-select-side-offset);\n    max-block-size: calc(100dvb - 1rem);\n    max-inline-size: calc(100dvi - 1rem);\n    overflow: auto;\n    overscroll-behavior: contain;\n    position: fixed;\n    position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n    scrollbar-gutter: stable;\n    transition-behavior: allow-discrete;\n    transition-property: display, overlay;\n  }\n\n  .hui-select-content:not(:popover-open) {\n    display: none !important;\n  }\n\n  .hui-select-content:popover-open {\n    display: flex;\n  }\n\n  /* Cross-axis nudge (alignOffset) runs along the side's perpendicular axis.\n     ponytail: keyed on data-side, not a resolved physical side. Select's\n     left/right map to the LOGICAL inline-start/inline-end position-areas\n     below, so there is no physical side to key on — and the cross axis is the\n     same either way (a left- or right-side panel nudges vertically), so the\n     mirror does not change which axis moves. `translate` rather than\n     `transform` keeps the nudge independent of the theme's scale animation. */\n  .hui-select-content[data-side=\"bottom\"],\n  .hui-select-content[data-side=\"top\"] {\n    translate: var(--_hui-select-align-offset) 0;\n  }\n\n  .hui-select-content[data-side=\"left\"],\n  .hui-select-content[data-side=\"right\"] {\n    translate: 0 var(--_hui-select-align-offset);\n  }\n\n  .hui-select-content[data-side=\"top\"][data-align=\"start\"] {\n    position-area: block-start span-inline-end;\n  }\n  .hui-select-content[data-side=\"top\"][data-align=\"center\"] {\n    position-area: block-start;\n  }\n  .hui-select-content[data-side=\"top\"][data-align=\"end\"] {\n    position-area: block-start span-inline-start;\n  }\n  .hui-select-content[data-side=\"bottom\"][data-align=\"start\"] {\n    position-area: block-end span-inline-end;\n  }\n  .hui-select-content[data-side=\"bottom\"][data-align=\"center\"] {\n    position-area: block-end;\n  }\n  .hui-select-content[data-side=\"bottom\"][data-align=\"end\"] {\n    position-area: block-end span-inline-start;\n  }\n  .hui-select-content[data-side=\"left\"][data-align=\"start\"] {\n    position-area: inline-start span-block-end;\n  }\n  .hui-select-content[data-side=\"left\"][data-align=\"center\"] {\n    position-area: inline-start;\n  }\n  .hui-select-content[data-side=\"left\"][data-align=\"end\"] {\n    position-area: inline-start span-block-start;\n  }\n  .hui-select-content[data-side=\"right\"][data-align=\"start\"] {\n    position-area: inline-end span-block-end;\n  }\n  .hui-select-content[data-side=\"right\"][data-align=\"center\"] {\n    position-area: inline-end;\n  }\n  .hui-select-content[data-side=\"right\"][data-align=\"end\"] {\n    position-area: inline-end span-block-start;\n  }\n\n  /* An option group is a flex item of the column; it owns its own column so\n     grouped options do not collapse onto one line. */\n  .hui-select-group {\n    display: flex;\n    flex-direction: column;\n  }\n\n  /* The naming wrapper is not a box of its own — options size to their text. */\n  .hui-select-item-text {\n    display: inline;\n  }\n\n  .hui-select-separator {\n    flex: none;\n  }\n\n  /* Highlighted option (aria-activedescendant target) — structural cue only\n     when theme is absent: a 2px inset outline so keyboard users still see it. */\n  .hui-select-item[data-highlighted=\"true\"] {\n    outline: 2px solid CanvasText;\n    outline-offset: -2px;\n  }\n\n  .hui-select-item[data-disabled=\"true\"] {\n    pointer-events: none;\n  }\n}\n",
      "path": "packages/heidi-ui/src/select/select.base.css",
      "target": "components/ui/heidi/select/select.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui select — VISUAL theme (opt-in). Consumes --hui-* semantic theme vars.\n */\n\n@layer heidi-ui {\n  .hui-select-trigger {\n    align-items: center;\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    display: inline-flex;\n    font: inherit;\n    gap: var(--hui-space-2);\n    justify-content: space-between;\n    min-inline-size: min(12rem, calc(100dvi - 1rem));\n    padding: var(--hui-space-1-5) var(--hui-space-3);\n    text-align: start;\n  }\n\n  .hui-select-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  .hui-select-trigger[data-disabled=\"true\"] {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  .hui-select-trigger[data-readonly=\"true\"] {\n    cursor: default;\n  }\n\n  .hui-select-label {\n    color: var(--hui-color-fg-default);\n    display: inline-block;\n    font-weight: var(--hui-font-weight-medium);\n  }\n\n  .hui-select-label[data-disabled=\"true\"] {\n    color: var(--hui-color-fg-muted);\n  }\n\n  .hui-select-value[data-placeholder=\"true\"] {\n    color: var(--hui-color-fg-muted);\n  }\n\n  /* ponytail: no `margin` here. The trigger↔listbox gap is the structural\n     sideOffset (--_hui-select-side-offset, default 4px = space-1) in\n     select.base.css. Rejected: leaving the gap in the theme — this sheet sits\n     in a later layer than heidi-ui-base, so a theme margin pins the gap and\n     makes the sideOffset prop silently inert under the shipped skin. */\n  .hui-select-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    gap: var(--hui-space-0-5);\n    min-width: min(12rem, calc(100dvi - 1rem));\n    opacity: 0;\n    padding: var(--hui-space-1-5);\n    transform: scale(0.97);\n    transition-duration: var(--hui-duration-fast);\n    transition-property: display, opacity, overlay, transform;\n    transition-timing-function: var(--hui-ease-out);\n  }\n\n  .hui-select-content:popover-open {\n    opacity: 1;\n    transform: none;\n  }\n\n  @starting-style {\n    .hui-select-content:popover-open {\n      opacity: 0;\n      transform: scale(0.97);\n    }\n  }\n\n  .hui-select-item {\n    border-radius: var(--hui-radius-full);\n    color: var(--hui-color-fg-default);\n    cursor: pointer;\n    font: inherit;\n    padding: var(--hui-space-1-5) var(--hui-space-2);\n  }\n\n  .hui-select-group-label {\n    color: var(--hui-color-fg-muted);\n    padding: var(--hui-space-1-5) var(--hui-space-2);\n  }\n\n  .hui-select-separator {\n    background: var(--hui-color-border-default);\n    block-size: var(--hui-border-width);\n    margin: var(--hui-space-1) var(--hui-space-2);\n  }\n\n  /*\n   * ponytail: the ghost tint is an ADDITIONAL cue, never the only one. It used\n   * to be paired with `outline: none`, which deleted the 2px structural ring\n   * base.css ships — and because DOM focus never leaves the trigger in the APG\n   * select-only pattern, `:focus-visible` cannot supply a replacement. A 6%\n   * (light) / 9% (dark) alpha wash measures ~1.05:1 against the raised panel,\n   * an order of magnitude under WCAG 2.1 SC 1.4.11's 3:1 for a focus\n   * indicator, and it is the same value as plain hover — so a keyboard user\n   * could not tell virtual focus from a stray pointer. Menu gets away with the\n   * tint alone only because it moves real DOM focus and therefore also gets a\n   * :focus-visible ring.\n   */\n  .hui-select-item[data-highlighted=\"true\"] {\n    background: var(--hui-color-interactive-ghost-bg-hover);\n  }\n\n  .hui-select-item[data-highlight-origin=\"keyboard\"] {\n    outline: var(--hui-focus-ring-width) solid var(--hui-color-focus-ring);\n    outline-offset: calc(-1 * var(--hui-focus-ring-offset));\n  }\n\n  /*\n   * Pointer-opened selects seed aria-activedescendant to the current option,\n   * but that seed is not a keyboard move. Match Menu's quiet hover treatment\n   * in that case; a real keyboard move changes the origin and restores the\n   * virtual-focus ring.\n   */\n  .hui-select-item[data-highlight-origin=\"pointer\"],\n  .hui-select-item[data-highlight-origin=\"seed\"] {\n    outline: none;\n  }\n\n  .hui-select-item[data-selected=\"true\"] {\n    font-weight: var(--hui-font-weight-medium);\n  }\n\n  .hui-select-item[data-disabled=\"true\"] {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .hui-select-content {\n      transition-duration: 0s;\n    }\n  }\n\n  @media (forced-colors: active) {\n    .hui-select-trigger {\n      border-color: CanvasText;\n    }\n\n    .hui-select-content {\n      border: var(--hui-border-width) solid CanvasText;\n    }\n\n    .hui-select-trigger:focus-visible {\n      outline-color: Highlight;\n    }\n\n    /* `forced-color-adjust: none` opts this rule out of the system palette, so\n       the highlight ring must be re-stated in system colors or it would keep\n       the brand focus-ring value against a Highlight ground. */\n    .hui-select-item[data-highlighted=\"true\"] {\n      background: Highlight;\n      color: HighlightText;\n      forced-color-adjust: none;\n      outline-color: HighlightText;\n    }\n\n    .hui-select-item[data-disabled=\"true\"] {\n      color: GrayText;\n      opacity: 1;\n    }\n\n    .hui-select-trigger[data-disabled=\"true\"] {\n      color: GrayText;\n      opacity: 1;\n    }\n\n    .hui-select-separator {\n      background: CanvasText;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/select/select.theme.css",
      "target": "components/ui/heidi/select/select.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui select — aggregator (base + Heidi adapter + theme).\n * Headless: import select.base.css only.\n * Themed: import this file (or heidi-ui/styles.css).\n */\n\n@import \"./select.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./select.theme.css\";\n",
      "path": "packages/heidi-ui/src/select/select.css",
      "target": "components/ui/heidi/select/select.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/select/select.base.css + packages/heidi-ui/src/select/select.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const SELECT_CLASSES = {\n  content: \"hui-select-content\",\n  group: \"hui-select-group\",\n  groupLabel: \"hui-select-group-label\",\n  item: \"hui-select-item\",\n  itemText: \"hui-select-item-text\",\n  label: \"hui-select-label\",\n  separator: \"hui-select-separator\",\n  trigger: \"hui-select-trigger\",\n  value: \"hui-select-value\",\n} as const;\n",
      "path": "packages/heidi-ui/src/select/select.classes.generated.ts",
      "target": "components/ui/heidi/select/select.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from select.anatomy.json + select.base.css + select.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type SelectAlign = \"center\" | \"end\" | \"start\";\nexport type SelectDisabled = \"true\";\nexport type SelectHighlightOrigin = \"keyboard\" | \"pointer\" | \"seed\";\nexport type SelectHighlighted = \"true\";\nexport type SelectPlaceholder = \"true\";\nexport type SelectReadonly = \"true\";\nexport type SelectSelected = \"true\";\nexport type SelectSide = \"bottom\" | \"left\" | \"right\" | \"top\";\n\nexport const SELECT_ANATOMY = {\n  \"component\": \"select\",\n  \"description\": \"APG select-only combobox on the popover shell: [popover=auto] top layer + light dismiss, CSS anchor positioning, role=combobox/listbox/option. DOM focus stays on the trigger; aria-activedescendant tracks the highlighted option. Typeahead via printable characters. Controlled value + open. `multiple` switches the value axis to arrays: aria-multiselectable listbox, options toggle without closing it, and the hidden control becomes a <select multiple> that submits one repeated name=value pair per selection. Participates in forms through a hidden native <select> (name/form/required/disabled/readOnly + reset). No editable text field (select-only).\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-labelledby\",\n          \"aria-multiselectable\"\n        ],\n        \"role\": \"listbox\"\n      },\n      \"class\": \"hui-select-content\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\",\n          \"inset\",\n          \"margin\",\n          \"position\",\n          \"position-area\",\n          \"position-try-fallbacks\",\n          \"transition-behavior\",\n          \"transition-property\",\n          \"translate\"\n        ]\n      },\n      \"dataPart\": \"select-content\",\n      \"description\": \"The [popover] listbox panel; anchored to the trigger; owns options. Named by Select.Label or a consumer aria-labelledby — never by the trigger, whose name is the selected value. aria-multiselectable is present only when the Root is multiple.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"align\",\n        \"alignOffset\",\n        \"aria-labelledby\",\n        \"className\",\n        \"onBeforeToggle\",\n        \"onToggle\",\n        \"ref\",\n        \"render\",\n        \"side\",\n        \"sideOffset\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":popover-open\"\n      ],\n      \"states\": {\n        \"data-align\": [\n          \"center\",\n          \"end\",\n          \"start\"\n        ],\n        \"data-side\": [\n          \"bottom\",\n          \"left\",\n          \"right\",\n          \"top\"\n        ]\n      }\n    },\n    \"group\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-labelledby\"\n        ],\n        \"role\": \"group\"\n      },\n      \"class\": \"hui-select-group\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\"\n        ]\n      },\n      \"dataPart\": \"select-group\",\n      \"description\": \"role=group inside the listbox (ARIA 1.2 allows only option and group as listbox children). Named by Select.GroupLabel.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"group-label\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-group-label\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-group-label\",\n      \"description\": \"Names the enclosing Select.Group via aria-labelledby. Not an option; never navigable.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"item\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-disabled\",\n          \"aria-selected\"\n        ],\n        \"role\": \"option\"\n      },\n      \"class\": \"hui-select-item\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-item\",\n      \"description\": \"role=option; data-highlighted tracks aria-activedescendant; data-highlight-origin distinguishes keyboard virtual focus from pointer or seeded highlights; data-selected mirrors the committed value.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"label\",\n        \"onClick\",\n        \"onMouseEnter\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"value\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-highlighted\": [\n          \"true\"\n        ],\n        \"data-highlight-origin\": [\n          \"keyboard\",\n          \"pointer\",\n          \"seed\"\n        ],\n        \"data-selected\": [\n          \"true\"\n        ]\n      }\n    },\n    \"item-text\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-item-text\",\n      \"css\": {\n        \"structural\": [\n          \"display\"\n        ]\n      },\n      \"dataPart\": \"select-item-text\",\n      \"description\": \"The option's authoritative text. Registers the label the trigger and typeahead read, so options with icons or badges still resolve a name.\",\n      \"element\": \"span\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"label\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-label\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-label\",\n      \"description\": \"Real <label for> pointing at the combobox button; its id names both the combobox and the listbox.\",\n      \"element\": \"label\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ]\n      }\n    },\n    \"separator\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-hidden\"\n        ],\n        \"role\": \"presentation\"\n      },\n      \"class\": \"hui-select-separator\",\n      \"css\": {\n        \"structural\": [\n          \"flex\"\n        ]\n      },\n      \"dataPart\": \"select-separator\",\n      \"description\": \"Decorative rule between groups. role=presentation + aria-hidden, because a listbox may only own option and group children.\",\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-activedescendant\",\n          \"aria-controls\",\n          \"aria-expanded\",\n          \"aria-haspopup\",\n          \"aria-labelledby\",\n          \"aria-readonly\",\n          \"aria-required\"\n        ],\n        \"role\": \"combobox\"\n      },\n      \"class\": \"hui-select-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-trigger\",\n      \"description\": \"role=combobox button; popoverTarget invoker; DOM focus stays here while open; aria-activedescendant points at the highlighted option. Select-only, so no aria-autocomplete: typeahead highlights an existing option and never filters the list. Root disabled/readOnly project here as native disabled and aria-readonly.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"aria-labelledby\",\n        \"className\",\n        \"onKeyDown\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-readonly\": [\n          \"true\"\n        ]\n      }\n    },\n    \"value\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-value\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-value\",\n      \"description\": \"Displays the selected option label, or the placeholder when empty. When the Root is multiple, joins every selected label with `separator` (default \\\", \\\"); use render + state.values for any other shape.\",\n      \"element\": \"span\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"placeholder\",\n        \"ref\",\n        \"render\",\n        \"separator\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-placeholder\": [\n          \"true\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultOpen\",\n    \"defaultValue\",\n    \"disabled\",\n    \"form\",\n    \"inputRef\",\n    \"multiple\",\n    \"name\",\n    \"onOpenChange\",\n    \"onValueChange\",\n    \"open\",\n    \"readOnly\",\n    \"required\",\n    \"value\"\n  ],\n  \"tokens\": [\n    \"--hui-border-style\",\n    \"--hui-border-width\",\n    \"--hui-color-bg-elevated\",\n    \"--hui-color-bg-raised\",\n    \"--hui-color-border-default\",\n    \"--hui-color-fg-default\",\n    \"--hui-color-fg-muted\",\n    \"--hui-color-focus-ring\",\n    \"--hui-color-interactive-ghost-bg-hover\",\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-full\",\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  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/select/select.anatomy.generated.ts",
      "target": "components/ui/heidi/select/select.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"select\",\n  \"description\": \"APG select-only combobox on the popover shell: [popover=auto] top layer + light dismiss, CSS anchor positioning, role=combobox/listbox/option. DOM focus stays on the trigger; aria-activedescendant tracks the highlighted option. Typeahead via printable characters. Controlled value + open. `multiple` switches the value axis to arrays: aria-multiselectable listbox, options toggle without closing it, and the hidden control becomes a <select multiple> that submits one repeated name=value pair per selection. Participates in forms through a hidden native <select> (name/form/required/disabled/readOnly + reset). No editable text field (select-only).\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"content\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-labelledby\",\n          \"aria-multiselectable\"\n        ],\n        \"role\": \"listbox\"\n      },\n      \"class\": \"hui-select-content\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\",\n          \"inset\",\n          \"margin\",\n          \"position\",\n          \"position-area\",\n          \"position-try-fallbacks\",\n          \"transition-behavior\",\n          \"transition-property\",\n          \"translate\"\n        ]\n      },\n      \"dataPart\": \"select-content\",\n      \"description\": \"The [popover] listbox panel; anchored to the trigger; owns options. Named by Select.Label or a consumer aria-labelledby — never by the trigger, whose name is the selected value. aria-multiselectable is present only when the Root is multiple.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"align\",\n        \"alignOffset\",\n        \"aria-labelledby\",\n        \"className\",\n        \"onBeforeToggle\",\n        \"onToggle\",\n        \"ref\",\n        \"render\",\n        \"side\",\n        \"sideOffset\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":popover-open\"\n      ],\n      \"states\": {\n        \"data-align\": [\n          \"center\",\n          \"end\",\n          \"start\"\n        ],\n        \"data-side\": [\n          \"bottom\",\n          \"left\",\n          \"right\",\n          \"top\"\n        ]\n      }\n    },\n    \"group\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-labelledby\"\n        ],\n        \"role\": \"group\"\n      },\n      \"class\": \"hui-select-group\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\"\n        ]\n      },\n      \"dataPart\": \"select-group\",\n      \"description\": \"role=group inside the listbox (ARIA 1.2 allows only option and group as listbox children). Named by Select.GroupLabel.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"group-label\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-group-label\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-group-label\",\n      \"description\": \"Names the enclosing Select.Group via aria-labelledby. Not an option; never navigable.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"item\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-disabled\",\n          \"aria-selected\"\n        ],\n        \"role\": \"option\"\n      },\n      \"class\": \"hui-select-item\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-item\",\n      \"description\": \"role=option; data-highlighted tracks aria-activedescendant; data-highlight-origin distinguishes keyboard virtual focus from pointer or seeded highlights; data-selected mirrors the committed value.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"label\",\n        \"onClick\",\n        \"onMouseEnter\",\n        \"ref\",\n        \"render\",\n        \"style\",\n        \"value\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-highlighted\": [\n          \"true\"\n        ],\n        \"data-highlight-origin\": [\n          \"keyboard\",\n          \"pointer\",\n          \"seed\"\n        ],\n        \"data-selected\": [\n          \"true\"\n        ]\n      }\n    },\n    \"item-text\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-item-text\",\n      \"css\": {\n        \"structural\": [\n          \"display\"\n        ]\n      },\n      \"dataPart\": \"select-item-text\",\n      \"description\": \"The option's authoritative text. Registers the label the trigger and typeahead read, so options with icons or badges still resolve a name.\",\n      \"element\": \"span\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"label\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-label\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-label\",\n      \"description\": \"Real <label for> pointing at the combobox button; its id names both the combobox and the listbox.\",\n      \"element\": \"label\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ]\n      }\n    },\n    \"separator\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-hidden\"\n        ],\n        \"role\": \"presentation\"\n      },\n      \"class\": \"hui-select-separator\",\n      \"css\": {\n        \"structural\": [\n          \"flex\"\n        ]\n      },\n      \"dataPart\": \"select-separator\",\n      \"description\": \"Decorative rule between groups. role=presentation + aria-hidden, because a listbox may only own option and group children.\",\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-activedescendant\",\n          \"aria-controls\",\n          \"aria-expanded\",\n          \"aria-haspopup\",\n          \"aria-labelledby\",\n          \"aria-readonly\",\n          \"aria-required\"\n        ],\n        \"role\": \"combobox\"\n      },\n      \"class\": \"hui-select-trigger\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-trigger\",\n      \"description\": \"role=combobox button; popoverTarget invoker; DOM focus stays here while open; aria-activedescendant points at the highlighted option. Select-only, so no aria-autocomplete: typeahead highlights an existing option and never filters the list. Root disabled/readOnly project here as native disabled and aria-readonly.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"aria-labelledby\",\n        \"className\",\n        \"onKeyDown\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ],\n        \"data-readonly\": [\n          \"true\"\n        ]\n      }\n    },\n    \"value\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-select-value\",\n      \"css\": {\n        \"structural\": []\n      },\n      \"dataPart\": \"select-value\",\n      \"description\": \"Displays the selected option label, or the placeholder when empty. When the Root is multiple, joins every selected label with `separator` (default \\\", \\\"); use render + state.values for any other shape.\",\n      \"element\": \"span\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"placeholder\",\n        \"ref\",\n        \"render\",\n        \"separator\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-placeholder\": [\n          \"true\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"defaultOpen\",\n    \"defaultValue\",\n    \"disabled\",\n    \"form\",\n    \"inputRef\",\n    \"multiple\",\n    \"name\",\n    \"onOpenChange\",\n    \"onValueChange\",\n    \"open\",\n    \"readOnly\",\n    \"required\",\n    \"value\"\n  ]\n}\n",
      "path": "packages/heidi-ui/src/select/select.anatomy.json",
      "target": "components/ui/heidi/select/select.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 * 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": "select",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Select",
  "type": "registry:ui"
}
