import * as React from 'react'; import IconContext from '@ant-design/icons/lib/components/Context'; import { FormProvider as RcFormProvider } from 'rc-field-form'; import { ValidateMessages } from 'rc-field-form/lib/interface'; import useMemo from 'rc-util/lib/hooks/useMemo'; import { RenderEmptyHandler } from './renderEmpty'; import LocaleProvider, { ANT_MARK, Locale } from '../locale-provider'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import { ConfigConsumer, ConfigContext, CSPConfig, DirectionType, ConfigConsumerProps, Theme, defaultIconPrefixCls, } from './context'; import SizeContext, { SizeContextProvider, SizeType } from './SizeContext'; import message from '../message'; import notification from '../notification'; import { RequiredMark } from '../form/Form'; import { registerTheme } from './cssVariables'; import defaultLocale from '../locale/default'; import { DesignToken, DesignTokenContext } from '../_util/theme'; import defaultThemeToken from '../_util/theme/default'; export { RenderEmptyHandler, ConfigContext, ConfigConsumer, CSPConfig, DirectionType, ConfigConsumerProps, }; 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', 'form', ]; export interface ConfigProviderProps { getTargetContainer?: () => HTMLElement; getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement; prefixCls?: string; iconPrefixCls?: string; children?: React.ReactNode; renderEmpty?: RenderEmptyHandler; csp?: CSPConfig; autoInsertSpaceInButton?: boolean; form?: { validateMessages?: ValidateMessages; requiredMark?: RequiredMark; colon?: boolean; }; input?: { autoComplete?: string; }; locale?: Locale; pageHeader?: { ghost: boolean; }; componentSize?: SizeType; direction?: DirectionType; space?: { size?: SizeType | number; }; virtual?: boolean; dropdownMatchSelectWidth?: boolean; theme?: { token?: Partial; hashed?: boolean; }; } interface ProviderChildrenProps extends ConfigProviderProps { parentContext: ConfigConsumerProps; legacyLocale: Locale; } export const defaultPrefixCls = 'ant'; export { defaultIconPrefixCls }; 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: (rootPrefixCls?: string, customizePrefixCls?: string) => { // Customize rootPrefixCls is first priority if (rootPrefixCls) { return rootPrefixCls; } // If Global prefixCls provided, use this if (globalPrefixCls) { return globalPrefixCls; } // [Legacy] If customize prefixCls provided, we cut it to get the prefixCls if (customizePrefixCls && customizePrefixCls.includes('-')) { return customizePrefixCls.replace(/^(.*)-[^-]*$/, '$1'); } // Fallback to default prefixCls return getGlobalPrefixCls(); }, }); const ProviderChildren: React.FC = props => { const { children, csp, autoInsertSpaceInButton, form, locale, componentSize, direction, space, virtual, dropdownMatchSelectWidth, legacyLocale, parentContext, iconPrefixCls, theme = {}, } = 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 config = { ...parentContext, csp, autoInsertSpaceInButton, locale: locale || legacyLocale, direction, space, virtual, dropdownMatchSelectWidth, getPrefixCls, }; // Pass the props used by `useContext` directly with child component. // These props should merged into `config`. PASSED_PROPS.forEach(propName => { const propValue: any = props[propName]; if (propValue) { (config as any)[propName] = propValue; } }); // https://github.com/ant-design/ant-design/issues/27617 const memoedConfig = useMemo( () => config, config, (prevConfig: Record, currentConfig) => { const prevKeys = Object.keys(prevConfig); const currentKeys = Object.keys(currentConfig); return ( prevKeys.length !== currentKeys.length || prevKeys.some(key => prevConfig[key] !== currentConfig[key]) ); }, ); const memoIconContextValue = React.useMemo( () => ({ prefixCls: iconPrefixCls, csp }), [iconPrefixCls], ); let childNode = 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) { childNode = ( {childNode} ); } if (componentSize) { childNode = {childNode}; } // ================================ Dynamic theme ================================ const memoTheme = React.useMemo( () => ({ token: { ...defaultThemeToken, ...theme?.token, }, hashed: theme?.hashed, }), [theme?.token, theme?.hashed], ); if (theme?.token || theme?.hashed) { childNode = ( {childNode} ); } // =================================== Render =================================== 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 => { React.useEffect(() => { if (props.direction) { message.config({ rtl: props.direction === 'rtl', }); notification.config({ rtl: props.direction === 'rtl', }); } }, [props.direction]); return ( {(_, __, legacyLocale) => ( {context => ( )} )} ); }; ConfigProvider.ConfigContext = ConfigContext; ConfigProvider.SizeContext = SizeContext; ConfigProvider.config = setGlobalConfig; export default ConfigProvider;