{"version":3,"file":"Tooltip.cjs","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["'use client'\n\nimport {\n  type BaseSyntheticEvent,\n  type ComponentProps,\n  type FC,\n  type FocusEvent,\n  type PointerEvent,\n  type PropsWithChildren,\n  type ReactElement,\n  type ReactNode,\n  type TouchEvent,\n  cloneElement,\n  memo,\n  useCallback,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { TooltipPortal } from './TooltipPortal'\n\nconst subscribeFullscreenChange = (callback: () => void) => {\n  window.addEventListener('fullscreenchange', callback)\n\n  return () => {\n    window.removeEventListener('fullscreenchange', callback)\n  }\n}\nconst getFullscreenElement = () => document.fullscreenElement\nconst getFullscreenElementOnSSR = () => null\n\ntype AbstractProps = PropsWithChildren<{\n  /** ツールチップ内に表示するメッセージ */\n  message: ReactNode\n  /** ツールチップを表示する対象のタイプ。アイコンの場合は `icon` を指定する */\n  triggerType?: 'icon' | 'text'\n  /** `true` のとき、ツールチップを表示する対象が省略されている場合のみツールチップ表示を有効にする */\n  ellipsisOnly?: boolean\n  /** ツールチップを表示する対象の tabIndex 値 */\n  tabIndex?: number\n  /** ツールチップを内包要素に紐付けるかどうか */\n  ariaDescribedbyTarget?: 'wrapper' | 'inner'\n}>\ntype Props = AbstractProps & Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby'>\n\nconst classNameGenerator = tv({\n  base: [\n    'smarthr-ui-Tooltip',\n    'shr-relative',\n    'shr-inline-block shr-max-w-full shr-align-bottom',\n    'focus-visible:shr-focus-indicator--outer',\n  ],\n  variants: {\n    isIcon: {\n      true: 'shr-leading-[0]',\n    },\n  },\n})\n\nexport const Tooltip: FC<Props> = ({\n  message,\n  children,\n  triggerType,\n  ellipsisOnly,\n  tabIndex = 0,\n  ariaDescribedbyTarget = 'wrapper',\n  className,\n  onPointerEnter,\n  onPointerLeave,\n  onTouchStart,\n  onTouchEnd,\n  onFocus,\n  onBlur,\n  ...rest\n}) => {\n  const [portalRoot, setPortalRoot] = useState<Element | null>(null)\n  const [isVisible, setIsVisible] = useState(false)\n  const [rect, setRect] = useState<DOMRect | null>(null)\n  const ref = useRef<HTMLDivElement>(null)\n  const messageId = useId()\n  const fullscreenElement = useSyncExternalStore(\n    subscribeFullscreenChange,\n    getFullscreenElement,\n    getFullscreenElementOnSSR,\n  )\n\n  useEnhancedEffect(() => {\n    setPortalRoot(fullscreenElement ?? document.body)\n  }, [fullscreenElement])\n\n  const toShowAction = useCallback(\n    (e: BaseSyntheticEvent) => {\n      // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない\n      if (!ref.current?.contains(e.target)) {\n        return\n      }\n\n      if (ellipsisOnly) {\n        const outerWidth = parseInt(\n          window\n            .getComputedStyle(ref.current.parentNode! as HTMLElement, null)\n            .width.match(/\\d+/)![0],\n          10,\n        )\n\n        if (outerWidth < 0 || outerWidth > ref.current.clientWidth) {\n          return\n        }\n      }\n\n      setRect(ref.current.getBoundingClientRect())\n      setIsVisible(true)\n    },\n    [ellipsisOnly],\n  )\n  const onDelegatePointerEnter = useMemo(\n    () =>\n      onPointerEnter\n        ? (e: PointerEvent<HTMLSpanElement>) => {\n            onPointerEnter(e)\n            toShowAction(e)\n          }\n        : toShowAction,\n    [onPointerEnter, toShowAction],\n  )\n  const onDelegateTouchStart = useMemo(\n    () =>\n      onTouchStart\n        ? (e: TouchEvent<HTMLSpanElement>) => {\n            onTouchStart(e)\n            toShowAction(e)\n          }\n        : toShowAction,\n    [onTouchStart, toShowAction],\n  )\n  const onDelegateFocus = useMemo(\n    () =>\n      onFocus\n        ? (e: FocusEvent<HTMLSpanElement>) => {\n            onFocus(e)\n            toShowAction(e)\n          }\n        : toShowAction,\n    [onFocus, toShowAction],\n  )\n\n  const toCloseAction = useCallback(() => setIsVisible(false), [])\n  const onDelegatePointerLeave = useMemo(\n    () =>\n      onPointerLeave\n        ? (e: PointerEvent<HTMLSpanElement>) => {\n            onPointerLeave(e)\n            toCloseAction()\n          }\n        : toCloseAction,\n    [onPointerLeave, toCloseAction],\n  )\n  const onDelegateTouchEnd = useMemo(\n    () =>\n      onTouchEnd\n        ? (e: TouchEvent<HTMLSpanElement>) => {\n            onTouchEnd(e)\n            toCloseAction()\n          }\n        : toCloseAction,\n    [onTouchEnd, toCloseAction],\n  )\n  const onDelegateBlur = useMemo(\n    () =>\n      onBlur\n        ? (e: FocusEvent<HTMLSpanElement>) => {\n            onBlur(e)\n            toCloseAction()\n          }\n        : toCloseAction,\n    [onBlur, toCloseAction],\n  )\n\n  const isIcon = triggerType === 'icon'\n  const actualClassName = useMemo(\n    () => classNameGenerator({ isIcon, className }),\n    [isIcon, className],\n  )\n  const isInnerTarget = ariaDescribedbyTarget === 'inner'\n  const childrenWithProps = useMemo(\n    () =>\n      isInnerTarget\n        ? cloneElement(children as ReactElement, { 'aria-describedby': messageId })\n        : children,\n    [children, isInnerTarget, messageId],\n  )\n\n  return (\n    // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n    <span\n      {...rest}\n      ref={ref}\n      tabIndex={tabIndex}\n      aria-describedby={isInnerTarget ? undefined : messageId}\n      onPointerEnter={onDelegatePointerEnter}\n      onTouchStart={onDelegateTouchStart}\n      onFocus={onDelegateFocus}\n      onPointerLeave={onDelegatePointerLeave}\n      onTouchEnd={onDelegateTouchEnd}\n      onBlur={onDelegateBlur}\n      className={actualClassName}\n    >\n      {portalRoot &&\n        createPortal(\n          <TooltipPortal\n            message={message}\n            isVisible={isVisible}\n            parentRect={rect}\n            isIcon={isIcon}\n          />,\n          portalRoot,\n        )}\n      {childrenWithProps}\n      <MemoizedVisuallyHiddenText id={messageId} visible={isVisible}>\n        {message}\n      </MemoizedVisuallyHiddenText>\n    </span>\n  )\n}\n\nconst MemoizedVisuallyHiddenText = memo<PropsWithChildren<{ id: string; visible: boolean }>>(\n  ({ id, visible, children }) => {\n    const hiddenText = useMemo(() => innerText(children), [children])\n\n    return (\n      <VisuallyHiddenText id={id} aria-hidden={!visible}>\n        {hiddenText}\n      </VisuallyHiddenText>\n    )\n  },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;AA8BA;AACE;AAEA;AACE;AACF;AACF;AACA;AACA;AAgBA;AACE;;;;;AAKC;AACD;AACE;AACE;AACD;AACF;AACF;AAEM;;;;AAmBL;AACA;;;AAQE;AACF;AAEA;;AAGI;;;;AAKE;;;AAOA;;;;;;AAOJ;AAGF;AAGM;;;;;AAON;AAGM;;;;;AAON;AAGM;;;;;AAQN;AACA;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAMV;;AAKA;AACA;;;;;;AAwBM;AAeR;AAEA;AAEI;AAEA;AAKF;;"}