import React, { AnchorHTMLAttributes, type ComponentProps, HTMLAttributes, forwardRef, useRef, } from "react"; import { ArrowRightIcon } from "@navikt/aksel-icons"; import { cl, composeEventHandlers, createStrictContext, ownerWindow, } from "../../helpers"; import { useMergeRefs } from "../../hooks"; import type { AsChildProps } from "../../types"; import { Slot } from "../slot/Slot"; type LinkAnchorOverlayContextProps = { anchorRef: React.RefObject; }; const { Provider: LinkAnchorContextProvider, useContext: useLinkAnchorContext, } = createStrictContext({ name: "LinkAnchorOverlayContext", }); type LinkAnchorOverlayProps = HTMLAttributes & AsChildProps; /* * NB: Clicks on the overlay are captured with onClick. This does not work with middle-mouse-click. * We could capture such click with onAuxClick, but last time we tried that, * "forwarding" the click with dispatchEvent didn't work properly. * - Chrome: Worked if we dispatched a regular click event. * - Firefox: Did not work. * - Safari: Opened the link in the same tab (tested in BrowserStack). * We could use window.open() instead, but this would not run on(Aux)Click-callbacks on the link. * We consider this unacceptable since many rely on this for tracking etc. */ const LinkAnchorOverlay = forwardRef( ( { children, asChild, className, onClick, ...restProps }: LinkAnchorOverlayProps, forwardedRef, ) => { const anchorRef = useRef(null); const Component = asChild ? Slot : "div"; return ( { if ( e.target === anchorRef.current || isTextSelected(anchorRef.current) ) { return; } const event = new MouseEvent("click", { bubbles: true, cancelable: true, view: window, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, altKey: e.altKey, metaKey: e.metaKey, button: e.button, screenX: e.screenX, screenY: e.screenY, clientX: e.clientX, clientY: e.clientY, }); anchorRef.current?.dispatchEvent(event); })} > {children} ); }, ); /* ------------------------------- LinkAnchor ------------------------------- */ type LinkAnchorProps = AnchorHTMLAttributes & ( | { children: React.ReactElement | false | null; /** * Renders the component and its child as a single element, * merging the props of the component with the props of the child. */ asChild: true; as?: never; } | { children: React.ReactNode; /** * Renders the component and its child as a single element, * merging the props of the component with the props of the child. */ asChild?: false; href: string; } ); const LinkAnchor = forwardRef( ( { children, asChild, className, ...restProps }: LinkAnchorProps, forwardedRef, ) => { const context = useLinkAnchorContext(false); const mergedRefs = useMergeRefs(forwardedRef, context?.anchorRef); const Component = asChild ? Slot : "a"; return ( {children} ); }, ); /* ---------------------------- LinkAnchor Arrow ---------------------------- */ type LinkAnchorArrowProps = Omit, "ref">; const LinkAnchorArrow = forwardRef( ({ className, ...restProps }: LinkAnchorArrowProps, forwardedRef) => { return ( ); }, ); /* -------------------------- LinkAnchor Utilities -------------------------- */ function isTextSelected(refElement: HTMLAnchorElement | null): boolean { return !!ownerWindow(refElement)?.getSelection()?.toString(); } export { LinkAnchor, LinkAnchorArrow, LinkAnchorOverlay }; export type { LinkAnchorArrowProps, LinkAnchorOverlayProps, LinkAnchorProps };