{"version":3,"file":"use-floating-trigger.cjs","names":[],"sources":["../../../src/overlay/shared/use-floating-trigger.ts"],"sourcesContent":["import type { Placement } from '@vielzeug/orbit';\n\nimport { computePosition, flip, offset, shift } from '@vielzeug/orbit';\nimport { type Readable, type Signal, signal, watch } from '@vielzeug/ripple';\n\nimport {\n  createOutsidePointerDismissal,\n  type DialogCloseReason,\n  lifecycleSignal,\n  type OverlayOpenReason,\n} from '../../core';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type FloatingTriggerType = 'click' | 'focus' | 'hover';\n\nexport type AriaBindFn = (triggerEl: HTMLElement) => () => void;\n\nexport type FloatingTriggerOptions = {\n  /** ARIA binding factory: called with the trigger element, returns a cleanup. */\n  bindTriggerAria: AriaBindFn;\n  /** Initial uncontrolled visibility. Ignored when `openProp` is defined. */\n  defaultOpen: Readable<boolean | undefined>;\n  /** If true, disable all trigger interactions and close if open. */\n  disabled: Readable<boolean>;\n  /** Returns the host element (e.g. ore-popover) so slotted content clicks aren't treated as outside clicks. */\n  getHost?: () => Element | null;\n  /** Returns the panel element, if mounted. */\n  getPanel: () => HTMLElement | null;\n  /** Gap from reference to floating panel in px. Default: 8. Accepts a signal for runtime reactivity. */\n  offset?: Readable<number | undefined> | number;\n  /** Cleanup registrar from the component setup ctx. Automatically called on disconnect. */\n  onCleanup: (fn: () => void) => void;\n  /** Called when the popover closes. */\n  onClose?: (reason: DialogCloseReason) => void;\n  /** Called when the popover opens. */\n  onOpen?: (reason: OverlayOpenReason) => void;\n  /** Callback when resolved placement changes (useful for CSS attribute). */\n  onPlacementChange?: (placement: Placement) => void;\n  /** Controlled open prop. When defined, disables uncontrolled logic. */\n  openProp: Readable<boolean | undefined>;\n  /** Preferred placement. Default: 'bottom'. */\n  placement: Readable<Placement>;\n  /** Slot element or a factory to lazily find it. Resolved each time bindEvents runs. */\n  slot: HTMLSlotElement | null | (() => HTMLSlotElement | null);\n  /** Slot elements signal — used to rebind when slotted elements change. */\n  slotElements: Readable<Element[]>;\n  /** Which triggers are active. Set to empty array or omit if handling events manually. */\n  triggers: Readable<FloatingTriggerType[]>;\n};\n\nexport type FloatingTriggerHandle = {\n  /** Closes the panel. No-op if controlled. */\n  close: (reason?: DialogCloseReason) => void;\n  /**\n   * Call inside `onMounted`. Sets up slot + trigger event watchers\n   * and returns a cleanup function to pass back to the framework.\n   */\n  mount: () => () => void;\n  /** Opens the panel. No-op if controlled. */\n  open: (reason?: OverlayOpenReason) => void;\n  /** Toggles the panel. No-op if controlled. */\n  toggle: () => void;\n  /** Manually trigger a position recalculation. */\n  updatePosition: () => void;\n  /** Whether the panel is currently visible. */\n  visible: Signal<boolean>;\n};\n\n// ── Implementation ─────────────────────────────────────────────────────────────\n\n/**\n * Shared logic for floating-panel triggers (tooltip, popover).\n *\n * Handles:\n * - Slot-based trigger element detection and rebinding\n * - Event binding per trigger type (hover / focus / click)\n * - Floating-element positioning via `@vielzeug/orbit`\n * - Controlled mode via `openProp` watcher\n * - Keyboard escape dismissal\n *\n * ARIA binding is intentionally delegated to the caller via `bindTriggerAria`\n * so that tooltip (`describedby`) and popover (`expanded`/`controls`/…) can diverge.\n */\nexport const useFloatingTrigger = (options: FloatingTriggerOptions): FloatingTriggerHandle => {\n  const {\n    bindTriggerAria,\n    defaultOpen,\n    disabled,\n    getPanel,\n    onClose,\n    onOpen,\n    onPlacementChange,\n    openProp,\n    placement,\n    slot,\n    slotElements,\n    triggers,\n  } = options;\n\n  const resolveSlot = (): HTMLSlotElement | null => (typeof slot === 'function' ? slot() : slot);\n  const getOffset = (): number => {\n    const o = options.offset;\n\n    if (o == null) return 8;\n\n    const val = typeof o === 'object' ? (o as Readable<number | undefined>).value : o;\n\n    return val ?? 8;\n  };\n  const abortSignal = lifecycleSignal(options.onCleanup);\n  const visible = signal(false);\n  const isControlled = () => openProp.value !== undefined;\n  let currentTrigger: HTMLElement | null = null;\n  let triggerBinding: (() => void) | null = null;\n\n  function updatePosition(): void {\n    const panel = getPanel();\n    const trigger =\n      currentTrigger ?? (resolveSlot()?.assignedElements({ flatten: true })[0] as HTMLElement | undefined);\n\n    if (!panel || !trigger) return;\n\n    const previousTransition = panel.style.transition;\n    const previousTransform = panel.style.transform;\n\n    panel.style.transition = 'none';\n    panel.style.transform = 'none';\n\n    const result = computePosition(trigger, panel, {\n      middleware: [offset(getOffset()), flip(), shift({ padding: 8 })],\n      placement: placement.value,\n    });\n\n    panel.style.left = `${result.x}px`;\n    panel.style.top = `${result.y}px`;\n    panel.style.transition = previousTransition;\n    panel.style.transform = previousTransform;\n    panel.dataset.placement = result.placement;\n\n    onPlacementChange?.(result.placement as Placement);\n  }\n\n  function showFloat(): void {\n    visible.value = true;\n\n    const panel = getPanel();\n\n    if (panel) {\n      panel.dataset.open = '';\n\n      if ('showPopover' in panel) {\n        try {\n          if (!panel.matches(':popover-open')) panel.showPopover();\n        } catch {\n          // Popover API restricted (e.g. sandboxed iframe); data-open CSS fallback handles visibility.\n        }\n      }\n    }\n\n    updatePosition();\n  }\n\n  function hideFloat(): void {\n    visible.value = false;\n\n    const panel = getPanel();\n\n    if (panel) {\n      delete panel.dataset.open;\n\n      if ('hidePopover' in panel) {\n        try {\n          if (panel.matches(':popover-open')) panel.hidePopover();\n        } catch {\n          // Popover API restricted; data-open removal handles visibility.\n        }\n      }\n    }\n  }\n\n  function open(reason: OverlayOpenReason = 'programmatic'): void {\n    if (isControlled() || disabled.value || visible.value) return;\n\n    showFloat();\n    onOpen?.(reason);\n  }\n\n  function close(reason: DialogCloseReason = 'trigger'): void {\n    if (isControlled() || !visible.value) return;\n\n    hideFloat();\n    onClose?.(reason);\n  }\n\n  function toggle(): void {\n    if (isControlled()) return;\n\n    if (visible.value) close();\n    else open('click');\n  }\n\n  function handleKeydown(e: KeyboardEvent): void {\n    if (e.key === 'Escape') close('escape');\n  }\n\n  function handleFocusOut(e: FocusEvent): void {\n    const next = e.relatedTarget as Element | null;\n    const panel = getPanel();\n\n    if (next && panel?.contains(next)) return;\n\n    if (next && currentTrigger?.contains(next)) return;\n\n    close('trigger');\n  }\n\n  const bindEvents = (): void => {\n    triggerBinding?.();\n    triggerBinding = null;\n\n    const triggerEl = resolveSlot()?.assignedElements({ flatten: true })[0] as HTMLElement | undefined;\n\n    if (!triggerEl) {\n      currentTrigger = null;\n\n      return;\n    }\n\n    currentTrigger = triggerEl;\n\n    const removeAria = bindTriggerAria(triggerEl);\n    const cleanups: Array<() => void> = [];\n\n    const add = (\n      target: EventTarget,\n      event: string,\n      listener: EventListener,\n      eventOptions?: AddEventListenerOptions,\n    ): void => {\n      target.addEventListener(event, listener, eventOptions);\n      cleanups.push(() => target.removeEventListener(event, listener, eventOptions));\n    };\n\n    const t = triggers.value;\n\n    if (t.includes('click')) {\n      add(triggerEl, 'click', toggle as EventListener);\n    }\n\n    if (t.includes('hover')) {\n      add(triggerEl, 'pointerenter', () => open('hover'));\n      add(triggerEl, 'pointerleave', () => close('trigger'));\n\n      const panelEl = getPanel();\n\n      if (panelEl) {\n        add(panelEl, 'pointerenter', () => open('hover'));\n        add(panelEl, 'pointerleave', () => close('trigger'));\n      }\n    }\n\n    if (t.includes('focus')) {\n      add(triggerEl, 'focusin', () => open('focus'));\n      add(triggerEl, 'focusout', handleFocusOut as EventListener);\n\n      const panelEl = getPanel();\n\n      if (panelEl) add(panelEl, 'focusout', handleFocusOut as EventListener);\n    }\n\n    add(document, 'keydown', handleKeydown as EventListener);\n\n    triggerBinding = () => {\n      removeAria();\n\n      for (const cleanup of cleanups) cleanup();\n\n      currentTrigger = null;\n    };\n  };\n\n  const stopOutsidePointerDismissal = createOutsidePointerDismissal({\n    getTargets: () => [currentTrigger, getPanel(), options.getHost?.()],\n    isActive: () => visible.value && triggers.value.includes('click'),\n    onDismiss: () => close('outsideClick'),\n    signal: abortSignal,\n  });\n\n  const mount = (): (() => void) => {\n    let initializedOpenProp = false;\n    const triggerSlot = resolveSlot();\n    const rebind = (): void => {\n      queueMicrotask(() => {\n        if (!abortSignal.aborted) bindEvents();\n      });\n    };\n\n    triggerSlot?.addEventListener('slotchange', rebind);\n    rebind();\n    watch(slotElements, bindEvents, { immediate: true });\n\n    watch(\n      openProp,\n      (openVal) => {\n        if (openVal === undefined || openVal === null) {\n          if (initializedOpenProp && visible.value) {\n            hideFloat();\n            onClose?.('programmatic');\n          }\n\n          initializedOpenProp = true;\n\n          return;\n        }\n\n        initializedOpenProp = true;\n\n        if (openVal) {\n          showFloat();\n          onOpen?.('programmatic');\n        } else if (visible.value) {\n          hideFloat();\n          onClose?.('programmatic');\n        }\n      },\n      { immediate: true },\n    );\n\n    if (openProp.value === undefined && defaultOpen.value) {\n      showFloat();\n      onOpen?.('programmatic');\n    }\n\n    watch(disabled, (isNowDisabled) => {\n      if (isNowDisabled) close('programmatic');\n    });\n\n    return () => {\n      triggerSlot?.removeEventListener('slotchange', rebind);\n      triggerBinding?.();\n      triggerBinding = null;\n\n      const panel = getPanel();\n\n      if (panel) {\n        delete panel.dataset.open;\n\n        if ('hidePopover' in panel) {\n          try {\n            if (panel.matches(':popover-open')) panel.hidePopover();\n          } catch {\n            // Popover API restricted; data-open removal handles visibility.\n          }\n        }\n      }\n\n      stopOutsidePointerDismissal();\n    };\n  };\n\n  return { close, mount, open, toggle, updatePosition, visible };\n};\n"],"mappings":"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"}