Spaces:
Runtime error
Runtime error
| "use client"; | |
| // src/tooltip.tsx | |
| import * as React from "react"; | |
| import { composeEventHandlers } from "@radix-ui/primitive"; | |
| import { useComposedRefs } from "@radix-ui/react-compose-refs"; | |
| import { createContextScope } from "@radix-ui/react-context"; | |
| import { DismissableLayer } from "@radix-ui/react-dismissable-layer"; | |
| import { useId } from "@radix-ui/react-id"; | |
| import * as PopperPrimitive from "@radix-ui/react-popper"; | |
| import { createPopperScope } from "@radix-ui/react-popper"; | |
| import { Portal as PortalPrimitive } from "@radix-ui/react-portal"; | |
| import { Presence } from "@radix-ui/react-presence"; | |
| import { Primitive } from "@radix-ui/react-primitive"; | |
| import { createSlottable } from "@radix-ui/react-slot"; | |
| import { useControllableState } from "@radix-ui/react-use-controllable-state"; | |
| import * as VisuallyHiddenPrimitive from "@radix-ui/react-visually-hidden"; | |
| import { jsx, jsxs } from "react/jsx-runtime"; | |
| var [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [ | |
| createPopperScope | |
| ]); | |
| var usePopperScope = createPopperScope(); | |
| var PROVIDER_NAME = "TooltipProvider"; | |
| var DEFAULT_DELAY_DURATION = 700; | |
| var TOOLTIP_OPEN = "tooltip.open"; | |
| var [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME); | |
| var TooltipProvider = (props) => { | |
| const { | |
| __scopeTooltip, | |
| delayDuration = DEFAULT_DELAY_DURATION, | |
| skipDelayDuration = 300, | |
| disableHoverableContent = false, | |
| children | |
| } = props; | |
| const isOpenDelayedRef = React.useRef(true); | |
| const isPointerInTransitRef = React.useRef(false); | |
| const skipDelayTimerRef = React.useRef(0); | |
| React.useEffect(() => { | |
| const skipDelayTimer = skipDelayTimerRef.current; | |
| return () => window.clearTimeout(skipDelayTimer); | |
| }, []); | |
| return /* @__PURE__ */ jsx( | |
| TooltipProviderContextProvider, | |
| { | |
| scope: __scopeTooltip, | |
| isOpenDelayedRef, | |
| delayDuration, | |
| onOpen: React.useCallback(() => { | |
| window.clearTimeout(skipDelayTimerRef.current); | |
| isOpenDelayedRef.current = false; | |
| }, []), | |
| onClose: React.useCallback(() => { | |
| window.clearTimeout(skipDelayTimerRef.current); | |
| skipDelayTimerRef.current = window.setTimeout( | |
| () => isOpenDelayedRef.current = true, | |
| skipDelayDuration | |
| ); | |
| }, [skipDelayDuration]), | |
| isPointerInTransitRef, | |
| onPointerInTransitChange: React.useCallback((inTransit) => { | |
| isPointerInTransitRef.current = inTransit; | |
| }, []), | |
| disableHoverableContent, | |
| children | |
| } | |
| ); | |
| }; | |
| TooltipProvider.displayName = PROVIDER_NAME; | |
| var TOOLTIP_NAME = "Tooltip"; | |
| var [TooltipContextProvider, useTooltipContext] = createTooltipContext(TOOLTIP_NAME); | |
| var Tooltip = (props) => { | |
| const { | |
| __scopeTooltip, | |
| children, | |
| open: openProp, | |
| defaultOpen, | |
| onOpenChange, | |
| disableHoverableContent: disableHoverableContentProp, | |
| delayDuration: delayDurationProp | |
| } = props; | |
| const providerContext = useTooltipProviderContext(TOOLTIP_NAME, props.__scopeTooltip); | |
| const popperScope = usePopperScope(__scopeTooltip); | |
| const [trigger, setTrigger] = React.useState(null); | |
| const contentId = useId(); | |
| const openTimerRef = React.useRef(0); | |
| const disableHoverableContent = disableHoverableContentProp ?? providerContext.disableHoverableContent; | |
| const delayDuration = delayDurationProp ?? providerContext.delayDuration; | |
| const wasOpenDelayedRef = React.useRef(false); | |
| const [open, setOpen] = useControllableState({ | |
| prop: openProp, | |
| defaultProp: defaultOpen ?? false, | |
| onChange: (open2) => { | |
| if (open2) { | |
| providerContext.onOpen(); | |
| document.dispatchEvent(new CustomEvent(TOOLTIP_OPEN)); | |
| } else { | |
| providerContext.onClose(); | |
| } | |
| onOpenChange?.(open2); | |
| }, | |
| caller: TOOLTIP_NAME | |
| }); | |
| const stateAttribute = React.useMemo(() => { | |
| return open ? wasOpenDelayedRef.current ? "delayed-open" : "instant-open" : "closed"; | |
| }, [open]); | |
| const handleOpen = React.useCallback(() => { | |
| window.clearTimeout(openTimerRef.current); | |
| openTimerRef.current = 0; | |
| wasOpenDelayedRef.current = false; | |
| setOpen(true); | |
| }, [setOpen]); | |
| const handleClose = React.useCallback(() => { | |
| window.clearTimeout(openTimerRef.current); | |
| openTimerRef.current = 0; | |
| setOpen(false); | |
| }, [setOpen]); | |
| const handleDelayedOpen = React.useCallback(() => { | |
| window.clearTimeout(openTimerRef.current); | |
| openTimerRef.current = window.setTimeout(() => { | |
| wasOpenDelayedRef.current = true; | |
| setOpen(true); | |
| openTimerRef.current = 0; | |
| }, delayDuration); | |
| }, [delayDuration, setOpen]); | |
| React.useEffect(() => { | |
| return () => { | |
| if (openTimerRef.current) { | |
| window.clearTimeout(openTimerRef.current); | |
| openTimerRef.current = 0; | |
| } | |
| }; | |
| }, []); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ jsx( | |
| TooltipContextProvider, | |
| { | |
| scope: __scopeTooltip, | |
| contentId, | |
| open, | |
| stateAttribute, | |
| trigger, | |
| onTriggerChange: setTrigger, | |
| onTriggerEnter: React.useCallback(() => { | |
| if (providerContext.isOpenDelayedRef.current) handleDelayedOpen(); | |
| else handleOpen(); | |
| }, [providerContext.isOpenDelayedRef, handleDelayedOpen, handleOpen]), | |
| onTriggerLeave: React.useCallback(() => { | |
| if (disableHoverableContent) { | |
| handleClose(); | |
| } else { | |
| window.clearTimeout(openTimerRef.current); | |
| openTimerRef.current = 0; | |
| } | |
| }, [handleClose, disableHoverableContent]), | |
| onOpen: handleOpen, | |
| onClose: handleClose, | |
| disableHoverableContent, | |
| children | |
| } | |
| ) }); | |
| }; | |
| Tooltip.displayName = TOOLTIP_NAME; | |
| var TRIGGER_NAME = "TooltipTrigger"; | |
| var TooltipTrigger = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeTooltip, ...triggerProps } = props; | |
| const context = useTooltipContext(TRIGGER_NAME, __scopeTooltip); | |
| const providerContext = useTooltipProviderContext(TRIGGER_NAME, __scopeTooltip); | |
| const popperScope = usePopperScope(__scopeTooltip); | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, ref, context.onTriggerChange); | |
| const isPointerDownRef = React.useRef(false); | |
| const hasPointerMoveOpenedRef = React.useRef(false); | |
| const handlePointerUp = React.useCallback(() => isPointerDownRef.current = false, []); | |
| React.useEffect(() => { | |
| return () => document.removeEventListener("pointerup", handlePointerUp); | |
| }, [handlePointerUp]); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Anchor, { asChild: true, ...popperScope, children: /* @__PURE__ */ jsx( | |
| Primitive.button, | |
| { | |
| "aria-describedby": context.open ? context.contentId : void 0, | |
| "data-state": context.stateAttribute, | |
| ...triggerProps, | |
| ref: composedRefs, | |
| onPointerMove: composeEventHandlers(props.onPointerMove, (event) => { | |
| if (event.pointerType === "touch") return; | |
| if (!hasPointerMoveOpenedRef.current && !providerContext.isPointerInTransitRef.current) { | |
| context.onTriggerEnter(); | |
| hasPointerMoveOpenedRef.current = true; | |
| } | |
| }), | |
| onPointerLeave: composeEventHandlers(props.onPointerLeave, () => { | |
| context.onTriggerLeave(); | |
| hasPointerMoveOpenedRef.current = false; | |
| }), | |
| onPointerDown: composeEventHandlers(props.onPointerDown, () => { | |
| if (context.open) { | |
| context.onClose(); | |
| } | |
| isPointerDownRef.current = true; | |
| document.addEventListener("pointerup", handlePointerUp, { once: true }); | |
| }), | |
| onFocus: composeEventHandlers(props.onFocus, () => { | |
| if (!isPointerDownRef.current) context.onOpen(); | |
| }), | |
| onBlur: composeEventHandlers(props.onBlur, context.onClose), | |
| onClick: composeEventHandlers(props.onClick, context.onClose) | |
| } | |
| ) }); | |
| } | |
| ); | |
| TooltipTrigger.displayName = TRIGGER_NAME; | |
| var PORTAL_NAME = "TooltipPortal"; | |
| var [PortalProvider, usePortalContext] = createTooltipContext(PORTAL_NAME, { | |
| forceMount: void 0 | |
| }); | |
| var TooltipPortal = (props) => { | |
| const { __scopeTooltip, forceMount, children, container } = props; | |
| const context = useTooltipContext(PORTAL_NAME, __scopeTooltip); | |
| return /* @__PURE__ */ jsx(PortalProvider, { scope: __scopeTooltip, forceMount, children: /* @__PURE__ */ jsx(Presence, { present: forceMount || context.open, children: /* @__PURE__ */ jsx(PortalPrimitive, { asChild: true, container, children }) }) }); | |
| }; | |
| TooltipPortal.displayName = PORTAL_NAME; | |
| var CONTENT_NAME = "TooltipContent"; | |
| var TooltipContent = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const portalContext = usePortalContext(CONTENT_NAME, props.__scopeTooltip); | |
| const { forceMount = portalContext.forceMount, side = "top", ...contentProps } = props; | |
| const context = useTooltipContext(CONTENT_NAME, props.__scopeTooltip); | |
| return /* @__PURE__ */ jsx(Presence, { present: forceMount || context.open, children: context.disableHoverableContent ? /* @__PURE__ */ jsx(TooltipContentImpl, { side, ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ jsx(TooltipContentHoverable, { side, ...contentProps, ref: forwardedRef }) }); | |
| } | |
| ); | |
| var TooltipContentHoverable = React.forwardRef((props, forwardedRef) => { | |
| const context = useTooltipContext(CONTENT_NAME, props.__scopeTooltip); | |
| const providerContext = useTooltipProviderContext(CONTENT_NAME, props.__scopeTooltip); | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, ref); | |
| const [pointerGraceArea, setPointerGraceArea] = React.useState(null); | |
| const { trigger, onClose } = context; | |
| const content = ref.current; | |
| const { onPointerInTransitChange } = providerContext; | |
| const handleRemoveGraceArea = React.useCallback(() => { | |
| setPointerGraceArea(null); | |
| onPointerInTransitChange(false); | |
| }, [onPointerInTransitChange]); | |
| const handleCreateGraceArea = React.useCallback( | |
| (event, hoverTarget) => { | |
| const currentTarget = event.currentTarget; | |
| const exitPoint = { x: event.clientX, y: event.clientY }; | |
| const exitSide = getExitSideFromRect(exitPoint, currentTarget.getBoundingClientRect()); | |
| const paddedExitPoints = getPaddedExitPoints(exitPoint, exitSide); | |
| const hoverTargetPoints = getPointsFromRect(hoverTarget.getBoundingClientRect()); | |
| const graceArea = getHull([...paddedExitPoints, ...hoverTargetPoints]); | |
| setPointerGraceArea(graceArea); | |
| onPointerInTransitChange(true); | |
| }, | |
| [onPointerInTransitChange] | |
| ); | |
| React.useEffect(() => { | |
| return () => handleRemoveGraceArea(); | |
| }, [handleRemoveGraceArea]); | |
| React.useEffect(() => { | |
| if (trigger && content) { | |
| const handleTriggerLeave = (event) => handleCreateGraceArea(event, content); | |
| const handleContentLeave = (event) => handleCreateGraceArea(event, trigger); | |
| trigger.addEventListener("pointerleave", handleTriggerLeave); | |
| content.addEventListener("pointerleave", handleContentLeave); | |
| return () => { | |
| trigger.removeEventListener("pointerleave", handleTriggerLeave); | |
| content.removeEventListener("pointerleave", handleContentLeave); | |
| }; | |
| } | |
| }, [trigger, content, handleCreateGraceArea, handleRemoveGraceArea]); | |
| React.useEffect(() => { | |
| if (pointerGraceArea) { | |
| const handleTrackPointerGrace = (event) => { | |
| const target = event.target; | |
| const pointerPosition = { x: event.clientX, y: event.clientY }; | |
| const hasEnteredTarget = trigger?.contains(target) || content?.contains(target); | |
| const isPointerOutsideGraceArea = !isPointInPolygon(pointerPosition, pointerGraceArea); | |
| if (hasEnteredTarget) { | |
| handleRemoveGraceArea(); | |
| } else if (isPointerOutsideGraceArea) { | |
| handleRemoveGraceArea(); | |
| onClose(); | |
| } | |
| }; | |
| document.addEventListener("pointermove", handleTrackPointerGrace); | |
| return () => document.removeEventListener("pointermove", handleTrackPointerGrace); | |
| } | |
| }, [trigger, content, pointerGraceArea, onClose, handleRemoveGraceArea]); | |
| return /* @__PURE__ */ jsx(TooltipContentImpl, { ...props, ref: composedRefs }); | |
| }); | |
| var [VisuallyHiddenContentContextProvider, useVisuallyHiddenContentContext] = createTooltipContext(TOOLTIP_NAME, { isInside: false }); | |
| var Slottable = createSlottable("TooltipContent"); | |
| var TooltipContentImpl = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { | |
| __scopeTooltip, | |
| children, | |
| "aria-label": ariaLabel, | |
| onEscapeKeyDown, | |
| onPointerDownOutside, | |
| ...contentProps | |
| } = props; | |
| const context = useTooltipContext(CONTENT_NAME, __scopeTooltip); | |
| const popperScope = usePopperScope(__scopeTooltip); | |
| const { onClose } = context; | |
| React.useEffect(() => { | |
| document.addEventListener(TOOLTIP_OPEN, onClose); | |
| return () => document.removeEventListener(TOOLTIP_OPEN, onClose); | |
| }, [onClose]); | |
| React.useEffect(() => { | |
| if (context.trigger) { | |
| const handleScroll = (event) => { | |
| const target = event.target; | |
| if (target?.contains(context.trigger)) onClose(); | |
| }; | |
| window.addEventListener("scroll", handleScroll, { capture: true }); | |
| return () => window.removeEventListener("scroll", handleScroll, { capture: true }); | |
| } | |
| }, [context.trigger, onClose]); | |
| return /* @__PURE__ */ jsx( | |
| DismissableLayer, | |
| { | |
| asChild: true, | |
| disableOutsidePointerEvents: false, | |
| onEscapeKeyDown, | |
| onPointerDownOutside, | |
| onFocusOutside: (event) => event.preventDefault(), | |
| onDismiss: onClose, | |
| children: /* @__PURE__ */ jsxs( | |
| PopperPrimitive.Content, | |
| { | |
| "data-state": context.stateAttribute, | |
| ...popperScope, | |
| ...contentProps, | |
| ref: forwardedRef, | |
| style: { | |
| ...contentProps.style, | |
| // re-namespace exposed content custom properties | |
| ...{ | |
| "--radix-tooltip-content-transform-origin": "var(--radix-popper-transform-origin)", | |
| "--radix-tooltip-content-available-width": "var(--radix-popper-available-width)", | |
| "--radix-tooltip-content-available-height": "var(--radix-popper-available-height)", | |
| "--radix-tooltip-trigger-width": "var(--radix-popper-anchor-width)", | |
| "--radix-tooltip-trigger-height": "var(--radix-popper-anchor-height)" | |
| } | |
| }, | |
| children: [ | |
| /* @__PURE__ */ jsx(Slottable, { children }), | |
| /* @__PURE__ */ jsx(VisuallyHiddenContentContextProvider, { scope: __scopeTooltip, isInside: true, children: /* @__PURE__ */ jsx(VisuallyHiddenPrimitive.Root, { id: context.contentId, role: "tooltip", children: ariaLabel || children }) }) | |
| ] | |
| } | |
| ) | |
| } | |
| ); | |
| } | |
| ); | |
| TooltipContent.displayName = CONTENT_NAME; | |
| var ARROW_NAME = "TooltipArrow"; | |
| var TooltipArrow = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeTooltip, ...arrowProps } = props; | |
| const popperScope = usePopperScope(__scopeTooltip); | |
| const visuallyHiddenContentContext = useVisuallyHiddenContentContext( | |
| ARROW_NAME, | |
| __scopeTooltip | |
| ); | |
| return visuallyHiddenContentContext.isInside ? null : /* @__PURE__ */ jsx(PopperPrimitive.Arrow, { ...popperScope, ...arrowProps, ref: forwardedRef }); | |
| } | |
| ); | |
| TooltipArrow.displayName = ARROW_NAME; | |
| function getExitSideFromRect(point, rect) { | |
| const top = Math.abs(rect.top - point.y); | |
| const bottom = Math.abs(rect.bottom - point.y); | |
| const right = Math.abs(rect.right - point.x); | |
| const left = Math.abs(rect.left - point.x); | |
| switch (Math.min(top, bottom, right, left)) { | |
| case left: | |
| return "left"; | |
| case right: | |
| return "right"; | |
| case top: | |
| return "top"; | |
| case bottom: | |
| return "bottom"; | |
| default: | |
| throw new Error("unreachable"); | |
| } | |
| } | |
| function getPaddedExitPoints(exitPoint, exitSide, padding = 5) { | |
| const paddedExitPoints = []; | |
| switch (exitSide) { | |
| case "top": | |
| paddedExitPoints.push( | |
| { x: exitPoint.x - padding, y: exitPoint.y + padding }, | |
| { x: exitPoint.x + padding, y: exitPoint.y + padding } | |
| ); | |
| break; | |
| case "bottom": | |
| paddedExitPoints.push( | |
| { x: exitPoint.x - padding, y: exitPoint.y - padding }, | |
| { x: exitPoint.x + padding, y: exitPoint.y - padding } | |
| ); | |
| break; | |
| case "left": | |
| paddedExitPoints.push( | |
| { x: exitPoint.x + padding, y: exitPoint.y - padding }, | |
| { x: exitPoint.x + padding, y: exitPoint.y + padding } | |
| ); | |
| break; | |
| case "right": | |
| paddedExitPoints.push( | |
| { x: exitPoint.x - padding, y: exitPoint.y - padding }, | |
| { x: exitPoint.x - padding, y: exitPoint.y + padding } | |
| ); | |
| break; | |
| } | |
| return paddedExitPoints; | |
| } | |
| function getPointsFromRect(rect) { | |
| const { top, right, bottom, left } = rect; | |
| return [ | |
| { x: left, y: top }, | |
| { x: right, y: top }, | |
| { x: right, y: bottom }, | |
| { x: left, y: bottom } | |
| ]; | |
| } | |
| function isPointInPolygon(point, polygon) { | |
| const { x, y } = point; | |
| let inside = false; | |
| for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { | |
| const ii = polygon[i]; | |
| const jj = polygon[j]; | |
| const xi = ii.x; | |
| const yi = ii.y; | |
| const xj = jj.x; | |
| const yj = jj.y; | |
| const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi; | |
| if (intersect) inside = !inside; | |
| } | |
| return inside; | |
| } | |
| function getHull(points) { | |
| const newPoints = points.slice(); | |
| newPoints.sort((a, b) => { | |
| if (a.x < b.x) return -1; | |
| else if (a.x > b.x) return 1; | |
| else if (a.y < b.y) return -1; | |
| else if (a.y > b.y) return 1; | |
| else return 0; | |
| }); | |
| return getHullPresorted(newPoints); | |
| } | |
| function getHullPresorted(points) { | |
| if (points.length <= 1) return points.slice(); | |
| const upperHull = []; | |
| for (let i = 0; i < points.length; i++) { | |
| const p = points[i]; | |
| while (upperHull.length >= 2) { | |
| const q = upperHull[upperHull.length - 1]; | |
| const r = upperHull[upperHull.length - 2]; | |
| if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop(); | |
| else break; | |
| } | |
| upperHull.push(p); | |
| } | |
| upperHull.pop(); | |
| const lowerHull = []; | |
| for (let i = points.length - 1; i >= 0; i--) { | |
| const p = points[i]; | |
| while (lowerHull.length >= 2) { | |
| const q = lowerHull[lowerHull.length - 1]; | |
| const r = lowerHull[lowerHull.length - 2]; | |
| if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop(); | |
| else break; | |
| } | |
| lowerHull.push(p); | |
| } | |
| lowerHull.pop(); | |
| if (upperHull.length === 1 && lowerHull.length === 1 && upperHull[0].x === lowerHull[0].x && upperHull[0].y === lowerHull[0].y) { | |
| return upperHull; | |
| } else { | |
| return upperHull.concat(lowerHull); | |
| } | |
| } | |
| var Provider = TooltipProvider; | |
| var Root3 = Tooltip; | |
| var Trigger = TooltipTrigger; | |
| var Portal = TooltipPortal; | |
| var Content2 = TooltipContent; | |
| var Arrow2 = TooltipArrow; | |
| export { | |
| Arrow2 as Arrow, | |
| Content2 as Content, | |
| Portal, | |
| Provider, | |
| Root3 as Root, | |
| Tooltip, | |
| TooltipArrow, | |
| TooltipContent, | |
| TooltipPortal, | |
| TooltipProvider, | |
| TooltipTrigger, | |
| Trigger, | |
| createTooltipScope | |
| }; | |
| //# sourceMappingURL=index.mjs.map | |