{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "APG toolbar. role=toolbar with required label + aria-orientation. Roving tabindex among Toolbar.Button children via Arrow/Home/End. Toolbar.Button forwards native button props so Toggle.Root can render it as one composed host with both roving focus and pressed semantics. Composition via renderHeidiElement.",
  "files": [
    {
      "content": "\"use client\";\n\n/**\n * heidi-ui Toolbar — APG toolbar\n * (docs/HEIDI-UI-HEADLESS.md § P4 / Slice G). role=toolbar with required\n * label + aria-orientation. Roving tabindex among Toolbar.Button children\n * via Arrow/Home/End. Button `disabled` → native disabled + data-disabled;\n * nav skips disabled buttons.\n *\n * RSC rule: named exports from Server Components; Toolbar.X is client sugar.\n */\n\nimport {\n  type ComponentPropsWithoutRef,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type FocusEvent,\n  type KeyboardEvent,\n  type ReactNode,\n  type Ref\n} from \"react\";\nimport {\n  collectRovingItems,\n  NAVIGATION_KEYS,\n  resolveRovingIndex\n} from \"../_internal/roving-focus\";\nimport { isDisabledElement, nativeDisabledAttrs } from \"../_internal/disabled\";\nimport {\n  composeHeidiEventHandlers,\n  mergeHeidiRefs,\n  renderHeidiElement,\n  type HeidiIntrinsicHostProps\n} from \"../_internal/render-element\";\nimport { useHeidiLayoutEffect } from \"../_internal/use-layout-effect\";\nimport { type ToolbarOrientation } from \"./toolbar.anatomy.generated\";\nimport { TOOLBAR_CLASSES } from \"./toolbar.classes.generated\";\n\nexport type { ToolbarOrientation };\n\ntype ToolbarEntry = { disabled: boolean; id: string };\n\ntype ToolbarContextValue = {\n  claimInitialTabStop: (id: string, disabled: boolean) => boolean;\n  orientation: ToolbarOrientation;\n  registerId: (id: string, disabled: boolean) => void;\n  setTabbableId: (id: string) => void;\n  tabbableId: string | null;\n  unregisterId: (id: string) => void;\n};\n\nconst ToolbarContext = createContext<ToolbarContextValue | null>(null);\n\nfunction useToolbarContext(part: string): ToolbarContextValue {\n  const context = useContext(ToolbarContext);\n  if (!context) {\n    throw new Error(`Toolbar.${part} must be rendered inside Toolbar.Root.`);\n  }\n  return context;\n}\n\nfunction toolbarButtons(container: HTMLElement): HTMLButtonElement[] {\n  return collectRovingItems<HTMLButtonElement>(\n    container,\n    '[data-hui-part=\"toolbar-button\"]',\n    '[data-hui-part=\"toolbar-root\"]',\n    isDisabledElement\n  );\n}\n\n/** State handed to a `ToolbarRoot` className/style/render callback. */\nexport type ToolbarRootState = {\n  orientation: ToolbarOrientation;\n};\n\ntype ToolbarRootNativeProps = Omit<\n  ComponentPropsWithoutRef<\"div\">,\n  | \"aria-label\"\n  | \"aria-orientation\"\n  | \"children\"\n  | \"className\"\n  | \"onKeyDown\"\n  | \"ref\"\n  | \"role\"\n  | \"style\"\n>;\n\nexport type ToolbarRootProps = HeidiIntrinsicHostProps<ToolbarRootState, \"div\"> &\n  ToolbarRootNativeProps & {\n    children?: ReactNode;\n    /** Accessible name for the toolbar (aria-label). Required. */\n    label: string;\n    onKeyDown?: ComponentPropsWithoutRef<\"div\">[\"onKeyDown\"];\n    orientation?: ToolbarOrientation;\n    ref?: Ref<HTMLDivElement>;\n  };\n\nexport function ToolbarRoot({\n  children,\n  className,\n  label,\n  onKeyDown: onKeyDownProp,\n  orientation = \"horizontal\",\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToolbarRootProps) {\n  const [order, setOrder] = useState<ToolbarEntry[]>([]);\n  const [rovingId, setRovingId] = useState<string | null>(null);\n  const rootRef = useRef<HTMLDivElement | null>(null);\n  const enabledOrder = order.filter((entry) => !entry.disabled);\n  const tabbableId =\n    enabledOrder.find((entry) => entry.id === rovingId)?.id ??\n    enabledOrder[0]?.id ??\n    null;\n  const setTabbableId = useCallback((id: string) => {\n    setRovingId(id);\n  }, []);\n\n  const registerId = useCallback((id: string, disabled: boolean) => {\n    setOrder((prev) => {\n      const existing = prev.find((entry) => entry.id === id);\n      if (existing) {\n        if (existing.disabled === disabled) {\n          return prev;\n        }\n        return prev.map((entry) => (entry.id === id ? { disabled, id } : entry));\n      }\n      return [...prev, { disabled, id }];\n    });\n  }, []);\n\n  const unregisterId = useCallback((id: string) => {\n    setOrder((prev) => prev.filter((entry) => entry.id !== id));\n  }, []);\n\n  const reconcileDomOrder = useCallback(() => {\n    const root = rootRef.current;\n    if (!root) {\n      return;\n    }\n    const next = Array.from(\n      root.querySelectorAll<HTMLButtonElement>(\"[data-hui-toolbar-button-id]\")\n    )\n      .filter((element) => element.closest('[data-hui-part=\"toolbar-root\"]') === root)\n      .flatMap((element) => {\n        const id = element.dataset.huiToolbarButtonId;\n        return id ? [{ disabled: isDisabledElement(element), id }] : [];\n      });\n    setOrder((previous) =>\n      previous.length === next.length &&\n      previous.every(\n        (entry, index) =>\n          entry.disabled === next[index]?.disabled && entry.id === next[index]?.id\n      )\n        ? previous\n        : next\n    );\n  }, []);\n\n  // ponytail: keyed controls can move without unmounting, so registration order\n  // alone is not current DOM order. The reconciliation must follow a new child\n  // tree, not the Root's own `order` update: running after every internal\n  // commit made a composed toolbar's mount-time disabled transition recursively\n  // schedule layout updates until React tripped its maximum-depth boundary.\n  // Rejected: removing DOM reconciliation, which makes keyed reorder retain\n  // historical mount order instead of the order a keyboard user can see.\n  useHeidiLayoutEffect(reconcileDomOrder, [children, reconcileDomOrder]);\n\n  const onKeyDown = composeHeidiEventHandlers(\n    onKeyDownProp,\n    (event: KeyboardEvent<HTMLDivElement>) => {\n    if (!NAVIGATION_KEYS.includes(event.key)) {\n      return;\n    }\n    // ponytail: the APG toolbar example holds \"toggle buttons, radio buttons,\n    // a menu button, a spin button, a checkbox, and a link\" — this listener is\n    // on the CONTAINER, so every one of those lost its own arrow keys. The\n    // theft is unconditional, not incidental: resolveRovingIndex answers Home\n    // with 0 and End with the last index before it looks at what is focused,\n    // and an unregistered element indexes to -1, which an arrow resolves to\n    // item 0. A text field inside a toolbar had its caret movement\n    // preventDefault()ed away. `closest` on the item, then on the root, is\n    // also what makes a nested toolbar's keys stop at its own root instead of\n    // being handled twice on the way up.\n    // Rejected: moving the listener onto Toolbar.Button, which is where Radix\n    // puts it (RovingFocusGroup.Item). Same predicate, but it re-homes the\n    // consumer's Root `onKeyDown` prop — a public API change in a fix slice.\n    const item =\n      event.target instanceof Element\n        ? event.target.closest<HTMLElement>('[data-hui-part=\"toolbar-button\"]')\n        : null;\n    if (\n      !item ||\n      item.closest('[data-hui-part=\"toolbar-root\"]') !== event.currentTarget\n    ) {\n      return;\n    }\n    const items = toolbarButtons(event.currentTarget);\n    if (items.length === 0) {\n      return;\n    }\n    const next = resolveRovingIndex(\n      items,\n      document.activeElement,\n      event.key,\n      event.currentTarget,\n      { crossAxis: true, orientation }\n    );\n    if (next === null) {\n      return;\n    }\n    event.preventDefault();\n    const nextItem = items[next];\n    if (nextItem) {\n      const nextId = nextItem.dataset.huiToolbarButtonId;\n      if (nextId) {\n        setTabbableId(nextId);\n      }\n      nextItem.focus();\n    }\n    }\n  );\n\n  // ponytail: Strict Mode can render one Button twice against this context;\n  // identity makes that render-local SSR claim replay-safe.\n  let initialTabStopId: string | null = null;\n  const context: ToolbarContextValue = {\n    claimInitialTabStop: (buttonId, buttonDisabled) => {\n      if (\n        buttonDisabled ||\n        (initialTabStopId !== null && initialTabStopId !== buttonId)\n      ) {\n        return false;\n      }\n      initialTabStopId = buttonId;\n      return true;\n    },\n    orientation,\n    registerId,\n    setTabbableId,\n    tabbableId,\n    unregisterId\n  };\n\n  return (\n    <ToolbarContext value={context}>\n      {renderHeidiElement({\n        className: TOOLBAR_CLASSES.root,\n        dataPart: \"toolbar-root\",\n        element: \"div\",\n        props: {\n          ...nativeProps,\n          \"aria-label\": label,\n          \"aria-orientation\": orientation,\n          children,\n          \"data-orientation\": orientation,\n          onKeyDown,\n          ref: mergeHeidiRefs(rootRef, ref),\n          role: \"toolbar\"\n        },\n        renderProps: { className, render, style },\n        state: { orientation }\n      })}\n    </ToolbarContext>\n  );\n}\n\n/** State handed to a `ToolbarButton` className/style/render callback. */\nexport type ToolbarButtonState = { disabled: boolean };\n\ntype ToolbarButtonNativeProps = Omit<\n  ComponentPropsWithoutRef<\"button\">,\n  | \"children\"\n  | \"className\"\n  | \"disabled\"\n  | \"ref\"\n  | \"style\"\n  | \"tabIndex\"\n  | \"type\"\n>;\n\nexport type ToolbarButtonProps = HeidiIntrinsicHostProps<\n  ToolbarButtonState,\n  \"button\"\n> &\n  ToolbarButtonNativeProps & {\n    children?: ReactNode;\n    disabled?: boolean;\n    ref?: Ref<HTMLButtonElement>;\n  };\n\nexport function ToolbarButton({\n  children,\n  className,\n  disabled = false,\n  onFocus,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: ToolbarButtonProps) {\n  const {\n    claimInitialTabStop,\n    registerId,\n    setTabbableId,\n    tabbableId,\n    unregisterId\n  } =\n    useToolbarContext(\"Button\");\n  const id = useId();\n\n  useEffect(() => {\n    registerId(id, disabled);\n  }, [disabled, id, registerId]);\n\n  useEffect(\n    () => () => unregisterId(id),\n    [id, unregisterId]\n  );\n\n  const initialTabStop =\n    tabbableId === null && claimInitialTabStop(id, disabled);\n  const handleFocus = composeHeidiEventHandlers(\n    onFocus,\n    (_event: FocusEvent<HTMLButtonElement>) => {\n      if (!disabled) {\n        setTabbableId(id);\n      }\n    }\n  );\n\n  return renderHeidiElement({\n    className: TOOLBAR_CLASSES.button,\n    dataPart: \"toolbar-button\",\n    element: \"button\",\n    props: {\n      ...nativeProps,\n      children,\n      \"data-hui-toolbar-button-id\": id,\n      onFocus: handleFocus,\n      ref,\n      tabIndex: !disabled && (tabbableId === id || initialTabStop) ? 0 : -1,\n      type: \"button\",\n      ...nativeDisabledAttrs(disabled)\n    },\n    renderProps: { className, render, style },\n    state: { disabled }\n  });\n}\n\nexport const Toolbar = {\n  Button: ToolbarButton,\n  Root: ToolbarRoot\n} as const;\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.tsx",
      "target": "components/ui/heidi/toolbar/toolbar.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui toolbar — STRUCTURAL CSS only. No --hui-*.\n */\n\n@layer heidi-ui-base {\n  .hui-toolbar-root {\n    box-sizing: border-box;\n    display: inline-flex;\n    gap: 0;\n    max-inline-size: 100%;\n    min-inline-size: 0;\n  }\n\n  .hui-toolbar-root[data-orientation=\"horizontal\"] {\n    flex-direction: row;\n    overflow-x: auto;\n    overscroll-behavior-x: contain;\n  }\n\n  .hui-toolbar-root[data-orientation=\"vertical\"] {\n    flex-direction: column;\n  }\n\n  .hui-toolbar-button {\n    display: inline-flex;\n    flex: 0 0 auto;\n  }\n}\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.base.css",
      "target": "components/ui/heidi/toolbar/toolbar.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui toolbar — VISUAL theme (opt-in). Consumes --hui-*.\n */\n\n@layer heidi-ui {\n  .hui-toolbar-root {\n    background: var(--hui-color-bg-raised);\n    border: var(--hui-border-width) var(--hui-border-style) var(--hui-color-border-default);\n    border-radius: var(--hui-radius-lg);\n    gap: var(--hui-space-1);\n    padding: var(--hui-space-1);\n  }\n\n  .hui-toolbar-button {\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    font: inherit;\n    justify-content: center;\n    padding: var(--hui-space-1-5) var(--hui-space-3);\n  }\n\n  /* ponytail: both hover rules repaint the ground, so they must also pin the\n     foreground. A Toolbar button is routinely composed with another primitive\n     on the SAME element (the README recipe merges `hui-toolbar-button` onto a\n     pressed `hui-toggle-root`), and a ground reset with no matching foreground\n     reset left brand-white text on a near-transparent tint. `fg-default` is\n     already this button's rest colour, so this is a no-op for a plain Toolbar\n     button and only re-pairs the composed case. */\n  .hui-toolbar-button:hover {\n    background: var(--hui-color-interactive-ghost-bg-hover);\n    color: var(--hui-color-fg-default);\n  }\n\n  .hui-toolbar-button: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-toolbar-button:disabled,\n  .hui-toolbar-button[data-disabled=\"true\"] {\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  .hui-toolbar-button:disabled:hover,\n  .hui-toolbar-button[data-disabled=\"true\"]:hover {\n    background: var(--hui-color-bg-elevated);\n    color: var(--hui-color-fg-default);\n  }\n\n  @media (forced-colors: active) {\n    .hui-toolbar-root,\n    .hui-toolbar-button {\n      border-color: CanvasText;\n    }\n\n    .hui-toolbar-button:focus-visible {\n      outline-color: Highlight;\n    }\n\n    .hui-toolbar-button:disabled,\n    .hui-toolbar-button:disabled:hover,\n    .hui-toolbar-button[data-disabled=\"true\"],\n    .hui-toolbar-button[data-disabled=\"true\"]:hover {\n      border-color: GrayText;\n      color: GrayText;\n      opacity: 1;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.theme.css",
      "target": "components/ui/heidi/toolbar/toolbar.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui toolbar — aggregator.\n */\n\n@import \"./toolbar.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./toolbar.theme.css\";\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.css",
      "target": "components/ui/heidi/toolbar/toolbar.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/toolbar/toolbar.base.css + packages/heidi-ui/src/toolbar/toolbar.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const TOOLBAR_CLASSES = {\n  button: \"hui-toolbar-button\",\n  root: \"hui-toolbar-root\",\n} as const;\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.classes.generated.ts",
      "target": "components/ui/heidi/toolbar/toolbar.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from toolbar.anatomy.json + toolbar.base.css + toolbar.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\nexport type ToolbarDisabled = \"true\";\nexport type ToolbarOrientation = \"horizontal\" | \"vertical\";\n\nexport const TOOLBAR_ANATOMY = {\n  \"component\": \"toolbar\",\n  \"description\": \"APG toolbar. role=toolbar with required label + aria-orientation. Roving tabindex among Toolbar.Button children via Arrow/Home/End. Toolbar.Button forwards native button props so Toggle.Root can render it as one composed host with both roving focus and pressed semantics. Composition via renderHeidiElement.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"button\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-toolbar-button\",\n      \"css\": {\n        \"structural\": [\n          \"display\"\n        ]\n      },\n      \"dataPart\": \"toolbar-button\",\n      \"description\": \"Focusable toolbar control with full native button props; participates in roving tabindex. Native button type=button. Compose as Toggle.Root's render host for one button with both toolbar and pressed semantics.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"onFocus\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\",\n        \":hover\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ]\n      }\n    },\n    \"root\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-label\",\n          \"aria-orientation\"\n        ],\n        \"role\": \"toolbar\"\n      },\n      \"class\": \"hui-toolbar-root\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\",\n          \"gap\"\n        ]\n      },\n      \"dataPart\": \"toolbar-root\",\n      \"description\": \"role=toolbar container; owns arrow-key focus among buttons.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"label\",\n        \"onKeyDown\",\n        \"orientation\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-orientation\": [\n          \"horizontal\",\n          \"vertical\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"label\",\n    \"orientation\"\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-focus-ring\",\n    \"--hui-color-interactive-ghost-bg-hover\",\n    \"--hui-focus-ring-offset\",\n    \"--hui-focus-ring-width\",\n    \"--hui-radius-lg\",\n    \"--hui-radius-md\",\n    \"--hui-space-1\",\n    \"--hui-space-1-5\",\n    \"--hui-space-3\"\n  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.anatomy.generated.ts",
      "target": "components/ui/heidi/toolbar/toolbar.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"toolbar\",\n  \"description\": \"APG toolbar. role=toolbar with required label + aria-orientation. Roving tabindex among Toolbar.Button children via Arrow/Home/End. Toolbar.Button forwards native button props so Toggle.Root can render it as one composed host with both roving focus and pressed semantics. Composition via renderHeidiElement.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"button\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-toolbar-button\",\n      \"css\": {\n        \"structural\": [\n          \"display\"\n        ]\n      },\n      \"dataPart\": \"toolbar-button\",\n      \"description\": \"Focusable toolbar control with full native button props; participates in roving tabindex. Native button type=button. Compose as Toggle.Root's render host for one button with both toolbar and pressed semantics.\",\n      \"element\": \"button\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"disabled\",\n        \"onFocus\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":disabled\",\n        \":focus-visible\",\n        \":hover\"\n      ],\n      \"states\": {\n        \"data-disabled\": [\n          \"true\"\n        ]\n      }\n    },\n    \"root\": {\n      \"aria\": {\n        \"owns\": [\n          \"aria-label\",\n          \"aria-orientation\"\n        ],\n        \"role\": \"toolbar\"\n      },\n      \"class\": \"hui-toolbar-root\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\",\n          \"gap\"\n        ]\n      },\n      \"dataPart\": \"toolbar-root\",\n      \"description\": \"role=toolbar container; owns arrow-key focus among buttons.\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"label\",\n        \"onKeyDown\",\n        \"orientation\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {\n        \"data-orientation\": [\n          \"horizontal\",\n          \"vertical\"\n        ]\n      }\n    }\n  },\n  \"rootProps\": [\n    \"label\",\n    \"orientation\"\n  ]\n}\n",
      "path": "packages/heidi-ui/src/toolbar/toolbar.anatomy.json",
      "target": "components/ui/heidi/toolbar/toolbar.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 host rendering for heidi-ui parts — Base-UI-shaped composition\n * (docs/HEIDI-UI-HEADLESS.md § 4 / HEIDI-UI.md § 4.5):\n *   - className / style as value or (state) => value\n *   - render as ReactElement or (props, state) => ReactElement\n *   - ref forwarding\n *   - data-hui-part always set (stable unstyled hook)\n *\n * Structural inline styles (e.g. anchorName) are merged last so a consumer\n * style override cannot drop platform wiring.\n */\n\nimport {\n  cloneElement,\n  createElement,\n  isValidElement,\n  type CSSProperties,\n  type ComponentPropsWithRef,\n  type HTMLAttributes,\n  type ReactElement,\n  type ReactNode,\n  type Ref,\n  type RefCallback\n} from \"react\";\n\nexport type HeidiClassName<State> = string | ((state: State) => string | undefined) | undefined;\n\nexport type HeidiStyle<State> =\n  | CSSProperties\n  | ((state: State) => CSSProperties | undefined)\n  | undefined;\n\nexport type HeidiRenderFn<State, Props> = (\n  props: Props,\n  state: State\n) => ReactElement;\n\nexport type HeidiRender<State, Props> =\n  | ReactElement\n  | HeidiRenderFn<State, Props>\n  | undefined;\n\ntype DefaultRenderProps = HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> };\n\nexport type HeidiHostProps<\n  State,\n  RenderProps extends object = DefaultRenderProps\n> = {\n  className?: HeidiClassName<State>;\n  render?: HeidiRender<State, RenderProps>;\n  style?: HeidiStyle<State>;\n};\n\nexport type HeidiIntrinsicHostProps<\n  State,\n  Tag extends keyof HTMLElementTagNameMap\n> = HeidiHostProps<State, ComponentPropsWithRef<Tag>>;\n\nfunction resolveClassName<State>(\n  className: HeidiClassName<State>,\n  state: State\n): string | undefined {\n  return typeof className === \"function\" ? className(state) : className;\n}\n\nfunction resolveStyle<State>(style: HeidiStyle<State>, state: State): CSSProperties | undefined {\n  return typeof style === \"function\" ? style(state) : style;\n}\n\nfunction mergeClassNames(...parts: Array<string | undefined>): string | undefined {\n  const merged = parts.filter(Boolean).join(\" \");\n  return merged.length > 0 ? merged : undefined;\n}\n\n/**\n * Compose a consumer event with library behavior. Consumers run first and may\n * cancel the component behavior with `event.preventDefault()`. Heidi keeps\n * this native-event convention intentionally: it avoids a second branded event\n * API while still making async/controlled actions vetoable.\n */\nexport function composeHeidiEventHandlers<Event extends { defaultPrevented: boolean }>(\n  consumer: ((event: Event) => void) | undefined,\n  library: (event: Event) => void\n): (event: Event) => void {\n  return (event) => {\n    consumer?.(event);\n    if (!event.defaultPrevented) {\n      library(event);\n    }\n  };\n}\n\n/** React 19-safe ref fan-out, including callback-ref cleanup functions. */\nexport function mergeHeidiRefs<Element>(\n  ...inputRefs: Array<Ref<Element> | undefined>\n): Ref<Element> | undefined {\n  const refs = Array.from(\n    new Set(\n      inputRefs.filter(\n        (ref): ref is Exclude<Ref<Element>, null> => ref != null\n      )\n    )\n  );\n  if (refs.length === 0) {\n    return undefined;\n  }\n  if (refs.length === 1) {\n    return refs[0];\n  }\n\n  let cache = mergedRefCache;\n  for (const ref of refs) {\n    const key = ref as object;\n    let child = cache.children.get(key);\n    if (!child) {\n      child = { children: new WeakMap() };\n      cache.children.set(key, child);\n    }\n    cache = child;\n  }\n  if (cache.callback) {\n    return cache.callback as RefCallback<Element>;\n  }\n\n  const callback: RefCallback<Element> = (node) => {\n    const cleanups: Array<() => void> = [];\n    for (const ref of refs) {\n      if (typeof ref === \"function\") {\n        const cleanup = ref(node);\n        if (node !== null) {\n          cleanups.push(typeof cleanup === \"function\" ? cleanup : () => ref(null));\n        }\n      } else if (ref) {\n        ref.current = node;\n        if (node !== null) {\n          cleanups.push(() => {\n            ref.current = null;\n          });\n        }\n      }\n    }\n    return cleanups.length > 0\n      ? () => {\n          for (const cleanup of cleanups) {\n            cleanup();\n          }\n        }\n      : undefined;\n  };\n  cache.callback = callback as RefCallback<unknown>;\n  return callback;\n}\n\ntype MergedRefCache = {\n  callback?: RefCallback<unknown>;\n  children: WeakMap<object, MergedRefCache>;\n};\n\nconst mergedRefCache: MergedRefCache = { children: new WeakMap() };\n\ntype UnknownHandler = (...args: never[]) => unknown;\n\nfunction isEventHandler(key: string, value: unknown): value is UnknownHandler {\n  return /^on[A-Z]/.test(key) && typeof value === \"function\";\n}\n\nfunction defaultPrevented(args: unknown[]): boolean {\n  const event = args[0];\n  return (\n    typeof event === \"object\" &&\n    event !== null &&\n    \"defaultPrevented\" in event &&\n    event.defaultPrevented === true\n  );\n}\n\nfunction composeUnknownHandlers(\n  consumer: UnknownHandler,\n  library: UnknownHandler\n): UnknownHandler {\n  if (consumer === library) {\n    return library;\n  }\n  return ((...args: unknown[]) => {\n    (consumer as (...handlerArgs: unknown[]) => unknown)(...args);\n    if (!defaultPrevented(args)) {\n      (library as (...handlerArgs: unknown[]) => unknown)(...args);\n    }\n  }) as UnknownHandler;\n}\n\n/** Library-owned host props — permissive so button `type`, `data-*`, `popover`, etc. type-check. */\nexport type HeidiElementProps<Tag extends keyof HTMLElementTagNameMap> = Record<\n  string,\n  unknown\n> & {\n  children?: ReactNode;\n  className?: string;\n  ref?: Ref<HTMLElementTagNameMap[Tag]>;\n  style?: CSSProperties;\n};\n\ntype RenderElementParams<\n  State,\n  Tag extends keyof HTMLElementTagNameMap,\n  RenderProps extends object = DefaultRenderProps\n> = {\n  /** Default class from the generated hui-* map (theme/base target). */\n  className?: string;\n  /** Stable machine hook — always emitted. */\n  dataPart: string;\n  /** Intrinsic tag when `render` is omitted. */\n  element: Tag;\n  /** Props the library owns (aria, ids, handlers, popover, …). */\n  props: HeidiElementProps<Tag>;\n  /** Optional consumer composition props. */\n  renderProps?: HeidiHostProps<State, RenderProps>;\n  /** Typed state passed to functional className/style/render. */\n  state: State;\n  /** Structural inline styles that must survive consumer style merges. */\n  structuralStyle?: CSSProperties;\n  /**\n   * Render-element handlers to remove instead of composing. This is reserved\n   * for states such as a focusable disabled composite item where the public\n   * contract requires press handlers to be completely inert.\n   */\n  suppressRenderedHandlers?: readonly string[];\n};\n\n/**\n * Render a heidi-ui host element with optional Base-shaped composition.\n */\nexport function renderHeidiElement<\n  State,\n  Tag extends keyof HTMLElementTagNameMap,\n  RenderProps extends object = DefaultRenderProps\n>(\n  params: RenderElementParams<State, Tag, RenderProps>\n): ReactElement {\n  const {\n    className,\n    dataPart,\n    element,\n    props,\n    renderProps,\n    state,\n    structuralStyle,\n    suppressRenderedHandlers\n  } = params;\n  const consumerClass = resolveClassName(renderProps?.className, state);\n  const consumerStyle = resolveStyle(renderProps?.style, state);\n  const mergedStyle: CSSProperties | undefined =\n    props.style || consumerStyle || structuralStyle\n      ? { ...props.style, ...consumerStyle, ...structuralStyle }\n      : undefined;\n\n  const outProps: HTMLAttributes<HTMLElement> & {\n    \"data-hui-part\": string;\n    ref?: Ref<HTMLElement>;\n  } = {\n    ...(props as HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> }),\n    className: mergeClassNames(consumerClass, className),\n    \"data-hui-part\": dataPart,\n    style: mergedStyle\n  };\n\n  const render = renderProps?.render;\n  if (typeof render === \"function\") {\n    // Post-merge the returned element as well. This deliberately makes the\n    // stable data hook, owned semantics, refs, and internal handlers survive\n    // even when a render function forgets to spread one of the supplied props.\n    return mergeRenderedElement(\n      render(outProps as unknown as RenderProps, state),\n      outProps,\n      dataPart,\n      structuralStyle,\n      suppressRenderedHandlers\n    );\n  }\n  if (isValidElement(render)) {\n    return mergeRenderedElement(\n      render,\n      outProps,\n      dataPart,\n      structuralStyle,\n      suppressRenderedHandlers\n    );\n  }\n\n  return createElement(element, outProps as never, props.children);\n}\n\ntype RenderedProps = Record<string, unknown> & {\n  children?: ReactNode;\n  className?: string;\n  ref?: Ref<HTMLElement>;\n  style?: CSSProperties;\n};\n\nfunction mergeRenderedElement(\n  element: ReactElement,\n  libraryProps: HTMLAttributes<HTMLElement> & {\n    \"data-hui-part\": string;\n    ref?: Ref<HTMLElement>;\n  },\n  dataPart: string,\n  structuralStyle: CSSProperties | undefined,\n  suppressRenderedHandlers: readonly string[] | undefined\n): ReactElement {\n  const rendered = element as ReactElement<RenderedProps>;\n  const renderedProps = rendered.props;\n  const renderedClass =\n    renderedProps.className === libraryProps.className\n      ? undefined\n      : renderedProps.className;\n  const merged: RenderedProps = {\n    ...renderedProps,\n    ...libraryProps,\n    className: mergeClassNames(renderedClass, libraryProps.className),\n    \"data-hui-part\": dataPart,\n    ref: mergeHeidiRefs(libraryProps.ref, renderedProps.ref),\n    style: {\n      ...libraryProps.style,\n      ...renderedProps.style,\n      ...structuralStyle\n    }\n  };\n\n  if (Object.prototype.hasOwnProperty.call(renderedProps, \"children\")) {\n    merged.children = renderedProps.children;\n  }\n\n  const suppressedHandlers = suppressRenderedHandlers\n    ? new Set(suppressRenderedHandlers)\n    : null;\n  for (const key of new Set([...Object.keys(renderedProps), ...Object.keys(libraryProps)])) {\n    const consumer = renderedProps[key];\n    const library = (libraryProps as unknown as Record<string, unknown>)[key];\n    if (suppressedHandlers?.has(key)) {\n      merged[key] = library;\n      continue;\n    }\n    if (isEventHandler(key, consumer) && isEventHandler(key, library)) {\n      merged[key] = composeUnknownHandlers(consumer, library);\n    }\n  }\n\n  return cloneElement(rendered, merged as never);\n}\n",
      "path": "packages/heidi-ui/src/_internal/render-element.ts",
      "target": "components/ui/heidi/_internal/render-element.ts",
      "type": "registry:ui"
    },
    {
      "content": "/**\n * Shared roving-tabindex arrow navigation (P7).\n *\n * Radio, Tabs, Toolbar and ToggleGroup each declared `NAVIGATION_KEYS`\n * byte-identically, each wrote the same enabled-descendants query with two\n * different selector strings, and each resolved arrow keys to a target index.\n *\n * ponytail: the audit listed the resolvers as four divergent implementations —\n * \"radio treats ArrowDown as forward regardless of orientation; toggle-group\n * accepts both axes when horizontal; tabs restricts to its axis\". Normalising\n * the spellings shows THREE of the four agree exactly. Radio's\n * `ArrowDown || (rtl ? ArrowLeft : ArrowRight)` and Toolbar's\n * `(horizontal ? horizontalForward : \"ArrowDown\") || (horizontal && \"ArrowDown\")\n * || (!horizontal && \"ArrowRight\")` accept the identical key set in both\n * orientations and both directions; they only look different. The single real\n * divergence is Tabs, which returns early on a cross-axis key. So the option\n * that matters is `crossAxis`, and it is false in exactly one place.\n *\n * That distinction is deliberate and worth keeping: a Toolbar is a\n * two-dimensional cluster where reaching for either axis is reasonable, while\n * a tablist has one axis and a cross-axis arrow should stay available to the\n * page (scrolling, or an outer roving container).\n *\n * `rtl` is read per event rather than per render because `direction` can change\n * without a React update, and it is gated on horizontal orientation: on a\n * vertical list the inline direction says nothing about which way is \"next\".\n */\n\nexport const NAVIGATION_KEYS = [\n  \"ArrowDown\",\n  \"ArrowLeft\",\n  \"ArrowRight\",\n  \"ArrowUp\",\n  \"End\",\n  \"Home\"\n];\n\n/**\n * Enabled items owned by THIS container — `closest()` re-anchors the match so\n * a nested roving container's items never join its ancestor's ring.\n */\nexport function collectRovingItems<TItem extends HTMLElement>(\n  container: HTMLElement,\n  itemSelector: string,\n  rootSelector: string,\n  isDisabled: (element: HTMLElement) => boolean\n): TItem[] {\n  return [...container.querySelectorAll<TItem>(itemSelector)].filter(\n    (element) =>\n      !isDisabled(element) && element.closest(rootSelector) === container\n  );\n}\n\nexport type RovingResolveOptions = {\n  /** Accept the perpendicular axis too. False only for Tabs. */\n  crossAxis: boolean;\n  orientation: \"horizontal\" | \"vertical\";\n};\n\n/**\n * The index this key should move focus to, or `null` when the key does not\n * apply — the caller must then return WITHOUT calling `preventDefault`, so an\n * unhandled arrow stays available to the page.\n */\nexport function resolveRovingIndex(\n  items: readonly HTMLElement[],\n  activeElement: Element | null,\n  key: string,\n  container: HTMLElement,\n  { crossAxis, orientation }: RovingResolveOptions\n): number | null {\n  if (items.length === 0) {\n    return null;\n  }\n  if (key === \"Home\") {\n    return 0;\n  }\n  if (key === \"End\") {\n    return items.length - 1;\n  }\n  const horizontal = orientation === \"horizontal\";\n  const rtl =\n    horizontal && getComputedStyle(container).direction === \"rtl\";\n  const inlineForward = rtl ? \"ArrowLeft\" : \"ArrowRight\";\n  const inlineBackward = rtl ? \"ArrowRight\" : \"ArrowLeft\";\n  const forwardKey = horizontal ? inlineForward : \"ArrowDown\";\n  const backwardKey = horizontal ? inlineBackward : \"ArrowUp\";\n  const crossForward = horizontal ? \"ArrowDown\" : inlineForward;\n  const crossBackward = horizontal ? \"ArrowUp\" : inlineBackward;\n\n  const current = items.indexOf(activeElement as HTMLElement);\n  if (key === forwardKey || (crossAxis && key === crossForward)) {\n    return current === -1 ? 0 : (current + 1) % items.length;\n  }\n  if (key === backwardKey || (crossAxis && key === crossBackward)) {\n    return (current <= 0 ? items.length : current) - 1;\n  }\n  return null;\n}\n",
      "path": "packages/heidi-ui/src/_internal/roving-focus.ts",
      "target": "components/ui/heidi/_internal/roving-focus.ts",
      "type": "registry:ui"
    },
    {
      "content": "import { useEffect, useLayoutEffect } from \"react\";\n\n/**\n * `useLayoutEffect` that does not warn on the server.\n *\n * ponytail: fifteen primitives imported `useLayoutEffect` straight from React.\n * Every one of them carries `\"use client\"`, which is easy to misread as\n * \"client-only\" — in the App Router it means \"hydrated on the client\", and the\n * component is still rendered to HTML on the server first. React logs\n * \"useLayoutEffect does nothing on the server\" for each one, so a consumer\n * doing SSR saw a wall of warnings the app that ships this library never saw,\n * because it renders these routes on the client path.\n *\n * The branch is evaluated ONCE at module scope, not per render, and it is\n * therefore stable across a component's lifetime — swapping which hook is\n * called between renders would violate the rules of hooks. `typeof document`\n * rather than `typeof window`: both work, but `document` is the thing the\n * effect actually needs, and it keeps the check honest in exotic runtimes that\n * define a partial `window`.\n *\n * Rejected: `useInsertionEffect`, which runs earlier but is specified for\n * style injection and is not a general layout hook; and per-file guards, which\n * is what the 15 copies would have become.\n */\nexport const useHeidiLayoutEffect =\n  typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n",
      "path": "packages/heidi-ui/src/_internal/use-layout-effect.ts",
      "target": "components/ui/heidi/_internal/use-layout-effect.ts",
      "type": "registry:ui"
    }
  ],
  "name": "toolbar",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Toolbar",
  "type": "registry:ui"
}
