Files
NapCatQQ/.pnpm-store/v3/files/7c/62bf5e14ae3ecc8944986cb38dca3a113cd9cfb29b1b4c7c93ff57c86257fdcce54875520061c7504076b31642972c3cbbc59cc235d25003df8cefb7f8f463
TanTan e311ca3275
Build NapCat Artifacts / Windows Loader Test (push) Canceled after 0s
Build NapCat Artifacts / Type Check & Test (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Framework (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Shell (push) Canceled after 0s
docs: 仓库链接指向本镜像仓库
2026-11-13 09:25:58 +08:00

198 lines
6.3 KiB
Plaintext

/*
* Copyright 2020 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 {DOMAttributes} from '@react-types/shared';
import React, {AriaAttributes, JSX, ReactNode, useContext, useEffect, useMemo, useState} from 'react';
import ReactDOM from 'react-dom';
import {useIsSSR} from '@react-aria/ssr';
import {useUNSAFE_PortalContext} from './PortalProvider';
export interface ModalProviderProps extends DOMAttributes {
children: ReactNode
}
interface ModalContext {
parent: ModalContext | null,
modalCount: number,
addModal: () => void,
removeModal: () => void
}
const Context = React.createContext<ModalContext | null>(null);
/**
* Each ModalProvider tracks how many modals are open in its subtree. On mount, the modals
* trigger `addModal` to increment the count, and trigger `removeModal` on unmount to decrement it.
* This is done recursively so that all parent providers are incremented and decremented.
* If the modal count is greater than zero, we add `aria-hidden` to this provider to hide its
* subtree from screen readers. This is done using React context in order to account for things
* like portals, which can cause the React tree and the DOM tree to differ significantly in structure.
*/
export function ModalProvider(props: ModalProviderProps): JSX.Element {
let {children} = props;
let parent = useContext(Context);
let [modalCount, setModalCount] = useState(0);
let context = useMemo(() => ({
parent,
modalCount,
addModal() {
setModalCount(count => count + 1);
if (parent) {
parent.addModal();
}
},
removeModal() {
setModalCount(count => count - 1);
if (parent) {
parent.removeModal();
}
}
}), [parent, modalCount]);
return (
<Context.Provider value={context}>
{children}
</Context.Provider>
);
}
export interface ModalProviderAria {
/**
* Props to be spread on the container element.
*/
modalProviderProps: AriaAttributes
}
/**
* Used to determine if the tree should be aria-hidden based on how many
* modals are open.
*/
export function useModalProvider(): ModalProviderAria {
let context = useContext(Context);
return {
modalProviderProps: {
'aria-hidden': context && context.modalCount > 0 ? true : undefined
}
};
}
/**
* Creates a root node that will be aria-hidden if there are other modals open.
*/
function OverlayContainerDOM(props: ModalProviderProps) {
let {modalProviderProps} = useModalProvider();
return <div data-overlay-container {...props} {...modalProviderProps} />;
}
/**
* An OverlayProvider acts as a container for the top-level application.
* Any application that uses modal dialogs or other overlays should
* be wrapped in a `<OverlayProvider>`. This is used to ensure that
* the main content of the application is hidden from screen readers
* if a modal or other overlay is opened. Only the top-most modal or
* overlay should be accessible at once.
*/
export function OverlayProvider(props: ModalProviderProps): JSX.Element {
return (
<ModalProvider>
<OverlayContainerDOM {...props} />
</ModalProvider>
);
}
export interface OverlayContainerProps extends ModalProviderProps {
/**
* The container element in which the overlay portal will be placed.
* @default document.body
* @deprecated - Use a parent UNSAFE_PortalProvider to set your portal container instead.
*/
portalContainer?: Element
}
/**
* A container for overlays like modals and popovers. Renders the overlay
* into a Portal which is placed at the end of the document body.
* Also ensures that the overlay is hidden from screen readers if a
* nested modal is opened. Only the top-most modal or overlay should
* be accessible at once.
*/
export function OverlayContainer(props: OverlayContainerProps): React.ReactPortal | null {
let isSSR = useIsSSR();
let {portalContainer = isSSR ? null : document.body, ...rest} = props;
let {getContainer} = useUNSAFE_PortalContext();
if (!props.portalContainer && getContainer) {
portalContainer = getContainer();
}
React.useEffect(() => {
if (portalContainer?.closest('[data-overlay-container]')) {
throw new Error('An OverlayContainer must not be inside another container. Please change the portalContainer prop.');
}
}, [portalContainer]);
if (!portalContainer) {
return null;
}
let contents = <OverlayProvider {...rest} />;
return ReactDOM.createPortal(contents, portalContainer);
}
interface ModalAriaProps extends DOMAttributes {
/** Data attribute marks the dom node as a modal for the aria-modal-polyfill. */
'data-ismodal': boolean
}
export interface AriaModalOptions {
isDisabled?: boolean
}
export interface ModalAria {
/** Props for the modal content element. */
modalProps: ModalAriaProps
}
/**
* Hides content outside the current `<OverlayContainer>` from screen readers
* on mount and restores it on unmount. Typically used by modal dialogs and
* other types of overlays to ensure that only the top-most modal is
* accessible at once.
*/
export function useModal(options?: AriaModalOptions): ModalAria {
// Add aria-hidden to all parent providers on mount, and restore on unmount.
let context = useContext(Context);
if (!context) {
throw new Error('Modal is not contained within a provider');
}
useEffect(() => {
if (options?.isDisabled || !context || !context.parent) {
return;
}
// The immediate context is from the provider containing this modal, so we only
// want to trigger aria-hidden on its parents not on the modal provider itself.
context.parent.addModal();
return () => {
if (context && context.parent) {
context.parent.removeModal();
}
};
}, [context, context.parent, options?.isDisabled]);
return {
modalProps: {
'data-ismodal': !options?.isDisabled
}
};
}