{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "overflow",
  "type": "registry:ui",
  "title": "Overflow",
  "description": "Headless priority-overflow system — the core mechanism behind a single-line Fluent 2 Ribbon. A row of controls that cannot fit hides its lowest-priority items into a '…' overflow menu and restores them (highest-priority first) as it grows. Ships a framework-agnostic manager (createOverflowManager) plus React bindings: Overflow provider, OverflowItem, OverflowDivider, and the useOverflowMenu / useIsOverflowItemVisible / useIsOverflowGroupVisible / useOverflowCount hooks. ResizeObserver-driven, priority + pinned ranking with DOM-position tie-break, size caching, group tracking, and a referentially-stable subscribe/getSnapshot store built for useSyncExternalStore. SSR-safe (everything visible until measured) and headless (no styling of its own).",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/overflow.tsx",
      "type": "registry:ui",
      "target": "components/ui/overflow.tsx",
      "content": "\"use client\";\n\nimport {\n  Children,\n  cloneElement,\n  createContext,\n  isValidElement,\n  useCallback,\n  useContext,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  useEffect,\n  useLayoutEffect,\n  useSyncExternalStore,\n} from \"react\";\nimport type { CSSProperties, ReactElement, Ref } from \"react\";\n\n/**\n * Overflow — a **headless** priority-overflow system (no visuals of its own).\n *\n * This is the core mechanism behind the kit's upcoming single-line **Ribbon**\n * (Fluent 2 / Office). A row of controls (a `Toolbar`) that cannot fit its\n * container hides its **lowest-priority** items into a \"…\" overflow menu, and\n * restores them, highest-priority-first, as the container grows — exactly the\n * behavior captured live from Word Online in `docs/design/ribbon-behavior-spec.md`\n * (\"Single-line mode\": items drop into \"…\", grouped under section headers named\n * after their source group).\n *\n * ## Licensing / provenance\n * This reimplements the **publicly documented priority-overflow *pattern*** —\n * ResizeObserver-driven, priority + pinned ranking, DOM-position tie-break, a\n * subscribe/getSnapshot store, hide-via-CSS so hidden items stay measurable, and\n * the \"…\"-trigger width reserved from the budget. **No Fluent UI source was\n * consulted or copied** (conventions §0: Fluent 2 is a behavioral reference\n * only). The contract below was designed from the spec doc + public API research.\n *\n * ## What lives here\n * 1. `createOverflowManager` — the framework-agnostic core. Holds registered\n *    items, runs the measure/rank/hide/show loop, and exposes a\n *    `subscribe`/`getSnapshot` store (built for `useSyncExternalStore`). Exported\n *    so the logic is unit-testable with synthetic sizes and reusable outside\n *    React.\n * 2. `<Overflow>` — React provider; attaches a manager to its single child\n *    container element (clone + ref-merge, the Slot idiom) and observes it.\n * 3. `<OverflowItem>` / `<OverflowDivider>` — clone their single child, register\n *    it, and toggle its CSS visibility from the snapshot.\n * 4. `useOverflowMenu` / `useIsOverflowItemVisible` / `useIsOverflowGroupVisible`\n *    / `useOverflowCount` — hooks for the \"…\" trigger and for building the\n *    overflow menu's contents.\n *\n * ## Ranking (which item survives longer)\n * `pinned` (never hidden) → higher `priority` survives longer → tie-break by DOM\n * position: for `overflowDirection: \"end\"` the item **nearest the overflow end**\n * (last in document order) hides first and reappears last; `\"start\"` mirrors it.\n * Because the visible set is always the top-K of this total order, items\n * \"reappear in reverse order\" of hiding for free as the container grows.\n *\n * ## Measurement & caching model (accurate space accounting, v1.1)\n * A `ResizeObserver` on the container drives updates. Available space =\n * container size on the overflow axis − `padding` (`padding` now defaults to `0`\n * and is pure **consumer slack**, no longer a hand-tuned reserve for costs the\n * manager can't see). Occupied for a candidate visible set models **every** cost\n * the flex row actually pays:\n *   - Σ(visible item sizes);\n *   - Σ(visible **divider** sizes) — dividers register and are measured too (see\n *     \"Group tracking\"), a divider counting whenever its group has ≥1 visible\n *     item;\n *   - the \"…\" **trigger** size whenever at least one item is hidden (so the\n *     trigger can never itself cause overflow); and\n *   - the **flex gap**: `gap × (slots − 1)`, where `slots` = visible items +\n *     visible dividers + the trigger (when shown). The gap is read once per\n *     recompute from the container's computed `column-gap`/`row-gap` via an\n *     injectable `getGap` (default: `getComputedStyle`).\n * The greedy prefix loop tracks item size, divider cost, and slot count\n * incrementally as it admits items in importance order, so it decides how many\n * items fit **without** rendering hypotheses.\n *\n * Item/divider sizes are **cached** every time they are measured while visible; a\n * hidden element keeps its last known size, so re-showing it is accurate. Hiding\n * is done via **CSS** (`display:none` set through the React binding) — the element\n * is **never unmounted**, so (a) the overflow menu can still read each item's\n * React metadata, and (b) the element becomes measurable again the moment it is\n * restored. Measurement uses the injectable `getSize` (default:\n * `offsetWidth`/`offsetHeight`) so the loop is testable under jsdom's zero-layout\n * DOM, and it never overwrites a good cached size with a transient `0`.\n *\n * ## Post-layout safety net\n * Predictive accounting is now accurate, but sub-pixel rounding or an\n * un-modelled cost could still leave a hair of overrun. After the React bindings\n * apply a snapshot, `settle()` runs (scheduled by the binding in a layout effect,\n * so the just-committed `display:none` is reflected): if the container **truly**\n * overflows (`getOverflowSize` — `scrollWidth`/`scrollHeight` — exceeds the\n * client size), it hides **one** more item and lets the next commit re-check.\n * The net is **hide-only** within a settle sequence (an oscillation guard: it may\n * never show), its extra-hidden count resets when the container size actually\n * changes or items (un)register, and a hard cap (`items.size`) backstops it. It\n * never hides below the pinned/`minimumVisible` floor.\n *\n * ## Group tracking\n * Each `groupId` derives a tri-state (`OverflowGroupState`): `\"visible\"` (all its\n * items shown), `\"overflow\"` (some shown, some hidden), `\"hidden\"` (all hidden).\n * `<OverflowDivider groupId>` **registers its element** so its width + gap\n * participate in the occupancy sum, and hides itself only when its group is\n * **fully overflowed** (`\"hidden\"`) — a dangling separator with nothing left\n * beside it. It is not a ranked item (it never hides by rank; its visibility is\n * derived from its group's state). The overflow menu renders a section (header +\n * items) for any group whose state is not `\"visible\"`.\n *\n * ## SSR story\n * `createOverflowManager` touches no DOM at construction (safe inside a `useState`\n * initializer during server render). No `ResizeObserver` is created and no layout\n * is read during render — the observer is wired only when the container ref\n * attaches (client-only). The initial/server snapshot shows **everything\n * visible** (`getServerSnapshot` returns \"visible\"/`true`/`0`), so the server\n * markup and the first client paint agree; the first real measurement happens\n * after mount and reflows once (the standard, hydration-safe behavior for this\n * pattern).\n *\n * ## Snapshot shape (referentially stable)\n * `getSnapshot()` returns the same object identity until the derived state\n * actually changes (membership-compared, not rebuilt every tick), so\n * `useSyncExternalStore` never loops:\n *\n * ```ts\n * type OverflowSnapshot = {\n *   visibleItemIds: ReadonlySet<string>;\n *   overflowItemIds: ReadonlySet<string>;   // hidden\n *   overflowCount: number;\n *   hasOverflow: boolean;\n *   groupStates: Readonly<Record<string, OverflowGroupState>>;\n * };\n * ```\n *\n * ## `\"use client\"` — required\n * This file owns React context, hooks, refs, and a `ResizeObserver`, all\n * client-only. It imports **no** `@fluentui/react-icons` and no styling helpers\n * (`cn`/`cva`) — it renders no visuals, so it has no registry `utils` dependency.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Derived visibility of a `groupId`'s items. */\nexport type OverflowGroupState = \"visible\" | \"overflow\" | \"hidden\";\n\n/** Which end items overflow toward, and which axis the container flows on. */\nexport type OverflowDirection = \"end\" | \"start\";\nexport type OverflowAxis = \"horizontal\" | \"vertical\";\n\n/** Injectable measurement fn (default reads `offsetWidth`/`offsetHeight`). */\nexport type OverflowGetSize = (element: HTMLElement, axis: OverflowAxis) => number;\n\n/** Injectable flex-gap reader (default reads computed `column-gap`/`row-gap`). */\nexport type OverflowGetGap = (element: HTMLElement, axis: OverflowAxis) => number;\n\n/**\n * Injectable \"true rendered extent\" reader for the post-layout safety net\n * (default reads `scrollWidth`/`scrollHeight`).\n */\nexport type OverflowGetOverflowSize = (\n  element: HTMLElement,\n  axis: OverflowAxis\n) => number;\n\nexport interface OverflowManagerOptions {\n  overflowDirection?: OverflowDirection;\n  overflowAxis?: OverflowAxis;\n  /**\n   * **Extra** slack the consumer wants on top of the manager's accurate space\n   * accounting (item sizes + dividers + flex gap + trigger are all modelled now).\n   * Default `0` — you no longer hand-tune this to cover gaps/dividers.\n   */\n  padding?: number;\n  /** Floor on the number of **non-pinned** items kept visible (pinned are always visible and never counted here). */\n  minimumVisible?: number;\n  getSize?: OverflowGetSize;\n  /** Reads the container's flex gap on the overflow axis (default: computed style). */\n  getGap?: OverflowGetGap;\n  /** Reads the container's true rendered extent for the safety net (default: `scrollWidth`). */\n  getOverflowSize?: OverflowGetOverflowSize;\n}\n\n/** What `<OverflowItem>` hands to the manager on register. */\nexport interface OverflowItemRegistration {\n  id: string;\n  element: HTMLElement | null;\n  priority?: number;\n  pinned?: boolean;\n  groupId?: string;\n}\n\nexport interface OverflowSnapshot {\n  visibleItemIds: ReadonlySet<string>;\n  overflowItemIds: ReadonlySet<string>;\n  overflowCount: number;\n  hasOverflow: boolean;\n  groupStates: Readonly<Record<string, OverflowGroupState>>;\n}\n\nexport interface OverflowManager {\n  subscribe: (listener: () => void) => () => void;\n  getSnapshot: () => OverflowSnapshot;\n  register: (registration: OverflowItemRegistration) => void;\n  unregister: (id: string) => void;\n  setItemElement: (id: string, element: HTMLElement | null) => void;\n  /**\n   * Register a group **divider** as a measured (but non-ranked) participant. Its\n   * width + gap count toward occupancy whenever its group has a visible item; it\n   * never hides by rank (its visibility is derived from its group's state).\n   */\n  registerDivider: (id: string, groupId: string, element: HTMLElement | null) => void;\n  setDividerElement: (id: string, element: HTMLElement | null) => void;\n  setContainer: (element: HTMLElement | null) => void;\n  setOverflowMenu: (element: HTMLElement | null) => void;\n  setOptions: (options: Partial<OverflowManagerOptions>) => void;\n  /** Synchronously re-measure and recompute (used by the observer and by tests). */\n  update: () => void;\n  /**\n   * One step of the post-layout safety net: if the container still truly\n   * overflows (`getOverflowSize` > client), hide one more item (hide-only,\n   * capped, never below the floor). Scheduled by the React binding after a\n   * commit that changed visibility; also callable directly in tests.\n   */\n  settle: () => void;\n  /**\n   * Dispose current resources (observer, listeners, items). NOT terminal: any\n   * subsequent `register`/`setContainer`/`setOverflowMenu` revives the manager.\n   * This makes the React binding safe under StrictMode's dev-only\n   * unmount/remount double-invoke, where the same useState-held manager is\n   * destroyed by the simulated unmount and must rebuild on the re-run.\n   */\n  destroy: () => void;\n}\n\ninterface InternalItem {\n  id: string;\n  element: HTMLElement | null;\n  priority: number;\n  pinned: boolean;\n  groupId?: string;\n  /** Last known measured size on the overflow axis; kept across hidden periods. */\n  size: number;\n  /** Document-order index, recomputed each pass. */\n  domIndex: number;\n}\n\ninterface InternalDivider {\n  id: string;\n  groupId: string;\n  element: HTMLElement | null;\n  /** Last known measured size on the overflow axis; kept across hidden periods. */\n  size: number;\n}\n\n// compareDocumentPosition bitmask constants (avoid a `Node` global reference).\nconst DOCUMENT_POSITION_PRECEDING = 2;\nconst DOCUMENT_POSITION_FOLLOWING = 4;\n\n/** Slack tolerated before the safety net treats the row as truly overflowing. */\nconst SAFETY_TOLERANCE = 1;\n\nconst defaultGetSize: OverflowGetSize = (element, axis) =>\n  axis === \"horizontal\" ? element.offsetWidth : element.offsetHeight;\n\nconst defaultGetGap: OverflowGetGap = (element, axis) => {\n  if (typeof getComputedStyle === \"undefined\") return 0;\n  const style = getComputedStyle(element);\n  const raw = axis === \"horizontal\" ? style.columnGap : style.rowGap;\n  const value = parseFloat(raw);\n  return Number.isFinite(value) ? value : 0;\n};\n\nconst defaultGetOverflowSize: OverflowGetOverflowSize = (element, axis) =>\n  axis === \"horizontal\" ? element.scrollWidth : element.scrollHeight;\n\n/**\n * The container's own inline padding on the overflow axis — it eats into the\n * space the flex children get, so it's subtracted from `available`. Read from\n * computed style (0 under SSR / jsdom, so synthetic-size tests are unaffected).\n */\nfunction readContainerPadding(element: HTMLElement, axis: OverflowAxis): number {\n  if (typeof getComputedStyle === \"undefined\") return 0;\n  const style = getComputedStyle(element);\n  const a = parseFloat(axis === \"horizontal\" ? style.paddingLeft : style.paddingTop);\n  const b = parseFloat(\n    axis === \"horizontal\" ? style.paddingRight : style.paddingBottom\n  );\n  return (Number.isFinite(a) ? a : 0) + (Number.isFinite(b) ? b : 0);\n}\n\n// ---------------------------------------------------------------------------\n// Core manager (framework-agnostic)\n// ---------------------------------------------------------------------------\n\nexport function createOverflowManager(\n  initialOptions: OverflowManagerOptions = {}\n): OverflowManager {\n  const options: Required<OverflowManagerOptions> = {\n    overflowDirection: initialOptions.overflowDirection ?? \"end\",\n    overflowAxis: initialOptions.overflowAxis ?? \"horizontal\",\n    padding: initialOptions.padding ?? 0,\n    minimumVisible: initialOptions.minimumVisible ?? 0,\n    getSize: initialOptions.getSize ?? defaultGetSize,\n    getGap: initialOptions.getGap ?? defaultGetGap,\n    getOverflowSize: initialOptions.getOverflowSize ?? defaultGetOverflowSize,\n  };\n\n  const items = new Map<string, InternalItem>();\n  const dividers = new Map<string, InternalDivider>();\n  const listeners = new Set<() => void>();\n\n  let container: HTMLElement | null = null;\n  let containerObserver: ResizeObserver | null = null;\n  let overflowMenuEl: HTMLElement | null = null;\n  let overflowMenuSize = 0;\n\n  // Flex gap on the overflow axis, read once per recompute.\n  let gap = 0;\n  // Post-layout safety net: extra items hidden beyond the accounted count.\n  // Hide-only within a settle sequence; reset on real size change / (un)register.\n  let extraHidden = 0;\n  let lastContainerSize = -1;\n\n  let updating = false;\n  let dirty = false;\n  let destroyed = false;\n\n  let snapshot: OverflowSnapshot = buildAllVisible();\n\n  function buildAllVisible(): OverflowSnapshot {\n    const visibleItemIds = new Set<string>();\n    const groupStates: Record<string, OverflowGroupState> = {};\n    for (const item of items.values()) {\n      visibleItemIds.add(item.id);\n      if (item.groupId) groupStates[item.groupId] = \"visible\";\n    }\n    return {\n      visibleItemIds,\n      overflowItemIds: new Set(),\n      overflowCount: 0,\n      hasOverflow: false,\n      groupStates,\n    };\n  }\n\n  function snapshotsEqual(a: OverflowSnapshot, b: OverflowSnapshot): boolean {\n    if (a.overflowCount !== b.overflowCount) return false;\n    if (a.visibleItemIds.size !== b.visibleItemIds.size) return false;\n    for (const id of a.visibleItemIds) if (!b.visibleItemIds.has(id)) return false;\n    const aKeys = Object.keys(a.groupStates);\n    const bKeys = Object.keys(b.groupStates);\n    if (aKeys.length !== bKeys.length) return false;\n    for (const key of aKeys) {\n      if (a.groupStates[key] !== b.groupStates[key]) return false;\n    }\n    return true;\n  }\n\n  function commit(next: OverflowSnapshot) {\n    if (snapshotsEqual(snapshot, next)) return; // referentially stable no-op\n    snapshot = next;\n    for (const listener of listeners) listener();\n  }\n\n  /** Sort items into document order and stamp `domIndex`. */\n  function orderByDom(list: InternalItem[]): InternalItem[] {\n    const withEl = list.filter((item) => item.element);\n    const withoutEl = list.filter((item) => !item.element);\n    withEl.sort((a, b) => {\n      const position = a.element!.compareDocumentPosition(b.element!);\n      if (position & DOCUMENT_POSITION_FOLLOWING) return -1; // b after a → a first\n      if (position & DOCUMENT_POSITION_PRECEDING) return 1;\n      return 0;\n    });\n    withEl.forEach((item, index) => {\n      item.domIndex = index;\n    });\n    // Elementless items (not yet mounted) sort last; they carry no size.\n    withoutEl.forEach((item) => {\n      item.domIndex = withEl.length;\n    });\n    return [...withEl, ...withoutEl];\n  }\n\n  /** Positive → `a` is MORE important (survives longer). */\n  function importance(a: InternalItem, b: InternalItem): number {\n    if (a.pinned !== b.pinned) return a.pinned ? 1 : -1;\n    if (a.priority !== b.priority) return a.priority - b.priority;\n    // Tie-break by distance from the overflow edge (farther = more important).\n    if (options.overflowDirection === \"end\") return b.domIndex - a.domIndex;\n    return a.domIndex - b.domIndex;\n  }\n\n  function recompute() {\n    // No container → everything visible; forget cached geometry.\n    if (!container) {\n      extraHidden = 0;\n      lastContainerSize = -1;\n      commit(buildAllVisible());\n      return;\n    }\n\n    const containerSize = options.getSize(container, options.overflowAxis);\n    gap = options.getGap(container, options.overflowAxis);\n    // A real container-size change ends any settle sequence (reset the net).\n    if (containerSize !== lastContainerSize) {\n      extraHidden = 0;\n      lastContainerSize = containerSize;\n    }\n    // Not yet measurable / collapsed → everything visible.\n    if (!(containerSize > 0)) {\n      commit(buildAllVisible());\n      return;\n    }\n\n    const ordered = orderByDom([...items.values()]);\n\n    // Measure only currently-visible items (a hidden `display:none` element\n    // reports 0); never clobber a good cached size with a transient 0.\n    for (const item of ordered) {\n      if (item.element && !snapshot.overflowItemIds.has(item.id)) {\n        const measured = options.getSize(item.element, options.overflowAxis);\n        if (measured > 0) item.size = measured;\n      }\n    }\n    if (overflowMenuEl) {\n      const measured = options.getSize(overflowMenuEl, options.overflowAxis);\n      if (measured > 0) overflowMenuSize = measured;\n    }\n    // Measure dividers that are currently rendered (group not fully hidden).\n    for (const divider of dividers.values()) {\n      if (\n        divider.element &&\n        snapshot.groupStates[divider.groupId] !== \"hidden\"\n      ) {\n        const measured = options.getSize(divider.element, options.overflowAxis);\n        if (measured > 0) divider.size = measured;\n      }\n    }\n    // Divider cost keyed by group (a group's divider is visible iff the group\n    // has ≥1 visible item).\n    const dividersByGroup = new Map<string, { size: number; count: number }>();\n    for (const divider of dividers.values()) {\n      const entry = dividersByGroup.get(divider.groupId) ?? { size: 0, count: 0 };\n      entry.size += divider.size;\n      entry.count += 1;\n      dividersByGroup.set(divider.groupId, entry);\n    }\n\n    // Flex children live inside the container's content box, so subtract its own\n    // inline padding (plus any extra consumer `padding` slack).\n    const available =\n      containerSize -\n      readContainerPadding(container, options.overflowAxis) -\n      options.padding;\n\n    // Most-important first. The visible set is always a prefix of this order.\n    const ranked = ordered.slice().sort((a, b) => importance(b, a));\n    const n = ranked.length;\n\n    // Floors: all pinned (front block) always visible; keep >= minimumVisible\n    // non-pinned items (they force-show and clip rather than disappear).\n    const pinnedCount = ranked.filter((item) => item.pinned).length;\n    const nonPinnedFloor = Math.min(options.minimumVisible, n - pinnedCount);\n    const floor = pinnedCount + nonPinnedFloor;\n\n    // Occupancy of the full set (no trigger): item sizes + every divider whose\n    // group is present + flex gap across all rendered slots.\n    const occupancyAll = (() => {\n      let itemSize = 0;\n      const groups = new Set<string>();\n      for (const item of ranked) {\n        itemSize += item.size;\n        if (item.groupId) groups.add(item.groupId);\n      }\n      let dividerSize = 0;\n      let dividerCount = 0;\n      for (const [groupId, entry] of dividersByGroup) {\n        if (groups.has(groupId)) {\n          dividerSize += entry.size;\n          dividerCount += entry.count;\n        }\n      }\n      const slots = n + dividerCount;\n      return itemSize + dividerSize + (slots > 0 ? gap * (slots - 1) : 0);\n    })();\n\n    let visibleCount: number;\n    if (occupancyAll <= available) {\n      visibleCount = n; // everything fits, no trigger needed\n    } else {\n      // At least one item won't fit → the \"…\" trigger will show (when present);\n      // it takes both a size and a gap slot. Admit items in importance order,\n      // tracking item size, divider cost, and slot count incrementally.\n      const triggerShown = overflowMenuEl != null;\n      const triggerSize = triggerShown ? overflowMenuSize : 0;\n      let itemSize = 0;\n      let dividerSize = 0;\n      let dividerCount = 0;\n      const seenGroups = new Set<string>();\n      let fit = 0;\n      for (let i = 0; i < n; i++) {\n        const item = ranked[i]!;\n        itemSize += item.size;\n        if (item.groupId && !seenGroups.has(item.groupId)) {\n          seenGroups.add(item.groupId);\n          const entry = dividersByGroup.get(item.groupId);\n          if (entry) {\n            dividerSize += entry.size;\n            dividerCount += entry.count;\n          }\n        }\n        const slots = i + 1 + dividerCount + (triggerShown ? 1 : 0);\n        const gapTotal = slots > 0 ? gap * (slots - 1) : 0;\n        const occupied = itemSize + dividerSize + gapTotal + triggerSize;\n        if (occupied <= available) fit = i + 1;\n        else break;\n      }\n      visibleCount = Math.min(n, Math.max(fit, floor));\n    }\n\n    // Post-layout safety net: hide `extraHidden` more, never below the floor.\n    visibleCount = Math.max(floor, visibleCount - extraHidden);\n\n    const visibleItemIds = new Set<string>();\n    const overflowItemIds = new Set<string>();\n    ranked.forEach((item, index) => {\n      (index < visibleCount ? visibleItemIds : overflowItemIds).add(item.id);\n    });\n\n    const groupTotals = new Map<string, { total: number; visible: number }>();\n    for (const item of ranked) {\n      if (!item.groupId) continue;\n      const entry = groupTotals.get(item.groupId) ?? { total: 0, visible: 0 };\n      entry.total += 1;\n      if (visibleItemIds.has(item.id)) entry.visible += 1;\n      groupTotals.set(item.groupId, entry);\n    }\n    const groupStates: Record<string, OverflowGroupState> = {};\n    for (const [groupId, { total, visible }] of groupTotals) {\n      groupStates[groupId] =\n        visible === total ? \"visible\" : visible === 0 ? \"hidden\" : \"overflow\";\n    }\n\n    commit({\n      visibleItemIds,\n      overflowItemIds,\n      overflowCount: overflowItemIds.size,\n      hasOverflow: overflowItemIds.size > 0,\n      groupStates,\n    });\n  }\n\n  function update() {\n    if (destroyed) return;\n    if (updating) {\n      // Re-entrant call (e.g. a listener mounted/unmounted an item) → loop again.\n      dirty = true;\n      return;\n    }\n    updating = true;\n    try {\n      do {\n        dirty = false;\n        recompute();\n      } while (dirty);\n    } finally {\n      updating = false;\n    }\n  }\n\n  /**\n   * One step of the post-layout safety net. Runs after a commit reflected the\n   * current visibility, so `getOverflowSize` sees the real rendered extent. Only\n   * ever hides (oscillation guard); backs off if hiding one more makes no\n   * difference (already at the floor); capped at `items.size`.\n   */\n  function settle() {\n    if (destroyed || !container) return;\n    const client = options.getSize(container, options.overflowAxis);\n    if (!(client > 0)) return;\n    const scroll = options.getOverflowSize(container, options.overflowAxis);\n    if (scroll <= client + SAFETY_TOLERANCE) return; // no true overflow\n    if (extraHidden >= items.size) return; // hard cap backstop\n    extraHidden += 1;\n    const before = snapshot;\n    recompute();\n    // No progress (floor reached) → don't creep toward the cap.\n    if (snapshot === before) extraHidden -= 1;\n  }\n\n  function attachObserver() {\n    if (containerObserver || !container) return;\n    if (typeof ResizeObserver === \"undefined\") return; // SSR / unsupported\n    containerObserver = new ResizeObserver(() => update());\n    containerObserver.observe(container);\n  }\n\n  function detachObserver() {\n    containerObserver?.disconnect();\n    containerObserver = null;\n  }\n\n  return {\n    subscribe(listener) {\n      listeners.add(listener);\n      return () => listeners.delete(listener);\n    },\n    getSnapshot() {\n      return snapshot;\n    },\n    register(registration) {\n      destroyed = false; // any new use revives a destroyed manager (StrictMode remount)\n      const existing = items.get(registration.id);\n      items.set(registration.id, {\n        id: registration.id,\n        element: registration.element,\n        priority: registration.priority ?? 0,\n        pinned: registration.pinned ?? false,\n        groupId: registration.groupId,\n        size: existing?.size ?? 0, // preserve a cached size across re-registers\n        domIndex: existing?.domIndex ?? 0,\n      });\n      extraHidden = 0; // membership change ends any settle sequence\n      update();\n    },\n    unregister(id) {\n      const removed = items.delete(id) || dividers.delete(id);\n      if (removed) {\n        extraHidden = 0;\n        update();\n      }\n    },\n    setItemElement(id, element) {\n      const item = items.get(id);\n      if (item) item.element = element;\n    },\n    registerDivider(id, groupId, element) {\n      destroyed = false;\n      const existing = dividers.get(id);\n      dividers.set(id, {\n        id,\n        groupId,\n        element,\n        size: existing?.size ?? 0, // preserve a cached size across re-registers\n      });\n      extraHidden = 0;\n      update();\n    },\n    setDividerElement(id, element) {\n      const divider = dividers.get(id);\n      if (divider) divider.element = element;\n    },\n    setContainer(element) {\n      if (element) destroyed = false; // revive on re-attach (StrictMode remount)\n      if (element === container) {\n        if (element) attachObserver(); // same node after a destroy → observer is gone\n        return;\n      }\n      detachObserver();\n      container = element;\n      if (element) attachObserver();\n      update();\n    },\n    setOverflowMenu(element) {\n      if (element) destroyed = false; // revive on re-attach (StrictMode remount)\n      overflowMenuEl = element;\n      update();\n    },\n    setOptions(next) {\n      Object.assign(options, {\n        ...(next.overflowDirection !== undefined && {\n          overflowDirection: next.overflowDirection,\n        }),\n        ...(next.overflowAxis !== undefined && { overflowAxis: next.overflowAxis }),\n        ...(next.padding !== undefined && { padding: next.padding }),\n        ...(next.minimumVisible !== undefined && {\n          minimumVisible: next.minimumVisible,\n        }),\n        ...(next.getSize !== undefined && { getSize: next.getSize }),\n        ...(next.getGap !== undefined && { getGap: next.getGap }),\n        ...(next.getOverflowSize !== undefined && {\n          getOverflowSize: next.getOverflowSize,\n        }),\n      });\n      update();\n    },\n    update,\n    settle,\n    destroy() {\n      destroyed = true;\n      detachObserver();\n      listeners.clear();\n      items.clear();\n      dividers.clear();\n    },\n  };\n}\n\n// ---------------------------------------------------------------------------\n// Ref helpers (Slot-style merge; no external dependency)\n// ---------------------------------------------------------------------------\n\ntype PossibleRef<T> = Ref<T> | undefined;\n\nfunction assignRef<T>(ref: PossibleRef<T>, value: T | null) {\n  if (typeof ref === \"function\") ref(value);\n  else if (ref) (ref as { current: T | null }).current = value;\n}\n\nfunction mergeRefs<T>(...refs: PossibleRef<T>[]) {\n  return (value: T | null) => {\n    for (const ref of refs) assignRef(ref, value);\n  };\n}\n\n/** In React 19 a JSX `ref` lives on `element.props.ref`. */\nfunction getChildRef(element: ReactElement): PossibleRef<HTMLElement> {\n  return (element.props as { ref?: PossibleRef<HTMLElement> }).ref;\n}\n\nfunction getChildStyle(element: ReactElement): CSSProperties | undefined {\n  return (element.props as { style?: CSSProperties }).style;\n}\n\n// ---------------------------------------------------------------------------\n// React bindings\n// ---------------------------------------------------------------------------\n\nconst OverflowContext = createContext<OverflowManager | null>(null);\n\nfunction useOverflowContext(): OverflowManager {\n  const manager = useContext(OverflowContext);\n  if (!manager) {\n    throw new Error(\n      \"Overflow components must be rendered inside an <Overflow> provider.\"\n    );\n  }\n  return manager;\n}\n\nconst useIsomorphicLayoutEffect =\n  typeof document !== \"undefined\" ? useLayoutEffect : useEffect;\n\nexport interface OverflowProps extends OverflowManagerOptions {\n  /** A single element (e.g. a `Toolbar` or `div`) used as the measured container. */\n  children: ReactElement;\n}\n\n/**\n * `<Overflow>` — provider + container binding. Clones its single child and\n * merges in the container ref (the Slot idiom the kit already uses for\n * composition), then observes that element. Pass the row itself as the child:\n *\n * ```tsx\n * <Overflow padding={16} minimumVisible={1}>\n *   <Toolbar aria-label=\"Formatting\" className=\"w-full\">…</Toolbar>\n * </Overflow>\n * ```\n */\nfunction Overflow({\n  children,\n  overflowDirection = \"end\",\n  overflowAxis = \"horizontal\",\n  padding = 0,\n  minimumVisible = 0,\n  getSize,\n  getGap,\n  getOverflowSize,\n}: OverflowProps) {\n  const [manager] = useState(() =>\n    createOverflowManager({\n      overflowDirection,\n      overflowAxis,\n      padding,\n      minimumVisible,\n      getSize,\n      getGap,\n      getOverflowSize,\n    })\n  );\n\n  // Keep the manager's options in sync with prop changes.\n  useIsomorphicLayoutEffect(() => {\n    manager.setOptions({\n      overflowDirection,\n      overflowAxis,\n      padding,\n      minimumVisible,\n      getSize,\n      getGap,\n      getOverflowSize,\n    });\n  }, [\n    manager,\n    overflowDirection,\n    overflowAxis,\n    padding,\n    minimumVisible,\n    getSize,\n    getGap,\n    getOverflowSize,\n  ]);\n\n  // Subscribe so the provider itself re-renders on every snapshot change (the\n  // item hooks alone re-render the children, not this component) — that is what\n  // lets the safety-net layout effect below run *after* a visibility commit.\n  const snapshot = useSyncExternalStore(\n    manager.subscribe,\n    manager.getSnapshot,\n    manager.getSnapshot\n  );\n\n  // Post-commit safety net: after a commit the DOM reflects the current\n  // visibility, so a layout-effect read of the container's true extent\n  // (`scrollWidth`) is accurate. If it still overruns, `settle()` hides one more\n  // and the resulting commit re-runs this effect until the row genuinely fits\n  // (hide-only, so it converges). No-ops when nothing overflows.\n  useIsomorphicLayoutEffect(() => {\n    manager.settle();\n  }, [manager, snapshot]);\n\n  // Tear the manager (and its observer) down on unmount.\n  useEffect(() => () => manager.destroy(), [manager]);\n\n  const containerRef = useCallback(\n    (element: HTMLElement | null) => manager.setContainer(element),\n    [manager]\n  );\n\n  const child = Children.only(children);\n  if (!isValidElement(child)) {\n    throw new Error(\"<Overflow> expects a single element child.\");\n  }\n  const mergedRef = useMemo(\n    () => mergeRefs<HTMLElement>(getChildRef(child), containerRef),\n    [child, containerRef]\n  );\n\n  return (\n    <OverflowContext.Provider value={manager}>\n      {cloneElement(child, {\n        ref: mergedRef,\n        \"data-overflow-container\": \"\",\n      } as Record<string, unknown>)}\n    </OverflowContext.Provider>\n  );\n}\n\nexport interface OverflowItemProps {\n  id: string;\n  priority?: number;\n  pinned?: boolean;\n  groupId?: string;\n  /** Exactly one child element — it is registered, measured, and CSS-hidden. */\n  children: ReactElement;\n}\n\n/**\n * `<OverflowItem>` — wraps exactly one element. Registers it on mount (with\n * `id`/`priority`/`pinned`/`groupId`), unregisters on unmount, and applies the\n * hidden treatment (inline `display:none` — inline wins over utility classes;\n * the element is never unmounted) when the snapshot says it overflowed. Stamps\n * `data-slot=\"overflow-item\"` and `data-overflowing` (+ `aria-hidden`, since the\n * item is instead offered inside the overflow menu) when hidden.\n */\nfunction OverflowItem({\n  id,\n  priority = 0,\n  pinned = false,\n  groupId,\n  children,\n}: OverflowItemProps) {\n  const manager = useOverflowContext();\n  const visible = useIsOverflowItemVisible(id);\n  const elementRef = useRef<HTMLElement | null>(null);\n\n  const captureRef = useCallback(\n    (element: HTMLElement | null) => {\n      elementRef.current = element;\n      manager.setItemElement(id, element);\n    },\n    [manager, id]\n  );\n\n  useIsomorphicLayoutEffect(() => {\n    manager.register({\n      id,\n      element: elementRef.current,\n      priority,\n      pinned,\n      groupId,\n    });\n    return () => manager.unregister(id);\n  }, [manager, id, priority, pinned, groupId]);\n\n  const child = Children.only(children);\n  if (!isValidElement(child)) {\n    throw new Error(\"<OverflowItem> expects a single element child.\");\n  }\n  const mergedRef = useMemo(\n    () => mergeRefs<HTMLElement>(getChildRef(child), captureRef),\n    [child, captureRef]\n  );\n  const childStyle = getChildStyle(child);\n\n  return cloneElement(child, {\n    ref: mergedRef,\n    \"data-slot\": \"overflow-item\",\n    \"data-overflowing\": visible ? undefined : \"\",\n    \"aria-hidden\": visible ? undefined : true,\n    style: visible ? childStyle : { ...childStyle, display: \"none\" },\n  } as Record<string, unknown>);\n}\n\nexport interface OverflowDividerProps {\n  groupId: string;\n  /** A single element (typically a `ToolbarSeparator`). */\n  children: ReactElement;\n}\n\n/**\n * `<OverflowDivider>` — renders its child (e.g. a `ToolbarSeparator`) and hides\n * itself only when its group is **fully overflowed** (`state === \"hidden\"`) — a\n * separator with nothing left beside it. Stamps `data-slot=\"overflow-divider\"`.\n * The divider **registers its element** with the manager (a non-ranked measured\n * participant): its width + gap count toward occupancy whenever its group has a\n * visible item, so the row's accounting is exact — no `padding` reserve needed.\n */\nfunction OverflowDivider({ groupId, children }: OverflowDividerProps) {\n  const manager = useOverflowContext();\n  const state = useIsOverflowGroupVisible(groupId);\n  const hidden = state === \"hidden\";\n  const id = useId();\n  const elementRef = useRef<HTMLElement | null>(null);\n\n  const captureRef = useCallback(\n    (element: HTMLElement | null) => {\n      elementRef.current = element;\n      manager.setDividerElement(id, element);\n    },\n    [manager, id]\n  );\n\n  useIsomorphicLayoutEffect(() => {\n    manager.registerDivider(id, groupId, elementRef.current);\n    return () => manager.unregister(id);\n  }, [manager, id, groupId]);\n\n  const child = Children.only(children);\n  if (!isValidElement(child)) {\n    throw new Error(\"<OverflowDivider> expects a single element child.\");\n  }\n  const mergedRef = useMemo(\n    () => mergeRefs<HTMLElement>(getChildRef(child), captureRef),\n    [child, captureRef]\n  );\n  const childStyle = getChildStyle(child);\n\n  return cloneElement(child, {\n    ref: mergedRef,\n    \"data-slot\": \"overflow-divider\",\n    \"data-overflowing\": hidden ? \"\" : undefined,\n    \"aria-hidden\": hidden ? true : undefined,\n    style: hidden ? { ...childStyle, display: \"none\" } : childStyle,\n  } as Record<string, unknown>);\n}\n\n/**\n * `useOverflowMenu()` — for the \"…\" trigger. Attach the returned `ref` to the\n * trigger element: the manager measures it and reserves its size from the budget\n * whenever anything overflows (so it never causes overflow itself). Returns\n * `overflowCount` and `isOverflowing`; the trigger should stamp\n * `data-overflow-menu` and be hidden (via CSS) when nothing overflows.\n *\n * **Keep the trigger measurable when hidden.** Unlike items (which are measured\n * once while visible, then cached), the trigger starts hidden, so hiding it with\n * `display:none` would leave it unmeasured. Hide it with `visibility:hidden` +\n * out-of-flow positioning (see the preview) so it stays measurable while not\n * consuming row width.\n */\nexport function useOverflowMenu<T extends HTMLElement = HTMLElement>(): {\n  ref: (element: T | null) => void;\n  overflowCount: number;\n  isOverflowing: boolean;\n} {\n  const manager = useOverflowContext();\n  const overflowCount = useOverflowCount();\n  const ref = useCallback(\n    (element: T | null) => manager.setOverflowMenu(element),\n    [manager]\n  );\n  return { ref, overflowCount, isOverflowing: overflowCount > 0 };\n}\n\n/** Is item `id` currently visible in the row? (Server/first paint: `true`.) */\nexport function useIsOverflowItemVisible(id: string): boolean {\n  const manager = useOverflowContext();\n  return useSyncExternalStore(\n    manager.subscribe,\n    () => manager.getSnapshot().visibleItemIds.has(id),\n    () => true\n  );\n}\n\n/**\n * Derived visibility of a `groupId` (`\"visible\" | \"overflow\" | \"hidden\"`).\n * (Server/first paint: `\"visible\"`.) Drives `OverflowDivider` (hide on\n * `\"hidden\"`) and overflow-menu sections (render when not `\"visible\"`).\n */\nexport function useIsOverflowGroupVisible(groupId: string): OverflowGroupState {\n  const manager = useOverflowContext();\n  return useSyncExternalStore(\n    manager.subscribe,\n    () => manager.getSnapshot().groupStates[groupId] ?? \"visible\",\n    () => \"visible\" as OverflowGroupState\n  );\n}\n\n/** Number of items currently overflowed. (Server/first paint: `0`.) */\nexport function useOverflowCount(): number {\n  const manager = useOverflowContext();\n  return useSyncExternalStore(\n    manager.subscribe,\n    () => manager.getSnapshot().overflowCount,\n    () => 0\n  );\n}\n\nexport { Overflow, OverflowItem, OverflowDivider };\n"
    }
  ],
  "docs": "Headless — renders no visuals and imports no `cn`, so it has no `utils` registry dependency. Reimplements the publicly documented priority-overflow *pattern* (no Fluent UI source consulted). Wrap the measured row in `<Overflow>` (it clones its single child and merges in the container ref), wrap each item in `<OverflowItem id priority? pinned? groupId?>` (never unmounted — hidden via CSS so it stays measurable and its metadata stays readable for the overflow menu), put `<OverflowDivider groupId>` between groups (hides when its group is fully overflowed), and build the '…' trigger with `useOverflowMenu()` (attach its `ref`; the trigger's width is reserved from the budget so it never causes overflow — keep it measurable when hidden via `visibility:hidden` + out-of-flow positioning, not `display:none`). Build the overflow menu's contents from `useIsOverflowItemVisible(id)` / `useIsOverflowGroupVisible(groupId)` / `useOverflowCount()` (render only hidden items, grouped under section headers per `groupId`). Compose with the kit `Toolbar` (the row) and `DropdownMenu` (the menu). Ranking: pinned never hides, higher `priority` survives longer, DOM position breaks ties; `minimumVisible` floors the visible non-pinned count. Options on `<Overflow>`: `overflowDirection` (\"end\" default / \"start\"), `overflowAxis` (\"horizontal\" first-class / \"vertical\"), `padding`, `minimumVisible`, and an injectable `getSize`. See `overflow.tsx`'s doc comment for the full contract."
}
