import { createEventListener, createEventListenerMap } from "@solid-primitives/event-listener"; import { access, type MaybeAccessor } from "@solid-primitives/utils"; import { createEffect, createSignal, on, onCleanup, splitProps, type Component, type FlowComponent, type JSX } from "solid-js"; import { autoUpdate, computePosition, size, shift, flip, offset, autoPlacement, } from "@floating-ui/dom"; import { mergeRefs } from "@solid-primitives/refs"; import { createPresence } from "@solid-primitives/presence"; import clsx from "clsx/lite"; import { tooltipStyle } from "./tooltip.css"; const wait = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout)); type FloatingState = { x: number; y: number; } type TooltipState = "inactive" | "delay" | "active"; export type TooltipProps = { for: MaybeAccessor; } & Omit, "style" | "role" | "popover">; const SUPPORTS_POPOVER = Object.prototype.hasOwnProperty.call(HTMLElement.prototype, "povover"); export const tooltipFactory = (variant: T) => { const component: Component = (props) => { const [localProps, otherProps] = splitProps( props, ["ref", "for", "class", "children"], ); let ref!: HTMLElement; const [floatingState, setFloatingState] = createSignal({ x: 0, y: 0 }); const [visible, setVisible] = createSignal(false); const { isMounted, isVisible } = createPresence( visible, { transitionDuration: 100 }, ); const [hovered, setHovered] = createSignal(false); createEffect(() => { const anchor = access(localProps.for); const cleanup = autoUpdate( anchor, ref, () => void computePosition( anchor, ref, { placement: "top", middleware: [ flip(), shift(), offset(8), ], }, ).then( ({ x, y }) => setFloatingState({ x, y }) ), ); onCleanup(cleanup); }); createEffect( on(isMounted, (mounted) => { if(mounted) ref.showPopover(); else ref.hidePopover(); }), ); createEventListenerMap( props.for, { pointerenter: async () => { setHovered(true); await wait(500); if(hovered()) setVisible(true); }, pointerleave: async () => { setHovered(false); await wait(1500); if(!hovered()) setVisible(false); }, } ); return (
ref = element)} class={clsx(tooltipStyle({ variant }), localProps.class)} {...otherProps} role="tooltip" style={{ display: isMounted() ? undefined : "none", left: `${floatingState().x}px`, top: `${floatingState().y}px`, opacity: isVisible() ? 1 : 0, }} popover="manual"> {localProps.children}
); } return component; }