{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [],
  "description": "Native <details>/<summary> accordion with ZERO JavaScript: expand/collapse, keyboard, and semantics are the browser's; exclusive groups via the native name attribute; open/close animation via ::details-content + interpolate-size (progressive enhancement). The header part supplies the APG-required role=heading wrapper at a consumer-chosen level. Server-component safe.",
  "files": [
    {
      "content": "/**\n * heidi-ui Accordion — ZERO JavaScript (docs/HEIDI-UI.md § 2). Native\n * <details>/<summary>: expand/collapse, keyboard, and semantics are the\n * browser's; exclusive groups are the native `name` attribute; open/close\n * animation is ::details-content in accordion.base.css (progressive\n * enhancement). Because it ships no client JS, this renders in Server\n * Components — the Accordion.X namespace object is safe from RSC too.\n *\n * Composition: className / style / ref everywhere, plus render on Root,\n * Header, and Content. Item and Trigger intentionally stay native\n * <details>/<summary>; polymorphing either would destroy zero-JS behavior.\n * Structural CSS: accordion.base.css. Theme: accordion.theme.css.\n *\n * NOTE: the `name` is DOCUMENT-GLOBAL (native <details name>), not scoped\n * to a Root — give each accordion group on a page a unique name.\n */\n\nimport type {\n  DetailsHTMLAttributes,\n  HTMLAttributes,\n  ReactNode,\n  Ref\n} from \"react\";\nimport {\n  renderHeidiElement,\n  type HeidiHostProps\n} from \"../_internal/render-element\";\nimport { ACCORDION_CLASSES } from \"./accordion.classes.generated\";\n\ntype AccordionEmptyState = Record<string, never>;\ntype AccordionNativeHostProps<State> = Omit<HeidiHostProps<State>, \"render\">;\ntype AccordionNativeProps<Props> = Omit<\n  Props,\n  \"children\" | \"className\" | \"ref\" | \"style\"\n>;\n\nexport type AccordionRootProps = HeidiHostProps<AccordionEmptyState> &\n  AccordionNativeProps<HTMLAttributes<HTMLDivElement>> & {\n  children?: ReactNode;\n  ref?: Ref<HTMLDivElement>;\n};\n\nexport function AccordionRoot({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: AccordionRootProps) {\n  return renderHeidiElement({\n    className: ACCORDION_CLASSES.root,\n    dataPart: \"accordion-root\",\n    element: \"div\",\n    props: { ...nativeProps, children, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\n/** Item stays a native <details>; replacing it would destroy zero-JS behavior. */\nexport type AccordionItemProps = AccordionNativeHostProps<AccordionEmptyState> & {\n  children?: ReactNode;\n  /**\n   * Same name across sibling items = native exclusive group (opening one\n   * closes the others). DOCUMENT-GLOBAL — unique per group on the page.\n   */\n  name?: string;\n  /**\n   * Initially open (native `open` attribute). The browser owns live state\n   * after render; style interactive state with the native `[open]` selector.\n   */\n  open?: boolean;\n  ref?: Ref<HTMLDetailsElement>;\n} & AccordionNativeProps<\n    Omit<DetailsHTMLAttributes<HTMLDetailsElement>, \"name\" | \"open\">\n  >;\n\nexport function AccordionItem({\n  children,\n  className,\n  name,\n  open,\n  ref,\n  style,\n  ...nativeProps\n}: AccordionItemProps) {\n  return renderHeidiElement({\n    className: ACCORDION_CLASSES.item,\n    dataPart: \"accordion-item\",\n    element: \"details\",\n    props: {\n      ...nativeProps,\n      children,\n      name,\n      ...(open !== undefined ? { open } : {}),\n      ref\n    },\n    renderProps: { className, style },\n    state: {}\n  });\n}\n\n/** Trigger stays a native <summary> and must be the Item's first child. */\nexport type AccordionTriggerProps = AccordionNativeHostProps<AccordionEmptyState> & {\n  children?: ReactNode;\n  ref?: Ref<HTMLElement>;\n} & AccordionNativeProps<HTMLAttributes<HTMLElement>>;\n\nexport function AccordionTrigger({\n  children,\n  className,\n  ref,\n  style,\n  ...nativeProps\n}: AccordionTriggerProps) {\n  return renderHeidiElement({\n    className: ACCORDION_CLASSES.trigger,\n    dataPart: \"accordion-trigger\",\n    element: \"summary\",\n    props: { ...nativeProps, children, ref },\n    renderProps: { className, style },\n    state: {}\n  });\n}\n\n/** APG heading levels — the correct one depends on the page's outline. */\nexport type AccordionHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\nconst ACCORDION_HEADING_ELEMENTS = {\n  1: \"h1\",\n  2: \"h2\",\n  3: \"h3\",\n  4: \"h4\",\n  5: \"h5\",\n  6: \"h6\"\n} as const satisfies Record<AccordionHeadingLevel, keyof HTMLElementTagNameMap>;\n\n/**\n * The APG heading wrapper for a section title: \"Each accordion header button\n * is wrapped in an element with role heading that has a value set for\n * aria-level that is appropriate for the information architecture of the\n * page\" (https://www.w3.org/WAI/ARIA/apg/patterns/accordion/). Without it an\n * accordion is invisible to the heading rotor, the primary AT affordance for\n * this pattern.\n *\n * ponytail: the heading sits INSIDE the <summary>, inverting the Radix/Base\n * nesting (Header > Trigger). It is not a style choice — HTML requires\n * <summary> to be the FIRST child of <details>, so nothing can wrap it\n * without destroying native disclosure. `<summary>` accepts heading content\n * by spec, and Chromium exposes the inner h1–h6 as a real, non-ignored\n * heading node with the right level while the summary keeps its\n * DisclosureTriangle role (verified against the smoke's own AX-tree probe).\n * Rejected: (a) a JS accordion with <h3><button> — it would delete the zero-JS\n * / RSC differentiator this component exists for and break the six native\n * <details> behaviours the smoke pins; (b) stamping role=\"heading\" +\n * aria-level onto the host — redundant on a real h1–h6, and actively wrong\n * once a consumer supplies their own heading through `render`, because the\n * library's aria-level would override the element's own level.\n */\nexport type AccordionHeaderProps = HeidiHostProps<AccordionEmptyState> &\n  AccordionNativeProps<HTMLAttributes<HTMLHeadingElement>> & {\n  children?: ReactNode;\n  /**\n   * Heading level for this section (default 3). Choose the level that fits\n   * the surrounding outline; a hardcoded h3 is wrong wherever the accordion\n   * is not nested under an h2. `render` accepts a heading element instead\n   * when the level alone is not enough.\n   */\n  level?: AccordionHeadingLevel;\n  ref?: Ref<HTMLHeadingElement>;\n};\n\nexport function AccordionHeader({\n  children,\n  className,\n  level = 3,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: AccordionHeaderProps) {\n  return renderHeidiElement({\n    className: ACCORDION_CLASSES.header,\n    dataPart: \"accordion-header\",\n    element: ACCORDION_HEADING_ELEMENTS[level],\n    props: { ...nativeProps, children, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\nexport type AccordionContentProps = HeidiHostProps<AccordionEmptyState> &\n  AccordionNativeProps<HTMLAttributes<HTMLDivElement>> & {\n  children?: ReactNode;\n  ref?: Ref<HTMLDivElement>;\n};\n\nexport function AccordionContent({\n  children,\n  className,\n  ref,\n  render,\n  style,\n  ...nativeProps\n}: AccordionContentProps) {\n  return renderHeidiElement({\n    className: ACCORDION_CLASSES.content,\n    dataPart: \"accordion-content\",\n    element: \"div\",\n    props: { ...nativeProps, children, ref },\n    renderProps: { className, render, style },\n    state: {}\n  });\n}\n\n/**\n * Namespace sugar. Accordion has no client JS, so this is safe from RSC too.\n */\nexport const Accordion = {\n  Content: AccordionContent,\n  Header: AccordionHeader,\n  Item: AccordionItem,\n  Root: AccordionRoot,\n  Trigger: AccordionTrigger\n} as const;\n",
      "path": "packages/heidi-ui/src/accordion/accordion.tsx",
      "target": "components/ui/heidi/accordion/accordion.tsx",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui accordion — STRUCTURAL CSS only (platform behavior).\n * ::details-content animation wiring only. The native disclosure marker stays\n * visible in the headless skin; the opt-in theme replaces it. No --heidi-*.\n */\n\n@layer heidi-ui-base {\n  .hui-accordion-root {\n    display: flex;\n    flex-direction: column;\n  }\n\n  .hui-accordion-item {\n    /* height-to-auto animation opt-in (Chromium; harmless elsewhere) */\n    interpolate-size: allow-keywords;\n  }\n\n  .hui-accordion-item::details-content {\n    block-size: 0;\n    overflow: hidden;\n    transition-behavior: allow-discrete;\n    transition-property: block-size, content-visibility, overflow;\n  }\n\n  .hui-accordion-item[open]::details-content {\n    block-size: auto;\n    /* Do not leave expanded content inside a permanent clipping ancestor. */\n    overflow: visible;\n  }\n\n  .hui-accordion-trigger,\n  .hui-accordion-content {\n    min-inline-size: 0;\n    overflow-wrap: anywhere;\n  }\n\n  /*\n   * The heading LEVEL is information architecture, not typography: swapping\n   * h3 for h2 must not resize the trigger label or push the theme's floated\n   * disclosure glyph onto its own line, so the UA heading box is neutralized\n   * down to what a bare text label rendered.\n   */\n  .hui-accordion-header {\n    display: inline;\n    font: inherit;\n    margin: 0;\n  }\n}\n",
      "path": "packages/heidi-ui/src/accordion/accordion.base.css",
      "target": "components/ui/heidi/accordion/accordion.base.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui accordion — VISUAL theme (opt-in). Consumes --hui-* semantic theme vars (wired via adapters/heidi.css).\n */\n\n@layer heidi-ui {\n  .hui-accordion-root {\n    border: var(--hui-border-width) var(--hui-border-style) var(--hui-color-border-default);\n    border-radius: var(--hui-radius-lg);\n  }\n\n  .hui-accordion-item + .hui-accordion-item {\n    border-top: var(--hui-border-width) var(--hui-border-style) var(--hui-color-border-default);\n  }\n\n  .hui-accordion-item::details-content {\n    transition-duration: var(--hui-duration-fast), var(--hui-duration-fast), 0s;\n    transition-timing-function: var(--hui-ease-out);\n  }\n\n  .hui-accordion-item[open]::details-content {\n    /* Keep content clipped only while the opening block-size transition runs. */\n    transition-delay: 0s, 0s, var(--hui-duration-fast);\n  }\n\n  .hui-accordion-trigger {\n    align-items: center;\n    color: var(--hui-color-fg-default);\n    column-gap: var(--hui-space-2);\n    cursor: pointer;\n    display: grid;\n    font-weight: var(--hui-font-weight-medium);\n    grid-template-columns: minmax(0, 1fr) var(--hui-space-5);\n    list-style: none;\n    padding: var(--hui-space-3) var(--hui-space-4);\n  }\n\n  .hui-accordion-trigger::-webkit-details-marker {\n    display: none;\n  }\n\n  .hui-accordion-trigger::after {\n    /* Empty alternative text keeps this decorative glyph out of the AX name. */\n    align-items: center;\n    block-size: var(--hui-space-5);\n    content: \"+\" / \"\";\n    display: inline-flex;\n    font-size: var(--hui-space-4);\n    inline-size: var(--hui-space-5);\n    justify-content: center;\n    line-height: 1;\n    transition-duration: var(--hui-duration-fast);\n    transition-property: rotate;\n    transition-timing-function: var(--hui-ease-out);\n  }\n\n  .hui-accordion-item[open] > .hui-accordion-trigger::after {\n    rotate: 45deg;\n  }\n\n  .hui-accordion-trigger:focus-visible {\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  .hui-accordion-content {\n    color: var(--hui-color-fg-muted);\n    padding: 0 var(--hui-space-4) var(--hui-space-3);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .hui-accordion-item::details-content,\n    .hui-accordion-item[open]::details-content,\n    .hui-accordion-trigger::after {\n      transition-delay: 0s;\n      transition-duration: 0s;\n    }\n  }\n\n  @media (forced-colors: active) {\n    .hui-accordion-root,\n    .hui-accordion-item + .hui-accordion-item {\n      border-color: CanvasText;\n    }\n\n    .hui-accordion-trigger:focus-visible {\n      outline-color: Highlight;\n    }\n\n    .hui-accordion-content {\n      color: CanvasText;\n    }\n  }\n}\n",
      "path": "packages/heidi-ui/src/accordion/accordion.theme.css",
      "target": "components/ui/heidi/accordion/accordion.theme.css",
      "type": "registry:ui"
    },
    {
      "content": "/*\n * heidi-ui accordion — aggregator (base + Heidi adapter + theme).\n * Headless: import accordion.base.css only.\n * Themed: import this file (or heidi-ui/styles.css).\n */\n\n@import \"./accordion.base.css\";\n@import \"../../../../styles/heidi-ui-adapter.css\";\n@import \"./accordion.theme.css\";\n",
      "path": "packages/heidi-ui/src/accordion/accordion.css",
      "target": "components/ui/heidi/accordion/accordion.css",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from packages/heidi-ui/src/accordion/accordion.base.css + packages/heidi-ui/src/accordion/accordion.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\nexport const ACCORDION_CLASSES = {\n  content: \"hui-accordion-content\",\n  header: \"hui-accordion-header\",\n  item: \"hui-accordion-item\",\n  root: \"hui-accordion-root\",\n  trigger: \"hui-accordion-trigger\",\n} as const;\n",
      "path": "packages/heidi-ui/src/accordion/accordion.classes.generated.ts",
      "target": "components/ui/heidi/accordion/accordion.classes.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "/** GENERATED from accordion.anatomy.json + accordion.base.css + accordion.theme.css — do not edit by hand.\n * Rebuild: `bun run hui:build`. Freshness: `bun run contracts:check`. */\n\n\nexport const ACCORDION_ANATOMY = {\n  \"component\": \"accordion\",\n  \"description\": \"Native <details>/<summary> accordion with ZERO JavaScript: expand/collapse, keyboard, and semantics are the browser's; exclusive groups via the native name attribute; open/close animation via ::details-content + interpolate-size (progressive enhancement). The header part supplies the APG-required role=heading wrapper at a consumer-chosen level. Server-component safe.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"content\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-content\",\n      \"css\": {\n        \"structural\": [\n          \"min-inline-size\",\n          \"overflow-wrap\"\n        ]\n      },\n      \"dataPart\": \"accordion-content\",\n      \"description\": \"Inner wrapper for the expandable body (padding lives here; the animation lives on ::details-content).\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"header\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": \"heading\"\n      },\n      \"class\": \"hui-accordion-header\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"font\",\n          \"margin\"\n        ]\n      },\n      \"dataPart\": \"accordion-header\",\n      \"description\": \"The APG heading wrapper for the trigger label. Renders <h1>…<h6> from `level` (default 3) so heading navigation reaches every section; it lives INSIDE the <summary> because HTML requires <summary> to be the first child of <details>. Optional — a Trigger with bare text still works.\",\n      \"element\": \"h3\",\n      \"nativeProps\": true,\n      \"props\": [\n        \"className\",\n        \"level\",\n        \"ref\",\n        \"render\",\n        \"style\"\n      ],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"item\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-item\",\n      \"css\": {\n        \"structural\": [\n          \"interpolate-size\"\n        ]\n      },\n      \"dataPart\": \"accordion-item\",\n      \"description\": \"The native <details> element; [open] is the state, ::details-content animates the reveal.\",\n      \"element\": \"details\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\n        \"className\",\n        \"name\",\n        \"open\",\n        \"ref\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \"[open]\",\n        \"::details-content\"\n      ],\n      \"states\": {}\n    },\n    \"root\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-root\",\n      \"css\": {\n        \"structural\": [\n          \"display\",\n          \"flex-direction\"\n        ]\n      },\n      \"dataPart\": \"accordion-root\",\n      \"description\": \"Group container (border + dividers).\",\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        \"role\": null\n      },\n      \"class\": \"hui-accordion-trigger\",\n      \"css\": {\n        \"structural\": [\n          \"min-inline-size\",\n          \"overflow-wrap\"\n        ]\n      },\n      \"dataPart\": \"accordion-trigger\",\n      \"description\": \"The native <summary>; it must remain the Item's first child. Wrap its label in the header part to satisfy the APG heading requirement. Its disclosure marker remains in headless output and the opt-in theme replaces it with a decorative plus icon.\",\n      \"element\": \"summary\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\n        \"className\",\n        \"ref\",\n        \"style\"\n      ],\n      \"pseudoStates\": [\n        \":focus-visible\",\n        \"::-webkit-details-marker\"\n      ],\n      \"states\": {}\n    }\n  },\n  \"rootProps\": [],\n  \"tokens\": [\n    \"--hui-border-style\",\n    \"--hui-border-width\",\n    \"--hui-color-border-default\",\n    \"--hui-color-fg-default\",\n    \"--hui-color-fg-muted\",\n    \"--hui-color-focus-ring\",\n    \"--hui-duration-fast\",\n    \"--hui-ease-out\",\n    \"--hui-focus-ring-offset\",\n    \"--hui-focus-ring-width\",\n    \"--hui-font-weight-medium\",\n    \"--hui-radius-lg\",\n    \"--hui-space-2\",\n    \"--hui-space-3\",\n    \"--hui-space-4\",\n    \"--hui-space-5\"\n  ]\n} as const;\n",
      "path": "packages/heidi-ui/src/accordion/accordion.anatomy.generated.ts",
      "target": "components/ui/heidi/accordion/accordion.anatomy.generated.ts",
      "type": "registry:ui"
    },
    {
      "content": "{\n  \"component\": \"accordion\",\n  \"description\": \"Native <details>/<summary> accordion with ZERO JavaScript: expand/collapse, keyboard, and semantics are the browser's; exclusive groups via the native name attribute; open/close animation via ::details-content + interpolate-size (progressive enhancement). The header part supplies the APG-required role=heading wrapper at a consumer-chosen level. Server-component safe.\",\n  \"schemaVersion\": 2,\n  \"parts\": {\n    \"content\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-content\",\n      \"css\": {\n        \"structural\": [\"min-inline-size\", \"overflow-wrap\"]\n      },\n      \"dataPart\": \"accordion-content\",\n      \"description\": \"Inner wrapper for the expandable body (padding lives here; the animation lives on ::details-content).\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"header\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": \"heading\"\n      },\n      \"class\": \"hui-accordion-header\",\n      \"css\": {\n        \"structural\": [\"display\", \"font\", \"margin\"]\n      },\n      \"dataPart\": \"accordion-header\",\n      \"description\": \"The APG heading wrapper for the trigger label. Renders <h1>…<h6> from `level` (default 3) so heading navigation reaches every section; it lives INSIDE the <summary> because HTML requires <summary> to be the first child of <details>. Optional — a Trigger with bare text still works.\",\n      \"element\": \"h3\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"level\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"item\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-item\",\n      \"css\": {\n        \"structural\": [\"interpolate-size\"]\n      },\n      \"dataPart\": \"accordion-item\",\n      \"description\": \"The native <details> element; [open] is the state, ::details-content animates the reveal.\",\n      \"element\": \"details\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\"className\", \"name\", \"open\", \"ref\", \"style\"],\n      \"pseudoStates\": [\"[open]\", \"::details-content\"],\n      \"states\": {}\n    },\n    \"root\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-root\",\n      \"css\": {\n        \"structural\": [\"display\", \"flex-direction\"]\n      },\n      \"dataPart\": \"accordion-root\",\n      \"description\": \"Group container (border + dividers).\",\n      \"element\": \"div\",\n      \"nativeProps\": true,\n      \"props\": [\"className\", \"ref\", \"render\", \"style\"],\n      \"pseudoStates\": [],\n      \"states\": {}\n    },\n    \"trigger\": {\n      \"aria\": {\n        \"owns\": [],\n        \"role\": null\n      },\n      \"class\": \"hui-accordion-trigger\",\n      \"css\": {\n        \"structural\": [\"min-inline-size\", \"overflow-wrap\"]\n      },\n      \"dataPart\": \"accordion-trigger\",\n      \"description\": \"The native <summary>; it must remain the Item's first child. Wrap its label in the header part to satisfy the APG heading requirement. Its disclosure marker remains in headless output and the opt-in theme replaces it with a decorative plus icon.\",\n      \"element\": \"summary\",\n      \"nativeProps\": true,\n      \"polymorphic\": false,\n      \"props\": [\"className\", \"ref\", \"style\"],\n      \"pseudoStates\": [\":focus-visible\", \"::-webkit-details-marker\"],\n      \"states\": {}\n    }\n  },\n  \"rootProps\": []\n}\n",
      "path": "packages/heidi-ui/src/accordion/accordion.anatomy.json",
      "target": "components/ui/heidi/accordion/accordion.anatomy.json",
      "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"
    }
  ],
  "name": "accordion",
  "registryDependencies": [
    "@heidi-ui/heidi-tokens"
  ],
  "title": "Accordion",
  "type": "registry:ui"
}
