{"version":3,"file":"tooltip.mjs","names":[],"sources":["../../../../package/common/tooltip/tooltip.tsx"],"sourcesContent":["import React, { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\nimport { useLabels } from 'components/common/configProvider';\nimport { usePopoverPosition } from '../popover/usePopoverPosition';\nimport { PopoverAlign, PopoverPlacement } from '../popover/popover.types';\nimport { isSelfActivation } from '../utils/activationKey';\nimport styles from './tooltip.module.pcss';\nimport type { TooltipLabels, TooltipProps } from './tooltip.types';\n\n/** The gap the arrow sits in, between the bubble and the trigger it points at. */\nconst TOOLTIP_OFFSET = 6;\n\nconst DEFAULT_LABELS: Required<TooltipLabels> = { details: 'More information' };\n\nexport default function Tooltip({\n  variant,\n  message = '',\n  isVisible = true,\n  tooltipPosition,\n  onClick,\n  labels,\n}: TooltipProps) {\n  const [detailsHovered, setDetailsHovered] = useState(false);\n  const text = useLabels('Tooltip', labels, DEFAULT_LABELS);\n\n  const sentinelRef = useRef<HTMLSpanElement>(null);\n  const detailsRef = useRef<HTMLDivElement>(null);\n  const triggerRef = useRef<HTMLElement | null>(null);\n  const bubbleRef = useRef<HTMLDivElement>(null);\n  // The bubble draws its own arrow in CSS, pointed by `--tooltip-trigger-width`,\n  // rather than taking Popover's separate arrow element.\n  const arrowRef = useRef<HTMLElement | null>(null);\n  const [triggerWidth, setTriggerWidth] = useState(10);\n  const bubbleId = useId();\n\n  const shown = variant === 'details' ? detailsHovered : isVisible;\n\n  useLayoutEffect(() => {\n    triggerRef.current = variant === 'details' ? detailsRef.current : (sentinelRef.current?.parentElement ?? null);\n  });\n\n  // The trigger is whatever element the consumer happened to put the Tooltip inside, so the\n  // description can only be attached to it from here, imperatively. Removed again when the\n  // bubble goes, since aria-describedby pointing at a missing id reads as no description at all.\n  useEffect(() => {\n    const trigger = triggerRef.current;\n    if (!trigger || !shown) return;\n    trigger.setAttribute('aria-describedby', bubbleId);\n    return () => trigger.removeAttribute('aria-describedby');\n  }, [shown, bubbleId]);\n\n  useLayoutEffect(() => {\n    if (shown) {\n      const rect = triggerRef.current?.getBoundingClientRect();\n      setTriggerWidth(rect?.width ?? 10);\n    }\n  }, [shown]);\n\n  usePopoverPosition({\n    isOpen: shown,\n    placement: PopoverPlacement.Top,\n    // An error hangs off the trailing edge of the field it belongs to; the other\n    // two off the leading edge, which is where their arrow is drawn.\n    align: variant === 'error' ? PopoverAlign.End : PopoverAlign.Start,\n    offset: TOOLTIP_OFFSET,\n    triggerRef,\n    floatingRef: bubbleRef,\n    panelRef: bubbleRef,\n    arrowRef,\n    // A bubble that slid off its trigger's edge to stay on screen would leave its\n    // arrow pointing at nothing. Wrapping one more line keeps both.\n    shrinkToFit: true,\n  });\n\n  const bubble = shown && (\n    <BodyEnd>\n      <div\n        ref={bubbleRef}\n        id={bubbleId}\n        // An error appears in answer to something the user just did, so it has to be announced;\n        // the other two only describe what is already focused.\n        role={variant === 'error' ? 'alert' : 'tooltip'}\n        className={cx(variant === 'error' ? styles.tooltipError : styles.tooltipBubble, tooltipPosition)}\n        style={{ '--tooltip-trigger-width': `${triggerWidth}px` } as React.CSSProperties}\n        onClick={onClick}\n      >\n        {message}\n      </div>\n    </BodyEnd>\n  );\n\n  if (variant === 'details') {\n    return (\n      <div ref={detailsRef} className={styles.tooltipDetails}>\n        <span\n          className={styles.tooltipDetailsIcon}\n          role='button'\n          tabIndex={0}\n          aria-label={text.details}\n          aria-expanded={detailsHovered}\n          onMouseEnter={() => setDetailsHovered(true)}\n          onMouseLeave={() => setDetailsHovered(false)}\n          // Hover alone would leave the icon unreachable without a pointer, and it is the only\n          // way to read what it explains.\n          onFocus={() => setDetailsHovered(true)}\n          onBlur={() => setDetailsHovered(false)}\n          onKeyDown={(event) => {\n            if (!isSelfActivation(event)) return;\n            event.preventDefault();\n            setDetailsHovered((open) => !open);\n          }}\n        >\n          ?\n        </span>\n        {bubble}\n      </div>\n    );\n  }\n\n  if (variant === 'info' || variant === 'error') {\n    return (\n      <>\n        <span ref={sentinelRef} style={{ display: 'none' }} />\n        {bubble}\n      </>\n    );\n  }\n\n  return null;\n}\n"],"mappings":"giBAWA,IAAM,EAAiB,EAEjB,EAA0C,CAAE,QAAS,kBAAmB,EAE9E,SAAwB,EAAQ,CAC9B,UACA,UAAU,GACV,YAAY,GACZ,kBACA,UACA,UACe,CACf,GAAM,CAAC,EAAgB,GAAqB,EAAS,EAAK,EACpD,EAAO,EAAU,UAAW,EAAQ,CAAc,EAElD,EAAc,EAAwB,IAAI,EAC1C,EAAa,EAAuB,IAAI,EACxC,EAAa,EAA2B,IAAI,EAC5C,EAAY,EAAuB,IAAI,EAGvC,EAAW,EAA2B,IAAI,EAC1C,CAAC,EAAc,GAAmB,EAAS,EAAE,EAC7C,EAAW,EAAM,EAEjB,EAAQ,IAAY,UAAY,EAAiB,EAEvD,MAAsB,CACpB,EAAW,QAAU,IAAY,UAAY,EAAW,QAAW,EAAY,SAAS,eAAiB,IAC3G,CAAC,EAKD,MAAgB,CACd,IAAM,EAAU,EAAW,QACvB,GAAC,GAAY,EAEjB,OADA,EAAQ,aAAa,mBAAoB,CAAQ,MACpC,EAAQ,gBAAgB,kBAAkB,CACzD,EAAG,CAAC,EAAO,CAAQ,CAAC,EAEpB,MAAsB,CACpB,GAAI,EAAO,CACT,IAAM,EAAO,EAAW,SAAS,sBAAsB,EACvD,EAAgB,GAAM,OAAS,EAAE,CACnC,CACF,EAAG,CAAC,CAAK,CAAC,EAEV,EAAmB,CACjB,OAAQ,EACR,UAAW,EAAiB,IAG5B,MAAO,IAAY,QAAU,EAAa,IAAM,EAAa,MAC7D,OAAQ,EACR,aACA,YAAa,EACb,SAAU,EACV,WAGA,YAAa,EACf,CAAC,EAED,IAAM,EAAS,GACb,EAAC,EAAD,CAAA,SACE,EAAC,MAAD,CACE,IAAK,EACL,GAAI,EAGJ,KAAM,IAAY,QAAU,QAAU,UACtC,UAAW,EAAG,IAAY,QAAU,EAAO,aAAe,EAAO,cAAe,CAAe,EAC/F,MAAO,CAAE,0BAA2B,GAAG,EAAa,GAAI,EAC/C,UAER,SAAA,CACE,CAAA,CACE,CAAA,EAwCX,OArCI,IAAY,UAEZ,EAAC,MAAD,CAAK,IAAK,EAAY,UAAW,EAAO,eAAxC,SAAA,CACE,EAAC,OAAD,CACE,UAAW,EAAO,mBAClB,KAAK,SACL,SAAU,EACV,aAAY,EAAK,QACjB,gBAAe,EACf,iBAAoB,EAAkB,EAAI,EAC1C,iBAAoB,EAAkB,EAAK,EAG3C,YAAe,EAAkB,EAAI,EACrC,WAAc,EAAkB,EAAK,EACrC,UAAY,GAAU,CACf,EAAiB,CAAK,IAC3B,EAAM,eAAe,EACrB,EAAmB,GAAS,CAAC,CAAI,EACnC,EACD,SAAA,GAEK,CAAA,EACL,CACE,IAIL,IAAY,QAAU,IAAY,QAElC,EAAA,EAAA,CAAA,SAAA,CACE,EAAC,OAAD,CAAM,IAAK,EAAa,MAAO,CAAE,QAAS,MAAO,CAAI,CAAA,EACpD,CACD,CAAA,CAAA,EAIC,IACT"}