{"version":3,"file":"tour-overlay.cjs","sources":["../../../components/tour/tour-overlay.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'class-variance-authority';\nimport { type ComponentPropsWithoutRef, useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport styles from './tour.module.css';\nimport { useTourContext } from './tour-context';\nimport { usePrefersReducedMotion } from './use-prefers-reduced-motion';\nimport { rectsEqual, type SpotlightRect } from './utils';\n\nexport interface TourOverlayProps extends ComponentPropsWithoutRef<'div'> {\n  /**\n   * Space between the target and the spotlight edge in pixels; steps can\n   * override. @default 4\n   */\n  spotlightPadding?: number;\n  /** Spotlight corner radius in pixels; steps can override. @default 6 */\n  spotlightRadius?: number;\n  /**\n   * Allow pointer interaction with the spotlighted element; steps can\n   * override. @default false\n   */\n  spotlightClicks?: boolean;\n}\n\nfunction useSpotlightRect(element: Element | null): SpotlightRect | null {\n  const [rect, setRect] = useState<SpotlightRect | null>(null);\n\n  useEffect(() => {\n    if (!element) {\n      setRect(null);\n      return;\n    }\n    let frame = 0;\n    const track = () => {\n      if (element.isConnected) {\n        const next = element.getBoundingClientRect();\n        setRect(prev =>\n          prev && rectsEqual(prev, next)\n            ? prev\n            : { x: next.x, y: next.y, width: next.width, height: next.height }\n        );\n      }\n      frame = requestAnimationFrame(track);\n    };\n    track();\n    return () => cancelAnimationFrame(frame);\n  }, [element]);\n\n  return element ? rect : null;\n}\n\nfunction useSpotlightFade(\n  target: Element | null,\n  { fade, revealed }: { fade: boolean; revealed: boolean }\n) {\n  const [displayed, setDisplayed] = useState<Element | null>(target);\n\n  useEffect(() => {\n    if (!fade || target == null || revealed || displayed == null) {\n      setDisplayed(target);\n    }\n  }, [target, revealed, fade, displayed]);\n\n  const shown = fade\n    ? revealed && displayed === target && target != null\n    : true;\n  return { displayed: fade ? displayed : target, shown };\n}\n\nexport function TourOverlay({\n  spotlightPadding = 4,\n  spotlightRadius = 6,\n  spotlightClicks: spotlightClicksProp = false,\n  className,\n  ...rest\n}: TourOverlayProps) {\n  const {\n    open,\n    step,\n    status,\n    spotlightElement,\n    disableOverlay: disableOverlayTour,\n    revealed\n  } = useTourContext('Tour.Overlay');\n  const disabled = step?.disableOverlay ?? disableOverlayTour;\n  const reducedMotion = usePrefersReducedMotion();\n  const fade = !reducedMotion;\n\n  const targeted =\n    step != null && (step.target != null || step.spotlightTarget != null);\n\n  const { displayed, shown } = useSpotlightFade(\n    open && !disabled && targeted ? spotlightElement : null,\n    { fade, revealed }\n  );\n  const rect = useSpotlightRect(displayed);\n\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n\n  const [entered, setEntered] = useState(false);\n  useEffect(() => {\n    if (!open || disabled) {\n      setEntered(false);\n      return;\n    }\n    if (revealed) setEntered(true);\n  }, [open, disabled, revealed]);\n\n  if (!open || !mounted || disabled || !step) return null;\n  // Requiring the live `spotlightElement` prevents an orphaned dim after a target unmounts.\n  if (targeted && (!spotlightElement || !displayed || !rect)) return null;\n\n  const padding = step.spotlightPadding ?? spotlightPadding;\n  const radius = step.spotlightRadius ?? spotlightRadius;\n  const spotlightClicks = step.spotlightClicks ?? spotlightClicksProp;\n\n  const hole =\n    targeted && rect\n      ? {\n          x: rect.x - padding,\n          y: rect.y - padding,\n          width: rect.width + padding * 2,\n          height: rect.height + padding * 2\n        }\n      : null;\n\n  const holeOpen = fade ? shown : true;\n\n  return createPortal(\n    // `rest` spread first so consumers can't clobber the chrome attributes below.\n    <div\n      {...rest}\n      aria-hidden\n      data-slot='tour-overlay'\n      data-status={status}\n      data-entered={entered ? 'true' : 'false'}\n      data-hole-open={holeOpen ? 'true' : 'false'}\n      className={cx(styles.overlay, className)}\n    >\n      <div\n        data-slot='tour-spotlight'\n        className={styles.spotlight}\n        style={\n          hole\n            ? {\n                left: hole.x,\n                top: hole.y,\n                width: hole.width,\n                height: hole.height,\n                borderRadius: radius\n              }\n            : { left: '50%', top: '50%', width: 0, height: 0 }\n        }\n      />\n      {hole && (\n        <div\n          data-slot='tour-spotlight-cover'\n          className={styles.spotlightCover}\n          style={{\n            left: hole.x,\n            top: hole.y,\n            width: hole.width,\n            height: hole.height,\n            borderRadius: radius\n          }}\n        />\n      )}\n      {hole ? (\n        <>\n          {/* Hit strips block clicks around the hole; the center strip is\n              dropped when spotlightClicks lets clicks through. */}\n          <div\n            data-slot='tour-overlay-hit'\n            className={styles.overlayHit}\n            style={{ top: 0, left: 0, right: 0, height: Math.max(hole.y, 0) }}\n          />\n          <div\n            data-slot='tour-overlay-hit'\n            className={styles.overlayHit}\n            style={{\n              top: hole.y,\n              left: 0,\n              width: Math.max(hole.x, 0),\n              height: hole.height\n            }}\n          />\n          <div\n            data-slot='tour-overlay-hit'\n            className={styles.overlayHit}\n            style={{\n              top: hole.y,\n              left: hole.x + hole.width,\n              right: 0,\n              height: hole.height\n            }}\n          />\n          <div\n            data-slot='tour-overlay-hit'\n            className={styles.overlayHit}\n            style={{ top: hole.y + hole.height, left: 0, right: 0, bottom: 0 }}\n          />\n          {!(spotlightClicks && holeOpen) && (\n            <div\n              data-slot='tour-overlay-hit'\n              className={styles.overlayHit}\n              style={{\n                top: hole.y,\n                left: hole.x,\n                width: hole.width,\n                height: hole.height\n              }}\n            />\n          )}\n        </>\n      ) : (\n        <div\n          data-slot='tour-overlay-hit'\n          className={styles.overlayHit}\n          style={{ inset: 0 }}\n        />\n      )}\n    </div>,\n    document.body\n  );\n}\n\nTourOverlay.displayName = 'Tour.Overlay';\n"],"names":[],"mappings":";;;;;;;;;;;;AAyBA;;;;;;;;;AAUM;AACE;AACA;AAEI;;;AAIN;AACF;AACA;AACA;AACF;;AAGF;AAEA;;;AAOI;;;;;;;AAQF;AACF;AAEM;;AAeJ;AACA;AACA;AAEA;AAGA;AAIA;;;;;AAOE;;;;AAIA;;;;AAG0C;;;AAEgB;AAE5D;AACA;AACA;AAEA;AAEI;AACI;AACA;AACA;AACA;AACD;;;AAKP;;;AAgBU;;;;;AAKI;AACD;AACH;;;;;AAYA;;;AAkBE;;;;;AAUA;AACA;;;;;;;AAkBC;AAcf;AAEA;;"}