Files
volleyball-dev-frontend/node_modules/@react-aria/utils/src/openLink.tsx
Marc Wieland 33181acf83
Some checks are pending
Deploy Volleyball CMS / deploy (push) Waiting to run
Last
2025-06-02 18:56:22 +02:00

186 lines
6.7 KiB
TypeScript

/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {focusWithoutScrolling, isMac, isWebKit} from './index';
import {Href, LinkDOMProps, RouterOptions} from '@react-types/shared';
import {isFirefox, isIPad} from './platform';
import React, {createContext, DOMAttributes, JSX, ReactNode, useContext, useMemo} from 'react';
interface Router {
isNative: boolean,
open: (target: Element, modifiers: Modifiers, href: Href, routerOptions: RouterOptions | undefined) => void,
useHref: (href: Href) => string
}
const RouterContext = createContext<Router>({
isNative: true,
open: openSyntheticLink,
useHref: (href) => href
});
interface RouterProviderProps {
navigate: (path: Href, routerOptions: RouterOptions | undefined) => void,
useHref?: (href: Href) => string,
children: ReactNode
}
/**
* A RouterProvider accepts a `navigate` function from a framework or client side router,
* and provides it to all nested React Aria links to enable client side navigation.
*/
export function RouterProvider(props: RouterProviderProps): JSX.Element {
let {children, navigate, useHref} = props;
let ctx = useMemo(() => ({
isNative: false,
open: (target: Element, modifiers: Modifiers, href: Href, routerOptions: RouterOptions | undefined) => {
getSyntheticLink(target, link => {
if (shouldClientNavigate(link, modifiers)) {
navigate(href, routerOptions);
} else {
openLink(link, modifiers);
}
});
},
useHref: useHref || ((href) => href)
}), [navigate, useHref]);
return (
<RouterContext.Provider value={ctx}>
{children}
</RouterContext.Provider>
);
}
export function useRouter(): Router {
return useContext(RouterContext);
}
interface Modifiers {
metaKey?: boolean,
ctrlKey?: boolean,
altKey?: boolean,
shiftKey?: boolean
}
export function shouldClientNavigate(link: HTMLAnchorElement, modifiers: Modifiers): boolean {
// Use getAttribute here instead of link.target. Firefox will default link.target to "_parent" when inside an iframe.
let target = link.getAttribute('target');
return (
(!target || target === '_self') &&
link.origin === location.origin &&
!link.hasAttribute('download') &&
!modifiers.metaKey && // open in new tab (mac)
!modifiers.ctrlKey && // open in new tab (windows)
!modifiers.altKey && // download
!modifiers.shiftKey
);
}
export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpening = true): void {
let {metaKey, ctrlKey, altKey, shiftKey} = modifiers;
// Firefox does not recognize keyboard events as a user action by default, and the popup blocker
// will prevent links with target="_blank" from opening. However, it does allow the event if the
// Command/Control key is held, which opens the link in a background tab. This seems like the best we can do.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=257870 and https://bugzilla.mozilla.org/show_bug.cgi?id=746640.
if (isFirefox() && window.event?.type?.startsWith('key') && target.target === '_blank') {
if (isMac()) {
metaKey = true;
} else {
ctrlKey = true;
}
}
// WebKit does not support firing click events with modifier keys, but does support keyboard events.
// https://github.com/WebKit/WebKit/blob/c03d0ac6e6db178f90923a0a63080b5ca210d25f/Source/WebCore/html/HTMLAnchorElement.cpp#L184
let event = isWebKit() && isMac() && !isIPad() && process.env.NODE_ENV !== 'test'
// @ts-ignore - keyIdentifier is a non-standard property, but it's what webkit expects
? new KeyboardEvent('keydown', {keyIdentifier: 'Enter', metaKey, ctrlKey, altKey, shiftKey})
: new MouseEvent('click', {metaKey, ctrlKey, altKey, shiftKey, bubbles: true, cancelable: true});
(openLink as any).isOpening = setOpening;
focusWithoutScrolling(target);
target.dispatchEvent(event);
(openLink as any).isOpening = false;
}
// https://github.com/parcel-bundler/parcel/issues/8724
(openLink as any).isOpening = false;
function getSyntheticLink(target: Element, open: (link: HTMLAnchorElement) => void) {
if (target instanceof HTMLAnchorElement) {
open(target);
} else if (target.hasAttribute('data-href')) {
let link = document.createElement('a');
link.href = target.getAttribute('data-href')!;
if (target.hasAttribute('data-target')) {
link.target = target.getAttribute('data-target')!;
}
if (target.hasAttribute('data-rel')) {
link.rel = target.getAttribute('data-rel')!;
}
if (target.hasAttribute('data-download')) {
link.download = target.getAttribute('data-download')!;
}
if (target.hasAttribute('data-ping')) {
link.ping = target.getAttribute('data-ping')!;
}
if (target.hasAttribute('data-referrer-policy')) {
link.referrerPolicy = target.getAttribute('data-referrer-policy')!;
}
target.appendChild(link);
open(link);
target.removeChild(link);
}
}
function openSyntheticLink(target: Element, modifiers: Modifiers) {
getSyntheticLink(target, link => openLink(link, modifiers));
}
export function useSyntheticLinkProps(props: LinkDOMProps): DOMAttributes<HTMLElement> {
let router = useRouter();
const href = router.useHref(props.href ?? '');
return {
'data-href': props.href ? href : undefined,
'data-target': props.target,
'data-rel': props.rel,
'data-download': props.download,
'data-ping': props.ping,
'data-referrer-policy': props.referrerPolicy
} as DOMAttributes<HTMLElement>;
}
/** @deprecated - For backward compatibility. */
export function getSyntheticLinkProps(props: LinkDOMProps): DOMAttributes<HTMLElement> {
return {
'data-href': props.href,
'data-target': props.target,
'data-rel': props.rel,
'data-download': props.download,
'data-ping': props.ping,
'data-referrer-policy': props.referrerPolicy
} as DOMAttributes<HTMLElement>;
}
export function useLinkProps(props?: LinkDOMProps): LinkDOMProps {
let router = useRouter();
const href = router.useHref(props?.href ?? '');
return {
href: props?.href ? href : undefined,
target: props?.target,
rel: props?.rel,
download: props?.download,
ping: props?.ping,
referrerPolicy: props?.referrerPolicy
};
}