{"version":3,"file":"positioner.cjs","names":[],"sources":["../../src/core/positioner.ts"],"sourcesContent":["import {\n  autoUpdate,\n  computePosition,\n  flip,\n  getClippingAncestorRect,\n  offset,\n  type Placement,\n  type Rect,\n  shift,\n  size,\n} from '@vielzeug/orbit';\n\nimport { elementDirection } from './direction';\n\n// ── Overlay positioner contract ──────────────────────────────────────────────\n\nexport type OverlayPositioner = {\n  floating: () => HTMLElement | null;\n  reference: () => HTMLElement | null;\n  /** Start continuous position auto-updates. Returns a stop function. */\n  startAutoUpdate?: () => () => void;\n  update: () => void;\n};\n\n// ── Dropdown positioner ───────────────────────────────────────────────────────\n\nexport type DropdownPositionerOptions = {\n  /**\n   * Clipping boundary for flip/shift/size — the edges the dropdown must stay within.\n   * Defaults to the nearest ancestor that actually clips content (`overflow:\n   * hidden|auto|scroll|clip`, e.g. a dialog panel), auto-detected per update via\n   * `getClippingAncestorRect()`, intersected with the viewport. Without this, flip/shift only\n   * avoid the full page viewport — a dropdown can be \"in view\" by that measure while still\n   * visibly overhanging a much smaller container it's actually nested in. Pass an explicit\n   * boundary to override auto-detection.\n   */\n  boundary?: Element | Rect;\n  /**\n   * Returns the text direction used for placement mirroring. Defaults to the resolved\n   * direction of the reference element (nearest `dir=\"ltr\"|\"rtl\"` ancestor, falling back to\n   * computed style) — most callers never need to pass this explicitly.\n   */\n  getDir?: () => 'ltr' | 'rtl';\n  /** Getter for the floating (dropdown panel) element. */\n  getFloating: () => HTMLElement | null;\n  /** Getter for the current placement. Defaults to 'bottom-start' (RTL-aware: flips to 'bottom-end'). */\n  getPlacement?: () => Placement;\n  /** Getter for the reference (trigger) element. */\n  getReference: () => HTMLElement | null;\n  /** Whether to match the floating element width to the reference. Default: true */\n  matchWidth?: boolean;\n  /** Additional offset in pixels between reference and floating element. Default: 0 */\n  offsetPx?: number;\n  /** Padding (px) used by flip, shift, and size middleware. Default: 6 */\n  padding?: number;\n  /**\n   * Whether to constrain placement to a clipping ancestor when `boundary` is unset. Default:\n   * true. Disable for native Popover API panels, which render in the top layer.\n   */\n  useClippingAncestor?: boolean;\n};\n\n/** Mirrors a start/end placement for RTL layouts. */\nconst rtlPlacement = (p: Placement): Placement => {\n  if (p.endsWith('-start')) return p.replace('-start', '-end') as Placement;\n\n  if (p.endsWith('-end')) return p.replace('-end', '-start') as Placement;\n\n  return p;\n};\n\n/**\n * Creates an Orbit-powered positioner for dropdown overlays (select, combobox, menu).\n *\n * Uses flip + shift + optional width-matching middleware. Supports continuous\n * auto-update for repositioning on scroll or resize.\n *\n * RTL-aware: start/end placements are automatically mirrored so dropdowns open on the\n * correct side, based on `getDir` (or the reference element's resolved direction when\n * `getDir` is omitted).\n */\nexport function createDropdownPositioner({\n  boundary,\n  getDir,\n  getFloating,\n  getPlacement,\n  getReference,\n  matchWidth = true,\n  offsetPx = 0,\n  padding = 6,\n  useClippingAncestor = true,\n}: DropdownPositionerOptions): OverlayPositioner {\n  function resolvedPlacement(): Placement {\n    const base = getPlacement?.() ?? 'bottom-start';\n    const dir = getDir?.() ?? elementDirection(getReference());\n\n    return dir === 'rtl' ? rtlPlacement(base) : base;\n  }\n\n  function updatePosition(): void {\n    const ref = getReference();\n    const floating = getFloating();\n\n    if (!ref || !floating) return;\n\n    const placement = resolvedPlacement();\n    // Defaults to `undefined` (meaning \"auto-detect\"), not the detected value itself —\n    // re-running the detection on every update (rather than once, at positioner-creation time)\n    // matters because the dialog/scroll-container an instance is rendered into can change\n    // across opens (e.g. the same `<ore-select>` reused in different dialogs), and the detected\n    // ancestor's own rect can change between opens even when it's the same element (a resized\n    // dialog).\n    const resolvedBoundary = boundary ?? (useClippingAncestor ? getClippingAncestorRect(floating) : undefined);\n\n    const result = computePosition(ref, floating, {\n      boundary: resolvedBoundary,\n      middleware: [\n        ...(offsetPx ? [offset(offsetPx)] : []),\n        flip({ padding }),\n        shift({ padding }),\n        ...(matchWidth ? [size({ padding })] : []),\n      ],\n      placement,\n    });\n\n    floating.style.left = `${result.x}px`;\n    floating.style.top = `${result.y}px`;\n\n    // `computePosition()` assumes `left`/`top` resolve relative to the viewport (correct for\n    // `position: fixed`) — true almost always, but not when some ancestor establishes a\n    // containing block for fixed descendants (a non-`none` `transform`, even a visually-identity\n    // one left over from an entrance transition that never resets to `none` at rest is a real,\n    // easy-to-hit case — a modal dialog's panel, say). Analytically detecting *that* ancestor via\n    // `getContainingBlock()` and pre-subtracting its rect turned out to be unreliable in\n    // practice: browsers don't apply the \"ancestor transform traps fixed descendants\" rule\n    // uniformly across every element nested under it in every case we tested — some floating\n    // elements in the exact same subtree needed the correction and some didn't, for reasons we\n    // couldn't fully pin down. Measuring what actually happened after writing `left`/`top`\n    // and correcting only the observed gap sidesteps that: it's a no-op when the browser already\n    // put the element where asked, and it fixes the same class of bug the moment there's a real\n    // mismatch, regardless of which specific ancestor (if any) turns out to be responsible.\n    //\n    // The measurement itself must not be fooled by the floating element's *own* transform,\n    // though: dropdowns/popovers here all use a `transform: translateY(...)` entrance transition\n    // driven by a `[data-open]` attribute + `@starting-style` — right at open, before the\n    // transition has visibly progressed, `getBoundingClientRect()` can still reflect that\n    // starting offset (a handful of px, matching `--overlay-enter-translate-y`), which this\n    // correction would otherwise misread as a permanent ancestor-driven mismatch and bake in\n    // forever. Temporarily neutralizing the floating element's own transform for the measurement\n    // (and restoring it before anything paints, so there's no visible flash) isolates the\n    // correction to ancestor-driven offsets only, which is the only thing it's meant to fix.\n    const prevTransition = floating.style.transition;\n    const prevTransform = floating.style.transform;\n\n    floating.style.transition = 'none';\n    floating.style.transform = 'none';\n\n    const actual = floating.getBoundingClientRect();\n    const deltaX = result.x - actual.left;\n    const deltaY = result.y - actual.top;\n\n    floating.style.transition = prevTransition;\n    floating.style.transform = prevTransform;\n\n    if (deltaX !== 0 || deltaY !== 0) {\n      floating.style.left = `${result.x + deltaX}px`;\n      floating.style.top = `${result.y + deltaY}px`;\n    }\n\n    if (matchWidth) {\n      floating.style.width = `${ref.getBoundingClientRect().width}px`;\n    }\n  }\n\n  function startAutoUpdate(): () => void {\n    const ref = getReference();\n    const floating = getFloating();\n\n    if (!ref || !floating) return () => {};\n\n    return autoUpdate(ref, floating, updatePosition);\n  }\n\n  return { floating: getFloating, reference: getReference, startAutoUpdate, update: updatePosition };\n}\n"],"mappings":"oEA+DA,IAAM,EAAgB,GAChB,EAAE,SAAS,QAAQ,EAAU,EAAE,QAAQ,SAAU,MAAM,EAEvD,EAAE,SAAS,MAAM,EAAU,EAAE,QAAQ,OAAQ,QAAQ,EAElD,EAaT,SAAgB,EAAyB,CACvC,WACA,SACA,cACA,eACA,eACA,aAAa,GACb,WAAW,EACX,UAAU,EACV,sBAAsB,IACyB,CAC/C,SAAS,GAA+B,CACtC,IAAM,EAAO,IAAe,GAAK,eAGjC,OAFY,IAAS,GAAK,EAAA,iBAAiB,EAAa,CAAC,KAE1C,MAAQ,EAAa,CAAI,EAAI,CAC9C,CAEA,SAAS,GAAuB,CAC9B,IAAM,EAAM,EAAa,EACnB,EAAW,EAAY,EAE7B,GAAI,CAAC,GAAO,CAAC,EAAU,OAEvB,IAAM,EAAY,EAAkB,EAO9B,EAAmB,IAAa,GAAA,EAAsB,EAAA,wBAAA,CAAwB,CAAQ,EAAI,IAAA,IAE1F,GAAA,EAAS,EAAA,gBAAA,CAAgB,EAAK,EAAU,CAC5C,SAAU,EACV,WAAY,CACV,GAAI,EAAW,EAAA,EAAC,EAAA,OAAA,CAAO,CAAQ,CAAC,EAAI,CAAC,GACrC,EAAA,EAAA,KAAA,CAAK,CAAE,SAAQ,CAAC,GAChB,EAAA,EAAA,MAAA,CAAM,CAAE,SAAQ,CAAC,EACjB,GAAI,EAAa,EAAA,EAAC,EAAA,KAAA,CAAK,CAAE,SAAQ,CAAC,CAAC,EAAI,CAAC,CAC1C,EACA,WACF,CAAC,EAED,EAAS,MAAM,KAAO,GAAG,EAAO,EAAE,IAClC,EAAS,MAAM,IAAM,GAAG,EAAO,EAAE,IAyBjC,IAAM,EAAiB,EAAS,MAAM,WAChC,EAAgB,EAAS,MAAM,UAErC,EAAS,MAAM,WAAa,OAC5B,EAAS,MAAM,UAAY,OAE3B,IAAM,EAAS,EAAS,sBAAsB,EACxC,EAAS,EAAO,EAAI,EAAO,KAC3B,EAAS,EAAO,EAAI,EAAO,IAEjC,EAAS,MAAM,WAAa,EAC5B,EAAS,MAAM,UAAY,GAEvB,IAAW,GAAK,IAAW,KAC7B,EAAS,MAAM,KAAO,GAAG,EAAO,EAAI,EAAO,IAC3C,EAAS,MAAM,IAAM,GAAG,EAAO,EAAI,EAAO,KAGxC,IACF,EAAS,MAAM,MAAQ,GAAG,EAAI,sBAAsB,CAAC,CAAC,MAAM,IAEhE,CAEA,SAAS,GAA8B,CACrC,IAAM,EAAM,EAAa,EACnB,EAAW,EAAY,EAI7B,MAFI,CAAC,GAAO,CAAC,MAAuB,CAAC,GAErC,EAAO,EAAA,WAAA,CAAW,EAAK,EAAU,CAAc,CACjD,CAEA,MAAO,CAAE,SAAU,EAAa,UAAW,EAAc,kBAAiB,OAAQ,CAAe,CACnG"}