{"version":3,"file":"tooltip.cjs","names":[],"sources":["../src/overlay/tooltip/tooltip.ts"],"sourcesContent":["import type { Placement } from '@vielzeug/orbit';\n\nimport {\n  bind,\n  createStableId,\n  define,\n  getHost,\n  html,\n  onCleanup,\n  onMounted,\n  prop,\n  useEmit,\n  useSlots,\n} from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { type OverlayOpenChangeDetail, type OverlayOpenReason, parseStringTriggers } from '../../core';\nimport { disablableBundle, sizableBundle } from '../../shared';\nimport { forcedColorsMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport { useFloatingTrigger } from '../shared/use-floating-trigger';\nimport styles from './tooltip.css?inline';\n\ntype TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';\ntype TooltipTrigger = 'click' | 'focus' | 'hover';\n\nconst LEFT_GAP_COMPENSATION = 4;\nconst DEFAULT_TOOLTIP_TRIGGERS: TooltipTrigger[] = ['hover', 'focus'];\nconst VALID_TOOLTIP_TRIGGERS = new Set<TooltipTrigger>(['click', 'focus', 'hover']);\n\nconst parseDelayMs = (value: string | null): number => {\n  if (value == null || value.trim() === '') return 0;\n\n  const parsed = Number(value);\n\n  return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;\n};\n\nconst parseOptionalBool = (value: string | null): boolean | undefined =>\n  value == null ? undefined : value === '' || value === 'true';\n\nconst normalizeTriggers = (value: string | null | undefined): TooltipTrigger[] =>\n  parseStringTriggers(value, VALID_TOOLTIP_TRIGGERS, DEFAULT_TOOLTIP_TRIGGERS);\n\n/** Tooltip component properties */\nexport type OreTooltipEvents = {\n  'open-change': OverlayOpenChangeDetail;\n};\n\nexport type OreTooltipProps = {\n  /** Hide delay in ms */\n  'close-delay'?: number;\n  /** Tooltip text content */\n  content?: string;\n  /** Initial uncontrolled open state. Ignored when `open` is set. */\n  'default-open'?: boolean;\n  /** Show delay in ms */\n  delay?: number;\n  /** Disable the tooltip */\n  disabled?: boolean;\n  /** Controlled open state */\n  open?: boolean;\n  /** Preferred placement relative to trigger */\n  placement?: TooltipPlacement;\n  /** Tooltip size */\n  size?: ComponentSize;\n  /** Which trigger(s) show/hide the tooltip */\n  trigger?: string;\n  /** Visual variant */\n  variant?: 'dark' | 'light';\n};\n\n/**\n * A lightweight tooltip shown on hover/focus/click relative to the slotted trigger.\n *\n * @element ore-tooltip\n *\n * @attr {string} content - Tooltip text content\n * @attr {string} placement - 'top' | 'bottom' | 'left' | 'right' (default: 'top')\n * @attr {string} trigger - 'hover' | 'focus' | 'click' or comma-separated combination\n * @attr {number} delay - Show delay in milliseconds (default: 0)\n * @attr {boolean} open - Controlled open state\n * @attr {boolean} default-open - Initial uncontrolled open state\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - 'dark' (default) | 'light'\n * @attr {boolean} disabled - Disable the tooltip\n *\n * @slot - Trigger element that the tooltip is anchored to\n * @slot content - Complex tooltip content (overrides the `content` attribute)\n * @fires open-change - Fired when the tooltip state changes. detail: { open, reason }\n *\n * @cssprop --tooltip-max-width - Max width of the tooltip bubble\n *\n * @part tooltip - Tooltip container.\n * @example\n * ```html\n * <!-- Simple text tooltip -->\n * <ore-tooltip content=\"Copy to clipboard\">\n *   <button>Copy</button>\n * </ore-tooltip>\n *\n * <!-- Placement and delay -->\n * <ore-tooltip content=\"Save your work\" placement=\"right\" delay=\"300\">\n *   <ore-icon name=\"save\"></ore-icon>\n * </ore-tooltip>\n *\n * <!-- Hover + focus trigger -->\n * <ore-tooltip content=\"Required field\" trigger=\"hover,focus\" placement=\"top\">\n *   <ore-input label=\"Email\" type=\"email\"></ore-input>\n * </ore-tooltip>\n *\n * <!-- Rich content slot -->\n * <ore-tooltip>\n *   <ore-button>Help</ore-button>\n *   <div slot=\"content\">\n *     <strong>Keyboard shortcuts</strong>\n *     <p>Press Ctrl+S to save.</p>\n *   </div>\n * </ore-tooltip>\n * ```\n */\nexport const TOOLTIP_TAG = 'ore-tooltip' as const;\ndefine<OreTooltipProps>(TOOLTIP_TAG, {\n  props: {\n    ...sizableBundle,\n    ...disablableBundle,\n    'close-delay': { default: 0, parse: parseDelayMs },\n    content: prop.string(),\n    'default-open': prop.bool(false),\n    delay: { default: 0, parse: parseDelayMs },\n    open: { default: undefined as boolean | undefined, parse: parseOptionalBool },\n    placement: prop.oneOf(['top', 'bottom', 'left', 'right'] as const, 'top'),\n    trigger: prop.string('hover,focus'),\n    variant: prop.string<'dark' | 'light'>(),\n  },\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreTooltipEvents>();\n    const slots = useSlots();\n\n    const shadowRoot = el.shadowRoot;\n    const isDisabled = computed(() => Boolean(props.disabled.value));\n    const isControlled = computed(() => props.open.value !== undefined);\n    const activePlacement = signal<TooltipPlacement>('top');\n    const tooltipId = createStableId('tooltip');\n    const triggers = computed<TooltipTrigger[]>(() => normalizeTriggers(props.trigger.value));\n    let tooltipEl: HTMLElement | null = null;\n    let showTimer: ReturnType<typeof setTimeout> | null = null;\n    let hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n    const clearShowTimer = (): void => {\n      if (!showTimer) return;\n\n      clearTimeout(showTimer);\n      showTimer = null;\n    };\n    const clearHideTimer = (): void => {\n      if (!hideTimer) return;\n\n      clearTimeout(hideTimer);\n      hideTimer = null;\n    };\n\n    const floating = useFloatingTrigger({\n      bindTriggerAria: (triggerEl) => bind({ aria: { describedby: () => tooltipId } }, { target: triggerEl }),\n      defaultOpen: props['default-open'],\n      disabled: isDisabled,\n      getPanel: () => tooltipEl,\n      offset: 8,\n      onCleanup,\n      onClose: (reason) => emit('open-change', { open: false, reason }),\n      onOpen: (reason) => emit('open-change', { open: true, reason }),\n      onPlacementChange: (p) => {\n        const side = p.split('-')[0] as TooltipPlacement;\n\n        activePlacement.value = side;\n\n        if (side === 'left' && tooltipEl) {\n          tooltipEl.style.left = `${parseFloat(tooltipEl.style.left) - LEFT_GAP_COMPENSATION}px`;\n        }\n      },\n      openProp: props.open as typeof props.open & { value: boolean | undefined },\n      placement: computed(() => props.placement.value as Placement),\n      slot: () => shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])') ?? null,\n      slotElements: slots.elements(),\n      triggers: computed(() => [] as TooltipTrigger[]),\n    });\n\n    function show(reason: OverlayOpenReason = 'hover'): void {\n      if (isControlled.value) return;\n\n      if (isDisabled.value || (!props.content.value && !slots.has('content').value)) return;\n\n      clearHideTimer();\n      clearShowTimer();\n\n      const delay = Number(props.delay.value) || 0;\n\n      if (delay > 0) {\n        showTimer = setTimeout(() => {\n          showTimer = null;\n          floating.open(reason);\n          floating.updatePosition();\n        }, delay);\n      } else {\n        floating.open(reason);\n        floating.updatePosition();\n      }\n    }\n\n    function hide(): void {\n      if (isControlled.value) return;\n\n      clearShowTimer();\n\n      const closeDelay = Number(props['close-delay'].value) || 0;\n\n      if (closeDelay > 0) {\n        clearHideTimer();\n        hideTimer = setTimeout(() => {\n          hideTimer = null;\n          floating.close('trigger');\n        }, closeDelay);\n      } else {\n        floating.close('trigger');\n      }\n    }\n\n    onMounted(() => {\n      // Manually bind trigger events so we can wrap show/hide with delay timers.\n      // useFloatingTrigger handles ARIA, positioning, controlled mode, and keyboard dismiss.\n      const triggerSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n      let eventCleanups: Array<() => void> = [];\n\n      const addEvent = (target: EventTarget, event: string, handler: EventListener): void => {\n        target.addEventListener(event, handler);\n        eventCleanups.push(() => target.removeEventListener(event, handler));\n      };\n\n      const bindTriggerEvents = (): void => {\n        for (const cleanup of eventCleanups) cleanup();\n        eventCleanups = [];\n\n        const triggerEl = triggerSlot?.assignedElements({ flatten: true })[0] as HTMLElement | undefined;\n\n        if (!triggerEl) return;\n\n        const t = triggers.value;\n\n        if (t.includes('hover')) {\n          addEvent(triggerEl, 'pointerenter', () => show('hover'));\n          addEvent(triggerEl, 'pointerleave', hide);\n        }\n\n        if (t.includes('focus')) {\n          addEvent(triggerEl, 'focusin', () => show('focus'));\n          addEvent(triggerEl, 'focusout', hide);\n        }\n\n        if (t.includes('click')) {\n          addEvent(triggerEl, 'click', () => (floating.visible.value ? hide() : show('click')));\n        }\n      };\n\n      triggerSlot?.addEventListener('slotchange', bindTriggerEvents);\n      bindTriggerEvents();\n\n      const destroyFloating = floating.mount();\n\n      return () => {\n        triggerSlot?.removeEventListener('slotchange', bindTriggerEvents);\n        for (const cleanup of eventCleanups) cleanup();\n        clearShowTimer();\n        clearHideTimer();\n        destroyFloating();\n      };\n    });\n\n    return html`\n      <slot></slot>\n      <div\n        class=\"tooltip\"\n        part=\"tooltip\"\n        id=\"${tooltipId}\"\n        role=\"tooltip\"\n        popover=\"manual\"\n        ref=${(ref: HTMLElement) => {\n          tooltipEl = ref;\n        }}\n        data-placement=\"${activePlacement}\"\n        aria-hidden=\"${() => String(!floating.visible.value)}\">\n        <slot name=\"content\"><span class=\"tooltip-text\">${props.content}</span></slot>\n      </div>\n    `;\n  },\n  styles: [forcedColorsMixin, styles],\n});\n"],"mappings":"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"}