UNPKG

@kobalte/core

Version:

Unstyled components and primitives for building accessible web apps and design systems with SolidJS.

316 lines (309 loc) 10.1 kB
import { PopperArrow, Popper } from './LMWVDFW6.js'; import { DismissableLayer } from './BASJUNIE.js'; import { createDisclosureState } from './7LCANGHD.js'; import { LinkRoot } from './3QQCO6IA.js'; import { __export } from './5ZKAE4VZ.js'; import { createComponent, mergeProps, Portal, isServer } from 'solid-js/web'; import { mergeRefs, mergeDefaultProps, createGlobalListeners, isPointInPolygon, getEventPoint, callHandler, contains } from '@kobalte/utils'; import { createContext, useContext, splitProps, Show, createUniqueId, createSignal, createEffect, onCleanup, createMemo } from 'solid-js'; import { combineStyle } from '@solid-primitives/props'; import createPresence from 'solid-presence'; // src/hover-card/index.tsx var hover_card_exports = {}; __export(hover_card_exports, { Arrow: () => PopperArrow, Content: () => HoverCardContent, HoverCard: () => HoverCard, Portal: () => HoverCardPortal, Root: () => HoverCardRoot, Trigger: () => HoverCardTrigger, useHoverCardContext: () => useHoverCardContext }); var HoverCardContext = createContext(); function useHoverCardContext() { const context = useContext(HoverCardContext); if (context === void 0) { throw new Error("[kobalte]: `useHoverCardContext` must be used within a `HoverCard` component"); } return context; } // src/hover-card/hover-card-content.tsx function HoverCardContent(props) { const context = useHoverCardContext(); const [local, others] = splitProps(props, ["ref", "style"]); return createComponent(Show, { get when() { return context.contentPresent(); }, get children() { return createComponent(Popper.Positioner, { get children() { return createComponent(DismissableLayer, mergeProps({ ref(r$) { const _ref$ = mergeRefs((el) => { context.setContentRef(el); }, local.ref); typeof _ref$ === "function" && _ref$(r$); }, disableOutsidePointerEvents: false, get style() { return combineStyle({ "--kb-hovercard-content-transform-origin": "var(--kb-popper-content-transform-origin)", position: "relative" }, local.style); }, onFocusOutside: (e) => e.preventDefault(), get onDismiss() { return context.close; } }, () => context.dataset(), others)); } }); } }); } function HoverCardPortal(props) { const context = useHoverCardContext(); return createComponent(Show, { get when() { return context.contentPresent(); }, get children() { return createComponent(Portal, props); } }); } // src/hover-card/utils.ts function getHoverCardSafeArea(placement, anchorEl, floatingEl) { const basePlacement = placement.split("-")[0]; const anchorRect = anchorEl.getBoundingClientRect(); const floatingRect = floatingEl.getBoundingClientRect(); const polygon = []; const anchorCenterX = anchorRect.left + anchorRect.width / 2; const anchorCenterY = anchorRect.top + anchorRect.height / 2; switch (basePlacement) { case "top": polygon.push([anchorRect.left, anchorCenterY]); polygon.push([floatingRect.left, floatingRect.bottom]); polygon.push([floatingRect.left, floatingRect.top]); polygon.push([floatingRect.right, floatingRect.top]); polygon.push([floatingRect.right, floatingRect.bottom]); polygon.push([anchorRect.right, anchorCenterY]); break; case "right": polygon.push([anchorCenterX, anchorRect.top]); polygon.push([floatingRect.left, floatingRect.top]); polygon.push([floatingRect.right, floatingRect.top]); polygon.push([floatingRect.right, floatingRect.bottom]); polygon.push([floatingRect.left, floatingRect.bottom]); polygon.push([anchorCenterX, anchorRect.bottom]); break; case "bottom": polygon.push([anchorRect.left, anchorCenterY]); polygon.push([floatingRect.left, floatingRect.top]); polygon.push([floatingRect.left, floatingRect.bottom]); polygon.push([floatingRect.right, floatingRect.bottom]); polygon.push([floatingRect.right, floatingRect.top]); polygon.push([anchorRect.right, anchorCenterY]); break; case "left": polygon.push([anchorCenterX, anchorRect.top]); polygon.push([floatingRect.right, floatingRect.top]); polygon.push([floatingRect.left, floatingRect.top]); polygon.push([floatingRect.left, floatingRect.bottom]); polygon.push([floatingRect.right, floatingRect.bottom]); polygon.push([anchorCenterX, anchorRect.bottom]); break; } return polygon; } // src/hover-card/hover-card-root.tsx function HoverCardRoot(props) { const defaultId = `hovercard-${createUniqueId()}`; const mergedProps = mergeDefaultProps({ id: defaultId, openDelay: 700, closeDelay: 300 }, props); const [local, others] = splitProps(mergedProps, ["id", "open", "defaultOpen", "onOpenChange", "openDelay", "closeDelay", "ignoreSafeArea", "forceMount"]); let openTimeoutId; let closeTimeoutId; const [triggerRef, setTriggerRef] = createSignal(); const [contentRef, setContentRef] = createSignal(); const [currentPlacement, setCurrentPlacement] = createSignal(others.placement); const disclosureState = createDisclosureState({ open: () => local.open, defaultOpen: () => local.defaultOpen, onOpenChange: (isOpen) => local.onOpenChange?.(isOpen) }); const { present: contentPresent } = createPresence({ show: () => local.forceMount || disclosureState.isOpen(), element: () => contentRef() ?? null }); const { addGlobalListener, removeGlobalListener } = createGlobalListeners(); const openWithDelay = () => { if (isServer) { return; } openTimeoutId = window.setTimeout(() => { openTimeoutId = void 0; disclosureState.open(); }, local.openDelay); }; const closeWithDelay = () => { if (isServer) { return; } closeTimeoutId = window.setTimeout(() => { closeTimeoutId = void 0; disclosureState.close(); }, local.closeDelay); }; const cancelOpening = () => { if (isServer) { return; } window.clearTimeout(openTimeoutId); openTimeoutId = void 0; }; const cancelClosing = () => { if (isServer) { return; } window.clearTimeout(closeTimeoutId); closeTimeoutId = void 0; }; const isTargetOnHoverCard = (target) => { return contains(triggerRef(), target) || contains(contentRef(), target); }; const getPolygonSafeArea = (placement) => { const triggerEl = triggerRef(); const contentEl = contentRef(); if (!triggerEl || !contentEl) { return; } return getHoverCardSafeArea(placement, triggerEl, contentEl); }; const onHoverOutside = (event) => { const target = event.target; if (isTargetOnHoverCard(target)) { cancelClosing(); return; } if (!local.ignoreSafeArea) { const polygon = getPolygonSafeArea(currentPlacement()); if (polygon && isPointInPolygon(getEventPoint(event), polygon)) { cancelClosing(); return; } } if (closeTimeoutId) { return; } closeWithDelay(); }; createEffect(() => { if (!disclosureState.isOpen()) { return; } addGlobalListener(document, "pointermove", onHoverOutside, true); onCleanup(() => { removeGlobalListener(document, "pointermove", onHoverOutside, true); }); }); onCleanup(() => { cancelOpening(); cancelClosing(); }); const dataset = createMemo(() => ({ "data-expanded": disclosureState.isOpen() ? "" : void 0, "data-closed": !disclosureState.isOpen() ? "" : void 0 })); const context = { dataset, isOpen: disclosureState.isOpen, contentPresent, openWithDelay, closeWithDelay, cancelOpening, cancelClosing, close: disclosureState.close, isTargetOnHoverCard, setTriggerRef, setContentRef }; return createComponent(HoverCardContext.Provider, { value: context, get children() { return createComponent(Popper, mergeProps({ anchorRef: triggerRef, contentRef, onCurrentPlacementChange: setCurrentPlacement }, others)); } }); } function HoverCardTrigger(props) { const context = useHoverCardContext(); const [local, others] = splitProps(props, ["ref", "onPointerEnter", "onPointerLeave", "onFocus", "onBlur"]); const onPointerEnter = (e) => { callHandler(e, local.onPointerEnter); if (e.pointerType === "touch" || others.disabled || e.defaultPrevented) { return; } context.cancelClosing(); if (!context.isOpen()) { context.openWithDelay(); } }; const onPointerLeave = (e) => { callHandler(e, local.onPointerLeave); if (e.pointerType === "touch") { return; } context.cancelOpening(); }; const onFocus = (e) => { callHandler(e, local.onFocus); if (others.disabled || e.defaultPrevented) { return; } context.cancelClosing(); if (!context.isOpen()) { context.openWithDelay(); } }; const onBlur = (e) => { callHandler(e, local.onBlur); context.cancelOpening(); const relatedTarget = e.relatedTarget; if (context.isTargetOnHoverCard(relatedTarget)) { return; } context.closeWithDelay(); }; onCleanup(context.cancelOpening); return createComponent(LinkRoot, mergeProps({ ref(r$) { const _ref$ = mergeRefs(context.setTriggerRef, local.ref); typeof _ref$ === "function" && _ref$(r$); }, onPointerEnter, onPointerLeave, onFocus, onBlur }, () => context.dataset(), others)); } // src/hover-card/index.tsx var HoverCard = Object.assign(HoverCardRoot, { Arrow: PopperArrow, Content: HoverCardContent, Portal: HoverCardPortal, Trigger: HoverCardTrigger }); export { HoverCard, HoverCardContent, HoverCardPortal, HoverCardRoot, HoverCardTrigger, hover_card_exports, useHoverCardContext };