import { createTheme } from '@ant-design/cssinjs'; import IconContext from '@ant-design/icons/lib/components/Context'; import { FormProvider as RcFormProvider } from 'rc-field-form'; import type { ValidateMessages } from 'rc-field-form/lib/interface'; import useMemo from 'rc-util/lib/hooks/useMemo'; import * as React from 'react'; import type { ReactElement } from 'react'; import type { Options } from 'scroll-into-view-if-needed'; import type { RequiredMark } from '../form/Form'; import type { Locale } from '../locale'; import LocaleProvider, { ANT_MARK } from '../locale'; import LocaleReceiver from '../locale/LocaleReceiver'; import defaultLocale from '../locale/en_US'; import { DesignTokenContext } from '../theme/internal'; import defaultSeedToken from '../theme/themes/seed'; import type { ConfigConsumerProps, CSPConfig, DirectionType, Theme, ThemeConfig } from './context'; import { ConfigConsumer, ConfigContext, defaultIconPrefixCls } from './context'; import { registerTheme } from './cssVariables'; import type { RenderEmptyHandler } from './defaultRenderEmpty'; import { DisabledContextProvider } from './DisabledContext'; import useTheme from './hooks/useTheme'; import type { SizeType } from './SizeContext'; import SizeContext, { SizeContextProvider } from './SizeContext'; import useStyle from './style'; export { type RenderEmptyHandler, ConfigContext, ConfigConsumer, type CSPConfig, type DirectionType, type ConfigConsumerProps, }; export { defaultIconPrefixCls }; export const configConsumerProps = [ 'getTargetContainer', 'getPopupContainer', 'rootPrefixCls', 'getPrefixCls', 'renderEmpty', 'csp', 'autoInsertSpaceInButton', 'locale', 'pageHeader', ]; // These props is used by `useContext` directly in sub component const PASSED_PROPS: Exclude[] = [ 'getTargetContainer', 'getPopupContainer', 'renderEmpty', 'pageHeader', 'input', 'pagination', 'form', 'select', ]; export interface ConfigProviderProps { getTargetContainer?: () => HTMLElement | Window; getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement; prefixCls?: string; iconPrefixCls?: string; children?: React.ReactNode; renderEmpty?: RenderEmptyHandler; csp?: CSPConfig; autoInsertSpaceInButton?: boolean; form?: { validateMessages?: ValidateMessages; requiredMark?: RequiredMark; colon?: boolean; scrollToFirstError?: Options | boolean; }; input?: { autoComplete?: string; }; select?: { showSearch?: boolean; }; pagination?: { showSizeChanger?: boolean; }; locale?: Locale; pageHeader?: { ghost: boolean; }; componentSize?: SizeType; componentDisabled?: boolean; direction?: DirectionType; space?: { size?: SizeType | number; }; virtual?: boolean; dropdownMatchSelectWidth?: boolean; theme?: ThemeConfig; } interface ProviderChildrenProps extends ConfigProviderProps { parentContext: ConfigConsumerProps; legacyLocale: Locale; } export const defaultPrefixCls = 'ant'; let globalPrefixCls: string; let globalIconPrefixCls: string; function getGlobalPrefixCls() { return globalPrefixCls || defaultPrefixCls; } function getGlobalIconPrefixCls() { return globalIconPrefixCls || defaultIconPrefixCls; } const setGlobalConfig = ({ prefixCls, iconPrefixCls, theme, }: Pick & { theme?: Theme }) => { if (prefixCls !== undefined) { globalPrefixCls = prefixCls; } if (iconPrefixCls !== undefined) { globalIconPrefixCls = iconPrefixCls; } if (theme) { registerTheme(getGlobalPrefixCls(), theme); } }; export const globalConfig = () => ({ getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => { if (customizePrefixCls) return customizePrefixCls; return suffixCls ? `${getGlobalPrefixCls()}-${suffixCls}` : getGlobalPrefixCls(); }, getIconPrefixCls: getGlobalIconPrefixCls, getRootPrefixCls: () => { // If Global prefixCls provided, use this if (globalPrefixCls) { return globalPrefixCls; } // Fallback to default prefixCls return getGlobalPrefixCls(); }, }); const ProviderChildren: React.FC = (props) => { const { children, csp: customCsp, autoInsertSpaceInButton, form, locale, componentSize, direction, space, virtual, dropdownMatchSelectWidth, legacyLocale, parentContext, iconPrefixCls: customIconPrefixCls, theme, componentDisabled, } = props; const getPrefixCls = React.useCallback( (suffixCls: string, customizePrefixCls?: string) => { const { prefixCls } = props; if (customizePrefixCls) return customizePrefixCls; const mergedPrefixCls = prefixCls || parentContext.getPrefixCls(''); return suffixCls ? `${mergedPrefixCls}-${suffixCls}` : mergedPrefixCls; }, [parentContext.getPrefixCls, props.prefixCls], ); const iconPrefixCls = customIconPrefixCls || parentContext.iconPrefixCls || defaultIconPrefixCls; const shouldWrapSSR = iconPrefixCls !== parentContext.iconPrefixCls; const csp = customCsp || parentContext.csp; const wrapSSR = useStyle(iconPrefixCls); const mergedTheme = useTheme(theme, parentContext.theme); const baseConfig = { csp, autoInsertSpaceInButton, locale: locale || legacyLocale, direction, space, virtual, dropdownMatchSelectWidth, getPrefixCls, iconPrefixCls, theme: mergedTheme, }; const config = { ...parentContext, }; Object.keys(baseConfig).forEach((key: keyof typeof baseConfig) => { if (baseConfig[key] !== undefined) { (config as any)[key] = baseConfig[key]; } }); // Pass the props used by `useContext` directly with child component. // These props should merged into `config`. PASSED_PROPS.forEach((propName) => { const propValue = props[propName]; if (propValue) { (config as any)[propName] = propValue; } }); // https://github.com/ant-design/ant-design/issues/27617 const memoedConfig = useMemo( () => config, config, (prevConfig, currentConfig) => { const prevKeys = Object.keys(prevConfig) as Array; const currentKeys = Object.keys(currentConfig) as Array; return ( prevKeys.length !== currentKeys.length || prevKeys.some((key) => prevConfig[key] !== currentConfig[key]) ); }, ); const memoIconContextValue = React.useMemo( () => ({ prefixCls: iconPrefixCls, csp }), [iconPrefixCls, csp], ); let childNode = shouldWrapSSR ? wrapSSR(children as ReactElement) : children; // Additional Form provider let validateMessages: ValidateMessages = {}; if (locale) { validateMessages = locale.Form?.defaultValidateMessages || defaultLocale.Form?.defaultValidateMessages || {}; } if (form && form.validateMessages) { validateMessages = { ...validateMessages, ...form.validateMessages }; } if (Object.keys(validateMessages).length > 0) { childNode = {children}; } if (locale) { childNode = ( {childNode} ); } if (iconPrefixCls || csp) { childNode = ( {childNode} ); } if (componentSize) { childNode = {childNode}; } // ================================ Dynamic theme ================================ const memoTheme = React.useMemo(() => { const { algorithm, token, ...rest } = mergedTheme || {}; const themeObj = algorithm && (!Array.isArray(algorithm) || algorithm.length > 0) ? createTheme(algorithm) : undefined; return { ...rest, theme: themeObj, token: { ...defaultSeedToken, ...token, }, }; }, [mergedTheme]); if (theme) { childNode = ( {childNode} ); } // =================================== Render =================================== if (componentDisabled !== undefined) { childNode = ( {childNode} ); } return {childNode}; }; const ConfigProvider: React.FC & { /** @private internal Usage. do not use in your production */ ConfigContext: typeof ConfigContext; SizeContext: typeof SizeContext; config: typeof setGlobalConfig; } = (props) => ( {(_, __, legacyLocale) => ( {(context) => ( )} )} ); ConfigProvider.ConfigContext = ConfigContext; ConfigProvider.SizeContext = SizeContext; ConfigProvider.config = setGlobalConfig; if (process.env.NODE_ENV !== 'production') { ConfigProvider.displayName = 'ConfigProvider'; } export default ConfigProvider;