{"version":3,"file":"useTooltip.cjs","names":[],"sources":["../../../src/components/Tooltip/useTooltip.ts"],"sourcesContent":["/*\n * Copyright 2024 New Vector Ltd.\n *\n * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\n * Please see LICENSE files in the repository root for full details.\n */\n\nimport {\n  arrow,\n  autoUpdate,\n  flip,\n  offset,\n  type OpenChangeReason,\n  type Placement,\n  shift,\n  useDelayGroup,\n  useDismiss,\n  useFloating,\n  useFocus,\n  useHover,\n  useId,\n  useInteractions,\n  useRole,\n} from \"@floating-ui/react\";\nimport {\n  useMemo,\n  useRef,\n  useState,\n  type JSX,\n  type AriaAttributes,\n  useEffect,\n} from \"react\";\nimport { hoverDelay } from \"./TooltipProvider\";\n\nexport interface CommonUseTooltipProps {\n  /**\n   * The controlled open state of the tooltip.\n   * If provided, the tooltip will be in controlled mode.\n   * When true, the tooltip is always open. When false, the tooltip is always hidden.\n   * When undefined, the tooltip will manage its own open state.\n   * You will mostly want to omit this property. Will be used the vast majority\n   * of the time during development.\n   * @default undefined\n   */\n  open?: boolean;\n  /**\n   * Whether the tooltip should be forced to be in a closed state.\n   */\n  // TODO: Deprecate this? It seems redundant to open: false.\n  disabled?: boolean;\n  /**\n   * The caption of the tooltip.\n   * JSX.Element can be used to provide accessibility content like kbd element.\n   * Keep in mind, the caption should not be used for interactive content.\n   */\n  caption?: string | JSX.Element;\n  /**\n   * The event handler for the open change.\n   */\n  onOpenChange?: (\n    open: boolean,\n    event?: Event | undefined,\n    reason?: OpenChangeReason | undefined,\n  ) => void;\n  /**\n   * The placement of the tooltip.\n   * @default \"bottom\"\n   */\n  placement?: Placement;\n  /**\n   * Whether the trigger element is interactive.\n   * When trigger is interactive:\n   *      - tooltip will be shown after a 300ms delay.\n   * When trigger is not interactive:\n   *      - tooltip will be shown instantly when pointer enters trigger.\n   *      - trigger will be wrapped in a span with a tab index from prop nonInteractiveTriggerTabIndex\n   */\n  isTriggerInteractive: boolean;\n\n  /**\n   * Additional aria-* attributes to pass through to the floating tooltip for\n   * edge cases which require more user awareness like errors & alerts.\n   */\n  \"aria-atomic\"?: AriaAttributes[\"aria-atomic\"];\n  \"aria-live\"?: AriaAttributes[\"aria-live\"];\n}\n\nexport interface TooltipLabel {\n  /**\n   * A label for the target element.\n   */\n  label: string;\n}\n\nexport interface TooltipDescription {\n  /**\n   * A description for the target element.\n   */\n  description: string;\n}\n\ntype UseTooltipProps = CommonUseTooltipProps &\n  (TooltipLabel | TooltipDescription);\n\nexport function useTooltip({\n  open: controlledOpen,\n  disabled = false,\n  onOpenChange,\n  placement = \"bottom\",\n  isTriggerInteractive,\n  caption,\n  \"aria-atomic\": ariaAtomic,\n  \"aria-live\": ariaLive,\n  ...props\n}: UseTooltipProps) {\n  const labelId = useId();\n  const captionId = useId();\n  const arrowRef = useRef(null);\n\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n\n  // Use controlledOpen if it is provided, otherwise use uncontrolledOpen\n  const open = disabled ? false : (controlledOpen ?? uncontrolledOpen);\n  const setOpen = (\n    open: boolean,\n    event?: Event | undefined,\n    reason?: OpenChangeReason | undefined,\n  ) => {\n    onOpenChange?.(open, event, reason);\n    // we are in uncontrolled mode\n    if (controlledOpen === undefined) setUncontrolledOpen(open);\n  };\n\n  const data = useFloating({\n    placement,\n    open,\n    onOpenChange: setOpen,\n    whileElementsMounted: autoUpdate,\n    middleware: [\n      // arrow height 6px\n      offset(6),\n      flip({\n        crossAxis: placement.includes(\"-\"),\n        fallbackAxisSideDirection: \"start\",\n        padding: 5,\n      }),\n      shift({ padding: 5 }),\n      // add the little arrow along with the floating content\n      arrow({\n        element: arrowRef,\n      }),\n    ],\n  });\n\n  const context = data.context;\n  const { delay, initialDelay } = useDelayGroup(context);\n  // We can tell if no delay group has been provided, because the delay will\n  // default to zero\n  if (initialDelay !== hoverDelay)\n    throw new Error(\"Tooltips must be wrapped in a global <TooltipProvider>\");\n\n  const hover = useHover(context, {\n    move: false,\n    enabled: controlledOpen === undefined,\n    // Show tooltip after a delay when trigger is interactive\n    delay: isTriggerInteractive ? delay : {},\n    mouseOnly: true,\n  });\n\n  const focus = useFocus(context, {\n    enabled: controlledOpen === undefined,\n  });\n\n  // On touch screens, show the tooltip on a long press\n  const pressTimer = useRef<number | undefined>(undefined);\n  useEffect(() => () => window.clearTimeout(pressTimer.current), []);\n  const press = useMemo(() => {\n    const onTouchEnd = () => {\n      if (pressTimer.current === undefined)\n        pressTimer.current = window.setTimeout(() => {\n          setOpen(false);\n          pressTimer.current = undefined;\n        }, 1500);\n      else window.clearTimeout(pressTimer.current);\n    };\n    return {\n      // Set these props on the anchor element\n      reference: {\n        onTouchStart: () => {\n          if (pressTimer.current !== undefined)\n            window.clearTimeout(pressTimer.current);\n          pressTimer.current = window.setTimeout(() => {\n            setOpen(true);\n            pressTimer.current = undefined;\n          }, 500);\n        },\n        onTouchEnd,\n        onTouchCancel: onTouchEnd,\n      },\n    };\n  }, []);\n\n  const dismiss = useDismiss(context);\n\n  const purpose = \"label\" in props ? \"label\" : \"description\";\n  // A descriptive tooltip should set role=\"tooltip\" and aria-describedby\n  const role = useRole(context, {\n    enabled: purpose === \"description\",\n    role: \"tooltip\",\n  });\n\n  // A label tooltip should set aria-labelledby with no role regardless of\n  // whether the tooltip is visible.\n  // (Source: https://zoebijl.github.io/apg-tooltip/#tooltip-main-label)\n  // useRole doesn't support this use case correctly, so we do it manually.\n  const label = useMemo(\n    () =>\n      purpose === \"label\"\n        ? {\n            // Set these props on the anchor element\n            reference: {\n              \"aria-labelledby\": labelId,\n              \"aria-describedby\": caption ? captionId : undefined,\n            },\n          }\n        : {},\n    [purpose, labelId, captionId],\n  );\n\n  const interactions = useInteractions([\n    hover,\n    focus,\n    press,\n    dismiss,\n    role,\n    label,\n  ]);\n\n  return useMemo(\n    () => ({\n      labelId,\n      captionId: caption ? captionId : undefined,\n      caption,\n      purpose: purpose as \"label\" | \"description\",\n      open,\n      setOpen,\n      tooltipProps: {\n        \"aria-atomic\": ariaAtomic,\n        \"aria-live\": ariaLive,\n      },\n      ...interactions,\n      ...data,\n      arrowRef,\n    }),\n    [\n      labelId,\n      captionId,\n      caption,\n      role,\n      open,\n      setOpen,\n      interactions,\n      data,\n      arrowRef,\n    ],\n  );\n}\n"],"mappings":";;;;;AAwGA,SAAgB,WAAW,EACzB,MAAM,gBACN,WAAW,OACX,cACA,YAAY,UACZ,sBACA,SACA,eAAe,YACf,aAAa,UACb,GAAG,SACe;CAClB,MAAM,WAAA,GAAA,mBAAA,QAAiB;CACvB,MAAM,aAAA,GAAA,mBAAA,QAAmB;CACzB,MAAM,YAAA,GAAA,MAAA,QAAkB,KAAK;CAE7B,MAAM,CAAC,kBAAkB,wBAAA,GAAA,MAAA,UAAgC,MAAM;CAG/D,MAAM,OAAO,WAAW,QAAS,kBAAkB;CACnD,MAAM,WACJ,MACA,OACA,WACG;AACH,iBAAe,MAAM,OAAO,OAAO;AAEnC,MAAI,mBAAmB,KAAA,EAAW,qBAAoB,KAAK;;CAG7D,MAAM,QAAA,GAAA,mBAAA,aAAmB;EACvB;EACA;EACA,cAAc;EACd,sBAAsB,mBAAA;EACtB,YAAY;kCAEH,EAAE;gCACJ;IACH,WAAW,UAAU,SAAS,IAAI;IAClC,2BAA2B;IAC3B,SAAS;IACV,CAAC;iCACI,EAAE,SAAS,GAAG,CAAC;iCAEf,EACJ,SAAS,UACV,CAAC;GACH;EACF,CAAC;CAEF,MAAM,UAAU,KAAK;CACrB,MAAM,EAAE,OAAO,kBAAA,GAAA,mBAAA,eAA+B,QAAQ;AAGtD,KAAI,iBAAiB,wBAAA,WACnB,OAAM,IAAI,MAAM,yDAAyD;CAE3E,MAAM,SAAA,GAAA,mBAAA,UAAiB,SAAS;EAC9B,MAAM;EACN,SAAS,mBAAmB,KAAA;EAE5B,OAAO,uBAAuB,QAAQ,EAAE;EACxC,WAAW;EACZ,CAAC;CAEF,MAAM,SAAA,GAAA,mBAAA,UAAiB,SAAS,EAC9B,SAAS,mBAAmB,KAAA,GAC7B,CAAC;CAGF,MAAM,cAAA,GAAA,MAAA,QAAwC,KAAA,EAAU;AACxD,EAAA,GAAA,MAAA,uBAAsB,OAAO,aAAa,WAAW,QAAQ,EAAE,EAAE,CAAC;CAClE,MAAM,SAAA,GAAA,MAAA,eAAsB;EAC1B,MAAM,mBAAmB;AACvB,OAAI,WAAW,YAAY,KAAA,EACzB,YAAW,UAAU,OAAO,iBAAiB;AAC3C,YAAQ,MAAM;AACd,eAAW,UAAU,KAAA;MACpB,KAAK;OACL,QAAO,aAAa,WAAW,QAAQ;;AAE9C,SAAO,EAEL,WAAW;GACT,oBAAoB;AAClB,QAAI,WAAW,YAAY,KAAA,EACzB,QAAO,aAAa,WAAW,QAAQ;AACzC,eAAW,UAAU,OAAO,iBAAiB;AAC3C,aAAQ,KAAK;AACb,gBAAW,UAAU,KAAA;OACpB,IAAI;;GAET;GACA,eAAe;GAChB,EACF;IACA,EAAE,CAAC;CAEN,MAAM,WAAA,GAAA,mBAAA,YAAqB,QAAQ;CAEnC,MAAM,UAAU,WAAW,QAAQ,UAAU;CAE7C,MAAM,QAAA,GAAA,mBAAA,SAAe,SAAS;EAC5B,SAAS,YAAY;EACrB,MAAM;EACP,CAAC;CAoBF,MAAM,gBAAA,GAAA,mBAAA,iBAA+B;EACnC;EACA;EACA;EACA;EACA;2BAjBE,YAAY,UACR,EAEE,WAAW;GACT,mBAAmB;GACnB,oBAAoB,UAAU,YAAY,KAAA;GAC3C,EACF,GACD,EAAE,EACR;GAAC;GAAS;GAAS;GAAU,CAC9B;EASA,CAAC;AAEF,SAAA,GAAA,MAAA,gBACS;EACL;EACA,WAAW,UAAU,YAAY,KAAA;EACjC;EACS;EACT;EACA;EACA,cAAc;GACZ,eAAe;GACf,aAAa;GACd;EACD,GAAG;EACH,GAAG;EACH;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF"}